天天看點

保護篇-檔案完整性檢查1.磁盤檔案完整性檢查2.記憶體映像完整性檢查3.實作執行個體

 在軟體保護中,增加對自身的完整性檢查,以防止解密者修改程式達到破解的目的。完整性檢查又包括磁盤檔案檢查和記憶體映像的檢查。完整性檢查的實作原理是用散列函數計算檔案的散列值,并将該值放在某處,以後檔案每次運作時,重新計算檔案的散列值,并與原散列值比較,以判斷檔案是否被修改。

1.磁盤檔案完整性檢查

打開需要完整性檢查的程式,用散列計算從PE檔案頭開始到檔案末尾的值,儲存在PE檔案頭得某個位置。在程式運作時取出存儲的值進行比較。需要一個額外的應用程式處理程式。

檔案處理程式:

打開目标程式,用散列函數計算出從PE檔案頭到檔案結束的散列值,并把散列值寫到PE檔案頭前那個空白處。

校驗程式:

(1)讀取自身檔案從PE頭開始到檔案結束的内容,用同樣的散列函數計算檔案内容的散列值,這時得到一個“原始“檔案的散列值。

(2) 讀取自身檔案先前存儲的散列值(PE檔案前),這個值時通過處理程式寫入的。

(3) 把步驟(1)和步驟(2)的值進行比較,如果相等,說明檔案沒有被修改過,反之,檔案被修改了。

缺陷:

解密者可能自己計算散列值,重新寫入檔案。

2.記憶體映像完整性檢查

磁盤檔案完整性校驗可以抵禦破解者修改磁盤檔案,但對記憶體更新檔卻沒有效果。是以對記憶體關鍵代碼也實行檢驗。每個程式都有資料塊和代碼塊,由于資料塊會動态變化,是以檢查這部分是沒有意義的。而代碼塊是隻讀的,存放的是程式代碼,在程式運作過程中是不會發生變化的,是以用這部分進行記憶體校驗是可行的。

實作過程:

(1) 從記憶體映像得到PE相關資料,入代碼區塊的RVA值和記憶體大小等。

(2) 根據得到的代碼區塊的RVA值和記憶體大小,計算其記憶體資料的散列值。

(3) 讀取自身檔案先前存儲的散列值。

(4) 比較兩個散列值。

這個方法還可以有效的抵抗調試器的普通斷點,因為調試器一般通過給應用程式下INT3指令來實作中斷,這樣就改變了代碼區塊的資料,計算散列值時會與原來的不同。

3.實作執行個體

對磁盤檔案完整性檢查主要檢視代碼段是否被修改,是以對磁盤檔案同樣可以隻檢查代碼段的MD5值。由于靜态磁盤檔案和記憶體映像中代碼段是相同的。是以隻需計算靜态檔案代碼段得MD5值,寫入PE檔案頭的某個位置。程式在運作時分别計算靜态磁盤檔案和記憶體鏡像代碼段的MD5值跟PE檔案頭預存放值進行比較。在計算MD5值時,最好加些特殊字元,以增加破解的難度。

3.1檔案處理程式

步驟:

1、打開檔案,擷取dos檔案頭。

2、通過dos頭的結構體内變量計算出PE檔案頭得位置。

3、通過PE檔案結構體變量計算出塊表位置。

4、通過塊表第一個區塊表計算出代碼段的起始位置和長度。

5、計算MD5值。

6、将計算出的MD5值寫入PE檔案頭之前的8個位元組中。

代碼:

size_t GetDiskFileCodeSectionEncrypt(const char* filename, unsigned char infor[8])

{

    PIMAGE_DOS_HEADER pDosHeader = NULL;

    PIMAGE_NT_HEADERS pNtHeader = NULL;

    PIMAGE_SECTION_HEADER pCodeSection = NULL;

    HANDLE hFile = NULL;

    DWORD dwLowFileSize = 0, dwHighFileSize = 0,dwReturn = 0;

    TCHAR *pBuffer = NULL;

    encrypt e;

    unsigned char en[16];

    int i = 0;

    TCHAR tcCodeSignature[] = ".text";

    TCHAR *pCodeSectBuffer = NULL;

    DWORD dwNumberOfSection = 0, j = 0;

    TCHAR tcAdditional[] = "特殊字元";

    //Open file need to calc code section MD5.

    hFile = CreateFile(

        filename,

        GENERIC_READ | GENERIC_WRITE,

        FILE_SHARE_READ,

        NULL,

        OPEN_EXISTING,

        FILE_ATTRIBUTE_NORMAL,

        NULL);

    if (hFile == INVALID_HANDLE_VALUE)

    {

        //Open file fail

        return 0;

    }

    dwLowFileSize = GetFileSize(hFile, &dwHighFileSize);

    if (dwLowFileSize == 0xFFFFFFFF)

    {

        CloseHandle(hFile);

        return 0;

    }

    pBuffer = malloc(sizeof(TCHAR)*dwLowFileSize);//allocate buffer

    if (ReadFile(hFile, pBuffer, dwLowFileSize, &dwReturn, NULL) == 0)

    {

        CloseHandle(hFile);

        return 0;

    }

    pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;

    pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + (pDosHeader->e_lfanew));//DWORD must be exist in this place. if no DWORD, ocur fail.

    dwNumberOfSection = pNtHeader->FileHeader.NumberOfSections;

    pCodeSection = IMAGE_FIRST_SECTION(pNtHeader);

    pCodeSectBuffer = (TCHAR *)pCodeSection;

    for (j = 0; j < dwNumberOfSection; j++)

    {

        if (_strnicmp(pCodeSectBuffer,tcCodeSignature,8) ==0 ) // find ".text" section, .text section is the code section.

        {

            break;

        }

        pCodeSectBuffer += IMAGE_SIZEOF_SECTION_HEADER;

    }

    if (j == dwNumberOfSection)

    {

        CloseHandle(hFile);

        return 0;

    }

    pCodeSection = (PIMAGE_SECTION_HEADER)pCodeSectBuffer;

    //calc MD5 value

    encryptInit(&e);

    encryptUpdate(&e, pBuffer + pCodeSection->PointerToRawData, pCodeSection->Misc.VirtualSize);// 注意PointerToRawData和VirtualSize

    encryptUpdate(&e, tcAdditional, strlen(tcAdditional));

    encryptFinal(en, &e);

    for (i=0; i < 8; i++)

    {

        infor[i] = en[i*2];

    }

    free(pBuffer);

    CloseHandle(hFile);

    return 1;

}

3.2.磁盤檔案校驗

步驟:

1、2、3、4、5與檔案處理程式步驟相同。

6、取出PE檔案頭之前8個位元組中的值。

7、将第5步與第6步的結果進行比較。若結果相同,說明檔案代碼段沒有被改動。若結果不同,說明檔案代碼段被修改。

代碼:

size_t CheckDiskFileCodeSectionEncrypt()

{

    PIMAGE_DOS_HEADER pDosHeader = NULL;

    PIMAGE_NT_HEADERS pNtHeader = NULL;

    PIMAGE_SECTION_HEADER pCodeSection = NULL;

    HANDLE hFile = NULL;

    DWORD dwLowFileSize = 0, dwHighFileSize = 0,dwReturn = 0;

    TCHAR *pBuffer = NULL;

    encrypt e;

    unsigned char en[16];

    int i = 0;

    TCHAR tcCodeSignature[] = ".text";

    TCHAR *pCodeSectBuffer = NULL;

    DWORD dwErrorLevel = 0, dwLength = 0;

    TCHAR tcModuleName[MAX_PATH];

    unsigned char ucOriginalMD5[8]="";

    unsigned char ucNewMD5[8]="";

    DWORD dwNumberOfSection = 0, j = 0;

    TCHAR tcAdditional[] = "特殊字元";

    dwErrorLevel = GetModuleFileName(NULL, tcModuleName, MAX_PATH);//get the module file name

    if (dwErrorLevel == 0)

    {

        return 0;

    }

    //Open file need to calc code section MD5.

    hFile = CreateFile(

        tcModuleName,

        GENERIC_READ,

        FILE_SHARE_READ,

        NULL,

        OPEN_EXISTING,

        FILE_ATTRIBUTE_NORMAL,

        NULL);

    if (hFile == INVALID_HANDLE_VALUE)

    {

        //Open file fail

        return 0;

    }

    dwLowFileSize = GetFileSize(hFile, &dwHighFileSize);

    if (dwLowFileSize == 0xFFFFFFFF)

    {

        CloseHandle(hFile);

        return 0;

    }

    pBuffer = malloc(dwLowFileSize * sizeof(TCHAR));//allocate buffer

    if (ReadFile(hFile, pBuffer, dwLowFileSize, &dwReturn, NULL) == 0)

    {

        CloseHandle(hFile);

        return 0;

    }

    CloseHandle(hFile);

    pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;

    pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + (pDosHeader->e_lfanew));//DWORD must be exist in this place. if no DWORD, ocur fail.

    dwNumberOfSection = pNtHeader->FileHeader.NumberOfSections;

    pCodeSection = IMAGE_FIRST_SECTION(pNtHeader);

    pCodeSectBuffer = (TCHAR *)pCodeSection;

    for (j = 0; j < dwNumberOfSection; j++)

    {

        if (_strnicmp(pCodeSectBuffer,tcCodeSignature,8) ==0 ) // find ".text" section, .text section is the code section.

        {

            break;

        }

        pCodeSectBuffer += IMAGE_SIZEOF_SECTION_HEADER;

    }

    if (j == dwNumberOfSection)

    {

        CloseHandle(hFile);

        return 0;

    }

    pCodeSection = (PIMAGE_SECTION_HEADER)pCodeSectBuffer;

    for (i = 0; i < 8; i++)

    {

        ucOriginalMD5[i] = *(unsigned char*)((DWORD)pNtHeader - 8 +i);

    }

    //calc MD5 value

    encryptInit(&e);

    encryptUpdate(&e, (unsigned char*)(pBuffer + pCodeSection->PointerToRawData), pCodeSection->Misc.VirtualSize);//注意PointerToRawData和VirtualSize

    encryptUpdate(&e, tcAdditional, strlen(tcAdditional));

    encryptFinal(en, &e);

    for (i=0; i < 8; i++)

    {

        ucNewMD5[i] = en[i*2];

    }

    //compare the value with new and original md5 value.

    for (i = 0; i < 8; i++)

    {

        if (ucNewMD5[i] != ucOriginalMD5[i])

        {

            //printf("file has beed destoryed!\n");

            return 0;

        }

    }

    if (pBuffer != NULL)

    {

        free(pBuffer);

    }

    return 1;

}

3.3記憶體映像校驗

步驟:

1、取出記憶體中PE檔案的起始位址,擷取dos檔案頭。

2、通過dos頭的結構體内變量計算出PE檔案頭得位置。

3、通過PE檔案結構體變量計算出塊表位置。

4、通過塊表第一個區塊表計算出代碼段的起始位置和長度。

5、計算MD5值。

6、從記憶體中取出PE檔案頭之前位置的8個位元組值。

7、将第5步與第6步的結果進行比較。若結果相同,說明記憶體映像代碼段沒有被破壞,若結果不同說明記憶體映像中代碼段被修改。

提示:在内容映像檢驗程式中如果加斷點調式會使記憶體代碼段得MD5值與PE檔案頭之前取出的MD5值不同,正驗證前面提到的“這個方法還可以有效的抵抗調試器的普通斷點,因為調試器一般通過給應用程式下INT3指令來實作中斷,這樣就改變了代碼區塊的資料,計算散列值時會與原來的不同。”

代碼:

size_t CheckMemCodeSectionEncrypt()

{

    int i = 0;

    unsigned char ucOriginalMD5[8]="";

    unsigned char ucNewMD5[8]="";

    DWORD dwImageBase = 0;

    PIMAGE_DOS_HEADER pDosHeader = NULL;

    PIMAGE_NT_HEADERS pNtHeader = NULL;

    PIMAGE_SECTION_HEADER pCodeSection = NULL;

    TCHAR *pCodeSectBuffer = NULL;

    TCHAR tcCodeSignature[] = ".text";

    encrypt e;

    unsigned char en[16];

    DWORD dwNumberOfSection = 0, j = 0;

    TCHAR tcAdditional[] = "特殊字元";

    dwImageBase = (DWORD)GetModuleHandle(NULL);//the module handle is the memory address

    pDosHeader = (PIMAGE_DOS_HEADER)dwImageBase;

    pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

    dwNumberOfSection = pNtHeader->FileHeader.NumberOfSections;

    pCodeSection = IMAGE_FIRST_SECTION(pNtHeader);

    pCodeSectBuffer = (TCHAR *)pCodeSection;

    for (i = 0; i < 8; i++)//get the original MD5 value from memory ahead PE header

    {

        ucOriginalMD5[i] = *(unsigned char*)((DWORD)pNtHeader - 8 +i);

    }

    for (j = 0; j < dwNumberOfSection; j++)

    {

        if (_strnicmp(pCodeSectBuffer,tcCodeSignature,8) ==0 ) // find ".text" section, .text section is the code section.

        {

            break;

        }

        pCodeSectBuffer += IMAGE_SIZEOF_SECTION_HEADER;

    }

    if (j == dwNumberOfSection)

    {

        return 0;

    }

    pCodeSection = (PIMAGE_SECTION_HEADER)pCodeSectBuffer;

    //calc code section MD5 value in memory

    encryptInit(&e);

    encryptUpdate(&e, (unsigned char *)(dwImageBase + pCodeSection->VirtualAddress), pCodeSection->Misc.VirtualSize);//注意VirtualAddress和VirtualSize

    encryptUpdate(&e, tcAdditional, strlen(tcAdditional));

    encryptFinal(en, &e);

    for (i=0; i < 8; i++)

    {

        ucNewMD5[i] = en[i*2];

    }

    //compare the value with new and original md5 value.

    for (i = 0; i < 8; i++)

    {

        if (ucNewMD5[i] != ucOriginalMD5[i])

        {

            //printf("file has beed destoryed!\n");

            return 0;

        }

    }

    return 1;

}