我們有時候需要對運行中的程式打記憶體補丁,或者對它的代碼掛一些鉤子之類的工作。但是現在相當多軟體進行了運行時的代碼檢測。一旦發現記憶體中的代碼被修改掉,就會進行處理。本文介紹了一種比較特別的辦法,用於通過這些檢測。
首先需要說一下做運行時代碼校正的方法。一般來說,校正者需要取得當前模組的基地址,通過分析PE結構,獲得代碼節的位移和大小,然後對記憶體中的代碼進行CRC或者其他的一些校正。
這其中有個很大的問題,校正者預設了通過這種方式取得的代碼節就是當前被使用到的代碼,但是事實卻不一定如此。一般編譯器正常產生的程式碼,絕大部分跳轉和call語句都使用相對位址,因此,我們完全可以把代碼節或者整個exe檔案映像複製到記憶體其他地方,並操作進程內的所有線程,使得它執行在新複製的那份代碼中。這樣,校正者仍然在掃描舊的地址,新的那份我們就可以隨意修改了。
由於一旦進程開始執行,並且建立其他線程之後,我們通過取得線程Context獲得的EIP,多半在系統代碼中間,所以難以改變。唯一的方法就是,在exe的EntryPoint被執行前,將EntryPoint重新導向到新的代碼,並Hook CreateThread,將新線程也重新置放。可以通過下面幾個步驟來實現:
- 如果想處理的進程為a.exe,並且a.exe是由b.exe建立的,那麼我們需要hook掉b.exe的進程建立函數,一般是CreateProcess。
- 在Hook的CreateProcess中,以CREATE_SUSPENDED標誌建立a.exe。並向a.exe中注入我們的dll,等待這個dll完成處理之後才ResumeThread a.exe的主線程。
- 注入的dll需要完成幾件事。首先要獲得主模組的基地址和大小,並分配足夠的空間,將原始映像複製過去。然後Hook掉EntryPoint,並Hook CreateThread,最後恢複a.exe主線程。
- Hook掉的EntryPoint中,恢複被Hook的EntryPoint代碼,防止在後面被檢查出來,然後jmp到新分配的代碼地區即可。
- Hook的CreateThread中,重新計算代碼線程函數地址,並修改後建立。這樣,所有線程就都在新分配的代碼中執行了。
下面是注入的dll的實現代碼:
pfnCreateThread
g_pCreateThread = ::CreateThread;
PBYTE g_pbyNewImage = NULL;
#pragma
pack(push,1)
typedef
struct
_PUSH_RETN
{
BYTE
byOpcodePush;//0×68
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;
//這裡使用Detours庫Hook掉CreateThread。
BOOL
HookThreadCreate()
{
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread());
if( DetourAttach( &(PVOID&)g_pCreateThread, Hook_CreateThread) != NO_ERROR)
{
DebugOut( TEXT( “Hook CreateThread failrn”));
}
if( DetourTransactionCommit() != NO_ERROR)
{
DebugOut( TEXT( “Hook failrn”));
return
FALSE;
}
else
{
DebugOut( TEXT( “Hook okrn”));
return
TRUE;
}
}
//Hook的CreateThread裡面,重新計算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 HookEntryPointrn”));
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 = 0×68;
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%Xrn
Old image base = 0x%X, Old EntryPoint = 0x%Xrn”), g_pbyNewImage, g_pbyNewEntry, hMod, g_pbyOldEntry);
DebugOut( _T( “HookEntryPoint OKrn”));
}
//在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=%drn”), 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之後迅速崩潰。本文僅僅是介紹一種思想,有時候巧妙的構思可以使得複雜問題很快得到解決。