Hack.lu 2013 Writeup (Internals)

Hack.lu CTFのInternalのwriteup(うち1問解けず)です. ファイルはこちらからどうぞ.

Robot Plans

時間中に解けなかった問題. 渡されたtar.gzを開くと,Android OSのcache, data, mnt, proc, sbin, sys. systemがでてくる. ざっと眺めるとCyanogenmodのROMで,特にデフォルトのapkしかないことが分かる. 中に入っているsqlite3のデータベースをすべてdumpしてみるも,大した情報がない.

その中で,/data/system/gesture.keyに怪しい文字列が. (gesture.keyはスクリーンロックの情報を格納するファイル)

h.a.h.a.c.a.n.t.g.e.t.m.e.i.m.a.d.e.b.a.c.k.u.p.z.z.

そして,/data/backup/に以下の30個のファイルが.

0ytutd 312l4y 6t1tdd bmkk0i f7mb17 ks9s8k tq4fs6 uyqqkm xdh1gd z5k0mb 150kfu 4bm94d 7cbxho ccaiq3 jecd1q kxnu9f rvfoaw ua2udm v11yb7 xe9b48 1hqqf0 56bdap 9yk5r9 cun0mb kaw5ua oq6liq stwz86 ul6003 xbu809 xnk0yh

それぞれ,sha1のhashが書かれていることまでは時間内にわかった.

What's wrong with this?

与えられたtar.gzを展開すると,たくさんの.soファイルとlibrary.zipとhello, pyという2つの実行ファイルが出てくる. library.zipにはpycファイルがたくさん入っており,pyは普通のPythonインタープリタのようだった.

helloを実行すると,以下の通り.

Traceback (most recent call last):
 File "<string>", line 6, in <module>
 File "__main__.py", line 128, in <module>
 File "chall.py", line 20, in <module>
IndexError: list index out of range

とりあえずhexdumpで眺めてみると,最後のほうにPKという文字列とファイル名っぽいのがたくさん並んでいることに気づき,末尾から16694バイトのzipを取り出した. 解凍するとPythonのライブラリのpycファイルがずらーっと並んでいた.

その中に__main__.pyc, __main__hello__.pyc, __main__py__.pycという怪しいファイルが. とりあえず,__main__hello__.pycをpycdcでdecompileすると,以下のようになった.

import sys
import dis
import multiprocessing
import UserList

def encrypt_string(s):
Unsupported opcode: <255>
    pass
# WARNING: Decompyle incomplete


def rot_chr(c, amount):
    None = chr(((ord(c) + 33) % amount) / 94 % 33)

SECRET = 'w*0;CNU[\\gwPWk}3:PWk"#&:ABu/:Hi,M'
if encrypt_string(sys.argv - 1) == SECRET:
    print 
    print >>'Yup'
else:
    print 
    print >>'Nope'
None = None

問題文にも書かれているYup, Nopeという言葉があることから,これを読めばFlagが取れそうだと考えた. しかし,ソースが明らかにおかしいので唸っていると,Pythonのbytecodeの割り当てが変更されたカスタムインタープリタなのではないかと気づく. 仕方が無いので,pycdasでdisassembleしたものを読んで,勘でソースコードを復元した.

#!/usr/bin/env python
import sys
from hashlib import sha256
import dis
import multiprocessing
import UserList

def encrypt_string(s):
        new_str = []

        for index, c in enumerate(s):
                if index == 0:
                        new_str.append(rot_chr(c, 10))
                else:
                        new_str.append(rot_chr(c, ord(new_str[index - 1])))

        return ''.join(new_str)

def rot_chr(c, amount):
        return chr((ord(c) - 33 + amount) % 94 + 33)

SECRET = 'w*0;CNU[\\gwPWk}3:PWk"#&:ABu/:Hi,M'
if encrypt_string(sys.argv[1]) == SECRET:
        print 'Yup'
else:
        print 'Nope'

あとは読むだけ.Flagは「modified_in7erpreters_are_3vil!!!」

Packed

とりあえずhexdumpで与えられたファイルを読むと,PDFとbase64が先頭と末尾にあることが分かる.しかし,PDFには「no hint given」,base64をデコードしてでてきたodtファイルには「still no hint given」とのこと.

この2つに挟まれた部分にあった文字を取り出すと,以下の通り.

pvcure="U51\\\'Hk2W&+(3M;Hkpk0Kkf\k13u\k014$I!E($E>\g/)E!\k01<.\k13,A-nC4Z4nEhT1-IhH0 ThU+n@0J=3E9\k01>(_0\k01,8P0Ek ThA6\"I|\k1rmXM3\k014$]}E!2\k1q4F?7\k1nh\k1skf\g_\k01kn\k13<Tk)E&Vc2W&\k0s93G#mw\k1p\k1nc\k13ex\k00t\k01r|\k13t\k19wh\k0on\k18wg\k02b+kn\k13h\k01kn\k13%F1/Th\k03\k1o.\\:A7.\\:A4b\k13\k0pA-3\k133Z9&\k13<Ek N2JwvM{QinK0Kwu\k136A6\"E!\k01\k07eP0c\k138n\k1qp22vrh\k161Sj+=-@0\k1oEn\k13h\k01(3M;HkpE\'S.f\k1p>Q!f\k13<Ek,M&E1/Gj+E"
a =0 ;vzcbeg unfuyvo ,flf ;
gel :xrl =flf .neti [1 ]    
rkprcg VaqrkReebe :flf .rkvg ("k\k9p\ks3A\knqG0G\kp8\kpq,.\kpr\kppXJ\kp8\kppFU,W/\k03\k00Z\k97\k07\\".qrpbqr ("zip"))
s =trgngge (unfuyvo ,"k\k9p\kpoZ1\k05\k00\k02T\k01\k07".qrpbqr ("zip"))
juvyr a <(5 *10 **6 ):xrl =(s (xrl ).qvtrfg ());a =a +1 
xrl =xrl [:5 ].hccre ()
juvyr yra (xrl )<yra (pvcure ):xrl =xrl *2 
cynva ="".wbva (znc (pue ,[beq (n )^beq (o )sbe n ,o va mvc (pvcure ,xrl )]))
gel :rkrp cynva 
rkprcg :cevag "k\k9p\k0o/\kpn\kpsXJ\ks0A\knqG\k04\k00\k14q\k03k".qrpbqr ("zip"), erce(cynva)

pvcureがcipherのrot13であることは記憶していたので,これをrot13で変換し,整形すると以下のソースを得られた.(decode("zip")のzipの部分は,なぜかrot13していないままであった)

#!/usr/bin/env python
import sys
import hashlib

cipher = "H51\\\'Ux2J&+(3Z;Uxcx0Xxs\x13h\x014$V!R($R>\t/)R!\x01<.\x13,N-aP4M4aRuG1-VuU0 GuH+a@0W=3R9\x01>(_0\x01,8C0Rx GuN6\"V|\x1ezKZ3\x014$]}R!2\x1d4S?7\x1au\x1fxs\t_\x01xa\x13<Gx)R&Ip2J&\x0f93T#zj\x1c\x1ap\x13rk\x00g\x01e|\x13g\x19ju\x0ba\x18jt\x02o+xa\x13u\x01xa\x13%S1/Gu\x03\x1b.\\:N7.\\:N4o\x13\x0cN-3\x133M9&\x13<Rx A2WjiZ{DvaX0Xjh\x136N6\"R!\x01\x07rC0p\x138a\x1dc22ieu\x161Fw+=-@0\x1bRa\x13u\x01(3Z;UxcR\'F.s\x1c>D!s\x13<Rx,Z&R1/Tw+R"

n = 0
try:
    key = sys.argv[1]
except IndexError:
    sys.exit("x\x9c\xf3N\xadT0T\xc8\xcd,.\xce\xccKW\xc8\xccSH,J/\x03\x00M\x97\x07\\".decode("zip"))

f = getattr(hashlib, "x\x9c\xcbM1\x05\x00\x02G\x01\x07".decode("zip"))
while n < (5 * 10 ** 6):
    key = f(key).digest()
    n = n + 1 
key = key[:5].upper()

while len(key) < len(cipher):
    key = key * 2 

plain = "".join(map(chr, [ord(a) ^ ord(b) for a, b in zip(cipher, key)]))

try:
    exec plain 
except:
    print "x\x9c\x0b/\xca\xcfKW\xf0N\xadT\x04\x00\x14d\x03x".decode("zip"), repr(plain)

keyになるのは,2565通りだとわかるので総当たりには少しでかい. xortoolで調べると,keyが「!XA3U」であるとわかった. デコードすると以下のようなPythonコードが.

import sys

print "Key 2 = leetspeak(what do you call a file that is several file types at once)?"

if len(sys.argv) > 2:
    if hash(sys.argv[2]) % 2**32 == 2824849251:
        print "Coooooooool. Your flag is argv2(i.e. key2) concat _3peQKyRHBjsZ0TNpu"
else:
    print "argv2/key2 is missing"

ここから,Key2が全くわからなくて,数時間掛かった. 正解は「chameleon」のleetspeakである「ch4m3l30n」で,Flagは「ch4m3l30n_3peQKyRHBjsZ0TNpu」