模仿Cheat Engine內存搜索——(Sunday算法)


想要獲取一個進程里面的某個數據,需要先知道這個數據的位置
  對於全局變量:偏移是固定的,可以通過“基址+偏移”直接定位
  對於局部變量:位置是隨機的,只能通過攔截或者搜索去定位

  分析企業微信的時候,發現很多數據都不是全局變量,不能像個人微信那樣直接跨進程讀取。如果能夠用代碼模仿Cheat Engine進行內存搜索,就可以定位到局部變量,進而實現非注入讀取。甚至可以做到兼容所有版本(比如登錄二維碼,直接內存掃描出來)。
  
  Cheat Engine是開源的,下面是特征碼搜索的源碼:

DWORD AOBScan(HANDLE hProcess, const char* pattern, const char* mask, uint64_t start, uint64_t end, int inc, int protection,uint64_t * match_addr)
{
    // 查詢內存塊信息,看下是不是可讀可寫可執行
    VirtualQueryEx(hProcess, (void*)tmp, &rinfo, NULL);
    if((rinfo.protection & protection) != 0) ...

    // 讀取內存,用暴力算法BF(Brute Force)算法進行字符匹配
    ReadProcessMemory(hProcess, (void*)tmp2, MemoryBuff, 4096)
    for (int i = 0; i < 4096; i += inc)
        for (int k = 0; k < patternLength; k++)
            if (!(mask[k] == '?' || pattern[k] == (MemoryBuff[k])))
}

  可以看出Cheat Engine內存搜索的核心邏輯是
    1、查看內存塊信息:VirtualQueryEx
    2、跨進程讀取內存:ReadProcessMemory
    3、通過“字符串模式匹配算法”進行比較定位

  
  “字符串模式匹配算法”中比較好的算法是“Sunday算法”,代碼如下:

int SundaySearch(const byte* target, int tLen, const byte* pattern, int pLen)
{
    const int SHIFT_SIZE = 0x100;
    byte shift[SHIFT_SIZE] = { 0 };

    memset(shift, pLen + 1, SHIFT_SIZE);
    for (int i = 0; i < pLen; i++) shift[pattern[i]] = pLen - i;

    for (int i = 0; i < tLen - pLen; i += shift[target[i + pLen]])
    {
        for (int j = 0; j < pLen; j++)
        {
            if (target[i + j] != pattern[j]) break;
            if (j == pLen - 1) return i;
        }
    }

    return -1;
}

  
  再結合VirtualQueryEx和ReadProcessMemory就可以模仿Cheat Engine 內存搜索,代碼如下:

int ScanPattern(HANDLE hProcess, byte* pattern, int pLen)
{
    const int MEM_SIZE = 0x1000;
    byte mem[MEM_SIZE] = { 0 };

    MEMORY_BASIC_INFORMATION mbi;
    for (int curAddress = 0x0400000; curAddress < 0x3FFFFFFF; curAddress += mbi.RegionSize)
    {
        VirtualQueryEx(hProcess, (void*)curAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
        if ((int)mbi.RegionSize <= 0) break;
        if (mbi.State != MEM_COMMIT) continue;
        //if (mbi.Protect != PAGE_READONLY && mbi.Protect != PAGE_READWRITE) continue;

        for (int memIndex = 0; memIndex < (int)mbi.RegionSize / MEM_SIZE; memIndex++)
        {
            int beginAddress = curAddress + memIndex * MEM_SIZE;
            ReadProcessMemory(hProcess, (void*)(beginAddress), mem, MEM_SIZE, 0);
            int offset = SundaySearch(mem, MEM_SIZE, pattern, pLen);
            if (offset >= 0) return beginAddress + offset;
        }
    }

    return -1;
}

  
  調用實例

int main()
{
    DWORD dwPid = 0x28F4;
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, dwPid);

    int address = 0x14EE4020;
    byte* pattern = (byte*)&address;
    int pLen = 4;

    int result = ScanPattern(hProcess, pattern, pLen);
    std::cout << std::hex  << result << "\n";
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM