pwnable.kr题解之uaf

前言:
这道题目反应了uaf的基本原理,pwn入门必做的题目,如果这一块了解的不够透彻,直接去打现在涉及各种奇技淫巧的pwn,肯定会被绕晕掉。所以为了照顾萌新(其实是我自己菜)把这道题目单独拿出来写一下。

这是一道uaf的题目,把二进制文件拉到本地来研究
在这里插入图片描述
在分析前,先简单说一下c++的虚函数和uaf的前置知识
在c++中,如果类中有虚函数,那么这个类就会有一个虚函数表的指针vfptr,而子类会继承。
在这里插入图片描述
uaf的原理:
在释放内存后未将指向原内存的指针置为null,use after free的意思就是在释放以后进行use。
举个简单的例子:
在这里插入图片描述
原来的p指针指向一个结构体,当结构体没释放之后没有将p置为null,如果我们重新分配原结构体大小的空间,则指针p可以继续使用。但是再次使用时,因为p指针指向的内存包含的数据不是原来的数据了,此时的引用对于正常程序来说是有风险的,不过对于黑客来说,反而方便其进行控制。比如可以进行这些攻击:
任意地址读:puts(p->name)—————>puts(char*(addr2))
任意地址写:strcpy(p->name,data);——>strcpy((char *)(addr2),data)
控制流劫持:p->func()———————>call addr3

这次的uaf题目基本相当于任意地址写
先看源码
uaf@pwnable:~$ cat uaf.cpp
#include <fcntl.h>
#include
#include
#include
#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和woman两个子类继承自human父类
human父类存在虚函数,会创建有虚表指针指向的一个虚表。Man和woman子类会继承,子类的虚表中会继承父类的所有项(并且当子类存在同名虚函数时,会修改vtable表项,指向自己的函数的地址。如果父类有私有函数,但是这个私有函数是虚函数,那么子类的vtable中同样会有这个函数的表项。每个虚表只有一个vptr,就算有多个虚函数也一样。但是当多重继承的时候,就会有多个vptr。

再看main
在这里插入图片描述
可以知道程序运行后供我们输入
在这里插入图片描述
1会分配内存,2会写内存,3会释放内存。
根据uaf的原理,程序运行后会自动分配内存,我们需要先释放内存,然后将exp写入data,这样当输入1时就会被我们劫持了。
这里需要注意几点:
1.输入2,也就是case2时,需要确定要分配多少内存给我们写,我们知道原程序申请了int age为4字节,string name为16字节,加上一个虚函数指针4字节,共24字节。
2.这里程序自动申请分配给了man,woman,以24字节为单位。而case3是先delete m,在delete w,所以我们这里需要分配两个24字节的内存,即按两次2才能得到m所指向的空间
3.case2需要制定24字节的长度,以及要从哪个文件中读内容来覆盖原先分配的空间,这个文件可以随意指定,关键是文件的内容是什么,这就是我们接下来要研究的地方

注意到在我们use after free的use步骤,也就是输入case1的时候,按照程序逻辑而言执行的是introduce
其实这里调用的是父类human::introduce,而我们想要的是giveshell。
由前面虚函数的知识我们可以知道,这两个虚函数是在一张表上的,那么我们只要在调用human::introduce之前将其地址改为giveshell的,这样在输入case1的时候就可以拿到shell了。
虚表里面一共就两项,第一项是giveshell,第二项是introduce,关键就是找到两者间的偏移,以及虚表指针
在这里插入图片描述
可以看到introduce和giveshell差了8
在这里插入图片描述
上图是case1的汇编
可以看到执行了add rax,8后会执行introduce,那么我们给rax的值-8,这样执行了该指令后就会执行giveshell
虚表原地址我们知道是0x401570
所以我们现在要把它覆盖成0x401570-8=0x401568
也就是说我们case2,在分配24字节,写0x401568来覆盖原内容,根据内存布局,其实就是相当于覆盖了虚表指针vfptr

所以pwn的步骤就很简单了,如图所示
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值