首页
社区
课程
招聘
[原创]一种躲避运行时代码校验的方法
发表于: 2008-1-25 15:16 46689

[原创]一种躲避运行时代码校验的方法

2008-1-25 15:16
46689

一种躲避运行时代码校验的方法

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之后迅速崩溃。本文仅仅是介绍一种思想,有时候巧妙的构思可以使得复杂问题很快得到解决。
    改良之后,这个技巧可以用到不少地方,呵呵


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (39)
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
没有重定位表时这方法不通用, 效果经常不好
类多的程序(尤其MFC) 一碰到虚表指针就打回原形了
2008-1-25 15:34
0
雪    币: 7309
活跃值: (3788)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
3
传说中的Shadow walk?
2008-1-25 15:41
0
雪    币: 321
活跃值: (271)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
4
嗯,先顶起来再慢慢品
2008-1-25 16:36
0
雪    币: 2134
活跃值: (14)
能力值: (RANK:170 )
在线值:
发帖
回帖
粉丝
5
哈哈哈,严重学习...
2008-1-25 17:16
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
6
memory clone,在 pelock脱壳机中用过这招
2008-1-25 18:01
0
雪    币: 229
活跃值: (70)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
7
妙………………
2008-1-26 01:37
0
雪    币: 707
活跃值: (1301)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
8
ASM 中的3个代码可搞定重定位问题吧

call @
@
pop
SUB

楼主的想法不错!!
2008-1-26 12:02
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
楼上没有明白我说的
自己拿个任意mfc程序试试就明白了
2008-1-26 12:09
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
先顶起来再慢慢品
2008-1-27 10:05
0
雪    币: 178
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
这将会被运用到游戏外挂中
2008-2-15 11:45
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
这样的文章太好了.要是有个例子,就好了
2008-2-15 20:07
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tkb
13
好复杂哦~~~
2008-2-15 20:14
0
雪    币: 608
活跃值: (91)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
想法很秒,期待更好的。能完整对付带壳的就好了。
2008-2-15 22:59
0
雪    币: 331
活跃值: (56)
能力值: ( LV13,RANK:410 )
在线值:
发帖
回帖
粉丝
15
终于有人贴出来了。如果遇到Jmp table太多的就不好用了
2008-2-15 23:00
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
好文章,学习
2008-6-19 11:00
0
雪    币: 381
活跃值: (140)
能力值: ( LV13,RANK:330 )
在线值:
发帖
回帖
粉丝
17
好像在那个地方见到过一篇,利用CPU指令预取来躲过检测的
2008-6-19 21:27
0
雪    币: 131
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
先学习  再学习 然后再学习  好的东西反复学习
2008-6-21 08:42
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
最好有完整的代码,顶起来
2008-7-2 19:17
0
雪    币: 10
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
好东西,下载收藏。
2008-7-4 17:25
0
雪    币: 109
活跃值: (498)
能力值: ( LV12,RANK:220 )
在线值:
发帖
回帖
粉丝
21
有个 日影。。
2008-12-30 00:38
0
雪    币: 239
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
好帖子,值得学习
2009-2-16 22:16
0
雪    币: 183
活跃值: (228)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
呵呵,看的口水都流出来了,好文~
2009-2-19 19:11
0
雪    币: 215
活跃值: (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
请问加载的时候需要注意些什么?为什么我只有启动小程序能成功,有的小程序控件还无效了,看不到Button等,IE一点网页就出错,再大一点的程序就直接报错。
我是这样做的:
CreateProcess启动一个暂定的程序
->CreateRemoteThread注入Dll
->DLL_PROCESS_ATTACH时调用OnAttachProcess
->Sleep(500)
->ResumeThread(pi.hThread)
2009-3-20 00:58
0
雪    币: 232
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
先顶一个,回去慢慢专研
2009-3-24 00:16
0
游客
登录 | 注册 方可回帖
返回
//