Relocating entry point

Dec 10, 2012 at 1:41 AM

I'm using EasyHook 2.6 with Visual Studio 2008.

When trying to hook a function, I got a "STATUS_NOT_SUPPORTED" error.

I stepped through the debugger and saw that the error is coming from LhRelocateEntryPoint. This was because the first instruction of the function I was trying to hook is a jmp imm8, which is not supported.

Would it be possible to support it in a way similar to how jmp imm16 is? Below is a modified LhRelocateEntryPoint. The two changes are commented with "XXX: CHANGE TO SUPPORT JMP IMM8". First, I attempt to retrieve the address to jmp to and the length of the opcode, then when converting the code, I added a case to handle the 0xEB opcode.

Would this work, or is there something different about jmp imm8 that makes this not feasible? 

EASYHOOK_NT_INTERNAL LhRelocateEntryPoint(
				UCHAR* InEntryPoint,
				ULONG InEPSize,
				UCHAR* Buffer,
				ULONG* OutRelocSize)
{
/*
Description:

    Relocates the given entry point into the buffer and finally
    stores the relocated size in OutRelocSize.

Parameters:

    - InEntryPoint

        The entry point to relocate.

    - InEPSize

        Size of the given entry point in bytes.

    - Buffer

        A buffer receiving the relocated entry point.
        To ensure that there is always enough space, you should
        reserve around 100 bytes. After completion this method will
        store the real size in bytes in "OutRelocSize".

    - OutRelocSize

        Receives the size of the relocated entry point in bytes.

Returns:

*/
#ifdef _M_X64
    #define POINTER_TYPE    LONGLONG
#else
    #define POINTER_TYPE    LONG
#endif
	UCHAR*				pRes = Buffer;
	UCHAR*				pOld = InEntryPoint;
    UCHAR			    b1;
	UCHAR			    b2;
	ULONG			    OpcodeLen;
	POINTER_TYPE   	    AbsAddr;
	BOOL			    a16;
	BOOL			    IsRIPRelative;
    ULONG               InstrLen;
    NTSTATUS            NtStatus;

	ASSERT(InEPSize < 20);

	while(pOld < InEntryPoint + InEPSize)
	{
		b1 = *(pOld);
		b2 = *(pOld + 1);
		OpcodeLen = 0;
		AbsAddr = 0;
		a16 = FALSE;
		IsRIPRelative = FALSE;

		// check for prefixes
		switch(b1)
		{
		case 0x67: a16 = TRUE; continue;
		}

		/////////////////////////////////////////////////////////
		// get relative address value
		switch(b1)
		{
			case 0xE9: // jmp imm16/imm32
			{
				/* only allowed as first instruction and only if the trampoline can be planted 
				   within a 32-bit boundary around the original entrypoint. So the jumper will 
				   be only 5 bytes and whereever the underlying code returns it will always
				   be in a solid state. But this can only be guaranteed if the jump is the first
				   instruction... */
				if(pOld != InEntryPoint)
					THROW(STATUS_NOT_SUPPORTED, L"Hooking far jumps is only supported if they are the first instruction.");
				
				// ATTENTION: will continue in "case 0xE8"
			}
		case 0xE8: // call imm16/imm32
			{
				if(a16)
				{
					AbsAddr = *((__int16*)(pOld + 1));
					OpcodeLen = 3;
				}
				else
				{
					AbsAddr = *((__int32*)(pOld + 1));
					OpcodeLen = 5;
				}
			}break;
		// XXX: CHANGE TO SUPPORT JMP IMM8
		case 0xEB: // jmp imm8
			AbsAddr = *((__int8*)(pOld + 1));
			OpcodeLen = 2;
			break;
		/*
			The problem with (conditional) jumps is that there will be no return into the relocated entry point.
			So the execution will be proceeded in the original method and this will cause the whole
			application to remain in an unstable state. Only near jumps with 32-bit offset are allowed as
			first instruction (see above)...
		*/
		case 0xE3: // jcxz imm8
			{
				THROW(STATUS_NOT_SUPPORTED, L"Hooking near (conditional) jumps is not supported.");
			}break;
		case 0x0F:
			{
				if((b2 & 0xF0) == 0x80) // jcc imm16/imm32
					THROW(STATUS_NOT_SUPPORTED,  L"Hooking far conditional jumps is not supported.");
			}break;
		}

		if((b1 & 0xF0) == 0x70) // jcc imm8
			THROW(STATUS_NOT_SUPPORTED,  L"Hooking near conditional jumps is not supported.");

		/////////////////////////////////////////////////////////
		// convert to: mov eax, AbsAddr

		if(OpcodeLen > 0)
		{
			AbsAddr += (POINTER_TYPE)(pOld + OpcodeLen);

#ifdef _M_X64
			*(pRes++) = 0x48; // REX.W-Prefix
#endif
			*(pRes++) = 0xB8;

			*((LONGLONG*)pRes) = AbsAddr;

			pRes += sizeof(void*);

			// points into entry point?
			if((AbsAddr >= (LONGLONG)InEntryPoint) && (AbsAddr < (LONGLONG)InEntryPoint + InEPSize))
				/* is not really unhookable but not worth the effort... */
				THROW(STATUS_NOT_SUPPORTED, L"Hooking jumps into the hooked entry point is not supported.");

			/////////////////////////////////////////////////////////
			// insert alternate code
			switch(b1)
			{
			case 0xE8: // call eax
				{
					*(pRes++) = 0xFF;
					*(pRes++) = 0xD0;
				}break;
			case 0xE9: // jmp eax
			// XXX: CHANGE TO SUPPORT JMP IMM8

			case 0xEB: // jmp imm8
				{
					*(pRes++) = 0xFF;
					*(pRes++) = 0xE0;
				}break;
			}

			/* such conversions shouldnt be necessary in general...
			   maybe the method was already hooked or uses some hook protection or is just
			   bad programmed. EasyHook is capable of hooking the same method
			   many times simultanously. Even if other (unknown) hook libraries are hooking methods that
			   are already hooked by EasyHook. Only if EasyHook hooks methods that are already
			   hooked with other libraries there can be problems if the other libraries are not
			   capable of such a "bad" circumstance.
			*/

			*OutRelocSize = (ULONG)(pRes - Buffer);
		}
		else
		{
#ifndef DRIVER
            if(DbgIsEnabled())
            {
                // RIP relative detection
                DbgRelocateRIPRelative((ULONGLONG)pOld, (ULONGLONG)pRes, &IsRIPRelative);
            }
#endif
		}

		// find next instruction
		FORCE(InstrLen = LhGetInstructionLength(pOld));

		if(OpcodeLen == 0)
		{
			// just copy the instruction
			if(!IsRIPRelative)
				RtlCopyMemory(pRes, pOld, InstrLen);

			pRes += InstrLen;
		}

		pOld += InstrLen;
		IsRIPRelative = FALSE;
	}

	*OutRelocSize = (ULONG)(pRes - Buffer);

	RETURN(STATUS_SUCCESS);

THROW_OUTRO:
FINALLY_OUTRO:
    return NtStatus;
}

Coordinator
Jan 2, 2013 at 2:17 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Coordinator
Jan 13, 2013 at 2:05 AM

This has been implemented