んで ASCII 縛りの説明とか
ASCII 縛りというか C 文字列縛りというか。
いやこれ結構大変なんですよ。とりあえず使える命令がわからんと話にならないのでこういう表を作る。
http://shinh.skr.jp/binary/x86_ops.txt
これはバイナリツールと化している irb でサクサク作りました。以下とか参考に。
http://d.hatena.ne.jp/shinichiro_h/20061117#p3
あとこんなんでアセンブラかました結果がどういう機械語になるかを調べるものも作っておきました。 .irbrc に入ってます。
def asm(b) tmp = Tempfile.new('irb_asm', '/tmp') tmp.print(b) tmp.close system("nasm #{tmp.path} -o /tmp/irb_asm2") print `od -t x1z -A x /tmp/irb_asm2` nil end
で、この表を見ていると色々気付きます。
まず絶対必須な int 0x80 (0xcd 0x80) も、 ret (0xc3) もありません。いきなり死ねる。そこで自己書き換えです…でも、どうやって?
まず call がありません。よって Binary Hacks #79 的な call pop での EIP 取得術はできません。
えーとどうするねん…と思ったのですが、これは以前考えたことがある問題でして、 return する場所は普通にスタックにあって、 return アドレスの少し後ろには call 命令、つまり main のアドレスがあるに違いないっ…と思って探したのですが、なんかありませんでした。
んで呼び出し元付近を適当に逆アセしてみると、結局、 call *8(%ebp) とかで main に飛んでくるみたいで、これは持ってる環境だとだいたいそんな感じでした。んじゃ main に限っては、 movl 8(%ebp), %esi で main のアドレスを拾って来れるわけです。
さて今度は、 main のアドレスから、 int 0x80 を置きたい位置にポインタを進めないといけない。しかし add は全て封印されています。繰り返し処理はたぶんできません(条件 jmp はあるんだけど、負値を指定しないと後ろに戻れない)。つーわけで適当にデカい負値を sub で作って、その値を sub することによって負数の引き算は足し算ですよーとかいう原理でポインタを進めます。
まぁそんな感じで自己書き換えはできる。あとは気合いとかで。
- 実はレジスタ間の演算が一切できないのもつらいところ。必ずメモリvsレジスタで計算することになる。
- さらに、 sub [EAX], ESI はいいけど、 sub [EAX], EAX はダメとか、レジスタに微妙な拘束条件がつきまくる。
- でも移動は push/pop でできる。つか push pop はありがたい。
- もちろん inc/dec も便利。
- 即値 0 が使えたらラクなのになあという。最初に push 0x20; pop EAX; and AL, 0x40 とかで作って、適当に何度か push して使っています。
- 小さい値使えないせいで、 [EBP+8] をするためにわざわざ 32 を引いてから [EBP+40] で取得的な苦労を…
というようなことして作られたソースコードは以下に置いときます。 \n を実行時に作ろうとしてうまくいかんかった痕跡が残っています。