旧文迁移,逆向分析之”翠花”,其实开始学这个还是因为被卫士管家坑,它勾了API使进程即使有系统权限也会操作受限,只有学原理再把它勾回来哦,当然现在还只是用户层的勾取,其实原理就是改变原来API的入口,让它转到目的地址执行后再继续,可以控制程序流程。
技术图表
如图,他们相互组合就死一种钩取方式:
动态-进程内存-代码-调试技术
直接看代码就能理解了,不多说,这里直接上记事本WriteFile
的勾取代码,此API的作用是将数据写入文件,勾取做的操作是若发现小写字母,将其替换成大写字母,于是保存在文件中的字符串就不存在小写字符了。这里使用的是钩取API:
首先,需要知道操作的数据存放在哪里?根据MSDN:
1 | BOOL WriteFile( |
(其他一些基础知识就不复制了,可以看这个)调用API时,数据存放在ESP+4的位置,数据长度放在ESP+8的位置。然后。。上代码:
1 |
|
动态-进程内存-IAT-注入dll- CreateRemoteThread
它是修改进程的IAT,将原函数的地址替换成新函数的地址,在新函数中对输入参数做处理后再调用原函数,如图:
(钩取前)
(钩取后)
还时直接上代码:
1 |
|
这是主要代码,作用是当DLL载入时进行钩取:替换原函数为自定义函数;当DLL卸载时进行脱钩:将新函数替换为原函数地址。下面的没有敲了,就是之前学过的dll注入工具的代码,copy自书上:
1 |
|
动态-进程内存-注入-DLL注入- CreateRemoteThread
隐藏进程:
ZwQuerySystemInformation
获取进程列表的函数有很多,但是一般最终都是调用ZwQuerySystemInformation获取进程列表(这是个低级的函数,win8后使用特定分开的函数了),所以学习时勾取这个函数,根据MSDN:
1 | NTSTATUS WINAPI ZwQuerySystemInformation( |
这个函数可以查询多种系统信息,从参数中可以看出来,第一个参数就是定义要查询什么信息,第二个是用来存放结果的,它的大小由第一个参数决定,不同类型的信息有不同的大小,若是不知道可以将第二三个参数指定为空,第四个参数将会返回需要的大小,此时再动态申请并再次调用即可,至于第一个参数:SystemInformationClass
为一个枚举类型(但是不能直接在vssdk里面看到,因为这个函数是为公开的,需要自己定义,这里就不全部列出来了)
1 | typedef enum _SYSTEM_INFORMATION_CLASS { |
同样,第二个参数结构也不能直接在sdk里面看到,需要自己定义,进程信息的结构体为:
1 | typedef struct _SYSTEM_PROCESS_INFORMATION { |
由于未公开,只能使用GetProcAddress
获取函数地址,要使用需要先定义函数结构:
1 | typedef NTSTATUS (WINAPI *PFZWQUERYSYSTEMINFORMATION) |
现在就可以使用此函数获取进程列表了,这也是要勾取的函数,新函数替代此函数后还需要调用此函数。
5字节勾取:
与上一种修改IAT不同,这里是直接修改API入口处的5字节代码,将其修改为远跳转:
(钩取前)
(钩取后)
实现代码如下:
1 |
|
7字节钩取
5字节代码钩取使用范围很广,但是存在一个问题就是多线程时,若在正在钩取时执行会抛出异常,于是根据部分API的特性有了7字节勾取方法-热补丁勾取:
在比较上层的库中:
User32.dll
Gdi32.dll
Kernel.dll
Ntdll.dll
可以看到在前三个库中,每个函数开始前都有7个字节是准空指令,无实际用途,那么就可以在5个Nop指令里面写远跳转指令,在mov reg,reg
里面写近跳转,跳转到远跳转指令处,那么在新函数中就不用再脱钩+钩取了,可以直接调用原函数地址向后偏移2字节处,下面以CreateProcess
函数为例(因为ZwSystemInformation属于Ntdll内,不能使用这种方法钩取):
1 | BOOL hook_by_hotpatch(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew) |
新函数中为:
1 | typedef BOOL(WINAPI *PFCREATEPROCESSA)( |
高级全局钩取
要进行全局钩取,需要钩取已存在的所有进程和新建的所有进程。
钩取已存在进程
还是利用RemoteCreateThread
函数,之前是指定PID和指定进程名,记得指定进程名的时候最后还是通过遍历进程列表匹配进程名最终获得进程ID,再使用PID来注入DLL,现在注入所有进程可以说更简单,就是遍历时不判断,无条件注入:
1 | typedef void(*PFN_SetProcName)(LPCTSTR szProcName); |
钩取新创建的进程
创建进程一般使用CreateProcess(A/W)
函数,他们在内部又分别调用了CreateProcessInternal(A/W)
函数,为了尽量不遗漏需要全部钩取他们,而这些函数一定是被某个进程调用的,于是就可以在被注入的dll中添加上钩取这些API的函数,这里比之前的注入简单一些,不需要通过pid再OpenProcess获得进程句柄,CreateProcess
创建子进程成功后会返回子进程句柄,于是直接拿去用就好了:
1 | bRet = ((PFCREATEPROCESSA)pFunc)(lpApplicationName, |
现在就实现全局钩取了:
记事本已经从进程浏览器里面消失:
新建的进程也无法看到notepad进程:
低级全局钩取
上面是高级钩取,钩取的是CreateProcessA/W,CreateProcessInternalA/W
这四个函数,它们属于比较高级的函数,已经文档化了,比较稳定,一般不会改变,但是他们存在一些问题,观察钩取部分:
可以看到,钩取发生在创建子进程后,这样可能导致钩取不及时,而且还有些其他问题,例如可能程序是调用其他API,幸运的是逆向大神已经发现了这四个函数最终都会调用到ZwResumeThread
,更多的,子进程创建完成后会被挂起,此函数会恢复挂起进程,钩取它可以在子进程运行前注入DLL,这就是低级钩取,当然他也有缺点,看长相就知道这个也是没有文档化的函数,可能在其他系统会发生变化。。。继续,为了篇幅,开始表演:
钩取函数
查找要钩取的函数的方法有很多,要么根据经验(nil),要么使用API监视器,要么就是调试跟踪,这个例子是钩取浏览器的API,使其当访问指定域名时重定向到另外的域名,作者的经验是钩取InternetConnect
:
1 | HINTERNET InternetConnect( |
第二个是服务名,可以是域名型,也可以是IP型,现在使用od验证一下:
先设置断点,接着在浏览器输入betamao.me进行访问:
发现中断在了入口处,至少说明的确调用了此函数,此时将堆栈指向的内容改成baidu.com
看到自动重定向到了www.baidu.com
说明这的确是需要钩取的API,至于钩取方法还是昨天学的那个方法,好了,字数凑得差不多了,开始正事。
钩取代码
这里使用的为5字节代码钩取,其实热补丁方式也是可以的,看代码也很简单:
1 | HINTERNET WINAPI NewInternetConnectW |
低级钩取
这是对新进程注入部分,就是先注入再调用ZwResumeThread
,由于这个函数位于NtDll
,不能使用热补丁
1 | NTSTATUS WINAPI NewZwResumeThread(HANDLE ThreadHandle, PULONG SuspendCount) |
书上作者不仅对每一步调用结果做了判断,并输出调试信息,而且它的注入代码也有改进,之前遇到过,注入win7失败,其实是因为内部发生了变化,不能直接使用RemoteCreateThread
需要注入更底层的API,参数发生了细微变化,于是这里作者也对目标系统做出了判断,自动切换了API:
1 | //判断系统版本 |
于是可以查看注入”效果图”:
来源
逆向工程核心工程