签名扫描说明

时间:2015-12-13 06:12:42

标签: c++ reverse-engineering

我最近开始学习逆向工程,我突然遇到了签名扫描。

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;
} 

我想知道:

  1. 这个功能到底是做什么的?
  2. 应该在哪里使用?
  3. 这个功能/方法的工作原理是什么?
  4. 我们将什么以及为什么传递给“char * szPattern”?

2 个答案:

答案 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 (...)来说明如何使用它。