Introducing 

Prezi AI.

Your new presentation assistant.

Refine, enhance, and tailor your content, source relevant images, and edit visuals quicker than ever before.

Loading…
Transcript

テストを書いていると、

一時的に関数の挙動を書き換えたいときがあります。

//2000年以上か?

bool isOver2000year()

{

//現在動かしたら、絶対 TRUE にしかならない

return time(NULL) >= 946652400; //2000-01-01 00:00:00

}

PCの時計を変更

うーん、めんどくさい。

そんなことで困ってませんか?

//time関数を乗っ取る all your function are belong to us

SEXYHOOK_BEGIN(time_t,SEXYHOOK_CDECL,&time,(time_t * a))

{

//昔の時刻を返すようにする

return 915116400;//1999-01-01 00:00:00

}

SEXYHOOK_END();

//現在は2010年なので、結果は TRUE になりそうだが、

//上で関数をフックしているので、結果は FALSE

bool r = isOver2000year();

printf("%d" , (int)r); //0

SEXYHOOK_BEGIN(戻り値,呼出規約,&関数名,(引数....) )

{

フックする処理

return 返却したい値;

}

SEXYHOOK_END();

//ターゲット

//上の例でもあったtime関数をフックしてみる

time(NULL);

//関数をフックするときは、 SEXYHOOK_FUNC_HOOK_1_BEGIN を使います。

SEXYHOOK_BEGIN(time_t,SEXYHOOK_CDECL,&time,(time_t * a) )

{

return 915116400;//1999-01-01 00:00:00

}

SEXYHOOK_END();

//例 その2

//strstr関数をフックしてみる。

const char * p = strstr("hello","hel");

//strstr をフックする場合

SEXYHOOK_(const char*,SEXYHOOK_CDECL,

&strstr,(const char * a , const char * b) )

{

//絶対失敗する strstr 関数にする

return NULL;

}

SEXYHOOK_END();

//例 その3

//自作関数をフックしたい場合

int add(int a,int b)

{

return a + b;

}

//add関数を書き換えて「引き算」にする。

SEXYHOOK_BEGIN(int,SEXYHOOK_CDECL,&add,(int a,int b) )

{

return a - b; //引き算に書き換える

}

SEXYHOOK_END();

SEXYHOOK_BEGIN(戻り値,呼び出し規約,&メソッド名,(引数....) )

{

フックする処理

return 返却したい値;

}

SEXYHOOK_END();

//テンプレート

template<typename _T> class TempTest

{

public:

int add(_T a,_T b)

{

return a + b;

}

};

{

int r;

TempTest<int> t;

//ここからフック

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS,&TempTest<int>::add,int a,int b)

{

return a - b; //足し算を引き算に書き換える

}

SEXYHOOK_END();

r = t.add(1,2);

printf("\r\nテンプレートクラスのテスト\r\n");

printf("TempTest<int> : %d //足し算を引き算に\r\n",r);

SEXYHOOK_ASSERT(r == -1);

}

Child child;

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS, &Child::g , () )

{

return 103; //Child::g

}

SEXYHOOK_END(&child); //thisを渡す

SEXYHOOK_BEGIN

(戻り値,SEXYHOOK_STDCALL,&API名,(引数....) )

{

フックする処理

return 返却したい値;

}

SEXYHOOK_END();

//HeapCreate API を失敗させてみる。

SEXYHOOK_BEGIN(HANDLE,SEXYHOOK_STDCALL,

&HeapCreate,(HANDLE a1,DWORD a2,SIZE_T a3) )

{

return NULL;

}

SEXYHOOK_END();

//必ず失敗する.

HANDLE h = HeapCreate(0,0,100);

SEXYHOOK_ASSERT(h == NULL);

//非クラス系

void func1(){}; //SEXYHOOK_CDECL

void __stdcall func2(){}; //SEXYHOOK_STDCALL

void __fastcall func3(){}; //SEXYHOOK_FASTCALL

//クラス系

class MyClass

{

public:

void func1(){}; //SEXYHOOK_CLASS

void __stdcall func2(){}; //SEXYHOOK_CLASS_STDCALL

void __cdecl func3(){}; //SEXYHOOK_CLASS_CDECL //別らしい

void __fastcall func4(){}; //SEXYHOOK_CLASS_FASTCALL

//staticは関数として呼び出してください

static void func5(){}; //SEXYHOOK_CDECL

static void __stdcall func6(){}; //SEXYHOOK_STDCALL

static void __fastcall func6(){}; //SEXYHOOK_FASTCALL

}

//一時的にtime 関数をフックする。

SEXYHOOK_BEGIN

(time_t,SEXYHOOK_CDECL,&time,(time_t * a) )

{

//time が呼ばれたらここがコールされる。

//昔の時刻を返すようにする。

return 915116400;//1999-01-01 00:00:00

}

SEXYHOOK_END();

//プリプロセッサによりこのように展開されます。

class SEXYHOOKFunc72 : public SEXYHOOKFuncBase

{

public:

SEXYHOOKFunc72() { }

void Hook(void* inVCallThisPointer = 0,void* inFuncAddress2 = 0)

{

*(getSexyhookThisPointer()) = (uintptr_t)this;

FunctionHookFunction(

inFuncAddress2 ? inFuncAddress2 : SEXYHOOK_DARKCAST(0,(HookDef)&time)

,SEXYHOOK_DARKCAST(0,&SEXYHOOKFunc72::HookFunction)

,SEXYHOOK_DARKCAST(0,&SEXYHOOKFunc72::CallOrignalFunction)

,inVCallThisPointer

);

}

virtual ~SEXYHOOKFunc72()

{

FunctionUnHookFunction();

}

typedef time_t ( SEXYHOOKFuncBase::* HookDef) (time_t * a) ;

typedef time_t ( SEXYHOOKFuncBase::* HookDefConst) (time_t * a) ;

static uintptr_t* getSexyhookThisPointer()

{

static uintptr_t thisSaver = 0;

return &thisSaver ;

}

time_t CallOrignalFunction (time_t * a)

{

throw 0;

}

int HookFunction (time_t * a)

{

SEXYHOOKFunc72* sexyhookThis = ((SEXYHOOKFunc72*)(*getSexyhookThisPointer()) );

void * sexyhookOrignalThis = (void*) this;

{

//time が呼ばれたらここがコールされる。

//昔の時刻を返すようにする。

return 915116400;//1999-01-01 00:00:00

}

}

} objectFUNCHook74;

objectFUNCHook74.Hook();

//呼び出される関数

6: int add(int a,int b)

7: {

00401220 push ebp //←ここを書き換える

00401221 mov ebp,esp

8: return a + b;

00401223 mov eax,dword ptr [a]

00401226 add eax,dword ptr [b]

9: }

00401229 pop ebp

0040122A ret

//呼び出される関数

6: int add(int a,int b)

7: {

00401220 jmp フックルーチン //書き換えた!!

...

00401226 add eax,dword ptr [b]

9: }

00401229 pop ebp

0040122A ret

フックルーチンアドレス =

命令実行時のアドレス(eip) + 飛ぶアドレス

関数はどうやって呼び出されるか?

//呼び出し側

95: int a = add(1,2);

0040145D push 2

0040145F push 1

00401461 call @ILT+70(add) (0040104b)

00401466 add esp,8

00401469 mov dword ptr [a],eax

@ILT+70(?add@@YAHHH@Z):

0040104B jmp add (00401220)

//呼び出される関数

6: int add(int a,int b)

7: {

00401220 push ebp

00401221 mov ebp,esp

8: return a + b;

00401223 mov eax,dword ptr [a]

00401226 add eax,dword ptr [b]

9: }

00401229 pop ebp

0040122A ret

(void*)&add 等で取得した関数ポインタの先頭が

0xe9 (JMP命令)で始まっていれば、

ILTを経由していると判断してアドレスを再計算しています。

uintptr_t overraideFunctionAddr = 0;

if (*((unsigned char*)inFunctionAddress+0) == 0xe9)

{

//フック関数も ILT経由で飛んでくる場合

//0xe9 call [4バイト相対アドレス]

uintptr_t jmpaddress = *((uintptr_t*)

((unsigned char*)inFunctionAddress+1));

overraideFunctionAddr =

(((uintptr_t)inFunctionAddress) + jmpaddress) + 5;

//+5は e9 00 00 00 00 (ILTのサイズ)

}

else

{

//即、プログラム領域に飛んでくる場合

overraideFunctionAddr = (uintptr_t)inFunctionAddress;

}

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]

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;

int cf = child.f();

204: cf = child.f();

00401714 lea ecx,[child]

00401717 call @ILT+205(Child::f) (004010d2)

0040171C mov dword ptr [cf],eax

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 バイト等の数字

overraideFunctionAddr = (uintptr_t)

*(

(void**)*(

(void***)inVCallThisPointer

) + plusAddress

);

//アセンブリでは、

//さくっとかけるポインタ処理を

//C言語で汎用的に書くのは大変です。

lea ecx,[child]

mov eax,dword ptr [ecx]

jmp dword ptr [eax+4]

//アドレスを求めてみる。

printf("&Child::aaa: %p\r\n", &Child::aaa);

printf("&Child::f: %p\r\n", &Child::f);

printf("&Child::g: %p\r\n", &Child::g);

VC++

&Child::aaa: 00401127

&Child::f: 0040116D

&Child::g: 00401186

gcc

&Child::aaa: 0x804871c

&Child::f: 0x1 ← !?

&Child::g: 0x5 ← !?

//定義

int add(int a,int b)

{

return a + b;

}

//関数のフックのテスト

{

//add関数を書き換えて引き算にする。

SEXYHOOK_BEGIN

(int,SEXYHOOK_CDECL,&add,(int a,int b) )

{

return a - b;

}

SEXYHOOK_END();

int cc = add(10,20);

printf("\r\n関数のフックのテスト\r\n");

printf("cc: %d\r\n",cc);

SEXYHOOK_ASSERT(cc == -10);

}

class myClass{

public:

int setA(int a){

this->Member = a;

}

};

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS,

&myClass::setA,(int a))

{

this->Member = a; //エラー

}

SEXYHOOK_END();

class myClass{

public:

int setA(int a){

this->Member = a;

}

};

class SEXYHOOKFunc72 :

public SEXYHOOKFuncBase

{

public:

//フックした中身

int HookFunction (int a)

{

this->Member = a; //エラー

}

};

private_testclass c;

//ここではまだフックしていない

int r = c.public_add(10,20);

SEXYHOOK_ASSERT(r == 30);

//private メソッドのアドレスを算出してフック.

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS,

SEXYHOOKAddrUtil::strstr_addr("private_testclass::private_add"),

(int a,int b) )

{

return a - b;

}

SEXYHOOK_END();

//フックできたことを確認!

r = c.public_add(10,20);

SEXYHOOK_ASSERT(r == -10);

元のソースコードを一切変更せずに

テスト用の接合部を

作ることができます。

関数、クラスメソッド、

APIの3つをフックできます。

実装はアセンブリとマシン語が

入り乱れる魔術サイドです

SEXYHOOKを利用して

みんなもっとセクシーになりましょう。

SEXYHOOKを利用すると、

既存のソースコードに一切手を加えずに

新たな接合部を自由に作成できる。

第1部

                       ヘ(^o^)ヘ いいぜ

                         |∧  

                     /  /

                 (^o^)/ てめえが

                /(  )    テストできないほど他者に

       (^o^) 三  / / >      依存しているというのなら

 \     (\\ 三

 (/o^)  < \ 三 

 ( /

 / く  まずはそのふざけた

       関数をフックする

とうまー ねーとうまー

私はおなかがすいたんだよ

えんいー\e

リファクタリング

//2000年以上か?

bool isOver2000year(time_t t)

{

//現在なら間違いなく true. これを falseにするには?

return t >= 946652400; //2000-01-01 00:00:00

}

最近、JSOXやら監査やらうぜぇぇぇ

リファクタリング工数

sexyhook概要

イントロダクション

接合部ありますか?

たいていのソースコードは

例のようなAPIのハードコーディング、

深い依存性の上に創れています。

SEXYHOOKは

クラスを作成するとき

インターフェース継承で作成したり、

APIをすべてクラスで抽象化して

作ることの方がマレ

第一部

SEXYHOOKの使い方を書こうとしたが、

それを書くにはこの余白は小さすぎる

レガシーコード改善ガイド(Working Effectively With Legacy Code)

Q:

リリースビルド(最適化)で

動作しません

仕様です。

SEXYHOOKはデバッグビルドのみで

利用できます。

関数の依存性を下げ、

ソースコード上に「接合部」を

作り出すことでテストを行いやすく

FAQ

Q

どこが SEXY?

コード

実装部分を知ると

よりセクシーになれます。

まとめ

プロジェクト

第一部

終章

http://code.google.com/p/sexyhook/

コンパイラの仕様との戦い

イントロダクション

ご自由にお使いください。

VC2003以降は /ZI→ /Zi

http://support.microsoft.com/kb/199057/ja

テストルーチンの時だけ、

一時的に time() を

昔に戻せないだろうか。

sexyhookを使えば、

一時的にAPIや関数、クラスメソッドを

自由に書き換えることが出来ます。

動作環境

・Windows

VC++6 / VC++2003

VC++2005 / VC++2008

VC++2010

・Linux

GCC

APIや他のクラスに依存している

関数のテストは非常に難しい

32bit/バッグビルド

{

//現在は2010年なので結果はTRUE

bool r = isOver2000year();

printf("%d" , (int)r); //1

}

{

//time関数を乗っ取る all your function are belong to us

SEXYHOOK_BEGIN(time_t,SEXYHOOK_CDECL,&time,(time_t * a))

{

//昔の時刻を返すようにする

return 915116400;//1999-01-01 00:00:00

}

SEXYHOOK_END();

//現在は2010年なので、結果は TRUE になりそうだが、

//上で関数をフックしているので、結果は FALSE

bool r = isOver2000year();

printf("%d" , (int)r); //0

}

{

//ブロックを抜けたのでフックが解除される

    //現在は2010年なので true になる.

bool r = isOver2000year();

printf("%d" , (int)r); //1

}

time()関数を

一時的に書き換える

こんな関数のテストするとして、

time()関数をライブラリの中に書いてあるので、

失敗ルーチンのテストを書くのが非常に難しい。。。

第2部

SEXYHOOK使い方

http://code.google.com/p/sexyhook/wiki/SEXYHOOK_Users_Manual

関数、

クラスメソッド、

API

さまざまなフック

第2部

SEXYHOOK使い方

関数をフックしたい場合

クラスメソッドを

フックしたい場合

//オペレータオーバーロードのテスト

class OpClass

{

int A;

public:

OpClass(int inA) : A(inA)

{

}

int getA() const

{

return this->A;

}

OpClass operator+( const OpClass& x )

{

OpClass z( this->A + x.A );

return z;

}

};

//演算子オーバーロードのテスト

{

OpClass a(10);

OpClass b(20);

//フック関数を定義する前だからフックされない

OpClass c = a + b;

//ここからフック

SEXYHOOK_BEGIN(OpClass,SEXYHOOK_CLASS,

&OpClass::operator +, (const OpClass& x) )

{

OpClass z( 1000 );

return z;

}

SEXYHOOK_END();

//これはフックされる.

OpClass d = a + b;

SEXYHOOK_ASSERT(c.getA() == 30);

SEXYHOOK_ASSERT(d.getA() == 1000); //固定値

}

class testclass2

{

public:

bool check()

{

if (this->checkCore(1))

{

return true;

}

else

{

//ここのテストがしたい

return false;

}

}

bool checkCore(int a ) // ←これをフックする

{

return a ? true : false;

}

};

SEXYHOOK_BEGIN(bool,SEXYHOOK_CLASS,&testclass2::checkCore,(int a) )

{

return false;

}

SEXYHOOK_END();

testclass2 myclass2;

bool r = myclass2.check(); //FALSE になる。

第2部

more sample?

仮想関数は少し特殊

SEXYHOOKの main.cpp に

テスト兼サンプルソースが

載っています

SEXYHOOKの魔術はこれからだ。

俺たちの冒険はまだまだ続く。

WindowsAPIを

フックしたい場合

//関数

SEXYHOOK_BEGIN

(戻り値,呼出規約,&関数名,(引数....) )

{

フックする処理

return 返却したい値;

}

SEXYHOOK_END();

//クラスメソッド

//呼び出し規約はたいてい SEXYHOOK_CLASS

SEXYHOOK_BEGIN

(戻り値,呼出規約,&メソッド名,(引数....) )

{

フックする処理

return 返却したい値;

}

SEXYHOOK_END();

//クラスメソッド(仮想関数)

SEXYHOOK_BEGIN

(戻り値,呼出規約,&メソッド名,(引数....) )

{

フックする処理

return 返却したい値;

}

SEXYHOOK_END(クラスインスタンス);

//WindowsAPI

SEXYHOOK_BEGIN

(戻り値,SEXYHOOK_STDCALL,&メソッド名,(引数....) )

{

フックする処理

return 返却したい値;

}

SEXYHOOK_END();

class Parent

{

public:

virtual int f()

{

return 1;

}

virtual int g() =0;

};

class Child : public Parent

{

public:

int aaa()

{

return 0;

}

virtual int f()

{

return 2;

}

virtual int g()

{

return 3;

}

};

{

Child child;

//ここからフック

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS,&Child::g ,() )

{

return 103; //Child::g

}

SEXYHOOK_END(&child); //this

int cg = child.g();

SEXYHOOK_ASSERT(cg == 103);

}

フック中に、元の関数を呼び出したい。 その2

呼び出し規約

フック中に、元の関数を呼び出したい。 その1

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS,

&classMethodCallTest::Add,(int a))

{

//一時的にフックを解除する

SEXYHOOK_UNHOOK();

return CallOrignalFunction(a);

}

フック中に this が使いたい

とある関数の接合部(スィーム)

CallOrignalFunction();

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS,

&classMethodCallTest::Add,(int a))

{

return

SEXYHOOK_THIS(classMethodCallTest*)->Sub(5);

}

元の関数を呼び出す

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS,

&classMethodCallTest::Add,(int a))

{

//一時的にフックを解除する

SEXYHOOK_UNHOOK();

return

SEXYHOOK_THIS

(classMethodCallTest*)->Add(a);

}

自動的に UNHOOK

されればいいんだけど、、

今は無理。

SEXYHOOK_THIS(クラス名*)

SEXYHOOK_UNHOOK();

ブロックの終了まで、フックを解除する

SEXYHOOK_THIS(myClass*)->Member = 10;

public メソッド、

public メンバ変数にしかアクセス出来ません。

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS,

&classMethodCallTest::Add,(int a))

{

{

//フックしている

}

{

//一時的に元に戻す.

SEXYHOOK_UNHOOK();

//元に戻っている

}

{

//再度フックされる.

}

}

gcc の __fastcall について。

ディフォルトでは、

__attribute__ ((regparm(3))) として定義しています。

自分でレジスタ数を定義できるバージョンも用意しました。

#define SEXYHOOK_CLASS_FASTCALL \

__attribute__ ((regparm(3)))

#define SEXYHOOK_CLASS_FASTCALLN(N) \

__attribute__ ((regparm(N)))

sexyhook2の使い方と実装について

by rti

人口4人の自称国家。

第2次大戦後の要塞を勝手に占拠して独立宣言。

爵位販売中

プログラム設計、インフラ設計

シーランド公国とは?

love:c++,アセンブラ,php,javascript

イギリス

シーランド公国伯爵

ハンドル:rti

自己紹介

画像:wikipediaから

関数呼び出し その2

直接関数が呼ばれる場合

第3部

SEXYHOOKの実装部

http://code.google.com/p/sexyhook/wiki/SEXYHOOK_Hackers_Manual

魔術サイド

vcall

ecxに格納されている thisから

virtual なアドレス解決をする

http://rtilabs.net/

実体へ

飛ぶ

関数呼び出し その3

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;

}

}

アドレスは動的に決まる

追跡するコード

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

!=

関数の本体の

アドレスを追跡する

call @ILT+205(Child::f) (004010d2)

求めたアドレスは実体ではない。

void* p のアドレスは vcall

第3部

SEXYHOOKの実装部

@super_rti

ポインタ参照はメンドイ

一度

ILTを

経由で

される

(void*)&addは

ここのアドレス

関数呼び出し その1

ILTを経由する場合

all your function are belong to us.

関数の実体を求めて

gcc と仮想関数フック

gccは、

vtableのindexが帰ってくる

解決策

thisの保存と挿げ替え.

第3部

//呼び出し側

95: int a = add(1,2);

0040145D push 2

0040145F push 1

00401461 call @ILT+70(add) (0040104b) ←

00401466 add esp,8

00401469 mov dword ptr [a],eax

//呼び出される関数

6: int add(int a,int b)

7: {

00401220 push ebp

00401221 mov ebp,esp

8: return a + b;

00401223 mov eax,dword ptr [a]

00401226 add eax,dword ptr [b]

9: }

00401229 pop ebp

0040122A ret

ご愛読

ありがとうございました。

call されたアドレスと

add関数が始まっているアドレスが違う

class myClass{

public:

int setA(int a){

asm{

jmp SEXYHOOKFunc72::HookFunction;

};

}

};

class SEXYHOOKFunc72 :

public SEXYHOOKFuncBase

{

public:

//フックした中身

int HookFunction (int a)

{

//SEXYHOOKFunc72 自体の

//自分の thisはどこに?

this->Member = a;

}

};

class SEXYHOOKFunc72 : public SEXYHOOKFuncBase

{

public:

//thisを保存するだけの領域として利用する.

static uintptr_t* getSexyhookThisPointer()

{

static uintptr_t thisSaver = 0;

return &thisSaver ;

}

//フックを開始する.

void Hook(void* inVCallThisPointer = 0,void* inFuncAddress2 = 0)

{

//フックする直前に自分の this を保存する.

*(getSexyhookThisPointer()) = (uintptr_t)this;

//フック開始

FunctionHookFunction(

inFuncAddress2 ? inFuncAddress2 : SEXYHOOK_DARKCAST(0,(HookDef)&time)

,SEXYHOOK_DARKCAST(0,&SEXYHOOKFunc72::HookFunction)

,SEXYHOOK_DARKCAST(0,&SEXYHOOKFunc72::CallOrignalFunction)

,inVCallThisPointer

);

}

//フックした中身

int HookFunction (int a)

{

//SEXYHOOKのthisを復元する.

SEXYHOOKFunc72* sexyhookThis = ((SEXYHOOKFunc72*)(*getSexyhookThisPointer()) );

//実行時、コンパイル時ともにエラーにならないように拾っとく.

void * sexyhookOrignalThis = (void*) this;

{

//キャストしてアクセスする.

((myClass*)sexyhookOrignalThis)->Member = a;

}

}

};

-Wno-pmf-conversions

オプションで直るよ!

別の問題:

逆にSEXYHOOK側の

thisがなくなっちゃう。

マクロ展開

printf("&Child::aaa: %p\r\n", (void*)&Child::aaa);

printf("&Child::f: %p\r\n", (void*)&Child::f);

printf("&Child::g: %p\r\n", (void*)&Child::g);

結局vtableのindexを

自前で求めた

つけてもキャストミスると

不思議なアドレス

トリッキーな

コード大杉w

&Child::aaa: 0x80484e0

&Child::f: 0x80484ea

&Child::g: 0x80484f4

HookDef:int ( SEXYHOOKFuncBase::* )() )

本来 :int ( Child::* )() )

(void*)&Child.f //OK

(int (Child::*)())&Child.f //OK

(HookDef)&Child.f //NG

本来の型または、void* 以外に

キャストするとダメ

int* vtable =

(int*) *((int*)*this)

#ifdef __GNUC__

//gccでは仮想関数のポインタを取得しようとすると、 vtable からの index を返してしまう。

if ( (uintptr_t)inFunctionAddress < 100 )

{

//クラスのインスタンス(thisポインタ)が渡されていれば、indexから実体の場所を計算可能。

if (inVCallThisPointer == NULL)

{

//thisがないなら計算不可能なので、とりあえずとめる.

SEXYHOOK_BREAKPOINT;

}

//thisがあれば計算してvtableのアドレスを求める.

//参考: http://d.hatena.ne.jp/Seasons/20090208/1234115944

uintptr_t* vtable = (uintptr_t*) (*((uintptr_t*)inVCallThisPointer));

//index値の補正

//とりあえず、 (index - 1) / sizeof(void*) でアドレスが求まるみたい.

uintptr_t index = ((uintptr_t)inFunctionAddress - 1) / sizeof(void*);

//vtable から index を計算する.

inFunctionAddress = (void*) (vtable[index] );

}

#endif

class myClass{

public:

int setA(int a){

asm{

//呼び出し規約が全く同じなので、

//thisもスタックも引き継ぐ形になる。

jmp SEXYHOOKFunc72::HookFunction;

};

}

};

class SEXYHOOKFunc72 :

public SEXYHOOKFuncBase

{

public:

//フックした中身

int HookFunction (int a)

{

//thisはコンパイル時には、SEXYHOOKを

//実行時には、本来のクラスを指している。

this->Member = a; //実行時は有効。

}

};

仮想関数  キャスト値 値計算結果

&Child::f0x01(0x01 - 1) / 4 = 0

&Child::g0x05(0x05 - 1) / 4 = 1

LAT経由での手法もあとで提供したい

しかし、実行時には

このthisは有効である。

コンパイル時のみ無効になる。

//最初の呼び出し

394: int cc = add(10,20);

00402137 push 14h

00402139 push 0Ah

0040213B call @ILT+165(add) (004010aa)----ILT経由でadd関数呼び出し

|

00402140 add esp,8 <<<<<<<<< <<< | <<<<<<<<<<<<<<---------------------戻り値

00402143 mov dword ptr [cc],eax | |

| |

| |

//ILT | |

@ILT+165(?add@@YAHHH@Z): | |

004010AA jmp add (00401470) <------------ |

|add関数の実体呼び出し |

| |

| |

| |

9: //関数のテスト | |

10: int add(int a,int b) | |

11: { / |

//強引に書き換えてフックルーチンへ飛ばす |

00401470 jmp `main'::`83'::SEXYHOOKFunc388::HookFunction (00402a5e) |

... | |

00401477 inc ebp //ここは実行されない | |

00401478 or al,5Dh //壊れたアセンブラ | |

0040147A ret //ここは実行されない | |

12 } | |

| |

| |

| |

//フックルーチンのインナークラス | |

class SEXYHOOKFunc388 : public SEXYHOOKFuncBase | |

{ | |

//中略 | |

//足し算を引き算に書き換え | |

389: { | |

00402A5E push ebp //←ここに飛ぶ <-- |

00402A5F mov ebp,esp |

390: return a - b; |

00402A61 mov eax,dword ptr [a] |

00402A64 sub eax,dword ptr [b] |

391: } |

00402A67 pop ebp |

00402A68 ret //←ここの ret 命令で、 |

//最初に add命令を呼び出したところ(00402140)に戻る. >--------------|

//フック関数と 呼び出し規約と引数をそろえているため、

//スタックは破壊されない。

//中略

}

thisポインタの

引継問題について

お客様の中で

に詳しい方は

いらっしゃいませんか?

NTカーネル

we love 0xE9

協力してくれた

方々に感謝!!

SEXYHOOKの中では thisを使えない。

関数フック

SEXYHOOK_BEGINは

クラス内クラスを作るため。

別クラス

jmp フックルーチン

0xE9 [フックルーチンへの相対アドレス]

5バイトの命令

DLLのメモリ共有と

NTカーネル

関数の呼び出し

フロー図

//書き換えるマシン語

FUNCTIONHOOK_ASM interraoutASMCode ;

//フックされる関数の先頭を書き換えて、フックルーチンへ制御を移すようにする。

//参考 http://www.artonx.org/diary/200809.html

// http://hrb.osask.jp/wiki/?faq/asm

*((unsigned char*)interraoutASMCode+0) = 0xe9; //近隣ジャンプ JMP

*((uintptr_t*)((unsigned char*)interraoutASMCode+1)) =

hookFunctionAddr - (uintptr_t)overraideFunctionAddr ;

this->OrignalFunctionAddr = (void*)overraideFunctionAddr;

ReplaceFunction(this->OrignalFunctionAddr , interraoutASMCode , &this->OrignalAsm);

番外編

未来のせくしー

DLLってNTカーネル系でも、

メモリ共有しているのでしょうか。

XPで試してみると、

DLLのコード領域を破壊しても、

他のプログラムには影響がない。。。

SEXYHOOKFuncBase::FunctionHookFunction? 関数の下の方

let's ハンドアセンブル

APIフック

C++でリフレクションが

実装できるかも。。。。

とりあえず動く。

dbghelp.lib

関数アドレスをシンボルから検索

privateメソッドだって、

symbolはあるはずだから、

symbol経由で呼べばいいんだよ!

privateメソッドのフック

dbghelp.lib

疑問点:

DLLのプログラム部分を書き換えていいの?

理由:ソースと書き方の統一

*.pdb(mapファイルみたいなもの)から

シンボルを検索したり、

シンボルからソースコードを求めたり、

ソースコードからシンボルを求めたり etc...

ver2からデグレードしました。

LAT から ふつーのフックに

変更しました。

sexyhook_addrutil.h

なぜ privateメソッドが

フックできないのか?

こんなのセクシーぢゃない。

privateだから、アクセス出来ない。

SEXYHOOK_BEGIN(int,SEXYHOOK_CLASS,

&myclass::private_add,(int a,int b)) //エラー

{

return a - b;

}

SEXYHOOK_END();

なんとかならないの?

Learn more about creating dynamic, engaging presentations with Prezi