如何加快我的内存扫描程序?

时间:2016-02-22 03:10:41

标签: c++ windows algorithm memory assembly

我目前正在编写一个内存扫描程序,用于扫描另一个进程中的AOB。 aob包含通配符,由一个看起来像39 35 ?? ?? ?? ?? 75 10 6A 01 E8

的字符串表示

这是我到目前为止所拥有的:

  1. 我只需扫描与特定保护常数匹配的记忆区域。例如PAGE_READWRITE。
  2. 但是,由于我必须扫描大范围的内存,所以不可能将整个部分读入我的地址空间一次。我必须用缓冲区来做;每次我读入一个块并处理那个小块。在我的程序中,我持有一个currentAddress变量,它存储了我正在查看的地址。
  3. #2中的方法问题在于,aob可能位于两个块之间。我解决这个问题的方法是:每当搜索因为缓冲区的结束而结束但到目前为止字节匹配时,请采取N步骤。(其中N是匹配的字节数。)
  4. 我的算法采取天真的方式;它暴力强迫问题并搜索所有可能的位置。代码如下:

    char *haystack = .....
    short *needle = .... //"39 35 ?? ?? ?? ?? 75 10 6A 01 E8"
    outer:for(int i = 0; i < lengthOfHayStack - lengthOfNeedle; i ++)
    {
        for(int j = 0; j < lengthOfNeedle; j ++)
        {
            if(buffer[i+j] != needle[j] && needle[j] != WILDCARD)
                 continue outer;
        }
        //found one?
    }
    
  5. 这是算法明智的。实施明智,我首先使用repne scasb来查找大海捞针的第一个字节。此过程由内联汇编完成。找到索引后,我使用c代码来比较其余部分,因为我需要处理外卡。

  6. 我的Memory Scanner的性能还可以,但我仍然希望改进它。有哪些方法,无论是算法方式还是实现方式,都可以加速我的内存扫描器?

    PS:AOB的模块未知。因此,我必须扫描整个内存区域。

3 个答案:

答案 0 :(得分:3)

1)此处的其他答案建议建立DFA,即线性时间。 您可以构建Knuth-Morris-Pratt search,并在许多情况下实现次线性次。它跳过可以包含模式的内存块,基于它在跳过的块之前已经看到的位。如果你想要这么快,我想你会发现核心算法必须用汇编语言编码。

2)我不想从目标进程空间中读取块(需要通过内核进行复制),而是试图将虚拟页面从目标空间映射到搜索器的空间。你可以使这些页面相当大(16Mb?),这可以分摊映射成本;复制成本为零。

答案 1 :(得分:2)

理论答案

将搜索模式视为正则表达式,并将其转换为Deterministic Finite AutomatonDFA。除了这个维基百科条目,你应该找到大量的谷歌食物来调查。

基本上,搜索模式会转换为状态机。状态机的输入是来自您正在搜索的存储器的字节流,自动机的最终状态是遇到搜索模式后达到的状态。

在数学上不可能提出逻辑上更快的算法,因为状态机的输入只是对内存范围的线性扫描,而不是当前代码中的嵌套循环方法。搜索复杂度应为O(n),与搜索的内存大小成线性关系。在这里,不要认为它在理论上可以实现更好的复杂性。

正则表达式基本上是nondeterministic finite automatonNFA(如本文引用的维基百科条目所示),它使用最方便的算法转换为确定性有限自动机。然后,要扫描的内存范围成为DFA状态机的输入,一旦达到DFA的最终状态,就会找到该模式。

实际答案

std::regex_search使用一对双向迭代器来定义使用正则表达式搜索的序列。

定义并实现一个迭代器类,该类满足双向迭代器的要求,并迭代您要搜索的内存区域。将搜索模式转换为std::regex,并使用std::regex_search进行搜索。

通过正则表达式库的正式定义的简短扫描似乎并不表示std::regex_search保证某种类型的最大复杂性(我可能在这里错了,我没有执行穷尽搜索整个图书馆规范);此外,它需要双向迭代器,而不是输入或转发迭代器,这表明实现可能不如沼泽标准DFA那么高效,但实际上,可能需要最少量的工作,以获得相当快的结果。

答案 2 :(得分:0)

repne scasb isn't faster than a plain byte-at-a-time loop, unfortunately.

使用向量指令扫描起始字节会好得多:

使用pcmpeqb一次检查整个向量以找到匹配的起始字节。使用匹配的位位置作为偏移量来加载完整匹配候选项。 (未对齐的加载比尝试执行数据相关的移位或随机播放更容易,因为palignr仅在立即计数时可用。索引pshufb shuffle表掩码是可能的,但没有帮助,因为无论如何你需要加载更多。

# load your search pattern into xmm4
#broadcast the first byte to every byte of xmm5
# then
.loop:
    ...
    vpcmpeqb   xmm0, xmm5, [rsi]
    vpmovmskb  ecx, xmm0
    test       ecx,ecx
    jnz    .found_a_0x39_byte
.resume_search:
    add        rsi, 16
    cmp        rsi, rdi  # end pointer
    jb     .loop
...
    .found_a_0x39_byte
    bsf        edx, ecx
    vpcmpeqb   xmm0, xmm4, [rsi+rdx]    ; check against the full pattern (unaligned load, use movdqu if implementing without avx)
    vpmovmskb  eax, xmm0

    ; eax has a one bit for every matching byte
    ; "39 35 ?? ?? ?? ?? 75 10 6A 01 E8"
    ;0b 1  1  0  0  0  0  1  1  1  1  1   reversed because little endian
    not        eax                 ; 0 bits are matching bytes
    test       eax, 0b11111000011  ; check that all bits we care about are zero
    jnz .try_again_with_next_set_bit_in_ecx  ; TODO implement this loop
    # .found_match:
    add        rdx, rsi    ; pointer to the start of the match

您需要在ecx中循环设置位位置,以检查所有候选起点。或者可以通过检查模式的第二个字节进行优化,将该位掩码左移一个,并将其与第一个位掩码进行AND运算。然后你只有一个掩码,其中只有0x39后跟一个0x35的位置。

循环设置位:BMI1&#39; s BLSR将清除源中的最低设置位,如果结果为零,则设置ZF。它可能会有所帮助。 (如果源头为零,它也会设置CF,但这里没有用处)。如果您无法使用BMI1,there are other ways to clear the lowest bit

注意bsf如果输入为零,则设置ZF,即使在这种情况下输出寄存器未定义。 (在这种情况下,使用BMI1&#39; tzcnt获得3264的保证结果。来自C的更多功能(函数无法返回值)和布尔值),但并不总是asm的改进。)

你可能很容易对内存带宽产生瓶颈,所以可能会做类似

的事情
vpcmpeqw    xmm0, xmm5, [rsi]
vpcmpeqw    xmm1, xmm5, [rsi+1]

当你找到候选的两个字节序列时,才会突破主搜索循环。但是,这将导致Sandybridge的L1中的缓存库冲突。它只能从128B块(2个缓存线)的相同1/8的每个时钟服务一个负载。英特尔Haswell以及后来没有缓存库冲突。理论上,SnB 可以通过仅使用对齐的加载来获胜,并使用palignr来获得第二次检查的未对齐加载。这可能是问题。在SnB之前很好,只有一个加载端口,并且你也希望使用数据进行对齐检查。

为了利用库函数进行繁重的工作,GNU libc提供了memmem。它与strstr类似,但采用显式大小而不是对以null结尾的字符串进行操作。你是在Windows上,但也许有类似的功能,它具有矢量优化的实现。在75 10 6A 01 E8序列上使用它来查找潜在的最终候选者。

在块之间的边界处,可能只是做一些手动一次一字节检查?或者使用palignr以两种可能的方式将一个块的最后16B与下一个块的前16B组合在一起?

如果从块的末尾开始小于11B的0x39,可能只会执行palignr吗?