WechatBakTool icon indicating copy to clipboard operation
WechatBakTool copied to clipboard

提升搜索速度的建议

Open bigsinger opened this issue 1 year ago • 6 comments

对代码做了一些优化,目前测试提高了近50倍(另外使用了缓存以及缩短了搜索范围),贴一下,作为参考:

/// <summary>
/// 内存过滤规则,例如:(state == MEM_COMMIT && (protect == PAGE_EXECUTE || protect == PAGE_EXECUTE_READ || protect == PAGE_EXECUTE_READ || protect == PAGE_READWRITE || protect == PAGE_READONLY))
/// </summary>
/// <param name="state"></param>
/// <param name="protect"></param>
/// <returns>返回值1:该内存页是否需要扫描;返回值2:是否使用内部默认扫描函数,true表示采用默认</returns>
public delegate (bool, bool) CustomMemoryFilter(uint state, uint protect);

/// <summary>
/// 自定义的字节序列数据搜索函数
/// </summary>
/// <param name="data"></param>
/// <param name="bytesToFind"></param>
/// <returns>相对偏移,负数表示未搜索到,否则是匹配的相对偏移量</returns>
public delegate int CustomSearcher(byte[] data, byte[] bytesToFind);




/// <summary>
/// 在进程所有内存空间搜索字节数组
/// </summary>
/// <param name="handle">进程句柄</param>
/// <param name="searchBytes">待搜索的字节数组</param>
/// <param name="customMemoryFilter">过滤内页面属性的条件</param>
/// <param name="customSearch">自定义搜索函数</param>
/// <returns></returns>
public static List<long> SearchProcessAllMemory(IntPtr handle, byte[] searchBytes, CustomMemoryFilter? customMemoryFilter, CustomSearcher? customSearch) {
    List<long> addrList = new();
    IntPtr minAddress = IntPtr.Zero;
    IntPtr maxAddress = IntPtr.MaxValue;
    int pos = 0;
    bool shouldScan = false;
    bool useDefaultScan = true;

    while (minAddress < maxAddress) {
        MEMORY_BASIC_INFORMATION64 memInfo;
        int result = VirtualQueryEx(handle, minAddress, out memInfo, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION64)));

        if (result == 0) { break; }

        if (customMemoryFilter != null) {
            (shouldScan, useDefaultScan) = customMemoryFilter(memInfo.State, memInfo.Protect);
        } else {
            shouldScan = (memInfo.State == MEM_COMMIT && (memInfo.Protect == PAGE_EXECUTE || memInfo.Protect == PAGE_EXECUTE_READ || memInfo.Protect == PAGE_EXECUTE_READ || memInfo.Protect == PAGE_READWRITE || memInfo.Protect == PAGE_READONLY));
        }

        if (shouldScan) {
            byte[] buffer = new byte[(long)memInfo.RegionSize];
            if (ReadProcessMemory(handle, memInfo.BaseAddress, buffer, buffer.Length, out _)) {
                pos = -1;
                if (useDefaultScan) {
                    pos = SearchBytes(buffer, searchBytes);
                } else if (customSearch != null) {
                    pos = customSearch(buffer, searchBytes);
                }

                if (pos >= 0) { addrList.Add(memInfo.BaseAddress + pos); }
            }
        }

        minAddress = memInfo.BaseAddress + (nint)memInfo.RegionSize;
    }

    return addrList;
}

/// <summary>
/// 在内存数据中查找字节序列,注意:搜索方法不回溯且每次递进8字节
/// </summary>
/// <param name="data"></param>
/// <param name="bytesToFind"></param>
/// <returns></returns>
private static int SearchBytes(byte[] data, byte[] bytesToFind) {
    for (int i = 0; i < data.Length - bytesToFind.Length; i += sizeof(long)) {
        for (int j = 0; j < bytesToFind.Length;) {
            if (data[i + j] != bytesToFind[j]) {
                break;
            } else {
                j++;
                if (j == bytesToFind.Length) {
                    return i;
                }
            }
        }
    }

    return -1;
}

外部使用时:

// 在进程的所有地址空间里搜索。讨巧:该字符串特征为0x10对齐,且内存页面属性为可读写,可以提高搜索速度。
byte[] searchBytes = Encoding.UTF8.GetBytes("-----BEGIN PUBLIC KEY-----");
var listAddr = NativeAPIHelper.SearchProcessAllMemory(handle, searchBytes,
        (state, protect) => ((state == NativeAPI.MEM_COMMIT && protect == NativeAPI.PAGE_READWRITE), false),
        (data, search) => HexPatternMatcherKMP.SearchBytes(data, search, 0x10)
    );

// 对所有地址在指定某块内里搜索
List<int> listTargetAddr = new();
foreach (var address in listAddr) {
    matchedOffset = HexPatternMatcherKMP.SearchNumber(session.CacheBuffer, address);
    if (matchedOffset >= 0) { listTargetAddr.Add(matchedOffset); }
}

// 取较大的那个,该地址只比手机型号地址大0x28
matchedOffset = listTargetAddr.Max();

bigsinger avatar Mar 01 '24 09:03 bigsinger

这位师傅厉害,回头我学习下看看合并进来!感谢!

SuxueCode avatar Mar 01 '24 09:03 SuxueCode

@SuxueCode https://github.com/xaoyaoo/PyWxDump 这个里面 还有一个把手机型号作为搜索特征的方法。 综合起来,相当于有三个:

  1. 搜索注册ID
  2. 搜索手机型号
  3. 搜索公钥头信息(虽然慢,但是思路挺好,至少也是一个有效特征,而且有点意思)

测试下来前两个最快,基本上在1ms以内就完成了。

bigsinger avatar Mar 01 '24 09:03 bigsinger

123点方法现在已经全部失效了 我软件内也写有基于用户名和公钥头的方法,2和3出现的时间差不多,但是3的效果比较好,最后还是移植了3过来 根据之前的体验,公钥头的速度C#跑起来其实并不慢,主要还是搜索内存这块实现起来比较麻烦一些,毕竟对底层不熟悉

SuxueCode avatar Mar 04 '24 02:03 SuxueCode

123点方法现在已经全部失效了 我软件内也写有基于用户名和公钥头的方法,2和3出现的时间差不多,但是3的效果比较好,最后还是移植了3过来 根据之前的体验,公钥头的速度C#跑起来其实并不慢,主要还是搜索内存这块实现起来比较麻烦一些,毕竟对底层不熟悉

亲测3.9.9.43版本三个方法都有效。

bigsinger avatar Mar 04 '24 03:03 bigsinger

囧,跑了一下还真是,之前有一段时间接到批量反馈说不行了 但是1/2点的成功率实际环境下,没有3高 因为用户名基于地址去获取本质上还是要维护地址,2的话实际环境下,机型干扰可能会比较大 公钥头这个是目前观察到成功率最高的算法了

SuxueCode avatar Mar 04 '24 03:03 SuxueCode

囧,跑了一下还真是,之前有一段时间接到批量反馈说不行了 但是1/2点的成功率实际环境下,没有3高 因为用户名基于地址去获取本质上还是要维护地址,2的话实际环境下,机型干扰可能会比较大 公钥头这个是目前观察到成功率最高的算法了

不用刻意去维护,减少搜索时间的技巧是缩小内存范围,可以找一些大概的特征去限定下,速度就上来了。

bigsinger avatar Mar 04 '24 03:03 bigsinger