gpt4 book ai didi

c++ - 如何拦截64位进程中的API方法调用?

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:45:22 31 4
gpt4 key购买 nike

背景

我正在开发一个遗留产品,它可以通过注入(inject) dll 成功拦截被注入(inject)进程试图进入任意 dll 的任意方法调用。特别是 gdi32.dll 库。不幸的是,当它嵌入到 64 位应用程序中时它不起作用。它成为一个热门话题,是时候升级它的功能了。同样不幸的是,源代码中没有评论(典型的 >:-<),而且从它的外观来看,写这篇文章的人对 x86 指令集相当熟悉。我已经很多年没有从事装配工作了,而当我从事装配工作时,是摩托罗拉装配。

在搜索互联网后,我发现了 this英特尔员工的文章。如果我们的源代码没有比这篇文章早大约 7 年,我会说这正是我们的 NoComments 开发人员学会执行 API 方法拦截的地方。这就是程序的相似之处。这篇文章也总结在一个很好的 pdf ( Intercepting System API calls ) 中,也可以从上述网站找到链接。

问题

我想真正理解英特尔网页链接中提供的示例,以便我可以很好地破解为 64 位场景创建解决方案的问题。它有据可查,对我来说更容易理解。以下是 InterceptAPI() 例程的摘录。我添加了我自己的评论,用“//#” 表示(原始评论用标准“//”标注),我在其中解释了我认为我知道的以及我不知道的:

BOOL InterceptAPI(HMODULE hLocalModule, const char* c_szDllName,
const char* c_szApiName, DWORD dwReplaced, DWORD dwTrampoline, int offset)
{
//# Just a foreword. One of the bigger mysteries of this routine to me is
//# this magical number 5 and the offset variable. Now I'm assuming, that
//# there are 5 bytes at the beginning of every method that are basically
//# there to set up some sort of pre-method-jump context switch, since its
//# about to leave the current method and jump to another. So I'm guessing
//# that for all scenarios, the minimum number of bytes is 5, but for some
//# there may be more than 5 bytes so that's what the "offset" variable is
//# for. In the aforementioned article, the author writes "One additional
//# complication exists, in that the sixth byte of the original code may be
//# part of the previous instruction. In that case, the function overwrites
//# part of the previous instruction and then crashes." So some method
//# starting code contains multi-byte opcodes while others don't apparently.
//# And if you don't know the instruction set well enough, I'm guessing
//# you'll just have to figure it out by trial and error.
int i;
DWORD dwOldProtect;

//# Fetching the address of the method that we want to capture and reroute
//# Example: c_szDllName="user32", c_szApiName="SelectObject"
DWORD dwAddressToIntercept = (DWORD)GetProcAddress(
GetModuleHandle((char*)c_szDllName), (char*)c_szApiName);


//# Storing address of method we are about to intercept in another variable
BYTE *pbTargetCode = (BYTE *) dwAddressToIntercept;

//# Storing address of method we are going to use to take the place of the
//# intercepted method in another variable.
BYTE *pbReplaced = (BYTE *) dwReplaced;

//# "Trampoline" appears to be a "Microsoft Detours" term, but its basically
//# a pointer so that we can get to the original "implementation" of the method
//# we are intercepting. Most of the time your replacement function will
//# want to call the original function so this is pretty important. What its
//# pointing to must already be pre allocated by the caller. The author of
//# the aforementioned article states "Prepare a dummy function that has the
//# same declaration that will be used as the trampoline. Make sure the dummy
//# function is more than 10 bytes long." I believe I'd prefer allocating this
//# memory within this function itself just to make using this InterceptAPI()
//# method easier, but this is the implementation as it stands.
BYTE *pbTrampoline = (BYTE *) dwTrampoline;


// Change the protection of the trampoline region
// so that we can overwrite the first 5 + offset bytes.
//# This is voodoo magic to me, but I'm guessing you just can't hop on the
//# stack and start changing execute instructions without ringing some
//# alarms, so this makes sure the alarms don't ring. Here we are allowing
//# permissions so we can change the bytes at the beginning of our
//# trampoline method.
VirtualProtect((void *) dwTrampoline, 5+offset, PAGE_WRITECOPY, &dwOldProtect);

//# More voodoo magic to me, but this appears to be a way to copy over extra
//# opcodes that may be needed. Some opcodes are multi byte I believe so this
//# is where you can make sure you don't miss them.
for (i=0;i<offset;i++)
*pbTrampoline++ = *pbTargetCode++;

//# Resetting the pbTargetCode pointer since it was modified it in the above
//# for loop.
pbTargetCode = (BYTE *) dwAddressToIntercept;


// Insert unconditional jump in the trampoline.
//# This is pretty understandable. 0xE9 the x86 JMP command. I looked
//# this up in Intel's documentation and it can be followed by a 16-bit
//# offset or a 32-bit offset. The 16-bit version is not supported in 64-bit
//# architecture but lets just hope they are all 32-bit and that this does
//# indeed do what it is intended in 64-bit scenarios
*pbTrampoline++ = 0xE9; // jump rel32

//# So basically here it looks like we are following up our jump command with
//# the address its supposed to jump too. This is a relative offset, that's why
//# we are subtracting pbTargetCode and pbTrampoline. Also, since JMP opcodes
//# jump relative to the address AFTER the jump address, that's why we are
//# adding 4 to pbTrampoline. Also, offset is added to pbTargetCode because we
//# advanced the pointers in the for loop above an "offset" number of bytes.
*((signed int *)(pbTrampoline)) = (pbTargetCode+offset) - (pbTrampoline + 4);

//# Not quite sure why we are changing the permissions on the trampoline function
//# again, but looks like we are making it executable here. Maybe this is the
//# last thing we have to do before it is actually callable and usable.
VirtualProtect((void *) dwTrampoline, 5+offset, PAGE_EXECUTE, &dwOldProtect);


// Overwrite the first 5 bytes of the target function
//# It seems we are now setting permissions so we can modify the original
//# intercepted routine. It is still pointing to its original code so we
//# need to eventually redirect it.
VirtualProtect((void *) dwAddressToIntercept, 5, PAGE_WRITECOPY, &dwOldProtect);

//# This will now instruct the original method to instead jump to the next
//# address it sees on the stack.
*pbTargetCode++ = 0xE9; // jump rel32

//# this is the address we want our original intercepted method to jump to.
//# Where its jumping to will have the code of our replacement method.
//# The "+ 4" is because the jump occurs relative to the address of the
//# NEXT instruction after the 4byte address.
*((signed int *)(pbTargetCode)) = pbReplaced - (pbTargetCode +4);

//# Changing the permissions of our original intercepted routine back to execute
//# permissions so it can be called by other methods.
VirtualProtect((void *) dwAddressToIntercept, 5, PAGE_EXECUTE, &dwOldProtect);


// Flush the instruction cache to make sure
// the modified code is executed.
//# I guess this is just to make sure that if any instructions from the old
//# state of the methods we changed, have wound up in cache, that it gets
//# purged out of there before it gets used.
FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

return TRUE;
}

我想我对这段代码中发生的事情有很好的理解。所以百万美元的问题是:这对 64 位进程不起作用怎么办?我的第一个想法是,“哦,好吧,地址现在应该是 8 个字节,所以这一定是错的。 “但我认为 JMP 命令仍然只需要一个相对的 32 位地址,因此即使在 64 位进程中使用 32 位地址,操作代码也应该仍然有效。除此之外,我唯一相信的可能是我们在方法调用开头的神奇的 5 个字节实际上是其他一些神奇的数字。有人有更好的见解吗?

注意:我知道还有一些其他解决方案,例如“Microsoft Detours”和“EasyHook”。前者太贵了,我目前正在探索后者,但到目前为止令人失望。所以,我想专门针对这个话题进行讨论。我发现它很有趣,也是解决我问题的最佳方法。所以请不要说“嘿,我对这篇文章一无所知,但请尝试{在此处插入第 3 方解决方案}。”

最佳答案

由于建议的代码看起来是针对 Microsoft 平台的目标,我建议您只使用 Detours .使用 Detours,您的蹦床将在 32 位和 64 位系统上运行。

关于c++ - 如何拦截64位进程中的API方法调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10838320/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com