pwnable.kr-uaf WP

UAF(Use After Free)释放后重用,其实是一种指针未置空造成的漏洞。

首先介绍一下迷途指针的概念

在计算机编程领域中,迷途指针,或称悬空指针野指针,指的是不指向任何合法的对象的指针

当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称迷途指针。若操作系统将这部分已经释放的内存重新分配给另外一个进程,而原来的程序重新引用现在的迷途指针,则将产生无法预料的后果。因为此时迷途指针所指向的内存现在包含的已经完全是不同的数据。通常来说,若原来的程序继续往迷途指针所指向的内存地址写入数据,这些和原来程序不相关的数据将被损坏,进而导致不可预料的程序错误。这种类型的程序错误,不容易找到问题的原因,通常会导致存储器区块错误(Linux系统中)和一般保护错误(Windows系统中)。如果操作系统的内存分配器将已经被覆盖的数据区域再分配,就可能会影响系统的稳定性。

以上内容引用于维基百科。

然后看一下源码

#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,两个派生类ManWoman,他们继承了Human类Human类中有两个虚函数give_shell()introduce(),在Man类Woman类中重新定义了虚函数introduce ()

下面介绍一下虚函数的工作原理:

通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数地址。

例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本的地址。如果派生类定义了新的虚函数,则需要在对象中添加一个地址成员,只是表的大小不同而已。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hFwoZ3A0-1603176838810)(https://i.loli.net/2019/06/22/5d0e1401a50cb64018.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YKSY1Bem-1603176838812)(https://i.loli.net/2019/06/22/5d0e14061eb0e69979.png)]

调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的虚函数地址表。如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。

然后看一下main函数,main函数首先new了两个对象,然后通过指针指向他们。然后接受一个操作码,根据操作码进行操作,各个操作的含义如下

  1. 调用
  2. 分配内存
  3. 释放内存

本题的大概思路就是通过3先释放内存,因为程序释放内存后没有将指针置空,所以指针仍然指向原来的内存。如果释放内存后又去申请相同大小的内存,操作系统会将刚刚释放掉的内存再次分配,我们对其进行精心构造后,再调用指针就可以让填充的数据使eip发生跳转。

uaf2.png

可以看到给对象分配了24字节的空间,下面Man::Man()是Man的构造函数,跟进去看看。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OVh7ZXn0-1603176838816)(https://i.loli.net/2019/06/22/5d0e20bef296862012.png)]

*a1为申请的内存空间的指针,可以看到将a1所指的前8个字节的空间赋值为off_401570,跟进去看看,发现这是指向虚函数表的指针。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yHenpJZk-1603176838817)(https://i.loli.net/2019/06/22/5d0e217c56e4a93227.png)]

可以看到Man的虚函数表,记录Human::give_shell()的地址和记录Man::introduce()的地址相差8个字节,我们每次调用Man::introduce()都会用off_401570的值+8去寻找introduce()函数,我们只要将off_401570的值-8写入到off_401570的位置,下次调用Man::introduce()的时候就会调用Human::give_shell()了。

至于怎么写呢,看源码中case2,将argv[1]转换为整数,去申请argv[1]大小的内存空间,从argv[2]指定的文件中读取argv[1]大小的数据到data中。通过上面的分析我们知道argv[1]应该为24,然后我们将off_401570的值-8也就是0x401568写入到一个文件,将文件目录作为arg[2]

然后3–>2–>2–>1,就能获取shell了,为什么是两次2呢?这里需要注意下释放的顺序是先释放的m后释放的w,因此在分配内存的时候优先分配到后释放的w,因此需要先申请一次空间,将w分配出去,再开一次,就能分配到m了。通过一次UAF只能修改w指向的内存空间,而在引用的时候却先引用了m指向的内存空间。这时m指向的内存空间已经被释放,会造成段错误。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tmsSwkPa-1603176838818)(https://i.loli.net/2019/06/22/5d0e2abe73aa258919.png)]

最后需要注意的是这是个64位的程序,所以不要写成32位的地址格式。

如果觉得我写的不是很清楚,可以参考一下逢魔安全实验室的WP

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值