- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我正在开发一个遗留产品,它可以通过注入(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/
我想了解 Ruby 方法 methods() 是如何工作的。 我尝试使用“ruby 方法”在 Google 上搜索,但这不是我需要的。 我也看过 ruby-doc.org,但我没有找到这种方法。
Test 方法 对指定的字符串执行一个正则表达式搜索,并返回一个 Boolean 值指示是否找到匹配的模式。 object.Test(string) 参数 object 必选项。总是一个
Replace 方法 替换在正则表达式查找中找到的文本。 object.Replace(string1, string2) 参数 object 必选项。总是一个 RegExp 对象的名称。
Raise 方法 生成运行时错误 object.Raise(number, source, description, helpfile, helpcontext) 参数 object 应为
Execute 方法 对指定的字符串执行正则表达式搜索。 object.Execute(string) 参数 object 必选项。总是一个 RegExp 对象的名称。 string
Clear 方法 清除 Err 对象的所有属性设置。 object.Clear object 应为 Err 对象的名称。 说明 在错误处理后,使用 Clear 显式地清除 Err 对象。此
CopyFile 方法 将一个或多个文件从某位置复制到另一位置。 object.CopyFile source, destination[, overwrite] 参数 object 必选
Copy 方法 将指定的文件或文件夹从某位置复制到另一位置。 object.Copy destination[, overwrite] 参数 object 必选项。应为 File 或 F
Close 方法 关闭打开的 TextStream 文件。 object.Close object 应为 TextStream 对象的名称。 说明 下面例子举例说明如何使用 Close 方
BuildPath 方法 向现有路径后添加名称。 object.BuildPath(path, name) 参数 object 必选项。应为 FileSystemObject 对象的名称
GetFolder 方法 返回与指定的路径中某文件夹相应的 Folder 对象。 object.GetFolder(folderspec) 参数 object 必选项。应为 FileSy
GetFileName 方法 返回指定路径(不是指定驱动器路径部分)的最后一个文件或文件夹。 object.GetFileName(pathspec) 参数 object 必选项。应为
GetFile 方法 返回与指定路径中某文件相应的 File 对象。 object.GetFile(filespec) 参数 object 必选项。应为 FileSystemObject
GetExtensionName 方法 返回字符串,该字符串包含路径最后一个组成部分的扩展名。 object.GetExtensionName(path) 参数 object 必选项。应
GetDriveName 方法 返回包含指定路径中驱动器名的字符串。 object.GetDriveName(path) 参数 object 必选项。应为 FileSystemObjec
GetDrive 方法 返回与指定的路径中驱动器相对应的 Drive 对象。 object.GetDrive drivespec 参数 object 必选项。应为 FileSystemO
GetBaseName 方法 返回字符串,其中包含文件的基本名 (不带扩展名), 或者提供的路径说明中的文件夹。 object.GetBaseName(path) 参数 object 必
GetAbsolutePathName 方法 从提供的指定路径中返回完整且含义明确的路径。 object.GetAbsolutePathName(pathspec) 参数 object
FolderExists 方法 如果指定的文件夹存在,则返回 True;否则返回 False。 object.FolderExists(folderspec) 参数 object 必选项
FileExists 方法 如果指定的文件存在返回 True;否则返回 False。 object.FileExists(filespec) 参数 object 必选项。应为 FileS
我是一名优秀的程序员,十分优秀!