前两天看了火绒安全实验室写的一篇分析360安全卫士极速版“诱导式”推广的文章,里面提到了一个下载安装模块360ini.dll会注入Windows资源管理器explorer.exe进程,利用系统进程下载360安装包,如下图所示。不过文章中仅仅只提到了360ini.dll模块会区分32位和64位的explorer进程进行注入,对于核心的注入技术细节没有再进一步深入,因此这篇文章尝试通过动静态等方式分析360选择的注入explorer的技术方式。

原文在这里:
先说结论:
还是利用NtSuspendThread、NtSetContextThread、NtResumeThread等组合函数劫持explorer负责消息处理的UI线程执行流,不过在shellcode执行结束后会将控制权返还给explorer。
火绒已经分析出了x86、x64注入的核心函数,参数为目标进程pid。

跟进x64的函数看一下,首先是一堆栈上赋值操作,推测应该是用了。这一大段数据猜测是后续用于注入的shellcode。

随后调用openprocess、openthread函数打开目标线程。

选择目标线程的时候则是利用GetThreadTimes函数获取线程创建时间,返回最早创建的线程tid。

之所以选择该线程可能是因为大部分时候都是处于WaitMessage等待事件消息的状态。

获取目标线程pid后两次调用常规函数跨进程申请内存、写内存。第一次写注入dll的路径,第二次写加载dll的shellcode1。

动态调试可以看到申请了地址0x3a20000大小64K的读写内存。

并向其中写入了注入dll的路径。

第二次申请了地址为0x2b2000大小64K的可执行内存。


写完dll路径和shellcode1后,进入sub_10003D50核心注入函数。首先挂起目标线程、获取目标线程的寄存器等上下文。随后继续申请内存写shellcode2。最后在改写完shellcode2和寄存器上下文之后设置线程将要执行的上下文,恢复目标线程。

下图可看到申请的shellcode2地址为0x3a30000,功能为创建到0x2b2000的shellcode1的线程。

这里可以看到原始的线程上下文EIP为0x773F932A指向user32.dll空间,将其修改为0x3A30000即shellcode2的地址。

最后一步设置目标线程的上下文,恢复线程即完成x64的整个注入explorer进程操作。
最后看下两段shellcode,shellcode2也就是劫持了原始执行流的shellcode调用了CreateThread函数,目标地址为shellcode1,参数为dll路径。

并在创建完成后返回到原始执行流也就是user32.dll空间下的0x773F932A地址。

shellcode1则直接调用LoadLibraryW在explorer中加载dll。

到这x64注入explorer进程的流程基本就结束了。下载的安装包证据如图。

不过最后还有一个疑点,shellcode2的地址申请的是读写权限的内存,不知道为啥执行的时候内存就变成读写可执行的了。这一点很疑惑,有没有大佬知道为啥的。
360ini.dll本身有反调试手段,如果后续想要动态调试的话需要patch或者动态过掉两个TLS回调函数,并且在dllmain中过掉这两个反调试函数。

360ini.dll为了阻碍静态分析,系统api大部分都是在dll加载时动态获取动态调用的,单纯用ida很难分析,并且某些关键api函数还利用了类似于wrapper层去调用ntdll的系统调用。不过好在静态代码还能看,没有上花里胡哨的混淆或者壳之类的。
sha256:f3690fae9bec82693a771f5f5839239ca18f90624a6aef631dcd0f19820eb38a