MinGW+GCCクロスコンパイル環境でOllyDbg Plugin開発

妙に遠回りしている感じもしますが、以下のような理由による物です。

  • メインの開発環境がLinux
  • 極力GUI依存しないTerminal経由で開発したい
  • 今更Borland C++やVisual C++の古いバージョンを入れたくない

こんな環境でやる人はまずいないと思いますが、手順としてCygwin+MinGW+GCCにも流用できるのと
纏めてるところが見あたらなかったのでメモとして記載。
Linux上でクロスコンパイルしているため、各コマンドにi686-pc-mingw32-等のプレフィックス
付いていますが、Cygwin+MinGW+GCCでやる場合はその部分を削ればそのまま動くと思います。


用意する物は以下


gccでは.lib形式でリンクできないので.a形式のファイルを生成する必要があります。
.lib形式から変換する手順もあるようですが、うまくいかなかったので.defファイルから
生成します。対象の関数の名前と呼び出し規約だけ分かっていれば良いので、生成に元のバイナリは必要ありません。

# dlltoolが処理できない不要な宣言削除、 関数名の_を外す
$ sed -e 's/^CODE.*//' -e 's/^EXETYPE.*//' -e 's/^DATA.*//' -e '/^$/d' -e 's/_//' Ollydbg.def > Ollydbg.def.mingw
# gccで扱える形式に変換
$ i686-pc-mingw32-dlltool -U --dllname ollydbg.exe --input-def Ollydbg.def.mingw --output-lib ollydbg.a


PDK付属のPlugin.hもGCCを想定して書かれていないので書き換える必要があります。
コールバック関数の辻褄あわせと、windows.hあたりで定義されている_exportの上書きが必要です。

  // If you like Microsoft compiler, this will force byte alignment and verify
  // that character is set to unsigned.
  #ifdef _MSC_VER
  ...
  #endif
+ // for MinGW gcc
+ #ifdef __GNUC__
+   #pragma pack(1)
+   #ifndef cdecl
+     #define cdecl __cdecl
+   #endif
+   #undef _export
+   #define ODBG_Plugindata      _ODBG_Plugindata
+   #define ODBG_Plugininit      _ODBG_Plugininit
+   #define ODBG_Pluginmainloop  _ODBG_Pluginmainloop
+   #define ODBG_Pluginsaveudd   _ODBG_Pluginsaveudd
+   #define ODBG_Pluginuddrecord _ODBG_Pluginuddrecord
+   #define ODBG_Pluginmenu      _ODBG_Pluginmenu
+   #define ODBG_Pluginaction    _ODBG_Pluginaction
+   #define ODBG_Pluginshortcut  _ODBG_Pluginshortcut
+   #define ODBG_Pluginreset     _ODBG_Pluginreset
+   #define ODBG_Pluginclose     _ODBG_Pluginclose
+   #define ODBG_Plugindestroy   _ODBG_Plugindestroy
+   #define ODBG_Paused          _ODBG_Paused
+   #define ODBG_Pausedex        _ODBG_Pausedex
+   #define ODBG_Plugincmd       _ODBG_Plugincmd
+ #endif


サンプルで付いているBookmark.c等の場合は必要ありませんが、ダイアログなどを使う場合は
リソースも含める必要があります。ResEdit等を使用して.rcファイルとresource.hを生成します。
今回はリソースを必要としないので詳細は割愛。
生成した.rcファイルは以下の通りコンパイルします。
windresのエラーは分かりにくいことこの上ないですが、生成した.rcファイルで起きたエラーは
.rcファイルでのインクルードが足りてない事が原因の場合が多いです。(windows.h等)

# テキストのrc形式からバイナリのres形式に変換
$ i686-pc-mingw32-windres --input-format=rc --output-format=coff -o Foo.res Foo.rc


メインのソースコードコンパイルします。-funsigned-char はOllyDbg Pluginビルドする際の
おまじないみたいな物です。Cygwin環境の場合は -mno-cygwin も付けた方が良いでしょう。
サンプルではDllEntryPointを使用するようになっていますが、MinGW+GCCの場合はこの
シンボルは定義していても参照してくれないので無視されます。
代わりにDllMainで定義すれば問題無く動きます。

- BOOL WINAPI DllEntryPoint(HINSTANCE hi,DWORD reason,LPVOID reserved) {
+ BOOL WINAPI DllMain(HINSTANCE hi,DWORD reason,LPVOID reserved) {
    if (reason==DLL_PROCESS_ATTACH)
      hinst=hi;                          // Mark plugin instance
    return 1;                            // Report success
  };
# ソースコードをオブジェクトファイルにコンパイル
$ i686-pc-mingw32-gcc -g -W -Wall -Wno-unused-parameter -funsigned-char -mwindows -o Bookmark.o Bookmark.c


ここまでで必要な物はすべてコンパイル出来たのでリンクをします。
直接ldでやってもよいですが、取りあえずgcc経由にしています。

# ollydbg.aはlibollydbg.aとかで生成して-L./と-lollydbgオプションでリンクさせてもよいかも
$ i686-pc-mingw32-gcc -g -W -Wall -Wno-unused-parameter -funsigned-char -mwindows Bookmark.o ollydbg.a \
    -o Bookmark.dll -W -shared -Wl,--dll


生成された.dllファイルをOllyDbgのプラグインディレクトリにコピーして起動すれば読み込まれます。
参照しているシンボルの整合性取れてないとか、何か間違っている場合は起動時にエラーが出るので
それを元に修正しましょう。
関数のインポートやエクスポートがどの名前で行われているかはobjdump, PEView,Stud_PE等を
使って確認すると分かりやすいと思います。
概ね引っかかるのは関数名先頭の_有無や関数名末尾の@有無あたりだと思います。(cdecl/stdcallとかその辺の話)


Immunity Debuggerも大体同じですが、途中で仕様で変わったりしてるのでまた今度。