yyy

CTFつよくなりたい

セキュリティ・キャンプ全国大会2017に参加して最高の5日間を過ごした話

はじめに

参加してきました.噂に聞いてた通り最高のキャンプでした.

セキュリティ・キャンプってなにって人向けにIPAのサイトを.

www.ipa.go.jp

最高レベルの講師に最高の講義をして頂いて最高の体験を得るようなキャンプです.

応募用紙を書いて合格を頂けると行くことができます.

僕が書いた応募用紙はこちらです.

ywkw1717.hatenablog.com

今回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 マルウェア機械学習

講師は村上さん.機械学習は経験がなかったのですが,検知手法に興味があったので受講しました.「機械学習とは」から始まり,マルウェアを検知するための特徴量の抽出などのアイデア出しをグループで行ったりしていました.その後実際に自分で決めた特徴量を用いて検知率がどのくらいか,などを検証していました.VMのイメージのダウンロードが遅すぎて進まなかったりしていましたが,チューターさんのはっぴーのーとさんに助けて頂きました,ありがとうございました.やっぱり機械学習は,これから自分に必要な知識になってきそうな予感.

D5 The Anatomy of Malware

講師は中津留さん.これも結構人気の講義で自分も楽しみにしていました.(講義資料を最後のほう読むの忘れていて焦った)ひたすら静的解析をする講義で,マルウェアに興味は持ちつつもまだ解析はしたことがなかった自分にとって,いいきっかけになったのではないかと思います.最後にやった演習では,どのようなことを行っているルーチンなのかを当てることができて嬉しかったです.今後マルウェア解析にのめり込んでいきたい!

昼食はD4を受講してた游び人さんと高専の方と食べて,女の子の話をしたりして楽しかったです.

夕食は講師やチューターさん,外部の方と混ざって食事をするはずが,人数の関係で参加者の方1人しかいなく,その人と楽しくお喋りしていました.

BoFは「ゲスリティキャンプ」と「サイバー空間へようこそ」にしました.

どちらも新鮮なお話でした.

企業プレゼンテーションはNEC日本電気)さん.

「サイバー空間へようこそ」の人でした.(この人と実は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のデモ版で実行しながらみていくことにした.

f:id:ywkw1717:20170625185419p:plain

左下のルーチンを"biscu”という回数分繰り返すので,このルーチンの前あたりにブレークポイントを張って動的解析していく.

f:id:ywkw1717:20170625185453p:plain

“biscu"という文字列を,先頭から順に1文字ずつ取り出して_shift_charに渡すという処理が計5回行われる.

call _shift_char

に対してステップインして中を見ていく.

f:id:ywkw1717:20170625185614p:plain

最初に渡されるのは"b".

それを"abcdefg…..“というスタックに積んであるものに対して"b"が出てくるまで1文字ずつシフト.

その後,シフトした回数+1というか↑のルーチンを実行した回数分,"abcde…“という文字列を先頭から左にシフト.(正確に言うと,スタックにある"abcde…."のアドレスに回数分を加算)

すると,"b"の場合は"c",次の"i"は"h",次の"s"は"o"...となっていって最終的に"choux"となる.

f:id:ywkw1717:20170625190148p:plain

よって,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力~~~~

セキュキャン2017 応募用紙

はじめに

合格を頂くことができました.年齢的に今年が最初で最後の応募だったので,応募開始してから死ぬ気で埋めていた甲斐がありました.応募用紙に関しては,落ちても受かっても晒そうと思っていたので晒します.

感想として,学ぶことがとても多かったと思います.GWに実家に帰省してからも問題に取り組んでいて,大学の休み時間や家に帰ってきてからまた取り組んだり,とにかく空き時間はほとんど費やしていた気がします.選択問題A-6で1週間使ってなんとか実装し終わったと思ったら,選択問題A-5が激ムズで問題変えようか悩んだりと,そんな感じでした.

少しでも,来年以降応募する人のためになれば幸いです.

(共通問題2とかイキリまくってて恥ずかしいので共通問題は省略します)

共通問題1

高校生の時に作った自作PCやバイト先で書いたプログラム,個人で制作したものなどを10個書きました.どうして作りたいと思ったか,なぜ作ることになったか,という動機と背景をしっかり書きました.

共通問題2

1つ目は自分の無知さやプログラミング力の無さを実感したこと,2つ目はCTFで全く問題が解けなかったことを書きました.アドバイスは本当にその人に向けて書くように文体も変えて書きました.

共通問題3

受講したい講義は7個ほど挙げました.それぞれの講義で「どうして受講したいのか」と「なにを学びたいのか」を書きました. セキュリティキャンプでやりたいこととしては,「今後何をやっていきたいかを考える為のきっかけを作りたい」ということと「同じ志を持った人達と交流を深めたい」ことを書きました.

共通問題全体で2万5千字ぐらいでした.

選択問題A-4

まずは、printf()関数についてです。一言で言うならば、問題文にもある通り「数値や文字列を表示する関数」ですが、その表示する過程にどのようなことをやっているのか、わからなかったので調べました。まず以下のようなプログラムを書いてみました。

#include <stdio.h>

int main(){
printf(“hoge\n”);

return 0;
}

これは、"hoge"という文字列を表示する最もシンプルなプログラムです。このプログラムの処理をgdbというデバッガで追いかけてみることにしました。私は普段、“peda"というgdbの機能をより高めてくれるツールが導入されたgdbを使っているので、以下の実行例はpedaが導入されたgdbで行っています。また、環境はUbuntu16.04の64bitです。64bitだと、レジスタが拡張されます。今回の目的はprintf()関数について調べることなので、もっとわかりやすい32bitプログラムで調べるために”-m32"というオプションを付けてgccコンパイルしました。startというmain関数にブレークポイントをしかけてから開始することができるコマンドを実行すると、

0x8048419 <main+14>:   sub esp,0x4

という行で止まりました。これは、ESPというスタックポインタであるレジスタから、0x4を引き算しているという命令ですが、これから使うスタックの領域を確保するためにこのようなことを行います。スタックは下方伸長なので、アドレスが低位に向かって伸びるため、足し算ではなく引き算を行います。スタックポインタはスタックの先頭を現しています。niというステップアウト実行できるコマンドを行ったところ、

0x804841c <main+17>:   sub esp,0xc

また、スタックから0xcだけ領域を確保しています。さらに進めます。

0x804841f <main+20>:   push 0x80484c0

0x80484c0というアドレスをスタックにpushしています。実行するとESPの値が以下のようになりました。

ESP: 0xffffbf50 --> 0x80484c0 (“hoge”)

0x80484c0という場所に"hoge"という文字列は格納されているのかなと思い、

x/1wx 0x80484c0

という風にして、0x80484c0のアドレスから4バイトで1個のメモリを16進数値として調べてみると、

0x80484c0:   0x65676f68

このように、確かに0x80484c0の場所に0x65676f68という値が格納されていました。ASCIIコードで65は"e"、67は"g"、6fは"o"、68は"h"であり、逆順に格納されているのはリトルエンディアンという方式を使っているからです。再びniで次の命令である、

0x8048424 <main+25>:   call 0x80482e0 puts@plt

を実行してみると、"hoge"という文字列が表示されました。

ここである疑問が湧きました。ソースコードではprintf()関数を使っていたのに、なぜputs()関数に変わったのかということです。すると、このサイト( http://0xcc.net/blog/archives/000065.html )で答えを見つけることができました。どうやら、文字列の末尾が"\n"で終わり、%d などのフォーマット指定がないときは最適化が行われ、puts()関数が使われるみたいです。今回使ったソースコードでは、printf(“hoge\n”);という使い方をしており、まさに最適化に含まれる対象だったので、puts()関数に変わったということがわかり、勉強になりました。しかし、gccで"-O0"というオプションをつければ、最適化は無効化されるはずが、これでコンパイルしてもputs()関数に変わってしまったので、オプションより強い制約が内部でかかっているのかなと考察しました。printf()関数を調べるために、改行文字を省いたプログラムを再度コンパイルし、gdbで実行しました。すると、今度は最適化に含まれる対象ではなかったようで、printf()関数が呼ばれていました。このprintf()関数の中に入り、さらに処理を追っていこうと思います。今までステップアウト実行であるniのみで進めてきましたが、ステップインするsiというコマンドを使って、実際に関数の中に入っていきます。すると、

0x80482e0 printf@plt:    jmp DWORD PTR ds:0x804a00c

という場所に飛びました。なぜこのpltという領域に飛ぶのかを説明するために、静的リンクと動的リンクの話をします。

プログラムがコンパイラによってコンパイルされるとき、リンカによって静的リンクか動的リンクのどちらかのリンク方式により、共有ライブラリがリンクされます。静的リンクの場合は、リンク時に実行ファイルに共有ライブラリをリンクしてしまい、実行ファイル内に内蔵してしまう形式です。なので、ファイルサイズがその分大きくなります。対して動的リンクの場合は、リンク時に実行ファイルに直接共有ライブラリをリンクするのではなく、どれを使うかという情報のみをリンクさせます。なので、後でシンボル解決と呼ばれる行為を行う必要があります。起動時には、動的リンクより静的リンクのほうが時間がかかってしまいますが、それぞれにメリットがあります。この実行ファイルをLinuxのfileコマンドを使って調べると、“dynamically linked"の文字列が確認できるので、動的リンクだということがわかります。なので、動的リンクの話を続けます。動的リンクでは、共有ライブラリの中にある使いたいものをPLT領域を経由して呼び出します。なぜそのようなことを行うかというと、動的リンクの場合共有ライブラリは実行時にリンクされます。なので、共有ライブラリのアドレスを調べてシンボル解決する必要があり、そのための領域としてPLT領域とGOT領域が存在します。まずはPLT領域ですが、シンボル解決をするために、さらにPLTからGOT領域へ飛びます。PLT領域を"objdump -d -M intel -j .plt filename"で逆アセンブルしてみます。私はintel形式のほうが好きなので、”-M intel"オプションをつけています。

080482e0 puts@plt:
80482e0: jmp DWORD PTR ds:0x804a00c
…

0x804a00cという場所には以下のように、

x/1wx 0x804a00c
0x804a00c:  0x080482e6

0x80482e6が入っているので、これはそのまま次の命令に進み、0x0をスタックにpushし、最終的に0x80482d0に飛ぶようになっています。

080482d0 printf@plt-0x10:
80482d0: push DWORD PTR ds:0x804a004
80482d6: jmp DWORD PTR ds:0x804a008
…

0x8048ed0に飛んだ後は、最終的に2行目のjmp命令で、0x804a008というアドレスに飛びますが、このアドレスが指している場所がGOT領域です。なぜわかったのかというと、"readelf -S filename"でセクションヘッダがわかるのですが、

[24] .got.plt PROGBITS 0804a000 001000 000014 04 WA 0 0 4

その結果の内の.got.pltの開始アドレスが0x0804a000になっていてこのセクションに含まれていることがわかるからです。GOT領域では、プロセスごとに独立している共有ライブラリへの参照を保持します。関数呼び出しを行う際に、呼び出す本体へのポインタを別のところで保持しておき、ポインタ経由で本体である関数を呼び出すということをやっています。このように、PLT→GOT→共有ライブラリの順番で処理しています。2段階になっているのは、シンボル解決のためであり、そのためにPLT領域で準備をしています。いつシンボル解決を行うかですが、関数が最初に呼ばれた時に行います。最初、GOTにはシンボル解決を行う為の処理のアドレスが入っており、それが完了すると、2回目以降からは本体を直接参照するようになります。これは遅延リンクと呼ばれ、実際に使わない関数はシンボル解決を行わないので、起動時間を短縮できます。

話が少しそれますが、GOT領域を利用した攻撃手法にGOT Overwriteというものがあります。その名の通り、GOT領域を上書きする攻撃です。最近のコンパイラには、RELROというセキュリティ機構があります。これはRELocation ReadOnlyの略で、マッピングされるデータのどの部分にReadOnly属性を付けるかを決定します。ReadOnly属性を付けることで、悪意のあるコードが注入されたときに、値を書き換えられて制御を奪われてしまう行為を防ぐことができます。プログラム起動時に上で述べたシンボル解決をしてしまい、GOT領域を読み込み専用にしてしまうFull RELRO以外のNo RELRO、Partial RELROの場合はGOT Overwriteを行うことができます。書き込み可能になっているGOT領域を書式文字列攻撃で書き換えると、そのコードが実行された時に、書き換えた値へと制御を移すことが可能です。上手くやればシェルまで奪えてしまうので、起動時間が遅くなってしまうというデメリットがありますが、セキュリティリスクを減らすためにもFull RELROにしておくほうがいいかと思います。ここら辺の知見はksnctfというサイトのVillagerAという問題を解いた時に得ました。

話を戻します。動的リンクの場合はこのようにPLT領域やGOT領域が絡んでくるので、純粋にprintf()関数について調べたい時には邪魔だと思うので、リンク方式を静的リンクに変えます。"-static"オプションを付けて、再度コンパイルしてgdbで解析します。printf()が呼ばれている場所でステップインすると、以下の場所に飛びました。

0x804ebe0 <printf>:    sub esp,0xc

どうやらprintf()の内部へと入れたようです。さらにステップ実行していくと、

0x804ebf5 <printf+21>: call 0x807e190 <vfprintf>

vfprintf()とはなんなのか、C言語の関数だと思うのでとりあえずmanコマンドで調べてみました。

The functions vprintf(), vfprintf(), … are equivalent to the functions printf(), fprintf(), …

という記述が見つかり、他の説明も読む限り大体は同じで引数の渡し方とかが違うのかなと思いました。vfprintf()をさらに追ってみます。文字列を出力している実態のようなものがないか、ひたすらステップ実行していったのですが、実行されてそうなところが見つからないので、おかしいなと思い、最初に戻りprintf()関数をステップインせずに実行してみました。しかし"hoge"という文字列が表示されません。使っているpedaのバグかと思いましたが.gdbinitを書き換えてgdb単体で実行してみるもやはりダメでした。これはおかしいなと思いながら、最後まで実行してみると、exitが呼ばれた時と同時に"hoge"という文字列は表示されていました。改行を使っていなく4文字しか表示していないため、表示の際のバグが起きているのかと思い、出力する文字列を"hoge\nfuga"としてみました。すると、printf()関数を実行したときに"hoge"、exitしたときに"fuga"という風に出力されてしまいました。少し気持ち悪いですが、その場で表示されないといつ表示されたかがわからないので、"hoge\nfuga"という文字列で再度コンパイルして続けます。vfprintf()まで進めます。ステップ実行していくと、

0x807e23c <vfprintf+172>:  call DWORD PTR [eax+0x1c]

この場所で"hoge"が表示されました。この関数にステップインして続けます。すると_IO_new_file_xsputn()という関数に入りました。やはりcall命令が怪しいので、そこに注目して続けます。

0x805211c <_IO_new_file_xsputn+204>:   call 0x8053810 <_IO_default_xsputn>

で"hoge"が表示されたのでまたステップインします。ループしている箇所があり、4回ループして"hoge\nfuga"の内の改行文字"\n"を引数に、

0x805387c <_IO_default_xsputn+108>:    call DWORD PTR [eax+0xc]

このcall文を実行したときに"hoge"が出力されました。もう一度詳しく追ってみると、改行文字を引数に渡した場合のみ、

0x8052b5e <_IO_new_file_overflow+190>: call 0x80523e0 <_IO_new_do_write>

という関数がルーチンの中に現れました。これはcallした先で6回ほど比較して条件に合えばジャンプということをやっており、最後の比較である

0x8052aef <_IO_new_file_overflow+79>: cmp esi,0xa
0x8052af2 <_IO_new_file_overflow+82>: je 0x8052b50 <_IO_new_file_overflow+176>

この部分で、0xaという改行文字のASCIIコードと比較して等しければ0x8052b50にジャンプするということをやっている為です。恐らく、それまでの文字列をどこかバッファのようなところに保存しておき、改行が現れたら一気に表示するということをやっているんだろうと考えました。これによって、先に述べた謎の現象も少し理解することができました。改行文字が現れていないため、"fuga"という文字列は表示されることがなくexitの後に表示されているのだと思います。

では、改行文字と比較するのではなく"hoge\nfuga"の内の"a"と比較すれば、“a"は最後にしか現れていないので"hoge\fuga"が一気に表示できるのではないかと考えました。よって、バイナリを書き換えてみます。objdumpで逆アセンブルした結果を見て、上の比較しているところの前後の機械語などを調べます。そして、vimのバイナリモードを使い、その機械語にマッチする部分を探します。”%!xxd"で16進ダンプすると、2735行目の末尾から2736行目の頭にかけて、見つけることができました。0xaaefから0xaaf1の3バイトです。0xaaf1の"0a"を"61"に書き換えます。"%!xxd -r"で保存し、再度gdbで見てみると、

0x8052aef <_IO_new_file_overflow+79>: cmp esi,0x61

となっており、書き換えることができました。そして、"hoge\nfuga"という文字列の内の"a"を渡したときにジャンプが成功し、"hoge\nfuga"を一気に表示することができました。では、表示している関数に対してさらにステップインしてみます。この先4、5回ステップインするので、遷移した先だけ書きます。

0x80523f8 <_IO_new_do_write+24>: call 0x8050cd0 <new_do_write>

0x8050cfc <new_do_write+44>:  call DWORD PTR [eax+0x3c]

0x8051b5b <_IO_new_file_write+91>:    call 0x806cfc0 <write>

0x806cfdc <__write_nocancel+18>:  call DWORD PTR ds:0x80ea9f

ここでようやく終わりを迎えました。最終的にどうやって文字列が出力されていたかというと、

0xf7ffcc85 <__kernel_vsyscall+5>:  sysenter

このように、sysenterという命令によってシステムコールが呼ばれていたことがわかりました。sysenterの下に、int 0x80というこれもまたシステムコールを呼ぶ命令ですが、これは実行されていませんでした。

gdbではwhereコマンドを使えば関数の呼び出し手順を調べることができます。

gdb-peda$ where
#0 0xf7ffcc89 in __kernel_vsyscall ()
#1 0x0806cfe2 in __write_nocancel ()
#2 0x08051b60 in _IO_new_file_write ()
#3 0x08050cff in new_do_write ()
#4 0x080523fd in _IO_new_do_write ()
#5 0x08052b63 in _IO_new_file_overflow ()
#6 0x0805387f in _IO_default_xsputn ()
#7 0x08052121 in _IO_new_file_xsputn ()
#8 0x0807e23f in vfprintf ()
#9 0x0804ebfa in printf ()
#10 0x0804889a in main ()
#11 0x08048ad1 in generic_start_main ()
#12 0x08048ccd in __libc_start_main ()
#13 0x08048757 in _start ()

printf()関数のまとめです。

printf()関数はvfprintf()や他の関数を実行していき、writeシステムコールも使いながら、最終的にsysenter等のシステムコールを使い、文字列を表示する関数です。

次はfork()についてです。 私はまず、fork()をほとんど使ったことがなかったので、manで調べました。NAMEが"fork - create a child process"となっている通り、子プロセスを作るものだとわかります。SYNOPSISに、

#include <unistd.h>

pid_t fork(void);

このように書かれており、"pid_t"という型で宣言されているのだとわかりました。"pid_t"は構造体でどこかに宣言されているのだろうかと思い、"unistd.h"ファイルに答えがあるのではないかと思い、"find / -type f |grep unistd.h"として、探しました。環境はprintf()の時と同じく、ubuntu16.04の64bitです。大量にヒットしましたが、本体が書いてありそうな

/usr/include/unistd.h

このファイルを見てみました。すると、263行目に書いてありました。

typedef __pid_t pid_t;

__pid_tをtypedefしたものであるのはわかったのですが、__pid_tの正体がわからないのでさらに調べます。"find ./ -type f |xargs grep ‘__pid_t’"を/usr/include内で実行して調べると、これまた大量にヒットしましたが、

./x86_64-linux-gnu/bits/types.h

に、それっぽいものを見つけました。

__STD_TYPE __PID_T_TYPE __pid_t; /* Type of process identifications. */

__STD_TYPEは上のほうで

# define __STD_TYPE __extension__ typedef

このようにマクロで定義されていました。__extension__を使う理由は、この定義の上に"C89でlonglongのような標準外の型を使うから"と書かれていました。__PID_T_TYPEを調べる必要があるので、さらに続けます。"find ./ -type f |xargs grep ‘PID_T_TYPE’"で調べると、

./x86_64-linux-gnu/bits/typesizes.h

このファイルがヒットしたので見てみると、

#define __PID_T_TYPE __S32_TYPE

これもマクロで定義されていたので、"__S32_TYPE"を調べます。'S32_TYPE’をgrepするようにしてみると、先ほど見たtypes.hがヒットしました。もう一度見てみると、

#define  __S32_TYPE int

ありました。結局"pid_t"という型はint型だとわかりました。あとはfork()の戻り値について知りたいので、またmanの結果を眺めます。RETURN VALUEに書いてありました。

どうやら、成功すると子プロセスのPIDが親プロセスに返されて、0が子プロセスに返されるみたいです。失敗すると-1が親プロセスに返されて、子プロセスは作られずに適切なerrornoがセットされるとあります。実際に下のようなコードを書いて実行してみました。

https://gist.github.com/ywkw1717/16332f539dc05d232657b4b2295245d4

pidの戻り値によって分岐するようにして、それぞれでpidを出力するようにしました。実行結果は

parent
23802
child
0

このようになりました。プロセスの複製に成功して子プロセスのPIDが親プロセスに返されてparentが表示され、その後0が子プロセスに返されて今度はchildが表示される、ということでしょうか。また、fork()と聞いたとき、forkbombが真っ先に思い浮かびました。

:(){:|:&};:

これがforkbombです。:という名前の、自分自身を2回起動してバックグラウンドで実行するという関数になっています。最後の:が実行を意味しています。実行すると、プロセスが無限に増殖するというDoS攻撃の一つです。仮想マシンで実行したとき

:: fork failed: resource temporarily unavailable

という表示が大量に出力され、ほとんどなにもできなくなってしまいました。どうすれば防げるのだろうと考えましたが、プログラムごとに生成できるプロセスに制限をかけてしまえば問題ないのかなと思いました。

fork()が実行される過程をgdbで追いかけようと思ったのですが、printf()に文字数を使いすぎてしまったので、これで終わりにします。

選択問題A-5

個人的にこの問題が一番難しかったです。試行錯誤しましたがエクスプロイトが書けておらず、問題の完答ができていないので、できたところまでを書きます。

まずは問題文にある通り、カーネルのバージョンを3.8~4.4のものを用意することから始めました。手元のUbuntu16.04は4.8.0-52-genericだったので、もっと古いのを用意する必要がありました。Ubuntuで探してみたところ、15.10であるWily Werewolfのカーネルのバージョンが4.2だったので、これで試してみました。 与えられたソースコードでまず気になったのは、keyutils.hというヘッダファイルです。私は、テキストエディタは普段Vimを使っていて、与えられたソースコードを保存してみると、keyutils.hの行がシンタックスチェックに引っかかりました。そこでkeyutils.hを探すことから始めました。Ubuntuでは”apt-cache search ファイル名”で、そのファイルがどのパッケージに入っているのか調べることができるので、探してみました。以下のようになりました。

keyutils - Linux Key Management Utilities
keyutils-dbg - Linux Key Management Utilities (debug)
libkeyutils-dev - Linux Key Management Utilities (development)
libkeyutils1 - Linux Key Management Utilities (library)

下の2行のどちらかだと思うので、libkeyutils1をインストールしてみました。ですが既に最新バージョンだと言われてしまったので、あとはlibkeyutils-devしかないだろうと思いインストールしてみたところ、シンタックスチェックは通りました。ですがgccコンパイルしてみたところ、`keyctl’ に対する定義されていない参照です、とエラーを吐かれました。lオプションで必要なライブラリをリンクする必要があると思い、-lkeyutilsを付けて実行してみたところコンパイルできました。

次に、ソースコードを読んでみました。最初にint型でカウンタ変数iを、key_serial_t型でserialを、それぞれ宣言しています。key_serial_tは、/usr/include/keyutils.hを見たところ、int32_tのtypedefになっていました。int32_tは4バイトです。その後、serialにkeyctl(KEYCTL_JOIN_SESSION_KEYRING, “leaked-keyring”)を代入しています。keyctlがわからなかったので、そこから調べました。検索するとIBMのサイト( https://www.ibm.com/developerworks/jp/linux/library/l-key-retention.html )がTopに出てきて、Linuxの鍵保存サービスについて詳細に解説されていたので、まずはLinuxのkeyringという機構について理解することから始めました。暗号化方式、認証トークン、ドメイン間のユーザー・マッピングなどのセキュリティー関連項目などの認証データをLinuxカーネルにキャッシュすることで、様々な利便性の向上を図ろうというものです。

この問題の脆弱性はCVE-2016-0728として登録されており、当時大きな問題になっていたことがわかりました。keyctlにKEYCTL_JOIN_SESSION_KEYRINGを渡すと、セッション鍵リングを新しいセッション鍵リングで置き換える関数である、join_session_keyringを呼ぶことができます。join_session_keyringは、process_keys.cに書かれており、脆弱性はこの関数に存在します。カーネルのバージョンが4.4以降なら修正されているはずなので、4.5とのdiffを取ってみました。すると、797行目の

key_put(keyring);

という行だけが違いました。つまり、この行を追加するだけで脆弱性に対応できているということです。key_putは鍵を開放する関数なので、鍵の解放を怠っていたせいで脆弱性が生まれてしまったということでした。このkey_put(keyring)が追加されている部分は

else if (keyring == new->session_keyring) {
key_put(keyring);
ret = 0;
goto error2;
}

このようになっています。keyringにはfind_keyring_by_name(name, false)の結果が代入されています。find_keyring_by_nameはkeyring.cというファイルに定義されており、nameに該当するものがあればそのkeyringへのポインタを返し、無ければ-ENOKEYを返します。一方、newにはprepare_creds()の結果が代入されています。prepare_credsは、”修正するために新たなcredentialsのセットを準備する”役割を担っており、commit_creds()と対で使用されています。処理の中には、key_get(new->session_keyring)という部分があります。key_getは真ならば__key_get(key)を返し、偽ならばkeyを返します。__key_get(key)では、渡されたkeyのusageをインクリメントするように

atomic_int(&key->usage);
return key;

という処理になっており、ここでusageの値がインクリメントされていることがわかります。この処理に行き着く条件というのはkeyringとnew->session_keyringが等しい時です。それは、プロセスが現在のセッション鍵リングを全く同じものに置き換えようとする時を指します。つまり、脆弱性の修正のために追加されたkey_put(keyring)が存在しなかった場合、キーリングオブジェクトへの参照が残っているので、usageの値がoverflowしてしまう危険性があります。実際に構造体keyが定義されているkey.hを見てみると

struct key {
atomic_t usage; /* number of references */

このように、atomic_t型で宣言されています。atomic_tはtype.hで宣言されていて、int型の要素を一つ持つだけなので、atomic_tはint型と同じサイズであると言えるはずです。この脆弱性を悪用するためには、ここ( http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/ )で解説されている通りに、まずusageを0x100000000まで増加させ、0にする必要があります。

この脆弱性を突いてroot権限を奪うPoCが公開されていたので、まずはそれを動かしてみて挙動を把握し、それから自分でプログラムを書いてみるという順序で進めることにしました。検証した環境は、冒頭で記述したUbuntu15.10です。実行してみたところ、いつまで経っても終わる気配がありませんでした。Intel Core i5-7200U のノートPC上でVirtualboxを起動させ、そこで実行させていました。どうしようかと思いましたが、プログラムの処理を並列化することで高速化できないかと考えた結果、openmpというものを使うことにしました。プログラムに数行加えるだけだったので、とても簡単に実現することができました。本来であれば家に余っている残り2台のノートPCを合わせて、グリッドコンピューティング的なことができないかと考えていたのですが、少し設定をやってみて詰まるところがあり、ここで時間を取られるのも嫌だなと思ったのでデスクトップの自作PCを使うことにしました。こちらはコア数が4つであり、高速化が期待できそうだったので再びUbuntu15.10で環境構築を行い、実行してみました。すると、20分ほどでシェルを起動するところまではいったのですが、root権限が取れていませんでした。並列化はうまくいっているようでしたが、肝心のroot権限が奪えていなかったので、もう少し調べてみました。ここ( http://cyseclabs.com/blog/cve-2016-0728-poc-not-working )で解説されている内容を読んでみると、セキュリティ機構であるSMEP/SMAPが原因か、prepare_kernel_cred()とcommit_creds()のアドレスが正しくないことが原因だとありました。私はPoCの先頭で定義されているCOMMIT_CREDS_ADDRとPREPARE_KERNEL_CREDS_ADDRを見落としており、自分の環境に合わせて定義する必要がありました。これらの情報は/proc/kallsymsで見ることができますが、KADRというセキュリティ機構が有効になっていると、アドレスが全て0で隠されてしまいます。これを

$ sudo sysctl -w kernel.kptr_restrict=0

で無効化します。そして/proc/kallsymsを見ると、しっかりとアドレスが表示されており、その情報をプログラムに記載しました。ここら辺は、ももいろテクノロジーさんの記事( http://inaz2.hatenablog.com/entry/2015/03/21/175433 )を参考にしました。これで権限昇格を行えるかなと思って実行してみましたが、またしてもrootは取れていませんでした。そこで、もう一つの原因だと考えられるSMEP/SMAPについて調べてみました。るくすさんのスライド( http://sssslide.com/speakerdeck.com/rkx1209/kanerukong-jian-karafalsesekiyuritei )で紹介されていたので参考にしました。SMEP(Supervisor Mode Execution Prevention)が、カーネルモードの時はユーザ空間のコードを実行できないようにするもので、SMAP(Supervisor Mode Access Prevention)が、カーネルモードの時はユーザ空間のデータにアクセスできないようにするものだったので、今回はSMEPが原因だと考えました。そこで、SMEPを無効化できれば攻撃は成功するのではないかと思い、まずは自分の環境でSMEPが有効なのかどうかを調べてみることにしました。

/proc/cpuinfoのflagsにsmepが現れていればsmepが有効だと、上述のももいろテクノロジーさんの記事にあったので、smepをgrepしてみました。ですが、有効ではありませんでした。当然有効化されているものだと思っていたので、どういうことかわからず、Windows側でも調べてみました。Coreinfo.exeというものを使って調べてみたところ、こちらでは有効になっていました。ホストOSでは有効化されているが仮想マシン上だと無効化されているのか、そもそも/proc/cpuinfoの情報が誤りで実は有効化されているのか、2つのパターンが考えられました。そこで、別の環境でも調べてみようと思い、Ubuntu14.04 LTSを用意しました。ですが、問題で与えられたプログラムを実行することで/proc/keysにleaked-keyringという名前で100個の参照をもつキーリングオブジェクトの情報が表示されるはずが、そもそも/proc/keysが存在しないと言われてしまいました。/proc/key-usersなどは存在するのに、なぜだろうと思い調べたところ、Ubuntuのバグだという情報( https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1344405 )がありました。/boot/config-3.13.0-24-genericを見てみると、確かにCONFIG_KEYS_DEBUG_PROC_KEYが有効になっていませんでした。カーネルをリビルトする必要があるなと思い、この問題を修正すると同時に、SMEPも明示的に無効化できれば何か進捗があるのではないかと思い、ここ( https://forums.ubuntulinux.jp/viewtopic.php?id=17851 )を参考に進めました。/boot/config-3.13.0-24-genericにおいて、

CONFIG_KEYS_DEBUG_PROC_KEY=y
CONFIG_SECURITY_CAPABILITIES=y

をそれぞれ追加してから、念のためCONFIG_X86_SMAP=yを削除。そして、

$ sudo apt-get install kernel-package libncurses5-dev

で必要なパッケージをインストールして

$ sudo apt-get install linux-source-3.13.0

カーネルのソースをインストールします。次に、ビルドする段階でSMEPを無効化します。念のためSMAPも無効化しました。linux-source-3.13.0/arch/x86/kernel/cpu/common.cの883行目のsetup_smepと884行目のsetup_smapをそれぞれコメントアウトlinux-source-3.13.0/arch/x86/kernel/cpu/common.cの270行目と271行目もコメントアウト

// if (cpu_has(c, X86_FEATURE_SMEP))
// set_in_cr4(X86_CR4_SMEP)
$ sudo cp /boot/config-3.13.0-63-generic .config
$ sudo make oldconfig

.configファイルを作ってから、makeします。ここでいろいろ聞かれますがsmapについて聞かれたとき以外は全てEnterしました。次は

$ sudo make menuconfig

でパラメータを設定します。Security optionsで、"Enable the /proc/keys file by which keys may be viewed"にYを入力して*にします。最後に

$ sudo make-kpkg clean
$ sudo make-kpkg --initrd kernel_image

カーネルのリビルドを行います。

2時間くらいかかりましたが無事終わったので、.debをインストールして再起動。

$ sudo dpkg -i linux-image-3.13.11-ckt39_3.13.11-ckt39-10.00.Custom_i386.deb
$ sudo reboot

uname -rでカーネルのバージョンを調べてみると、3.13.11-ckt39になっていました。肝心の/proc/keysですが、ちゃんと存在していました。しかし、与えられたプログラムを実行してみても/proc/keysに情報が追加されないので、まさかパッチが当たったものではないかと思いprocess_keys.hを探してみると、/usr/src/linux-source-3.13.0/security/keys/process_keys.cにjoin_session_keyring関数があり、脆弱性が修正された印であるkey_put(keyring)が存在していました。その行をコメントアウトしてから、再びカーネルビルドします。無事終了したので、PoCを動かしてみると

keyctl: Disk quota exceeded

という文章が大量に出力され、クラッシュしてしまいました。再起動してもう1度実行してみるとシェルを起動するところまでいきましたが、肝心のroot権限が取れていません。まだSMEPを無効化する手段はあったのでそちらも試します。/usr/src/linux-source-3.13.0/Documentation/kernel-arameters.txtの2043行目にnosmepがあるので、これを追加してみました。起動する時のUbuntuのロゴが出てくる段階でShiftキーを押します。eで編集できるので、linuxという行を選択してnosmep=1を追加。再び実行してみるも、keyctl: Disk quota exceededでクラッシュしました。指定の仕方が間違っているのかなと、nosmepという風に変えて追加しましたが、これでもrootになることはできませんでした。

他にわかったことが1つあり、Ubuntu15.10で作業していた時のことです。PoCの実行を開始するとフル稼働するため画面が固まって動かなくなるので、ネットワークの設定を変えて、仮想マシンにプライベートIPアドレスを割り振ってsshで接続できるようにしました。終了するのを待っていたのですが、CPU使用率的に実行が完了しているはずなのに、画面が固まってしまったときがありました。sshしてからtopコマンドで見てみると、systemd-journalctlというプロセスがCPU使用率100%になっていました。journalctlは、/var/logにjournalというディレクトリを作っておくことで、ログデータが消えずに保存することができるので、それで確認してみたところ以下のようなログがあり、

May 27 01:10:43 yyy-VirtualBox kernel: BUG: unable to handle kernel NULL pointer dereference at 0000000000000009
May 27 01:10:43 yyy-VirtualBox kernel: IP: [<ffffffff811de366>] kmem_cache_alloc+0x76/0x200
May 27 01:10:43 yyy-VirtualBox kernel: PGD d6a0b067 PUD d4574067 PMD 0
May 27 01:10:43 yyy-VirtualBox kernel: Oops: 0000 [#1] SMP

NULL pointer deferenceを確認することができました。

いろいろ試行錯誤しましたが、カーネルという巨大な存在が絡んでくると、一気に難易度が増すなと感じました。

動かそうとしていたPoC( https://gist.github.com/PerceptionPointTeam/18b1e86d1c0f8531ff8f )と、これに並列処理を加えたもの( https://gist.github.com/ywkw1717/124025091edeedf35a64f6c53980114a )を記載しておきます。( /proc/keysの値の変化を見たかったので、それをcatする処理も追加しています )

最後に、このような攻撃を緩和する対策手法について書きたいと思います。SMEPやSMAPやKADRは今回初めて知ることができ、調べていく過程でSELinuxなるものも知りました。攻撃を緩和するためには、SMEPのような防御機構は必須だと思います。ですが調べていく中で、SMEPはROP等のテクニックを使うことで回避が可能だとわかりました。ではどのような対策方法が考えられるのか、3つほど挙げてみました。 まずは、当たり前ですが一番有効かつ簡潔な対策として、アップデートを頻繁に行い常に最新版にしておくということが非常に重要だと思います。公式から提供されているパッチが当たったバージョンを使っていない場合、既知の脆弱性を攻撃者に突かれてしまう危険性があります。頻繁にアップデートしておくことはとても大事なことだと思います。 次に、この問題を解いていて気になったのは、攻撃段階で必要になった2つのアドレスです。これらはstaticなものだと書かれていましたが、ASLRのようにアドレスをランダマイズ化することはできないのでしょうか。ASLRは総当たりで破られてしまったり、攻撃を完全に防ぐ手段ではないですが、緩和することは可能です。 また、今回の脆弱性の検証に、公開されているPoCを使いましたが、このプログラム内では大量の計算を行っています。約42億回のループを回しており、明らかに不自然な動きをしているプログラムです。よって、そのような振る舞いを検知して強制終了させるような防御機構があれば、今回のような攻撃は防げるのかなと考えます。

解いてみた感想としては、難易度が高く、正直他の問題に変えようかと考えた時もありました。しかし、問題に興味を持って始めたのに難しいからやらないというのは理由にならないと思います。わからないぶん、多くのことを自分で調べて手を動かしてやってみたので、得たものは非常に大きかったです。

選択問題A-6

まず完成したコードを貼ります。 gistでsecretにして公開しました。

https://gist.github.com/ywkw1717/d1e5927306b64ea31f10c24aca0b937d

PEファイルはCTFでもよく出てきており、Ollydbgなどで解析したこともあったので、より理解を深めるためにもこの問題に取り組みました。最初はPythonで書こうと思いました。pefileというモジュールを見つけたのですが、これを使っては他者のコードは利用しないというルールに反するかなと思ったので、やめました。次に目を付けたのがC言語です。どうやらWinNt.hというヘッダファイルにPEファイルの構造体が含まれているらしいのでそれをincludeして使おうと思いました。ですが、これでは環境依存するプログラムになってしまうのではないかと考えました。なぜなら、WinNt.hはWindowsでしか使うことができないからです。そこで、Linuxなどでも使えるような環境依存しないプログラムを作ろうと考え、WinNT.hで定義されている構造体をそのまま自分のプログラムに直書きするようにしました。そして、ファイルの頭からパースしていき文字列型リソースを取り出すようにしました。

PEヘッダの構造に関してはこのサイト( http://home.a00.itscom.net/hatada/mcc/doc/pe.html )や持っていた本(デバッガによるx86プログラム解析入門)を参考にしました。より詳細なところはMicrosoftの資料( https://msdn.microsoft.com/en-us/library/ms809762.aspx )を参考にしました。

このプログラムはまず、コマンドライン引数から対象のファイル名を受け取ります。コマンドライン引数が渡されていないときやファイルが開けないときはエラーメッセージを出力して終了します。バイナリモードでファイルを開いた後は、getDosHeader関数にファイルポインタを渡してMZスタブを取得します。PEファイルは、MZスタブやMZ-DOS ヘッダと呼ばれるものから始まります。0x5A4Dがシグネチャとなっており、16進ダンプしたときに先頭に”MZ"という文字が現れるはずです。これはWinNT.hで構造体として宣言されています。WinNT.hのソースはここ( https://source.winehq.org/source/include/winnt.h )を参考にしました。型の宣言で使われているWORDはunsigned shortのtypedefで2バイトです。DWORDはここ( https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx )ではunsinged longとしてtypedefされていましたが、自分の環境(Ubuntu16.04 64bit)でsizeof(unsigned long)を出力してみたところ8バイトになってしまいました。これは環境による問題で、WindowsMac OS XUbuntu・Solaris11などで試してみましたが、統一されておらず、さらに32bitと64bitでも違う値になってしまいました。そこで”stdint.h"に入っている”uint32_t”を使うことで環境による差異を無くしました。uint32_tは4バイトです。MZスタブの中でも特に、NTヘッダへのオフセットを保持している”e_lfanew”というメンバが重要です。NTヘッダではPEファイルであることを示すシグネチャ”0x50450000(PE00)“、Fileヘッダ、Optionalヘッダをメンバとして保持しています。ですがこのプログラムでは、構造体を厳密に宣言する必要はないと考え、NTヘッダはPEファイルのシグネチャのみを保持するようにしました。getNtHeaderでNTヘッダを読み込んだ後は、Fileヘッダです。Machineというメンバは、32bitなら0x014C、64bitなら0x8664です。NumberOfSectionsはセクションの数を表すので、セクションヘッダを確保するときに使用します。Fileヘッダを読み込んだ後はOptionalヘッダを読み込みますが、Optionalヘッダは32bitの場合と64bitの場合で少し構造が違います。なので、FileヘッダのMachineで条件分岐して、32bitと64bitに対応するようにしました。Optionalヘッダの下にはセクションヘッダがあり、その下には各セクションの実態があります。先ほど述べた通り、セクションヘッダを確保するためにFileヘッダのNumberOfSectionsの数だけfor文を回し、各セクションの情報を取得します。飛ばし飛ばしでしたが、これでPEヘッダの取得が完了しました。ここまでは特に詰まることなく実装できたのですが、この後からが難しかったです。

まず、問題文にある「文字列型リソース」が何を指しているのかがわからず、stringsコマンドを打った時に表示されるような、表示可能文字列全てを取得するのか.rsrcセクションにある文字列を取得すればいいのか、よくわかりませんでした。そこで、実行したときの動作を考察したり、ILSpyという.NETアプリケーションをデコンパイルできるツールを使い、与えられたプログラムのソースコードを見てみました。与えられた.NETアプリケーションは”Hello world!”、”hoge fuga”、”string test”という3つの文字列を表示して終了しています。さらに、ILSpyでデコンパイルした結果の中にあった、Resourcesというフォルダの中の”ConsoleApplication1.Properties.Resources.resources”というファイルにString Tableがあり、”String1”、”String2”、”String3”をName、”Hello world!”、”hoge fuga”、”string test”をValueとして表していました。よって、”わざわざ3つも文字列を出力していること”、”String Tableが3つのペアを保持していること”から、問題文にある文字列型リソースとはこのString Tableで管理している3つの文字列のことを指しているのではないかと推測しました。

また、.textセクションの構造が全くわかりませんでした。今まで見てきたPEファイルの構造を説明しているサイトや本では、プログラムコードが格納されていること以外はわからず、どうやってパースしようか詰まってしまいました。ですが、ここであることを思い出しました。このプログラムはただのPEファイルではなく.NETアプリケーションであることです。そのことに注意しながらさらに調べると.NETアプリケーションでは.textセクションに格納される内容が違うことがわかりました。stackoverflowに投稿されていたこの質問( http://stackoverflow.com/questions/25095894/understanding-text-section-of-pe-executable )の解答が参考になりました。.NETでは.textセクションに格納される内容が、普通のPEファイルで格納されるものとは違う、.NET特有のものが格納されるのだとわかりました。CLRヘッダというものが使われることがわかり、CorHdr.hファイルで定義されているらしいので、そこに書かれていた”IMAGE_COR20_HEADER”をプログラムの中で定義して、CLRヘッダを取得してみました。最初は.textセクションの開始数バイトが何を指しているのかわからず、ConsoleApplication1.exeのバイナリを見てみて、CLRヘッダの場所を決め打ちで指定していましたが、CLRヘッダへのオフセットを求めてそれを使うようにしました。CLRヘッダへのオフセットもどこかに格納されているはずだと思い調べたところ、Optionalヘッダの中の”DataDirectory”の15番目にそれらしきものが格納されていることがわかりました。DataDirectoryは”IMAGE_DATA_DIRECTORY”という構造体で宣言されています。MicrosoSoftの資料( https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx )を参考にしました。ですがこの中に格納されていたのは仮想アドレスだったので、CLRヘッダのVirtualAddressから.textセクションのVirtualAddressを引くことで、.textセクションからCLRヘッダへのオフセットを求めることができました。CLRヘッダを読み込んだ後の0x1330から始まる機械語がなにを指しているのかわかりませんでしたが、このサイト( http://www.atmarkit.co.jp/fdotnet/technology/idnfw11_05/idnfw11_05_03.html )の後半で解説されていました。どうやらメイン関数やその他の関数、つまりコード部分が格納されていて、.NETではILという中間言語が使われていることがわかりました。ILSpyでも平気ですが、使ったことがなかったので、上のサイトで使われていたildasm.exeを使ってConsoleApplication1.exeのメイン部分を読んでみました。メイン関数では”get_String1”、”get_String2”、”get_String3”という3つの関数と、”WriteLine”というコンソールへの出力を担っていそうな関数で構成されていました。ildasm.exeの結果とConsoleApplication1.exeのバイナリを対応させながら読んでいき、callが0x28、retが0x2a、stloc.0~stloc.2がそれぞれ0xa,0xb,0xc、ldloc.0~ldloc.2がそれぞれ0x6,0x7,0x8など、命令と機械語の対応がわかったりしました。

ですが、コード部分が終わったあとが何を指しているのかがまたわからなくなりました。そこで今度は、他の.NETアプリケーションとConsoleApplication1.exeの構造を比較してみることにしました。サンプルとなるものが、過去に参加したCTFで得た.NETアプリケーション2つしかありませんでしたが、それらと比較してみました。すると、”BSJB”という文字列が3つのプログラム全てに現れていました。さらに、ConsoleApplication1.exeの”Hello world!”などの文字列はこのBSJBのすぐ前に現れていること、他の2つのプログラムはBSJBの前に特にそれらしき文字列が現れていないことがわかりました。”BSJBは何かのシグネチャであること”、”ConsoleApplication1.exeは文字列を表現するのにString Tableのようなリソースを使う方式を使っていること”について考察しました。バイトオーダはリトルエンディアンかなと思い、BJSBとし、”0x424A5342”という文字列で検索してみると、このサイト( http://www.ntcore.com/files/dotnetformat.htm )で情報を見つけることができ、Metadataセクションのシグネチャであることがわかりました。知りたいのはMetadataセクションとILコードの間に格納されているデータなので詳しくは調べませんでしたが、.NETアプリケーションには必ず格納されているものだとわかりました。

ここからが一番詰まったのではないかと個人的には思っています。MetadataセクションとILコードの間に格納されているデータが、一体どのような構造で格納されているのかがさっぱりでした。ふと、バイナリに現れている”System.Resources.ResourceReader”という文字列が気になり調べていたらようやく進みました。MicrosoSoftの資料( https://msdn.microsoft.com/ja-jp/library/system.resources.resourcemanager.magicnumber(v=vs.110).aspx )に、ResourceManagerのMagicNumberフィールドは値が”0xBEEFCACE”に設定されると書いてあります。ConsoleApplication1.exeにも0x32Cからリトルエンディアンで格納されていました。ResourceManagerと、それより下の構造に関してはResourceSet.csに定義されている構造体( https://referencesource.microsoft.com/#mscorlib/system/resources/runtimeresourceset.cs,95 )と、実際のバイナリを見ながら実装しました。CLRヘッダの下のILコードは読み込む必要がなかったので、CLRヘッダの時と同様にオフセットを求めて、Resource Manager Headerにアクセスするようにしました。Resource Manager HeaderのメンバであるMagicNumberが始まる前の数バイトは、数えてみるとどうやらRuntime Resource Reader Data Sectionまでのサイズを表していることがわかったので、Resource Manager Headerに格納するようにしました。Runtime Resource Reader Name Sectionの、UTF-16で格納されている文字の前にある1バイトは文字数の長さを指していたので、これもメンバとして保持するようにしました。また、それぞれの文字列の長さの前の1バイトが常に0x1となっていて、これは文字列の開始を表すのかなと思いましたが、確証がなかったのでTopというメンバで保持するようにしました。

ここまで実装することで、ようやくConsoleApplication1.exeの3つの文字列型リソースを取り出すことができました。もしResource Managerなどを使わない形式であれば、Resource Headerが見つからないということで終了するようにしました。他にも、各ヘッダのシグネチャが不正だった場合などは、即終了するようにしました。

実行結果を貼ります。各セクションの情報を表示してから、最後に文字列型リソースを表示するようにしました。1万文字の制限を超えてしまいそうなので、少し省略しています。

$ ./string_parser ConsoleApplication1.exe
File size : 6656

----DOS HEADER----
e_magic: 5A4D
e_clip: 90
…
e_lfanew: 80

----NT HEADER----
Signature: 4550

----FILE HEADER----
Machine: 14C
…

----OPTIONAL HEADER----
Magic: 10B
…
LoaderFlags: 00
NumberOfRvaAndSizes: 10

----IMAGE_DATA_DIRECTORY----
VirtualAddress: 00
Size: 00

VirtualAddress: 2E1C
Size: 4F
…
VirtualAddress: 2008
Size: 48

VirtualAddress: 00
Size: 00

----SECTION HEADER----
[1]
Name: .text
PhysicalAddress: E74
VirtualSize: E74
VirtualAddress: 2000[2]
Name: .rsrc
PhysicalAddress: 590
VirtualSize: 590
…

----CLR HEADER----
cb: 48
MajorRuntimeVersion: 02
MinorRuntimeVersion: 05
…

----Resource Manager Header----
Magic: BEEFCACE
HeaderVersion: 01
…

----Runtime Resource Reader Header----
Version: 02
NumberOfResources: 03
NumberOfType: 00
Padding: PADPADP

HashValues: 82821361
VirtualOffset: 00

HashValues: 82821362
VirtualOffset: 13
…

----String Resource----
[1]
Name: String1
Value: Hello world!

[2]
Name: String2
Value: hoge fuga

[3]
Name: String3
Value: string test

これで終わりかと思いましたが、テストケースなどが手元にないので文字列型リソースが取得できない場合もあるのではないかと思い、他のプログラムもパースしてみることにしました。対象にしたのは”mscorlib.dll”です。これは.NETフレームワークの基本的なクラスやライブラリを格納しているファイルです。よって、このファイルの文字列型リソースを取得することができれば、大抵のファイルで成功するのではないかと考えました。

早速mscorlib.dllを対象に実行してみると、案の定取得できていませんでした。Runtime Resrouce Reader Name Sectionの処理の途中で取得したものが無茶苦茶になっており、どうやら原因は取得しようとしたもののサイズでした。ある一定以上のサイズの時は、サイズを格納する1バイトの後に、もう1バイト”なにか”が格納されていました。とりあえずこれを適当にスキップして回避してみましたが、Runtime Resource Reader Data Sectionのほうも同様でした。ある一定以上のサイズの時は、サイズのあとの1バイトに”なにか”が格納されています。その”なにか”は、0x3である時と0x1である時が見つかりました。0x1の時は、サイズを格納している1バイトの数でパースすることで成功するのですが、0x3の時は明らかにサイズが足りませんでした。サイズの1バイトと、”なにか”を合計した2バイトをリトルエンディアンとして計算してみてもサイズが大きすぎます。全くわからなかったので、その現象が起きるところをいくつか集め、考察してみました。すると0x3の場合は、実際の文字列の長さからサイズの1バイトを引いた値が、どれも0x100になりました。”なにか”が0x2になっている箇所はないかともう一度探してみたところ、ありました。0x2の時は、実際の文字列の長さからサイズの1バイトを引いた値が0x80になりました。0x1の時は、実際の文字列の長さとサイズの1バイトが変わらないことから、これは”0x80 * (n - 1)”をサイズの1バイトに足すことで、実際の文字列のサイズが取得できるのではないかと考えました。1バイトで表現できる数は0xffで255文字です。だからと言って、0xffを超えた時だけサイズをもう1バイト増やしていたのでは、サイズとして1バイト読み込んだ時に、その次の1バイトがサイズの情報なのか、実際の文字列の情報なのかを判別する術がありません。”なにか”が0x1の時に、サイズの1バイトと実際の文字列のサイズが変わらなかったのは、0x80以上の時は2バイト確保するようにしており、0x80~0xffの時は0x1、もし0xffを超えることがあれば0x1が0x2になる、0x3も同様です。このようにすることで、サイズとして1バイト読み込んだ時に、次の1バイトがサイズなのか文字列なのかがわかるようになっているのだと思います。よって、サイズとして確保した1バイトが0x80以上の時は、もう1バイト読み込んで実際のサイズを求めるようにしました。これでようやく、全ての文字列型リソースをパースすることができました。mscorlib.dllの文字列型リソースは、3162個ありました。

実行結果が多すぎるので、文字列型リソースの部分の結果だけを貼ります。

$ ./string_parser mscorlib.dll

…

----String Resource----
[1]
Name: Acc_CreateAbst
Value: Cannot create an abstract class.

[2]
Name: Acc_CreateAbstEx
Value: Cannot create an instance of {0} because it is an abstract class.

[3]
Name: Acc_CreateArgIterator
Value: Cannot dynamically create an instance of ArgIterator.

…

[3159]
Name: event_TaskScheduled
Value: Task {2} scheduled to TaskScheduler {0}.

[3160]
Name: event_TaskStarted
Value: Task {2} executing.

[3161]
Name: event_TaskWaitBegin
Value: Beginning wait ({3}) on Task {2}.

[3162]
Name: event_TaskWaitEnd
Value: Ending wait on Task {2}.

Ubuntu16.04 64bit、Mac OS X 10.9 64bit、Solaris11 32bit、Windows10 64bitなどで動作することを確認しました。Windowsで動かそうと思いexeファイルを生成しようとしたのですが、Visual Studio 2010ではエラーが出ることなくビルドできましたが、Visual Studio 2017では fopenではなく、fopen_sを使えということでエラーを吐かれました。fopen_sを使うようにすればビルドも成功し、正常な動作を確認することができたので、Visual Studio 2017でビルドする場合はfopen_sを使う必要がありそうです。

実装するのに1週間ほどかかってしまいましたが、PEファイルや.NETアプリケーションの構造の理解がかなり深まり、とても勉強になりました。

DEF CON CTF Qualifier 2017 Writeup

f:id:ywkw1717:20170501053356p:plain

開催期間(JST)

04/29 AM9:00 ~ 05/01 AM9:00

結果

・チーム名:wabisabi

・得点:85pt

・順位:得点したチーム中,139/368

解いた問題

・crackme1(Baby’s First)

・magic(Crackme 2000)

・sorcery(Crackme 2000)

取り組んだが解けなかった問題

・smashme(Baby’s First)

・beatmeonthedl(Baby’s First)

・alchemy(Crackme 2000)

はじめに

参加しました.

去年は1問も解けず敗北したのでリベンジ.

最終日は夜しか参加できなくて,朝までやってたら英語とオートマトンの授業を寝過ごして死亡しました.

Writeup

crackme1(Baby’s First)

問題文

crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me:10001

配布されたバイナリ.

$ bzip2 -dc 8a97fb8c264a3b34dad0a707dbfc92832067a0fa0f2b5a576c73557960b11506.tar.bz2|tar xvf -
$ file 4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652
4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, stripped

ncで繋いでみる.

$ nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001

send your solution as base64, followed by a newline
4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652

base64で送ってくれと言われるので適当に送ってみると

aG9nZQo=
didn't exit happy, sorry

とりあえず,objdumpでバイナリを逆アセンブルして,静的解析する.

objdump -d -M intel 4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652

適当に見ていくと,入力値をチェックしているようなところを見つけた.

cmp    rdi,0x79
je     94f <_init+0x257>
sub    rsp,0x8
mov    edi,0x1
call   758 <_init+0x60>
mov    eax,0xa7
ret

cmp    rdi,0x65
je     969 <_init+0x271>
sub    rsp,0x8
mov    edi,0x2
call   758 <_init+0x60>
mov    rax,0xffffffffffffff9b
ret

cmp    rdi,0x73
je     985 <_init+0x28d>
sub    rsp,0x8
mov    edi,0x3
call   758 <_init+0x60>
mov    eax,0xa0
ret

cmp    rdi,0x20
je     99f <_init+0x2a7>
sub    rsp,0x8
mov    edi,0x4
call   758 <_init+0x60>
mov    eax,0x16
ret

cmp    rdi,0x61
je     9b9 <_init+0x2c1>
sub    rsp,0x8
mov    edi,0x5
call   758 <_init+0x60>
mov    rax,0xfffffffffffffff0
ret

cmp    rdi,0x6e
je     9d5 <_init+0x2dd>
sub    rsp,0x8
mov    edi,0x6
call   758 <_init+0x60>
mov    eax,0x190
ret

cmp    rdi,0x64
je     9ef <_init+0x2f7>
sub    rsp,0x8
mov    edi,0x7
call   758 <_init+0x60>
mov    eax,0x1d
ret

cmp    rdi,0x20
je     a09 <_init+0x311>
sub    rsp,0x8
mov    edi,0x8
call   758 <_init+0x60>
mov    eax,0xc5
ret

cmp    rdi,0x68
je     a23 <_init+0x32b>
sub    rsp,0x8
mov    edi,0x9
call   758 <_init+0x60>
mov    eax,0x4
ret

cmp    rdi,0x69
je     a3d <_init+0x345>
sub    rsp,0x8
mov    edi,0xa
call   758 <_init+0x60>
mov    eax,0xc1
ret

cmp    rdi,0x73
je     a57 <_init+0x35f>
sub    rsp,0x8
mov    edi,0xb
call   758 <_init+0x60>
mov    eax,0x10e
ret

cmp    rdi,0x20
je     a71 <_init+0x379>
sub    rsp,0x8
mov    edi,0xc
call   758 <_init+0x60>
mov    rax,0xffffffffffffff4e
ret

cmp    rdi,0x68
je     a8d <_init+0x395>
sub    rsp,0x8
mov    edi,0xd
call   758 <_init+0x60>
mov    eax,0x23
ret

cmp    rdi,0x61
je     aa7 <_init+0x3af>
sub    rsp,0x8
mov    edi,0xe
call   758 <_init+0x60>
mov    eax,0xae
ret

cmp    rdi,0x6e
je     ac1 <_init+0x3c9>
sub    rsp,0x8
mov    edi,0xf
call   758 <_init+0x60>
mov    eax,0x58
ret

cmp    rdi,0x64
je     adb <_init+0x3e3>
sub    rsp,0x8
mov    edi,0x10
call   758 <_init+0x60>
mov    eax,0xb
ret

cmp    rdi,0x73
je     af5 <_init+0x3fd>
sub    rsp,0x8
mov    edi,0x11
call   758 <_init+0x60>
mov    eax,0x2b
ret

cmp    rdi,0x20
je     b0f <_init+0x417>
sub    rsp,0x8
mov    edi,0x12
call   758 <_init+0x60>
mov    rax,0xffffffffffffff6d
ret

cmp    rdi,0x73
je     b2b <_init+0x433>
sub    rsp,0x8
mov    edi,0x13
call   758 <_init+0x60>
mov    eax,0x6b
ret

cmp    rdi,0x68
je     b45 <_init+0x44d>
sub    rsp,0x8
mov    edi,0x14
call   758 <_init+0x60>
mov    eax,0x10
ret

cmp    rdi,0x6f
je     b5f <_init+0x467>
sub    rsp,0x8
mov    edi,0x15
call   758 <_init+0x60>
mov    eax,0x18e
ret

cmp    rdi,0x6f
je     b79 <_init+0x481>
sub    rsp,0x8
mov    edi,0x16
call   758 <_init+0x60>
mov    eax,0x91
ret

cmp    rdi,0x6b
je     b93 <_init+0x49b>
sub    rsp,0x8
mov    edi,0x17
call   758 <_init+0x60>
mov    eax,0xce
ret

cmp    rdi,0x20
je     bad <_init+0x4b5>
sub    rsp,0x8
mov    edi,0x18
call   758 <_init+0x60>
mov    rax,0xffffffffffffff17
ret

cmp    rdi,0x77
je     bc9 <_init+0x4d1>
sub    rsp,0x8
mov    edi,0x19
call   758 <_init+0x60>
mov    eax,0xab
ret

cmp    rdi,0x69
je     be3 <_init+0x4eb>
sub    rsp,0x8
mov    edi,0x1a
call   758 <_init+0x60>
mov    eax,0x166
ret

cmp    rdi,0x74
je     bfd <_init+0x505>
sub    rsp,0x8
mov    edi,0x1b
call   758 <_init+0x60>
mov    rax,0xffffffffffffffe8
ret

cmp    rdi,0x68
je     c19 <_init+0x521>
sub    rsp,0x8
mov    edi,0x1c
call   758 <_init+0x60>
mov    eax,0x53
ret

cmp    rdi,0x20
je     c33 <_init+0x53b>
sub    rsp,0x8
mov    edi,0x1d
call   758 <_init+0x60>
mov    rax,0xffffffffffffffb1
ret

cmp    rdi,0x65
je     c4f <_init+0x557>
sub    rsp,0x8
mov    edi,0x1e
call   758 <_init+0x60>
xor    eax,eax
ret

cmp    rdi,0x78
je     c66 <_init+0x56e>
sub    rsp,0x8
mov    edi,0x1f
call   758 <_init+0x60>
mov    eax,0x9e
ret

見やすいように,cmpで区切っている.

比較している対象を切り出してみる.

0x79
0x65
0x73
0x20
0x61
0x6e
0x64
0x20
0x68
0x69
0x73
0x20
0x68
0x61
0x6e
0x64
0x73
0x20
0x73
0x68
0x6f
0x6f
0x6b
0x20
0x77
0x69
0x74
0x68
0x20
0x65
0x78

これをlistとか適当なファイル名で保存して,pythonで文字列に変換してみる.

#!/usr/bin/env python

import sys

def main():
    f = open('./list')
    for i in f:
        sys.stdout.write(chr(int(i.strip('\n'), 16)))


if __name__ == '__main__':
    main()

すると,以下のような文字列が得られる.

yes and his hands shook with ex

なんかパッとしなかったのだが,とりあえずbase64エンコードしてから投げてみる.

$ nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001

send your solution as base64, followed by a newline
4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652
eWVzIGFuZCBoaXMgaGFuZHMgc2hvb2sgd2l0aCBleAo=
The flag is: important videos best playlist Wigeekuk8

いやいやこんな簡単なわけあるか・・?と思いながらも

important videos best playlist Wigeekuk8

という文字列をsubmitしてみると,通った.

magic(Crackme 2000)

問題文

cm2k-magic_b46299df0752c152a8e0c5f0a9e5b8f0.quals.shallweplayaga.me:12001

配布されるバイナリは,恐らく上のcrackme1で渡されたものの,比較している文字が変わったものを200個

自動化しろということだと思うので,上でやったことをコード一発でできるようにした.

とりあえず,書いたコードを貼る.

#!/usr/bin/env python

import commands
import sys
import base64
import socket

def main():
    dic = dic_generator()
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('cm2k-magic_b46299df0752c152a8e0c5f0a9e5b8f0.quals.shallweplayaga.me', 12001))

    while True:
        res_list = []
        data = s.recv(256)
        if data == "":
            break

        if repr(data).find('send your solution') > -1:
            pass
        elif repr(data).find('flag') > -1:
            print repr(data)
            return
        else:
            index = repr(data)[1:-3]
            print dic[index]
            s.sendall(dic[index] + '\n')
    s.close()


def dic_generator():
    f = open('./list')
    dic = {}

    for i in f:
        result = commands.getoutput('objdump -d -M intel ' + i.strip('\n') + '|grep 48\ 83\ ff')
        res_list = result.split('\n')

        string = ''
        for j in res_list:
            string = string + chr(int(j[-4:], 16))

        str_base64 = base64.b64encode(string)

        dic[i.strip('\n')] = str_base64

    f.close()
    return dic


if __name__ == '__main__':
    main()

listという名前のファイルは,200個のファイル名をコピペして作った,ファイル名が格納されているもの.

どうやったかというと,objdumpで逆アセンブルした結果に対して,機械語grepした.

比較している部分で使われている機械語はユニークなもので,

48 83 ff 比較対象

という組み合わせは,他の部分では存在しなかったので,簡単にできた.

その後dic_generatorという関数で,ファイル名が「キー」で,値が「比較対象をbase64エンコードしたもの」である,辞書を作る.

あとは,ソケット通信を開始してから

send your solution as base64, followed by a newline
65cb596908789372c2d6fbeb0ac3a0e3a1089039138711a016ec3994ad5c7f10

この2行目のファイル名のみを取り出して,それをキーにして値を取り出し,送り返す.

10回連続で正解すると,flagが貰えるっぽい.

$ python script.py

ZGVyLiAiVGhhbmtzLiI=
aW5kIGFzIEkgc3RhZ2dlcmVkIGludG8gdGhlIGtpdGNoZW4sIHdoZXJlIExpbCA=
IEhhdW50ZWQgTWFuc2lvbiwgaWYgeW91IGp1c3QgZ2l2ZSBt
YWxlIG9yIG90aGVyIGxpbWl0YXRpb25zIG9uIHRoZSBleGNsdXNpdmUgcmlnaA==
Y3Jld3Mgc2N1cnJ5aW5nIGk=
b3VzZXMgaW4gdGhlaXIgaG9tZXRvd25zLiBUaGUgTWFuc2lvbidzIGJldHRlciA=
eSBvZiwgb3IgdGhl
YnVybmVkIGFsbCBoaXMgcmVwdXRhdGlvbiBjYXBpdGE=
LCB0dXJuaW5nIG1lIGludA==
IGltbWVkaWF0ZQ==
'The flag is: a color map of the sun sokemsUbif\n'

sorcery(Crackme 2000)

問題文

cm2k-sorcery_13de8e6bf26e435fc43efaf46b488eae.quals.shallweplayaga.me:12002

またmagicと同じように,解凍すると200個のバイナリ.

上で書いたスクリプトがそのまま使えるかなと思って試してみたらだめだったので,逆アセンブルして読んでみる.

magicと同じように機械語grepすると,余分なものまで取ってきてしまう.

↓これは,80 f9(cmp cl) でgrepしたもの.0x80以降のcmp全部がいらない.そう思った根拠は,ASCIIコードがないから.あるものもあるけど,明らか違うので排除.

cmp    cl,0x6e
cmp    cl,0x69
cmp    cl,0x63
cmp    cl,0x20
cmp    cl,0x46
cmp    cl,0x72
cmp    cl,0x6f
cmp    cl,0x6e
cmp    cl,0x74
cmp    cl,0x80
cmp    cl,0xf0
cmp    cl,0xf4
cmp    cl,0x80
cmp    cl,0x80
cmp    cl,0xe0
cmp    cl,0xed
cmp    cl,0xee
cmp    cl,0x80
cmp    cl,0x3
cmp    cl,0xe0
cmp    cl,0xf0
cmp    cl,0x1
cmp    cl,0x3
cmp    cl,0x2
cmp    cl,0x1
cmp    cl,0x3
cmp    cl,0x2
cmp    cl,0x1
cmp    cl,0x3
cmp    cl,0x2
cmp    cl,0x4

これでいけるかと思ったらまだダメ.

もう一度注意深く読んでみると,

.
.
.
.

cmp    cl,0x74
jne    38b6 <pthread_mutex_lock@plt+0xd26>
cmp    rax,0x9
je     37e2 <pthread_mutex_lock@plt+0xc52>
mov    al,BYTE PTR [rdi+0x9]

cmp    al,0x69
jne    38c2 <pthread_mutex_lock@plt+0xd32>
mov    rax,QWORD PTR [rsp+0x8]
test   rax,rax
je     377e <pthread_mutex_lock@plt+0xbee>
.
.
.
.

この,cmp al, 0x69のところ.

最後の1文字だけ,alレジスタを使って比較している.

よって,こいつも取り出してあげる必要がある.コマンドで頑張ったらいけた.

あとは,書いたコード.magicで書いたコードをちょっと変えただけ.

#!/usr/bin/env python

import commands
import sys
import base64
import socket

def main():
    dic = dic_generator()

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('cm2k-sorcery_13de8e6bf26e435fc43efaf46b488eae.quals.shallweplayaga.me', 12002))

    while True:
        res_list = []
        data = s.recv(256)
        if data == "":
            break

        if repr(data).find('didn\'t exit happy, sorry') > -1:
            pass
        elif repr(data).find('send your solution as base64') > -1:
            pass
        elif repr(data).find('flag') > -1:
            print repr(data)
            return
        else:
            index = repr(data)[1:-3]
            print dic[index]
            s.sendall(dic[index] + '\n')
    s.close()


def dic_generator():
    f = open('./list')
    dic = {}

    for i in f:
        result = commands.getoutput('objdump -d -M intel ' + i.strip('\n') + '|grep 80\ f9 |head -n -22')
        result = result + '\n' + commands.getoutput('objdump -d -M intel ' + i.strip('\n') + '|grep cmp |grep al |head -2 |tail -1')
        res_list = result.split('\n')

        string = ''
        for j in res_list:
            string = string + chr(int(j[-4:], 16))

        str_base64 = base64.b64encode(string)

        dic[i.strip('\n')] = str_base64

    f.close()
    return dic


if __name__ == '__main__':
    main()

実行結果.

$ python script.py

IEkgc2FpZC4gIkp1c3QgZ29pbmcgc29tZXdoZXJlIGVsc2UsIHN0YXJ0aW4=
b2QgcG9pbnQsIExpc2EuIFRoZSBvZmZlciB3ZSdyZSBtYWtpbmcgdG8g
IHRoYXQgaGFwcGVucywgdGhlcmUncyBub3RoaW5n
IGJyb3VnaHQgb3Zlcg==
IG9mIGdldHRpbmcgaW50byBjaGFyYWN0ZXIuIFNv
aG9tZSwgYXM=
cyBhIHNpbmdsZSBjYXN0bWVtYg==
ZyBhIGxpdHQ=
bWluZyB0byB0aGUgcG9pbnQuICJXZSByZWFkIHlvdQ==
aWNjZWQgbmVydm91c2x5IGFzIEkgd2F0Y2hlZCBteSBwcm9ncmVzcw==
"The flag is: don't forget me when you're famous Klousovnec\n"

取り組んだが解けなかった問題

beatmeonthedl(Baby’s First)

問題文

I really like to be beaten but keep it on the dl.

Connect to:

beatmeonthedl_498e7cad3320af23962c78c7ebe47e16.quals.shallweplayaga.me 6969

配布されたバイナリ.

$ file beatmeonthedl
beatmeonthedl: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, not stripped

実行してみるとこんな感じ.

      __      __       .__                                  __           
        /  \    /  \ ____ |  |   ____  ____   _____   ____   _/  |_  ____   
        \   \/\/   // __ \|  | _/ ___\/  _ \ /     \_/ __ \  \   __\/  _ \  
         \        /\  ___/|  |_\  \__(  <_> )  Y Y  \  ___/   |  | (  <_> ) 
          \__/\  /  \___  >____/\___  >____/|__|_|  /\___  >  |__|  \____/  
               \/       \/          \/            \/     \/                 
      __  .__             .__         .__                 _____    __  .__            
    _/  |_|  |__   ____   |  | _____  |__|______    _____/ ____\ _/  |_|  |__   ____  
    \   __\  |  \_/ __ \  |  | \__  \ |  \_  __ \  /  _ \   __\  \   __\  |  \_/ __ \ 
     |  | |   Y  \  ___/  |  |__/ __ \|  ||  | \/ (  <_> )  |     |  | |   Y  \  ___/ 
     |__| |___|  /\___  > |____(____  /__||__|     \____/|__|     |__| |___|  /\___  >
               \/     \/            \/                                      \/     \/ 
  _________.__                .___              __________                __
 /   _____/|  |__ _____     __| _/______  _  __ \______   \_______  ____ |  | __ ___________  ______
 \_____  \ |  |  \\__  \   / __ |/  _ \ \/ \/ /  |    |  _/\_  __ \/  _ \|  |/ \// __ \_  __ \/  ___/
 /        \|   Y  \/ __ \_/ /_/ (  <_> )     /   |    |   \ |  | \(  <_> )    <\  ___/|  |  \/\___ \ 
/_______  /|___|  (____  /\____ |\____/ \/\_/    |______  / |__|   \____/|__|_ \\___  >__|    /____ >
        \/      \/     \/      \/                       \/                    \/    \/             \/ 
Enter username: 

PCで見ないと表示が崩れてそう.

Enter username: hoge
Invalid user: hoge

Enter username: 

適当なusernameだと入れない.

gdbで,正当なusernameを探す.

$ gdb beatmeonthedl
[-------------------------------------code-------------------------------------]
   0x40125a <main+30>:   call   0x400cf6 <header>
   0x40125f <main+35>:   nop
   0x401260 <main+36>:   mov    eax,0x0
=> 0x401265 <main+41>:    call   0x400ec8 <login>
   0x40126a <main+46>:   test   eax,eax
   0x40126c <main+48>:   je     0x401260 <main+36>
   0x40126e <main+50>:   mov    eax,0x0
   0x401273 <main+55>:   call   0x400f52 <printmenu>

loginという関数があるので,ステップインして追いかける.

loginではさらに,checkuserとcheckpassという関数があり,usernameとpasswordをチェックしている.

checkuserにおいて,

0x400d9e <checkuser+86>:  mov    rax,QWORD PTR [rip+0x208c53]        # 0x6099f8 <user>

ここが実行された後にRAXを見ると,usernameがわかった.

RAX: 0x4085d8 --> 0x776100796c66636d ('mcfly')

さらに今度はpasswordを探す.

checkpassにおいて,

0x400e77 <checkpass+135>: mov    rax,QWORD PTR [rip+0x208b82]        # 0x609a00 <pass>

ここが実行された後にRAXを見ると,passwordがわかった.

RAX: 0x4085de --> 0x70616e73657761 ('awesnap')

この2つでログインすると,

Enter username: mcfly
Enter Pass: awesnap
I) Request Exploit.
II) Print Requests.
III) Delete Request.
IV) Change Request.
V) Go Away.
| 

ここから,pwnのようなことをやってシェルを奪うのかなと思ったが,全くわからなかった.

alchemy(Crackme 2000)

magicやsorceryと同じように自動化する問題?

自動化できないようにさらに工夫がされてて厳しかった.

感想

意外に結構解けたので嬉しかった.

smashmeとbeatmeonthedlは解いている人が多かったけど自分はサッパリだったのでこういうのも解けるようになりたい・・・

PlaidCTF 2017 Writeup

開催期間(JST)

04/22 AM6:00 ~ 04/24 AM6:00

結果

・チーム名:wabisabi

・得点:51pt

・順位:332/1150

解いた問題

・sanity check(Misc 1)

・zipper(Misc 50)

取り組んだが解けなかった問題

・Pykemon(Web 151)

・no_mo_flo(Reversing 125)

はじめに

参加しました.

また1問しか解けてないやんけ!!!ってことで,とりあえずやったことまとめ.

Writeup

sanity check(Misc 1)

PCTF{poop}

zipper(Misc 50)

問題文

Something doesn't seem quite right with this zip file. 

Can you fix it and get the flag?

zipファイルが渡される.

解凍しようとすると,

Archive:  zipper_50d3dc76dcdfa047178f5a1c19a52118.zip
warning:  filename too long--truncating.
:  bad extra field length (central)

バイナリを読む.

f:id:ywkw1717:20170424095413p:plain

ファイル名のサイズを格納しているところが2923となっていることから,めちゃくちゃな長さになっていることがわかる.(0x2329 → 9001文字?)

ここを正しい値にしないといけないが,正しい値がわからないので他の情報を元に修正する.

まず,zipファイルは大体,Local file header,File data,Central directory header,End of central directory recordの4つのフィールドから構成されている.

ZIP書庫ファイル フォーマット - 略して仮。

ここが詳しい.

読んでいくと,格納データがASCIIであることや,デフレート形式で圧縮されていることなどがわかる.

Central directory headerのファイル名が格納されている場所と,End of centralのシグネチャに注目した.

この間に格納されているデータは,

-----------
relative offset
of local header
-----------
file name
------------
extra field
------------
file comment
------------
End of central
dir signature
(0x50,0x4b,0x05,0x06)
------------

のようになっており,Central directory headerを読むとfile commentは0であることから,End of central directoryのシグネチャがある部分からextra fieldのサイズ分手前に戻し,求まった場所とrelative offset of local headerの間にあるのがファイル名であり,ファイルサイズも求めることができる.

extra fieldのサイズは,0x18(24bytes)

求まったファイル名は,

0000 0000 0000 0000

サイズは,0x8であることがわかる.

よって,Local file headerとCentral directory headerにあるfile name lengthという2byteの領域に書いてある,

2923

を,以下のように変更する.

0800

これで,いけると思ったがファイル名が指定されていないので適当に

666c6167

flagとかいうファイル名で書き換える.

これで解凍すると,

Huzzah, you have captured the flag:
PCTF{f0rens1cs_yay}

このようなテキストファイルが抽出できて,おしまい.

取り組んだが解けなかった問題

Pykemon(Web 151)

問題文

Gotta catch them FLAGs! 

Take this with you.

flaskで書かれたWebアプリケーションが与えられる.

Pykemonという名前の,ポケモンの丸パクリのゲーム.Pykemonを捕まえて,その記録とかを見れる.FLAGを捕まえてFLAGを読みだす.

こんな感じ.

f:id:ywkw1717:20170424125010p:plain

ソースコードも与えられる.

コードを読んでいくと,それぞれのPykemonにrarityという捕まえられる確率的なものが設定されていて,結論から言うとFLAGを捕まえることは不可能.

どうにかして,捕まえる必要がある.

やったこととしては,Burpを使ってセッションを書き換えたり,Pykemonをrenameするところに以下のようなstored XSSを見つけたりした.

f:id:ywkw1717:20170424125326p:plain

XSS使って何かするんだろうなぁと思ってたんだけど,結局はセッションをローカルでデコードするだけで良かったっぽい.

詳しくは他の方のwriteupへ.

no_mo_flo(Reversing 125)

問題文

Can you go with the flow?

file

no_flo_f51e2f24345e094cd2080b7b690f69fb: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b9de9235c1a8c49096daba03ce36e334cf150c1c, stripped

入力を受け取り,32文字より小さければ

Short input

32文字与えると,

You aint goin with the flow....

正しい入力値を与えて,

Good flow!!

と出力する値を探す.

中で,チェッカーみたいな関数が2つありなにかやって,その戻り値で分岐していることはわかって,objdump, gdb, radare2を使ってこんな感じで↓頑張っていた.

f:id:ywkw1717:20170424145358p:plain

分岐を書き換えてGood flow!!にすることはできたけど,それでは意味なくて,結局タイムアップ.

感想

rev問に関しては,解けなかったけど1日中向き合ってたこともあって,アセンブリを読むことに抵抗がなくなって少しは読めるようになってきたので収穫はあった.

Web担当のid:ktr_0731,忙しくて参加できてないのかなぁと思っていたら忘れていたらしく,今度からremindしまくってやるからなという気持ち.

ASIS CTF Quals 2017 Writeup

開催期間(JST)

04/07 PM21:30 ~ 04/09 PM21:30

結果

・チーム名:wabisabi

・得点:169pt

・順位:132/451

解いた問題

・Welcome!(Trivia 1)

・Piper TV(Misc & Forensics 159)

・CTF Survey(Trivia 9)

(「Welcome!」と「CTF Servey」の2問はボーナス問題)

途中まで解いた問題

・Flour(Reversing 114)

はじめに

SECCON予選以来CTFに出ていなかったので,みんなで出ようとなって参加した.

自分にとってはレベルが高い問題ばかりでほぼ解けなくて惨敗.

全く解けてないぞお前!と未来の自分に残すために解いた問題などをまとめておく.

Writeup

Welcome!(Trivia 1)

ボーナス問題.

問題文

What is the smallest valid flag for ASIS CTF?

よって答えは,ASIS{}

Piper TV(Misc & Forensics 159)

問題文

Recently, a very interesting program has been broadcasted on Piper TV, have you watched it?

$ file PiperTV_e65d6f13bae89c187d2d719ee8bf35cfd9e96387     
$ PiperTV_e65d6f13bae89c187d2d719ee8bf35cfd9e96387: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 262144)

pcapファイルだとわかるので,Wiresharkで見てみる.

f:id:ywkw1717:20170409233653p:plain

後半のほうにある緑色に色付けされたパケットを見ていく.

これをHTTPストリームして,Raw形式で保存する.

f:id:ywkw1717:20170409234013p:plain

$ file target
$ target: MPEG transport stream data

MPEGだとわかるので拡張子を.mpegに変えて再生してみる.

すると,可愛らしい動物の動画が・・・

f:id:ywkw1717:20170409234519p:plain

19秒ほどの動画だが,よく見ていると14秒くらいで上のほうにフラグらしき文字列が一瞬現れるようになっている.

動体視力がくそで何回やってもそこで止まらないのでフレームを抽出することに.

ffmpegを使うといい感じに切り出せるので,フレームレート等を適当に設定して使う.

$ ffmpeg -i target.mpeg -f image2 -vf fps=100 frame%05d.png

約1800枚の画像ができる.少しやりすぎた感があった.

で,フラグが表示されている辺りを見ていくと

f:id:ywkw1717:20170409235638p:plain

CTF Survey(Trivia 9)

ボーナス問題.

どうでしたか~みたいなのをGoogleフォームに記入していくと貰える.

途中まで解いた問題

Flour(Reversing 114)

問題文

Flour is a basic ingredient in all bread making, what about this bread?

テキストファイルが与えられて,中身はよくわからない文字列.

:100000000C9436000C945E000C945E000C945E0020
:100010000C945E000C945E000C945E000C945E00E8
:100020000C945E000C945E000C945E000C945E00D8
:100030000C945E000C945E000C945E000C945E00C8
:100040000C9481020C945E000C944F020C942902D3
:100050000C945E000C945E000C945E000C945E00A8

こんなのが200行以上に渡って書かれている.

調べてみると,Intel Hexフォーマットというやつらしい.

バイナリをテキスト形式で運ぶため?に作られたものらしく,だったらデコードもできるだろってことでツールを探す.

sourceforge.net

このHex2Binというやつを使ってバイナリに戻す.

出てきたバイナリに対してstringsしてみると

Hey! Good job.
This is not real flag: ASIS{is_th1s_f4k3_flag?!!}
Enter real flag: 

という文章は見つけたものの,fake以外は見つからない.

だいぶ時間が経っても誰一人解いている人がいなくて諦めた.

感想

他にはpwn問のStartを友人とやってたけど解けなかった.

rev力とpwn力が無さすぎる.

とても厳しかった.

実家に帰ってきたと思ったら、ノートPC(Win7)をクラックすることになった話

はじめに

帰ってきたばかりで疲れてる時に朝4時まで作業していたからくそ眠い.

無駄に消耗した気がするので,その記録.

経緯

実家のノートPCがネットに接続できない云々で頼まれたから,とりあえずPC開いてみたら管理者のパスワードがわからない

少し酔ってそうな親父に聞いてもわからんと言われたので,クラックするしか無いやん!っていう.

アカウントはもう一個あって,そっち使ってたのかな・・?(そもそもPCを使うことがほぼ無いと言ってた)

やったこと

Ophcrackっていう便利ツールがあるのは知ってて,簡単に言うとWindowsのパスワードを簡単にクラックできるぜっていうツール.

使ってみたこと無いなって思って,ちょうど良い機会だしやってみることにした.

OphcrackはUSBやCDなどからブートできるので,手元にあったUSBに入れてやってみたけど・・・・

SYSLINUX 3.80 2009-05-04 CBIOS Copyright © 1994-2009 H.Peter Anvin et al で止まる

嘘やん・・・って思いつついろいろ調べると,こういうことらしい↓

springpaduser.blogspot.jp

USBブート無理な感じなのか・・・?とか思いつつ,ココらへんで消耗するのも嫌だなぁと思って,自分のPCのWindows(Ubuntuとのデュアルブートにしてある)側に,exeを落としてきて(iso以外にもexeがあった)そこで解析しようと考えた.

そこで必要になるのは,

  1. C:\Windows\System32\config\SYSTEM
  2. C:\Windows\System32\config\SAM

の2つのファイル.

特にSAMファイルには,ユーザがログオンするときに使うパスワードのハッシュが格納されている.

パスワードハッシュにも LM Hash(LAN Manager Hash)NT Hash(Windows NT Hash)があるらしく,文字数や暗号化手法にも違いがある. 詳しくは↓

Windowsのパスワードハッシュについてsec401mentor.wordpress.com

まずは,2つのファイルを取ってくることから始める.

パスワードハッシュファイルの取得

手元にあったKali Linux 64bit LightをUSBブートさせて,ファイルの取得を試みた.

パーティションテーブルを確認

sudo fdisk -l

Windowsパーティションを選択して,ntfsで/mntにマウント

mount -t ntfs /dev/sda3 /mnt

SAMデータベースがある場所へ移動

cd /mnt/Windows/System32/config

syskeyとbootkeyをダンプするツール(コマンド?)であるbkhiveがない・・・

入れるぞ!

curlがない -> apt-get

curl http://http.us.debian.org/debian/pool/main/s/samdump2/samdump2_1.1.1-1.1_amd64.deb > samdump2_1.1.1-1.1_amd64.deb

dpkg -i samdump2_1.1.1-1.1_amd64.deb

libssl1.0.0がない

他のパッケージがない

Kali「おいおいこのパッケージもねえよ」

「」

みたいな感じで,全く入る気配がないので諦めた

Light版だからか,Liveモードだからか?よくわからない.

なので,Ubuntubkhiveともう一つ,パスワードハッシュをダンプするのに必要なツールsamdump2を入れることにした.

さ〜てやるぞ〜と思っていた1時半頃・・・

親父が起きてきて一言.

親父「パスワードがわからんのか?」

自分「おう」

親父「あ〜これだよこれ」(受話器の横に置いてあったメモを取り出す)

親父「〜のパスワードは◯◯◯◯◯で〜のパスワードは◯◯◯◯◯だな」

自分「」

(わからんって言ったやんけ!!!!!!!!!!!!!!!)

パスワードを聞いてしまったのだが,ここまで来て辞めるなんてことは絶対にしたくなかったので続ける・・・

Ubuntu(15.04)へbkhiveとsamdump2を入れる

  1. curl http://http.us.debian.org/debian/pool/main/s/samdump2/samdump2_1.1.1-1.1_i386.deb > samdump2_1.1.1-1.1_i386.deb

  2. dpkg -i samdump2_1.1.1-1.1_i386.deb

  3. curl http://http.us.debian.org/debian/pool/main/b/bkhive/bkhive_1.1.1-1_i386.deb > bkhive_1.1.1-1_i386.deb

  4. dpkg -i bkhive_1.1.1-1_i386.deb

bkhivesamdump2が入った.これらのコマンドを使って,

bkhive(tmp/hoge.txtは出力するファイル)

bkhive SYSTEM tmp/hoge.txt

samdump2(bkhiveで作ったファイルとSAMファイルを渡して,tmp/hash.txtは出力するファイル)

samdump2 SAM tmp/hoge.txt > tmp/hash.txt

すると,

Administrator:xxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:::

このような形式のものが何行かにわたって記載されたhashファイルが完成する.

あとはOphcrackの出番!!

解析結果

f:id:ywkw1717:20170313054027j:plain

AdministratorとGuestはempty(解析可能ではないということ)になり,$というユーザ(なんだこのユーザ・・?)はパスワードが見つからない,つまりある程度は堅牢なパスワードだということ.

逆に,数字のみのものに関しては全て破られている.(数字4桁なので当たり前だろうと思うけど,数字10桁とかでも危なそう)

OphcrackはRainbow Tableを使って解析するため,他のテーブルをダウンロードしてきて使うこともできる.デフォルトで入っている以外のもので,数字のみに特化したものを使うことなども可能.

まとめ

消耗した記録として残すものなので,やってみたい人は他の人の使ってみた系のブログを参考にすることを強くオススメ致します・・・

脆弱なパスワード,ダメ.

おまけ

親父から聞いたパスワードのうち,管理者のほうは間違っていたので,

解析した甲斐があった!!!!!!!!

(聞いたのは昔のもので,親が全く覚える気配がなくてわかりやすいようにしてくれと頼まれて変更したことを忘れてた)



おわり

参考