内核态拦截用户模式代码注入
2014-11-24 21:16
302 查看
前段时间领导要求实现拦截恶意软件的代码注入(包括dll注入和代码Patch),需要兼容从Windows XP到Windows 8.1的所有PC版本和服务器版本操作系统,需要兼容杀毒软件。
当时拿到这个需求非常的头痛,因为需要拦截所有进程对其他进程的注入,而我们的软件本来就是安全软件,如果我再把代码注入到所有进程中去的话很有可能这个操作就被杀毒软件定性为恶意行为,而如果在内核中hook某些内核函数也不现实,因为PatchGuard的存在导致x64位内核函数Hook直接BSOD.
但是.....领导的需求,是必须满足的,不让领导失望是下属的最重要的准则之一。经过一个多星期的主动加班、几十次BSOD,终于搞定。最终的解决方案就是这篇日记的标题,下面详细的记录一下整个过程。
首先想到的是在ImageLoadNotification回调中检测dll.如果检测至是恶意dll,就直接写Patch Dllmain让其加载失败,但是很快就被自己否定了,因为我无法去判断一个dll是进程自己需要的dll,还是在被恶意注入。
然后想到从源头查起,在进程创建的时候,检测该进程中是否正在使用某些敏感函数,如CreateRemoteThread\QueueUserAPC等。如果有的话就inline hook它们。可是在调试过程中发现CreateProcessNotification回调被调用时EPROCESS的PEB还没有完全建立,取不到模块列表,当然也取不到函数地址,而且2003 64位上的32位进程的EPROCESS->Wow64Proess还与其他的64位版本不一样,多了一层指针,不知道微软当时为什么要这样设计。
后来把时机转到了SystemThread中才一切正常。
因为功能特殊,所以不得已用到了几个未公开的函数。
首先用MASM写两个程序用来实现hook敏感函数后跳板的功能。
编译后得到以下片段:
然后从4到一个很大的数进行PsLookupProcessByProcessId.然后如果没有被Patch过,那就ZwQueryInformationProcess 看该进程32位还是64位,ZwAllocateVirtualMemory一块大小至少能装下PatchCode的虚拟内存,再PsSuspendProcess。分两种情况进行函数查找(PE & PE32++)。
KeAttachProcess后复制上面的PatchCode到分配的虚拟内存中。修改PatchCode进行函数重定位。Inline Hook CreateRemoteThread QueyeUserAPC SetWindowsHookExA SetWindowsHookExW函数跳至PatchCode.然后PsResumeProcess。
而PatchCode中实现的功能很简单,将线程上下文通过DeviceIoControl发到我们的设备进行分析。然后根据分析结果决定是拦截还是继续执行。
必要的线程上下文信息定义如下:
32位 进程
64位进程
驱动中使用下面的结构:
有了线程上下文,就可以判断调用者传入的参数,从而拦截恶意的注入。其实通过这个方法可以做更多行为的判断而不仅仅是拦截注入。
这种方法优点是没有HOOK内核函数,没有dll,可以在64位环境中运行,缺点是使用了未公开的函数,需要细心的定位。
欢迎加QQ群:333483823进行技术讨论.
当时拿到这个需求非常的头痛,因为需要拦截所有进程对其他进程的注入,而我们的软件本来就是安全软件,如果我再把代码注入到所有进程中去的话很有可能这个操作就被杀毒软件定性为恶意行为,而如果在内核中hook某些内核函数也不现实,因为PatchGuard的存在导致x64位内核函数Hook直接BSOD.
但是.....领导的需求,是必须满足的,不让领导失望是下属的最重要的准则之一。经过一个多星期的主动加班、几十次BSOD,终于搞定。最终的解决方案就是这篇日记的标题,下面详细的记录一下整个过程。
首先想到的是在ImageLoadNotification回调中检测dll.如果检测至是恶意dll,就直接写Patch Dllmain让其加载失败,但是很快就被自己否定了,因为我无法去判断一个dll是进程自己需要的dll,还是在被恶意注入。
然后想到从源头查起,在进程创建的时候,检测该进程中是否正在使用某些敏感函数,如CreateRemoteThread\QueueUserAPC等。如果有的话就inline hook它们。可是在调试过程中发现CreateProcessNotification回调被调用时EPROCESS的PEB还没有完全建立,取不到模块列表,当然也取不到函数地址,而且2003 64位上的32位进程的EPROCESS->Wow64Proess还与其他的64位版本不一样,多了一层指针,不知道微软当时为什么要这样设计。
后来把时机转到了SystemThread中才一切正常。
因为功能特殊,所以不得已用到了几个未公开的函数。
首先用MASM写两个程序用来实现hook敏感函数后跳板的功能。
编译后得到以下片段:
char PatchCodeX86[] = "\x55\x8b\xec\x83\xc4\xdc\x89\x5d\xdc\x89\x75\xe0\x89\x7d\xe4\xeb" "\x3b\x00\x00\x00\x00\x8b\x5d\xdc\x8b\x75\xe0\x8b\x7d\xe4\xc9\x90" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x68\x44\x33\x22\x11\xc3\x5c\x5c\x2e\x5c\x61\x68\x5f\x61\x6e\x74" "\x69\x5f\x69\x6e\x6a\x65\x63\x74\x69\x6f\x6e\x00\x89\x5d\xe8\x89" "\x6d\xec\x89\x65\xf0\x6a\x00\x68\x80\x00\x00\x00\x6a\x03\x6a\x00" "\x6a\x03\x68\xff\x01\x1f\x00\xe8\x6e\x00\x00\x00\x83\xe8\x36\x50" "\xe8\x65\x00\x00\x00\x83\xc0\x0a\x50\x68\x44\x33\x22\x11\xc3\x89" "\x45\xfc\x83\xf8\xff\x74\x4e\x6a\x00\x8d\x45\xf8\x50\x6a\x04\x8d" "\x45\xf4\x50\x6a\x0c\x8d\x45\xe8\x50\x68\x04\x40\x22\x00\xff\x75" "\xfc\xe8\x34\x00\x00\x00\x83\xc0\x0a\x50\x68\x44\x33\x22\x11\xc3" "\xff\x75\xfc\xe8\x22\x00\x00\x00\x83\xc0\x0a\x50\x68\x44\x33\x22" "\x11\xc3\x83\x7d\xf4\x00\x74\x0d\xc9\x59\xba\x1c\x00\x00\x00\x03" "\xe2\x51\x33\xc0\xc3\xe9\x3b\xff\xff\xff\x58\xff\xe0"; char PatchCodeX64[] = "\x55\x48\x8b\xec\x48\x81\xc4\x68\xff\xff\xff\x48\x89\x8d\x68\xff" "\xff\xff\x48\x89\x95\x70\xff\xff\xff\x4c\x89\x85\x78\xff\xff\xff" "\x4c\x89\x4d\x80\x4c\x89\x65\x88\x4c\x89\x6d\x90\x4c\x89\x75\x98" "\x4c\x89\x7d\xa0\x48\x89\x5d\xa8\x48\x89\x75\xb0\x48\x89\x7d\xb8" "\xeb\x71\x00\x00\x00\x00\x00\x00\x00\x00\x48\x8b\x8d\x68\xff\xff" "\xff\x48\x8b\x95\x70\xff\xff\xff\x4c\x8b\x85\x78\xff\xff\xff\x4c" "\x8b\x4d\x80\x4c\x8b\x65\x88\x4c\x8b\x6d\x90\x4c\x8b\x75\x98\x4c" "\x8b\x7d\xa0\x48\x8b\x5d\xa8\x48\x8b\x75\xb0\x48\x8b\x7d\xb8\xc9" "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" "\x49\xba\x88\x77\x66\x55\x44\x33\x22\x11\x41\x52\xc3\x5c\x5c\x2e" "\x5c\x61\x68\x5f\x61\x6e\x74\x69\x5f\x69\x6e\x6a\x65\x63\x74\x69" "\x6f\x6e\x00\x48\x89\x4d\xc0\x48\x89\x55\xc8\x4c\x89\x45\xd0\x4c" "\x89\x4d\xd8\x48\x89\x65\xe0\x57\x48\x83\xec\x40\xe8\xd4\x00\x00" "\x00\x48\x8d\x48\xcc\xba\xff\x01\x1f\x00\x41\xb8\x03\x00\x00\x00" "\x45\x33\xc9\x48\xc7\x44\x24\x30\x00\x00\x00\x00\xc7\x44\x24\x28" "\x80\x00\x00\x00\xc7\x44\x24\x20\x03\x00\x00\x00\xe8\xa4\x00\x00" "\x00\x4c\x8b\xd0\x49\x83\xc2\x15\x41\x52\x48\xb8\x88\x77\x66\x55" "\x44\x33\x22\x11\xff\xe0\x48\x89\x45\xf8\x48\x83\xf8\xff\x74\x6e" "\x48\xc7\x44\x24\x38\x00\x00\x00\x00\x48\x8d\x45\xf0\x48\x89\x44" "\x24\x30\xc7\x44\x24\x28\x08\x00\x00\x00\x48\x8d\x45\xe8\x48\x89" "\x44\x24\x20\x41\xb9\x28\x00\x00\x00\x4c\x8d\x45\xc0\xba\x04\x40" "\x22\x00\x48\x8b\x4d\xf8\xe8\x4a\x00\x00\x00\x4c\x8b\xd0\x49\x83" "\xc2\x15\x41\x52\x48\xb8\x88\x77\x66\x55\x44\x33\x22\x11\xff\xe0" "\x48\x8b\x4d\xf8\xe8\x2c\x00\x00\x00\x4c\x8b\xd0\x49\x83\xc2\x15" "\x41\x52\x48\xb8\x88\x77\x66\x55\x44\x33\x22\x11\xff\xe0\x48\x83" "\xc4\x40\x5f\x48\x8b\x45\xe8\x48\x85\xc0\x0f\x84\xaa\xfe\xff\xff" "\x48\x33\xc0\xc9\xc3\x58\xff\xe0";
然后从4到一个很大的数进行PsLookupProcessByProcessId.然后如果没有被Patch过,那就ZwQueryInformationProcess 看该进程32位还是64位,ZwAllocateVirtualMemory一块大小至少能装下PatchCode的虚拟内存,再PsSuspendProcess。分两种情况进行函数查找(PE & PE32++)。
KeAttachProcess后复制上面的PatchCode到分配的虚拟内存中。修改PatchCode进行函数重定位。Inline Hook CreateRemoteThread QueyeUserAPC SetWindowsHookExA SetWindowsHookExW函数跳至PatchCode.然后PsResumeProcess。
而PatchCode中实现的功能很简单,将线程上下文通过DeviceIoControl发到我们的设备进行分析。然后根据分析结果决定是拦截还是继续执行。
必要的线程上下文信息定义如下:
context struct _ebx dd ? _ebp dd ? _esp dd ? context ends
32位 进程
context struct _rcx dq ? _rdx dq ? _r8 dq ? _r9 dq ? _rsp dq ? context ends
64位进程
驱动中使用下面的结构:
struct _X86ThreadContext { DWORD Ebx; DWORD Ebp; DWORD Esp; }; struct _X64ThreadContext { UINT64 Rcx; UINT64 Rdx; UINT64 R8; UINT64 R9; UINT64 Rsp; };
有了线程上下文,就可以判断调用者传入的参数,从而拦截恶意的注入。其实通过这个方法可以做更多行为的判断而不仅仅是拦截注入。
这种方法优点是没有HOOK内核函数,没有dll,可以在64位环境中运行,缺点是使用了未公开的函数,需要细心的定位。
欢迎加QQ群:333483823进行技术讨论.
相关文章推荐
- 内核代码中和用户栈相关的几个片段
- 任意用户模式下执行 ring 0 代码
- 用户模式与内核模式
- 使用用户模式linux(UML, User mode linux)来进行内核Debug
- 用户模式与内核模式(2)
- 转载kernet的永恒国度——任意用户模式下执行Ring代码
- Linux用户模式和内核模式
- WINCE下内核模式和用户模式有什么区别
- 若要调试此模块,请将其项目生成配置更改为“调试”模式。若要取消显示此消息,请禁用“启动时若没有用户代码则发出警告”调试器选项。
- Communication Between User Mode and Kernel Mode 用户模式和内核模式间的通信
- 用APC实现在内核模式运行用户程序
- 任意用户模式下执行 ring 0 代码
- windbg 如何再内核模式调试用户空间的程序
- 用户模式和内核模式的区别
- linux内核初始化及启动之用户模式开始
- 使用ZwSystemDebugControl的简易用户模式Rootkit检测器代码
- 若要调试此模块,请将其项目生成配置更改为“调试”模式。若要取消显示此消息,请禁用"启动时若没有用户代码则发出警告"调试器选项。
- 任意用户模式下执行 ring 0 代码
- Windows 7驱开发系列(二)--用户模式与内核模式
- 用户模式与内核模式