このprintfデバッグからの卒業♪

こんにちは!近頃咳と痰と鼻水と鼻づまりがすごく多い、金曜日の天使ことhasegawayosukeです。
ActivePerlで任意のx86バイナリを実行しているようなときには、バイナリ部分のデバッグに結構手間取るときがあります。あまり準備に手間をかけずに気合いだけでad-hokに修正したいときは

    my $x86 = "\x8b\x44\x24\x08......";
    print unpack( 'H2' x length( $x86 ), $x86 );

のように、実行するバイナリコードを目視で確認すればよいのですが、jmp命令が入るとアドレスの計算などがけっこう面倒になります。そんなときはやはりデバッガを使ってデバッグしたくなるのが人情なので、ActivePerlとデバッガをうまく連携させる方法を考えました。
Visual Studioがインストールされた環境では、「C:\Windows\System32\vsjitdebugger.exe -p プロセスID 」とすることで、任意のプロセスをデバッガにアタッチすることができます。ところが、Perlのsystem関数を使ってそのままvsjitdebugger.exeを呼び出したのでは、デバッガ終了までperl側の処理がブロックされてしまい、デバッグすることができません。そこで、perldoc のときと同様に start コマンド経由で vsjitdebugger.exe を呼び出すことにします。

system("start", "vsjitdebugger.exe", "-p", "$$" );

こうすることで、startコマンドは vsjitdebugger.exe を起動すると速やかに終了するので、perl側はデバッガと並行してコードの実行を進めることができます。


ただし、これではデバッガが起動しデバッグ対象プロセス(ActivePerl)にアタッチしてデバッグの準備ができるのを待つことなく、Perl側はどんどんコードの実行を進めてしまいますので、今度はデバッガの準備ができるまでPerl側ではコードの実行を停止させます。これには、Windows APIIsDebuggerPresent関数を使います。

while( $IsDebuggerPresent->Call() == 0 ){
    sleep( 1 );
};

これで、デバッガがきちんとアタッチしていない間は有意なコードの実行を停止させることができます。


つぎに、x86バイナリコードにブレークポイントを置くわけですが、これは単純に int 3 を実行することでデバッガにブレークを通知できます。なお、int 3 は 0xcc です。いかにもbkという感じで素敵ですね。

my $x86 = "\xCC.....";

この0xCCを実行するとデバッガ側にはブレークポイントとして通知され、以降のコードを自由にデバッガ上で動かすことができます。


というわけで、サンプルプログラムの全体を以下に示します。

#!/usr/bin/perl
# Call Perl code from x86 code using Win32::API::Callback 
use strict;
use warnings;
use Win32::API;
use Win32::API::Callback;

#include <windows.h>
my $EnumWindows = Win32::API->new( "user32", "EnumWindows", "NK", "N" );
my $IsDebuggerPresent = Win32::API->new( "kernel32", "IsDebuggerPresent", "", "N" );

my $callback = Win32::API::Callback->new( 
    sub {
        my $value = shift;
        print "Hello, Perl World! arg = $value\n";
        return 0;
    },
    "N", "N",
);

my $x86 = ""
.   "\xcc"                      # int 3
.   "\x8b\x44\x24\x08"          # mov eax, dword ptr[ callback ]
.   "\x68\x4E\x61\xBC\x00"      # push 12345678
.   "\xff\xd0"                  # call eax
.   "\x33\xc0"                  # xor eax, eax
.   "\xc2\x08\x00"              # ret
;

print "pid=$$\n";
system("start", "vsjitdebugger.exe", "-p", "$$" );
while( $IsDebuggerPresent->Call() == 0 ){
    sleep( 1 );
};
$EnumWindows->Call( unpack( 'L', pack( 'P', $x86 ) ), $callback ); 

これをデバッガで実行した様子が次の図です。

0xCCに引き続き、0x8B 0x44 0x24 0x08 とPerl上で用意したバイナリコードが次行に見えているのがわかると思います。これでどんなプログラムでも怖くないですね!
さぁ皆さんもActivePerl使って素敵なWinアプリ開発ライフを過ごしてください。Enjoy!