CodeZine(コードジン)

特集ページ一覧

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

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

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2010/03/02 14:00

ダウンロード 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;
        }
}

  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

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

著者プロフィール

  • rti(あーるてぃーあい)

    働いたら負けだと思っていた元ニートのプログラマ 歌って踊れてさくらたんにもハァハァできます。 love:C++,アセンブラ,PHP,javascript好きのOO厨房 低レイヤープログラムやネットワークサーバからサーバサイドプログラム、ソフトウェア設計、インフラ設計運用までと幅広くやってます。...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5