5.1 內存CRC32完整性檢測

CRC校驗技術是用於檢測數據傳輸或存儲過程中是否出現了錯誤的一種方法,校驗算法可以通過計算應用與數據的循環冗餘校驗(CRC)檢驗值來檢測任何數據損壞。通過運用本校驗技術我們可以實現對特定內存區域以及磁盤文件進行完整性檢測,並以此來判定特定程序內存是否發生了變化,如果發生變化則拒絕執行,通過此種方法來保護內存或磁盤文件不會被非法篡改。總之,內存和磁盤中的校驗技術都是用於確保數據和程序的完整性和安全性的重要技術。

內存CRC32特徵檢測通常用於防止軟件破解或打補丁,內存特徵碼檢查實現原理是通過定位到.text節表的首地址及該節的長度,然後計算該節的CRC32值並存入全局變量,通過在程序內部打開一個子線程用於實時監測內存,一旦發現CRC32值發生了變化,則可執行終止程序運行等操作,以此來實現防止破解或打補丁的目的。

我們來看這樣一段代碼,程序通過GetModuleHandle(NULL)函數獲取到自身程序的句柄,並通過PE結構定位到.text節,取出該節內的VirtualAddress虛擬地址,以及VirtualSize虛擬長度,最後調用CRC32((BYTE*)(va_base), sec_len)獲取到該節的CRC數據。

// 檢查內存中CRC32特徵值
DWORD CalculateMemoryCRC32()
{
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNtHeader = NULL;
    PIMAGE_SECTION_HEADER pSecHeader = NULL;
    DWORD ImageBase;

    // 獲取基地址
    ImageBase = (DWORD)GetModuleHandle(NULL);

    // 定位到PE頭結構
    pDosHeader = (PIMAGE_DOS_HEADER)ImageBase;
    pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew);

    // 定位第一個區塊地址,因爲默認的話第一個就是.text節
    pSecHeader = IMAGE_FIRST_SECTION(pNtHeader);
    DWORD va_base = ImageBase + pSecHeader->VirtualAddress;   // 定位代碼節va基地址
    DWORD sec_len = pSecHeader->Misc.VirtualSize;             // 獲取代碼節長度
    printf("鏡像基址(.text): %x | 鏡像大小: %d \n", va_base, sec_len);

    DWORD CheckCRC32 = CRC32((BYTE*)(va_base), sec_len);
    printf(".text節CRC32 = %x \n", CheckCRC32);

    return CheckCRC32;
}

當主程序執行時,我們首先通過CalculateMemoryCRC32函數獲取到當前代碼段的校驗碼,並存儲到OriginalCRC32全局變量內,在循環體內通過不斷的計算CRC數據並與全局初始值做對比,以此來實現防止破解的作用。

int main(int argc, char *argv[])
{
    // 用於保存初始化時 .text 節中的CRC32值
    DWORD OriginalCRC32 = 0;

    // 初始化時,給全局變量賦值,記錄下初始的CRC32值
    OriginalCRC32 = CalculateMemoryCRC32();

    while (1)
    {
        // 每隔3秒計算一次
        Sleep(3000);

        // 計算新的CRC
        DWORD NewCRC32 = CalculateMemoryCRC32();
        if (OriginalCRC32 == NewCRC32)
        {
            printf("[+] 當前CRC [ %x ] 程序沒有被打補丁 \n",NewCRC32);
        }
        else
        {
            printf("[-] 當前CRC [ %x ] 已被打補丁 \n", NewCRC32);
        }
    }

    system("pause");
    return 0;
}

編譯並運行上述程序片段,當讀者使用x64dbg修改內存中的字節時,此處將int3修改爲nopCRC32會提示我們內存已經被打補丁,輸出效果如下圖所示;

當然上述方法雖然可以對全局進行保護,但如果程序過大則此類驗證效率將變得很低,我們需要通過使用打標籤的方式對特定內存區域進行保護,如下代碼中所示,我們通過begin設置開始保護標籤,通過end設置結束保護標籤,通過size = end_addr - begin_addr;計算即可獲取到當前所需要保護的內存長度,最後通過CalculateMemoryCRC32實現計算內存CRC的目的,讀者可以在當前進程內啓動子線程用於實現專門的內存檢測。

// 檢查內存中CRC32特徵值
DWORD CalculateMemoryCRC32(DWORD va_base, DWORD sec_len)
{
    DWORD CheckCRC32 = CRC32((BYTE*)(va_base), sec_len);
    return CheckCRC32;
}

int main(int argc, char *argv[])
{
    // 用於保存初始化時 .text 節中的CRC32值
    DWORD OriginalCRC32 = 0;

    DWORD begin_addr, end_addr, size;
    
    // 獲取到兩個位置的偏移地址
    __asm mov begin_addr, offset begin;
    __asm mov end_addr, offset end;

    // 計算出 兩者內存差值
    size = end_addr - begin_addr;

    // 校驗指定內存位置
    OriginalCRC32 = CalculateMemoryCRC32(begin_addr, size);

    while (1)
    {
        // 標記爲需要保護的區域
    begin:
        printf("hello lyshark \n");
        printf("hello lyshark \n");
        printf("hello lyshark \n");

        // 保護區域聲明結束
    end:

        // 計算並對比
        if (OriginalCRC32 == CalculateMemoryCRC32(begin_addr, size))
        {
            printf("[+] 此區域沒有被修改 \n");
        }
        else
        {
            printf("[-] 此區域已被修改\n");
        }

        Sleep(3000);
    }
    system("pause");
    return 0;
}

當保護區域內的參數發生變化時則會彈出數據被篡改,如下所示我們通過填充一個nop指令,觀察下圖,讀者能夠發現我們的檢測生效了;

本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/541f4225.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章