CODEGATE 2012 - Network 100 Writeup

Take a look at Eindbazen's write-up on Network 100.

I wanted to do the same write-up, highlighting an alternate path. (This will be the last CODEGATE 2012 write-up of mine, since both Leetmore and Eindbazen have all the other challenges we solved well documented.)

You start with a file: A0EBE9F0416498632193F769867744A3

And a note:

Someone have leaked very important documents. We couldn't find any proof without one PCAP file. But this file was damaged.

¡Ø The password of disclosure document is very weakness and based on Time, can be found easily.

Cryptographic algorithm is below. Msg = "ThisIsNotARealEncryption!SeemToEncoding"
Key = 0x20120224 (if date format is 2012/02/24 00:01:01)
Cryto = C(M) = Msg * Key = 0xa92fd3a82cb4eb2ad323d795322c34f2d809f78

Answer: Decrypt(Msg)

The PCAP is missing it's header. Dump the first 500 bytes in a hex viewer and you'll see: 

$ hexdump -n 500 -C A0EBE9F0416498632193F769867744A3
00000000  e4 8d 3b 4f a4 85 04 00  3e 00 00 00 3e 00 00 00  |..;O....>...>...|
00000010  ff ff ff ff ff ff ff ff  ff ff ff ff 08 00 45 00  |..............E.|
00000020  00 30 2e 87 40 00 80 06  c6 3b 01 01 01 01 02 02  |.0..@....;......|
00000030  02 02 fd f2 00 50 9a 11  26 2d 00 00 00 00 70 02  |.....P..&-....p.|
00000040  20 00 9e 98 00 00 02 04  05 b4 01 01 04 02 e4 8d  | ...............|
00000050  3b 4f b8 8b 04 00 3e 00  00 00 3e 00 00 00 ff ff  |;O....>...>.....|
00000060  ff ff ff ff ff ff ff ff  ff ff 08 00 45 00 00 30  |............E..0|
00000070  00 00 40 00 3e 06 36 c3  02 02 02 02 01 01 01 01  |..@.>.6.........|
00000080  00 50 fd f2 ad b5 c9 24  9a 11 26 2e 70 12 16 d0  |.P.....$..&.p...|
00000090  30 dd 00 00 02 04 05 b4  01 01 04 02 e4 8d 3b 4f  |0.............;O|
000000a0  0a 8c 04 00 36 00 00 00  36 00 00 00 ff ff ff ff  |....6...6.......|
000000b0  ff ff ff ff ff ff ff ff  08 00 45 00 00 28 2e 88  |..........E..(..|
000000c0  40 00 80 06 c6 42 01 01  01 01 02 02 02 02 fd f2  |@....B..........|
000000d0  00 50 9a 11 26 2e ad b5  c9 25 50 10 fa f0 79 80  |.P..&....%P...y.|
000000e0  00 00 e4 8d 3b 4f f1 7a  05 00 00 02 00 00 00 02  |....;O.z........|
000000f0  00 00 ff ff ff ff ff ff  ff ff ff ff ff ff 08 00  |................|
00000100  45 00 01 f2 2e 8a 40 00  80 06 c4 76 01 01 01 01  |E.....@....v....|
00000110  02 02 02 02 fd f2 00 50  9a 11 26 2e ad b5 c9 25  |.......P..&....%|
00000120  50 18 fa f0 24 a9 00 00  47 45 54 20 2f 77 6f 6f  |P...$...GET /woo|
00000130  73 2f 68 61 63 6b 74 68  65 70 61 63 6b 65 74 2f  |s/hackthepacket/|
00000140  25 45 33 25 38 35 25 38  42 25 45 33 25 38 35 25  |%E3%85%8B%E3%85%|
00000150  38 42 25 45 33 25 38 35  25 38 42 25 45 33 25 38  |8B%E3%85%8B%E3%8|
00000160  35 25 38 42 20 48 54 54  50 2f 31 2e 31 0d 0a 48  |5%8B HTTP/1.1..H|
00000170  6f 73 74 3a 20 63 6f 67  65 2e 68 61 63 6b 74 68  |ost: coge.hackth|
00000180  65 70 61 63 6b 65 74 2e  63 6f 6d 0d 0a 43 6f 6e  |epacket.com..Con|
00000190  6e 65 63 74 69 6f 6e 3a  20 6b 65 65 70 2d 61 6c  |nection: keep-al|
000001a0  69 76 65 0d 0a 55 73 65  72 2d 41 67 65 6e 74 3a  |ive..User-Agent:|
000001b0  20 4d 6f 7a 69 6c 6c 61  2f 35 2e 30 20 28 57 69  | Mozilla/5.0 (Wi|
000001c0  6e 64 6f 77 73 20 4e 54  20 36 2e 31 3b 20 57 4f  |ndows NT 6.1; WO|
000001d0  57 36 34 29 20 41 70 70  6c 65 57 65 62 4b 69 74  |W64) AppleWebKit|
000001e0  2f 35 33 35 2e 37 20 28  4b 48 54 4d 4c 2c 20 6c  |/535.7 (KHTML, l|
000001f0  69 6b 65 20                                       |ike |
000001f4

There's some things that should jump out at you, first the GET request, second the pairs of "FF FF FF FF FF FF". This looks like part of a packet capture. Let's fix it, open a "known good" capture (from an Ethernet network) in Wireshark and print a hexdump as well. Look at the Ethernet frame. Now find the MAC address in the hexdump and notice the 4 double words (32 bytes) preceding it. This is the Packet header

$ hexdump -n 200 -C ../../WS-Challenge/pcap2.pcap | grep "00 25 22 b0 36 84" -B40
00000000  d4 c3 b2 a1 02 00 04 00  00 00 00 00 00 00 00 00  |................|
00000010  ff ff 00 00 01 00 00 00  0b 35 4b 4e bd aa 0c 00  |.........5KN....|
00000020  3c 00 00 00 3c 00 00 00  00 25 22 b0 36 84 00 24  |<...<....%".6..$|
00000030  b2 92 6a fe 08 06 00 01  08 00 06 04 00 01 00 24  |..j............$|
00000040  b2 92 6a fe c0 a8 00 01  00 00 00 00 00 00 c0 a8  |..j.............|
00000050  00 77 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |.w..............|
00000060  00 00 00 00 0b 35 4b 4e  d3 aa 0c 00 2a 00 00 00  |.....5KN....*...|
00000070  2a 00 00 00 00 24 b2 92  6a fe 00 25 22 b0 36 84  |*....$..j..%".6.|
00000080  08 06 00 01 08 00 06 04  00 02 00 25 22 b0 36 84  |...........%".6.|

Now, back to the challenge file, notice it's missing a few bytes that the "known good" PCAP contained before the first packet header.

d4 c3 b2 a1 02 00 04 00 00 00 00 00 00 00 00 00 ff ff 00 00 01 00 00 00

This is the Global Header. It's simple to create your own, and it's even simpler to copy the global header from your "known good" capture to this "damaged capture". Note: Make sure you're using an Ethernet capture with max snap length; this should be "normal". Now try to open the PCAP in Wireshark and you'll notice another problem. One of the packet header's is reporting an invalid capture size. 

The capture file appears to be damaged or corrupt.(pcap: File has 1191182380-byte packet, bigger than maximum of 65535)

There are two ways to tackle this, convert the invalid length to hex (47 00 00 2C), then search for that in the capture, or find the last printed packet and search the file directly after that. You'll find that Wireshark is searching for a packet header (packet size) overlapping into an HTTP GET request. Our approach was the increase the size of the preceding TCP SYN ACK to 0x225 and open the capture without the GET request. 

Next, find that the response to that GET is the most interesting thing in the capture. The other responses are HTTP 404 messages, which should not be discounted, but we focused out attention on this file first. 

At first it seems like garbage, but in a hexdump there are interesting pieces. Our approach to unknown files is: (0) run foremost (1) Perform an incremental search for the first few bytes, perhaps they are an exotic magic number, (2) Strings for possible meta-data identifiers, (3) Start hunting for patterns. It's difficult to recount the exact procedure, but you should notice file names throughout, with all three at the bottom.

  • 1984-was-not-supposed-to-be-an-instruction-manual2-1.jpg
  • George_Orwell.jpg
  • key

Pro Tip: "strings" by default looks for ASCII strings of length >4. In CTFs, the word "key" is key, use: "strings -n 3" instead.

If you're familiar with the ZIP file format, the PKs above each name should also stand out. Let's try to recover this ZIP:

$ zip -FF file2.zip --out fixed.zip
Fix archive (-FF) - salvage what can
	zip warning: Missing end (EOCDR) signature - either this archive
                     is not readable or the end is damaged
Is this a single-disk archive?  (y/n): y
  Assuming single-disk archive
Scanning for entries...
 copying: George_Orwell.jpg  (19535 bytes)
 copying: key  (49 bytes)
Central Directory found...
no local entry: 1984-was-not-supposed-to-be-an-instruction-manual2-1.jpg

Grrr, I really want that file, why? Because the ZIP is password protected, with the ZIP 2.0 (PKZip) encryption algorithm. I strongly dislike CTF "gotcha's" and prefer to find technical clues. So if there's a password protected ZIP, the first step I'll consider is using known attacks against PKZip encryption. So why do I want that file, because the Biham-Kocher known-plaintext attack against PKZip encryption requires a plaintext version of one of the files in the encrypted ZIP. "1984-was-not-supposed-to-be-an-instruction-manual2-1.jpg" is a very unique name. I could probably have found George_Orwell.jpg, but I don't want to try very hard searching for images on the web. 

The ZIP can be fixed by (again) adding the header (PK) and fixing the trailer. How do you know to fix the trailer? You look at a "known good" ZIP and realize in the "damaged" ZIP there is a "P[" instead of a "PK" in the trailer. A bit much for a 100-level challenge, but I prefer this method over guessing a ZIP password.

Google, find, and save the 1984-was-not-supposed-to-be-an-instruction-manual2-1.jpg.

$ zip plaintext.zip 1984-was-not-supposed-to-be-an-instruction-manual2-1.jpg

Quick sanity check:

$ unzip -v plaintext.zip 
Archive:  plain.zip
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
  190783  Defl:N   179010   6% 2012-02-15 19:44 73801993  1984-was-not-supposed[...].jpg
--------          -------  ---                            -------
  190783           179010   6%                            1 file
teddy@0:/mnt/codegate/n100$ unzip -v pk2.zip
Archive:  pk2.zip
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
  190783  Defl:N   179421   6% 2012-02-15 19:44 73801993  1984-was-not-supposed[...].jpg
   19717  Defl:N    19523   1% 2012-02-15 19:47 5951d85d  George_Orwell.jpg
      37  Stored       37   0% 2012-02-15 19:41 9ccb06cf  key
--------          -------  ---                            -------
  210537           198981   6%                            3 files

Download the PkCrack source code, compile, and run:

$ ./pkcrack-1.2.2/src/extract plain.zip 1984-was-not-supposed-to-be-an-instruction-manual2-1.jpg plain.jpg
$ ./pkcrack-1.2.2/src/extract pk2.zip 1984-was-not-supposed-to-be-an-instruction-manual2-1.jpg enc.jpg
$ pkcrack-1.2.2/src/pkcrack -c enc.jpg -p new_plain.jpg 
Files read. Starting stage 1 on Fri Feb 24 16:20:41 2012
Generating 1st generation of possible key2_179432 values...done.
Found 4194304 possible key2-values.
Now we're trying to reduce these...
Lowest number: 994 values at offset 170573
Lowest number: 946 values at offset 170571
Lowest number: 922 values at offset 170526
Lowest number: 857 values at offset 170525
Lowest number: 815 values at offset 170507
Lowest number: 806 values at offset 170501
Lowest number: 741 values at offset 170492
Lowest number: 734 values at offset 170449
Lowest number: 702 values at offset 162872
Lowest number: 698 values at offset 162868
Lowest number: 678 values at offset 162865
Lowest number: 672 values at offset 162864
Lowest number: 663 values at offset 162861
Lowest number: 613 values at offset 162860
Lowest number: 600 values at offset 162859
Lowest number: 584 values at offset 162681
Lowest number: 567 values at offset 162676
Lowest number: 549 values at offset 162665
Lowest number: 524 values at offset 162658
Lowest number: 514 values at offset 162657
Lowest number: 496 values at offset 162653
Lowest number: 485 values at offset 162632
Lowest number: 477 values at offset 162631
Lowest number: 459 values at offset 162292
Lowest number: 458 values at offset 162277
Lowest number: 438 values at offset 162271
Lowest number: 423 values at offset 162270
Lowest number: 409 values at offset 162264
Lowest number: 381 values at offset 162227
Lowest number: 361 values at offset 162190
Lowest number: 333 values at offset 140880
Lowest number: 303 values at offset 140869
Lowest number: 298 values at offset 140311
Lowest number: 294 values at offset 140305
Lowest number: 273 values at offset 140303
Lowest number: 268 values at offset 140302
Lowest number: 264 values at offset 140301
Lowest number: 257 values at offset 140293
Lowest number: 253 values at offset 120326
Lowest number: 234 values at offset 120325
Lowest number: 232 values at offset 120323
Lowest number: 225 values at offset 120321
Lowest number: 211 values at offset 120320
Lowest number: 191 values at offset 120316
Lowest number: 190 values at offset 120308
Lowest number: 180 values at offset 120298
Lowest number: 177 values at offset 120293
Lowest number: 173 values at offset 120290
Lowest number: 158 values at offset 120289
Lowest number: 157 values at offset 120286
Lowest number: 152 values at offset 120282
Lowest number: 130 values at offset 120281
Lowest number: 124 values at offset 120252
Lowest number: 121 values at offset 120203
Lowest number: 112 values at offset 120200
Lowest number: 107 values at offset 120199
Lowest number: 104 values at offset 120198
Lowest number: 86 values at offset 120197
Done. Left with 86 possible Values. bestOffset is 120197.
Stage 1 completed. Starting stage 2 on Fri Feb 24 16:21:30 2012
Ta-daaaaa! key0=da230399, key1=ad702336, key2=e213add2
Probabilistic test succeeded for 59240 bytes.
Ta-daaaaa! key0=da230399, key1=ad702336, key2=e213add2
Probabilistic test succeeded for 59240 bytes.
Ta-daaaaa! key0=da230399, key1=ad702336, key2=e213add2
Probabilistic test succeeded for 59240 bytes.
Ta-daaaaa! key0=da230399, key1=ad702336, key2=e213add2
Probabilistic test succeeded for 59240 bytes.
Stage 2 completed. Starting password search on Fri Feb 24 16:21:33 2012
Key: 31 39 38 34 31 31 32 38
Or as a string: '19841128' (without the enclosing single quotes)
Finished on Fri Feb 24 16:21:33 2012

Sure, you could have used the date the file was downloaded (Wed, 28, Nov 1984 10:50:28 GMT), as per twisting the hint a bit. But that hint was focused towards decrypting the data in the key file (or at least that's how I read it, and it WAS (also) how the date should have been used). And wasn't this way more fun!?

Finally, Send Peter Conrad a postcard! (Did you not read the PkCrack Readme?)

Now you'll have decrypted "key" and see the output in "encrypted" form, as per the challenge note.

Use the date the ZIP file was downloaded and you'll have the answer:

$ python
>>> hex(int("be7790a9f6e79752d1f9e55a79a33f421cf68", 16) / int("19841128", 16))
'0x776f30306f735230634b696e473a29L'
>>> "776f30306f735230634b696e473a29".decode("hex")
'wo00osR0cKinG:)'