SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

「SEXYHOOK」 とある関数の接合部

SEXYHOOKの実装部
とある関数の接合部(2)

関数、クラスメソッド、APIをフックする方法

  • X ポスト
  • このエントリーをはてなブックマークに追加

ダウンロード sexyhook0.8 (80.0 KB)

関数呼び出し2:直接関数が呼ばれる場合

 直接関数本体が呼び出されるケースもあります。以下の例は飛んだ先がいきなり関数の内部です。

95:       time_t t = time(NULL);
0040145D   push        0
0040145F   call        time (00402880)       ;とんだ先がいきなり関数の中身
00401464   add         esp,4
00401467   mov         dword ptr [t],eax

--- time.c  --------------------------------------------------------------------
time:
00402880   push        ebp
00402881   mov         ebp,esp
00402883   sub         esp,0D8h
00402889   lea         eax,[loct]
0040288C   push        eax
0040288D   call        dword ptr [__imp__GetLocalTime@4 (0042a290)]
00402893   lea         ecx,[gmt]

関数呼び出し3:vcall仮想関数

 仮想関数の呼び出しはさらに特殊です。

class Parent
{
public:
        virtual int f()
        {
                return 1;
        }
        virtual int g() =0;
};

class Child : public Parent
{
public:
        virtual int f()
        {
                return 2;
        }
        virtual int g()
        {
                return 3;
        }
};

Child child;
child.f();

 このとき、Child::fが呼ばれるまでには複雑な工程をたどります。Microsoft Visual C++ではこのようになります。

lea ecx,[child] //クラスのインスタンス(thisポインタ)をecxに積む
call Child::f   //Child::fの実態は関数ではなく、クラスのインスタンスからのジャンプテーブル

 このようにして取得したアドレスはChild::fではなく、vcallのアドレスになります。

void* p = SEXYHOOK_DARKCAST( &Child::f );

 vcallはこのようなアセンブリコードです。

vcall:
00402BA0   mov         eax,dword ptr [ecx]
00402BA2   jmp         dword ptr [eax]

マシン語
8B 01 FF 20

 or (2番目virtualメソッドの場合)

004025D0   mov         eax,dword ptr [ecx]
004025D2   jmp         dword ptr [eax+4]

マシン語
8B 01 FF 60 04
         ↑4バイト目が 60 の場合、次の1バイトが +4 バイト等をしている数字

 そうやって、jmpした先がChild::fの関数の実体です。

 一度vcallを挟んでいる点と、クラスのインスタンスから相対アドレスを求めている点が大きく異なります。これを追跡するコードは以下のようになりました。

//仮想関数の vcallだった場合...
if (
                *((unsigned char*)overraideFunctionAddr+0) == 0x8B
        &&      *((unsigned char*)overraideFunctionAddr+1) == 0x01
        &&      *((unsigned char*)overraideFunctionAddr+2) == 0xFF
        )
{
        int plusAddress = 0;
        if (*((unsigned char*)overraideFunctionAddr+3) == 0x20)
        {
                //[[this] + 0] にジャンプ
                plusAddress = 0;
        }
        else if (*((unsigned char*)overraideFunctionAddr+3) == 0x60)
        {
                //[[this] + ?] にジャンプ
                plusAddress = (int) *((unsigned char*)overraideFunctionAddr+4); //4バイト目の1バイト分が加算する値
        }
        else
        {
                //[[this] + ?] にジャンプを計算出来ませんでした...
                SEXYHOOK_BREAKPOINT;
        }
        //C言語のおせっかいで、ポインタは型分プラスしてしまうので、ポインタのサイズで割っとく.
        plusAddress = plusAddress / sizeof(void*);

        //このような関数に一時的に飛ばされている場合...
        //                      vcall:
        //                      00402BA0   mov         eax,dword ptr [ecx]
        //                      00402BA2   jmp         dword ptr [eax]
        //8B 01 FF 20
        //
        // or
        //
        //004025D0   mov         eax,dword ptr [ecx]
        //004025D2   jmp         dword ptr [eax+4]
        //8B 01 FF 60 04
        if ( inVCallThisPointer == NULL )
        {
                //vcallのフックには、 thisポインタが必要です。
                //SEXYHOOK_CLASS_END_VCALL(thisClass) を利用してください。
                SEXYHOOK_BREAKPOINT;
        }

        /*
                //こういう演算をしたい  inVCallThisPointer = &this;
        _asm
        {
                mov ecx,inVCallThisPointer;
                mov ecx,[ecx];
                mov ecx,[ecx];
                mov overraideFunctionAddr,ecx;
        }

                         or
        _asm
        {
                mov ecx,inVCallThisPointer;
                mov ecx,[ecx];
                mov ecx,[ecx+4];                        //+? は定義された関数分 virtualの数だけ増えるよ
                mov overraideFunctionAddr,ecx;
        }
        */
        //多分こんな感じ,,,泣けてくるキャストだ.
        overraideFunctionAddr = (uintptr_t) *((void**)*((void***)inVCallThisPointer) + plusAddress);
        //そこにあるのは  関数の本体 jmp への命令のはず.
        if (*((unsigned char*)overraideFunctionAddr+0) == 0xe9)
        {
                //ついでなので関数の中を書き換えるため、関数の実体へのアドレスを求める.
                uintptr_t jmpaddress = *((uintptr_t*)((unsigned char*)overraideFunctionAddr+1));
                overraideFunctionAddr = (((uintptr_t)overraideFunctionAddr) + jmpaddress) + 5;  //+5は e9 00 00 00 00 (ILTのサイズ)
        }
        else if (*((unsigned char*)overraideFunctionAddr+0) == 0x55)
        {
                //push ebx
                overraideFunctionAddr = overraideFunctionAddr;
        }
        else
        {
                //vcallの解析に失敗しました...
                SEXYHOOK_BREAKPOINT;
        }
}

次のページ

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
「SEXYHOOK」 とある関数の接合部連載記事一覧
この記事の著者

rti(あーるてぃーあい)

働いたら負けだと思っていた元ニートのプログラマ歌って踊れてさくらたんにもハァハァできます。love:C++,アセンブラ,PHP,javascript好きのOO厨房低レイヤープログラムやネットワークサーバからサーバサイドプログラム、ソフトウェア設計、インフラ設計運用までと幅広くやってます。多分設計信者。 あんまり得意ではないけど 人工知能(学習エンジン)とかシステム...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/4883 2010/03/03 17:59

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング