Mini Hook是一个小巧的支持32位 64位的Hook 引擎,而且是开源的,http://www.codeproject.com/Articles/44326/MinHook-The-Minimalistic-x-x-API-Hooking-Libra
本文对mini hook的核心部分代码进行阅读并给出讲解,希望新手们能学习一些知识,
//-------------------------------------------------------------------------
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal)
{
//创建一个HOOK
_MH_EnterSpinLock();//锁住全局量
__try
{
UINT pos;
LPVOID pBuffer;
TRAMPOLINE ct;
PHOOK_ENTRY pHook;
if(g_hHeap == NULL)
return MH_ERROR_NOT_INITIALIZED;
if(!_MH_IsExecutableAddress(pTarget) || !_MH_IsExecutableAddress(pDetour))
return MH_ERROR_NOT_EXECUTABLE; //内存不可用返回失败信息
pos = FindHookEntry(pTarget);//查找是否已经被MiniHook HOOK
if(pos != INVALID_HOOK_POS)
return MH_ERROR_ALREADY_CREATED;//已经hOOK就返回
pBuffer = AllocateBuffer(pTarget);//分配一块内存用来保存跳板
if(pBuffer == NULL)
return MH_ERROR_MEMORY_ALLOC;
ct.pTarget = pTarget;
ct.pDetour = pDetour;
ct.pTrampoline = pBuffer;
if(!CreateTrampolineFunction(&ct))
{
FreeBuffer(pBuffer);//创建跳板失败返回不支持hook
return MH_ERROR_UNSUPPORTED_FUNCTION;
}
pHook = NewHookEntry();//创建一个HookInfo信息
if(pHook == NULL)
{
FreeBuffer(pBuffer);
return MH_ERROR_MEMORY_ALLOC;
}
pHook->pTarget = ct.pTarget;
#ifdef _M_X64
pHook->pDetour = ct.pRelay;
#else
pHook->pDetour = ct.pDetour;
#endif
pHook->pTrampoline = ct.pTrampoline;
pHook->patchAbove = ct.patchAbove;
pHook->isEnabled = FALSE;
pHook->nIP = ct.nIP;
memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs));
memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs));
//备份原始函数头部
if(ct.patchAbove)
{
memcpy(pHook->backup, (LPBYTE)pTarget - sizeof(JMP_REL), sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
}
else
{
memcpy(pHook->backup, pTarget, sizeof(JMP_REL));
}
if(ppOriginal != NULL)
{
(*ppOriginal) = pHook->pTrampoline;
}
return MH_OK;
}
__finally
{
_MH_LeaveSpinLock();//解除自旋锁
}
}
//-------------------------------------------------------------------------
BOOL CreateTrampolineFunction(PTRAMPOLINE ct)
{
//创建跳板函数
#ifdef _M_X64
CALL_ABS call = { //64位绝对地址call 占16Byte
0xFF, 0x15, 0x00000002, // FF15 00000002: CALL [RIP+8]
0xEB, 0x08, // EB 08: JMP +10
0x0000000000000000ULL // Absolute destination address
};
JMP_ABS jmp = { //64位绝对地址jmp 占14B
0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6]
0x0000000000000000ULL // Absolute destination address
};
JCC_ABS jcc = { //64位绝对地址条件跳转 占16B
0x70, 0x0E, // 7* 0E: J** +16
0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6]
0x0000000000000000ULL // Absolute destination address
};
#else
CALL_REL call = {
0xE8, // E8 xxxxxxxx: CALL +5+xxxxxxxx
0x00000000 // Relative destination address
};
JMP_REL jmp = {
0xE9, // E9 xxxxxxxx: JMP +5+xxxxxxxx
0x00000000 // Relative destination address
};
JCC_REL jcc = {
0x0F, 0x80, // 0F8* xxxxxxxx: J** +6+xxxxxxxx
0x00000000 // Relative destination address
};
#endif
UINT8 oldPos = 0;//oldPos表示读取Target的偏移
UINT8 newPos = 0;//newPos表示写入跳板的偏移
ULONG_PTR jmpDest = 0; // Destination address of an internal jump.
BOOL finished = FALSE; // Is the function completed?
#ifdef _M_X64
UINT8 instBuf[16];
#endif
ct->patchAbove = FALSE;
ct->nIP = 0;
do
{
//这个循环每次读取一个指令并根据可能存在相对地址跳转的情况重新解析指令中的地址写入跳板函数中
HDE hs;//hs 是一个反汇编引擎
UINT copySize;
LPVOID pCopySrc;
ULONG_PTR pOldInst = (ULONG_PTR)ct->pTarget + oldPos;
ULONG_PTR pNewInst = (ULONG_PTR)ct->pTrampoline + newPos;
copySize = HDE_DISASM((LPVOID)pOldInst, &hs);
if (hs.flags & F_ERROR)
return FALSE;
pCopySrc = (LPVOID)pOldInst;
if (oldPos >= sizeof(JMP_REL))
{
// 解析出足够长的空间可以插入JMP_REL指令的时候就可以结束跳板创建
#ifdef _M_X64
jmp.address = pOldInst;
#else
jmp.operand = (UINT32)(pOldInst - (pNewInst + sizeof(jmp)));
#endif
pCopySrc = &jmp;
copySize = sizeof(jmp);
finished = TRUE;
}
#ifdef _M_X64
else if ((hs.modrm & 0xC7) == 0x05)
{
//解析出一个RIP相对转移指令(x64新增的一种寻址方式)(ModR/M = 00???101B)
PUINT32 pRelAddr; // 换算在跳板函数中的新RIP相对偏移:Modify the RIP relative address.
// Avoid using memcpy to reduce the footprint.
__movsb(instBuf, (LPBYTE)pOldInst, copySize);
pCopySrc = instBuf;
// Relative address is stored at (instruction length - immediate value length - 4).
pRelAddr = (PUINT32)(instBuf + hs.len - ((hs.flags & 0x3C) >> 2) - 4);
(*pRelAddr) = (UINT32)((pOldInst + hs.len + (INT32)hs.disp.disp32) - (pNewInst + hs.len));
// Complete the function if JMP (FF /4).
if (hs.opcode == 0xFF && hs.modrm_reg == 4)//如果是一个jmp指令,没有必要继续解析指令了,因为跳到其他地方了
{
finished = TRUE;
}
}
#endif
else if (hs.opcode == 0xE8)
{
// Direct relative CALL(直接相对call)
ULONG_PTR dest = pOldInst + hs.len + (INT32)hs.imm.imm32;//重新解析地址
#ifdef _M_X64
call.address = dest;
#else
call.operand = (UINT32)(dest - (pNewInst + sizeof(call)));
#endif
pCopySrc = &call;
copySize = sizeof(call);
}
else if ((hs.opcode & 0xFD) == 0xE9)
{
// Direct relative JMP (EB or E9)(直接相对jmp)
ULONG_PTR dest = pOldInst + hs.len;
if (hs.opcode == 0xEB) // isShort jmp
dest += (INT8)hs.imm.imm8;
else
dest += (INT32)hs.imm.imm32;
// Simply copy an internal jump.
if ((ULONG_PTR)ct->pTarget <= dest
&& dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL)))
{
if (jmpDest < dest)
jmpDest = dest;
}
else
{
#ifdef _M_X64
jmp.address = dest;
#else
jmp.operand = (UINT32)(dest - (pNewInst + sizeof(jmp)));
#endif
pCopySrc = &jmp;
copySize = sizeof(jmp);
// Exit the function If it is not in the branch
finished = (pOldInst >= jmpDest);
}
}
else if ((hs.opcode & 0xF0) == 0x70
|| (hs.opcode & 0xFC) == 0xE0
|| (hs.opcode2 & 0xF0) == 0x80)
{
// Direct relative Jcc(相对条件跳转)
ULONG_PTR dest = pOldInst + hs.len;
if ((hs.opcode & 0xF0) == 0x70 // Jcc
|| (hs.opcode & 0xFC) == 0xE0) // LOOPNZ/LOOPZ/LOOP/JECXZ
dest += (INT8)hs.imm.imm8;
else
dest += (INT32)hs.imm.imm32;
// Simply copy an internal jump.
if ((ULONG_PTR)ct->pTarget <= dest
&& dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL)))
{
if (jmpDest < dest)
jmpDest = dest;
}
else if ((hs.opcode & 0xFC) == 0xE0)
{
// LOOPNZ/LOOPZ/LOOP/JCXZ/JECXZ to the outside are not supported.(不支持的指令,他们会跳到函数外部)
return FALSE;
}
else
{
UINT8 cond = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F);
#ifdef _M_X64
// Invert the condition.
jcc.opcode = 0x71 ^ cond;
jcc.address = dest;
#else
jcc.opcode1 = 0x80 | cond;
jcc.operand = (UINT32)(dest - (pNewInst + sizeof(jcc)));
#endif
pCopySrc = &jcc;
copySize = sizeof(jcc);
}
}
else if ((hs.opcode & 0xFE) == 0xC2)
{
// RET (C2 or C3)
// Complete the function if not in a branch.
finished = (pOldInst >= jmpDest);
}
// Can't alter the instruction length in a branch.
if (pOldInst < jmpDest && copySize != hs.len)
return FALSE;
if ((newPos + copySize) > TRAMPOLINE_MAX_SIZE)
return FALSE;
if (ct->nIP >= ARRAYSIZE(ct->oldIPs))
return FALSE;
ct->oldIPs[ct->nIP] = oldPos;
ct->newIPs[ct->nIP] = newPos;
ct->nIP++;
// Avoid using memcpy to reduce the footprint.
__movsb((LPBYTE)ct->pTrampoline + newPos, pCopySrc, copySize);
newPos += copySize;
oldPos += hs.len;//加上解析出的这条指令的长度
}
while (!finished);
//是否能够容得下一个长跳转 14B,IsCodePadding用来判断后续代码是编译器填充代码(如果是填充代码可以看作可用的)
if (oldPos < sizeof(JMP_REL)
&& !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL) - oldPos))
{
// 如果不能,看看能否放一个短跳转 2B
if (oldPos < sizeof(JMP_REL_SHORT)
&& !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL_SHORT) - oldPos))
{
return FALSE;//短的也放不下就返回不支持
}
// 短的跳转能放下,这个短跳转是跳转到函数前面的一块地方,那里在放一个长跳转
if (!_MH_IsExecutableAddress((LPBYTE)ct->pTarget - sizeof(JMP_REL)))
return FALSE;
if (!IsCodePadding((LPBYTE)ct->pTarget - sizeof(JMP_REL), sizeof(JMP_REL)))
return FALSE;//这个函数前面的区域不是填充代码的也不行
ct->patchAbove = TRUE;//返回这个标记,当EnableHook的时候这个标记就是要通过2B短跳转到Target-14B处的长跳转
}
#ifdef _M_X64
// Create a relay function.
jmp.address = (ULONG_PTR)ct->pDetour;
ct->pRelay = (LPBYTE)ct->pTrampoline + newPos;
memcpy(ct->pRelay, &jmp, sizeof(jmp));
#endif
return TRUE;
}
//-------------------------------------------------------------------------
static BOOL IsCodePadding(LPBYTE pInst, UINT size)
{
//这个函数判断pInst开始的size个字节的指令是否都是一样的00 90 cc,也就是编译器填充的指令
UINT i;
//0xCC=int 3
//0x90=nop
//0x00=???
if(pInst[0] != 0x00 && pInst[0] != 0x90 && pInst[0] != 0xCC)
return FALSE;
for(i = 1; i < size; ++i)
{
if(pInst[i] != pInst[0])
return FALSE;
}
return TRUE;
}