首页
论坛
专栏
课程

[原创]windows dll注入/Api钩取技术简单总结

2019-3-2 14:41 11387

[原创]windows dll注入/Api钩取技术简单总结

2019-3-2 14:41
11387
关于windows DLL注入、api钩取的技术文章有关前辈已经做了很多相关的总结,这里只是自己看书总结的一些简单的知识点。

windows消息钩取

    通过api钩取,可以查看或者截取来往信息而使用的一切工具称为“挂钩”。钩取技术广泛用于计算机领域。我们不仅可以哈看来往于“OS-应用程序-用户

之间的全部信息,也可以操作他们,并且伸不知,鬼不觉。os消息队列与应用程序消息队列之间存在一条“钩链”,设置好键盘消息钩子

之后,处于钩链种的键盘消息钩子会比应用程序先看到相应的信息,在键盘消息钩子函数内部,除了可以看到消息外,还可以修改消息本身,而且还可以堆消息实施拦截,阻止消息传递。可以同时设置多个键盘消息钩子,按照设置顺序依次调用这些钩子,他们组成的链称为钩链。

1.1 SetWindowsHookEx()

HHOOK WINAPI SetWindowsHookEx(
__in int idHook, \\钩子类型
__in HOOKPROC lpfn, \\回调函数地址
__in HINSTANCE hMod, \\实例句柄
__in DWORD dwThreadId); \\线程ID

    钩子过程(hook procedure)是操作系统调用的回调函数。安装消息钩子的时候,钩子过程需要在某个dll的内部,该dll的实例句柄就是hMod.若swThreadID参数被设置为0,则安装的钩子称为“全局钩子”,会影响到运行中的(以及以后要运行的)所有进程。设置好消息钩子候,在某个进程种生成指定的消息的时候,擦欧总系统会将相关的dll强制注入相应的进程,然后调用注册的钩子过程。注入进程的时候几乎不需要做什么,非常方便。注入的过程如下,首先有个最先加载相应的hook.dll并安装键盘钩子的程序,之后有程序发生相应的键盘钩子事件的时候都会强制注入相应的dll并安装对应的键盘钩子处理函数。od可以对注入的dll进行相应的分析,调试按钮的optiong->debugging optio->events->break on new module(DLL)开启相应的选项之后,每次当新的dll被装入被调试(debuggee)进程的时候就会自动暂停调试。这从dll注入的时候开始调试非常有用。

    相应的危害:恶意键盘记录器

   

2.dll注入

    dll注入是指想运行种的其他进程强制插入特定的dll文件。从技术上来说,dll注入就是命令其他进程自行调用lodalibrary()api,加载(loading)用户指定的dll文件,dll注入与一般dll加载的区别在于,加载的目标进程是其自身或其他进程。注入的dll拥有访问目标进曾内存的权限,这样用户就可以做任何想做的事情。

dll被加载到进程后会自动运行dllmain函数,用户可以把想执行的代码放到DLLMain()函数,没当加载DLL时,添加的代码就会自然而然的得到执行。利用该特性可以修复程序bug,或者向该程序添加新功能。使用loadlibrary()API加载某个dll时候,该dll种的dllmain()函数就会被执行,dll注入的工作原理就是从外部促使目标进程调用LoadLibrary()api,所以会强制执行dll种的DLLMain()函数,并且,被注入的dll拥有目标进程内存的访问权限,用户可以随意操作。改善功能与修复bug,消息钩取,api钩取,恶意代码。

dll注入的实现方法:

    1)创建远程线程(CreateRemoteThread()API)

    2)使用注册表(AppInit_DLLs值)

    3)消息钩取(SetWindowsHookEx()API)

2.1 CreateRemoteThread()

    实施的具体的步骤

    1)获取目标进程句柄

hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID))
    调用OpenProcess()API,借助运行时参数形式传过来的dwPID值,获取目标进程的句柄(PROCESS_ALL_ACCESS权限),然后可以使用句柄hProcess控制对应的进程。

    2)将要注入的dll路径写入目标进程内存

pRemoteBuf=VirtualAllocEx(hProcess,NULL,dwBufSize,MEM_COMMIT,PAGE_READWRITE); 

   将要加载的DLL文件的路径写入目标进程。因为内存空间无法进行写入操作,故先使用VirtualAllocEx()API在目标进程的内存空间中分配一块缓冲区,且指定该缓冲区的大小为DLL文件路径的字符串长度。WriteProcessMemory(hProcess,pRemoteBuf,szDLLName,dwBufSize,NULL);将字符串路径写入分配的缓冲区。

    3)获取LoadLibraryW()API地址

    LoadLibrayW()在kernel32.dll里面,由于目标进程中的函数地址和注入程序中的函数地址相同,所以可以通过获取当前函数的地址来代替目标继承的函数地址。

hMod=GetModuleHandle("kernel32.dll");
pThreadProc=(LPTHREAD_START_ROUTINE)GetProceAddress(hMod,"LoadLiryaryW");

在windows系中,kernel32.dll在每个进程中的加载地址都是相同的。在运行期间每次都会被映射到相同的地址。windows系统中,dll首次进入内存叫做“加载(loadding)",以后其他进程需要使用相同的dll的时候不必再次加载,只要将加载的dll代码与资源映射一下就可以了。

    4)在目标进程中运行远程线程

   hThread=CreateRemoteThread(hProcess,NULL,0,pThreadProc,pRemoteBuf,0,NULL);

    pThreadProc=目标进程内存中LoadLibraryW()函数地址    pRemoteBuf=目标进程中的dll绝对位置字符串CreateRemoteThread()API用来在目标继承中执行其创建出的线程。

HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess,   //目标进程句柄
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,  //线程函数地址
__in LPVOID lpParameter,   //线程参数地址
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadId
);

    lpStartAddress, 线程函数地址;lpParameter,   线程参数地址;这两个地址都应该在目标进程虚拟内存空间中(这样目标进程才能认识他们)这里只需要把LoadLibrary()函数的地址传递给第四个参数lpStartAddress,把要注入的DLL的路径字符串地址传递给第五个参数lpParameter即可(必须时目标进程的虚拟内存空间地址)。

    调试方法

2.2 AppInit_DLLs

    dll注入的第二种方法是使用注册表。windows操作系统的注册表中默认提供了AppInt_DLLs与LoadAppinit_DLLs两个注册表笑。在注册表编器中,将要注入的DLL的路径字符串写入Appinit_DLLs项目,然后把LoadAppInit_DLLs的项目值设置为1,重启后,指定的DLL会注入所有运行的进程。

    实现的原理是:User32.dll被加载到进程的时候,会读取AppInit_DLLs注册表项,若有值,则调用LoadLibrary()API 加载用户DLL,所以严格的说,相应DLL并不会被加载到所有的进程,而只是加载到加载user32.dll的进程。

运行注册表编辑器retedit.exe,进入如下路径。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

    编辑修改AppInit_Dlls表项的值。

2.3 SetWindwosHookEx()

    第三个方法就是消息钩取。用SetWindowsHookEx()API安装好消息“钩子”,然后由OS将执行DLL(包含有“钩子”过程)强制注入到相应进程。


3 . DLL卸载

    DLL卸载是将插入进程的DLL强制弹出的一种技术,基本原理与使用CreateRemoteThread API进行DLL注入的原理类似。这里是驱使目标进程调用FreeLibrary()API.这里需要考虑引用计数的问题(调用多少次loadlibrary()就有多少个引用计数)

dll卸载的步骤:

    1)hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,dwPID);

    使用CreateToolhelp32Snapshot()API可以获取加载到进程模块(DLL)信息将获取的hSnapshot句柄传递给Module32First()/Module32Next()函数后,即可以设置与MODULEENTRY32结构体相关的模块信息。

    2)获取目标进程句柄

hProcess=openProcess(PROCESS_ALL_ACCESS,FALSE,dwPID);

    根据目标进程ID来获取目标进程句柄。

    3)获取FreeLibrary()API地址

hModule=GetModuleHandle(L"kernel32.dll")
pThreadProc=GetProcessAddress(hModule,"FreeLibrary");

    4)在目标进程中运行线程

hThread=CreateRemoteThread(hProcess,NULL,0,pThreadProc,ModBaseAddr,0,NULL)

pThreadProc参数使FreeLibrary()API地址,me.modBaseAddr参数使要卸载的DLL的加载地址。


4.通过修改PE加载DLL

   可以通过手动修改可执行文件的方法加载用户只当的DLL文件,这种方法只要运行依次后,每当进程开始运行时就会自动加载指定的DLL文件。修改PE文件主要是修改对应的import tabel.这里涉及到对应的idt表的移动问题,确定移动的目标位置的时候,又下面三种

方式:

    1)查找文件种的空白区域

    2)增加文件最后一个节区的大小

    3)在文件末尾尝试添加新节区


5.代码注入技术

    代码注入是一种向目标进程插入独立运行的代码并使之运行的技术,一般使用CreateRemoteThread()API以远程线程的方式运行插入代码,所以也称为线程注入。首先向目标进程插入代码与数据,在此过程种,代码以线程过程的形式插入,而代码种使用的数据以线程参数的形式传入。也就是说,代码与数据是分别注入的。

    使用代码注入的原因:

    1)占用内存少

    要注入的代码与数据较少,不需要做成dll的形式再注入,直接采用代码注入的方式能够取得和DLL注入同样的效果,且占用的内存会更少。

    2)难以查找痕迹

    采用dll注入会在目标进程中留下相关哼唧,很容易让人判断目标进程是否被执行过注入操作。采用代码注入的方式几乎不会留下任何痕迹。调试代码注入的时候让选择od的option->debugging option ->events->break on new thread.来进行调试。这里计算函数大小的方法直接只用的两个函数的地址

    进行运算:因为在visual c++中使用release模式编译程序代码后,源代码中函数顺序与二进制代码中的顺序是一致的。


6.api钩取技术

    api(application programming interface,应用程序编程接口)。windows系统资源都是由windows os系统直接管理的,用户不能直接访问。用户需要访问系统资源的时候,必须向系统内核(kernel)申请,申请的方法就是使用微软提供的win32api.为了实际运行相应的程序代码,需要加载许多系统库(DLL).所有的进程都会默认加载kernel32.dll,kernel32.dll又会加载ntdll.dll库。

    用户模式中的应用程序访问系统资源的时候,由ntdll.dll向内核模式提出访问请求。一般常规系统资源的api会经由kernel32.dll与ntdll.dll不断向下调用,最后同故宫SYSENTRY命令进入内核模式。

    使用api钩取技术可以实现对某些win32API 的嗲用过程拦截,并获得相应的控制权限,使用api钩取技术的优势如下:

    1)再api调用前/后运行用户的”钩子“代码。

    2)查看或操作传递给api的参数或者传递api的返回值

    3)取消对api的调用,或者更改执行流程,运行用户代码

   下表列出了api钩取的技术图表:

    根据对象的不同,api钩取可以分为静态方法与动态方法。静态方法是针对的是“文件”,而动态方法针对的是进程内存。根据钩取的位置不懂,可以分为:

    1)iat,将iad内部的api地址更改为钩取函数地址,该方法的优点是实现起来非常见到那,缺点是不能钩取不在iat中的api.

    2)代码:系统库(*.dll)映射到进程内存时,从中查找api的实际嗲hi,并直接修改代码,具体的方法分为以下几种:使用jmp指令修改起始代码,覆写函数局部,仅更改必须部分的局部

    3)eat:将记录在dll中的eat中的api起始地址更改为钩取函数地址也可以实现api钩取。

    根据对应的技术不同,可以分为以下几种技术:

    1)调试技术

       调试法通过调试目标继承钩取api.调试器拥有被调试者的所有权限(执行控制,内存访问),所以可以向被调试进程的内存任意位置设置钩取函数。这里的调试器是用户编写的用来钩取的程序。在用户编写的程序中使用调试api附加到目标进程,然后设置钩取函数,这样,重启运行时就能偶完全实现api钩取了。也可以向现有的调试器(od,ida,windbg)使用自动化脚本,自动钩取api.

    2)注入

      向目标进程内存区域进行渗透测试的技术,根据注入对象的不同,可以分为dll注入与代码注入两种技术。

   a)dll注入

    使用dll注入即使可以使目标进程强制加载用户指定的dll文件。使用该技术的时候需要先在注入的dll中创建钩取代码与设置钩代码,然后再dllmain()中调用设置代码,注入的同时即可完成api钩取。

    b)代码注入

    代码注入技术广泛用于恶意代码,代码注入技术比dll注入技术更发达。


7.使用调试的api钩取技术

    调试器用来确认被调试者是否正确运行,发现程序错误。调试器能偶逐一执行被调试者的执行,拥有对寄存器与内存的所有访问权限。调试器的工作原理,调试进程经过注册以后,每当被调试者发生调试事件时,os就会暂停其运行,并向调试器报告相应的事件。调试器对相应的事件进行处理之后,使被调试者继续运行。

    1)一般的异常也属于调试事件

    2)若进程处于非调试,调试事件会由自身的异常处理或者os的异常处理机制中被处理掉。

    3)调试器无法处理或者不关心的异常处理最终由os处理。

下图介绍了相应的流程:

    调试器必须处理的是EXCEPTION_BREAKPOINT异常。断点对应的汇编指令为int3,ia_32指令为0xcc.代码运行到int3指令即中断运行,EXCEPTION_BREAKPOINT异常处理事件呗传送到调试器,此时调试器可以做多种处理。

    调试实现断点的方法非常简单,找到要设置断点的代码在内存中的起始地址,只要把第一个地址修改为0xcc就可以了。向继续调试,恢复原值就可以了。调试技术的流程:在调试器-被调试者状态下,将被调试者的api起始地址修改为0xcc,控制权转移到调试器后执行指定的操作,最后使被调试者重新进入运行状态。具体的调试流程:对想钩取的进程进行附加操作,使之称为被调试者;“钩子”:将api起始地址的第一个字节修改为0xcc;调用相应的api时,控制权转移到调试者;执行相应的操作(操作参数,返回值等);脱钩:将0xcc恢复为原值(为了正常运行API);运行相应的API无0xcc的正常状态);"钩子“,再次修改为0xcc(为了继续钩取);控制权被返还给被调试者。

      DebugActiveProcess(DWORD dwProcess)API 将调试器附加到该运行的进程上,开始调试。使用GetThreadContext()API获取线程上下文。

 

8.更改idt进行api钩取

     通过dll注入技术啦钩取某个api,dll注入目标进程后,修改idt来更改进程中调的特定的api的功能。钩取的方法是将要钩取的钩取的api在用户的dll中重新定义,然后再注入目标进程。

     寻找idt的代码:

hMod=GetModule(NUL) //hMod=ImageBase
pAddr=(PBYTE)hMod;     //pAddr=ImageBase
pAddr+=*((DWORD*)&pAddr[0x30]);//pAddr="PE" signature
dwRVA=*((DWORD*)&pAddr[0x80]);//dwRVA=RVA of  IMAGE_IMPORT_
pImportDest=(PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

    隐藏进程

    通过修改api代码(code path)实现api 钩取的技术)。api代码修改技术就是讲api代码的前5个字节修改为JMP xxxxxxx指令来钩取

API。调用指定被钩取API时,修改后的JMP XXXXXXXX指令就会被执行,转而控制hooking函数。

    可以通过钩取ZwQuerySystemInformation()来隐藏进程,可以获取所有进程的信息,形成一个链表(Linked list),操作该链表即可以隐藏相关进程。这里的JMP XXXXXXXX中的XXXXXXXX为相对地址不是绝对地址,通过上下文的方式可以计算出相应的相对地址

    XXXXXXXX=要跳转的地址-当前指定地址-当前指令长度(5)

   可以使用其它绝对地址跳转:

     1)PUSH+RET来进行绝对跳转

     2)MOV+JMP来进行绝对跳转

    热补丁技术,api起始代码的mov指令(2个字节)与其上方的5个nop指令(5个字节)合起来共7个字节的指令没有任何意义,可以用来进行API钩取。二次跳转,首先将API起始代码的5个字节修改为FAR JMP指令,跳转到用户钩取函数处,然后将api起始代码的2个字节修改为shot jmp指令。该shot  jmp指令用来跳转到前面的far jmp指令处。不需要进行脱钩挂钩操作。

      自动钩取就是将dll自动注入新创建的程序当中。



这里只是自己看《逆向工程核心原理》根据书中的内容做的一些简单的总结



2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!

最新回复 (2)
supersoar 2020-1-8 17:39
2
0
不错 感谢分享。
niuzuoquan 2020-2-4 18:53
3
0
mark
游客
登录 | 注册 方可回帖
返回