verox›Casino Heist

Casino Heist

Casino Heist cover

Machine got flagged for acting weird late at night, and someone pulled the full packet capture before killing it.

unzip attachment-1785961082183.zip

Got stolen_jackpot.pcapng out of it.

First thing I do with any pcap is get a quick protocol breakdown so I know what I'm dealing with before diving in blind.

tshark -r stolen_jackpot.pcapng -q -z io,phs

protocol hierarchy

701 frames total, mostly TCP, and the HTTP traffic included a large /stealer download.

Pulled every file that went over HTTP out of the capture to see what that blob actually was.

tshark -r stolen_jackpot.pcapng --export-objects "http,extracted_http" -qls -la extracted_http

extracted http objects

Four files came out: admin, cms, flag (all 469 bytes each) and stealer at a chunky 20MB. Ran file on all of them the three small ones turned out to be identical generic 404 pages, and stealer came back as a stripped 64-bit ELF binary. That's the getaway car the challenge briefing was talking about.

The 404s were a dead end, so I moved on to figuring out where the actual stolen data went. HTTP only showed the download of stealer, no upload traffic so if something got exfiltrated, it went out over a different connection. Listed all the TCP conversations in the capture to check.

tshark -r stolen_jackpot.pcapng -q -z conv,tcp

tcp conversations

Found two short TCP streams going to 172.20.0.3:4444 — classic 4444 port. That's not HTTP traffic, that's raw TCP, which lines up with the brief saying the loot "went straight over the wire."

Followed both streams in ASCII to see what actually got sent.

tshark -r stolen_jackpot.pcapng -q -z follow,tcp,ascii,172.20.0.1:49496,172.20.0.3:4444tshark -r stolen_jackpot.pcapng -q -z follow,tcp,ascii,172.20.0.1:49500,172.20.0.3:4444

tcp stream ascii flag.jackpot

Both streams start with the filename flag.jackpot followed by a chunk of binary garbage encrypted data. So the stealer grabbed a file called flag.jackpot, encrypted it, and shipped it out over that port 4444 connection. Now I needed the key.

The briefing said the tool was "built in a hurry" and the key would be "sitting in plain sight" — so the obvious move is to crack open the stealer binary itself. Installed pyinstxtractor-ng through pipx and ran it against the binary.

pipx install pyinstxtractor-ngpyinstxtractor-ng extracted_http/stealer

pyinstxtractor unpack

It's a PyInstaller-packed Python binary explains the 20MB size. Extraction spit out a bunch of files, and one stood out as the real entry point: stealer.pyc, matching the binary's own name.

Ran strings on it to see if the key was sitting there in plain text.

strings -n 6 stealer_extracted/stealer.pyc

strings raw key/iv candidates

Two strings jumped out immediately: J4ckp0tH4ck3rKeys and Iv_For_Exf1ltr8!z clearly a key and an IV. But I noticed other strings nearby were slightly corrupted (Cipherr, Paddingr instead of Cipher/Padding), which meant raw strings was bleeding extra characters into some of these — so I didn't trust those values as-is.

To get a clean read, I wrote a small Python script using the xdis library to parse the bytecode constants properly.

import sysfrom xdis.load import load_module def walk(co, seen=None):    if seen is None:        seen = set()    if id(co) in seen:        return    seen.add(id(co))    for const in co.co_consts:        if isinstance(const, str) and len(const) >= 4:            print(repr(const))        elif isinstance(const, bytes) and len(const) >= 4:            print(repr(const))        elif hasattr(const, 'co_consts'):            walk(const, seen) path = sys.argv[1]result = load_module(path)co = next(x for x in result if hasattr(x, 'co_consts'))walk(co)
python3 dump_consts.py stealer_extracted/stealer.pyc

clean key and iv extracted

That confirmed the real key is J4ckp0tH4ck3rKey and the real IV is Iv_For_Exf1ltr8!, both clean 16-byte strings, no trailing junk like the raw strings dump showed. Also saw 172.20.0.3 and .jackpot in there, matching what we already found on the wire.

Now I needed the actual ciphertext bytes. Pulled the same TCP stream again but in hex this time, since ASCII mode mangles binary data.

tshark -r stolen_jackpot.pcapng -q -z follow,tcp,hex,172.20.0.1:49496,172.20.0.3:4444

tcp stream hex ciphertext

Clean structure: flag.jackpot\n as a plaintext header (13 bytes), followed by exactly 48 bytes of ciphertext 3 full AES blocks, so AES-CBC made sense.

I wrote a quick decrypt script using AES-CBC with the recovered key and IV.

from Crypto.Cipher import AESfrom Crypto.Util.Padding import unpad key = b'J4ckp0tH4ck3rKey'iv  = b'Iv_For_Exf1ltr8!' ciphertext = bytes.fromhex(    "9bcc341f8374f7d031a5a0ee46635013"    "13b15466b184e33a3f295efd0cd1b4f5"    "c64b48dd831bbca7ec4423e3782f8fe5") cipher = AES.new(key, AES.MODE_CBC, iv)plaintext = cipher.decrypt(ciphertext)plaintext = unpad(plaintext, 16)print(plaintext)
python3 decrypt.py

flag decrypted

Padding checked out clean and out popped the flag:

THM{J@ckP05_R0bb3ry_VIA_pYth0nN}

New content available