UAF原理与利用

0x00 UAF原理

 技术分享

如上代码所示,指针p1申请内存,打印其地址,值

然后释放p1

指针p2申请同样大小的内存,打印p2的地址,p1指针指向的值

Gcc编译,运行结果如下:

 技术分享

p1与p2地址相同,p1指针释放后,p2申请相同的大小的内存,操作系统会将之前给p1的地址分配给p2,修改p2的值,p1也被修改了。

由此我们可以知道:

1.在free一块内存后,接着申请大小相同的一块内存,操作系统会将刚刚free掉的内存再次分配。

根本原因是dllmalloc:

参考资料:http://blog.youkuaiyun.com/ycnian/article/details/12971863

当应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。

 

2.通过p2能够操作p1,如果之后p1继续被使用(use after free),则可以达到通过p2修改程序功能等目的。

 

0x01 一个利用场景

在网上找了一个有UAF漏洞的ctf程序,参考博客如下:

http://www.syjzwjj.com/use-after-free-tutorial/

作者已经分析的很详细了,通过对整个程序的调试,来理解UAF的利用。

1.       结合IDA与程序运行,简要理解程序的功能。

 技术分享

选择1可以留言,信息会存到1个链表中

选择2将会遍历链表找到对应的节点,打印节点信息

打印完,可以对其进行删除修改等操作

 技术分享

链表节点结构如下

 技术分享

Tips:在逆向代码中应用数据结构参考《IDA Pro权威指南》第8章数据类型与数据结构

 2.       UAF漏洞代码

 技术分享

链表节点被删除后,可以继续进入modify函数,modify函数之后可以继续进入modify函数。

Delete函数如下:

 技术分享

Delete函数中对节点的进行了free操作,如果在循环代码中,进行delete操作,释放节点之后,再选择2进入modify函数。

Modify函数如下:

 技术分享

Modify函数从用户读取数据,然后拷贝到对应的指针中,但此时使用的是一个已经释放的指针。当输入content时,会取content的长度作为大小分配内存,当分配内存大小等于msg结构大小(48字节,通过前面的结构获得)时,会将刚才释放的内存分配给content指针。

如下所示

 

技术分享

Content指向了msg结构本身

 技术分享

接着将content拷贝到content指针中,即我们的输入会拷贝到这个被释放的节点内存中。

 

在循环代码中,modify完之后可以继续进入modify。 此时会再对msg结构的author,title,content指针指向的地址进行拷贝。 由于上一步已经能够对msg结构进行随意更改了,所以将几个memcpy的目的地址修改成想要的地址即可进行任意内存(属于该程序的合法内存)的修改了。

 技术分享

至此我们已经能够完成任意内存地址的修改了。

下面需要考虑的就是完成一些命令执行的利用。

3.       漏洞利用执行命令

要执行命令,需要调用system函数,但是代码中并没有system函数,需要如何完成命令执行呢?

可以利用linux的延迟加载功能,改变strlen函数的指向,将原本要执行的strlen,改成执行system。

Tips:延迟加载

当调用标准函数时,需要从其他so文件中将标准函数加载进来,并不直接调用函数的地址,而是通过一张中间表跳转到函数的真正地址。

以strlen函数的调用为例

 技术分享技术分享

 技术分享

在程序调试中,打印0x804c04c的信息

 技术分享

整个过程如下:

Call strlen跳转到strlen函数,里面只有一句jmp ds:off_804c04c

当程序运行起来时0x804c04c里的值为0xb7658210,才是strlen的真正地址

即0x804c04c中存储libc库中的strlen的真正地址。

 

如果将0x804c04c的值改掉,改成system的地址0xb7614360。 虽然看起来调用的是strlen,但真正执行的是system函数。

修改前:

Call strlen  

                    Strlen:

                            Jmp 0x804c04c

                                                         0x804c04c: 0xb7658210(strlen)

 

修改后:

Call strlen  

                    Strlen:

                            Jmp 0x804c04c

                                                         0x804c04c: 0xb7614360 (system)

 技术分享

Tips: 寻址system的真正地址

由于整个程序并没有调用system函数,所以在程序的重定位表中找不到system。 所以需要自己定位一下system在这个程序中的真正地址。

 技术分享

Libc被装到0xb75d6000-0xb777a000 地址空间,大小为0x1a4000

编写程序调用system函数

 技术分享

调试运行查看其libc地址空间

 技术分享

被装入到0xb7e10000-0xb7fb4000,大小也为0x1a4000。

所以system在漏洞程序中的地址应为 =(system在调用程序中的地址-调用程序libc起始地址+漏洞程序libc起始地址)

 技术分享

0xb7e4e360

System在漏洞程序中地址= 0xb7e4e360-0xb7e10000+0xb75d6000= 0xb7614360

 4.       poc运行效果

执行一个mkdir hack命令建立一个hack目录

Poc片段

 技术分享

技术分享

技术分享

5.       过程回顾

1)  delete函数中释放节点

2)  modify函数传入被释放的指针

3)  modify函数中分配内存大小可控,通过分配与节点相同的大小,取得被释放内存的控制权

4)  修改将要被拷贝的目的地址msg->author指针指向将要被执行的函数strlen的中间表地址

5)  将strlen指向的真实strlen地址,修改为system的真实地址

6)  看似执行call strlen,实则执行了system函数

 

0x02 总结

在指针释放后再申请相同大小的内存,系统会将释放的地址进行分配,以提高系统运行速度,因此可以修改到被释放的内存数据,如果被释放的指针继续被使用,则会造成UAF漏洞。

通过UAF漏洞,可能可以造成一些任意内存的修改,结合代码特点,可能会造成任意内存的读取或者,严重的能够造成任意命令的执行,获得shell。 取决于被释放的指针是怎么使用的。

相关知识点:gdb调试(如何调试fork出来的程序),延迟加载(plt与got),标准函数在内存中的定位,UAF修改被释放指针内容的原因dllmalloc

<think>UAF(Use-After-Free)漏洞是一种内存安全漏洞,指的是在程序释放了某个内存块后,仍然继续使用指向该内存块的指针。攻击者可以通过精心设计的内存操作,在释放的内存中填入恶意数据,从而在程序使用已释放指针时控制程序执行流程。 下面是一个简单的UAF漏洞利用示例,使用C语言编写。这个示例程序存在UAF漏洞,我们将通过这个漏洞执行一个简单的攻击:修改虚函数表指针,使其指向我们构造的虚假虚函数表,从而控制程序流。 注意:这个示例仅用于教育目的,实际漏洞利用需要根据具体环境调整。 ### 漏洞程序 ```c #include <iostream> #include <cstring> #include <cstdlib> class Base { public: virtual void show() { std::cout << "Base::show()" << std::endl; } virtual ~Base() {} }; class Derived : public Base { public: void show() override { std::cout << "Derived::show()" << std::endl; } }; int main() { Base *a = new Derived(); a->show(); // 正常调用 delete a; // 释放内存 // 分配一个Base大小相同的对象,尝试覆盖被释放的内存 char *b = new char[sizeof(uintptr_t) * 2]; // 通常虚函数表指针占用一个指针大小,但为了覆盖整个对象,我们分配足够空间 uintptr_t *vtable_ptr = reinterpret_cast<uintptr_t*>(b); // 构造一个假的虚函数表指针,指向我们控制的地址(这里假设我们有一个恶意函数地址) // 注意:实际中我们需要知道一个可执行的地址,这里用0x41414141模拟 vtable_ptr[0] = 0x41414141; // 覆盖虚函数表指针 // 使用已释放的指针 a->show(); // UAF发生,调用虚函数时,会跳转到0x41414141 delete[] b; return 0; } ``` ### 漏洞利用说明 1. 程序创建了一个`Derived`对象,并通过基类指针`a`指向它。 2. 然后释放了`a`指向的内存。 3. 接着分配了一个字符数组`b`,大小`Base`对象相同(通常包含一个指向虚函数表的指针)。在释放的内存上重新分配,这样`b`就覆盖了原来`a`指向的内存。 4. 在`b`中,我们构造了一个假的虚函数表指针,将其指向一个我们控制的地址(这里用`0x41414141`模拟,实际中需要替换为有效的函数指针)。 5. 当再次通过`a`调用虚函数`show()`时,程序会去假的虚函数表中查找函数地址,并跳转到`0x41414141`执行,从而控制程序流程。 ### 漏洞利用的关键点 - 需要知道释放后重新分配的内存大小,确保分配相同大小的内存块。 - 需要控制重新分配的内存内容,写入伪造的虚函数表指针。 - 需要知道一个可执行的地址(例如,攻击者注入的shellcode地址),这里用0x41414141模拟,实际运行会导致崩溃,但证明控制流被劫持。 ### 实际利用中的挑战 - 现代操作系统有ASLR(地址空间布局随机化),使得地址难以预测。 - DEP(数据执行保护)使得注入的代码无法执行。 - 堆分配器的行为可能复杂(例如,不同大小的分配可能使用不同的缓存)。 ### 缓解措施 - 使用智能指针(如`std::shared_ptr`,`std::unique_ptr`)自动管理内存。 - 释放指针后立即置空,避免悬垂指针。 - 使用内存安全语言(如Rust)或内存安全工具(如AddressSanitizer)检测UAF。 ### 相关安全技术 - 利用UAF通常需要结合堆喷(Heap Spraying)来在内存中布置大量恶意数据,增加跳转地址命中的概率。 - 结合信息泄露绕过ASLR:先泄露某个对象的地址,计算出基地址,再构造虚假虚函数表。 ### 注意事项 上述代码在编译时可能会因为现代编译器的保护(如虚函数表保护)而无法直接触发,且运行时会崩溃。但在没有保护的环境中(如关闭安全机制),可以演示UAF的基本原理。 ### 编译运行 在Linux下编译运行(关闭DEP,但ASLR可能仍然存在): ```bash g++ -fno-stack-protector -z execstack -o uaf uaf.cpp ``` ### 实际漏洞利用代码示例(概念性) 由于实际漏洞利用需要结合具体环境和目标,以下是一个更实际的利用步骤(伪代码): 1. 释放对象A。 2. 分配一个可控对象B,占据A释放的内存。 3. 在对象B中伪造虚函数表指针,指向一个恶意构造的虚函数表(其中包含恶意函数指针)。 4. 触发虚函数调用(如调用原对象的虚函数),跳转到恶意代码。 ### 防御建议 - 使用AddressSanitizer(ASan)检测UAF:编译时加上`-fsanitize=address`。 - 使用智能指针。 §§相关问题§§: 1. 如何检测UAF漏洞? 2. 现代操作系统有哪些安全机制可以缓解UAF漏洞的利用? 3. 在C++中,如何安全地管理内存以避免UAF? 4. 请解释堆喷(Heap Spraying)技术及其在漏洞利用中的作用。</think>### UAF漏洞利用代码示例 UAF(Use-After-Free)漏洞发生在程序释放内存后仍继续使用该内存指针。下面是一个经典的C++ UAF漏洞利用示例,通过虚函数表劫持实现代码执行: ```cpp #include <iostream> #include <cstring> class VulnerableClass { public: virtual void execute() { std::cout << "Legitimate function" << std::endl; } virtual ~VulnerableClass() {} }; class MaliciousClass { public: virtual void payload() { std::cout << "Malicious payload executed!" << std::endl; // 实际攻击中这里会执行shellcode } }; int main() { // 分配原始对象 VulnerableClass* obj = new VulnerableClass(); // 释放内存 delete obj; // UAF漏洞点:内存被释放但指针未置空 // 分配恶意对象(占用相同内存位置) size_t objSize = sizeof(VulnerableClass); char* fakeVtable = new char[objSize]; memset(fakeVtable, 0, objSize); // 构造恶意虚函数表 void (**maliciousVtable)() = new void(*[2])(); maliciousVtable[0] = reinterpret_cast<void(*)()>( static_cast<void (MaliciousClass::*)()>(&MaliciousClass::payload) ); // 将恶意虚表指针写入内存 *reinterpret_cast<void***>(fakeVtable) = reinterpret_cast<void**>(maliciousVtable); // 触发UAF - 调用已释放对象的虚函数 obj->execute(); // 实际执行MaliciousClass::payload() delete[] fakeVtable; delete[] maliciousVtable; return 0; } ``` ### 代码解释 1. **漏洞创建**: - 创建`VulnerableClass`对象并立即删除,但保留悬垂指针`obj` - 典型漏洞模式:释放后未置空指针 2. **内存占用**: - 分配相同大小的内存`fakeVtable`占据被释放的内存空间 - 使用`memset`清零确保内存控制 3. **虚表劫持**: ```cpp void (**maliciousVtable)() = new void(*[2])(); maliciousVtable[0] = reinterpret_cast<void(*)()>( static_cast<void (MaliciousClass::*)()>(&MaliciousClass::payload) ); ``` - 创建伪造虚函数表 - 将第一个虚表项指向恶意函数`payload()` - 使用双重转换绕过C++类型检查 4. **内存布局控制**: ```cpp *reinterpret_cast<void***>(fakeVtable) = reinterpret_cast<void**>(maliciousVtable); ``` - 在伪造对象头部写入恶意虚表指针 - 对象内存布局:`[vptr][成员变量...]` 5. **触发利用**: ```cpp obj->execute(); // 实际调用maliciousVtable[0] ``` - 通过悬垂指针调用虚函数 - CPU从虚表加载函数指针并执行恶意代码 ### 防护措施 1. **释放后置空**: ```cpp delete obj; obj = nullptr; // 防止UAF ``` 2. **智能指针**: ```cpp std::unique_ptr<VulnerableClass> obj = std::make_unique<VulnerableClass>(); ``` 3. **内存消毒工具**: ```bash # 使用AddressSanitizer编译 g++ -fsanitize=address -g uaf.cpp -o uaf ``` 4. **安全分配器**: - 使用jemalloc或tcmalloc增强堆安全性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值