原创 | Windows API Hooking和DLL注入

admin 2021年5月17日21:06:50评论198 views字数 4548阅读15分9秒阅读模式

一、概述

Hooking技术是一系列技术的通称,通过拦截在软件构件之间传递的函数调用(functioncalls)/信息(messages)/事件(events)来改变OS, 应用(application)或者其他软件构件的行为。譬如改变准备调用的函数指针使之指向需要的hook函数,在执行了hook函数之后再返回原来的函数指针。当我们试图用一段代码来分析程序中某段逻辑路径被执行的频率,或者想要在其中插入更多功能时就会用到钩子。通过Hook,我们可以暂停系统调用,或者通过改变系统调用的参数来改变正常的输出结果,甚至可以中止一个当前运行中的进程并且将控制权转移到自己手上。这种用来实现hooking技术的代码叫做hook(钩子)。恶意软件也经常使用hook。例如从正在运行的进程列表中隐藏或拦截按键事件、窃取密码,信用卡数据等敏感输入。

Hook实现的方法分为一下两种:

1.在应用运行之前更改源:通过逆向工程修改可执行文件或者库,通常用来拦截函数调用,从而监控或者完全替换掉调用。

通过反汇编器,可以找到在一个模块里面的函数入口点。更改入口点从而动态地加载我们需要的库;

修改可执行文件的入口表,入口表修改之后可以加载任何附加的的库模块,同时在应用程序调用函数时修改外部代码的调用;

通过包装库(wrapper library)拦截函数调用。包装库:满足所有原库可调用的函数,并附加了自己的实现。

2.运行时(runtime) 修改

操作系统和会提供途径让我们在运行时很容易地插入hook(需要插入hook的进程有足够的权限)当操作系统没有提供这一功能时。可以使用修改内存中的中断向量表,入口描述符表的方法(对于有共享库的操作系统)。(实质上这种办法也是修改代码,只是在运行时更改进程内存中的指令和结构)。Unix系统用LD_PRELOAD环境变量轻松地实现了函数重载,当你在创建了一个已经存在的共享函数库中的函数时,你会在DYLD_INSERT_LIBRARIES注册你自己的函数,这样就会调用自己的函数了。

API Hooking可分为以下几种类型:

1.本地挂钩:这些仅影响特定的应用程序。

2.全局挂钩:这些会影响所有系统进程。

在本文中,我们将介绍Windows的挂钩技术,该技术属于通过使用C/C++和本机API进行运行时修改来完成的本地类型。

二、Hooking 原理

2.1注入

使用运行时修改方法实现的本地Hook必须运行在目标程序的地址空间内。操纵目标过程并使之hook的程序称为注入器。 

Hook分为一下几个步骤:

1.获取目标过程句柄。

2.在目标进程中分配内存,然后将外部DLL路径写入其中(此处是指编写包含该钩子的动态库路径)。

3.在目标进程内创建一个线程,该线程将加载库并设置钩子。

Windows API提供了一些适合于实现注射器的系统调用。假设目标进程尚未运行,我们想在目标程序启动后立即插入钩子。为此,注入器应首先调用CreateProcess API来运行目标进程。 

原创 | Windows API Hooking和DLL注入

为了在目标进程启动后立即设置钩子,注入器必须通过传递一个CREATE_SUSPENDED标志(dwCreationFlags)来挂起目标,然后在注入钩子之后,通过调用ResumeThread API函数来恢复目标进程。在挂起状态下启动进程的方法如下:

原创 | Windows API Hooking和DLL注入

使用系统调用VirtualAllocExWriteProcessMemory的组合来执行目标进程中的内存分配和写入。注入器应首先使用VirtualAllocEx分配内存。

原创 | Windows API Hooking和DLL注入

然后使用WriteProcessMemory向其写入数据。

原创 | Windows API Hooking和DLL注入

下面是分配内存并将其写入目标进程:

原创 | Windows API Hooking和DLL注入

下一步是在目标进程内创建一个线程,该线程用钩子加载库。利用CreateRemoteThread API实现: 

原创 | Windows API Hooking和DLL注入

因此,CreateRemoteThread在hProcess句柄指定的目标远程进程中创建一个状态参数为dwCreationFlags的新线程。新创建的线程将执行lpStartAddress指向的函数,并将lpParameter作为第一个参数传递给它。然后我们使用此API函数来启动线程并使其加载DLL,实现方式如下:

( 1 ) 作为参数lpStartAddress将指针传递给LoadLibrary()。

( 2 将一个指向DLL钩子(使用VirtualAllocEx和WriteProcessMemory初始化的钩子)的指针作为参数lpParameter。

我们使用GetProcAddress在目标进程中找到指向LoadLibrary函数的指针地址。

原创 | Windows API Hooking和DLL注入

其中hModule是指对导出LoadLibrary的DLL。HMODULE可以通过GetModuleHandle获得。

原创 | Windows API Hooking和DLL注入

将带有钩子的DLL加载到目标进程中的代码如下:

原创 | Windows API Hooking和DLL注入

这些都是注入的所有必要步骤。如果注入顺利,则将钩子库加载到目标进程中,然后再DllMain中执行该函数,这样我们可以设置任何所需的钩子。

2.2hook引擎

要实现挂钩本身,我们建议使用许多现有的方法。它们有很多作为开源、免费或部分免费的方式提供。例如功能强大的挂钩引擎Microsoft Detour在免费版本中支持x86架构(它需要付费才能hook x64)。另一个用得比较多的是NtHookEngine,它支持x86和x64并具有设计良好且非常简单的API。实际上,此引擎仅导出三个易于使用的功能:

原创 | Windows API Hooking和DLL注入

HookFunction——设置一个hook。此函数可用于挂钩当前进程的虚拟地址空间中存在的任何功能。

UnhookFunction——删除特定的挂钩。

GetOriginalFunction——返回指向原始函数的指针。当需要在挂钩函数中调用原始函数时,这非常有用。 

三、实例

3.1Hook Library

我们挂钩了GetAdaptersInfo方法,并伪造了网络适配器名称及其MAC地址值。

原创 | Windows API Hooking和DLL注入

然后我们hook GetAdaptersInfo()与FakeGetAdaptersInfo()。在FakeGetAdaptersInfo()函数内,我们使用GetOriginalFunction()获取实际的适配器信息。接下来,我们用假值替换第一个适配器信息。

原创 | Windows API Hooking和DLL注入

3.2注入器

注入器可以为x86或x64体系结构构建。但是,为x86构建的注射器不会在x64环境中运行。我们的注入器独自运行目标应用程序代码如下:

BOOL WINAPI InjectDll(__in LPCWSTR lpcwszDll, __in LPCWSTR targetPath){    SIZE_T nLength;    LPVOID lpLoadLibraryW = NULL;    LPVOID lpRemoteString;    STARTUPINFO             startupInfo;    PROCESS_INFORMATION     processInformation;    memset(&startupInfo, 0, sizeof(startupInfo));    startupInfo.cb = sizeof(STARTUPINFO);    if (!CreateProcess(targetPath, NULL, NULL, NULL, FALSE,        CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInformation))    {        PrintError(TEXT("Target process is failed to start"));        return FALSE;    }    lpLoadLibraryW = GetProcAddress(GetModuleHandle(L"KERNEL32.DLL"), "LoadLibraryW");    if (!lpLoadLibraryW)    {        PrintError(TEXT("GetProcAddress failed"));        // close process handle        CloseHandle( processInformation.hProcess);        return FALSE;    }    nLength = wcslen(lpcwszDll) * sizeof(WCHAR);    // allocate mem for dll name    lpRemoteString = VirtualAllocEx(processInformation.hProcess, NULL, nLength + 1, MEM_COMMIT, PAGE_READWRITE);    if (!lpRemoteString)    {        PrintError(TEXT("VirtualAllocEx failed"));        // close process handle        CloseHandle(processInformation.hProcess);        return FALSE;    }    // write dll name    if (!WriteProcessMemory(processInformation.hProcess, lpRemoteString, lpcwszDll, nLength, NULL)) {        PrintError(TEXT("WriteProcessMemory failed"));        // free allocated memory        VirtualFreeEx(processInformation.hProcess, lpRemoteString, 0, MEM_RELEASE);        // close process handle        CloseHandle(processInformation.hProcess);        return FALSE;    }    // call loadlibraryw    HANDLE hThread = CreateRemoteThread(processInformation.hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)lpLoadLibraryW, lpRemoteString, NULL, NULL);    if (!hThread) {        PrintError(TEXT("CreateRemoteThread failed"));    }    else {        WaitForSingleObject(hThread, 4000);        //resume suspended process        ResumeThread(processInformation.hThread);    }    //  free allocated memory    VirtualFreeEx(processInformation.hProcess, lpRemoteString, 0, MEM_RELEASE);    // close process handle    CloseHandle(processInformation.hProcess);    return TRUE;}

3.3运行结果

为了检是否hook到目标函数,钩子是否正常工作,我们先直接运行程序,然后在运行钩子,比较两次运行的不通。

首先,我们在没有应用钩子的情况下运行目标应用程序:

原创 | Windows API Hooking和DLL注入

然后我们检查加载了钩子的输出。请注意,列表中第一个适配器的“适配器名称”和“适配器地址”字段已更改。

原创 | Windows API Hooking和DLL注入



转载请注明来源:关键基础设施安全应急响应中心

原创 | Windows API Hooking和DLL注入

本文始发于微信公众号(关键基础设施安全应急响应中心):原创 | Windows API Hooking和DLL注入

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年5月17日21:06:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   原创 | Windows API Hooking和DLL注入http://cn-sec.com/archives/274171.html

发表评论

匿名网友 填写信息