看了detour的原理,心裡想著自己能不能動手類比一個。
比劃的時候發現了一些細節問題。detour將函數的前幾個位元組(剛開始看文檔的時候說5個位元組)替換成jmp __
。但是發生機器碼截斷整麼辦?可以在vs的反組譯碼視窗看到每條彙編指令的機器碼,每條指令的機器碼長度並不是固定的,我想detour並不是固定複製5個位元組這麼簡單(目前還沒研究detour源碼)。所以退而求其次(這是一個學習性質的實驗),改變設計。
我選擇在自己定義的函數執行完之後將修改的代碼再改回去,跳回原函數運行。看起來是這樣的:
而且自訂函數是不知道自己返回到的是traceBack的。
即原函數總是會被執行。這就變成了鉤子,而且只能鉤住一次,因為我把被修改地區複原了。
不過暫且就不計較這個了(這是一個學習性質的實驗)。
真正自己實現的時候才發現一些有趣的細節問題,比如jmp對應有多個機器碼,選擇哪一個,字碼頁原來是不允許寫的,要VirtualProtect函數修改,traceBack不能return只能jmp,為了保證棧平衡需要寫成__declspec(naked)之類。
還有我修改了15個位元組,三個句子:
push __
push __
jmp __
第一個push壓入的資訊提供給traceBack,好讓他能找到修改前的代碼拷回去。
第二個push壓入traceBack的地址,這樣跳轉到traceBack就對自訂函數(hook)是透明的了。
最後轉跳,是一個近轉跳,所以還要計算相對位移。
效果:
1: #include "detour.h"
2: #include <stdio.h>
3: #include <windows.h>
4:
5: #pragma optimize("",off)
6:
7: void hook()
8: {
9: printf("detoured\n");
10: }
11:
12: int main()
13: {
14: detourInit();
15: //---------------------------------------------------------
16:
17: detour( Sleep,hook );
18:
19: Sleep(1);
20:
21: system("pause");
22: return 0;
23: }
PS: detour.h 即我自己實現的源碼。(代碼在下面)
結果:
Sleep就被勾住了。但是再次調用Sleep就是正常的。
具體代碼:
detour.h ( 其實應該起名hook的 )
1: #ifndef __DETOUR_H__
2: #define __DETOUR_H__
3:
4: #pragma once
5:
6: #include <windows.h>
7: #include <assert.h>
8: #include <stdlib.h>
9:
10: typedef unsigned char u8;
11:
12: //push (下標) + push (traceBack地址) + jmp (hook) 覆蓋15個位元組
13: #define TBSIZE 15
14: #define INFOSIZE 5
15: #define REPSIZE 15
16:
17: //machine code
18: #define MC_FARJMP 0xE9
19: #define MC_PUSH 0x68
20:
21: struct s_detour
22: {
23: void *oldFunc;
24: void *hook;
25: u8 backUp[TBSIZE];
26: };
27:
28: s_detour gInfo[INFOSIZE];
29:
30: void detourInit()
31: {
32: assert( sizeof(gInfo) > sizeof(s_detour) );
33: memset( gInfo,0,sizeof(gInfo) );
34: }
35:
36: LPDWORD ftb_old = new DWORD;
37: BOOL ftb_res;
38: void* ftb_addr;
39: __declspec(naked) void traceBack( int tn )
40: {
41: //restore
42: //ps:只能jmp不能return
43: __asm
44: {
45: push 0x1 //假裝地址填充.使得tn能夠正確訪問
46: push ebp
47: mov ebp,esp
48: }
49:
50: assert( tn >= 1 && tn < INFOSIZE );
51:
52: //#the return would work?
53: ftb_res = VirtualProtect( gInfo[tn].oldFunc,REPSIZE,PAGE_EXECUTE_READWRITE,ftb_old );
54: assert( ftb_res );
55:
56: memcpy( gInfo[tn].oldFunc,gInfo[tn].backUp,REPSIZE );
57:
58: ftb_res = VirtualProtect( gInfo[tn].oldFunc,REPSIZE,*ftb_old,ftb_old );
59: assert( ftb_res );
60:
61: //ready to back
62: ftb_addr = (void*)gInfo[tn].oldFunc;
63: gInfo[tn].oldFunc = NULL;
64: __asm
65: {
66: mov esp,ebp
67: pop ebp
68: pop eax //pop 0x1
69: pop eax //balance stack pop tn
70:
71: mov eax,ftb_addr
72: jmp eax
73: }
74: }
75:
76: //register
77: bool detour( void *oldFunc,void *hook )
78: {
79: if( oldFunc == NULL || hook == NULL )
80: return false;
81:
82: int tn;
83: for( tn = 1;tn < INFOSIZE;++tn )
84: {
85: if( gInfo[tn].oldFunc == NULL ) //could be used
86: break;
87: }
88: if( tn == INFOSIZE )
89: return false;
90:
91: gInfo[tn].oldFunc = oldFunc;
92: gInfo[tn].hook = hook;
93:
94: LPDWORD lpOld = new DWORD;
95: BOOL res = VirtualProtect( oldFunc,REPSIZE,PAGE_EXECUTE_READWRITE,lpOld );
96: assert( res );
97:
98: memcpy( gInfo[tn].backUp,oldFunc,REPSIZE ); //back up
99:
100: //replace
101: u8 *pf = (u8*)oldFunc;
102: pf[0] = MC_PUSH;
103: *((int*)((int)pf+1)) = tn; //壓入標記
104:
105: pf = (u8*)( (int)pf + 5 );
106: pf[0] = MC_PUSH;
107: *((int*)((int)pf+1)) = (int)traceBack; //壓入traceBack地址
108:
109: pf = (u8*)( (int)pf + 5 );
110: pf[0] = MC_FARJMP;
111: *((int*)((int)pf+1)) = (int)((int)hook - ((int)pf) - 5 ); //near jmp 相對位址
112:
113: res = VirtualProtect( oldFunc,REPSIZE,*lpOld,lpOld );
114: assert( res );
115: free( lpOld );
116:
117: return true;
118: }
119:
120: #endif