For this challenge, we are provided with the following pcap file.
Looking at the data given, nothing really stands out so lets look at the time for each request. I extracted the time values directly using the command below:
tshark -r timingiseverything.pcap -T fields -e frame.time
tshark -r timingiseverything.pcap -T fields -e frame.time_relative
When we examine the output, the specific dates themselves don’t seem to reveal anything interesting.
Looking at the “Seconds Since First Captured Packet” time value, we see what could potentially be encoded data.
After messing around with the data in CyberChef, nothing was really standing out to me so I went back into Wireshark.
Here I saw the time difference between frames which could also contain hidden data.
Using the command below I extracted the time delta between requests.
tshark -r timingiseverything.pcap -T fields -e frame.time_delta
After doing the same thing in CyberChef, I realized that 83 and 73 are SI in ASCII. We know the flag format for these challenges is “SIVUSCG{}” so lets strip the decimal values from the time deltas, and format each value so it only grabs the first 3 decimals.
tshark -r timingiseverything.pcap -T fields -e frame.time_delta | cut -d. -f2 | cut -c1-3
We can then convert this into the ASCII using CyberChef.
For the next challenge, we are given a .vmem file. That means its Volatiltiy time.
Let’s load this up and see what we’re dealing with.
python3 vol.py -f TimeWillTell.vmem windows.info
First thing I like to do is list all the files. We can do so with the following command:
python3 vol.py -f TimeWillTell.vmem windows.filescan.FileScan > timewilltell.files
After looking for txt files and anything related to the title of the challenge, there’s a word doc that stands out. “7LayerCakeRecipe.docx”
Oh boy…
Let’s extract the file and see what we’re dealing with.
python3 vol.py -f TimeWillTell.vmem windows.dumpfiles.DumpFiles --virtaddr=0x8a8fe971e250
Looks like we get an error. Instead of a word document, volatility gave us a .vacb and .dat.
Trying to open the .vacb file in LibreOffice results in the following:
LibreOffice “repairs” the file and we now take a look at this recipe.
Along with a fun recipe for what looks like a delicious cake, we are provided with like 10 images of clocks with some messages scattered in between.
We see the images tell us that there's a filename on GitHub we need to find that I’m sure the clocks are conveying somehow.
Each clock only has a minute hand, and we are provided with 2E and 5A already.
The only thing I can think of is equate each value of the minute hand to a number.
Lets try grabbing the minute value from left to right, top to bottom, and see if this is some hidden hex value.
That doesn’t seem right, and as predicted no results on GitHub.
Next, I tried extracting the document.xml and reviewing that order.
Here I noticed there was a “descr” field next to each image.
Let’s put them in order based on this value.
1. Picture14 = 5a
2. Picture5 = 33
3. Picture4 = 45
4. Picture10 = 39
5. Picture9 = 52
6. Picture1 = 44
7. Picture7 = 30
8. Picture12 = 59
9. Picture2 = 41
10. Picture15 = 32
11. Picture13 = 34
12. Picture6 = 23
13. Picture3 = 43
14. Picture16 = '='
15. Picture8 = 'Filename'
16. Picture11 = 'On Github'
We can then drop each value associated with each image into cyberchef.
Still looks like gibberish but at least it looks like a filename. Let’s search for it on GitHub
Clicking on the Git repo we should get the flag to solve th….
….and there’s still more to do.
Looking at this code, there’s some encoded data that has been XOR with 0x5A. Once again we can just use CyberChef to solve this.
We get some Base32 encoded data, which one last time we can just decode directly in CyberChef.
Finally, for real this time we’re done.
Our final challenge is “Chip & Dip”. Here we are provided with a .pcap file, and informed of a recent breach. We need to investigate the data and see if anything was exfiltrated.
First thing to check is if there were any files transferred. Let’s look at the Export HTTP Objects.
A couple things here look interesting but lets start with “system_update”.
Looks like a request was made to the “system_update” endpoint and resulted in a binary being transferred.
Damn. Well, let’s download it and take a look.
Looking at the functions in this executable, we see the following in main.
Loading this up in Ghidra, we look deeper.
Looks like the binary is parsing a public key, and then generating a random value.
It then stores the the key in “main.salsa_key”
The stored Salsa20 key is then encrypted using the RSA public key, and converted to Base64.
The key then gets transmitted to the C2 server.
We can actually see this happen in Wireshark.
After it transmits the encrypted key in Base64, the binary goes in a loop where it requests for a command from the C2 server, executes it, and then returns the output.
The responses contain a Client Id along with the Base64 data that has been encrypted with the key in “main.salsa_key”. To solve this we need the RSA private key. Luckily for us, the public key is embedded in the binary and its very small.
We can use the following code to derive the private key from the public key.
First, we put the public key into a file called “system_update.pub”. Then we find the values of n and e using PyCrypto.
Next we can use msieve like the stackexchange recommends to factorize n.
After waiting for a minute we get our p and q values.
Now, using the code from the stack exchange as well as our values for p, q, and e, we can generate the private key.
def egcd(a, b):
"""Extended Euclidean algorithm"""
x, y, u, v = 0, 1, 1, 0
while a != 0:
q, r = b // a, b % a
m, n = x - u * q, y - v * q
b, a, x, y, u, v = a, r, u, v, m, n
return b, x, y
def modinv(e, m):
"""Modular multiplicative inverse"""
g, x, y = egcd(e, m)
if g != 1:
return None
else:
return x % m
def pqe2rsa(p, q, e):
"""Generate an RSA private key from p, q and e"""
from Crypto.PublicKey import RSA
n = p * q
phi = (p - 1) * (q - 1)
d = modinv(e, phi)
key_params = (n, e, d, p, q)
priv_key = RSA.construct(key_params)
print(priv_key.exportKey().decode())
# Prime factors and public exponent
p = 269873625246095923675069663332674915311
q = 329724059231806499277310245605369154643
e = 65537
# Generate and print the RSA private key
pqe2rsa(p, q, e)
Now that we have the private key, we can decrypt the salsa key that was transmitted and from there, extract the encrypted output of all the commands ran via the C2 server.
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long
from Crypto.Cipher import Salsa20
import base64
privpem = '''-----BEGIN RSA PRIVATE KEY-----
MIGpAgEAAiEAxLsPt3Ywql0RgiLytt+PRWR4JfpM+NK5R4UUi/eHUn0CAwEAAQIg
fUNieX1+9Sr3V/ZqtvhYH0blpZBTR/vQ/Jt6sJxhrj0CEQDLB8UE6RvcOGAGexCM
gUvvAhEA+A6LPWuEetHOxkPEDDuMUwIQI7zqYULnNIx32qwu7YyU4QIQPI9Qby5Q
qauPT9g7hMEFAQIQYIgNEUQv5iNaAnaOz6IgZQ==
-----END RSA PRIVATE KEY-----'''
private_key = RSA.importKey(privpem)
x = bytes_to_long(base64.b64decode("p3pCyMhV+P19R/my0cSe86fo1Jg1MET30Uq5b7bhidE="))
salsa_key = (pow(x,private_key.d,private_key.n))
print("Salsa Key:", salsa_key)
Now that we have the key, lets step back a bit and take a closer look at what is being sent back and forth.
In Ghidra, we analyzed both the “GetC2Command” function and “SendC2Output” fucntion. We see the binary is generating a random “nonce” to be used with Salsa20 to encrypt each message. Next, the message is then encoded in Base64 and transmitted. Then, the response is received, decoded from Base64, decrypted using the same nonce.
Let’s export the C2 data from Wireshark into a file. Now lets ask ChatGPT to help us write python to parse the data, decrypt each message, and print the command ran and output for each request.
import re
import base64
from Crypto.Cipher import Salsa20
from Crypto.Util.number import long_to_bytes
# Define the Salsa20 key
salsa20_key = long_to_bytes(12263018261584017519120326105195548256363564600656592076273160063619184525144)
# Path to the Wireshark data file
file_path = 'C2data.txt'
# Read the file content
with open(file_path, 'r') as file:
data = file.read()
# Regular expressions to extract Nonce and Msg
nonce_regex = r'"Nonce":"([A-Za-z0-9+/=]+)"'
msg_regex = r'"Msg":"([A-Za-z0-9+/=]+)"'
# Find all matches for Nonce and Msg in the data
nonces = re.findall(nonce_regex, data)
msgs = re.findall(msg_regex, data)
# Decrypt each message using the corresponding Nonce and Salsa20 key
for nonce_b64, msg_b64 in zip(nonces, msgs):
nonce = base64.b64decode(nonce_b64)
msg = base64.b64decode(msg_b64)
cipher = Salsa20.new(key=salsa20_key, nonce=nonce)
plaintext = cipher.decrypt(msg)
print("Decrypted Message:", plaintext)
We then run this and find the last command ran was “cat /flag.txt” and our flag is right there in the output.