一、内核空间
每个进程的低2G都是独立的,而高2G是共享的。
我们可以做一个小实验,在一个进程的高2G定义申请一块内存,去另一个进程里,用相同的线性地址读取,会发现是同一块物理内存。
驱动A
#include <ntddk.h>
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path);
VOID DriverUnload(PDRIVER_OBJECT driver);
// 高2G申请一块内存
UINT32 g_H2GValue = 0;
// 入口函数,相当于main
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
g_H2GValue = 0x20201018;
DbgPrint("[%p]: %08X\n", &g_H2GValue, g_H2GValue);
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
// 卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("驱动卸载成功\n");
}
驱动B
#include <ntddk.h>
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path);
VOID DriverUnload(PDRIVER_OBJECT driver);
// 入口函数,相当于main
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
PUINT32 pUint32 = (PUINT32)0xF88DB01C; // 驱动A变量的线性地址,这个值是驱动A打印的
DbgPrint("驱动B读取驱动A的变量值: %08X\n", *pUint32);
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
// 卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("驱动卸载成功\n");
}
二、内核模块,驱动名字的由来
高2G里有许多模块,操作系统内核(如101012分页的ntoskrnl.exe)也在其中。接下来的课后试验我们会编程遍历高2G模块。
内核模块一般是.sys,也可以是其他格式,他们都遵循PE格式。
我们经常说“驱动”,这个名字的来源其实是内核程序入口函数的参数。
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path);
PDRIVER_OBJECT 驱动对象,就是驱动这个名字的由来。
三、PDRIVER_OBJECT 驱动对象
我们可以在windbg中查看 _DRIVER_OBJECT 结构体:
kd> dt _DRIVER_OBJECT
ntdll!_DRIVER_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 Flags : Uint4B
+0x00c DriverStart : Ptr32 Void
+0x010 DriverSize : Uint4B
+0x014 DriverSection : Ptr32 Void
+0x018 DriverExtension : Ptr32 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING
+0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
+0x028 FastIoDispatch : Ptr32 _FAST_IO_DISPATCH
+0x02c DriverInit : Ptr32 long
+0x030 DriverStartIo : Ptr32 void
+0x034 DriverUnload : Ptr32 void
+0x038 MajorFunction : [28] Ptr32 long
挑几个比较重要的属性来说明:
DriverStart:驱动在内存中的基址
DriverSize:驱动在内存中的大小
DriverSection:内核模块链表基址(这个待会详细说)
DriverName:驱动名
这样看起来干巴巴的,干脆我们写一个驱动,看看它里面这个结构的数据是长什么样的:
#include <ntddk.h>
// 卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("驱动程序停止运行了.\r\n");
}
// 入口函数,相当于main
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
DbgPrint("PDRIVER_OBJECT: %p %wZ\n",driver,reg_path);
// 设置一个卸载函数,便于退出
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
在windbg中查看这个驱动进程的 _DRIVER_OBJECT 结构体:
kd> dt _DRIVER_OBJECT 81ECC880
ntdll!_DRIVER_OBJECT
+0x000 Type : 0n4
+0x002 Size : 0n168
+0x004 DeviceObject : (null)
+0x008 Flags : 0x12
+0x00c DriverStart : 0xf8910000 Void
+0x010 DriverSize : 0x6000
+0x014 DriverSection : 0x81d65498 Void
+0x018 DriverExtension : 0x81ecc928 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING "\Driver\内核编程基础"
+0x024 HardwareDatabase : 0x80690a90 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
+0x028 FastIoDispatch : (null)
+0x02c DriverInit : 0xf8911020 long _empty_!DriverEntry+0
+0x030 DriverStartIo : (null)
+0x034 DriverUnload : 0xf8911000 void _empty_!DriverUnload+0
+0x038 MajorFunction : [28] 0x804fb87e long nt!IopInvalidDeviceRequest+0
可以看到,除了刚才说的几个属性,HardwareDatabase 其实就是入口函数第二个参数,我们用工具注册驱动时,就是在注册表里做了修改,用的就是这个字符串。
接下来,着重介绍 DriverSection 属性。
四、DriverSection / 内核模块链表
在windbg 中查看 DriverSection 属性,类型是 void,它实际上是 _LDR_DATA_TABLE_ENTRY 类型。这个结构体我在上一篇《3环PEB断链》中介绍了,它是一个链表的项。
kd> dt _LDR_DATA_TABLE_ENTRY
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING
+0x034 Flags : Uint4B
+0x038 LoadCount : Uint2B
+0x03a TlsIndex : Uint2B
+0x03c HashLinks : _LIST_ENTRY
+0x03c SectionPointer : Ptr32 Void
+0x040 CheckSum : Uint4B
+0x044 TimeDateStamp : Uint4B
+0x044 LoadedImports : Ptr32 Void
+0x048 EntryPointActivationContext : Ptr32 Void
+0x04c PatchInformation : Ptr32 Void
和3环有点区别,在0环中InMemoryOrderLinks 和 InInitializationOrderLinks 是没用的,只需要关注第一个链表 InLoadOrderLinks。_LIST_ENTRY 这个结构体存了两个地址,指向前一个节点和下一个节点:
kd> dt _LIST_ENTRY
ntdll!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
这里多一句嘴,我们在Windows中见到过很多"ENTRY"了,PDE PTE,还有这里的LIST_ENTRY,这个ENTRY其实就是“项”的意思。
通过这个 InLoadOrderLinks,我们可以遍历整个高2G的模块了。InLoadOrderLinks.Flink 指向的就是下一个 _LDR_DATA_TABLE_ENTRY。下面给出遍历内核模块链表的代码:
#include <ntddk.h>
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
UINT32 SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
UINT32 Flags;
UINT16 LoadCount;
UINT16 TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
UINT32 CheckSum;
UINT32 TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path);
VOID DriverUnload(PDRIVER_OBJECT driver);
// 入口函数,相当于main
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
PLDR_DATA_TABLE_ENTRY pLdteHead; // 内核模块链表头
PLDR_DATA_TABLE_ENTRY pLdteCur; // 遍历指针
pLdteHead = (PLDR_DATA_TABLE_ENTRY)driver->DriverSection;
pLdteCur = pLdteHead;
do
{
PLDR_DATA_TABLE_ENTRY pLdte = CONTAINING_RECORD(pLdteCur, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
DbgPrint("DllBase: %p, SizeOfImage: %08X %wZ\n", pLdteCur->DllBase, pLdteCur->SizeOfImage, &(pLdteCur->FullDllName));
pLdteCur = (PLDR_DATA_TABLE_ENTRY)pLdteCur->InLoadOrderLinks.Flink;
} while (pLdteHead != pLdteCur);
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
// 卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("驱动卸载成功\n");
}
注意,windbg 无法打印带中文的 UNICODE_STRING,建议把项目名取成全英文。
还有一点,这里打印的内核模块名是写死的 ntoskrnl.exe,即使系统当前是 2-9-9-12分页,这是XP的BUG。
https://bbs.pediy.com/thread-97717.htm
五、定位未导出函数 PspTerminateProcess
课上老师和同学给出了几种办法,接下来分别介绍。比如,我们想找 PspTerminateProcess 这个函数,这是一个未导出函数,用来杀进程的。
1.windbg+pdb
在有内核PDB的情况下,用windbg可以直接找到该函数:
kd> u PspTerminateProcess l40
nt!PspTerminateProcess:
8062f050 8bff mov edi,edi
8062f052 55 push ebp
8062f053 8bec mov ebp,esp
8062f055 56 push esi
8062f056 64a124010000 mov eax,dword ptr fs:[00000124h]
8062f05c 8b7508 mov esi,dword ptr [ebp+8]
8062f05f 3b7044 cmp esi,dword ptr [eax+44h]
8062f062 7507 jne nt!PspTerminateProcess+0x1b (8062f06b)
8062f064 b80d0000c0 mov eax,0C000000Dh
8062f069 eb5a jmp nt!PspTerminateProcess+0x75 (8062f0c5)
8062f06b 57 push edi
8062f06c 8dbe48020000 lea edi,[esi+248h]
8062f072 f6470120 test byte ptr [edi+1],20h
8062f076 7412 je nt!PspTerminateProcess+0x3a (8062f08a)
8062f078 8d8674010000 lea eax,[esi+174h]
8062f07e 50 push eax
8062f07f 56 push esi
8062f080 68caf06280 push offset nt!NtTerminateProcess+0x14c (8062f0ca)
8062f085 e800feffff call nt!PspCatchCriticalBreak (8062ee8a)
8062f08a 6a08 push 8
8062f08c 58 pop eax
8062f08d f00907 lock or dword ptr [edi],eax
8062f090 6a00 push 0
8062f092 56 push esi
8062f093 e854faf4ff call nt!PsGetNextProcessThread (8057eaec)
8062f098 8bf8 mov edi,eax
8062f09a 85ff test edi,edi
8062f09c 741e je nt!PspTerminateProcess+0x6c (8062f0bc)
8062f09e ff750c push dword ptr [ebp+0Ch]
8062f0a1 57 push edi
8062f0a2 e824d3f4ff call nt!PspTerminateThreadByPointer (8057c3cb)
8062f0a7 57 push edi
8062f0a8 56 push esi
8062f0a9 e83efaf4ff call nt!PsGetNextProcessThread (8057eaec)
8062f0ae 8bf8 mov edi,eax
8062f0b0 85ff test edi,edi
8062f0b2 75ea jne nt!PspTerminateProcess+0x4e (8062f09e)
8062f0b4 3986bc000000 cmp dword ptr [esi+0BCh],eax
8062f0ba 7406 je nt!PspTerminateProcess+0x72 (8062f0c2)
8062f0bc 56 push esi
8062f0bd e882c1ffff call nt!ObClearProcessHandleTable (8062b244)
8062f0c2 33c0 xor eax,eax
8062f0c4 5f pop edi
8062f0c5 5e pop esi
8062f0c6 5d pop ebp
8062f0c7 c20800 ret 8
8062f050 就是函数头,然而这个值由于重定位,可能会变的,所以我们就要用其他办法,确保每次都能找到这个函数。
2.通过已导出函数
第二种办法是根据已导出函数找未导出函数,在驱动里找已导出函数使用的函数是 MmGetSystemRoutineAddress 。我们通过IDA交叉引用,并没有找到调用 PspTerminateProcess 的导出函数。
所以,这种办法在这里无法使用。
3.模块基址+偏移
虽然模块基址会变,但是函数相对基址的偏移是不变的,通过这个规律也可以找到想要的函数。
PspTerminateProcess 相对内核基址的偏移 = 8062f050 - 804D8000 = 157050
只要找到内核基址,加上 0x157050 就是 PspTerminateProcess 的地址。
这种方法我就不贴代码了,因为原理比较简单。
4.特征码匹配(最常用)
特征码提取时,要避免使用全局变量等和重定位有关的指令,也要避免提取这种所有函数都有的指令。
8062f050 8bff mov edi,edi
8062f052 55 push ebp
8062f053 8bec mov ebp,esp
看看函数头部的汇编:
kd> u PspTerminateProcess l10
nt!PspTerminateProcess:
8062f050 8bff mov edi,edi
8062f052 55 push ebp
8062f053 8bec mov ebp,esp
8062f055 56 push esi
8062f056 64a124010000 mov eax,dword ptr fs:[00000124h]
8062f05c 8b7508 mov esi,dword ptr [ebp+8]
8062f05f 3b7044 cmp esi,dword ptr [eax+44h]
8062f062 7507 jne nt!PspTerminateProcess+0x1b (8062f06b)
8062f064 b80d0000c0 mov eax,0C000000Dh
8062f069 eb5a jmp nt!PspTerminateProcess+0x75 (8062f0c5)
8062f06b 57 push edi
8062f06c 8dbe48020000 lea edi,[esi+248h]
8062f072 f6470120 test byte ptr [edi+1],20h
8062f076 7412 je nt!PspTerminateProcess+0x3a (8062f08a)
8062f078 8d8674010000 lea eax,[esi+174h]
8062f07e 50 push eax
选取这部分作为特征码:
8062f056 64a124010000 mov eax,dword ptr fs:[00000124h]
8062f05c 8b7508 mov esi,dword ptr [ebp+8]
8062f05f 3b7044 cmp esi,dword ptr [eax+44h]
8062f062 7507 jne nt!PspTerminateProcess+0x1b (8062f06b)
8062f064 b80d0000c0 mov eax,0C000000Dh
8062f069 eb5a jmp nt!PspTerminateProcess+0x75 (8062f0c5)
8062f06b 57 push edi
8062f06c 8dbe48020000 lea edi,[esi+248h]
8062f072 f6470120 test byte ptr [edi+1],20h
8062f076 7412 je nt!PspTerminateProcess+0x3a (8062f08a)
8062f078 8d8674010000 lea eax,[esi+174h]
用dd打印一下:
接下来编程只需要找这段作为特征码匹配即可。代码如下:
#include <ntddk.h>
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
UINT32 SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
UINT32 Flags;
UINT16 LoadCount;
UINT16 TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
UINT32 CheckSum;
UINT32 TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
// 函数声明
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path);
VOID GetKernelBase(PDRIVER_OBJECT driver, PVOID *pKrnlBase, PUINT32 uKrnlImageSize);
NTSTATUS PsLookupProcessByProcessId(IN ULONG ulProcId, OUT PEPROCESS * pEProcess);//定义了这个函数就可以编译通过
PVOID MemorySearch(PVOID bytecode, UINT32 bytecodeLen, PVOID pBeginAddress, PVOID pEndAddress);
VOID DriverUnload(PDRIVER_OBJECT driver);
typedef NTSTATUS (*_PspTerminateProcess)(PEPROCESS pEprocess, NTSTATUS ExitCode);
_PspTerminateProcess PspTerminateProcess;
// 入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
UINT32 bytecode[] = {
0x0124a164, 0x758b0000, 0x44703b08, 0x0db80775,
0xebc00000, 0xbe8d575a, 0x00000248, 0x200147f6,
0x868d1274, 0x00000174
};
PVOID pKrnlBase; // 内核基址
UINT32 uKrnlImageSize; // 内核大小
PEPROCESS pEprocess; // 要关闭的进程的EPROCESS
// 获取内核模块基址和大小
GetKernelBase(driver, &pKrnlBase, &uKrnlImageSize);
DbgPrint("内核基址: %p,大小: %X\n", pKrnlBase, uKrnlImageSize);
// 获取 PspTerminateProcess 函数地址
PspTerminateProcess = (_PspTerminateProcess)((UINT32)MemorySearch( \
bytecode,sizeof(bytecode),pKrnlBase,(PVOID)((UINT32)pKrnlBase+uKrnlImageSize)) - 6);
DbgPrint("PspTerminateProcess: %p\n", PspTerminateProcess);
// 根据PID获取EPROCESS
PsLookupProcessByProcessId((HANDLE)1796,&pEprocess); // 记事本PID是1796
// 调用 PspTerminateProcess 关闭进程
PspTerminateProcess(pEprocess, 0);
DbgPrint("记事本进程被 PspTerminateProcess 函数关闭了.\n");
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
// 获取内核基址,大小
VOID GetKernelBase(PDRIVER_OBJECT driver, PVOID *pKrnlBase, PUINT32 uKrnlImageSize)
{
PLDR_DATA_TABLE_ENTRY pLdteHead; // 内核模块链表头
PLDR_DATA_TABLE_ENTRY pLdteCur; // 遍历指针
UNICODE_STRING usKrnlBaseDllName; // 内核模块名
RtlInitUnicodeString(&usKrnlBaseDllName,L"ntoskrnl.exe");
pLdteHead = (PLDR_DATA_TABLE_ENTRY)driver->DriverSection;
pLdteCur = pLdteHead;
do
{
PLDR_DATA_TABLE_ENTRY pLdte = CONTAINING_RECORD(pLdteCur, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
//DbgPrint("DllBase: %p, SizeOfImage: %08X %wZ\n", pLdteCur->DllBase, pLdteCur->SizeOfImage, &(pLdteCur->FullDllName));
if (RtlCompareUnicodeString(&pLdteCur->BaseDllName, &usKrnlBaseDllName, TRUE) == 0)
{
*pKrnlBase = pLdteCur->DllBase;
*uKrnlImageSize = pLdteCur->SizeOfImage;
return;
}
pLdteCur = (PLDR_DATA_TABLE_ENTRY)pLdteCur->InLoadOrderLinks.Flink;
} while (pLdteHead != pLdteCur);
return;
}
// 特征码搜索
PVOID MemorySearch(PVOID bytecode, UINT32 bytecodeLen, PVOID pBeginAddress, PVOID pEndAddress)
{
PVOID pCur = pBeginAddress;
while (pCur != pEndAddress)
{
if (RtlCompareMemory(bytecode,pCur,bytecodeLen) == bytecodeLen)
{
return pCur;
}
((UINT32)pCur)++;
}
return 0;
}
// 卸载驱动
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("驱动卸载成功\n");
}
驱动运行前:
驱动运行后:
---------------------
作者:hambaga
来源:CSDN
原文:https://blog.csdn.net/Kwansy/article/details/109145110
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件