Difference between revisions of "NDH Writeups"
m (→The greatest) |
|||
(45 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
+ | ==2015 Nuit du Hack CTF Quals by Hackerzvoice== |
||
+ | |||
+ | It was a great moment of fun to participate to this year's [http://quals.nuitduhack.com/ CTF Quals] organised by Hackerzvoice |
||
+ | <br>Solving challenges involved all Pollypocket team members, here is only some polished results. |
||
+ | ===Game-of-Life=== |
||
+ | tar xzf GOL.tar.gz |
||
+ | ./jdlv.py $RANDOM cipher.txt |grep -ia flag |
||
+ | Flag : ToBeAndToLast |
||
+ | Yes, $RANDOM, any password works! |
||
+ | <br>If you want a much nicer write-up, see [https://github.com/smokeleeteveryday/CTF_WRITEUPS/tree/master/2015/NUITDUHACK/crypto/gameoflife here] but honestly I don't see the point of spending so much time to solve it and writing a cracking script... |
||
+ | |||
+ | ===Bleep=== |
||
+ | ''"The clock is running. Make the most of today. Time waits for no man. Yesterday is history. Tomorrow is a mystery. Today is a gift. That's why it is called the present." (A)'' |
||
+ | <br>''Bleeeep Bleep Bleep Bleep Bleep Bleeeep Bleep Bleep Bleep Bleep Bleep Bleeeep Bleeeep Bleep.'' |
||
+ | <br>''I'm the bleep, unbleep me.'' |
||
+ | <br>It was a 200 misc but only solved by 6 teams so I think it deserves a little write-up :-) |
||
+ | <br>You get a file [https://mega.co.nz/#!fMUQmbDJ!Cmde_sL6RKksEbRWiNbLuVznNWmW7fcFF05wTnD6xw4 Bleep.tar.gz] |
||
+ | <br>It contains a "consigne" (FR for instructions) text file: |
||
+ | [ Bleep ] |
||
+ | [+] You got this .hex file from a firmware dump can, you understand what's going on? |
||
+ | And a chall.hex that looks like: |
||
+ | :100000000C945F000C9487000C9487000C9487007C |
||
+ | :100010000C9487000C9487000C9487000C94870044 |
||
+ | :100020000C9487000C9487000C9487000C94870034 |
||
+ | etc. |
||
+ | |||
+ | chall.hex is indeed a firmware image in the classical [https://en.wikipedia.org/wiki/Intel_HEX Intel HEX] format. |
||
+ | <br>One can use e.g. http://sourceforge.net/projects/hex2bin/ to convert it to a bin/raw format. |
||
+ | <br>By an educated guess (what's most popular microcontroller especially in non-elec circles?) we assume it could be Arduino / Atmel AVR. |
||
+ | <br>Actually knowing this kind of details is not that important ;-) |
||
+ | <br>So the initial approach was to open the firmware image in Ida but I felt demotivated and didn't want to reverse all those little interrupt-driven functions. |
||
+ | <br>From the description (Bleep Bleeeep) the microcontroller is probably outputting a kind of Morse-encoded signal. |
||
+ | <br>Using my [https://doegox.github.io/ElectronicColoringBook/ ElectronicColoringBook.py] I can see quite a lot of patterns in the binary: |
||
+ | ./ElectronicColoringBook.py -x 128 chall.bin |
||
+ | <br>[[File:BleepECB.png]] |
||
+ | <br><br>And a quick look at the disassembly confirms the repetition of patterns made of args & fct calls: |
||
+ | avr-objdump -D -m avr5 chall.hex |
||
+ | [...] |
||
+ | 17a: 61 e0 ldi r22, 0x01 ; 1 |
||
+ | 17c: 80 91 00 01 lds r24, 0x0100 |
||
+ | 180: 0e 94 a5 0a call 0x154a ; 0x154a |
||
+ | 184: 68 ee ldi r22, 0xE8 ; 232 |
||
+ | 186: 73 e0 ldi r23, 0x03 ; 3 |
||
+ | 188: 80 e0 ldi r24, 0x00 ; 0 |
||
+ | 18a: 90 e0 ldi r25, 0x00 ; 0 |
||
+ | 18c: 0e 94 46 0b call 0x168c ; 0x168c |
||
+ | 190: 60 e0 ldi r22, 0x00 ; 0 |
||
+ | 192: 80 91 00 01 lds r24, 0x0100 |
||
+ | 196: 0e 94 a5 0a call 0x154a ; 0x154a |
||
+ | 19a: 68 ee ldi r22, 0xE8 ; 232 |
||
+ | 19c: 73 e0 ldi r23, 0x03 ; 3 |
||
+ | 19e: 80 e0 ldi r24, 0x00 ; 0 |
||
+ | 1a0: 90 e0 ldi r25, 0x00 ; 0 |
||
+ | 1a2: 0e 94 46 0b call 0x168c ; 0x168c |
||
+ | 1a6: 61 e0 ldi r22, 0x01 ; 1 |
||
+ | 1a8: 80 91 00 01 lds r24, 0x0100 |
||
+ | 1ac: 0e 94 a5 0a call 0x154a ; 0x154a |
||
+ | 1b0: 68 ee ldi r22, 0xE8 ; 232 |
||
+ | 1b2: 73 e0 ldi r23, 0x03 ; 3 |
||
+ | 1b4: 80 e0 ldi r24, 0x00 ; 0 |
||
+ | 1b6: 90 e0 ldi r25, 0x00 ; 0 |
||
+ | 1b8: 0e 94 46 0b call 0x168c ; 0x168c |
||
+ | 1bc: 60 e0 ldi r22, 0x00 ; 0 |
||
+ | 1be: 80 91 00 01 lds r24, 0x0100 |
||
+ | 1c2: 0e 94 a5 0a call 0x154a ; 0x154a |
||
+ | 1c6: 68 ee ldi r22, 0xE8 ; 232 |
||
+ | 1c8: 73 e0 ldi r23, 0x03 ; 3 |
||
+ | 1ca: 80 e0 ldi r24, 0x00 ; 0 |
||
+ | 1cc: 90 e0 ldi r25, 0x00 ; 0 |
||
+ | 1ce: 0e 94 46 0b call 0x168c ; 0x168c |
||
+ | 1d2: 61 e0 ldi r22, 0x01 ; 1 |
||
+ | 1d4: 80 91 00 01 lds r24, 0x0100 |
||
+ | 1d8: 0e 94 a5 0a call 0x154a ; 0x154a |
||
+ | 1dc: 64 ef ldi r22, 0xF4 ; 244 |
||
+ | 1de: 71 e0 ldi r23, 0x01 ; 1 |
||
+ | 1e0: 80 e0 ldi r24, 0x00 ; 0 |
||
+ | 1e2: 90 e0 ldi r25, 0x00 ; 0 |
||
+ | 1e4: 0e 94 46 0b call 0x168c ; 0x168c |
||
+ | etc |
||
+ | Looking at the byte level it means we have an interleaved repetition of the following patterns: |
||
+ | * 60e0809100010e94a50a |
||
+ | * 61e0809100010e94a50a |
||
+ | * 60ed77e080e090e00e94460b |
||
+ | * 64ef71e080e090e00e94460b |
||
+ | * 68ee73e080e090e00e94460b |
||
+ | Now time for some black magic one-liner! Let's print the patterns in a more compact form: |
||
+ | dd if=chall.bin bs=1 skip=378 count=1000 2>/dev/null|xxd -p|tr -d '\n' |\ |
||
+ | sed 's/61e0809100010e94a50a/^/g;s/68ee73e080e090e00e94460b/./g;s/60e0809100010e94a50a/_/g;s/64ef71e080e090e00e94460b/o/g;s/60ed77e080e090e00e94460b/O/g' |
||
+ | ^._.^._.^o_O^._.^._.^._O^._.^o_.^o_O^._.^._O^._.^._.^._O^._.^o_.^o_O^o_O^._.^._.^._O^._.^._ |
||
+ | At first I tried to interpret the 5 individual patterns as Morse . and _ and as pauses (short between symbols, longer between letters, longer between words) but nothing made properly sense. |
||
+ | <br>Then I realized combination of patterns was the key: one was there to toggle the output (^ =up, _ =down) and the other one indicates duration. |
||
+ | ^o = . |
||
+ | ^. = - |
||
+ | _. = pause between symbols |
||
+ | _O = pause between letters |
||
+ | So we can convert our previous output with |
||
+ | sed 's/\^\./-/g;s/\^o/./g;s/\^O/@/g;s/_\.//g;s/_o/ /g;s/_O/ /g' |
||
+ | or more elegantly rewrite our one-liner as: (split in several lines to show up more nicely on this wiki) |
||
+ | dd if=chall.bin bs=1 skip=378 count=990 2>/dev/null|xxd -p|tr -d '\n' |\ |
||
+ | sed 's/61e0809100010e94a50a64ef71e080e090e00e94460b/./g; |
||
+ | s/61e0809100010e94a50a68ee73e080e090e00e94460b/-/g; |
||
+ | s/60e0809100010e94a50a68ee73e080e090e00e94460b//g; |
||
+ | s/60e0809100010e94a50a60ed77e080e090e00e94460b/ /g; |
||
+ | s/$/\n/' |
||
+ | --. --- -.. -- --- -.. . --- -- |
||
+ | Which translates to "GODMODEOM". |
||
+ | <br>The flag didn't validate but "GODMODEON" worked so last symbol should be a "." and not a "-", maybe an error in the challenge or a side effect? |
||
+ | <br>Actually by carefully tuning the options and selecting a suitable color palette, my ElectronicColoringBook.py can reveal directly the code too: (red=-, green=.) |
||
+ | ./ElectronicColoringBook.py -x 1280 -b 22 -o 11.18 -P "#ff0000#aaaaaa#000000#00ff00#000000" chall.bin |
||
+ | <br><br>[[File:BleepMorseEBC.png]] |
||
+ | ===Weshgrow=== |
||
+ | <pre> |
||
+ | wget -q -O - --content-on-error "http://weshgrow.challs.nuitduhack.com/flag?hmac=%fd"|grep FLAG |
||
+ | <li onclick="toggle('pre29839800', 'post29839800')"> flag=FLAG</li> |
||
+ | <tr><td>fvars</td><td class="code"><div>{'FLAG': 'Can_I_haz_s3cureD_hm4c_plz?', |
||
+ | </pre> |
||
+ | This python stacktrace because of the utf8 parser is probably not the intended way ;-) |
||
+ | <br>For a write-up of the "real" solution, see [http://geeksspeak.github.io/blog/2015/04/06/ndh-ctf-2015-weshgrow-writeup/ here] |
||
+ | ==2014 Nuit du Hack CTF by Hackerzvoice== |
||
+ | ===Unter Cab Command and Control Radio Module=== |
||
+ | Not exactly a write-up as I could not finish this one but still, here are my notes: |
||
+ | ====Discovering==== |
||
+ | We got a DVB-T dongle and its drivers on a Windows CD. |
||
+ | <br>I directly recognize this kind of beast, it's a [[RTL-SDR]]-based dongle which means it gets a 50MHz-2GHz tuner and sends its I/Q samples to the PC where everything else is done in pure software. |
||
+ | <br>On our server we could find a README.md with the following content: |
||
+ | <pre> |
||
+ | Unter Cab Command and Control Radio Module |
||
+ | ========================================== |
||
+ | |||
+ | Description |
||
+ | ----------- |
||
+ | This document describes the radio module designed by Unter to broadcast data to cabs through Radio Frequency. |
||
+ | |||
+ | Specifications |
||
+ | -------------- |
||
+ | * Baseband: 433.92 MHz |
||
+ | * Encryption used: Ron's code |
||
+ | * Micro-controler: ATmega328P-PU |
||
+ | * Data structure: Datagram-based TX/RX |
||
+ | * CRC error control |
||
+ | |||
+ | Datagram |
||
+ | -------- |
||
+ | * 0x00: Magic (3 bytes) |
||
+ | * 0x03: Reserved (3 bytes) |
||
+ | * 0x06: IV (2 bytes) |
||
+ | * 0x08: Encrypted DATA (N bytes) |
||
+ | * 0x08 + N: Datagram CRC (from 0x00 to (0x08 + N), 1 byte) ) |
||
+ | </pre> |
||
+ | Microcontroller seems to indicate the emitter is made with an Arduino. |
||
+ | <br>Ron's code refers probably to Ron Rivest RC family. Later someone from the orga confirmed it was RC4. |
||
+ | ====Scanning the radio bands==== |
||
+ | I first ran [http://sourceforge.net/projects/gqrx/ gqrx] to see a waterfall around 433.92 MHz |
||
+ | <br>There were indeed strong "pulses" every second around 433.815 MHz |
||
+ | <br>Not exactly the same frequency but maybe the Arduino or the dongle weren't that precise so it's always good to first have a look with gqrx to fine tune the frequency to watch. |
||
+ | <br>With gqrx you can also listen to the chosen frequency and try different demodulations like FM, AM, ... and clearly AM gave the less noisy signal. |
||
+ | ====Acquiring analog signal==== |
||
+ | At this point you can make your own demodulator with GnuRadio but if it's just to demodulate AM you can also use rtl_fm which is part of the basic librtlsdr tools (librtlsdr-bin on Debian). |
||
+ | rtl_fm -f 433815000 -s 12k -p 45 -l 50 -M -o 4 data.out |
||
+ | Then opening data.out in Audacity, we see: |
||
+ | <br>[[File:Rtltaxi1.png]] |
||
+ | <br>Zooming: |
||
+ | <br>[[File:Rtltaxi2.png]] |
||
+ | <br>which can be decoded visually into (H=high / L=low) |
||
+ | HLHHLLLHHLHHHLHHHLHHLHHHHHHHHHHHHHHHHHHHHHLHHLLHHLLLHHLLLHHLLHLHLHHHLHHHHHHHHLLHLLLHLLHLLHHLLLHHHLHHLLLLLLHHHHHLHLHHHHHHLHHLLH |
||
+ | |||
+ | ====Decoding the captured signal==== |
||
+ | Now is H=1 and L=0 or vice versa? |
||
+ | <br>Let's assume H=1: |
||
+ | 10110001101110111011011111111111111111111111011001100011000110010101110111111110010001001001100011101100000011111010111111011001 |
||
+ | = B1BBB7FFFFF663195DFE4498EC0FAFD9 |
||
+ | <br>Let's assume H=0: |
||
+ | = 4E44480000099CE6A201BB6713F05026 |
||
+ | From README.md, first three bytes are "magic", a signature and as 4E4448 = "NDH" that's probably the right decoding :-) |
||
+ | <br>So if we try to decode the frame: |
||
+ | magic. reserv IV.. Encrypted..... CRC |
||
+ | 4E4448 000009 9CE6 A201BB6713F050 26 |
||
+ | Then I wrote a little crappy python script to decode the analog AM signal, which can take rtl_fm output in live: |
||
+ | <source lang=python> |
||
+ | #!/usr/bin/python |
||
+ | import os, sys, struct |
||
+ | scaledown=10 |
||
+ | x=struct.unpack_from('h',sys.stdin.read(2),0)[0] |
||
+ | d, t, mode, s=0, 0, 1, "" |
||
+ | def printbits(bitstring): |
||
+ | bytes=len(bitstring) / 8 |
||
+ | if bytes > 0: |
||
+ | n=0 |
||
+ | while len(bitstring)>7: |
||
+ | n=(n<<8) + \ |
||
+ | (int(bitstring[0])<<7) + (int(bitstring[1])<<6) + (int(bitstring[2])<<5) + (int(bitstring[3])<<4) + \ |
||
+ | (int(bitstring[4])<<3) + (int(bitstring[5])<<2) + (int(bitstring[6])<<1) + int(bitstring[7]) |
||
+ | bitstring=bitstring[8:] |
||
+ | print '%0*X' % (bytes*2,n) |
||
+ | |||
+ | while True: |
||
+ | x2=struct.unpack_from('h',sys.stdin.read(2),0)[0] |
||
+ | d2=(x2-x)>>scaledown |
||
+ | if d2 < 0: |
||
+ | d2=d2+1 |
||
+ | if d2==0 or (d>0 and d2>0 and t<30) or (d<0 and d2<0 and t<30): |
||
+ | t=t+1 |
||
+ | else: |
||
+ | if d<0: |
||
+ | t=-t |
||
+ | if (abs(t)>50): |
||
+ | if mode == 2: |
||
+ | mode=1 |
||
+ | if tl > 5: |
||
+ | s+="0" |
||
+ | else: |
||
+ | s+="1" |
||
+ | if len(s) == 16*8: |
||
+ | printbits(s) |
||
+ | pass |
||
+ | s="" |
||
+ | else: |
||
+ | if mode == 1: |
||
+ | tl=t |
||
+ | mode=2 |
||
+ | else: |
||
+ | if mode == 2: |
||
+ | mode=1 |
||
+ | if tl >= 5: |
||
+ | s+="0" |
||
+ | else: |
||
+ | s+="1" |
||
+ | d=d2 |
||
+ | t=1 |
||
+ | x=x2 |
||
+ | </source> |
||
+ | rtl_fm -f 433815000 -s 12k -p 45 -l 50 -M -o 4 - |./raw2bin.py |
||
+ | I collected about 8342 such beacons, which you can download [http://www.yobi.be/files/ucccrm.log here] |
||
+ | |||
+ | ====Filtering thanks to CRC... or not==== |
||
+ | Some may be wrong hopefully there is a CRC so let's filter them: |
||
+ | <br>Hum not so easy, there are [https://en.wikipedia.org/wiki/Cyclic_redundancy_check several CRC definitions] over a byte. |
||
+ | <br>After many attempts, even trying XOR, nothing works :-( |
||
+ | <br>Ok let's take another approach: |
||
+ | cat ucccrm.log |sort|uniq -c|sort -r -n|uniq -c -w 7|cut -c -15 |
||
+ | 11 3 |
||
+ | 228 2 |
||
+ | 7853 1 |
||
+ | 7853 beacons are unique, 228 repeat once and 11 repeat twice. |
||
+ | <br>But something striked me when I looked at the sorted beacons: |
||
+ | <pre> |
||
+ | cat ucccrm.log |sort|head |
||
+ | 4e44480000090021b8ae87bdb188bc05 |
||
+ | 4e44480000090021b8ae87bdb188bc07 |
||
+ | 4e44480000090021b8ae87bdb188bc0a |
||
+ | 4e44480000090021b8ae87bdb188bc0e |
||
+ | 4e44480000090021b8ae87bdb188bc13 |
||
+ | 4e44480000090021b8ae87bdb188bc16 |
||
+ | 4e44480000090021b8ae87bdb188bc1b |
||
+ | 4e44480000090021b8ae87bdb188bc1d |
||
+ | 4e44480000090021b8ae87bdb188bc1e |
||
+ | 4e44480000090021b8ae87bdb188bc1e |
||
+ | </pre> |
||
+ | Only the CRC byte is varying at each line!! |
||
+ | <br>That can't be. |
||
+ | <br>Another team considered the beacons were 15 bytes instead of 16 bytes (I think their decoder simply missed the last bit, therefore the last byte). |
||
+ | <br>Ok let's assume we remove the last byte so now the 15th byte becomes the CRC. |
||
+ | <br>The problem is that you can also find many things like this: |
||
+ | <pre> |
||
+ | cat ucccrm.log|grep -o "4e4448....09002163703a8a832a.." |
||
+ | 4e44487f4709002163703a8a832aea |
||
+ | 4e4448bde209002163703a8a832aea |
||
+ | 4e444848ba09002163703a8a832aea |
||
+ | 4e44480af709002163703a8a832aea |
||
+ | </pre> |
||
+ | Here we get beacons with different reserved bytes but still always the same 15th byte so it can't be CRC neither. |
||
+ | |||
+ | ====Filtering thanks to IVs & packets repetition==== |
||
+ | Forget about CRC and let's investigate those IVs: |
||
+ | <pre> |
||
+ | cat ucccrm.log |cut -c 13-16|sort|uniq -c|sort -n -r|head |
||
+ | 228 0ff6 |
||
+ | 206 a712 |
||
+ | 164 960b |
||
+ | 164 6805 |
||
+ | 157 790c |
||
+ | 151 ef1a |
||
+ | 140 0021 |
||
+ | 137 4e03 |
||
+ | 128 20fd |
||
+ | 123 c114 |
||
+ | </pre> |
||
+ | Oops, not that random! Actually a closer inspection shows that for a given value for the first IV byte, the second part of the IV is always the same: |
||
+ | cat ucccrm.log |cut -c 13-16|grep ^0f|uniq -c |
||
+ | 228 0ff6 |
||
+ | Let's try to look at the IV+payload and filter errors without the CRC: |
||
+ | cat ucccrm.log |cut -c 13-30|sort|wc -l |
||
+ | 8342 |
||
+ | cat ucccrm.log |cut -c 13-30|sort|uniq|wc -l |
||
+ | 1518 |
||
+ | Out of those 1518 unique beacons we want to filter away those that don't repeat less than three times: |
||
+ | cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|wc -l |
||
+ | 596 |
||
+ | Ok it seems we've 596 reliable unique beacon values that have been seen at least three times, so those ones are unlikely to be corrupted. |
||
+ | <br>Top cryptograms are: |
||
+ | cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|sort -n -r|head |
||
+ | 126 0ff62634969a205607 |
||
+ | 97 790c0c02ced3211618 |
||
+ | 89 a71299662e43a51052 |
||
+ | 88 68050eb867d375b4ae |
||
+ | 77 20fdd32f4c6d471ae1 |
||
+ | 74 0021b8ae87bdb188bc |
||
+ | 63 ef1af164362a274bd6 |
||
+ | 62 c1148882b72d4db20e |
||
+ | 62 4e03b62b9150d90d91 |
||
+ | 61 960bc050ef31b7a950 |
||
+ | cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|cut -c 9- > uniq_cryptograms.log |
||
+ | |||
+ | ====Investigating IVs further==== |
||
+ | Out of those beacons, let's see those starting with the same IV: |
||
+ | cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|cut -c 9-|sort |uniq -w 4 -c|sort -r -n|cut -c 4-12|head |
||
+ | 10 a712 |
||
+ | 7 960b |
||
+ | 6 b53d |
||
+ | 6 9d90 |
||
+ | 6 868c |
||
+ | 6 6805 |
||
+ | 6 4e03 |
||
+ | 6 43d7 |
||
+ | 5 f749 |
||
+ | 5 f5f5 |
||
+ | a712 seems a promising IV and we have 10 different messages encrypted with the same IV: |
||
+ | <pre> |
||
+ | cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|cut -c 9-|grep ^a712 |
||
+ | a71202303bb038c35f |
||
+ | a7120a71d6528ef2d1 |
||
+ | a7120af9e1382039f2 |
||
+ | a71226587dbb1dfa57 |
||
+ | a712594cf717dd99ff |
||
+ | a7126f6cc1f15a5510 |
||
+ | a7127047b9ea596e4d |
||
+ | a71271abbc20a25c31 |
||
+ | a7127e5ad415a22f61 |
||
+ | a71299662e43a51052 |
||
+ | </pre> |
||
+ | |||
+ | ====Bruteforcing RC4 keystream... or not==== |
||
+ | If IV is constant and the key is the same, the RC4 keystream should be constant. |
||
+ | <br>So we can try to bruteforce it: let's assume one value for the first byte of the keystream and apply it to the first byte of each encrypted message. We should get for the good keystream candidates only plaintexts which are printable ASCII. |
||
+ | <source lang=python> |
||
+ | #!/usr/bin/env python |
||
+ | |||
+ | c=["02303bb038c35f", |
||
+ | "0a71d6528ef2d1", |
||
+ | "0af9e1382039f2", |
||
+ | "26587dbb1dfa57", |
||
+ | "594cf717dd99ff", |
||
+ | "6f6cc1f15a5510", |
||
+ | "7047b9ea596e4d", |
||
+ | "71abbc20a25c31", |
||
+ | "7e5ad415a22f61", |
||
+ | "99662e43a51052", |
||
+ | "b5b3efa256e41a"] |
||
+ | c=[[ord(j) for j in x.decode('hex')] for x in c] |
||
+ | keybyte=0 |
||
+ | for i in range(256): |
||
+ | print i, |
||
+ | for x in c: |
||
+ | print repr(chr(x[keybyte]^i)), |
||
+ | print "" |
||
+ | </source> |
||
+ | <pre> |
||
+ | 0 '\x02' '\n' '\n' '&' '8' 'Y' 'g' 'm' 'o' 'p' 'q' '~' '\x91' '\x99' |
||
+ | 1 '\x03' '\x0b' '\x0b' "'" '9' 'X' 'f' 'l' 'n' 'q' 'p' '\x7f' '\x90' '\x98' |
||
+ | 2 '\x00' '\x08' '\x08' '$' ':' '[' 'e' 'o' 'm' 'r' 's' '|' '\x93' '\x9b' |
||
+ | 3 '\x01' '\t' '\t' '%' ';' 'Z' 'd' 'n' 'l' 's' 'r' '}' '\x92' '\x9a' |
||
+ | 4 '\x06' '\x0e' '\x0e' '"' '<' ']' 'c' 'i' 'k' 't' 'u' 'z' '\x95' '\x9d' |
||
+ | 5 '\x07' '\x0f' '\x0f' '#' '=' '\\' 'b' 'h' 'j' 'u' 't' '{' '\x94' '\x9c' |
||
+ | 6 '\x04' '\x0c' '\x0c' ' ' '>' '_' 'a' 'k' 'i' 'v' 'w' 'x' '\x97' '\x9f' |
||
+ | etc |
||
+ | </pre> |
||
+ | But none of the key candidates can decode the first byte of each of those beacons with same IV systematically into printable char. |
||
+ | <br>Same for the other keystream bytes. |
||
+ | <br>That's the point where I'm lost! |
||
+ | |||
+ | ====Conclusions so far==== |
||
+ | <br>To recap, the situation is: |
||
+ | * byte 16 cannot be consistently a checksum byte, no matter the encoding |
||
+ | * byte 15 cannot be consistently a checksum byte, no matter the encoding |
||
+ | * no keystream is able to decrypt payload into printable chars |
||
+ | * datagram format is described with a variable length in README.md but we only encountered 16-byte long beacons. |
||
+ | As sometimes submitting things like email addresses of encrypted .htpasswd files gave you points I tried to submit all the beacons I got to the scoreboard, without any result. |
||
+ | <!-- I'm almost to the point to think that their Arduino implementation is maybe broken :-( --> |
||
+ | <br>Anybody has a better idea? |
||
+ | <br>You can edit here or ping me on twitter @doegox or reply to [https://twitter.com/doegox/status/484216085672370176 my desperate tweet] :-) |
||
+ | <br>You can get all the 8342 intercepted beacons here: [http://www.yobi.be/files/ucccrm.log ucccrm.log] |
||
+ | <br>They are in chronological order, unfiltered so a few might be corrupted |
||
+ | ====UPDATE June 2015==== |
||
+ | During NDH2015 CTF final I could discuss with the author of this challenge, who was unfortunately not present during the NDH2014 when I was facing those troubles. |
||
+ | <br>It appeared that there was a 1-byte memory leakage in the Arduino code. |
||
+ | <br>So when he started the devic, it was working fine, then he left, then the leakage went on till the Arduino got completely messed up. |
||
+ | <br>So there was nothing we could have done, ''Garbage In, Garbage Out'' |
||
+ | <br>Too bad, but at least now I got an explanation ;) |
||
+ | |||
==2014 Nuit du Hack CTF Quals by Hackerzvoice== |
==2014 Nuit du Hack CTF Quals by Hackerzvoice== |
||
Line 33: | Line 432: | ||
Let's get it out of the mail using munpack from package mpack |
Let's get it out of the mail using munpack from package mpack |
||
munpack Mail |
munpack Mail |
||
− | greg.gif is... a GIF showing #1 world hacker :-) |
||
− | <br>Here again nothing else than the GIF itself in the file. |
||
file greg.gif |
file greg.gif |
||
greg.gif: GIF image data, version 89a, 500 x 645 |
greg.gif: GIF image data, version 89a, 500 x 645 |
||
+ | greg.gif is... a GIF showing #1 world hacker :-) |
||
− | Using gifsicle from the eponym package |
||
+ | <br>[[Image:Greg.gif]] |
||
+ | <br> |
||
+ | <br>Here again, after inspection, nothing else than the GIF itself in the file. |
||
+ | <br>Using gifsicle from the eponym package |
||
<pre> |
<pre> |
||
gifsicle --xinfo greg.gif |
gifsicle --xinfo greg.gif |
||
Line 66: | Line 467: | ||
</pre> |
</pre> |
||
243 colors were used (it's #000000 from 244 to 255) |
243 colors were used (it's #000000 from 244 to 255) |
||
− | <br>They are globally sorted from #FFFFFF to #000000 but there are quirks in the sorting: #126, #129, #191 and #193 in the partial dump above are not sorted properly. |
+ | <br>They are globally sorted from #FFFFFF to #000000 but there are quirks in the sorting: e.g. #126, #129, #191 and #193 in the partial dump above are not sorted properly. |
+ | <br>Clearly that can be a way to hide info but the problem as often in steganography challenges is that it's hard to guess ''how'' info was stored without the original tool. |
||
+ | <br>And indeed the mail was talking about a modified tool, remember? |
||
+ | <br>Googling for "gif stegano colormap" [http://steganography.wikispaces.com/Steganography+Tools one of the hits] mentioned ''[http://www.darkside.com.au/gifshuffle/ gifshuffle]'': a tool ''used to conceal messages which are stored in GIF images by “shuffling” the color map''. |
||
+ | <br>That sounds promising! |
||
+ | <br>And source code is available: http://www.darkside.com.au/gifshuffle/gifshuffle.tar.gz |
||
+ | <br>It compiles smoothly. |
||
+ | <br>Its man page explains very well how it embeds information by first sorting the colormap then changing the order of some of the colors. |
||
+ | <br>Exactly what we observed! |
||
+ | <br>There is also an encryption mode but that would be hard to crack and when used the colormap looks completely randomly sorted, which is not what we observed, luckily! |
||
+ | <br>There is also a basic compression option |
||
+ | <br>But when we try on the gif: |
||
+ | ./gifshuffle greg.gif |
||
+ | <output is binary garbage> |
||
+ | ./gifshuffle -C greg.gif |
||
+ | uttEioe)udi |
||
+ | sgahat aam wttf?ltkt tc(w a't weuos nllopcdyiccefohtr*c'npi na iarinrrrmgY rtolra"dbedna0tywUpea)ph ic.i@t)w i Rdlnos |
||
+ | igoi atyd tns s t hb bs0.iianiubiksiata gOobroiaw ..occsasniDtmdder tdpcatr. nI*ti inmuaoitdfK voesd ab leany0. l,nnetuCi .aabsas th e g |
||
+ | Eo)bvrncitrid |
||
+ | sa n. Msdree |
||
+ | Hmmm. |
||
+ | <br>Remember the mail, the tool was modified by the sender... |
||
+ | <br>We can use gifshuffle ourselves on a gif to add some data into it. |
||
+ | <br>We can use greg.gif as it will first sort the table, erasing the old data, then embed ours. |
||
+ | ./gifshuffle -m fooooooooooooooooooooooooooooooooooooooooooooooooooooo greg.gif greg2.gif |
||
+ | Message used approximately 27.25% of available space. |
||
+ | <pre> |
||
+ | gifsicle --color-info greg2.gif |
||
+ | * greg2.gif 1 image |
||
+ | logical screen 500x645 |
||
+ | global color table [256] |
||
+ | | 0: #000000 64: #3B608F 128: #7C9CC5 192: #95675B |
||
+ | | 1: #010001 65: #3C4230 129: #7CA2CD 193: #9C9499 |
||
+ | | 2: #030915 66: #44555D 130: #7CA3D2 194: #968C94 |
||
+ | [...] |
||
+ | | 61: #372819 125: #7B654B 189: #DFC69D 253: #000000 |
||
+ | | 62: #372925 126: #7B9ECB 190: #E6D9D8 254: #000000 |
||
+ | | 63: #394A58 127: #7C7456 191: #CFD8E9 255: #000000 |
||
+ | background 214 |
||
+ | + image #0 500x645 |
||
+ | </pre> |
||
+ | See, sorted colormap except for #191, #194 |
||
+ | <br>But global sorting is from #000000 to #FFFFFF (then some #000000 for the unused colors) |
||
+ | <br>That's the thing we need to modify too in the tool |
||
+ | <br>gifshuffle/encode.c colourmap_encode() starts by sorting the colormap: |
||
+ | <source lang=c> |
||
+ | if (encrypting_colourmap ()) { |
||
+ | ... |
||
+ | qsort (ci_array, ncols, sizeof (CMAP_INFO), cmap_encrypt_cmp); |
||
+ | } else |
||
+ | qsort (ci_array, ncols, sizeof (CMAP_INFO), cmap_cmp); |
||
+ | </source> |
||
+ | Sorting is based on the comparison function cmap_cmp() defined in the same file: |
||
+ | <source lang=c> |
||
+ | static int cmap_cmp ( const void *p1, const void *p2) { |
||
+ | return (rgb_cmp (&((CMAP_INFO *) p1)->rgb, &((CMAP_INFO *) p2)->rgb)); |
||
+ | } |
||
+ | </source> |
||
+ | Let's just swap its arguments to inverse its effect: |
||
+ | <source lang=c> |
||
+ | static int cmap_cmp ( const void *p1, const void *p2) { |
||
+ | return (rgb_cmp (&((CMAP_INFO *) p2)->rgb, &((CMAP_INFO *) p1)->rgb)); |
||
+ | } |
||
+ | </source> |
||
+ | Let's try it: |
||
+ | gifshuffle_inverted greg.gif |
||
+ | The flag for this wonderful Gregory D. Evans picture is : L0n9L1v3TheW0rldN00n3Hack3rGr3g0ryD.3van |
||
+ | ===[http://quals.nuitduhack.com/challenges/view/1 Another One]=== |
||
+ | The Another One was a crypto challenge: |
||
+ | |||
+ | ''This is a crypted image, you must extract the data.'' |
||
+ | <br>''Score 300'' |
||
+ | <br>''Link http://static.nuitduhack.com/crypted.bmp'' |
||
+ | |||
+ | Let's get this one: |
||
+ | <pre> |
||
+ | wget http://static.nuitduhack.com/crypted.bmp |
||
+ | hd crypted.bmp |head |
||
+ | 00000000 16 3c 04 03 3d 66 9e 02 ad 35 5d d0 3d ea 22 1e |.<..=f...5].=.".| |
||
+ | 00000010 c2 6c 32 02 13 d4 51 cc 37 3b 04 a9 7a ab 24 44 |.l2...Q.7;..z.$D| |
||
+ | 00000020 ed dd 39 17 4c 18 8e 20 f3 42 ab 86 c4 1c 4c a4 |..9.L.. .B....L.| |
||
+ | 00000030 da dc e7 65 22 33 97 9c f5 95 fd e1 34 cd a7 bb |...e"3......4...| |
||
+ | 00000040 fb 5e a1 b7 89 5c 5e 8d a7 5a 0d df 03 00 fe 3b |.^...\^..Z.....;| |
||
+ | * |
||
+ | 00001ec0 35 2e 5c 08 66 50 4d ab 60 04 d5 db da 80 32 17 |5.\.fPM.`.....2.| |
||
+ | 00001ed0 fb 5e a1 b7 89 5c 5e 8d a7 5a 0d df 03 00 fe 3b |.^...\^..Z.....;| |
||
+ | * |
||
+ | 00003180 d4 76 9b 3c d0 aa a5 57 e2 11 a3 e6 9e ab c5 6d |.v.<...W.......m| |
||
+ | </pre> |
||
+ | |||
+ | A 128 bits sequence is repeating itself quite often: |
||
+ | fb 5e a1 b7 89 5c 5e 8d a7 5a 0d df 03 00 fe 3b |
||
+ | This is typical of the usage of a block cipher in ECB mode when plaintext is repeating, which is likely to happen on an encrypted BMP drawing (much less likely if it's an actual photography). |
||
+ | <br>Let's XOR the file with that sequence and 0xFF to get 0xFF (white pixels?) (very dirty way... at CTF we want to code fast, not the program to be the most optimized...) |
||
+ | <source lang=python> |
||
+ | #!/usr/bin/env python |
||
+ | c=open("crypted.bmp", 'rb').read() |
||
+ | p=open("out.bmp",'wb') |
||
+ | k=[] |
||
+ | for i in range(len(c)): |
||
+ | if not k: |
||
+ | k=list("fb 5e a1 b7 89 5c 5e 8d a7 5a 0d df 03 00 fe 3b".replace(" ","").decode('hex')) |
||
+ | p.write(chr(ord(c[i]) ^ ord(k.pop(0)) ^ 0xFF)) |
||
+ | p.close() |
||
+ | </source> |
||
+ | The result is a file with many zeroes but it's still not a valid BMP (BTW is it really a BMP? Can we trust the file extension??). The background color was not encoded with 0x00 apparently. |
||
+ | <br>Neither with 0xFF, we tested it by XORing additionally with 0xFF. |
||
+ | <br>Time to read some [https://en.wikipedia.org/wiki/BMP_file_format BMP file format] description... |
||
+ | <br>Guessing what would be the initial header content is quite hard. |
||
+ | <br>I took another approach: |
||
+ | <br>Dump the raw content in a PPM file by adding a simple header. |
||
+ | <br>PPM encodes each pixel in 3 bytes |
||
+ | <br>BMP can encode each pixel in 2,3 or 4 bytes |
||
+ | <br>We'll see... |
||
+ | <br>We've also to provide the image size... Let's try 1200x1200: (1200*1200*3=4320000, the filesize) |
||
+ | <br>We also flip top and bottom as BMP encodes the image from bottom to top |
||
+ | <source lang=bash> |
||
+ | (echo -e -n "P6\n1200\n1200\n255\n";cat out.bmp)|pnmflip -tb|pnmtopng > out.png |
||
+ | </source> |
||
+ | <br> |
||
+ | [[File:ndhanotherone1.png]] |
||
+ | <br> |
||
+ | <br>We see things! |
||
+ | <br>Playing around with the image size, we find out a satisfying ratio: |
||
+ | <source lang=bash> |
||
+ | (echo -e -n "P6\n1600\n900\n255\n";cat out.bmp)|pnmflip -tb|pnmtopng > out.png |
||
+ | </source> |
||
+ | <br> |
||
+ | [[File:ndhanotherone2.png]] |
||
+ | <br> |
||
+ | <br>And this is enough to get our flag! |
||
+ | <br>Never never use EBC mode ;-) |
||
+ | <br>The quality is enough but actually one can do better, e.g. in cases the image is not that clear. |
||
+ | <br>By carefully choosing replacements for repeating or non-repeating ECB blocks we can obtain something like this: |
||
+ | <br> |
||
+ | [[File:ndhanotherone3.png]] |
||
+ | <br> |
||
+ | See https://doegox.github.io/ElectronicColoringBook/ for more ECB coloring fun! |
Latest revision as of 10:37, 29 June 2015
2015 Nuit du Hack CTF Quals by Hackerzvoice
It was a great moment of fun to participate to this year's CTF Quals organised by Hackerzvoice
Solving challenges involved all Pollypocket team members, here is only some polished results.
Game-of-Life
tar xzf GOL.tar.gz ./jdlv.py $RANDOM cipher.txt |grep -ia flag Flag : ToBeAndToLast
Yes, $RANDOM, any password works!
If you want a much nicer write-up, see here but honestly I don't see the point of spending so much time to solve it and writing a cracking script...
Bleep
"The clock is running. Make the most of today. Time waits for no man. Yesterday is history. Tomorrow is a mystery. Today is a gift. That's why it is called the present." (A)
Bleeeep Bleep Bleep Bleep Bleep Bleeeep Bleep Bleep Bleep Bleep Bleep Bleeeep Bleeeep Bleep.
I'm the bleep, unbleep me.
It was a 200 misc but only solved by 6 teams so I think it deserves a little write-up :-)
You get a file Bleep.tar.gz
It contains a "consigne" (FR for instructions) text file:
[ Bleep ] [+] You got this .hex file from a firmware dump can, you understand what's going on?
And a chall.hex that looks like:
:100000000C945F000C9487000C9487000C9487007C :100010000C9487000C9487000C9487000C94870044 :100020000C9487000C9487000C9487000C94870034 etc.
chall.hex is indeed a firmware image in the classical Intel HEX format.
One can use e.g. http://sourceforge.net/projects/hex2bin/ to convert it to a bin/raw format.
By an educated guess (what's most popular microcontroller especially in non-elec circles?) we assume it could be Arduino / Atmel AVR.
Actually knowing this kind of details is not that important ;-)
So the initial approach was to open the firmware image in Ida but I felt demotivated and didn't want to reverse all those little interrupt-driven functions.
From the description (Bleep Bleeeep) the microcontroller is probably outputting a kind of Morse-encoded signal.
Using my ElectronicColoringBook.py I can see quite a lot of patterns in the binary:
./ElectronicColoringBook.py -x 128 chall.bin
And a quick look at the disassembly confirms the repetition of patterns made of args & fct calls:
avr-objdump -D -m avr5 chall.hex [...] 17a: 61 e0 ldi r22, 0x01 ; 1 17c: 80 91 00 01 lds r24, 0x0100 180: 0e 94 a5 0a call 0x154a ; 0x154a 184: 68 ee ldi r22, 0xE8 ; 232 186: 73 e0 ldi r23, 0x03 ; 3 188: 80 e0 ldi r24, 0x00 ; 0 18a: 90 e0 ldi r25, 0x00 ; 0 18c: 0e 94 46 0b call 0x168c ; 0x168c 190: 60 e0 ldi r22, 0x00 ; 0 192: 80 91 00 01 lds r24, 0x0100 196: 0e 94 a5 0a call 0x154a ; 0x154a 19a: 68 ee ldi r22, 0xE8 ; 232 19c: 73 e0 ldi r23, 0x03 ; 3 19e: 80 e0 ldi r24, 0x00 ; 0 1a0: 90 e0 ldi r25, 0x00 ; 0 1a2: 0e 94 46 0b call 0x168c ; 0x168c 1a6: 61 e0 ldi r22, 0x01 ; 1 1a8: 80 91 00 01 lds r24, 0x0100 1ac: 0e 94 a5 0a call 0x154a ; 0x154a 1b0: 68 ee ldi r22, 0xE8 ; 232 1b2: 73 e0 ldi r23, 0x03 ; 3 1b4: 80 e0 ldi r24, 0x00 ; 0 1b6: 90 e0 ldi r25, 0x00 ; 0 1b8: 0e 94 46 0b call 0x168c ; 0x168c 1bc: 60 e0 ldi r22, 0x00 ; 0 1be: 80 91 00 01 lds r24, 0x0100 1c2: 0e 94 a5 0a call 0x154a ; 0x154a 1c6: 68 ee ldi r22, 0xE8 ; 232 1c8: 73 e0 ldi r23, 0x03 ; 3 1ca: 80 e0 ldi r24, 0x00 ; 0 1cc: 90 e0 ldi r25, 0x00 ; 0 1ce: 0e 94 46 0b call 0x168c ; 0x168c 1d2: 61 e0 ldi r22, 0x01 ; 1 1d4: 80 91 00 01 lds r24, 0x0100 1d8: 0e 94 a5 0a call 0x154a ; 0x154a 1dc: 64 ef ldi r22, 0xF4 ; 244 1de: 71 e0 ldi r23, 0x01 ; 1 1e0: 80 e0 ldi r24, 0x00 ; 0 1e2: 90 e0 ldi r25, 0x00 ; 0 1e4: 0e 94 46 0b call 0x168c ; 0x168c etc
Looking at the byte level it means we have an interleaved repetition of the following patterns:
- 60e0809100010e94a50a
- 61e0809100010e94a50a
- 60ed77e080e090e00e94460b
- 64ef71e080e090e00e94460b
- 68ee73e080e090e00e94460b
Now time for some black magic one-liner! Let's print the patterns in a more compact form:
dd if=chall.bin bs=1 skip=378 count=1000 2>/dev/null|xxd -p|tr -d '\n' |\ sed 's/61e0809100010e94a50a/^/g;s/68ee73e080e090e00e94460b/./g;s/60e0809100010e94a50a/_/g;s/64ef71e080e090e00e94460b/o/g;s/60ed77e080e090e00e94460b/O/g' ^._.^._.^o_O^._.^._.^._O^._.^o_.^o_O^._.^._O^._.^._.^._O^._.^o_.^o_O^o_O^._.^._.^._O^._.^._
At first I tried to interpret the 5 individual patterns as Morse . and _ and as pauses (short between symbols, longer between letters, longer between words) but nothing made properly sense.
Then I realized combination of patterns was the key: one was there to toggle the output (^ =up, _ =down) and the other one indicates duration.
^o = . ^. = - _. = pause between symbols _O = pause between letters
So we can convert our previous output with
sed 's/\^\./-/g;s/\^o/./g;s/\^O/@/g;s/_\.//g;s/_o/ /g;s/_O/ /g'
or more elegantly rewrite our one-liner as: (split in several lines to show up more nicely on this wiki)
dd if=chall.bin bs=1 skip=378 count=990 2>/dev/null|xxd -p|tr -d '\n' |\ sed 's/61e0809100010e94a50a64ef71e080e090e00e94460b/./g; s/61e0809100010e94a50a68ee73e080e090e00e94460b/-/g; s/60e0809100010e94a50a68ee73e080e090e00e94460b//g; s/60e0809100010e94a50a60ed77e080e090e00e94460b/ /g; s/$/\n/' --. --- -.. -- --- -.. . --- --
Which translates to "GODMODEOM".
The flag didn't validate but "GODMODEON" worked so last symbol should be a "." and not a "-", maybe an error in the challenge or a side effect?
Actually by carefully tuning the options and selecting a suitable color palette, my ElectronicColoringBook.py can reveal directly the code too: (red=-, green=.)
./ElectronicColoringBook.py -x 1280 -b 22 -o 11.18 -P "#ff0000#aaaaaa#000000#00ff00#000000" chall.bin
Weshgrow
wget -q -O - --content-on-error "http://weshgrow.challs.nuitduhack.com/flag?hmac=%fd"|grep FLAG <li onclick="toggle('pre29839800', 'post29839800')"> flag=FLAG</li> <tr><td>fvars</td><td class="code"><div>{'FLAG': 'Can_I_haz_s3cureD_hm4c_plz?',
This python stacktrace because of the utf8 parser is probably not the intended way ;-)
For a write-up of the "real" solution, see here
2014 Nuit du Hack CTF by Hackerzvoice
Unter Cab Command and Control Radio Module
Not exactly a write-up as I could not finish this one but still, here are my notes:
Discovering
We got a DVB-T dongle and its drivers on a Windows CD.
I directly recognize this kind of beast, it's a RTL-SDR-based dongle which means it gets a 50MHz-2GHz tuner and sends its I/Q samples to the PC where everything else is done in pure software.
On our server we could find a README.md with the following content:
Unter Cab Command and Control Radio Module ========================================== Description ----------- This document describes the radio module designed by Unter to broadcast data to cabs through Radio Frequency. Specifications -------------- * Baseband: 433.92 MHz * Encryption used: Ron's code * Micro-controler: ATmega328P-PU * Data structure: Datagram-based TX/RX * CRC error control Datagram -------- * 0x00: Magic (3 bytes) * 0x03: Reserved (3 bytes) * 0x06: IV (2 bytes) * 0x08: Encrypted DATA (N bytes) * 0x08 + N: Datagram CRC (from 0x00 to (0x08 + N), 1 byte) )
Microcontroller seems to indicate the emitter is made with an Arduino.
Ron's code refers probably to Ron Rivest RC family. Later someone from the orga confirmed it was RC4.
Scanning the radio bands
I first ran gqrx to see a waterfall around 433.92 MHz
There were indeed strong "pulses" every second around 433.815 MHz
Not exactly the same frequency but maybe the Arduino or the dongle weren't that precise so it's always good to first have a look with gqrx to fine tune the frequency to watch.
With gqrx you can also listen to the chosen frequency and try different demodulations like FM, AM, ... and clearly AM gave the less noisy signal.
Acquiring analog signal
At this point you can make your own demodulator with GnuRadio but if it's just to demodulate AM you can also use rtl_fm which is part of the basic librtlsdr tools (librtlsdr-bin on Debian).
rtl_fm -f 433815000 -s 12k -p 45 -l 50 -M -o 4 data.out
Then opening data.out in Audacity, we see:
Zooming:
which can be decoded visually into (H=high / L=low)
HLHHLLLHHLHHHLHHHLHHLHHHHHHHHHHHHHHHHHHHHHLHHLLHHLLLHHLLLHHLLHLHLHHHLHHHHHHHHLLHLLLHLLHLLHHLLLHHHLHHLLLLLLHHHHHLHLHHHHHHLHHLLH
Decoding the captured signal
Now is H=1 and L=0 or vice versa?
Let's assume H=1:
10110001101110111011011111111111111111111111011001100011000110010101110111111110010001001001100011101100000011111010111111011001 = B1BBB7FFFFF663195DFE4498EC0FAFD9
Let's assume H=0:
= 4E44480000099CE6A201BB6713F05026
From README.md, first three bytes are "magic", a signature and as 4E4448 = "NDH" that's probably the right decoding :-)
So if we try to decode the frame:
magic. reserv IV.. Encrypted..... CRC 4E4448 000009 9CE6 A201BB6713F050 26
Then I wrote a little crappy python script to decode the analog AM signal, which can take rtl_fm output in live:
#!/usr/bin/python
import os, sys, struct
scaledown=10
x=struct.unpack_from('h',sys.stdin.read(2),0)[0]
d, t, mode, s=0, 0, 1, ""
def printbits(bitstring):
bytes=len(bitstring) / 8
if bytes > 0:
n=0
while len(bitstring)>7:
n=(n<<8) + \
(int(bitstring[0])<<7) + (int(bitstring[1])<<6) + (int(bitstring[2])<<5) + (int(bitstring[3])<<4) + \
(int(bitstring[4])<<3) + (int(bitstring[5])<<2) + (int(bitstring[6])<<1) + int(bitstring[7])
bitstring=bitstring[8:]
print '%0*X' % (bytes*2,n)
while True:
x2=struct.unpack_from('h',sys.stdin.read(2),0)[0]
d2=(x2-x)>>scaledown
if d2 < 0:
d2=d2+1
if d2==0 or (d>0 and d2>0 and t<30) or (d<0 and d2<0 and t<30):
t=t+1
else:
if d<0:
t=-t
if (abs(t)>50):
if mode == 2:
mode=1
if tl > 5:
s+="0"
else:
s+="1"
if len(s) == 16*8:
printbits(s)
pass
s=""
else:
if mode == 1:
tl=t
mode=2
else:
if mode == 2:
mode=1
if tl >= 5:
s+="0"
else:
s+="1"
d=d2
t=1
x=x2
rtl_fm -f 433815000 -s 12k -p 45 -l 50 -M -o 4 - |./raw2bin.py
I collected about 8342 such beacons, which you can download here
Filtering thanks to CRC... or not
Some may be wrong hopefully there is a CRC so let's filter them:
Hum not so easy, there are several CRC definitions over a byte.
After many attempts, even trying XOR, nothing works :-(
Ok let's take another approach:
cat ucccrm.log |sort|uniq -c|sort -r -n|uniq -c -w 7|cut -c -15 11 3 228 2 7853 1
7853 beacons are unique, 228 repeat once and 11 repeat twice.
But something striked me when I looked at the sorted beacons:
cat ucccrm.log |sort|head 4e44480000090021b8ae87bdb188bc05 4e44480000090021b8ae87bdb188bc07 4e44480000090021b8ae87bdb188bc0a 4e44480000090021b8ae87bdb188bc0e 4e44480000090021b8ae87bdb188bc13 4e44480000090021b8ae87bdb188bc16 4e44480000090021b8ae87bdb188bc1b 4e44480000090021b8ae87bdb188bc1d 4e44480000090021b8ae87bdb188bc1e 4e44480000090021b8ae87bdb188bc1e
Only the CRC byte is varying at each line!!
That can't be.
Another team considered the beacons were 15 bytes instead of 16 bytes (I think their decoder simply missed the last bit, therefore the last byte).
Ok let's assume we remove the last byte so now the 15th byte becomes the CRC.
The problem is that you can also find many things like this:
cat ucccrm.log|grep -o "4e4448....09002163703a8a832a.." 4e44487f4709002163703a8a832aea 4e4448bde209002163703a8a832aea 4e444848ba09002163703a8a832aea 4e44480af709002163703a8a832aea
Here we get beacons with different reserved bytes but still always the same 15th byte so it can't be CRC neither.
Filtering thanks to IVs & packets repetition
Forget about CRC and let's investigate those IVs:
cat ucccrm.log |cut -c 13-16|sort|uniq -c|sort -n -r|head 228 0ff6 206 a712 164 960b 164 6805 157 790c 151 ef1a 140 0021 137 4e03 128 20fd 123 c114
Oops, not that random! Actually a closer inspection shows that for a given value for the first IV byte, the second part of the IV is always the same:
cat ucccrm.log |cut -c 13-16|grep ^0f|uniq -c 228 0ff6
Let's try to look at the IV+payload and filter errors without the CRC:
cat ucccrm.log |cut -c 13-30|sort|wc -l 8342 cat ucccrm.log |cut -c 13-30|sort|uniq|wc -l 1518
Out of those 1518 unique beacons we want to filter away those that don't repeat less than three times:
cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|wc -l 596
Ok it seems we've 596 reliable unique beacon values that have been seen at least three times, so those ones are unlikely to be corrupted.
Top cryptograms are:
cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|sort -n -r|head 126 0ff62634969a205607 97 790c0c02ced3211618 89 a71299662e43a51052 88 68050eb867d375b4ae 77 20fdd32f4c6d471ae1 74 0021b8ae87bdb188bc 63 ef1af164362a274bd6 62 c1148882b72d4db20e 62 4e03b62b9150d90d91 61 960bc050ef31b7a950 cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|cut -c 9- > uniq_cryptograms.log
Investigating IVs further
Out of those beacons, let's see those starting with the same IV:
cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|cut -c 9-|sort |uniq -w 4 -c|sort -r -n|cut -c 4-12|head 10 a712 7 960b 6 b53d 6 9d90 6 868c 6 6805 6 4e03 6 43d7 5 f749 5 f5f5
a712 seems a promising IV and we have 10 different messages encrypted with the same IV:
cat ucccrm.log |cut -c 13-30|sort|uniq -c|grep -v "^ *[12] "|cut -c 9-|grep ^a712 a71202303bb038c35f a7120a71d6528ef2d1 a7120af9e1382039f2 a71226587dbb1dfa57 a712594cf717dd99ff a7126f6cc1f15a5510 a7127047b9ea596e4d a71271abbc20a25c31 a7127e5ad415a22f61 a71299662e43a51052
Bruteforcing RC4 keystream... or not
If IV is constant and the key is the same, the RC4 keystream should be constant.
So we can try to bruteforce it: let's assume one value for the first byte of the keystream and apply it to the first byte of each encrypted message. We should get for the good keystream candidates only plaintexts which are printable ASCII.
#!/usr/bin/env python
c=["02303bb038c35f",
"0a71d6528ef2d1",
"0af9e1382039f2",
"26587dbb1dfa57",
"594cf717dd99ff",
"6f6cc1f15a5510",
"7047b9ea596e4d",
"71abbc20a25c31",
"7e5ad415a22f61",
"99662e43a51052",
"b5b3efa256e41a"]
c=[[ord(j) for j in x.decode('hex')] for x in c]
keybyte=0
for i in range(256):
print i,
for x in c:
print repr(chr(x[keybyte]^i)),
print ""
0 '\x02' '\n' '\n' '&' '8' 'Y' 'g' 'm' 'o' 'p' 'q' '~' '\x91' '\x99' 1 '\x03' '\x0b' '\x0b' "'" '9' 'X' 'f' 'l' 'n' 'q' 'p' '\x7f' '\x90' '\x98' 2 '\x00' '\x08' '\x08' '$' ':' '[' 'e' 'o' 'm' 'r' 's' '|' '\x93' '\x9b' 3 '\x01' '\t' '\t' '%' ';' 'Z' 'd' 'n' 'l' 's' 'r' '}' '\x92' '\x9a' 4 '\x06' '\x0e' '\x0e' '"' '<' ']' 'c' 'i' 'k' 't' 'u' 'z' '\x95' '\x9d' 5 '\x07' '\x0f' '\x0f' '#' '=' '\\' 'b' 'h' 'j' 'u' 't' '{' '\x94' '\x9c' 6 '\x04' '\x0c' '\x0c' ' ' '>' '_' 'a' 'k' 'i' 'v' 'w' 'x' '\x97' '\x9f' etc
But none of the key candidates can decode the first byte of each of those beacons with same IV systematically into printable char.
Same for the other keystream bytes.
That's the point where I'm lost!
Conclusions so far
To recap, the situation is:
- byte 16 cannot be consistently a checksum byte, no matter the encoding
- byte 15 cannot be consistently a checksum byte, no matter the encoding
- no keystream is able to decrypt payload into printable chars
- datagram format is described with a variable length in README.md but we only encountered 16-byte long beacons.
As sometimes submitting things like email addresses of encrypted .htpasswd files gave you points I tried to submit all the beacons I got to the scoreboard, without any result.
Anybody has a better idea?
You can edit here or ping me on twitter @doegox or reply to my desperate tweet :-)
You can get all the 8342 intercepted beacons here: ucccrm.log
They are in chronological order, unfiltered so a few might be corrupted
UPDATE June 2015
During NDH2015 CTF final I could discuss with the author of this challenge, who was unfortunately not present during the NDH2014 when I was facing those troubles.
It appeared that there was a 1-byte memory leakage in the Arduino code.
So when he started the devic, it was working fine, then he left, then the leakage went on till the Arduino got completely messed up.
So there was nothing we could have done, Garbage In, Garbage Out
Too bad, but at least now I got an explanation ;)
2014 Nuit du Hack CTF Quals by Hackerzvoice
It was a great moment of fun to participate to this year's CTF Quals organised by Hackerzvoice
Solving challenges involved all Pollypocket team members, here is only some polished results.
The greatest
The greatest was a steganography challenge:
We are sure that this e-mail contains hidden information, go get it !
Score 500
Link http://static.nuitduhack.com/mail.tar
Let's get this one:
wget http://static.nuitduhack.com/mail.tar file mail.tar mail.tar: POSIX tar archive (GNU)
And a quick inspection through an hexadecimal editor didn't reveal anything suspicious or noticeable.
So let's open it:
tar tvf mail.tar -rw-r--r-- null/null 296008 2014-04-05 07:05 Mail tar xvf mail.tar
And we get a file called Mail containing an email from BOOBA#rapfr.fr to theflag#nuitduhack.com
Hi dude! Check out this pic. I used the cool tool I told you about last time, except that I played around with the code a bit. Speaking of tools, Gregory Evans right? Have fun trying to find the hidden data ;) Peace out.
Together with an attachment (well, two attachments as the email was text+html)
The html version differed slightly ("this pic" => "this pick") but that didn't reveal to be of importance.
The other attachment:
Content-Type: image/gif; name=greg.gif Content-Disposition: attachment; filename=greg.gif Content-Transfer-Encoding: base64
Let's get it out of the mail using munpack from package mpack
munpack Mail file greg.gif greg.gif: GIF image data, version 89a, 500 x 645
greg.gif is... a GIF showing #1 world hacker :-)
Here again, after inspection, nothing else than the GIF itself in the file.
Using gifsicle from the eponym package
gifsicle --xinfo greg.gif * greg.gif 1 image logical screen 500x645 global color table [256] background 65 + image #0 500x645
There is no much possibilities for stegano in GIF as the image is made of refs to the colormap so it could be:
- position of pixels of a given color
- duplicates or alike in the colormap (e.g. #cccccc and #cccbcc) or other tricks
So let's dump the colormap:
gifsicle --color-info greg.gif * greg.gif 1 image logical screen 500x645 global color table [256] | 0: #FFFFFF 64: #A3835C 128: #1E3E71 192: #769DD1 | 1: #FCF5F6 65: #A37F81 129: #030915 193: #0F314D | 2: #F5E9E8 66: #A27C58 130: #546473 194: #5982BB [...] | 61: #A48A64 125: #675847 189: #4B3A47 253: #000000 | 62: #A3BCE1 126: #101627 190: #7CA2CD 254: #000000 | 63: #A38C6B 127: #4C6169 191: #594837 255: #000000 background 65 + image #0 500x645
243 colors were used (it's #000000 from 244 to 255)
They are globally sorted from #FFFFFF to #000000 but there are quirks in the sorting: e.g. #126, #129, #191 and #193 in the partial dump above are not sorted properly.
Clearly that can be a way to hide info but the problem as often in steganography challenges is that it's hard to guess how info was stored without the original tool.
And indeed the mail was talking about a modified tool, remember?
Googling for "gif stegano colormap" one of the hits mentioned gifshuffle: a tool used to conceal messages which are stored in GIF images by “shuffling” the color map.
That sounds promising!
And source code is available: http://www.darkside.com.au/gifshuffle/gifshuffle.tar.gz
It compiles smoothly.
Its man page explains very well how it embeds information by first sorting the colormap then changing the order of some of the colors.
Exactly what we observed!
There is also an encryption mode but that would be hard to crack and when used the colormap looks completely randomly sorted, which is not what we observed, luckily!
There is also a basic compression option
But when we try on the gif:
./gifshuffle greg.gif <output is binary garbage> ./gifshuffle -C greg.gif uttEioe)udi sgahat aam wttf?ltkt tc(w a't weuos nllopcdyiccefohtr*c'npi na iarinrrrmgY rtolra"dbedna0tywUpea)ph ic.i@t)w i Rdlnos igoi atyd tns s t hb bs0.iianiubiksiata gOobroiaw ..occsasniDtmdder tdpcatr. nI*ti inmuaoitdfK voesd ab leany0. l,nnetuCi .aabsas th e g Eo)bvrncitrid sa n. Msdree
Hmmm.
Remember the mail, the tool was modified by the sender...
We can use gifshuffle ourselves on a gif to add some data into it.
We can use greg.gif as it will first sort the table, erasing the old data, then embed ours.
./gifshuffle -m fooooooooooooooooooooooooooooooooooooooooooooooooooooo greg.gif greg2.gif Message used approximately 27.25% of available space.
gifsicle --color-info greg2.gif * greg2.gif 1 image logical screen 500x645 global color table [256] | 0: #000000 64: #3B608F 128: #7C9CC5 192: #95675B | 1: #010001 65: #3C4230 129: #7CA2CD 193: #9C9499 | 2: #030915 66: #44555D 130: #7CA3D2 194: #968C94 [...] | 61: #372819 125: #7B654B 189: #DFC69D 253: #000000 | 62: #372925 126: #7B9ECB 190: #E6D9D8 254: #000000 | 63: #394A58 127: #7C7456 191: #CFD8E9 255: #000000 background 214 + image #0 500x645
See, sorted colormap except for #191, #194
But global sorting is from #000000 to #FFFFFF (then some #000000 for the unused colors)
That's the thing we need to modify too in the tool
gifshuffle/encode.c colourmap_encode() starts by sorting the colormap:
if (encrypting_colourmap ()) {
...
qsort (ci_array, ncols, sizeof (CMAP_INFO), cmap_encrypt_cmp);
} else
qsort (ci_array, ncols, sizeof (CMAP_INFO), cmap_cmp);
Sorting is based on the comparison function cmap_cmp() defined in the same file:
static int cmap_cmp ( const void *p1, const void *p2) {
return (rgb_cmp (&((CMAP_INFO *) p1)->rgb, &((CMAP_INFO *) p2)->rgb));
}
Let's just swap its arguments to inverse its effect:
static int cmap_cmp ( const void *p1, const void *p2) {
return (rgb_cmp (&((CMAP_INFO *) p2)->rgb, &((CMAP_INFO *) p1)->rgb));
}
Let's try it:
gifshuffle_inverted greg.gif The flag for this wonderful Gregory D. Evans picture is : L0n9L1v3TheW0rldN00n3Hack3rGr3g0ryD.3van
Another One
The Another One was a crypto challenge:
This is a crypted image, you must extract the data.
Score 300
Link http://static.nuitduhack.com/crypted.bmp
Let's get this one:
wget http://static.nuitduhack.com/crypted.bmp hd crypted.bmp |head 00000000 16 3c 04 03 3d 66 9e 02 ad 35 5d d0 3d ea 22 1e |.<..=f...5].=.".| 00000010 c2 6c 32 02 13 d4 51 cc 37 3b 04 a9 7a ab 24 44 |.l2...Q.7;..z.$D| 00000020 ed dd 39 17 4c 18 8e 20 f3 42 ab 86 c4 1c 4c a4 |..9.L.. .B....L.| 00000030 da dc e7 65 22 33 97 9c f5 95 fd e1 34 cd a7 bb |...e"3......4...| 00000040 fb 5e a1 b7 89 5c 5e 8d a7 5a 0d df 03 00 fe 3b |.^...\^..Z.....;| * 00001ec0 35 2e 5c 08 66 50 4d ab 60 04 d5 db da 80 32 17 |5.\.fPM.`.....2.| 00001ed0 fb 5e a1 b7 89 5c 5e 8d a7 5a 0d df 03 00 fe 3b |.^...\^..Z.....;| * 00003180 d4 76 9b 3c d0 aa a5 57 e2 11 a3 e6 9e ab c5 6d |.v.<...W.......m|
A 128 bits sequence is repeating itself quite often:
fb 5e a1 b7 89 5c 5e 8d a7 5a 0d df 03 00 fe 3b
This is typical of the usage of a block cipher in ECB mode when plaintext is repeating, which is likely to happen on an encrypted BMP drawing (much less likely if it's an actual photography).
Let's XOR the file with that sequence and 0xFF to get 0xFF (white pixels?) (very dirty way... at CTF we want to code fast, not the program to be the most optimized...)
#!/usr/bin/env python
c=open("crypted.bmp", 'rb').read()
p=open("out.bmp",'wb')
k=[]
for i in range(len(c)):
if not k:
k=list("fb 5e a1 b7 89 5c 5e 8d a7 5a 0d df 03 00 fe 3b".replace(" ","").decode('hex'))
p.write(chr(ord(c[i]) ^ ord(k.pop(0)) ^ 0xFF))
p.close()
The result is a file with many zeroes but it's still not a valid BMP (BTW is it really a BMP? Can we trust the file extension??). The background color was not encoded with 0x00 apparently.
Neither with 0xFF, we tested it by XORing additionally with 0xFF.
Time to read some BMP file format description...
Guessing what would be the initial header content is quite hard.
I took another approach:
Dump the raw content in a PPM file by adding a simple header.
PPM encodes each pixel in 3 bytes
BMP can encode each pixel in 2,3 or 4 bytes
We'll see...
We've also to provide the image size... Let's try 1200x1200: (1200*1200*3=4320000, the filesize)
We also flip top and bottom as BMP encodes the image from bottom to top
(echo -e -n "P6\n1200\n1200\n255\n";cat out.bmp)|pnmflip -tb|pnmtopng > out.png
We see things!
Playing around with the image size, we find out a satisfying ratio:
(echo -e -n "P6\n1600\n900\n255\n";cat out.bmp)|pnmflip -tb|pnmtopng > out.png
And this is enough to get our flag!
Never never use EBC mode ;-)
The quality is enough but actually one can do better, e.g. in cases the image is not that clear.
By carefully choosing replacements for repeating or non-repeating ECB blocks we can obtain something like this:
See https://doegox.github.io/ElectronicColoringBook/ for more ECB coloring fun!