money-printer (150)

TCPサーバの接続情報と、ファイル Dockerfile および money-printer が与えられた。

file コマンドを使うと、ファイル money-printer は64bitのELFファイルらしいことわかった。
Ghidra で逆コンパイルすると、以下の main 関数が得られたが、役立ちそうな処理は見当たらなかった。

Information for connecting to a TCP server, and files Dockerfile and money-printer were given.

Using file command, I found that the file money-printer should be a 64-bit ELF file.
I decompiled the file using Ghidra. It emitted this function main, but I found nothing look useful from here.

ghidra_main.txt

ファイル money-printerTDM-GCCのobjdumpで逆アセンブルすると、
fgets で読み込んだ内容を printf の第一引数に渡し、その後 puts および exit を呼び出している以下の部分が見つかった。

I disassembled the file money-printer using objdump from TDM-GCC.
Then, I found this part that passes what is read via fgets to the 1st argument of printf, and calls functions puts and exit.

4009dc: 48 8d 45 90 lea -0x70(%rbp),%rax 4009e0: be 64 00 00 00 mov $0x64,%esi 4009e5: 48 89 c7 mov %rax,%rdi 4009e8: e8 13 fd ff ff callq 400700 <fgets@plt> 4009ed: 48 8d 3d 0f 02 00 00 lea 0x20f(%rip),%rdi # 400c03 <_IO_stdin_used+0x133> 4009f4: b8 00 00 00 00 mov $0x0,%eax 4009f9: e8 f2 fc ff ff callq 4006f0 <printf@plt> 4009fe: 48 8d 45 90 lea -0x70(%rbp),%rax 400a02: 48 89 c7 mov %rax,%rdi 400a05: b8 00 00 00 00 mov $0x0,%eax 400a0a: e8 e1 fc ff ff callq 4006f0 <printf@plt> 400a0f: 48 8d 3d fc 01 00 00 lea 0x1fc(%rip),%rdi # 400c12 <_IO_stdin_used+0x142> 400a16: e8 a5 fc ff ff callq 4006c0 <puts@plt> 400a1b: bf 00 00 00 00 mov $0x0,%edi 400a20: e8 1b fd ff ff callq 400740 <exit@plt>

main 関数の冒頭は、以下のようになっていた。

Also this is the first part of the function main.

0000000000400837 <main>: 400837: 55 push %rbp 400838: 48 89 e5 mov %rsp,%rbp 40083b: 48 81 ec c0 00 00 00 sub $0xc0,%rsp 400842: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 400849: 00 00 40084b: 48 89 45 f8 mov %rax,-0x8(%rbp) 40084f: 31 c0 xor %eax,%eax

%rsp%rbp - 0xc0fgets で文字列を読み込む位置は %rbp - 0x70 であり、その差は 80 バイトである。
レジスタに置かれる引数6個と合わせて、読み込まれる文字列の前には8バイトの引数が16個あるとみなせる。

さらに、逆アセンブル結果の以下の部分は、読み込んだ数値がある値以下かをチェックしているようである。

The value of %rsp is %rbp - 0xc0, and where to read a string using fgets is %rbp - 0x70. Their difference is 80 bytes.
Therefore, adding 6 arguments on the registers, it can be seen as there are 16 8-byte arguments before the string to be read.

Also, this part of the disassembled program looked like checking if the number read is not greater than some value.

40092a: 48 8d 85 4c ff ff ff lea -0xb4(%rbp),%rax 400931: 48 89 c6 mov %rax,%rsi 400934: 48 8d 3d f7 01 00 00 lea 0x1f7(%rip),%rdi # 400b32 <_IO_stdin_used+0x62> 40093b: b8 00 00 00 00 mov $0x0,%eax 400940: e8 eb fd ff ff callq 400730 <__isoc99_scanf@plt> 400945: e8 c6 fd ff ff callq 400710 <getchar@plt> 40094a: 8b 85 4c ff ff ff mov -0xb4(%rbp),%eax 400950: 83 f8 63 cmp $0x63,%eax 400953: 7f 4f jg 4009a4 <main+0x16d>

指定のサーバに Tera Term で接続すると、以下のメッセージが出力された。

I tried connecting to the server with Tera Term. Then, this message appeared.

I have 100 dollars, how many of them do you want?

-1 を入力すると、以下のメッセージが出力された。

Entering -1, this message appeared.

you can have -1 dollars! wow you've printed money out of thin air, you have 4294967295!!! Is there anything you would like to say to the audience?

そこで、以下の文字列を入力してみた。

Then, I entered this string.

0000_%15$p_%16$p_%17$p_%18$p

すると、以下のメッセージが出力され、入力した文字列の最初が printf 関数で16番目のデータとして用いられるらしいことがわかった。

As a result, this message appeared. This is suggesting that the first part of the string entered is used as the 16th data in the function printf.

wow you said: 0000_0x79336e30_0x3531255f30303030_0x70243631255f7024_0x255f70243731255f that's truly fascinating!

これを利用して、まずはいくつかの標準ライブラリ関数のアドレスを調べることにした。

.plt セクションの逆アセンブル結果より、printf 関数のアドレスが 0x601030 に、fopen 関数のアドレスが 0x601048 に格納されることがわかる。
これらのアドレスを利用した以下のデータを Tera Term の「ファイル送信」で送信し、通信内容を Wireshark で調べた。

Using this, I firstly obtained the addresses of some standard library functions.

According to the disassembled code of the .plt section, the address of the function printf will be stored at 0x601030 and one of the function fopen will be stored at 0x601048.
I sent this data, using these addresses, via "Send File" on Tera Term, and watched the communication using Wireshark.

payload1.bin

その結果、(例えば) printf 関数のアドレスは 0x7f2aa39ace40fopen 関数のアドレスは 0x7f2aa39c6de0 であった。
これらのアドレスを libc-database に入力して検索すると、2件ヒットし、用いる関数のアドレスは同じだった。

これにより printf 関数と system 関数のアドレスの差が求まるので、以下の手順によりシェルを起動することができる。

  1. printf のアドレスを取得しつつ、繰り返し入力できるように exit として実行する関数のアドレスを main 関数のアドレスにする。
  2. printf として実行する関数のアドレスを system 関数のアドレスにする。
  3. /bin/sh を入力し、system("/bin/sh"); を実行させる。

これを行う以下のプログラムを作成した。
printf のアドレス全部を書き換えようとすると入力文字数制限に引っかかったので、下位3バイトだけを書き換えるようにした。

As a result, I found that (for example) the address of the function printf is 0x7f2aa39ace40 and one of the function fopen is 0x7f2aa39c6de0.
I queried libc-database with these addresses. There were two results, and their addresses of functons to use were the same.

We can use this to calculate the difference between the address of the functions printf and system, so the shell can be launched by these steps.

  1. Obtain the address of printf, and at the same time set the address to be executed as exit to the address of the function main to have it accept inputs repeatedly.
  2. Set the address to be executed as printf to the address of the function system.
  3. Enter /bin/sh to have it execute system("/bin/sh");.

I wrote this program to perform these steps.
I failed to have it rewrite the whole address for printf due to the length limit for inputs, so I had it rewrite only the lower 3 bytes.

solve.pl

このプログラムを実行すると、シェルを起動できた。
シェルで ls -al コマンドを実行すると、ファイル flag.txt があることがわかった。
コマンド cat flag.txt を実行すると、flagが得られた。

I succeeded to launch the shell by running this program.
Executing a command ls -al on the shell revealed that a file flag.txt exists.
I obtained the flag by executing a command cat flag.txt.

sdctf{d4mn_y0u_f0unD_4_Cr4zY_4M0uN7_0f_M0n3y}

SDCTF 2023