Win64 驅動內核編程-7.內核裏操做進程

在內核裏操做進程編程

    在內核裏操做進程,相信是不少對 WINDOWS 內核編程感興趣的朋友第一個學習的知識點。但在這裏,我要讓你們失望了,在內核裏操做進程沒什麼特別的,就標準方法而言,仍是調用那幾個和進程相關的 NATIVE API 而已(固然了,本文所說的進程操做,還包括對線程和 DLL 模塊的操做)。本文包括 10 個部分:分別是:枚舉進程、暫停進程、恢復進程、結束進程、枚舉線程、暫停線程、恢復線程、結束線程、枚舉 DLL 模塊、卸載 DLL 模塊。函數

    1.枚舉進程。進程就是活動起來的程序。每個進程在內核裏,都有一個名爲 EPROCESS 的巨大結構體記錄它的詳細信息,包括它的名字,編號(PID),出生地點(進程路徑),老爹是誰(PPID 或父進程 ID)等。在 RING3 枚舉進程,一般只要列出全部進程的編號便可。不過在 RING0 裏,咱們還要把它的身份證(EPROCESS)地址給列舉出來。順帶說一句, 現實中男人最怕的事情 就是「 喜當爹」 , 這種事情在內核裏更加容易發生。由於 EPROCESS  裏 有 且只有 一個 成員 是記錄父進程 ID  的,稍微改一下,就能夠認任意進程爲爹了。枚舉進程的方法不少,標準方法是使用 ZwQuerySystemInformation 的 SystemProcessInformation 功能號,不過若是在內核裏也這麼用的話,那就真是脫了褲子放屁——畫蛇添足。由於在內核裏使用這個函數照樣是得不到進程的 EPROCESS 地址,並且一旦內存出錯,還會藍屏,更加逃不過任何隱藏進程的手法。 因此在 內核裏 穩定 又不失 強度 的 枚舉 進程方法舉 是枚舉 PspCidTable , 它能最大的好處是能獲得進程的 EPROCESS  地址 , 並且 能 檢查出 使用「 斷鏈 」 這種低級 手法 的隱藏進程。不過話也說回來,枚舉 PspCidTable 並非一件很爽的事情,由於 PspCidTable  是一個 不公開的變量,要 得到它地址 的話,必然 要 使用硬編碼或者符號。因此 個人 方法是:變相枚舉 PspCidTable。內核裏有個函數叫作 PsLookupProcessByProcessId,它能經過進程 PID 查到進程的 EPROCESS,它的內部實現正是枚舉了 PspCidTable。PID 的範圍是從 4 開始,到MAX_INT(2^31-1)結束,步進爲 4。但實際上,你們見到的 PID 基本都是小於 10000 的,而上 10000 的 PID 相信不少人都沒有見過。因此咱們實際的枚舉範圍是 4~2^18,若是PsLookupProcessByProcessId 返回失敗,則證實此進程不存在,若是返回成功,則把 EPROCESS、PID、PPID、進程名打印出來。學習

 

    1.枚舉進程
    //聲明 API
    NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process);
    NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process);
    //根據進程 ID 返回進程 EPROCESS,失敗返回 NULL
    PEPROCESS LookupProcess(HANDLE Pid)
    {
    PEPROCESS eprocess = NULL;
    if (NT_SUCCESS(PsLookupProcessByProcessId(Pid, &eprocess)))
    return eprocess;
    else
    return NULL;
    }
    //枚舉進程
    VOID EnumProcess()
    {
    ULONG i = 0;
    PEPROCESS eproc = NULL;
    for (i = 4; i<262144; i = i + 4)
    {
    eproc = LookupProcess((HANDLE)i);
    if (eproc != NULL)
    {
    DbgPrint("EPROCESS = %p, PID = %ld, PPID = %ld, Name = %s\n",
    eproc,
    (DWORD)PsGetProcessId(eproc),
    (DWORD)PsGetProcessInheritedFromUniqueProcessId(eproc),
    PsGetProcessImageFileName(eproc));
    ObDereferenceObject(eproc);
    }
    }
    }
     
    2.暫停進程。暫停進程就是暫停進程的活動,可是不將其殺死。暫停進程在 VISTA 以後有導
    出的函數:PsSuspendProcess。它的函數原型很簡單:
    NTKERNELAPI //聲明要使用此函數
    NTSTATUS //返回類型
    PsSuspendProcess(PEPROCESS Process); //惟一的參數是 EPROCESS
     
    3.恢復進程。恢復進程就是讓被暫停進程的恢復活動,是上一個操做的反操做。恢復進程在
    VISTA 以後有導出的函數:PsResumeProcess。它的函數原型很簡單:
    NTKERNELAPI //聲明要使用此函數
    NTSTATUS //返回類型
    PsResumeProcess(PEPROCESS Process); //惟一的參數是 EPROCESS
     
    4.結束進程。結束進程的標準方法就是使用 ZwOpenProcess 打開進程得到句柄,而後使用
    ZwTerminateProcess 結束,最後使用 ZwClose 關閉句柄。除了這種方法以外,還能用使用內
    存清零的方式結束進程,後者使用有必定的危險性,可能在特殊狀況下發生藍屏,但強度比
    前者大得多。在 WIN64 不能夠搞內核 HOOK 的大前提下,後者能夠結束任何被保護的進程。
     
    //正規方法結束進程
    void ZwKillProcess()
    {
    HANDLE hProcess = NULL;
    CLIENT_ID ClientId;
    OBJECT_ATTRIBUTES oa;
    //填充 CID
    ClientId.UniqueProcess = (HANDLE)2908; //這裏修改成你要的 PID
    ClientId.UniqueThread = 0;
    //填充 OA
    oa.Length = sizeof(oa);
    oa.RootDirectory = 0;
    oa.ObjectName = 0;
    oa.Attributes = 0;
    oa.SecurityDescriptor = 0;
    oa.SecurityQualityOfService = 0;
    //打開進程,若是句柄有效,則結束進程
    ZwOpenProcess(&hProcess, 1, &oa, &ClientId);
    if (hProcess)
    {
    ZwTerminateProcess(hProcess, 0);
    ZwClose(hProcess);
    };
    }
     
    內存清0方式結束進程
    NTKERNELAPI VOID NTAPI KeAttachProcess(PEPROCESS Process);
    NTKERNELAPI VOID NTAPI KeDetachProcess();
    //內存清零法結束進程
    void PVASE()
    {
    SIZE_T i = 0;
    //依附進程
    KeAttachProcess((PEPROCESS)0xFFFFFA8003ABDB30); //這裏改成指定進程的 EPROCESS
    for (i = 0x10000; i<0x20000000; i += PAGE_SIZE)
    {
    __try
    {
    memset((PVOID)i, 0, PAGE_SIZE); //把進程內存所有置零
    }
    _except(1)
    {
    ;
    }
    }
    //退出依附進程
    KeDetachProcess();
    }
    5.枚舉線程。線程跟進程相似,也有一個身份證同樣的結構體 ETHREAD 存放在內核裏,而它
    全部的 ETHREAD 也是放在 PspCidTable 裏的。因而有了相似枚舉進程的代碼:
    //根據線程 ID 返回線程 ETHREAD,失敗返回 NULL
    PETHREAD LookupThread(HANDLE Tid)
    {
    PETHREAD ethread;
    if (NT_SUCCESS(PsLookupThreadByThreadId(Tid, ðread)))
    return ethread;
    else
    return NULL;
    }
     
    //枚舉指定進程的線程
    VOID EnumThread(PEPROCESS Process)
    {
    ULONG i = 0, c = 0;
    PETHREAD ethrd = NULL;
    PEPROCESS eproc = NULL;
    for (i = 4; i<262144; i = i + 4)
    {
    ethrd = LookupThread((HANDLE)i);
    if (ethrd != NULL)
    {
    //得到線程所屬進程
    eproc = IoThreadToProcess(ethrd);
    if (eproc == Process)
    {
    //打印出 ETHREAD 和 TID
    DbgPrint("ETHREAD=%p, TID=%ld\n",
    ethrd,
    (ULONG)PsGetThreadId(ethrd));
    }
    ObDereferenceObject(ethrd);
    }
    }
    }
    6.掛起線程。相似於「掛起進程」,惟一的差異是沒有導出函數可用了。能夠自行定位
    PsSuspendThread,它的原型以下:
     
    NTSTATUS PsSuspendThread
    (IN PETHREAD Thread, //線程 ETHREAD
    OUT PULONG PreviousSuspendCount OPTIONAL) //掛起的次數,每掛起一次此值增 1
     
    7.恢復線程。相似於「恢復進程」, 惟一的差異是沒有導出函數可用了。能夠自行定位
    PsResumeThread,它的原型以下:
     
    NTSTATUS PsResumeThread
    (PETHREAD Thread, //線程 ETHREAD
    OUT PULONG PreviousCount); //恢復的次數,每恢復一次此值減 1,爲 0 時線程才正常
     
    8.結束線程。結束線程的標準方法是 ZwOpenThread+ZwTerminateThread+ZwClose,暴力方法
    是直接調用 PspTerminateThreadByPointer。暴力方法在後面的課程裏講,這裏先講標準方法。
    因爲 ZwTerminateThread 沒有導出,因此只能先硬編碼了(在 WINDBG 裏使用 x 命令得到地
    址:x nt!ZwTerminateThread):
     
    typedef NTSTATUS(__fastcall *ZWTERMINATETHREAD)(HANDLE hThread, ULONG uExitCode);
    ZWTERMINATETHREAD ZwTerminateThread = 0Xfffff80012345678; //要修改這個值
    //正規方法結束線程
    void ZwKillThread()
    {
    HANDLE hThread = NULL;
    CLIENT_ID ClientId;
    OBJECT_ATTRIBUTES oa;
    //填充 CID
    ClientId.UniqueProcess = 0;
    ClientId.UniqueThread = (HANDLE)1234; //這裏修改成你要的 TID
      //填充 OA
    oa.Length = sizeof(oa);
    oa.RootDirectory = 0;
    oa.ObjectName = 0;
    oa.Attributes = 0;
    oa.SecurityDescriptor = 0;
    oa.SecurityQualityOfService = 0;
    //打開進程,若是句柄有效,則結束進程
    ZwOpenProcess(&hThread, 1, &oa, &ClientId);
    if (hThread)
    {
    ZwTerminateThread(hThread, 0);
    ZwClose(hThread);
    };}
    9.枚舉 DLL 模塊。DLL 模塊記錄在 PEB 的 LDR 鏈表裏,LDR 是一個雙向鏈表,枚舉它便可。
    另外,DLL 模塊列表包含 EXE 的相關信息。換句話說, 枚舉 DLL  模塊 便可 實現 枚舉 進程 路徑。
     
    // 聲明偏移
    ULONG64 LdrInPebOffset = 0x018; //peb.ldr
    ULONG64 ModListInPebOffset = 0x010; //peb.ldr.InLoadOrderModuleList
    //聲明 API
    NTKERNELAPI PPEB PsGetProcessPeb(PEPROCESS Process);
    //聲明結構體
    typedef struct _LDR_DATA_TABLE_ENTRY
    {
    LIST_ENTRY64 InLoadOrderLinks;
    LIST_ENTRY64 InMemoryOrderLinks;
    LIST_ENTRY64 InInitializationOrderLinks;
    PVOID  DllBase;
    PVOID  EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    PVOID  SectionPointer;
    ULONG CheckSum;
    PVOID  LoadedImports;
    PVOID  EntryPointActivationContext;
    PVOID  PatchInformation;
    LIST_ENTRY64 ForwarderLinks;
    LIST_ENTRY64 ServiceTagLinks;
    LIST_ENTRY64 StaticLinks;
    PVOID  ContextInformation;
    ULONG64 OriginalBase;
    LARGE_INTEGER  LoadTime;
    } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
    //根據進程枚舉模塊
    VOID EnumModule(PEPROCESS Process)
    {
    ULONG64 Peb = 0;
    ULONG64 Ldr = 0;
    PLIST_ENTRY ModListHead = 0;
    PLIST_ENTRY Module = 0;
    ANSI_STRING AnsiString;
    KAPC_STATE ks;
    //EPROCESS 地址無效則退出
    if (!MmIsAddressValid(Process))
    return;
    //獲取 PEB 地址
    Peb = PsGetProcessPeb(Process);
    //PEB 地址無效則退出
    if (!Peb)
    return;
    //依附進程
    KeStackAttachProcess(Process, &ks);
    __try
    {
    //得到 LDR 地址
    Ldr = Peb + (ULONG64)LdrInPebOffset;
    //測試是否可讀,不可讀則拋出異常退出
    ProbeForRead((CONST PVOID)Ldr, 8, 8);
    //得到鏈表頭
    ModListHead = (PLIST_ENTRY)(*(PULONG64)Ldr + ModListInPebOffset);
    //再次測試可讀性
    ProbeForRead((CONST PVOID)ModListHead, 8, 8);
    //得到第一個模塊的信息
    Module = ModListHead->Flink;
    while (ModListHead != Module)
    {
    //打印信息:基址、大小、DLL 路徑
    DbgPrint("Base=%p, Size=%ld, Path=%wZ",
    (PVOID)(((PLDR_DATA_TABLE_ENTRY)Module)->DllBase),
    (ULONG)(((PLDR_DATA_TABLE_ENTRY)Module)->SizeOfImage),
    &(((PLDR_DATA_TABLE_ENTRY)Module)->FullDllName));
    Module = Module->Flink;
    //測試下一個模塊信息的可讀性
    ProbeForRead((CONST PVOID)Module, 80, 8);
    }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    DbgPrint("[EnumModule]__except (EXCEPTION_EXECUTE_HANDLER)");
    }
    //取消依附進程
    KeUnstackDetachProcess(&ks);
    }
     
    10.卸載 DLL 模塊。使用 MmUnmapViewOfSection 便可。MmUnmapViewOfSection 的原型如
    下。填寫正確的 EPROCESS 和 DLL 模塊基址就能把 DLL 卸載掉。若是卸載 NTDLL 等重要 DLL
    將會致使進程崩潰
    NTSTATUS MmUnmapViewOfSection
    (IN PEPROCESS Process, //進程的 EPROCESS
    IN PVOID BaseAddress) //DLL 模塊基址測試

相關文章
相關標籤/搜索