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自动注入新创建的程序当中。
这里只是自己看《逆向工程核心原理》根据书中的内容做的一些简单的总结