Chris P. [MVP] wrote:
> I would be interesting in taking a look when you have
> time.
The idea to not use Detours is simple: do the same stuff
Detours does on my own.
In the very end, what Detours does is quite "easy": Detours
copies N bytes from the start of the function (F) to
somewhere else (G), then overwrites the first 5 bytes of F
with a jump instruction to your function (H) and last
appends to G a jump to &F[N]. The difficulty is only in the
choice of N: N must be >= 5, it must not go beyond the end
of F and must contain an integral number of instructions.
This last part requires the code of F to be disassembled and
it is not difficult but very long to write given the mess of
the IA-32 opcodes. Of course, you need to VirtualProtect()
both the segments of F and G so that you can write to them
and them VirtualProtect() them as they were before.
Since I didn't want to write a table of IA-32 opcodes by
hand, I decided that I would copy the whole body of F, that
is N = size_of_body(F). This would also make the appended
jump not needed. Of course, there is no way to know where F
ends without disassembling the code. But I could then copy
the whole code segment where F resides (practically the
whole DLL code chunk). This wastes some memory, but doesn't
require me to know anything about IA-32 opcodes (but the
jump opcode I have to write). The idea is that either F lies
wholly inside this segment, or it has to perform an absolute
jump somewhere else sooner or later, and this jump would
still work to the original code I didn't copy. Since I copy
the whole segment, this works even if F jump backwards in
the segment.
I had to make an assumption that F contains at least 5
bytes, but this quite a safe assumptions (Detours actually
checks) given that shorter-than-5-bytes instructions are not
unconditional jumps and thus there must be other
instructions that follow.
So, let's say I want to detour F with my function H and get
a pointer to the original function in G, I do the following
(no error-checking or resource releasing):
VOID AxlDetourFunction(
LPVOID F, /// function to detour
LPVOID H, /// my new function
LPVOID* pG) /// old function
{
LPVOID pv;
DWORD dw, ul;
MEMORY_BASIC_INFORMATION mbi;
/// find out about F's segment
VirtualQuery(F,&mbi,sizeof(mbi));
/// allocate a new writable segment
pv = VirtualAlloc(NULL,mbi.RegionSize,
MEM_COMMIT,PAGE_READWRITE);
/// copy F's segment to the new one
MoveMemory(pv,mbi.BaseAddress,mbi.RegionSize);
/// make the new segment read/exec-only
VirtualProtect(pv,mbi.RegionSize,
PAGE_EXECUTE_READ,&dw);
/// the old function starts at the same offset
/// in the new segment where it started in the old one
*pG = (LPBYTE)pv + ((LPBYTE)F - (LPBYTE)mbi.BaseAddress);
/// make F's segment writable
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,
PAGE_EXECUTE_READWRITE,&dw);
/// write the jump opcode
((LPBYTE)F)[0] = (BYTE)0xE9;
/// calculate the jump offset
ul = (LPBYTE)H - &((LPBYTE)F)[6];
/// write the jump offset (since there is no guarantee
/// that the offset's DWORD is DWORD aligned, this is
/// safer even if not really needed on modern CPUs)
MoveMemory(((LPBYTE)F)[1],&ul,sizeof(ul));
/// protected F's segment as it was before
VirtualProtect(mbi.BaseAddress,
mbi.RegionSize,dw,&dw);
}
--
// Alessandro Angeli
// MVP :: Digital Media
// a dot angeli at psynet dot net