前言
HOOK(钩子) 其实就是改变程序执行流程的一种技术的统称
Inline hook
在windows系统下编程,应该会接触到api函数的使用,常用的api函数大概有2000个左右。今天随着控件,stl等高效编程技术的出现,api的使用概率在普通的用户程序上就变得越来越小了。当诸如控件这些现成的手段不能实现的功能时,我们还需要借助api。最初有些人对某些api函数的功能不太满意,就产生了如何修改这些api,使之更好的服务于程序的想法,这样api hook就自然而然的出现了。我们可以通过api hook,改变一个系统api的原有功能。基本的方法就是通过hook“接触”到需要修改的api函数入口点,改变它的地址指向新的自定义的函数
exe 调用函数,正常情况下直接找到dll导出函数,那么如果是hook,就把dll导出的这个函数进行一顿操作,把他首地址5个字节改了,exe因为这个被跳转了,跳转到我们的hook函数里面,
利用这个技术,我们可以监控API,比如应用程序会调用loadLibrary,那我们把它Hook了,
把Dll路径改成我们的,那加载的就是我们的dll了,当然 Hook的API很多,因为只要是Windows的API都能HOOK因为JMP的时候占五个字节,而WindowsAPI也是头部弄了五个字节,猜想可能是Windows自己留作Hook的.
inline Hook的步骤
我们先看一个简单的例子,我这里人为改变程序的流程,在add函数开头添加hook, add函数调用之前先调用fake函数 , 这里要提到两个函数。VirtualProtect和WriteProcessMemory.
1 |
|
方式以下:
我们要获取messagebos的函数地址
调用GetModuleHandlle,获取Dll模块(user32.dll的)的基地址
通过基地址,调用GetProcAddress获得Msg的函数地址
重定位跳转地址 Dest - MsgCode = offset -5 jmp 跳转到这个偏移即可
.跳转回来的时候则是 dest + offset + 5 = Msgcode +5的位置
IAT hook
改 IAT 表中对应的地址,来达到我们执行自己但是代码的目的
我们构造导入表的时候,将 IAT 表和 INT 表都指向的是函数名称所在的位置,然后在运行的时候,IAT 表中的内容会被替换成对应函数的地址,在调用的时候使用间接 CALL ,来调用其中所储存的地址。
IAT表在pe文件加载前他和INT表结构一样,都指向IMAGE_IMPORT_BY_NAME这个表,
首先操作系统会通过 Name 字段找到当前导入表的名字,然后调用 LoadLibrary 得到句柄,如果没有找到的话会提示找不到 dll 文件, 接着会根据 OriginalFirstThunk 找名字,OriginalFirstThunk 所指向的也就是咱们前面所说的 INT 表,通过 INT 表中的 RVA 地址来找函数的名字。
当然,这里也不一定存储的就是名字,也可能是导出序号,如果是导出序号的话,就会直接调用 GetProcAddress 得到函数地址,然后填写到 FirstThunk 所指向的对应位置,也就是 IAT 表中的对应位置。
如果存储的是名字的话,也就是指向了_IMAGE_IMPORT_BY_NAME结构,跳过第一个的Hint字节,就得到了调用函数的名字,接着还是调用 GetProcAddress 得到函数地址,填写到对应的位置。
pe文件加载前
PE加载后
既然我们明白了 IAT 表的填写方法,那么就可以通过更改 IAT 表中对应的地址,来达到我们执行自己但是代码的目的,同时为了保证原函数的功能不受影响,还需要进行一些其他的处理。 为了能更好的理解原理,我们采用在函数内部 HOOK 自己的方式来进行,如果想要 HOOK 其他程序的话,可以通过注入等方式来进行。
IAT hook思路
我们使用MessageBox函数来举例,为了保证原来的函数不受影响,我们先将函数的地址保存下来,获得函数地址的方法就是前面所描述的过程
接下来就需要找到MessageBox函数所对应在IAT表中的位置,因为已经得到了函数的地址
,那么我们就可以通过对比 IAT 表中所指向的地址与当前地址是否一致,来判断是不是我们想要找的函数。
在对比之前,首先要做的就是找到导入表所在的位置,这个就比较简单了,需要注意的是 Optional 头的大小不是固定的,需要根据 File 头中的SizeOfOptionalHeader来确定,然后通过数据目录中的第二项,就可以找到导入表所在的位置了
在获取句柄以后,把它赋值给了ImageBase变量,是因为所谓的句柄,实际上就是当前进程的起始位置,也就是在不考虑没有抢占到建议装载地址时候的基址。
在找到导入表之后,就需要进行遍历和对比了,因为导入表是依靠一个全零的结构来判断结束的,所以我们就采取对比FirstThunk和OriginalFirstThunk都为0时,来判断结束,然后在其中判断函数地址是否与我们所得到的原函数地址一致,如果一致说明找到了
接下来,还需要处理的就是我们 HOOK 以后需要进行的操作,为了保证程序的稳定,我们需要构造与被 HOOK 的函数一样结构的函数,同时为了保证原函数功能的正常运行,再定义一个函数指针,在自己的功能执行完成后,调用原来程序正常的功能。
1 |
|
SEH Hook
SSDT Hook
DLL注入技术
本身Hook本身一般来讲,没有太大意义,那么如何Hook其它进程?
- 若是使用的是SSDT hook,因为是在内核层,对全部的进程都是有效的。
- 若是是在应用层,如何Hook其它进程?咱们能够将咱们的函数放置到一个DLL中,将此DLL注入到其余进程中,这样其余进程调用DLL中的函数,将不会访问失败。
注入的方式有:
1、远程线程注入
2、APC注入
3、注册表注入
4、ComRes注入
5、劫持进程创建注入
6、输入法注入
7、消息钩子注入
8、依赖可信任进程注入
使用注册表来注入
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\ 中:
有一个键:AppInit_DLLs,能够包含一组DLL(逗号或空格分隔),将咱们的DLL路径写入此键值中。
建立一个名为LoadAppInit_DLLs,将其值设置为1.
当user32.dll被映射到一个新的进程时,会收到DLL_PROCESS_ATTACH通知。当user32.dll对它进行处理时,会取得上述注册表键值,并调用LoadLibrary载入这个键值存储的全部DLL。
缺点:
基于CUI的应用程序没有用到user32.dll,所以没法加载DLL。
我可能只想注入某些应用中,映射的越多,崩溃的可能性越大。
使用window 系统消息挂钩来注入
为了能让系统消息挂钩正常运行,Microsoft被迫设计出一种机制,让咱们能够将DLL注入到另外一进程空间中(想象一下:调用SetWindowsHookEx安装一个WH_GETMESSAGE钩子,若是让其余进程运行SetWindowsHookEx设置的函数?)
SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hInstDll,0);
GetMsgProc在调用此函数的进程空间中,假设为A进程。第三个参数为hInstDll,为当前进程空间中的一个DLL的句柄,此DLL包含了GetMsgProc函数,最后一个参数表示要给哪儿个线程安装,0表示给系统中全部的GUI线程安装挂钩。
接下来会发生什么:
- B进程的一个线程准备向一个窗口发送一条消息。
- 系统检测到已经安装了一个WH_GETMESSAGE钩子。
- 系统检测GetMsgProc所在的DLL是否已经被映射到进程B地址空间中。
- 若是还没有映射,系统会强制调用LoadLibrary强制将DLL映射到B进程地址空间中,DLL引用计数加一。
- DLL映射到B中的地址可能和A相同,也可能不一样。若是不一样,须要调整GetMsgProc的地址。
- GetMsgProc = hInstDll B + (GetMsgProcA – hInstDll A)
- 系统在进程B中地址该DLL的引用计数。
- 在B空间调用GetMsgProc,在此函数里,能够作API Hook处理,还能够建立一些消息机制,经过A进程进行功能控制。
使用远程线程来注入
线程注入,是通过开启远程线程的方式,将DLL加载到目标宿主进程中的常用方式。
首先Windows中链接库分为两种:
动态链接库DLL、静态链接库LIB。
① 静态链接库:在运行的时候就直接把代码全部加载到程序中,调用方式比如:
#prama comment (lib,”Psapi.lib”)
② 动态链接库:在需要的时候加载,加载方式为:
——使用LoadLibrary动态加载DLL
——使用GetProcAddress获取DLL中导出函数的指针
——最后用FreeLibrary卸载指定的DLL
动态链接库设计的目的是为了动态加载功能,动态地释放内存,节约内存,方便每一个程序的4GB内存的管理,4GB有2GB用来放系统内核,即系统大量的DLL。其本质上也是一个可以被加载的程序。同时系统对于DLL只会保存一份。
编写注入dll
vs 创建桌面向导,选择dll
关于DLL入口主函数第二个参数ul_reason_for_call,即DLL四种当前的状态:
创建头文件
1 |
|
Dllmain
1 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 |
导出函数
1 |
|
这里需要说一下注入的可行性:
流程:目标进程-传入DLL地址-开启远程线程-加载DLL-实现DLL注入
在另外一个进程中建立远程线程:
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
);
只要使用这个远程线程调用LoadLibrary,来加载咱们的DLL,就能达到DLL注入的目的。例如:
CreateRemoteThread(hProcessRemote,NULL, 0, LoadLibraryW,L”C:\MyLib.dll”, 0, NULL);
当远程线程在远程进程执行时,会当即调用LoadLibraryW,并传入DLL路径。
但并无这么简单
CreateRemoteThread(hProcessRemote,NULL, 0, LoadLibraryW,L”C:\MyLib.dll”, 0, NULL);
但存在两个问题:
- 第一个问题是:
上面咱们已经讲过LoadLibraryW是个导入函数,使用地址引用IAT的方式来调用,显然这个地址在不一样的PE文件里是不一样的。在其余进程中,这个地址是不同的。
对CreateRemoteThread调用,假定本进程和远程进程中,Kernel32.dll被映射到地址空间是同一内存。(虽然是假定,到目前为止,都是同一地址,重启会变,但启动后不变)。
能够改变为:
PTHREAD_START_ROUNTINE pfnThreadRtn = (PTHREAD_START_ROUNTINE)GetProcAddress(GetModuleHandle(_T(“Kernel32”)), “LoadLibraryW”);
CreateRemoteThread(hProcessRemote,NULL, 0, pfnThreadRtn,L”C:\MyLib.dll”, 0, NULL);
- 第二个问题:
L”C:\MyLib.dll”字符串在调用进程中,目标进程并无这个字符串,若是在目标程序执行,此地址则是执行一个未知地址,可能会崩溃。
解决这个问题的办法,须要把本地字符串放到远程进程去。调用VirtualAllocEx可让一个进程在另外一个进程中分配一块空间。WriteProcessMemory能够将字符串从本进程复制到远程进程中。
突破session 0的远线程注入
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1204342476@qq.com