に行ってきたので、どう迷走していたのかを簡単に。
何もしない .exe を流用して Ruby でコンパイラを作ろうとする
今回は各自好きなことをやっていいということなので、まずは参加できなかった
の内容をさらっと演習。
次は第4回の資料にコンパイラのサンプルがあったので、これを拡張して遊んでみようかなと。
気軽に書けるように Ruby あたりでやるとして。コンパイラ部分に集中したいから、PE の構造は自前で作らずに第3回で作った「なにもしない .exe」の必要部分だけ書き換える形にしようかな。
ということで、まず何もしない .exe を生成することにします。まず第3回の最初のスクリプト( https://bitbucket.org/7shi/peben/src/902f634fb33b/03/makeexe1.cpp )を Windows 7 でも動くように
oph->DllCharacteristics = 0x8540;
を
oph->DllCharacteristics = 0x8500;
に書き換え。あとは MinGW 上で
g++ -std=c++0x makeexe1.cpp && a.exe mv output.exe base.exe
としてコンパイル& base.exe としてコピー。
Ruby も pack が使えるからバイナリデータの生成もそれほど面倒ではなくて。C++ で
image[0x200] = 0xb8; *(DWORD*)&image[0x201] = 123; image[0x205] = 0xc3;
と書いていた部分は、
base[0x200, 6] = [0xb8, 123, 0xc3].pack("CIC")
こんな感じで書けちゃいます。(マニュアルページによると、I 等は I! のようにしたほうがいいみたいですが。)
ということで、
- https://gist.github.com/1033982/f26417aecde1baa469598d8aa0cc9fe9f5c21485
- pack が使えることを確認して
- https://gist.github.com/1033982/9b71c76e8be56ed86e4d72650bf3c25bfb4e5a1f
- 簡単に書けるように String#write を定義して
- https://gist.github.com/1033982/c7b24a4bf08fcb537319ac55a7ad647b99190523
- 命令をバイトコードに変換する処理に変えて(命令が小文字になってるのは好みです。あと EAX に値を設定する命令は set eax としました)
- https://gist.github.com/1033982/9c1df472c80491b26fd1346ea9a1fd1805521cce
- exit を書かなくても動作するようにして・・・
というところまでは順調だったんですが。サンプルは次に
- let/set で変数使えるようにする
- disp で変数の内容を出力できるようにする
といった実装を行っているようで。変数扱うためには .data セクションが必要だし、出力するためには DLL が利用できたほうがいい= .idata セクションが必要と。ここまでいくと何もしない .exe をベースにするのは限界がありそうなので、方針を変更して自前で PE を組み立てることに。
ちなみにこのあたりで勉強会は終了。続きは家に帰ってからやりました。
PE の組み立て方を YAML で定義してみる
さっき書いたスクリプトでは
text = compile(source) base.write(0x180, [text.length].pack("I")) base.write(0x200, text)
みたいにコンパイルしたデータ自体のセットと、そのデータ長をヘッダ部分にセットしてやる処理を両方書いていました。このへんはプログラム側でやるのではなくて、データの構造の一部として定義できるとスマートかなと、YAML のアンカーやエイリアスでやってみることに。
参考にしたのはこのあたりの URL。
YAML のエイリアスは別のところで定義したデータをそのまま利用することはできますが、そこで長さを求めるようなことはできないようで。どうしようかと思っていたんですが、YAML::add_domain_type を使えば自前の型を定義できて、その型が出現したときのデータ格納方法をコールバック的に定義できるようなので、この機能を使うことに。
これを使えば、例えば length という構造体をあらかじめ定義しておくことで、
(略) - !ruby/struct:ImageSectionHeader Name: ".text" Misc: !length *code VirtualAddress: 0x1000 SizeOfRawData: 0x200 PointerToRawData: 0x200 Characteristics: 0x60000020 - &code |- let eax 123 exit
みたいな感じですっきり書ける・・・と思ったんですが、どうもうまく行かず。YAML の仕様 をよく見ると
An alias node refers to the most recent preceding node having the same anchor.
とのことで、まあ出現していないアンカーを参照することはできないみたいですね。YAML は高速化のために one-pass で処理できるように仕様が作られてるんだろうということでここについては諦めて、最初の出現位置をアンカーに書き換える(&code と *code を入れ替える)ことで対応することにします。
あと Ruby の構造体は各フィールドが型の情報を持っていないので自前で pack 用の型情報を保持してやる必要があります。C 言語形式のヘッダファイルを利用する方法もなさそうですし、このあたりは型宣言が必要な言語のほうが楽そうでした。
ということで YAML で構造を書けるようにしたスクリプト。
YAML に構造の情報を外出しできているので、スクリプト部分はクラスの拡張や構造体の定義を除くとシンプルな処理にできたかなと。YAML のほうもそれなりにすっきりした構造なので満足です。
本当はここから .data や .idata が扱えるようにしたかったんですけど、余裕がないのでここまでで。
YAML の限界とか
本当は自分自身を含んだ領域のサイズを指定したいようなケース(上記の PointerToRawData とか)も YAML 中で計算できるようにしたかったんですけど、これは循環参照になってうまくいきませんでした。これは領域のサイズだけ先に決定して後で値を入れるような仕組みがないのが原因で・・・こういう部分もうまく処理しようと思うと、YAML で定義せずにプログラムでデータ構造を定義&XPath みたいな形で別のノードを参照して遅延評価するような形になりそうです。
あと .data セクションで定義した変数領域のアドレスを .text セクション側で取得しようと思うと、もっと複雑な仕組みが必要そうですし、次やるなら YAML は使わずに完全に別の実装になりそうです。
この辺もうまく扱ってくれるデータ記述形式があるといいんですけど・・・。