Note: I did not solve this challenge until after the CTF had officially ended.
File shows that bd, is a stripped binary which can make reversing a bit harder.
$ file bd
bd: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=1da3a1d77c7109ce6444919f4a15e7e6c63d02fa, stripped
Nothing relevant shows up in strings though it’s interesting to note the size of the binary.
Once opened in Ghidra, an interesting section can be seen in the program tree.
Pydata is a section that indicates this binary is actually a “frozen” python script that has been packaged with all needed dependencies inside an ELF file. This makes it fairly portable and also accounts for the size of the binary.
The next step is to extract the python from this executable. After that, it should be readable like any other python script. First, objcopy is used to extract pydata.
nightwolf@archlinux ~/CTF/cyber-apocalypse/ % objcopy --dump-section pydata=pydat.out bd
This can then be feed to pydecipher
to decompress the actual python code.
nightwolf@archlinux ~/CTF/cyber-apocalypse/ % ~/.local/bin/pydecipher -d -v bd
An output folder is created that contains all the decompressed data.
The majority of this is dependencies db.py
is is the most interesting. It contains an simple socket implementation that listens for connections providing the md5 sum of a specific string and then executing commands that follow.
Note: the local version of the script as show isn’t quiet right. All the strings need to be typed as bytes but weren’t by the extractor.
With this info, a script can be created that opens a connection with the remote target, sends s4v3_th3_w0rld
, and then executes commands to retrieve the flag.
#!/usr/bin/python3
import socket
import readline
from hashlib import md5
server = "127.0.0.1"
port = 4433
password = b"s4v3_th3_w0rld"
while True:
command = input(">")
if command == "exit":
print("Exiting...")
exit()
data = ( " command:" + command)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.sendall((md5(password).hexdigest() + " command:" + data).encode())
print(s.recv(4096).decode())
s.close()
It’s important to remember the server will only receive 32 bytes of data. Anything sent beyond that will be cut off and likely cause an error.