pwnわからん、でも簡単なのは解けたほうが良いな、バッファオーバーフローくらいならわかる気がする。いや細かいところがわからんぞ? 解けない、やーめた、pwnわからん。でも簡単なのは解けたほうが良いな……になるのでpwn初心者メモを残しておきたい。
特に断らなければx86(-64)のELFです。というかそれ以外のアーキテクチャにとりくんでる人は初心者じゃなさそう
バッファオーバーフロー
リターンアドレスの書き換え
図はCSAW CTF 2018のget it? これはgets
を利用しているのでバッファオーバーフローができる。入力はvar_20
に書き込まれて、64bitバイナリなのでリターンアドレスを書き換えるときのpayloadは "A"* (0x20 + 8) + p64(addr)
みたいな感じになる。
もしこのバイナリが32bitだったら"A" * (0x20 + 4) + p32(addr)
ローカル変数の上書き
図はTUCTF 2017のVuln Chat。ここでは最初のscanf
の呼び出しでvar_5
を書き換えたい。書き込み先はvar_19
なのでペイロードは "A" * (0x19 - 0x5) + "%99s"
とかになる
payload正しそうなのに落ちる
やっぱりCSAW CTF 2018のget it? なんだけど、ペイロード正しそうに見えるのにSegfaultしてしまうときは、ジャンプ先のアドレスに +1 して push rbp
を飛ばす。libc 2.27からはこれまで movups
命令が使われていた部分で movaps
命令が使われるようになっているんだけど、この命令が呼ばれたときにスタックが16byteでalignされていないと落ちる。ちゃんとcall
したときは大丈夫なんだけどリターンアドレスを書き換えたりしてjmp
相当の処理を行った場合はpush rbp
があるとずれるのでこれをなくしているんだなぁ。勉強になる
Stack CanaryのBrute Force
スタックの特定の位置にカナリア領域というのがあって、関数の呼び出し時に適当な値が埋め込まれ、関数の終了時にその値が変化していないことを確認する処理が入る。ここで値が変わっていたら __stack_chk_fail
が呼ばれて異常終了することになる。
stack canaryの値はforkでは変わらないので、fork→Stack Overflowでトライアンドエラーを繰り返せる状況ではstack canaryをブルートフォースで特定できる。方針としては、stack canaryを上書きしないギリギリのOverflow量を調べておいて、 もう1バイトだけ(stack canaryの先頭バイトだけ )オーバーフローするようにして、そこの値を0 から 255まで試して落ちなかった値が正常、という感じ。これを繰り返せばstack canaryが特定できる。*1
stack canaryの直前までをオーバーフローしたあとputsなどでcanaryがリークされるのを防ぐために、stack canaryの先頭の1バイトは0になっているので32bitバイナリなら3バイト、64bitバイナリなら7バイトの探索で特定できる。
ちなみにmaster canaryはThread Local Storageにおいてある。知らなかったけどすべての関数でcanaryの値は共通。
libc baseを求める
Buffer Overflowからlibc_baseを求める方法もある。うまくstack canaryが回避できるなら、オーバーフローでmain
の戻りアドレス手前までのメモリを埋めてしまって、そのあと puts
とかをすると puts("AAAAA..." + <__libc_start_main + 231>)
とかになって <__libc_start_main + 231>
から libc baseが求まる (231は適当)
ROPができるなら pop rdi; __libc_start_main@got;
を積んでmain
のputs呼び出すとかでも良い *2
ROP
毎回わからなくなるんだけど、実行したい順番にすなおにアドレスを並べて良い。
payload += p32(0x080bb496) # pop eax payload += p32(0xcafebabe) payload += p32(0x0806f34a) # pop edx payload += p32(0xdeadbeef)
上記の例では eax
に0xcafebabe
を設定したあと、edx
に0xdeadbeef
を設定する。
Format String Bug
%n
でリターンアドレス書き換えられないんですが
毎回めちゃくちゃ勘違いしていて、こんな勘違いするのは僕だけだと思うんですが、 %n
でスタック上の値を書き換えようとしてうまく行かねぇなぁって言ってます。これは %n
を使ったことがあればわかるんですが、以下のように %n
に対応する引数は ポインタ であることが想定されています。だから%n
ではスタック上に現れたポインタに対してのみ書き込みができ、アドレスのわからないローカル変数やリターンアドレスを書き換えることは出来ないというわけです。
printf("%s%n", somestr, &writtenbytes);
一方これを使うと、送るペイロード自体がスタックのどこかにあり、それが何かしらのポインタを指すように見えればそれを書き換えることができるので、アドレスがわかっている値(GOTのエントリとか)は書き換えられるわけです。
メモ
__x86_get_pc_thunk_bx
とか
x86ではmov ebx, eip
みたいなことが出来ないので、スタックに積まれた戻りアドレスからeipを求めるみたいなことをする。そのためにいるのが __x86_get_pc_thunk_bx
とかで、細かい関数名はレジスタ名やアーキテクチャ名によってことなるけど、挙動としては mov ebx, [esp]
みたいなことをしているだけ。そもそもこれを使って何をしているのかはよくわからない。