<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">Mini Hook是一个小巧的支持32位 64位的Hook 引擎,而且是开源的,http://www.codeproject.com/Articles/44326/MinHook-The-Minimalistic-x-x-API-Hooking-Libra</span>

本文对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();//解除自旋锁
	}
}

MiniHook是一个InlineHOOK 原理就是将目标函数的头部指令copy到一个称之为跳板的区域,并在头部修改代码指向钩子函数,钩子函数中可以调用跳板再跳回去,
32位情况下比较简单,因为一个绝对跳转只有5个字节,而64位需要14个字节,这样64位的情况比较复杂

创建Hook的函数CreateHook只是创建了“跳板函数”,新的函数地址,备份旧的函数地址,并没有进行hook
hook的时候调用EnableHook即可,这个期间会将跳转指令插入到原始函数头部(本文不讨论)

生成跳板的过程比较复杂,下面给出一些注释:


//-------------------------------------------------------------------------
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;
}


这里要解析原始函数前的一些指令,对于出现的相对偏移地址,在跳板中都要给出新的相对地址

对于跳板中可能存在跳出函数外部的指令LOOPNZ/LOOPZ/LOOP/JCXZ/JECXZ 等,不支持HOOK(其实还有一些从函数后面相对调回跳板部分的还没有分析方法?总之inlinehook不是太完美)

值得注意的是,这段程序如果解析出来很少一段指令(少于14B,比如解析到一个跳转就结束了),这种情况会利用函数前面部分的Padding数据来进行2次跳转


//-------------------------------------------------------------------------
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;
}


总结:InlineHOOK的麻烦事:
(1)能否HOOK?
fun: short jmp code1
db data1
.........(超过14B)
code1: ........
data1;此处出现的data1 如果函数被HOOK,则必将导致访问错误

(2)相对跳转
相对跳转的区域如果在跳板内就不需要修改,否则需要修改

(3)目标函数到底有多大?
如果目标函数仅仅一个ret 而后边马上是另一个函数了,这种hook很有可能破坏后面的函数导致调用它的时候出现问题

所以实际中使用Inline-hook一定是不得已而用之,能不用Inline-hook(Ring3:EAT hook IAT hook,Ring0:SSDT hook IDT hook ObjectHOOK sysenter Hook IRPhook都可以不用inlinehook )就不用,只有无法确定有那些调用者时才会使用Inline-hook,而且Inlinehook需要根据实际情况事先分析好被hook的程序代码才可以用







Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐