首页
论坛
课程
招聘
雪    币: 13
活跃值: 活跃值 (16)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝

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

2008-1-25 15:16 44161

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

2008-1-25 15:16
44161
一种躲避运行时代码校验的方法

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

[公告]SDC2020 看雪安全者开发者峰会10月23日将在上海举行!欢迎参加!

最新回复 (39)
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
海风日影 活跃值 2008-1-25 15:34
2
0
没有重定位表时这方法不通用, 效果经常不好
类多的程序(尤其MFC) 一碰到虚表指针就打回原形了
雪    币: 750
活跃值: 活跃值 (23)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
海风月影 活跃值 17 2008-1-25 15:41
3
0
传说中的Shadow walk?
雪    币: 108
活跃值: 活跃值 (34)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
combojiang 活跃值 26 2008-1-25 16:36
4
0
嗯,先顶起来再慢慢品
雪    币: 2071
能力值: (RANK:170 )
在线值:
发帖
回帖
粉丝
Aker 活跃值 4 2008-1-25 17:16
5
0
哈哈哈,严重学习...
雪    币: 5535
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
forgot 活跃值 26 2008-1-25 18:01
6
0
memory clone,在 pelock脱壳机中用过这招
雪    币: 215
活跃值: 活跃值 (10)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
skyege 活跃值 2 2008-1-26 01:37
7
0
妙………………
雪    币: 478
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
qyc 活跃值 4 2008-1-26 12:02
8
0
ASM 中的3个代码可搞定重定位问题吧

call @
@
pop
SUB

楼主的想法不错!!
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
海风日影 活跃值 2008-1-26 12:09
9
0
楼上没有明白我说的
自己拿个任意mfc程序试试就明白了
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhucheba 活跃值 2008-1-27 10:05
10
0
先顶起来再慢慢品
雪    币: 178
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
patriots 活跃值 2008-2-15 11:45
11
0
这将会被运用到游戏外挂中
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
duchuan 活跃值 2008-2-15 20:07
12
0
这样的文章太好了.要是有个例子,就好了
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tkb 活跃值 2008-2-15 20:14
13
0
好复杂哦~~~
雪    币: 601
活跃值: 活跃值 (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Second 活跃值 1 2008-2-15 22:59
14
0
想法很秒,期待更好的。能完整对付带壳的就好了。
雪    币: 246
活跃值: 活跃值 (11)
能力值: ( LV13,RANK:410 )
在线值:
发帖
回帖
粉丝
Isaiah 活跃值 10 2008-2-15 23:00
15
0
终于有人贴出来了。如果遇到Jmp table太多的就不好用了
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhbm 活跃值 2008-6-19 11:00
16
0
好文章,学习
雪    币: 301
活跃值: 活跃值 (10)
能力值: ( LV13,RANK:330 )
在线值:
发帖
回帖
粉丝
HSQ 活跃值 8 2008-6-19 21:27
17
0
好像在那个地方见到过一篇,利用CPU指令预取来躲过检测的
雪    币: 131
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xcczz 活跃值 2008-6-21 08:42
18
0
先学习  再学习 然后再学习  好的东西反复学习
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mykent 活跃值 2008-7-2 19:17
19
0
最好有完整的代码,顶起来
雪    币: 10
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
gesily 活跃值 2008-7-4 17:25
20
0
好东西,下载收藏。
雪    币: 110
活跃值: 活跃值 (15)
能力值: ( LV12,RANK:220 )
在线值:
发帖
回帖
粉丝
cater 活跃值 5 2008-12-30 00:38
21
0
有个 日影。。
雪    币: 239
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
天行客 活跃值 2009-2-16 22:16
22
0
好帖子,值得学习
雪    币: 201
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
eviliori 活跃值 2009-2-19 19:11
23
0
呵呵,看的口水都流出来了,好文~
雪    币: 215
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
veninson 活跃值 2009-3-20 00:58
24
0
请问加载的时候需要注意些什么?为什么我只有启动小程序能成功,有的小程序控件还无效了,看不到Button等,IE一点网页就出错,再大一点的程序就直接报错。
我是这样做的:
CreateProcess启动一个暂定的程序
->CreateRemoteThread注入Dll
->DLL_PROCESS_ATTACH时调用OnAttachProcess
->Sleep(500)
->ResumeThread(pi.hThread)
雪    币: 232
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
gcgl 活跃值 2009-3-24 00:16
25
0
先顶一个,回去慢慢专研
雪    币: 524
活跃值: 活跃值 (83)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
kagayaki 活跃值 2009-6-20 00:58
26
0
收藏!!!!
雪    币: 51
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kuty 活跃值 2009-12-8 09:29
27
0
顶上,学习了
雪    币: 50
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xupengpai 活跃值 2010-9-20 12:30
28
0
这方法我试过,对于一些小程序处理下还行。
不过很不通用,除了要hook CreateThread外还有很多东西需要hook
比如:SetTimer、RegisterClassNameA\W、DialogBoxParamA\W
等等,这些回调函数传入的都是绝对地址。
雪    币: 98
活跃值: 活跃值 (32)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
eGirlAsm 活跃值 2012-1-15 01:46
29
0
就算jmp是相对跳转,那call咋办啊
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhuanjia 活跃值 2012-1-25 23:49
30
0
收下来,慢慢学习~
雪    币: 111
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
kingsdows 活跃值 3 2012-4-19 20:46
31
0
的确是很妙,我爱你
雪    币: 507
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
BeWideWay 活跃值 2012-9-25 11:11
32
0
必须mark之
雪    币: 315
活跃值: 活跃值 (28)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
lhglhg 活跃值 1 2012-9-25 12:39
33
0
学习一下....
雪    币: 42
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
qinxijp 活跃值 1 2012-10-4 21:09
34
0
mark~~~
雪    币: 1804
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
youye 活跃值 2013-12-13 14:08
35
0
标记下,需要在过来看
雪    币: 613
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
天高 活跃值 2013-12-13 16:30
36
0
mark
雪    币: 79
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cqzj70 活跃值 2014-1-22 08:26
37
0
mark
雪    币: 56
活跃值: 活跃值 (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
s朽木s 活跃值 2014-2-26 16:06
38
0
mark
雪    币: 4376
活跃值: 活跃值 (156)
能力值: ( LV15,RANK:620 )
在线值:
发帖
回帖
粉丝
无名侠 活跃值 10 2014-4-11 20:32
39
0
学习了,一般来说,可以nop掉效验函数。
雪    币: 51
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cykefu 活跃值 2016-4-1 15:56
40
0
mark
游客
登录 | 注册 方可回帖
返回