ふるつき

v(*'='*)v かに

pwn初心者メモ

pwnわからん、でも簡単なのは解けたほうが良いな、バッファオーバーフローくらいならわかる気がする。いや細かいところがわからんぞ? 解けない、やーめた、pwnわからん。でも簡単なのは解けたほうが良いな……になるのでpwn初心者メモを残しておきたい。

特に断らなければx86(-64)のELFです。というかそれ以外のアーキテクチャは初心者じゃなさそう

バッファオーバーフロー

リターンアドレスの書き換え

f:id:Furutsuki:20200112190845p:plain

図はCSAW CTF 2018のget it? これはgetsを利用しているのでバッファオーバーフローができる。入力はvar_20に書き込まれて、64bitバイナリなのでリターンアドレスを書き換えるときのpayloadは "A"* (0x20 + 8) + p64(addr) みたいな感じになる。

もしこのバイナリが32bitだったら"A" * (0x20 + 4) + p32(addr)

ローカル変数の上書き

f:id:Furutsuki:20200112191519p:plain

図は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)

上記の例では eax0xcafebabeを設定したあと、edx0xdeadbeef を設定する。

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] みたいなことをしているだけ。そもそもこれを使って何をしているのかはよくわからない。