相关知识
c++虚函数:https://blog.csdn.net/haoel/article/details/1948051
uaf(Use After Free):https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/use_after_free/
参考文章:https://blog.csdn.net/qq_20307987/article/details/51511230
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;
}
new和delete、malloc和free,https://www.cnblogs.com/maluning/p/7944231.html
这里的delete相当于free,因为delete之后没有把m或者w指针置零,所以存在UAF漏洞。
先delete初始的m和w,然后申请一个24字节的空间,这时候系统给你的空间是原本w的空间,这时候你再申请一个24字节的空间,系统给你的是原本m,因为当一个分配出去的内存空间delete或者free后(系统会把这个内存空间存放在一个链表中,这个链表的每个节点记录着一个之前释放的内存空间的头地址和空间大小,每当有一个新的内存空间被释放时,就会在链表末产生一个新的结点,当程序要申请一个新的内存空间时,系统会优先从这个链表中匹配一个大小合适的内存空间,从链表末开始匹配。实际中的堆分配更加复杂,这里只是简单地概述一下其中一种堆分配的方法)。
要申请两次空间,因为第一次申请到的是w,这时候就执行use的话就会先读取m,因为m已经delete了,所以读到的是空地址,程序就会出错了。
说完内存空间的分配,接下来说说c++虚数表。
在c++中,类的数据空间里第一个就是指向虚数表的指针_vfptr,在我们写入的时候就可以把虚表指针内的地址值覆盖
可以看到在三个虚数表中,每个表都存放着give_shell
看ida伪代码,这里的v13、v14就是_vfptr,当函数读取introduce时,其实是读取_vfptr+8,_vfptr则指向虚数表的第一个函数。
在类实例化分配的空间中,开始就存储着虚数表指针,所以我们通过分配到原本m和w的空间,然后再输入一个地址覆盖原本的虚数表指针,就可以让系统执行我们想要的地址+8的程序。
最后
其中要进行覆盖的地址值可以有三种值,因为在ida查看虚数表的时候可以看到有三个地址都存放着give_shell函数的地址。