アイジア

CTF, 情報セキュリティの学んだことメモ

ksnctf 4 Villager A

ksnctfの4問目を解いていきます。ポイントはかなり高め。解くにはアセンブリの知識と、スタックのしくみについての理解が必要です。

ksnctf.sweetduet.info

SSHでサーバにアクセスする

TeraTermと問題文の情報を使ってサーバへリモートでログインします。問題の分類的にはpwn(サーバにアクセスして脆弱性を見つけたり、それを利用してフラグを読みだしたりすること)ですね。 f:id:favoritte15:20181113214343p:plain
サーバにはflag.txtがありますが権限がないため読み出しができません。

q4の解析

f:id:favoritte15:20181113214506p:plain
q4を起動すると名前を聞かれます。その後flagが欲しいかと聞かれるのですがyesと言っても教えてくれません。 gdbによるq4プログラムの解析を行っていきます。以下のコマンドを実行してください。

gdb -q q4<br>disas main

するとかなり長いアセンブリが出力されると思います。1行ずつ解析するのは大変かもしれませんが、call文だけ見れば何をしているのかだいたいわかったりします。以下区切り事にざっくりとした解説を載せていきます。

f:id:favoritte15:20181113220134p:plain
「What's your name?」という文字列をputchar関数で出力しています。

f:id:favoritte15:20181113220602p:plain
fgetsでユーザーからの入力を受付け、printfで「Hi, (ユーザーの入力した名前)」と出力する箇所。


f:id:favoritte15:20181113221039p:plain
putchar関数を呼び出して改行した後、一気にmain+205までジャンプしています。


f:id:favoritte15:20181113221601p:plain
詳しく解説はしませんが、普通に実行するとmain+102にジャンプします。


f:id:favoritte15:20181113221750p:plain
「Do you want the flag?」と永遠に聞いてくる部分です。strcmp関数で入力文字列と「no」を比較して同じならプログラムを終了します。


f:id:favoritte15:20181113222057p:plain
fopenでflag.txtファイルを開いて、その中身をprintf関数で出力させる処理です。しかし直前(0x0804868f <+219>)にジャンプ命令があり、普通に実行するだけではこの処理は行われません。

⑥の処理を実行させられるかどうかが問題を解く鍵になります。

printfの脆弱性を付く

この問題を解決するにはprintf関数の性質とフォーマット指定子%nを使った脆弱性を利用します。 f:id:favoritte15:20181113224838p:plain
printf関数には引数の数に決まりがありません。上の画像のように書式文字列をたくさん指定することでprintf関数は存在しない第2引数以降の値を参照しようとして、スタックの値を読み出してしまうのです。ここでは入力した値aaaaがスタックの上から6番目(0x61616161)に保存されていることが分かりました。

また%nは出力した文字列の総バイト数を第2引数以降で指定されたアドレスに格納します。 例として、

int main(){
     int i;
     printf("Hello%n\n" , &i);
}

上記のプログラムでは「Hello」と5バイト分出力しているのでiには5が入ります。もしここに第2引数を指定しなければ、代わりにスタックの値が書き換えられてしまうことになります。

これらを組み合わせて攻撃を行います。具体的にはputchar関数のアドレスを示す箇所を⑥のfopen関数が始まる部分(0x8048691)にします。

objdump -d q4

f:id:favoritte15:20181113230525p:plain
putchar関数のアドレスは0x80499e0に格納されているのでこのアドレスの中身を0x8048691にすることで攻撃は成功します。

\xe0\x99\x04\x08\xe2\x99\x04\x08%\$hn6

指定子%hnを使うことで下位2バイトに対しての書き込みを行うことができます。指定子を記述する前にすでに8バイト分出力しているので、上記の例ではアドレス0x80499e0の上位2バイトには8が入ります。ここには0x8691を入れたいのでたくさん文字を出力させる必要がありますが、これもフォーマット指定子を使って簡単に実現できます。

\xe0\x99\x04\x08\xe2\x99\x04\x08%34441x%\$hn6

「%34441x」で34441バイト分出力します。34441 + 8 =34449 = 0x8691なので下位ビットには8691が入ります。

\xe0\x99\x04\x08\xe2\x99\x04\x08%34441x%\$hn6%33139%\$hn7

同じようにして上位2ビットを0x804にしていきます。すでに0x8691バイト出力していますが、0x10804にすれば5桁以上が切り捨てられて0x804になります。0x10804-0x8691=0x8173 = 33139なので残り33139バイトを出力します。まとめると以下のようになります。

echo -e "\xe0\x99\x04\x08\xe2\x99\x04\x08%34441x%6\$hn%33139x%7\$hn" | ./q4

まとめ:アセンブリ読もう


CTFをやる人はアセンブリは必須知識なので学習しておくとよいと思います。

Linuxをはじめよう!:アセンブリをやってみよう! 0x100
初めての人はこの辺から。アセンブリのしくみや書き方が基礎から書かれています。

ksnctf 3 Crawling Chaos

ksnctf の3問目を解説していきます。

ksnctf.sweetduet.info

問題文はunya.htmlへのリンクで、アクセスすると送信フォームがあるだけ。  f:id:favoritte15:20181112154947p:plain

XSSかなと思ったけどNoと表示されるだけでした。

Chromeでページのソースを表示します(Ctrl + U)。 f:id:favoritte15:20181112155308p:plain

するとJavascriptタグには意味不明の顔文字たちが。送信ボタンを押すとこのコードが実行されているようです。

難読化されていて読めませんが、console.logによる出力でデコードできないか試してみます。やり方は簡単で、ソースコードを全コピーしてJavaScriptのコードの部分をconsole.log()で囲み、htmlファイルとして保存するだけです。

f:id:favoritte15:20181112171411p:plain

ブラウザで見るとそれらしきコードが復元されています。入力文字列1文字目のアスキーコードを×1、2文字目を×2...していき、それが配列pの各値と等しいかを比べているだけなので逆算のプログラムは極めて簡単なものになります。

  

var p=Array(70,152,195,284,475,612,791,896,810,850,737,1332,1469,1120,1470,832,1785,2196,1520,1480,1449);
var t=""
for(var i=0; i<p.length; i++)
        t += String.fromCharCode(p[i]/(i+1))
console.log(t);

なぜこんなことができるのか?

JavaScriptでは空の配列に演算子を付け加えると、それが数字や文字列として解釈されるようになります。

例:

+ → 0

~ → -1

ここで+演算子は足し算をしているのではなく、オペランド(ここでは空配列の)を数値に変換しています。 空配列を数値変換すると0になります。

(![]+"")→ true

({}+"") → [object Object]

同様に上記の記号を使用することでfalseやtrueなどの文字列を記号だけで表現できます。

これらの記号を組み合わせることで任意のjavascriptを実行できるようになります。詳しくは以下のリンクを参考にしてください。

記号だけのJavaScriptプログラミングの基本原理 - JPerl Advent Calendar 2010 Sym Track

(」・ω・)」うー!(/・ω・)/にゃー!encode - kusano_kの日記

 

ksnctf2 Easy Cipher

ksnctf.sweetduet.info

これからセキュリティの復習もかねてksnctfの問題を解き直していこうと思います。最初はeasycipherから。

 

問題文

EBG KVVV vf n fvzcyr yrggre fhofgvghgvba pvcure gung ercynprf n yrggre jvgu gur yrggre KVVV yrggref nsgre vg va gur nycunorg. EBG KVVV vf na rknzcyr bs gur Pnrfne pvcure, qrirybcrq va napvrag Ebzr. Synt vf SYNTFjmtkOWFNZdjkkNH. Vafreg na haqrefpber vzzrqvngryl nsgre SYNT.

 

謎のアルファベットが羅列されていて、このままでは読むことができません。

 

Writeup

これはシーザー暗号の一つであるROT13と呼ばれるものです。

f:id:favoritte15:20181109174807p:plain

例えば元の文が「A」なら「N」へ、「B」なら「O」という風に13文字分アルファベットをずらしていくという、最も古典的な暗号です。

 

ROT13 | Forensicist

変換できるサイトがあったのでそれを利用します。

ROT XIII is a simple letter substitution cipher that replaces a letter with the letter XIII letters after it in the alphabet. ROT XIII is an example of the Caesar cipher, developed in ancient Rome. Flag is FLAGSwzgxBJSAMqwxxAU. Insert an underscore immediately after FLAG.

 

フラグが出てきました。FLAGの後に「_」をつけるのを忘れずに。

 

Tips

古典暗号にはシーザー暗号のほかにVigenere暗号があり、過去のSECON CTF においてVigenere暗号に関する問題が出題されました。知っておいて損はないと思います。

参考:

古典暗号 - Vigenere暗号とカシスキー・テスト - ₍₍ (ง ˘ω˘ )ว ⁾⁾ < 暗号楽しいです

SECCON 2016 Online CTF: Vigenere (過去問題のWriteup)

 

 

 

crkme09

http://doomo.xyz/crack/crkme09.exe

前↓

aithea.hatenablog.com

前回と同じく名前とパスワードを入力する問題だったので同じ要領で解析しようと思ったけどなにかおかしい。

f:id:favoritte15:20180725110821p:plain

DB文がたくさん羅列していて、ラベル名にも関数が少ない。

 

前にやった問題で似たようなコードがあった。圧縮されているらしいのは確かだが、[右クリック] - [分析(Analysis)] - [コードを分析(Analyse code)]しても変わらない。

 

 

f:id:favoritte15:20180725111944p:plain

↑UPXでアンパックできるみたい。

 

アンパックした後もう一度Ollyで開くと今度はちゃんとしたアセンブリが表示されている。

f:id:favoritte15:20180725112438p:plain

00401255 > 83F8 16 CMP EAX,16

今回のパスは0x16(=22)文字。長い。

 

f:id:favoritte15:20180725112934p:plain

その後2つのサブルーチンに飛んで、

TEST EAX, EAXによる比較が行われる。この時点でEAXが0なら正解処理に飛ぶため2つのサブルーチンを実行し終わった後のEAXは1でなければならない。

 

先に下の関数(00401329)から解析していく。

 

f:id:favoritte15:20180725114817p:plain

パスワードの先頭4文字のアスキーコード - 0x283A4132 = 0x11111111でないと関数が終了してしまう。

0x283A4132 + 0x11111111 = 0x394B5243 = 9KRC

トルエンディアンなので逆にしてCRK9。

 

f:id:favoritte15:20180725115605p:plain

その次の処理。ECXに5文字目のパスワードを入れて0x2D(-)だったらOK。

 

f:id:favoritte15:20180725120524p:plain

それ以降の文字列は「MI5GQ51I-7V9WW2NT」と後ろから比較しているのでこれの逆を当てはめる。

f:id:favoritte15:20180725120832p:plain

 

 「MI5GQ51I-7V9WW2NT」の部分はNameによって変わると思う。

crkme08

http://doomo.xyz/crack/crkme08.exe

※前

aithea.hatenablog.com

 

実行

f:id:favoritte15:20180723102700p:plain

名前とパスワードを聞かれる。もちろんどちらもわからないので今回は2つの情報をリバースエンジニアリングで見つける必要がある。

 

解析

f:id:favoritte15:20180723103302p:plain

「GetDlgItemTextA」にBPを仕掛けて、適当に名前とパスワードを入力して実行する。

上図のアセンブリから分かることは

・正解パスワードは8文字であること

・入力したNameを引数として関数004012A8を呼び出していること

 その返り値をEDXに格納していること

・パスワードを引数として関数004012D0を呼び出していること

・EAXとEDXが等しいなら正解処理(0040128F)へ進むこと

 

004012A8の解析

f:id:favoritte15:20180723104129p:plain

①EAXに入力した1文字のASCIIコードを足す

②EAXを2乗する

③①~②を入力文字分やる

だいたいこんな感じ。

EAXの値は入力するNameの値によってさまざま。

004012D0の解析

f:id:favoritte15:20180723130401p:plain

 

見た感じ、

・パスワードが16進数で使う文字(0~9とA~F)

 その数値がそのままEBXに代入される

・それ以外の文字が入ってたらEBXを0xFFFFFFFFにする。

最終的にEBXはEAXに代入されて戻り値になる。

 

このEAXが関数004012A8で生成された値と同じになればよい。NameがAの時に生成される値は

f:id:favoritte15:20180723131735p:plain

0x183FB4ACなので、これが正解のパスワード。

 

crkme07

http://doomo.xyz/crack/crkme07.exe

※前→crkme06 - アイジア

 

実行

f:id:favoritte15:20180722153553p:plain

crkme07.exeを実行すると4つのテキストボックスが出てきて、シリアル番号的なものをを要求される。

 

解析

OllyDbgでcrkme07.exeを開く。アセンブリが表示されているウィンドウで右クリック->検索->ラベル名(現在のモジュール)をクリックする。

f:id:favoritte15:20180722154130p:plain

入力したシリアルが正しいかどうかを判定するには必ず入力テキストの情報を取得しなければならないので、そのあたりの処理に着目しよう。

GetWindowTextAが参照されているところでBPをセットして実行してみる。

 

1文字入力した瞬間にBPにぶつかる。多分半角数字以外のものが入力されていないかのチェックなのでいったんBPを外して適当なシリアルを全部入力する。

f:id:favoritte15:20180722155145p:plain

入力し終えたらもう一度GetWindowTextAのところにBPをセットして「登録」を押下。

.

.

.

ブレークポイントに引っかからない。

 

f:id:favoritte15:20180722154130p:plain

↑もう一度ラベル名を見てみる。テキストを取得する関数にはGetWindowTextのほかに、GetDlgItemIntが見つかった。

GetDlgItemInt 関数

テキストを整数値に変換してそれを戻り値とする。今回の入力は数値だけなのでこれが怪しい。ここにBPを設置してもう一度実行してみる。

f:id:favoritte15:20180722160400p:plain

↑今度はちゃんと引っかかった。

 

f:id:favoritte15:20180722161030p:plain

↑まずこのへんの処理から。ループ構造になっている。

GetDlgItem関数で戻ってきた値(入力した整数値)をEBXが示すアドレス(0x40311C~)に格納する。そしてEBXを+2、ECXとEDXを+1してにECX > 4ならもう一度ループする。

ECXを+1するのはループカウンタ(Cのfor文の変数iみたいな)だから。

 

EDXはGetDlgItemInt関数を呼び出すときの引数として使われている。

004012BC . 52 PUSH EDX ; |ControlID

ControllIDは整数値に変換したいテキストの識別子なのでこれを+1することで次のテキストを取得できるという仕組み。

 

EBXを足すのはテキストを格納するアドレスをずらすため。

004012C5 . 66:8903 MOV WORD PTR DS:[EBX],AX

 ↑ここではワード長(32ビット)でデータを格納するので+2している。

 

 

f:id:favoritte15:20180722162226p:plain

無事ループを抜けるとEBXが指定したアドレスに整数1234(=0x04D2)が4つ分入ってる。

f:id:favoritte15:20180722164842p:plain

0x40311Dに格納されている数値をESIに代入。一つ目のテキストに入力した整数値だ。

その数値と0x66666667を乗算する。

IMUL関数はEAXの乗算の結果をEAXに格納するが、おさまりきらないときは上位ビットがEDXに格納される。

 

さらにEDXを2ビット分算術右ビットシフト(=÷4する)。

さらに0x1F(=31)ビットシフトしたものを加えた結果が

EDXが0x3E7ならOK。

0x66666667と掛け算して上位9~12ビットが0x03E7ならなんでもいいらしいので逆算してみる(計算過程は略)。1つめのテキストボックスには9990を指定すればいいことが分かった。

 

f:id:favoritte15:20180722165439p:plain

0x40311F(2番目のテキスト)に入力する値は0x270F(=9999)以下でなければならないことが分かる。(3番目のテキストも同様。)

 

f:id:favoritte15:20180722172321p:plain

このへんはよくわからない。読むのが嫌になるくらい長い。CMP命令も一番下に行くまでなくて、入力した数値から別の数値を作ってる感じ。

この問題を作った方も解説できないとのこと。

なので、こういうときはCMP命令周りを見てみるといいらしい。

f:id:favoritte15:20180722172823p:plain

 004013B0 . 66:3BE8 CMP BP,AX

今、BPには4番目のテキストボックスの数値、AXには謎のメカニズムで生成された謎の数値が入っている(ここでは0x1732)。これが一致すればいいので4番目のテキストに0x1732(=5938)を入力する。

 

f:id:favoritte15:20180722173210p:plain

登録済と表示された。今回もいろいろ正解パターンがある。

crkme06

前→

crkme05 - アイジア

f:id:favoritte15:20180720150042p:plain

前と比べて正解処理にたどり着くまでの道のりがけっこう長くなっている。

・正解文字列は10文字(004011FD . 83F8 0A CMP EAX,0A)

 

0040120C . 0FBF06 MOVSX EAX,WORD PTR DS:[ESI]

 EAXにESIの先頭2文字(16バイト分)を格納する。ESIには入力データへのポインタが格納されているのでEAXには入力文字列の先頭2文字が格納される。

 

00401214 . 66:3D 454F CMP AX,4F45

このEAXの値は0x454F(= EO)でなければならない。

(EAXの値は0x4F45だけど、リトルエンディアンなので格納するときに順序が逆になる)

 

f:id:favoritte15:20180720150955p:plain

・3文字目以降が数字かを判定して、EDXに7文字目以降の数字の合計を格納。

その後EAXにEDXを転送してEDXを0にする。

 

f:id:favoritte15:20180720151817p:plain

0040124A . F7F1 DIV ECX

この命令、単にEAXをECXで除算するだけでなく、商をEAXに、余りをEDXに格納する。その後CMP EDX, 0 となってるのでEAXがちょうど10で割り切れるようにしなければならない。

EAXには8文字目~10文字目の数字の合計が格納されているのでこれらの和が10の倍数になればいいはず。

f:id:favoritte15:20180720152331p:plain

f:id:favoritte15:20180720152356p:plain

f:id:favoritte15:20180720152419p:plain

・最初がEO

・3~7文字目は数字

・8~10文字目は数字で合計が10の倍数

なら何でもいいみたい。