2016-09-17

CSAW 2016: Sleeping Guard Writeup

This challenge is prefaced by a flavor text indicating that there will be a PNG image file. An ip address and port are provided, which, when connected to (with nc or ncat), present a stream of base64 encoded data, exactly 169212 bytes long. I generally like to try things out on the cmdline first to see what I'm dealing with, and so I used

ncat crypto.chal.csaw.io 8000

which presented a wall of b64 text. Interestingly, the connection stayed open afterward, and didn't respond to any input, which slightly worried me. I used a CTRL+C to interrupt the ncat process and retried the connection/download of data, just to be sure my initial connect didn't get interrupted.

After getting the same behavior as before, I used

ncat crypto.chal.csaw.io 8000 > herp.txt

to redirect base64 STDOUT stream to a file, herp.txt. I opened the file in less:

less herp.txt

and then used the search command to search for any non-base64 characters using a regex:

/[^A-Za-z0-9+/=]

Seeing pattern not found I knew there wouldn't be any surprise data in the middle of the blob that would muck around with the b64 decode.

Using

cat herp.txt | base64 -d > output.what

I decoded the blob of data, and ended up with a file output.what that was 126909 bytes in size.

Looking at output.what


A quick file output.what revealed that the file's structure was unknown to the likes of libmagic, meaning it was probably encrypted somehow. To take a closer look at what the data looked like, I inspected the data with

cat output.what | xxd | less

and was presented with:

00000000: de3f 0f2f 524b 4541 6579 2132 1e27 053a  .?./RKEAey!2.'.:
00000010: 5f41 5eb7 6579 2197 5f69 4168 5f44 6517  _A^.ey!._iAh_De.
00000020: 2379 213f 5308 0025 1e41 5ffa ea72 dd5e  #y!?S..%.A_..r.^
00000030: 526f 4168 7f22 1719 2879 2145 716f 41e8  RoAh."..(y!EqoA.
00000040: db41 5fb1 6579 21bf bf6f 411d 6f41 5fa1  .A_.ey!..oA.oA_.
00000050: 0579 2105 cf6f 417f 2fdd e51a 5979 213f  .y!..oA./...Yy!?
00000060: 5e1f 0931 2c41 5f59 1179 212d 236e 9f0e  ^..1,A_Y.y!-#n..
00000070: 4039 5f4b 6420 486b 0f1b 1925 137b 3c24  @9_Kd Hk...%.{<$
00000080: 0857 405b 380d 2446 272c 2f4b 6579 213f  .W@[8.$F',/Key!?
00000090: 6b17 7b10 3231 322e 1118 0147 3a03 2f1b  k.{.212....G:./.
000000a0: 6539 6269 041d 4e5d 3255 2f1b 652c 3a3f  e9bi..N]2U/.e,:?
000000b0: 0456 031f 2f55 3905 2f35 3476 4721 6c6f  .V../U9./54vG!lo
000000c0: 772c 2e1a 3a61 6a65 5157 111d 6965 6148  w,..:ajeQW..ieaH
000000d0: 7f7d 2d2f 0343 737b 114f 3905 332f 2c71  .}-/.Cs{.O9.3/,q
000000e0: 171d 4702 7507 351c 2f7b 7064 120e 5611  ..G.u.5./{pd..V.
000000f0: 205c 6f07 2d26 707a 5c40 1810 675d 6e5a   \o.-&pz\@..g]nZ
00000100: 6d6c 2d2f 0354 5246 391b 2010 722f 2c68  ml-/.TRF9. .r/,h
00000110: 4747 2b1f 774f 6148 7f7d 2d2f 0343 655a  GG+.wOaH.}-/.CeZ
00000120: 240c 3301 2f35 3624 0b59 535b 3155 200a  $.3./56$.YS[1U .
00000130: 3034 2b76 475b 2b1f 774f 6148 7f61 7f6b  04+vG[+.wOaH.a.k
00000140: 4559 0147 3a03 2f1b 6535 362d 0344 0357  EY.G:./.e56-.D.W
........................<truncated>................................

Hmm, Random-ish looking bytes. But wait, I do see the word, "key" in there twice. ...Coincidence? Maybe this file was XOR'd with a repeating key. If the file was a PNG file, then there should be several 0-byte pairs throughout the header, which would effectively "leak" portions of an XOR key. (since \(0 \oplus 1 == 1\) and \(0 \oplus 0 == 0\)). To verify this, I googled for two random PNG images and ended up with Doge.png and cat.png.

Using

cat Doge.png | xxd | less

I saw:

00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 01f4 0000 0290 0806 0000 0069 5b88  .............i[.
00000020: 5b00 0000 0467 414d 4100 00b1 8f0b fc61  [....gAMA......a
00000030: 0500 000a 3b69 4343 5069 6363 0000 78da  ....;iCCPicc..x.
00000040: 9d53 7758 93f7 163e dff7 650f 5642 d8f0  .SwX...>..e.VB..
00000050: b197 6c81 0022 23ac 08c8 1059 a210 9200  ..l.."#....Y....
00000060: 6184 1012 40c5 8588 0a56 1415 119c 4855  a...@....V....HU
00000070: c482 d50a 489d 88e2 a028 b867 418a 885a  ....H....(.gA..Z
00000080: 8b55 5c38 ee1f dca7 b57d 7aef eded fbd7  .U\8.....}z.....
........................<truncated>................................

Using

doge cat.png | xxd | less

I saw: (diclaimer, I have alias doge=cat set)

00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 07c9 0000 09fd 0806 0000 00aa 8e03  ................
00000020: ee00 0000 0970 4859 7300 002e 2600 002e  .....pHYs...&...
00000030: 2601 551f 5a80 0000 0a4f 6943 4350 5068  &.U.Z....OiCCPPh
00000040: 6f74 6f73 686f 7020 4943 4320 7072 6f66  otoshop ICC prof
00000050: 696c 6500 0078 da9d 5367 5453 e916 3df7  ile..x..SgTS..=.
00000060: def4 424b 8880 944b 6f52 1508 2052 428b  ..BK...KoR.. RB.
00000070: 8014 9126 2a21 0910 4a88 21a1 d915 51c1  ...&*!..J.!...Q.
00000080: 1145 4504 1bc8 a088 038e 8e80 8c15 512c  .EE...........Q,

While there are 14 null-bytes that overlap in the first 0x20 bytes of both PNG files, the first 0x11 (17) bytes of both PNGs were identical, which, because of their contiguous arrangement, would allow revealing an XOR key under 17 bytes in length (assuming the encrypted file is a PNG).

Whereas, the 14 "revealed" bytes could potentially be duplicates of sections of the XOR key, or the same character repeated in different parts of the key.

Obtaining The Key


Since I have a blob of data that I could assume to be the plaintext version of the first 0x11 XOR-encrypted bytes, I could simply XOR these bytes with those of the encrypted file to reveal what the XOR key was:

#!/usr/bin/env python3

with open("output.what", 'rb') as f:
    fo = f.read()

with open("Doge.png", 'rb') as f:
    dg = f.read()

print(bytes([fo[i] ^ dg[i] for i in range(0, 0x11)]))

Et. Voila! b'WoAh_A_Key!?WoAh_' is what outputs from the above program, and it seems we've found our repeating XOR key!

So now I need to XOR every byte in output.what with a byte in the repeating key I found. The easiest way to get a repeating string of the exact length of the number of bytes in output.what for our rolling key is to use the itertools.cycle() function.
This produces a generator which we can then zip() with our bytes object to map each character of the key to a byte of the encrypted file in the form of a list of tuples. We can then iterate through this list, XORing the bytes in the tuples together, effectively decrypting the file.

Decrypting The File


We simply add a few lines to our program which already output the XOR key:

#!/usr/bin/env python3
from itertools import cycle

with open("output.what", 'rb') as f:
    fo = f.read()

with open("Doge.png", 'rb') as f:
    dg = f.read()

print(bytes([fo[i] ^ dg[i] for i in range(0, 0x11)]))

#b'WoAh_A_Key!?' is our key!
key = b'WoAh_A_Key!?'

newBytes = bytes([byte[0] ^ byte[1] for byte in zip(cycle(key), fo)])
with open("output.png", 'wb') as f:
    f.write(newBytes)

Looking in the directory reveals that a new file exists output.png, and opening it as an image file, eog output.png revealed the flag.

Icing on the Cake

After all this, I wanted a one-shot program that would open a socket, connect to the challenge server, download the data, base64 decode it, demonstrate how XORing the PNG magic bytes with the file would reveal the key, and finally decrypt and write the png file to disk. Here it is:

#!/usr/bin/env python3
import socket
import base64
from itertools import cycle

print("Opening connection to crypto.chal.csaw.io:8000...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3.0)
s.connect(('crypto.chal.csaw.io', 8000))
data = b''
try:
    while True:
        data += s.recv(8192)
        if not data:
            break
except Exception as e:
    pass

print("Data appears to be Base64 encoded!: {}".format(data))
print("Decoding...")

data2 = base64.b64decode(data)

magic = '89504e470d0a1a0a0000000d494844520000'
print("PNG magic bytes start as: {}", magic)

#turn hex string into integer array:
m = [int(magic[i:i+2], 16) for i in range(0, len(magic), 2)]

x = []
for i in range(0, len(m)):
    x.append(m[i] ^ data2[i])

#here we find the key is: WoAh_A_Key!?

#integer representation of key:
t = [87, 111, 65, 104, 95, 65, 95, 75, 101, 121, 33, 63]

print("XORING the base64 decoded bytes with the PNG magic: {}".format(bytes(x[0:12])))
#xoring entire data blob with key:
h = [x[0] ^ x[1] for x in zip(cycle(t), data2)]

with open('output.png', 'wb') as o:
    o.write(bytes(h))

FIN