一种躲避运行时代码校验的方法(Reload And Run)

转载 2014年05月02日 21:26:58

 转自看雪。


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

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

SSDT HOOK拦截远线程的创建(上)

http://nokyo.blogbus.com/logs/37787913.html   在ring3的API HOOK中,怎样迫使目标进程调用我们的傀儡DLL是我们非常重视的一个问题。在多数...
  • jiangqin115
  • jiangqin115
  • 2015-11-10 14:10:01
  • 563

决不应该调用CreateThread

在写c++代码时,一直牢记着一句话:决不应该调用CreateThread。相反,应该使用Visual   C++运行期库函数_beginthreadex。 好像CreateThread函数就是老虎,...
  • qq506124204
  • qq506124204
  • 2012-07-09 00:20:12
  • 1641

拦截进程创建(不会卡死桌面)

拦截进程创建(不会卡死桌面)
  • chenlycly
  • chenlycly
  • 2016-09-18 15:49:30
  • 682

遍历创建进程、创建线程、加载模块的回调函数

大家都知道在内核环境下有三个函数分别可以设置Process, Thread, Image的相关通知函数,他们是PsSetCreateProcessNotifyRoutine,PsSetCreateTh...
  • whatday
  • whatday
  • 2013-10-28 21:24:27
  • 5253

循环冗余校验(CRC)算法入门引导

写给嵌入式程序员的循环冗余校验(CRC)算法入门引导 前言 CRC校验(循环冗余校验)是数据通讯中最常采用的校验方式。在嵌入式软件开发中,经常要用到CRC 算法对各种数据进行校验。因此,掌握基本的CR...
  • liyuanbhu
  • liyuanbhu
  • 2012-08-19 12:42:34
  • 163497

某同学的inline hook检测程序简单逆向分析

 测试了下,主机上完全不行,虚拟机上运行成功过一次,然后运行了其他的ARK就BSOD的了。没有具体的分析dump文件;检测inline hook的结果不是很完善,且有一部分的错误. 简单说说我对程序的...
  • iiprogram
  • iiprogram
  • 2008-10-16 17:37:00
  • 1582

过XX保护之 InLine Hook

1--查看系统进程中的线程,发现有属于TeSafe.sys的线程,初步推测为守护线程。         2--系统回调,具体功能不予说明。 3-- SSDT  发现有3个Inline hook函数...
  • iCrack007
  • iCrack007
  • 2015-09-26 13:55:46
  • 640

Inline Hook 之(监视任意函数)

原文转自:http://blog.csdn.net/masefee/article/details/6326634 前面已经写过两次inline hook的博文了,第一篇为:《C/C++ H...
  • hpp24
  • hpp24
  • 2016-09-12 15:08:23
  • 1696

关于滴水的VT调试器

关于滴水的VT调试器 by 海风月影 论坛上今天吵的比较火热,主要是关于滴水的VT调试器,很多人不了解这个东西,我对Intel的VT技术略有了解,所以我来简单的介绍一下。 第一,什么是VT...
  • whatday
  • whatday
  • 2014-04-06 11:30:42
  • 2557

修改源码实现全局(无需root)注入躲开注入检测

看这篇文章需要的技能 1.会编译android源码(如果你不愿意编译源码,还有另外一种办法,下面我会提供) 2.会使用substrate或者xposed 以上2个网上资料很多我就不啰嗦了 ...
  • omnispace
  • omnispace
  • 2016-12-19 08:39:34
  • 1572
收藏助手
不良信息举报
您举报文章:一种躲避运行时代码校验的方法(Reload And Run)
举报原因:
原因补充:

(最多只允许输入30个字)