[转](36)内核空间与内核模块

 

一、内核空间

 

每个进程的低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博客文章一键转载插件

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值