內核里操作進程


                                   在內核里操作進程

在內核里操作進程,相信是很多對 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 模塊基址

 


免責聲明!

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



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