一種躲避運行時代碼校驗的方法

一種躲避運行時代碼校驗的方法

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

 

關鍵字:代碼校驗,內存補丁,hook

我們有時候需要對運行中的程序打內存補丁,或者對它的代碼掛一些鉤子之類的工作。但是現在相當多軟件進行了運行時的代碼檢測。一旦發現內存中的代碼被修改掉,就會進行處理。本文介紹了一種比較特別的辦法,用於通過這些檢測。

首先需要說一下做運行時代碼校驗的方法。一般來說,校驗者需要取得當前模塊的基地址,通過分析PE結構,獲得代碼節的偏移和大小,然後對內存中的代碼進行CRC或者其他的一些校驗。

這其中有個很大的問題,校驗者默認了通過這種方式取得的代碼節就是當前被使用到的代碼,但是事實卻不一定如此。一般編譯器正常生成的代碼,絕大部分跳轉和call語句都使用相對地址,因此,我們完全可以把代碼節或者整個exe文件映像複製到內存其他地方,並操作進程內的所有線程,使得它執行在新複製的那份代碼中。這樣,校驗者仍然在掃描舊的地址,新的那份我們就可以隨意修改了。

由於一旦進程開始執行,並且創建其他線程之後,我們通過取得線程Context獲得的EIP,多半在系統代碼中間,所以難以改變。唯一的方法就是,在exeEntryPoint被執行前,將EntryPoint重定向到新的代碼,並Hook CreateThread,將新線程也重新定位。可以通過下面幾個步驟來實現:

1、 如果想處理的進程爲a.exe,並且a.exe是由b.exe創建的,那麼我們需要hookb.exe的進程創建函數,一般是CreateProcess

2、 HookCreateProcess中,以CREATE_SUSPENDED標誌創建a.exe。並向a.exe中注入我們的dll,等待這個dll完成處理之後才ResumeThread a.exe的主線程。

3、 注入的dll需要完成幾件事。首先要獲得主模塊的基地址和大小,並分配足夠的空間,將原始映像複製過去。然後HookEntryPoint,並Hook CreateThread,最後恢復a.exe主線程。

4、 Hook掉的EntryPoint中,恢復被HookEntryPoint代碼,防止在後面被檢查出來,然後jmp到新分配的代碼區域即可。

5、 HookCreateThread中,重新計算代碼線程函數地址,並修改後創建。這樣,所有線程就都在新分配的代碼中執行了。

下面是注入的dll的實現代碼:

 

pfnCreateThread g_pCreateThread = ::CreateThread;

PBYTE    g_pbyNewImage = NULL;

 

#pragma pack(push,1)

typedef struct _PUSH_RETN

{

     BYTE byOpcodePush;//0x68

     DWORD dwRetnAddr;

     BYTE byOpcodeRetn;//0xC3

}PUSH_RETN, *PPUSH_RETN;

#pragma pack(pop)

 

BYTE g_abyOldEntry[6] = {0};

PBYTE g_pbyOldEntry = 0;

PBYTE g_pbyNewEntry = 0;

 

//這裏使用DetoursHookCreateThread

BOOL HookThreadCreate()

{

     DetourTransactionBegin();

     DetourUpdateThread( GetCurrentThread());

 

 

     if( DetourAttach( &(PVOID&)g_pCreateThread, Hook_CreateThread) != NO_ERROR)

     {

         DebugOut( TEXT( "Hook CreateThread fail/r/n"));

     }

 

     if( DetourTransactionCommit() != NO_ERROR)

     {

         DebugOut( TEXT( "Hook fail/r/n"));

         return FALSE;

     }

     else

     {

         DebugOut( TEXT( "Hook ok/r/n"));

         return TRUE;

     }

}

 

//HookCreateThread裏面,重新計算lpStartAddress地址,並按這個地址來創建

HANDLE WINAPI Hook_CreateThread(

                                     LPSECURITY_ATTRIBUTES lpThreadAttributes,

                                      SIZE_T dwStackSize,

                                     LPTHREAD_START_ROUTINE lpStartAddress,

                                     LPVOID lpParameter,

                                     DWORD dwCreationFlags,

                                     LPDWORD lpThreadId

                                     )

{

     PBYTE pfn = (PBYTE)lpStartAddress;

     HMODULE hMod = ::GetModuleHandle( NULL);

     pfn = g_pbyNewImage + (pfn - (PBYTE)hMod);

    HANDLE hThread = g_pCreateThread( lpThreadAttributes, dwStackSize, (LPTHREAD_START_ROUTINE)pfn, lpParameter, dwCreationFlags, lpThreadId);

     return hThread;

}

 

//Hook掉的入口點,恢復舊代碼並跳轉到新分配的代碼空間

__declspec( naked ) VOID Hook_EntryPoint()

{

     //_asm int 3

     for ( DWORD i = 0; i < sizeof(g_abyOldEntry); i++)

     {//恢復舊的代碼

         g_pbyOldEntry[i] = g_abyOldEntry[i];

     }

     _asm jmp g_pbyNewEntry;

}

 

//Hook掉入口點

VOID HookEntryPoint()

{

     DebugOut( _T( "In HookEntryPoint/r/n"));

 

     HMODULE hMod = ::GetModuleHandle( NULL);

     PIMAGE_DOS_HEADER pstDosHeader = (PIMAGE_DOS_HEADER)hMod;

     PIMAGE_NT_HEADERS pstHeader = (PIMAGE_NT_HEADERS)((PBYTE)hMod + pstDosHeader->e_lfanew);

     DWORD dwEntryRVA = pstHeader->OptionalHeader.AddressOfEntryPoint;

     PBYTE pbyRVA = (PBYTE)(hMod) + dwEntryRVA;

     PPUSH_RETN pstHook = (PPUSH_RETN)pbyRVA;

     memcpy( g_abyOldEntry, pbyRVA, sizeof(PUSH_RETN));

     pstHook->byOpcodePush = 0x68;

     pstHook->dwRetnAddr = (DWORD)Hook_EntryPoint;

     pstHook->byOpcodeRetn = 0xC3;

 

     g_pbyOldEntry = pbyRVA;

     g_pbyNewEntry = g_pbyNewImage + dwEntryRVA;

     DebugOut( _T("New image base = 0x%X, New EntryPoint = 0x%X/r/n /

                   Old image base = 0x%X, Old EntryPoint = 0x%X/r/n"), g_pbyNewImage, g_pbyNewEntry, hMod, g_pbyOldEntry);

     DebugOut( _T( "HookEntryPoint OK/r/n"));

}

 

//DllMain裏面Process Attach的時候調用

VOID OnAttachProcess()

{

     HMODULE hMod = ::GetModuleHandle( NULL);

     DWORD dwSize = GetImageSize();

     g_pbyNewImage = new BYTE[dwSize];

 

     DWORD dwOldProtect = 0;

     VirtualProtect( g_pbyNewImage, dwSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);

     VirtualProtect( (LPVOID)hMod, dwSize, PAGE_READWRITE, &dwOldProtect);

     memcpy( g_pbyNewImage, (LPVOID)hMod, dwSize);

 

     HookEntryPoint();

     HookThreadCreate();

}

 

//獲得主模塊映像大小

DWORD GetImageSize()

{

     HANDLE hShot = NULL;

     BOOL blResult = FALSE;

     MODULEENTRY32 stInfo = {0};

     stInfo.dwSize = sizeof( MODULEENTRY32);

     TCHAR tszTmp[MAX_PATH] = {0};

 

     ::GetModuleFileName( GetModuleHandle(NULL), tszTmp, MAX_PATH);

     _tcslwr( tszTmp);

     size_t s = _tcslen(tszTmp);

     for ( DWORD i = 0; i < s; i++)

     {

         if ( tszTmp[s - i] == '//')

         {

              s = s - i + 1;

              break;

         }

     }

     hShot = ::CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, ::GetCurrentProcessId());

     if ( INVALID_HANDLE_VALUE == hShot)

     {

         DebugOut( _T( "CreateToolhelp32Snapshot fail.Err=%d/r/n"), GetLastError());

         return 0;

     }

 

     blResult = ::Module32First( hShot, &stInfo);

     while ( blResult)

     {

         _tcslwr( stInfo.szModule);

         if ( _tcscmp( stInfo.szModule, tszTmp + s) == 0)

         {

              CloseHandle( hShot);

              return stInfo.modBaseSize;

         }

         blResult = ::Module32Next( hShot, &stInfo);

     }

     CloseHandle( hShot);

     return 0;

}

這種方法對加殼之後的exe作用有限,因爲Hook的並不是真實的OEP。這樣做常常會造成殼執行過程中,或者跳轉到OEP之後迅速崩潰。本文僅僅是介紹一種思想,有時候巧妙的構思可以使得複雜問題很快得到解決。

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