zed.0xff.me

hack.lu 2011 CTF -- Unknown Planet -- writeup

[ Lobotomy ]

0. original image file

1. analyzing image

all JPEGs have special EOF marker FF D9 and no data must be after this marker.

1
2
3
4
5
[zed@zmac 200.unknown.planet+]#irb -E binary
ruby-1.9.2-p290 :004 > data=File.read '0_8c4f14e28155a2c3cf4b2538c1e0958b.jpg'; data.size
 => 194420 
ruby-1.9.2-p290 :005 > data.split("\xff\xd9").map(&:size)
 => [192405, 2013] 

so, we can see that there’s 2013 spare bytes after EOF marker.

1
2
3
4
5
6
File.open('foo','w'){ |f| f<< data.split("\xff\xd9").last }
 => #<File:foo (closed)> 
ruby-1.9.2-p290 :007 > ^D

[zed@zmac 200.unknown.planet+]#file foo
foo: Zip archive data, at least v2.0 to extract

AHA! It’s a zip! :)

2. unzipping

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
[zed@zmac 200.unknown.planet+]#unzip foo 
Archive:  foo
  inflating: 5IIUED7GheR             
  inflating: 6JXtwsTTh9k             
  inflating: 87F1s5POUJc             
  inflating: BPiIOASG_Z6             
  inflating: nLPA8X0UJqf             
  inflating: rySOWi4fZkA             
  inflating: uvlSlG3Tgow             
  inflating: Uw105aD3qYh             
  inflating: Yui5oq58hlx    


[zed@zmac 200.unknown.planet+]#ls -la
-rw-r--r--@  1 zed  staff  20000 Apr 25 16:45 5IIUED7GheR
-rw-r--r--@  1 zed  staff  20000 Apr 25 16:45 6JXtwsTTh9k
-rw-r--r--@  1 zed  staff  20000 Apr 25 16:45 87F1s5POUJc
-rw-r--r--@  1 zed  staff  20000 Apr 25 16:45 BPiIOASG_Z6
-rw-r--r--@  1 zed  staff  20000 Apr 25 16:45 Uw105aD3qYh
-rw-r--r--@  1 zed  staff  20000 Apr 25 16:45 Yui5oq58hlx
-rw-r--r--@  1 zed  staff   1324 Apr 25 16:45 nLPA8X0UJqf
-rw-r--r--@  1 zed  staff  20000 Apr 25 16:45 rySOWi4fZkA
-rw-r--r--@  1 zed  staff  20000 Apr 25 16:45 uvlSlG3Tgow

[zed@zmac 200.unknown.planet+]#file *
5IIUED7GheR: data
6JXtwsTTh9k: data
87F1s5POUJc: 8086 relocatable (Microsoft)
BPiIOASG_Z6: data
Uw105aD3qYh: data
Yui5oq58hlx: data
nLPA8X0UJqf: 8086 relocatable (Microsoft)
rySOWi4fZkA: data
uvlSlG3Tgow: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 8 bit, mono 8000 Hz

Looks like audio file, that was split in chunks of 20000 bytes each.
uvlSlG3Tgow is a first chunk b/c it has a RIFF WAVE header.
nLPA8X0UJqf is a last tail chunk b/c it’s size less than 20000.

3. gluing waves

importing files in Audacity (or any other sound editor) discovers that source file is supposed to be a Morse – coded message. But we must find a correct order of chunks.
So, morse code consists of dots and dashes. Each kind must have fixed length.
We suppose that source file was generated programmatically, not recorder from line or mic. So, it’s timings must be perfect.
Following tool helps to manually find a correct chunks order.

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
#!/usr/bin/env ruby
STDOUT.sync = true

if ARGV.size == 0
  raise "gimme at least one chunk filename"
end

b0 = "\x80"*8
b1 = "\x27\x01\x27\x80\xd9\xff\xd9\x80"

data = ARGV.map{ |x| File.read(x) }.join.force_encoding('binary')
if data[0,4] == 'RIFF'
  data = data[44..-1]
end

N=120

b0 = b0*N
b1 = b1*N

r = ''
0.step(data.size-1,b0.size) do |i|
  case (d=data[i,b0.size])
  when b0
    print "."
    r << '0'
  when b1
    print "#"
    r << '1'
  else
    raise "SYNC ERROR" if d.size == b0.size
    raise "NOT ENOUGH DATA #{d.size}/#{b0.size}"
    raise "unknown #{d.size} (normal: #{b0.size}) bytes of data #{d.split('').map{|x| "%02x " % x.ord}.join}"
  end
end

calling with a single chunk – script says that it needs more data (more chunks):

1
2
[zed@zmac 1]#./2_manually_guess_chunk_order.rb uvlSlG3Tgow 
##..######..######.../2_manually_guess_chunk_order.rb:32:in `block in <main>': NOT ENOUGH DATA 756/960

calling with wrong 2nd chunk => SYNC ERROR:

1
2
[zed@zmac 1]#./2_manually_guess_chunk_order.rb uvlSlG3Tgow 6JXtwsTTh9k 
##..######..######..###./2_manually_guess_chunk_order.rb:31:in `block in <main>': SYNC ERROR 

two chunks in correct order, script says it needs more chunks:

1
2
[zed@zmac 1]#./2_manually_guess_chunk_order.rb uvlSlG3Tgow 5IIUED7GheR 
##..######..######..##......##..##..##..#./2_manually_guess_chunk_order.rb:32:in `block in <main>': NOT ENOUGH DATA 596/960 

all chunks in correct order:

1
2
[zed@zmac 1]#./2_manually_guess_chunk_order.rb uvlSlG3Tgow 5IIUED7GheR rySOWi4fZkA 87F1s5POUJc 6JXtwsTTh9k Uw105aD3qYh BPiIOASG_Z6 Yui5oq58hlx nLPA8X0UJqf 
##..######..######..##......##..##..##..##......##......##..##......######..##..######......######..##..######..######......######..######..######......##..##..##......

4. decoding Morse

we’ll need a ruby morse gem. install it with “gem install morse

1
2
3
4
5
6
[zed@zmac 200.unknown.planet+]#irb
ruby-1.9.2-p290 :001 > r='##..######..######..##......##..##..##..##......##......##..##......######..##..######......######..##..######..######......######..######..######......##..##..##......'
ruby-1.9.2-p290 :002 > require 'morse'
 => true 
ruby-1.9.2-p290 :005 > puts Morse.decode(r.gsub('......'," ").gsub('######','-').gsub('.','').gsub('##','.'))
PHEIKYOS

Voila! “Pheikyos” is the answer. Case-sensitive.

PS: all source & data files are available at my ctf github repo.