Author: vilkius

TJCTF 2026 - skeleton

author tmm

Description

I zipped up a picture of the flag, but I forgot the password. Luckily, I saved the zip2john hash. Can you recover the image?

Flag: tjctf{1ts_411_ab0ut_th3_keys}

Writeup

We are given a hash.txt:

flag.zip/flag.png:$pkzip2$1*1*2*0*12c*120*c8a6617a*0*26*0*12c*c8a6*81bd*36bee62e49e2b2c41f6260bdc2e5fdd8cabd38956eb51f1d8a48c8f6228fd7392a8c53f3199068e3017e11c65e32cd55ea33033ab8b2fb52c4f86373098af1732591290e5c99a2a74239243b67108f232def15a73aac1537e75a593abe81fb3a8b0338afeb00835c67f8a31896a5f73facd1f481fd5ebc8882b5b183819f9b71c89506b3ae7d17bc07ab187ece8413a88af072018ccdc8a2db425082cec0715fd5aa3b3c47bb4f5c93b397154eb2212ffd593d0e4e614d83dafba289710be2e538f4610e8cb53c025aa722bfe832ec4d6cbe33350c09b690c92560292893f72c7e9894a50efaaf9635d64c86b053053b861a00e1717d7b2b963782ea4fe407008153d2d0564e2cbe3792eaa0dacd611b9eaf9d3e7d5b54ab63ae9906b62c830ef4b873d954c25c22e8a221c9*$/pkzip2$:flag.png:flag.zip::flag.zip

The structure of the $pkzip2$ hash:

  • The parameters 12c and 120 represent the data lengths.

  • In legacy ZipCrypto, a 12-byte encryption header is added to the data. 12 + 288 = 300 bytes total.

  • Because the size matches perfectly, we can understand that the file (flag.png) was stored without compression (Deflate was not used).

  • Because the file is very small, zip2john embedded the entire encrypted payload inside the hash.

Known Plaintext Attack

  • Legacy ZipCrypto encryption is mathematically broken and vulnerable to a Known Plaintext Attack if we know at least 12 bytes of the unencrypted file.

  • Since the target file is a .png and it is uncompressed, we know its 16-byte magic header:

89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52

  • By supplying this known plaintext header to bkcrack, we can get the internal encryption keys without ever needing to brute-force the actual password.

Step 1: Extract the Ciphertext

We extract the hex block from the hash string and convert it into a raw binary file (cipher.bin) using xxd:

echo -n "36bee62e49e2b2c41f6260bdc2e5fdd8cabd38956eb51f1d8a48c8f6228fd7392a8c53f3199068e3017e11c65e32cd55ea33033ab8b2fb52c4f86373098af1732591290e5c99a2a74239243b67108f232def15a73aac1537e75a593abe81fb3a8b0338afeb00835c67f8a31896a5f73facd1f481fd5ebc8882b5b183819f9b71c89506b3ae7d17bc07ab187ece8413a88af072018ccdc8a2db425082cec0715fd5aa3b3c47bb4f5c93b397154eb2212ffd593d0e4e614d83dafba289710be2e538f4610e8cb53c025aa722bfe832ec4d6cbe33350c09b690c92560292893f72c7e9894a50efaaf9635d64c86b053053b861a00e1717d7b2b963782ea4fe407008153d2d0564e2cbe3792eaa0dacd611b9eaf9d3e7d5b54ab63ae9906b62c830ef4b873d954c25c22e8a221c9" | xxd -r -p > cipher.bin

Step 2: Create the Plaintext Signature File

We write the known 16-byte PNG header into a binary file (plain.bin):

printf "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52" > plain.bin

Step 3: Crack Internal Keys via bkcrack

We run bkcrack to find the internal keys:

bkcrack -c cipher.bin -p plain.bin

image1

Step 4: Decrypt the Ciphertext

Using the keys, we decrypt cipher.bin into decrypted.bin:

bkcrack -c cipher.bin -k c639d1ca b1fd3d6c 25bb9b08 -d decrypted.bin

image2

Step 5: File Carving

Because bkcrack discovered the plaintext header matching at index 7, the valid PNG data starts exactly 7 bytes into the decrypted file. We strip the padding bytes using dd:

dd if=decrypted.bin of=flag.png bs=1 skip=7

Opening flag.png reveals the valid visual text containing the flag.

image3