PWN——uaf漏洞

1.uaf漏洞原理

  在C语言中,我们通过malloc族函数进行堆块的分配,用free()函数进行堆块的释放。在释放堆块的过程中,如果没有将释放的堆块置空,这时候,就有可能出现use after free的情况。这里我写了一个demo

#include<stdio.h>
#include<stdlib.h>
typedef struct demo
{
char *s;
void(*func)(char *);
}DEMO;
void eval(char command[])
{
system(command);
}
void echo(char content[])
{
printf("%s",content);
}
int main()
{
DEMO*p1;
DEMO*p2;
p1=(DEMO*)malloc(sizeof(struct demo));
p1->s="I will tell you what's uaf.";
printf("%s\n",p1->s);
printf("p1 malloc address:%p\n",p1);
p1->func=echo;
p1->func("use after free!\n"); free(p1);
p1->s="~Heihei~";
printf("%s\n",p1->s);
p2=(DEMO*)malloc(sizeof(struct demo));
p2->func=eval;
// p1->func=eval;
printf("p2 malloc address:%p\n",p2);
p2->func("whoami"); p1=NULL;
printf("Now,I can't use address of p1.");
p2->func("whoami");
return 0;
}

运行的结果如下图所示  

  可以看出,在free()堆块之后,没有将堆块置空,堆块处于悬空状态,导致被free掉的堆块依然可以被使用。同时,如果我们申请相同大小的堆块的话,由于ptmalloc的堆管理机制,重新分配的堆块的位置和我们释放的堆块地址是一样的。但是,在将p1置空之后,再次使用堆块的时候,就会报出段错误。

  其实,一般的uaf漏洞利用,和我们上图的demo也是类似的,都是定义一个结构体,结构体中有一个函数指针,然后free,将chunk添加进fastbin,再次分配相同大小的内存块,这时候分配的就是刚才free掉悬空的堆块,然后改写函数指针,劫持数据流,配合不同的函数指针,可以实现任意地址读,任意地址写,以及命令执行。

注:这里第二次分配内存的时候,我们一般分配相同数据类型。比如上面的例子中,第一个释放的堆块p1的是DEMO类型的结构体,第二次分配的时候,我们们分配的p2也是一个DEMO类型的结构体,这种情况下,p2一定用的是之前p1的内存。但是在有些情况下,如果我们分配的数据类型不同,但是前后分配大小相同,也可能造成堆块的再次利用。

2.例题

这道例题,是pwnable.kr上的uaf这道题目,源码如下。

#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;
}

  之前没看过C++,花了点时间把C++的类与面向对象的一些知识学习了一下。

·   先看主函数,主函数中实现了分配堆块,释放堆块,以及从读入数据的功能。然后再看定义的几个类,Human是定义的一个基类,Man和Woman是定义的派生类,Human中public和protected定义的函数和变量可以在后面被子类继承和访问。但是定义的私有的give_shell虚函数,我们是无法在外部调用的,也无法通过子类来访问。只能通过Human类中定义的函数来调用。

我们这里需要一些预备知识:C++的类中有序函数的时候,会生成一个虚表vtable,编译器会生成一个vptr指针指向vtable(不管基类中有多少虚函数,只有一个_vptr指针指向vtable;多态继承时,继承了多个父类,相应就会又多个vtable)。对于共有成员变量来说,虚函数可以被子类同名函数重新调用,子类中有同名函数的时候,子类的vatble中指向基类虚函数的函数指针变为指向子类同名函数。对于私有成员来说,他的函数指针会保留在子类的vtable中(但是子类不是继承私有成员)。所以对于上面的源码来讲,Man类和Woman类的vtable中,introduce函数指针指不同,但是give_shell函数指针是相同的。我们可以再深入思考一下:vtable保留在哪里?通过IDA我们可以看出,vtable是保留在rodata段的。vtable保留着类的虚函数指针,它应该在编译前就完成,相应的它肯定不在代码段,bss段和data段分别保留的是未初始化和初始化后的局部变量,全局变量以及静态变量,所以vtable也不会在bss段或者data段。这样看来,vtable和字符串常量一样,保留在全局数据段是合理的。

这几张图片是从sakura师傅的博客取下来的,这里贴出来,大家可以看一下

       

  对于这道题而言,类比C程序中利用的步骤,我们应改首先创建堆块,然后通过case3释放堆块,但使堆指针悬空,最后通过case2,读入文件内容,覆写类的函数指针,劫持数据流。

  打开IDA,main函数的伪代码中可以得知,Man和Woman对象分配的堆的大小为0x18个字节。

  我们首先通过调试,找到相应的虚表函数,要找到虚表函数,就要先找到Man的构造函数。

找到Man的构造函数之后进入,会发现调用Human的构造函数(因为Man作为子类要继承父类),所以这时候继续单步,可以在寄存器窗口看见调用give_shell函数的地址

找vtable的话,进入IDA,到rodata段。

Man类vtable表,give_shell的函数指针的地址为0x401570。现在要具体关注一下堆块内部的内存布局,以便在后面有效地覆盖函数指针。

0x40117a是give_shell函数地址,0x4012d2是introduce函数地址。

按照C++实例化类的内存分配的规则,类中有虚函数的时候,分配的对象的内存数据中,前8个字节是_vptr指针,这个指针指向vtable表,以后是成员变量的内存数据(这里应该只是成员变量的内存数据,不包括成员函数,所有函数都是定义在text段的)。

vptr指向的地址,是vtable表的地址,vtable表上存储的是虚函数的地址。

case2的时候填充的数据就应该是把前8个字节修改

case1调用intreduce函数的代码如下

v13和v14应该分别是Man对象和Woman对象的vptr指针,前面看到过,give_shell函数地址在0x401570地址处,我们向文件中写入0x401570-8的地址。

这里要注意的是,再次分配地址的时候,由于最后delete的是Woman对象的地址,第一次写入的是之前Woman的地址,第二次才是写入到Man对象地址的vtable表上。

终于是写完了这篇博客。。。

C++和堆的很多知识我绝对理解的不是很到位,是要不断学习的,但是这道题是看着博客,跌跌撞撞做出来了。写出来是为了加深印象,如果哪里有说的不对的地方,还希望路过的师傅们能过及时指出来,谢过师傅们了。

PWN——uaf漏洞学习的更多相关文章

  1. PWN二进制漏洞学习指南

    目录 PWN二进制漏洞学习指南 前言 前置技能 PWN概念 概述 发音 术语 PWN环境搭建 PWN知识学习途径 常见漏洞 安全机制 PWN技巧 PWN相关资源博客 Pwn菜鸡小分队 PWN二进制漏洞 ...

  2. UAF漏洞学习

    产生原因: UAF漏洞的成因是一块堆内存被释放了之后又被使用.又被使用指的是:指针存在(悬垂指针被引用).这个引用的结果是不可预测的,因为不知道会发生什么.由于大多数的堆内存其实都是C++对象,所以利 ...

  3. Hitcon 2016 Pwn赛题学习

    PS:这是我很久以前写的,大概是去年刚结束Hitcon2016时写的.写完之后就丢在硬盘里没管了,最近翻出来才想起来写过这个,索性发出来 0x0 前言 Hitcon个人感觉是高质量的比赛,相比国内的C ...

  4. exim CVE-2017-16943 uaf漏洞分析

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这是最近爆出来的 exim 的一个 uaf 漏洞,可以进行远程代码 ...

  5. 【pwn】学pwn日记——栈学习(持续更新)

    [pwn]学pwn日记--栈学习(持续更新) 前言 从8.2开始系统性学习pwn,在此之前,学习了部分汇编指令以及32位c语言程序的堆栈图及函数调用. 学习视频链接:XMCVE 2020 CTF Pw ...

  6. [pwn基础]Pwntools学习

    目录 [pwn基础]Pwntools学习 Pwntools介绍 Pwntools安装 Pwntools常用模块和函数 pwnlib.tubes模块学习 tubes.process pwnlib.con ...

  7. XSS漏洞学习笔记

    XSS漏洞学习 简介 xss漏洞,英文名为cross site scripting. xss最大的特点就是能注入恶意的代码到用户浏览器的网页上,从而达到劫持用户会话的目的. 说白了就是想尽办法让你加载 ...

  8. Typecho-反序列化漏洞学习

    目录 Typecho-反序列化漏洞学习 0x00 前言 0x01 分析过程 0x02 调试 0x03 总结 0xFF 参考 Typecho-反序列化漏洞学习 0x00 前言 补丁: https://g ...

  9. XXE漏洞学习笔记

    XXE 参考文章 名称 地址 一篇文章带你深入理解漏洞之 XXE 漏洞 https://xz.aliyun.com/t/3357 Web Hacking 101 https://wizardforce ...

随机推荐

  1. kubeadm部署高可用版Kubernetes1.21[更新]

    环境规划 主机名 IP地址 说明 k8s-master01 ~ 03 192.168.3.81 ~ 83 master节点 * 3 k8s-master-lb 192.168.3.200 keepal ...

  2. LeSS 的诞生(一):大规模团队该何去何从

    <敏捷宣言>发布后,"敏捷"被越来越多的小型开发团队认可.与此同时,另一个问题也逐渐暴露了出来:以 Scrum 为首的敏捷方法论对那些大规模的开发团队并不友好. 基于此 ...

  3. JAVA 类修饰符

    JAVA类的修饰符主要有public,default,protected,private,final,abstract,static 其中外部类中用到的只有public,final,abstract或 ...

  4. HTTP:Java实现HTTP请求的三种方式

    目前JAVA实现HTTP请求的方法用的最多的有两种: 一种是通过HTTPClient这种第三方的开源框架去实现.HTTPClient对HTTP的封装性比较不错,通过它基本上能够满足我们大部分的需求,H ...

  5. Quartz和Spring Task定时任务的简单应用和比较

    看了两个项目,一个用的是Quartz写的定时器,一个是使用spring的task写的,网上看了2篇文章,写的比较清楚,这里做一下留存 链接一.菠萝大象:http://www.blogjava.net/ ...

  6. buu 刮开有奖

    一.查壳, 二.拖入ida,分析 直接搜字符串完全没头绪,在看了大佬的wp才找到了,关键函数. 明显那个String就是我们要求的flag,要开始分析程序. 字符串长度为8,同时这个函数对字符串进行了 ...

  7. NSDate小结

    dateFormat用法: y - 年 2013年,yyyy=2013,yy=13 M - 月 3月,M=3,MM=03,MMM=Mar,MMMM=March D - 一年中的第几天 d - 一月中的 ...

  8. 42. Trapping Rain Water [dp][stack]

    description: Given n non-negative integers representing an elevation map where the width of each bar ...

  9. ELK处理Spring Boot 日志,妙!

    在排查线上异常的过程中,查询日志总是必不可缺的一部分.现今大多采用的微服务架构,日志被分散在不同的机器上,使得日志的查询变得异常困难. 工欲善其事,必先利其器.如果此时有一个统一的实时日志分析平台,那 ...

  10. JS请求节流

    少废话,撸代码.欧耶! 1.节流器 // 对函数进行 节流 function throttle (fn, interval = 500) { let timer = null; let firstTi ...