Tuesday, May 25, 2010

Defcon 18 CTF quals writeup - Packet 100

Packet 100 was just... horrible right? :) However some teams managed to get it, here is my writeup.

Description: sumthing is not like the other... Here are your packets.

We have a capture file of several ICMP echo-request/echo-reply pings. We can ignore echo-reply packets because they carry the same payload (it's a ping).
First, we focused on the only packet to have a 129 bytes payload instead of 256, but achieved nothing, so we focused on the other packets.
We tried so many things: frequency analysis, cesar, xors, consider only ascii, sum things, IP checksum, Ethernet mac, use first packet as a key or initialisation vector. But in fact it appeared to be what was explained in the description "sumthing is not like the other", only consider bytes that only reside in this packet and concatenate (sum) them.

With our favourite packet manipulation tool Scapy, it becomes:
pcap = rdpcap('pkt100_6b233464726cfa8fa.pcap')
raws = [str(p[Raw]) for p in pcap if len(p[Raw])==256 and p[ICMP].type==8]
A function to return true if character c is not in strings strs except for exceptstr:
def alone(c, strs, exceptstr):
  for s in strs:
    if s!=exceptstr and c in s:
      return False
    return True
Now extract all bytes that are uniques among all the other packets:
unique = ''
for raw in raws:
  for c in raw:
    if alone(c, raws, raw):
      unique += c
Which gives us:
>>> unique
'\x8ba\\cZ\x8e^\\`\x8c`\x8b[a`^\x8b\x90`\x90\x8ea\x90\x8c\x8b_\x8e\x8c\\[^\x8f'
As a good practice, we try all Cesar ciphers (rotation by n) on it:
for i in range(256):
  print '%i: %s' % (i, repr(''.join([chr((ord(c)+i)%256) for c in unique])))
Iterations 200-240 are interesting with many ASCII printable characters, especially iteration #214 (equals -42):
214: 'a7290d426b6a1764af6fd7fba5db214e'
It appears to be the known md5 of Supercalifragilisticexpialidocious!, the key.

Qualifications ended, we learned that it was an yEnc file spread as payload of ICMP echo-request. They even gave us the perl script responsible for it.
Let's see that:
pcap = rdpcap('pkt100_6b233464726cfa8fa.pcap')
open('payload.yenc','w').write(''.join([str(p[Raw]) for p in pcap if p[ICMP].type==8]))
Then after having installed yEnc python module:
>>> from yenc import decode

>>> decode('payload.yenc','payload.bin')
(4192, 'x7adefb0')

>>> open('payload.bin','r').read()
[...]
%%/*.&*:?[:^}.[>%(&|~/[{<%,[@(//>.>]={|[%*[?/?:-..<}[?=.=~(-(]?=
a7290d426b6a1764af6fd7fba5db214e:.<</))][^=+>+<:/+(,/*^+^-%&~[([
+{.==><%&-?^(|^/))^|</)/+|=)&=<}@{{-&|[<>^(?/*,~]]~>+&/[&~^+](-~
[...]
Again this md5 hash.

If you too are curious why this -42 rotation worked: according to the yEnc spec, it is a "good rotation value for common binary formats, proof is in progress and will be done in the future, see: Hitchhikers Guide to the Galaxy". If you don't know the reference (shame on you), read this.

4 comments:

  1. Good work. Quick question, how do you think someone would know it was yenc file content? Does something in payload.yenc give it away? What if you had dumped it to payload.txt, could you still tell it was yenc file?

    ReplyDelete
  2. Hello,
    1) If you knew the format or have seen it before, you could have realized that it was an yEnc. I didn't see any magic number in the spec - allowing tools like "file" to detect the file type - but maybe I looked too fast.
    2) & 3) sorry but I don't understand.

    ReplyDelete
  3. :-)

    3) Never mind you answered my quesiton, I run tools like "file" on it and couldnt work out it was yenc so it all comes down to knowing it was a yenc if you had come across it before- pretty nasty if you hadnt don't you think?

    ReplyDelete
  4. Ok. Yes, nasty challenge, but that's the game! :)

    ReplyDelete