1. 程式人生 > >Win64 驅動核心程式設計-7.核心裡操作程序

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,它的內部實現正是枚舉了 PspCidTablePID 的範圍是從 開始,到MAX_INT2^31-1)結束,步進為 4。但實際上,大家見到的 PID 基本都是小於 10000 的,而上 10000 的 PID 相信很多人都沒有見過。所以我們實際的列舉範圍是 42^18,如果PsLookupProcessByProcessId 返回失敗,則證明此程序不存在,如果返回成功,則把 EPROCESSPIDPPID、程序名打印出來

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 模組基址