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

我们有时候需要对运行中的程序打内存补丁,或者对它的代码挂一些钩子之类的工作。但是现在相当多软件进行了运行时的代码检测。一旦发现内存中的代码被修改掉,就会进行处理。本文介绍了一种比较特别的办法,用于通过这些检测。

首先需要说一下做运行时代码校验的方法。一般来说,校验者需要取得当前模块的基地址,通过分析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;//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之后迅速崩溃。本文仅仅是介绍一种思想,有时候巧妙的构思可以使得复杂问题很快得到解决。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值