您的位置:首页 > 编程语言

内核态拦截用户模式代码注入

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敏感函数后跳板的功能。

 

编译后得到以下片段:

 

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进行技术讨论.

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: