babygame

TCPサーバの接続情報と、ELFファイル babygame が与えられた。

babygameGhidraで逆コンパイルすると、以下の処理をしているのが読み取れた。

  1. NAMEに0x20バイトまでread関数で読み込む。
  2. RANDBUF"/dev/urandom"のアドレスを設定する。
  3. ユーザに整数を入力させ、入力された整数に応じて以下の処理をする。
    • 1 を読み込んだ場合 (set_username())
      1. NAMEに、strlen(NAME)バイトをfread関数で読み込む。
    • 2 を読み込んだ場合 (print_username())
      1. NAMEのデータをputs関数で出力する。
    • 0x539 (1337) を読み込んだ場合 (game())
      1. RANDBUFが指すファイル名のファイルから4バイトの整数を読み込む。
      2. ユーザに整数を入力させる。
      3. ファイルから読み込んだ整数と、ユーザが入力した整数が一致した場合、system("/bin/sh");を実行する。

さらに、RANDBUFNAMEの0x20バイト先に配置されていた。
従って、最初にナル文字を含まない0x20バイトのデータを入力すると、NAMEに格納されている「文字列」がRANDBUFに繋がるので、
print_username()RANDBUFに格納されているアドレスを出力させたり、 set_username()RANDBUFに格納されているアドレスを変更したりできるようになる。

RANDBUFには最初は"/dev/urandom"のアドレスが格納されている。
これを"/bin/sh"のアドレスに書き換えると、game()がファイルから読み込む値が固定になると考えられる。
strings --radix=x babygame コマンドで調べると、位置2024に文字列/dev/urandomがあり、 位置20a3に文字列/bin/shがあることがわかった。

サーバ上のファイル/bin/shの内容はわからないが、仮にELFファイルであるとすれば、 先頭の4バイトはbabygameと同じ7f 45 4c 46であると予想できる。
これは、10進数にすると1179403647である。

この性質を用い、以下の手順でシェルを起動できた。

  1. Wiresharkでサーバとの通信内容を見られるようにする。
  2. サーバにTera Termで接続する。送信改行コードをLFに設定しておく。(これはシェルで利用する)
  3. flag{tomorinao_wa_ayaneru_rasii}をコピペして送信する。
    read関数に読み込ませるため一気に送信すること、改行を入れてはいけないことに注意する。
  4. 2を送信する。改行は入れない。
  5. Wiresharkでサーバの応答を見ると、最初に送信した文字列とputs関数によって追加される改行の間にアドレスが入っている部分があるはずである。例えば、この部分である。
    screenshot of Wireshark
  6. 最初に送信した文字列の後に、アドレスの最初の0x24を0xa3に置き換えたデータを書き込んだファイルを用意する。今回の例の場合、以下のファイルである。
    set_username_data.bin
  7. 1を送信する。改行は入れない。
  8. 用意したファイルをドラッグ&ドロップし、「ファイル送信」で送信する。
  9. 1337を送信する。全体をコピペで一気に送信し、改行は入れない。
  10. 1179403647を送信する。全体をコピペで一気に送信し、改行は入れない。

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

Information to connect to a TCP server and a ELF file babygame were given.

Decompiling babygame via Ghidra, I found it doing this process:

  1. Read at most 0x20 bytes via the function read to NAME.
  2. Store an address of "/dev/urandom" to RANDBUF.
  3. Have the user enter an integer and do following things based on the integer.
    • When it reads 1 (set_username())
      1. Read strlen(NAME) bytes via the function fread to NAME.
    • When it reads 2 (print_username())
      1. Output the data in NAME via the function puts.
    • When it reads 0x539 (1337) (game())
      1. Read an 4-byte integer from the file with the name pointed at by RANDBUF.
      2. Have the user enter an integer.
      3. Execute system("/bin/sh"); if the integer from the file and the integer the user entered are the same.

I also found that RANDBUF was placed 0x20 bytes ahead from NAME.
Therefore, when we put 0x20-byte data that doesn't contain any null characters at the first step, the "string" in NAME is connected to RANDBUF.
It enables to have print_username() print the address stored in RANDBUF and to have set_username() modify the address stored in RANDBUF.

After the initialization, the address of "/dev/urandom" is stored to RANDBUF.
Putting the address of "/bin/sh" there will make the value that game() reads from the file be fixed.
Using the command strings --radix=x babygame, I found the string /dev/urandom is at 2024 and the string /bin/sh is at 20a3.

The contents of the file /bin/sh on the server is unknown, but assuming that it is a ELF file, the first 4 bytes should be 7f 45 4c 46, which is the same as the file babygame.
This value is 1179403647 in decimal.

Using these facts, I succeeded to launch the shell by these steps:

  1. Prepare Wireshark to watch the communication with the server.
  2. Connect to the server via Tera Term. Set the newline code to send to LF (for using the shell).
  3. Send flag{tomorinao_wa_ayaneru_rasii} by copy-and-pasting.
    Note that this has to be sent at once for feeding to the function read, and that you shouldn't put a newline character after that.
  4. Send 2. Don't send newline characters.
  5. Seeing the response from the server via Wireshark, you will find an address between the string firstly sent and the newline added by the function puts. I mean this part, for example:
    screenshot of Wireshark
  6. Create a file which has the string firstly sent and the address with the first 0x24 replaced with 0xa3. This is the file in this case:
    set_username_data.bin
  7. Send 1. Don't send newline characters.
  8. Drag & drop the file prepared and send that via "Send file".
  9. Send 1337. Send the whole part at once by copy-and-pasting, without putting newline characters.
  10. Send 1179403647. Send the whole part at once by copy-and-pasting, without putting newline characters.

Executing a command ls -al on the shell, I found that there is a file flag.txt.
I obtained the flag by executing a command cat flag.txt.

DUCTF{whats_in_a_name?_5aacfc58}

DownUnderCTF 2021