作 者: NetRoc 时 间: 2008-01-25,15:16 链 接: http://bbs.pediy.com/showthread.php?t=58896 一种躲避运行时代码校验的方法 cc682/NetRoc 关键字:代码校验,内存补丁,hook 我们有时候需要对运行中的程序打内存补丁,或者对它的代码挂一些钩子之类的工作。但是现在相当多软件进行了运行时的代码检测。一旦发现内存中的代码被修改掉,就会进行处理。本文介绍了一种比较特别的办法,用于通过这些检测。 首先需要说一下做运行时代码校验的方法。一般来说,校验者需要取得当前模块的基地址,通过分析PE结构,获得代码节的偏移和大小,然后对内存中的代码进行CRC或者其他的一些校验。 这其中有个很大的问题,校验者默认了通过这种方式取得的代码节就是当前被使用到的代码,但是事实却不一定如此。一般编译器正常生成的代码,绝大部分跳转和call语句都使用相对地址,因此,我们完全可以把代码节或者整个exe文件映像复制到内存其他地方,并操作进程内的所有线程,使得它执行在新复制的那份代码中。这样,校验者仍然在扫描旧的地址,新的那份我们就可以随意修改了。 由于一旦进程开始执行,并且创建其他线程之后,我们通过取得线程Context获得的EIP,多半在系统代码中间,所以难以改变。唯一的方法就是,在exe的EntryPoint被执行前,将EntryPoint重定向到新的代码,并Hook CreateThread,将新线程也重新定位。可以通过下面几个步骤来实现: 1、 如果想处理的进程为a.exe,并且a.exe是由b.exe创建的,那么我们需要hook掉b.exe的进程创建函数,一般是CreateProcess。 2、 在Hook的CreateProcess中,以CREATE_SUSPENDED标志创建a.exe。并向a.exe中注入我们的dll,等待这个dll完成处理之后才ResumeThread a.exe的主线程。 3、 注入的dll需要完成几件事。首先要获得主模块的基地址和大小,并分配足够的空间,将原始映像复制过去。然后Hook掉EntryPoint,并Hook CreateThread,最后恢复a.exe主线程。 4、 Hook掉的EntryPoint中,恢复被Hook的EntryPoint代码,防止在后面被检查出来,然后jmp到新分配的代码区域即可。 5、 Hook的CreateThread中,重新计算代码线程函数地址,并修改后创建。这样,所有线程就都在新分配的代码中执行了。 下面是注入的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; //这里使用Detours库Hook掉CreateThread。 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; } } //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 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之后迅速崩溃。本文仅仅是介绍一种思想,有时候巧妙的构思可以使得复杂问题很快得到解决。 改良之后,这个技巧可以用到不少地方,呵呵
一种躲避运行时代码校验的方法
最新推荐文章于 2023-11-08 23:56:32 发布