Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

提升搜索速度的建议 #44

Open
bigsinger opened this issue Mar 1, 2024 · 6 comments
Open

提升搜索速度的建议 #44

bigsinger opened this issue Mar 1, 2024 · 6 comments
Labels
enhancement New feature or request

Comments

@bigsinger
Copy link

对代码做了一些优化,目前测试提高了近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();
@SuxueCode
Copy link
Owner

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

@SuxueCode SuxueCode added the enhancement New feature or request label Mar 1, 2024
@bigsinger
Copy link
Author

bigsinger commented Mar 1, 2024

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

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

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

@SuxueCode
Copy link
Owner

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

@bigsinger
Copy link
Author

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

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

@SuxueCode
Copy link
Owner

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

@bigsinger
Copy link
Author

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants