我最近开始学习逆向工程,我突然遇到了签名扫描。
DWORD FindPattern(char *szPattern, char *szMask)
{
// Get the current process information
MODULEINFO mInfo = {0};
GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &mInfo, sizeof(MODULEINFO));
// Find the base address
DWORD dwBase = (DWORD)mInfo.lpBaseOfDll;
DWORD dwSize = (DWORD)mInfo.SizeOfImage;
// Get the pattern length
DWORD dwPatternLength = (DWORD)strlen(szMask);
// Loop through all the process
for(DWORD i = 0; i < dwSize - dwPatternLength; i++)
{
bool bFound = true;
// Loop through the pattern caracters
for (DWORD j = 0; j < dwPatternLength; j++)
bFound &= szMask[j] == '?' || szPattern[j] == *(char*)(dwBase + i + j);
// If found return the current address
if(bFound)
return dwBase + i;
}
// Return null
return NULL;
}
我想知道:
答案 0 :(得分:1)
首先,看一下忘记所有模块内容的代码。如果你仔细观察,你会发现嵌套的for循环基本上是一个(相当低效的)子串搜索。它将给定的szPattern作为特定内存区域中的子字符串进行搜索。 szMask是模式的扩展,因此您可以使用&#34;通配符&#34;进行搜索。
GetModuleInformation
部分只获取主可执行模块的内存区域,因此您可以在可执行文件中搜索二进制字符串。 如果将它编译到程序中,这很有趣,FindPattern
调用很可能在二进制文件中找到自己的常量
那么这个东西用的是什么?您可以通过机器代码搜索已知函数的各个部分,因此您不必硬编码函数库地址。您甚至可以使用通配符来屏蔽变化的部分,如具体的变量或函数引用地址。
答案 1 :(得分:1)
我认为这个问题值得一个实际的解释。
签名扫描的目的是在内存中定位一系列指令或数据,这些指令或数据可能会在修补软件时改变位置。每当您修改的软件发生变化时,对这些值进行硬编码并不断推出新版软件是不切实际的。
uint8_t mask [] = { 0xff, 0xff, 0xff, // cmp [ebx+28h], eax
0, 0, // jz short <...>
0xff, 0xff, 0xff, // cmp eax, 2
0, 0, // jl short <...>
0xff, 0, 0, 0, 0, // mov <tickaddr>, eax
0xff, 0, 0, 0, 0, // mov <tickaddr2>, eax
0xff, 0xff, 0xff // mov [ebx+28h], eax
};
uint8_t sig [] = { 0x39, 0x43, 0x28, // cmp [ebx+28h], eax
0x74, 0x12, // jz short <...>
0x83, 0xF8, 0x02, // cmp eax, 2
0x7C, 0x0D, // jl short <...>
0xA3, 0x64, 0xB4, 0x17, 0x02, // mov <tickaddr>, eax
0xA3, 0x68, 0xB4, 0x17, 0x02, // mov <tickaddr2>, eax
0x89, 0x43, 0x28 // mov [ebx+28h], eax
};
if (*((DWORD *)config.framerate.speedresetcode2_addr) != 0x0F8831274) {
uintptr_t addr = (uintptr_t)TZF_Scan (sig, 23, mask);
if (addr != NULL) {
config.framerate.speedresetcode2_addr = addr + 3;
dll_log.Log (L"Scanned SpeedResetCode2 Address: %06Xh", addr + 3);
TICK_ADDR_BASE = *(DWORD *)((uint8_t *)(addr + 11));
dll_log.Log (L" >> TICK_ADDR_BASE: %06Xh", TICK_ADDR_BASE);
}
else {
dll_log.Log (L" >> ERROR: Unable to find SpeedResetCode2 memory!");
}
}
在这里,我已确定有问题的代码遵循某种模式,并且开发掩码以忽略经常更改的内存地址和跳转标签。我 DO 想要那些内存地址和跳转标签,但就功能签名而言,它们必须被忽略,因为它们不是常量。我对待这些&#34; DONTCARE&#34;掩码中使用 0x00 的字节在模式匹配期间忽略它们。
此外,我实际上已将地址写入配置文件。当存储的地址不再与预期值( 0x0F8831274 )匹配时,扫描仅在启动时发生。这是因为签名扫描可能是一个效率低下的过程 - 每个程序执行一次或两次就可以了,但我的软件必须找到并修补多个功能。因此缓存定位的地址是一个重要的优化策略。
原始问题中扫描仪的实施存在缺陷,需要解决。在实际软件中,您将遇到未提交和/或无法访问的进程内存区域。如果你天真地扫描整个图像的内存空间,很可能会产生访问冲突异常并导致你试图修改的程序崩溃。
调用VirtualQuery (...)
是必要的,以确保避免此问题。我将指出implementation of TZF_Scan (...)
来说明如何使用它。