Contest Source: COMP6[84]41 CTF
Note that all flags have been replaced with “COMP6841{REDACTED}”. This is to discourage you from just blindly submitting the final answer, and to encourage you to follow along and learn something along the way.
We are given a zip file containing two scripts: alice
and bob
. Running each of them individually gives:
$ ./alice
> Connecting to Bob...
Failed to connect() to bob
$ ./bob
> Setting up socket to listen for alice
> Waiting for alice...
This appears to be some sort of server-client setup. Let’s open a new terminal, and run them both side by side. Here are the outputs of both terminals:
$ ./alice
> Connecting to Bob...
> Waiting for key...
> Encrypting message with Bob's key...
> Sending encrypted message...
> Bye!
$ ./bob
> Setting up socket to listen for alice
> Waiting for alice...
> Sending key to alice...
> Waiting for secret from alice...
> Bye!
It appears that this is the order of events:
It’s also clear that both scripts connect via localhost, since it would have been impossible to know my computers IP address when the challenge makers were making the challenge.
As the title suggests, we are to play the role of Eve, the eve-dropper. We’d like to have a script in the middle, which takes input from Bob and passes it on to Alice and vice versa, but not before taking a peek at the bytes that are being passed through.
The first challenge we have to solve is what port is Bob listening on? We can solve this problem by running netstat -ap
, which gives us a list of processes and the ports they are listening on. Running this, we get:
tcp 0 0 127.0.0.1:31337 0.0.0.0:* LISTEN 1000 943147 155281/./bob
This means that Bob is listening on port 31337. Nice!
Next, let’s make a fake Alice script that will connect to Bob, and see what key Bob sends them. Our script looks like this:
from pwn import *
= remote("127.0.0.1", 31337)
conn
= conn.recv()
ret print(ret)
print("Sending encrypted message!")
b"hello this is a totally valid encrypted message!")
conn.sendline(b"hello this is a totally valid encrypted message!")
conn.sendline(b"hello this is a totally valid encrypted message!")
conn.sendline( conn.close()
$ p3 client.py
[+] Opening connection to 127.0.0.1 on port 31337: Done
b'y\x81L}?\x80]\xd9u\xf99\x8b\x96TiI'
Sending key!
Traceback (most recent call last):
File "/home/ubuntu/.local/lib/python3.8/site-packages/pwnlib/tubes/sock.py", line 65, in send_raw
self.sock.sendall(data)
BrokenPipeError: [Errno 32] Broken pipe
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "client.py", line 11, in
conn.sendline(b"hello this is a totally valid secret!")
File "/home/ubuntu/.local/lib/python3.8/site-packages/pwnlib/tubes/tube.py", line 798, in sendline
self.send(line + self.newline)
File "/home/ubuntu/.local/lib/python3.8/site-packages/pwnlib/tubes/tube.py", line 777, in send
self.send_raw(data)
File "/home/ubuntu/.local/lib/python3.8/site-packages/pwnlib/tubes/sock.py", line 70, in send_raw
raise EOFError
EOFError
[*] Closed connection to 127.0.0.1 port 31337
Our program crashes because we don’t handle the rest of the shutdown properly. However, we got what we wanted: The key that Bob sent us is b'y\x81L}?\x80]\xd9u\xf99\x8b\x96TiI'
. It’s worth noting that each time we run this script, the key that Bob sends us is different.
Anyway, let’s try and find out what Alice would typically send back if we were to really give her this key. Hence, we write a similar script, this time from the perspective of Bob.
from pwn import *
= listen(port=31337, fam='ipv4', typ='tcp')
l = l.wait_for_connection()
c
= b'y\x81L}?\x80]\xd9u\xf99\x8b\x96TiI'
msg
c.sendline(msg)
= c.recv()
ret print(ret)
c.close()
Running this as well as Alice’s script, we get:
$ p3 server.py
[+] Trying to bind to 0.0.0.0 on port 31337: Done
[+] Waiting for connections on 0.0.0.0:31337: Got connection from 127.0.0.1 on port 34108
b'\x92.\x1e9\xf4\xfb}VE\xf5\x8b\xf6\xec\xa9j\xb0\xb0\xea\x9cFq\xba\xab\xa2%\xa2\x93(\x19\x89\x95\x16s\x12V|v\xad\x9d\xd7\x92\xf8\xaa\xf8loc\xc3'
[*] Closed connection to 127.0.0.1 port 34108
In other words, we got b'\x92.\x1e9\xf4\xfb}VE\xf5\x8b\xf6\xec\xa9j\xb0\xb0\xea\x9cFq\xba\xab\xa2%\xa2\x93(\x19\x89\x95\x16s\x12V|v\xad\x9d\xd7\x92\xf8\xaa\xf8loc\xc3'
as the encrypted message.
Unfortunately, we are still no closer to solving the problem. All we have is a seemingly random key, as well as a encrypted flag. However, our efforts have given us a better understanding of the protocol involved. Next up, let’s try using Ghidra to reverse engineer our code.
… at least, we’d like to. Unfortunately, Ghidra cannot seem to guess a compiler, and hence can’t decompile our code. In fact, trying to run GDB on this file fails too:
$ gdb alice
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
"/home/ubuntu/tutor_ctf/alice_and_bob/test/alice": not in executable format: file format not recognized
(gdb) run
Starting program:
No executable file specified.
Use the "file" or "exec-file" command.
(gdb)
It doesn’t even seem to be able to identify that Alice is an executable file! Running readelf -h
on Alice and Bob gives a similar error message:
$ readelf -h alice
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4010de
Start of program headers: 0 (bytes into file)
Start of section headers: 64 (bytes into file)
Flags: 0x0
Size of this header: 0 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 0 (bytes)
Number of section headers: 0
Section header string table index: 0
readelf: Warning: possibly corrupt ELF file header - it has a non-zero section header offset, but no section headers
Just like some of our previous exercises (e.g.shimmy), it seems like the headers are corrupt. For reference, here’s how a regular header (./vuln
from bad_rand) looks like:
$ xxd ./vuln | head -2
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0300 3e00 0100 0000 d010 0000 0000 0000 ..>.............
Comparing the two, we see that vuln
has 0x02
in the 5th byte, instead of a 0x01
. Checking the Linux Foundation ELF Header Specs, this byte is called e_type
and defines the object file type. 0x01
means a “Relocatable file”, whereas 0x02
represents a “Executable file”. We can use a hexeditor to make the change, and now both Ghidra and GDB work again!
Opening up Alice in Ghidra, and cleaning it up a little bit, we get the following:
Hence, we see that at one point in lines 40-49, the flag is loaded in memory. It’s possible to reverse engineer the encrypt function, and then also control the key that Bob sends in order to extract out the flag from Alice. However, the easiest method would be just to open up Alice in GDB and inspect the memory. And sure enough, after stepping through and printing out the stack with x/5s %sp
, we’re able to get the flag.