Windows XP 驱动开发(二)

ʕ •ᴥ•ʔ ɔ:

0 驱动相关对象

驱动通信:

《Windows内核安全与驱动开发》 2.3、2.5、5、10.3.5、13.3、16.5、16.6

看雪的那两片驱动开发的文章

《内核原理与实现》6

《内核情景分析》 9

《驱动开发详解》 1 4 7

01 驱动对象

一个驱动对象代表了一个驱动程序,或者说一个内核模块

内核对象结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart; //这个内核模块在内核空间中的开始地址
ULONG DriverSize; //这个内核模块在内核空间中的大小
PVOID DriverSection; //内核模块链表,指向当前模块的_LDR_DATA_TABLE_ENTRY
PDRIVER_EXTENSION DriverExtension; //驱动的扩展信息,可以自定义存放我们的数据(非分页内存,不会被换页,当全局变量用)
UNICODE_STRING DriverName; //驱动的名字
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch; //快速 IO 分发函数
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload; //驱动的卸载地址
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; //普通分发函数
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;

Windbg查看结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
kd> dt _DRIVER_OBJECT -v
nt!_DRIVER_OBJECT
struct _DRIVER_OBJECT, 15 elements, 0xa8 bytes
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 to struct _DEVICE_OBJECT, 25 elements, 0xb8 bytes
+0x008 Flags : Uint4B
+0x00c DriverStart : Ptr32 to Void
+0x010 DriverSize : Uint4B
+0x014 DriverSection : Ptr32 to Void
+0x018 DriverExtension : Ptr32 to struct _DRIVER_EXTENSION, 6 elements, 0x1c bytes
+0x01c DriverName : struct _UNICODE_STRING, 3 elements, 0x8 bytes
+0x024 HardwareDatabase : Ptr32 to struct _UNICODE_STRING, 3 elements, 0x8 bytes
+0x028 FastIoDispatch : Ptr32 to struct _FAST_IO_DISPATCH, 28 elements, 0x70 bytes
+0x02c DriverInit : Ptr32 to long
+0x030 DriverStartIo : Ptr32 to void
+0x034 DriverUnload : Ptr32 to void
+0x038 MajorFunction : [28] Ptr32 to long

02 设备对象

一个驱动程序可以支持多个设备,所以,驱动程序对象中有一个链表记录了它所负责的所有设备对象。每个设备对象都必定有一个为它负责的驱动程序对设备对象的各种操作实际上是由为它负贵的驱动程序中的例程来完成的。

1 内核空间

每个进程的低2G都是独立的,而高2G是共享的

7.png

1.1 练习:验证高2G内存共享

  1. 先在驱动程序中定义一个全局变量,获取地址(高2G)。地址为:0xF79F9014。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <ntddk.h>

    // 卸载函数
    VOID DriverUnload(PDRIVER_OBJECT driver)
    {
    DbgPrint("驱动程序停止运行了\n");
    }

    ULONG ulNum = 12345678;

    // 入口函数,相当于main
    NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
    {
    DbgPrint("变量ulNum的地址为:[%p]:%08d\n",&ulNum,ulNum);

    // 设置一个卸载函数,便于退出
    driver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
    }

    8.png

  2. 在Windbg使用!process 0 0找任意一个进程的基地址,如DbgView:0x85d9b8d8 ,并使用.process 0x85d9b8d8切换到该进程空间。

    9.png

  3. 如下图,得到地址0xF79F9014中值为:0x00bc614e(12345678)。

    10.png

1.2 内核模块

高2G里有许多模块,操作系统内核(如101012分页的ntoskrnl.exe)也在其中。接下来的课后试验我们会编程遍历高2G模块。

内核模块一般是.sys,也可以是其他格式,他们都遵循PE格式。我们经常说“驱动”,这个名字的来源其实是内核程序入口函数的参数。

1
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path);

PDRIVER_OBJECT驱动对象,就是驱动这个名字的由来。

  • driver:指向当前模块的DRIVER_OBJECT。
  • reg_path:将当前模块注册到注册表哪个位置。
  1. 硬件种类繁多,不可能做一个兼容所有硬件的内核,所以,微软提供规定的接口格式,让硬件驱动人员安装规定的格式编写“驱动程序” 。

  2. 这些驱动程序每一个都是一个模块,称为“内核模块”,都可以加载到内核中,都遵守PE结构。但本质上讲,任意一个.sys文件与内核文件没有区别。

    11.png

1.3 DRIVER_OBJECT 结构体

DRIVER_OBJECT为一个结构体,每个内核模块都有一个这样对应的结构体,来描述这个模块在内核中的:位置、大小、名称等等

结构体DRIVER_OBJECT主要目的就是描述当前的这个模块,或者叫做驱动对象。

模块驱动对象

该结构在VS2010的wdm.hMSDN中的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart; //驱动对象的起始地址
ULONG DriverSize; //驱动对象的大小
PVOID DriverSection; //内核模块链表,指向当前模块的_LDR_DATA_TABLE_ENTRY
PDRIVER_EXTENSION DriverExtension; //驱动的扩展信息,可以自定义存放我们的数据(非分页内存,不会被换页,当全局变量用)
UNICODE_STRING DriverName; //驱动对象的名字
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload; //驱动对象的卸载地址
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;

几个重要的属性:

DriverStart:驱动在内存中的基址。
DriverSize:驱动在内存中的大小。
DriverSection:内核模块链表基址。_LDR_DATA_TABLE_ENTRY
DriverName:驱动(模块)名称。

在Windbg中查看该结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kd> dt _DRIVER_OBJECT
ntdll!_DRIVER_OBJECT
+0x000 Type : Int2B //2B = 2Byte(SHORT)
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 Flags : Uint4B
+0x00c DriverStart : Ptr32 Void //Ptr32:32位的指针类型
+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

获取当前驱动入口函数第一个参数PDRIVER_OBJECT的值(是一个结构体指针):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <ntddk.h>

// 卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("驱动程序停止运行了.\r\n");
}

// 入口函数,相当于main
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
DbgPrint("PDRIVER_OBJECT: %08X,%wZ\n",driver,reg_path);
// 设置一个卸载函数,便于退出
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

DbgPrint输出格式可参见:DbgPrint输出格式

  • %Z :ANSI_STRING字符串,注意输出时需要用&取字符串的地址。
  • %wZ:UNICODE_STRING字符串,注意输出时需要用&取字符串的地址。

12.png

如图,结构体_DRIVER_OBJECT的起始地址为:0x85EA8BC0。在windbg中查看这个驱动进程的 _DRIVER_OBJECT 结构体:dt _DRIVER_OBJECT 85EA8BC0

指令: dt _DRIVER_OBJECT 地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kd> dt _DRIVER_OBJECT 85EA8BC0
ntdll!_DRIVER_OBJECT
+0x000 Type : 0n4
+0x002 Size : 0n168
+0x004 DeviceObject : (null)
+0x008 Flags : 0x12
+0x00c DriverStart : 0xf789e000 Void
+0x010 DriverSize : 0x6000
+0x014 DriverSection : 0x863064b0 Void
+0x018 DriverExtension : 0x85ea8c68 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING "\Driver\0115_2_输出参数PDRIVER_OBJECT"
+0x024 HardwareDatabase : 0x8067e260 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
+0x028 FastIoDispatch : (null)
+0x02c DriverInit : 0xf789f020 long 0115_2_!DriverEntry+0
+0x030 DriverStartIo : (null)
+0x034 DriverUnload : 0xf789f000 void 0115_2_!DriverUnload+0
+0x038 MajorFunction : [28] 0x804f5552 long nt!IopInvalidDeviceRequest+0

1.4 遍历内核模块

我们可以根据_DRIVER_OBJECT结构的DriverSection(内核模块链表)属性来获取0环所有的内核模块。它实际上是 _LDR_DATA_TABLE_ENTRY结构类型。

双向循环链表

0环所有模块的_LDR_DATA_TABLE_ENTRY是一个双向循环链表。结构_LDR_DATA_TABLE_ENTRY存储当前模块的信息。

DriverSection 属性类型为PVOID的链表指针,该指针指向的结构为当前模块的_LDR_DATA_TABLE_ENTRY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

当前模块的DriverSection = 0x863064b0,解析当前模块的_LDR_DATA_TABLE_ENTRY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
WORD LoadCount;
WORD TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union
{
ULONG TimeDateStamp;
PVOID LoadedImports;
};
_ACTIVATION_CONTEXT * EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagLinks;
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kd> dt _LDR_DATA_TABLE_ENTRY 0x863064b0
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x8055e720 - 0x85d1fa88 ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xffffffff - 0xffffffff ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x12 - 0x0 ]
+0x018 DllBase : 0xf78a6000 Void
+0x01c EntryPoint : 0xf78a7020 Void
+0x020 SizeOfImage : 0x6000
+0x024 FullDllName : _UNICODE_STRING "\??\C:\Documents and Settings\A1v1n\桌面\0115_2_输出参数PDRIVER_OBJECT.sys"
+0x02c BaseDllName : _UNICODE_STRING "0115_2_输出参数PDRIVER_OBJECT.sys"
+0x034 Flags : 0x1104000
+0x038 LoadCount : 1
+0x03a TlsIndex : 0x63
+0x03c HashLinks : _LIST_ENTRY [ 0xffffffff - 0x2e30 ]
+0x03c SectionPointer : 0xffffffff Void
+0x040 CheckSum : 0x2e30
+0x044 TimeDateStamp : 0xfffffffe
+0x044 LoadedImports : 0xfffffffe Void
+0x048 EntryPointActivationContext : (null)
+0x04c PatchInformation : 0x00310030 Void

其中较重要的几个属性:

  • InLoadOrderLinks:是个结构体,是个链表。有两个成员:Flink 和 Blink。。
  • DllBase:当前模块的基址
  • SizeOfImage:当前模块大小
  • FullDllName:完整的模块名,以\??\格式开始

3环有点区别,在0环中InMemoryOrderLinks 和 InInitializationOrderLinks 是没用的,只需要关注第一个链表 InLoadOrderLinks。_LIST_ENTRY 这个结构体存了两个地址,指向前一个节点和下一个节点:

1
2
3
4
5
6
7
8
9
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

kd> dt _LIST_ENTRY
ntdll!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY

通过这个 InLoadOrderLinks,我们可以遍历整个高2G的模块了。InLoadOrderLinks.Flink 指向的就是下一个 _LDR_DATA_TABLE_ENTRY。下面给出遍历内核模块链表的代码:

网上说的比较常见的4种方法:

1、通过DriverEntry传入的DriverObject参数的DriverSection成员指向LDR_DATA_TABLE_ENTRY结构,通过遍历这张表得到ntoskrnl的基址和大小

2、ZwQuerySystemInformation大法

3、搜索内存

4、利用KPCR结构

则InLoadOrderLinks = 0x863064b0,执行如下:

1
2
3
4
5
kd> dt _LIST_ENTRY 0x863064b0
ntdll!_LIST_ENTRY
[ 0x8055e720 - 0x85d1fa88 ]
+0x000 Flink : 0x8055e720 _LIST_ENTRY [ 0x863fc3b0 - 0x863064b0 ]
+0x004 Blink : 0x85d1fa88 _LIST_ENTRY [ 0x863064b0 - 0x85fa9e80 ]

可知:

  • 0x8055e720:Flink,指向后一个_LDR_DATA_TABLE_ENTRY
  • 0x85d1fa88:Blink,指向前一个_LDR_DATA_TABLE_ENTRY

1.5 练习:遍历内核中所有模块

宏CONTAINING_RECORD:返回一个结构体实例的基地址,该结构的类型和结构的一个成员地址已知。

1
2
3
4
5
PCHAR  CONTAINING_RECORD( 
IN PCHAR Address,
IN TYPE Type,
IN PCHAR Field
);
  • Address:指向当前结构体实例某成员的指针。
  • Type:结构体名称。
  • Field:结构体某个成员的名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <ntddk.h>

// 卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("驱动程序停止运行了\n");
}

typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;//这个成员把系统所有加载(可能是停止没被卸载)已经读取到内存中,双链表
LIST_ENTRY InMemoryOrderLinks;//系统已经启动,没有被初始化,没有调用DriverEntry这个历程的时候,通过这个链表进程串接起来
LIST_ENTRY InInitializationOrderLinks;//已经调用DriverEntry这个函数的所有驱动程序
PVOID DllBase;
PVOID EntryPoint;//驱动的进入点 DriverEntry
ULONG SizeOfImage;
UNICODE_STRING FullDllName;//驱动的满路径
UNICODE_STRING BaseDllName;//不带路径的驱动名字
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

// 入口函数,相当于main
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driver, IN PUNICODE_STRING reg_path)
{
PLDR_DATA_TABLE_ENTRY pLDR_Head = NULL;
PLDR_DATA_TABLE_ENTRY pLDR_Tail = NULL;

pLDR_Head = (PLDR_DATA_TABLE_ENTRY)driver->DriverSection;
pLDR_Tail = pLDR_Head;

do
{
//pLDR_Blink = (PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(pLDR_Flink,LDR_DATA_TABLE_ENTRY,InLoadOrderLinks);
DbgPrint("DllBase:%08X,SizeOfImage:%08X,FullDllName:%wZ\r\n",pLDR_Tail->DllBase,pLDR_Tail->SizeOfImage,\
&pLDR_Tail->FullDllName);
pLDR_Tail = (PLDR_DATA_TABLE_ENTRY)pLDR_Tail->InLoadOrderLinks.Flink;
}
while(pLDR_Head != pLDR_Tail);

// 设置一个卸载函数,便于退出
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

注意,Windbg 无法打印带中文的 UNICODE_STRING,建议把项目名取成全英文。

13.png

2 未导出函数PspTerminateProcess

PspTerminateProcess是0环的一个未导出函数,用来结束一个进程。函数PspTerminateProcess在ntoskerlpa.exe(2-9-9-12)中,是未导出函数,许多流氓软件无法关闭,就是因为HOOK了这个函数。

从3环到0环的大概调用过程:TerminateProcess --> ntdll.NtTerminateProcess(ntdll.ZwTerminateProcess) --> PsTerminteProcess --> PspTerminateProcess --> PspTerminateThreadByPointer --> KeInitializeApc/KeInsertQueueApc(插入了一个核心态的APC调用,若是用户线程,会再插入用户态的APC调用,最终线程在自己的执行环境中) -> PspExitThread。

在结束一个进程的过程中,可能有很多API会被HOOK。海哥说过,如果他HOOK 3环的API,你就HOOK 0环的API,比他更底层他就防不住你。但是越是底层,对用户的体验越不好,稍微操作不当就会蓝屏。

函数PspTerminateProcess定义如下:

1
2
3
4
NTSTATUS NTAPI PspTerminateProcess(
PEPROCESS Process,
NTSTATUS ExitStatus
);

其中第一个参数是PEPROCESS的指针,可以根据PID通过PsLookupProcessByProcessId 获得(头文件ntifs.h),而第二个参数指定退出状态码。可以用ZwQuerySystemInformation通过进程名找PID。在驱动开发引入的<wdm.h>里提供了MmGetSystemRoutineAddress函数,根据函数名可以直接获取到已导出的函数地址。

1
2
3
4
5
6
7
8
9
10
11
NTSTATUS PsLookupProcessByProcessId(
IN HANDLE ProcessId,
OUT PEPROCESS *Process
);

NTSTATUS WINAPI ZwQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);

PspTerminateProcess是未导出函数,找未导出函数的几种思路:

  • Windbg + .pdb(得到函数特征码)
  • 特征码搜索,先得到函数所在模块的加载基址,然后遍历内存
  • A –> B,A是导出函数,调用了B(未导出函数),通过A调用的地址来定位B
  • 利用函数想对当前模块的偏移来定位函数:ImageBase+Offset

2.1 Windbg + .pdb

在有内核PDB的情况下,用Windbg可以直接找到该函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
kd> u PspTerminateProcess L20
nt!PspTerminateProcess:
805d3b18 8bff mov edi,edi
805d3b1a 55 push ebp
805d3b1b 8bec mov ebp,esp
805d3b1d 56 push esi
805d3b1e 64a124010000 mov eax,dword ptr fs:[00000124h]
805d3b24 8b7508 mov esi,dword ptr [ebp+8]
805d3b27 3b7044 cmp esi,dword ptr [eax+44h]
805d3b2a 7507 jne nt!PspTerminateProcess+0x1b (805d3b33)
805d3b2c b80d0000c0 mov eax,0C000000Dh
805d3b31 eb5a jmp nt!PspTerminateProcess+0x75 (805d3b8d)
805d3b33 57 push edi
805d3b34 8dbe48020000 lea edi,[esi+248h]
805d3b3a f6470120 test byte ptr [edi+1],20h
805d3b3e 7412 je nt!PspTerminateProcess+0x3a (805d3b52)
805d3b40 8d8674010000 lea eax,[esi+174h]
805d3b46 50 push eax
805d3b47 56 push esi
805d3b48 68ea3a5d80 push offset nt!NtTerminateProcess+0x14c (805d3aea)
805d3b4d e8f2eeffff call nt!PspCatchCriticalBreak (805d2a44)
805d3b52 6a08 push 8
805d3b54 58 pop eax
805d3b55 f00907 lock or dword ptr [edi],eax
805d3b58 6a00 push 0
805d3b5a 56 push esi
805d3b5b e8784f0000 call nt!PsGetNextProcessThread (805d8ad8)
805d3b60 8bf8 mov edi,eax
805d3b62 85ff test edi,edi
805d3b64 741e je nt!PspTerminateProcess+0x6c (805d3b84)
805d3b66 ff750c push dword ptr [ebp+0Ch]
805d3b69 57 push edi
805d3b6a e807fdffff call nt!PspTerminateThreadByPointer (805d3876)
805d3b6f 57 push edi

0x805d3b18 就是函数头,然而这个值由于重定位,可能会变的,所以我们就要用其他办法,确保每次都能找到这个函数。

2.2 通过已导出函数

第二种办法是根据已导出函数找未导出函数,在驱动开发引入的<wdm.h>里提供了MmGetSystemRoutineAddress函数,根据函数名可以直接获取到已导出的函数地址。

  1. 在IDA中任意找一个导出函数的地址:ZwQueryInformationProcess:0x00429934。

    14.png

  2. 在Windbg中获取该函数的地址:0x80501934。

    1
    2
    3
    4
    5
    6
    7
    8
    kd> u ZwQueryInformationProcess
    nt!ZwQueryInformationProcess:
    80501934 b89a000000 mov eax,9Ah
    80501939 8d542404 lea edx,[esp+4]
    8050193d 9c pushfd
    8050193e 6a08 push 8
    80501940 e80c0b0400 call nt!KiSystemService (80542451)
    80501945 c21400 ret 14h
  3. 函数PspTerminateProcess在Windbg的地址:0x805d3b18。则0x805d3b18 - (0x80501934 - 0x00429934) = 0x004FBB18。

  4. 在IDA中按G输入0x004FBB18定位到代码段:(该方法如果ntoskerlpa.exe重定位了就无法使用了

    15.png

  5. 在IDA,鼠标定位到该函数,View - Open subviews - Cross reference 查看该函数的交叉引用情况。

    16.png

可以看到A函数没有一个是导出函数,故该方法不适用。

2.3 模块基址+偏移

虽然模块基址会变,但是函数相对基址的偏移是不变的,通过这个规律也可以找到想要的函数。
PspTerminateProcess 相对内核基址的偏移 = 0x805d3b18 - (0x80501934 - 0x00429934) = 0x004FBB18
只要找到内核基址,加上 0x004FBB18 就是 PspTerminateProcess 的地址。

这种方法我就不贴代码了,因为原理比较简单。

2.4 特征码匹配(最常用)

提取特征码(硬编码)注意几个问题:

  • 绕开所有函数都有的指令。如PUSH EBP;MOV EBP,ESP等对应的硬编码
  • 然开全局变量等和重定位有关的指令,该模块下次加载地址会变
  • 要提取代码中特征明显,功能很小众的代码
  • 最好跳着提特征码:8b 75 08 3b 70 44 ?? ?? ?? ?? 68 20 d5 62 80 ?? ?? ?? ?? 68 20

2.5 练习:使用PspTerminateProcess关闭记事本

大致流程如下:

  1. 先获取函数特征码。
  2. 确定函数所在的内核模块,通过内核模块遍历获取ntoskerlpa.exe模块的基址和大小(使用到PDRIVER_OBJECT)。
  3. 到指定模块中,根据特征码按字节比对,确定函数PspTerminateProcess的地址。
  4. 通过函数指针,关闭指定进程。

选取函数一段特征码如下:

1
2
3
4
5
6
7
8
9
10
11
805d3b1e 64a124010000    mov     eax,dword ptr fs:[00000124h]
805d3b24 8b7508 mov esi,dword ptr [ebp+8]
805d3b27 3b7044 cmp esi,dword ptr [eax+44h]
805d3b2a 7507 jne nt!PspTerminateProcess+0x1b (805d3b33)
805d3b2c b80d0000c0 mov eax,0C000000Dh
805d3b31 eb5a jmp nt!PspTerminateProcess+0x75 (805d3b8d)
805d3b33 57 push edi
805d3b34 8dbe48020000 lea edi,[esi+248h]
805d3b3a f6470120 test byte ptr [edi+1],20h
805d3b3e 7412 je nt!PspTerminateProcess+0x3a (805d3b52)
805d3b40 8d8674010000 lea eax,[esi+174h]

特征码搜索,先得到函数所在模块(ntoskerlpa.exe)的加载基址,然后遍历内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <ntddk.h>
//#include <ntifs.h>

ULONG GetKernelMoudleInfo(IN PDRIVER_OBJECT pDriver,OUT PVOID* DllBase,OUT PULONG SizeOfImage);
PVOID Find_FeatureCode(PVOID pShellCode,ULONG ulShellCode_Len,PVOID DllBase,ULONG ulSizeOfImage);
// 卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("驱动程序停止运行了\r\n");
}

typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;//这个成员把系统所有加载(可能是停止没被卸载)已经读取到内存中,双链表
LIST_ENTRY InMemoryOrderLinks;//系统已经启动,没有被初始化,没有调用DriverEntry这个历程的时候,通过这个链表进程串接起来
LIST_ENTRY InInitializationOrderLinks;//已经调用DriverEntry这个函数的所有驱动程序
PVOID DllBase;
PVOID EntryPoint;//驱动的进入点 DriverEntry
ULONG SizeOfImage;
UNICODE_STRING FullDllName;//驱动的满路径
UNICODE_STRING BaseDllName;//不带路径的驱动名字
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

// 入口函数,相当于main
NTSTATUS DriverEntry( IN PDRIVER_OBJECT driver, IN PUNICODE_STRING reg_path)
{
ULONG ulShellCode[] =
{
0x0124a164, 0x758b0000, 0x44703b08, 0x0db80775,
0xebc00000, 0xbe8d575a, 0x00000248, 0x200147f6,
0x868d1274, 0x00000174
};
ULONG ulRet = 0;
PVOID DllBase = NULL;
ULONG SizeOfImage = 0;
typedef NTSTATUS (*_PspTerminateProcess)(PEPROCESS pEprocess, NTSTATUS ExitCode);
_PspTerminateProcess PspTerminateProcess;
PEPROCESS pEprocess = NULL;

ulRet = GetKernelMoudleInfo(driver,&DllBase,&SizeOfImage);
if(!ulRet)
{
DbgPrint("驱动程序获取ntoskrnl.exe模块失败.\r\n");
return 0;
}
PspTerminateProcess = (_PspTerminateProcess)Find_FeatureCode((PVOID)ulShellCode,sizeof(ulShellCode),DllBase,SizeOfImage);
if(!PspTerminateProcess)
{
DbgPrint("PspTerminateProcess特征码搜索失败.\r\n");
return 0;
}

PsLookupProcessByProcessId((HANDLE)2796,&pEprocess); // 记事本PID是2796
PspTerminateProcess(pEprocess, 0);
DbgPrint("记事本进程被 PspTerminateProcess 函数关闭了.\n");

// 设置一个卸载函数,便于退出
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
ULONG GetKernelMoudleInfo(IN PDRIVER_OBJECT pDriver,OUT PVOID* DllBase,OUT PULONG SizeOfImage)
{
PLDR_DATA_TABLE_ENTRY pLDR_Head = NULL;
PLDR_DATA_TABLE_ENTRY pLDR_Tail = NULL;
UNICODE_STRING UnicodeMoudleName = {0};

RtlInitUnicodeString(&UnicodeMoudleName,L"ntoskrnl.exe");

pLDR_Head = (PLDR_DATA_TABLE_ENTRY)pDriver->DriverSection;
pLDR_Tail = pLDR_Head;

do
{
if(RtlCompareUnicodeString(&pLDR_Tail->BaseDllName, &UnicodeMoudleName, TRUE) == 0)
{
*DllBase = pLDR_Tail->DllBase;
*SizeOfImage = pLDR_Tail->SizeOfImage;
return 1;
}
//pLDR_Blink = (PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(pLDR_Flink,LDR_DATA_TABLE_ENTRY,InLoadOrderLinks);
pLDR_Tail = (PLDR_DATA_TABLE_ENTRY)pLDR_Tail->InLoadOrderLinks.Flink;
}
while(pLDR_Head != pLDR_Tail);
return 0;
}
PVOID Find_FeatureCode(PVOID pShellCode,ULONG ulShellCode_Len,PVOID DllBase,ULONG ulSizeOfImage)
{
ULONG ulDllLastByte = 0;
PVOID pNextByte = NULL;

pNextByte = DllBase;
ulDllLastByte = (ULONG)DllBase + ulSizeOfImage;
while((ULONG)pNextByte + ulShellCode_Len <= ulDllLastByte)
{
if(RtlCompareMemory(pNextByte,pShellCode,ulShellCode_Len) == ulShellCode_Len)
{
return (PVOID)((ULONG)pNextByte - 6);
}
pNextByte = (PVOID)((ULONG)pNextByte + 1);
}

return NULL;
}

17.png

18.png

3 3环PEB断链

TEB:线程环境块,每个线程都有这么一块内存存储信息来描述当前线程的信息,fs:[0]指向TEB。

PEB:进程环境块,TEB偏移0x30就是PEB,即fs:[0x30]

PEB、TEB是3环的,都是3环的内存,在3环就可读取。FS段寄存器在3环和0环的含义不一样

fs:[0]:TEB --> fs:[0x30]:PEB --> PEB+0xC:PEB_LDR_DATA --> PEB_LDR_DATA+0xC:LDR_DATA_TABLE_ENTRY

示意图如下:具体可以参考:《PEB及LDR链PEB及LDR链

19.png

20.jpeg

⚠️注意:这里的LDR_DATA_TABLE_ENTRY是3环的,在3环通过该结构只能够列举该进程在3环加载的所有模块。

3.1 TEB、PEB

一、TEB

段寄存器FS(fs:[0]存的值指向TEB结构,在OD中可以看到3环下FS的值(0x7FFDF000)和对应的段选择子(0x3B)。

查看当前进程:

1
2
3
4
5
6
7
8
9
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 861969f8 SessionId: 0 Cid: 0924 Peb: 7ffd9000 ParentCid: 0688
DirBase: 06e401c0 ObjectTable: e1e919e8 HandleCount: 74.
Image: ollydbg.exe

PROCESS 861e1020 SessionId: 0 Cid: 0970 Peb: 7ffda000 ParentCid: 0b54
DirBase: 06e40320 ObjectTable: e1473788 HandleCount: 12.
Image: 0119_PEB.exe

切换到当前进程空间:

1
2
3
kd> .process 861e1020
Implicit process is now 861e1020
WARNING: .cache forcedecodeuser is not enabled

TEB结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
kd> dt _TEB 0x7FFDF000
nt!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : (null)
+0x030 ProcessEnvironmentBlock : 0x7ffdd000 _PEB
+0x034 LastErrorValue : 0
+0x038 CountOfOwnedCriticalSections : 0
+0x03c CsrClientThread : (null)
+0x040 Win32ThreadInfo : (null)
+0x044 User32Reserved : [26] 0
+0x0ac UserReserved : [5] 0
+0x0c0 WOW32Reserved : (null)
+0x0c4 CurrentLocale : 0x804
+0x0c8 FpSoftwareStatusRegister : 0
+0x0cc SystemReserved1 : [54] (null)
+0x1a4 ExceptionCode : 0n0
+0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK
+0x1bc SpareBytes1 : [24] ""
+0x1d4 GdiTebBatch : _GDI_TEB_BATCH
+0x6b4 RealClientId : _CLIENT_ID
+0x6bc GdiCachedProcessHandle : (null)
+0x6c0 GdiClientPID : 0
+0x6c4 GdiClientTID : 0
+0x6c8 GdiThreadLocalInfo : (null)
+0x6cc Win32ClientInfo : [62] 0
+0x7c4 glDispatchTable : [233] (null)
+0xb68 glReserved1 : [29] 0
+0xbdc glReserved2 : (null)
+0xbe0 glSectionInfo : (null)
+0xbe4 glSection : (null)
+0xbe8 glTable : (null)
+0xbec glCurrentRC : (null)
+0xbf0 glContext : (null)
+0xbf4 LastStatusValue : 0
+0xbf8 StaticUnicodeString : _UNICODE_STRING "kernel32.dll"
+0xc00 StaticUnicodeBuffer : [261] 0x6b
+0xe0c DeallocationStack : 0x00030000 Void
+0xe10 TlsSlots : [64] (null)
+0xf10 TlsLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0xf18 Vdm : (null)
+0xf1c ReservedForNtRpc : (null)
+0xf20 DbgSsReserved : [2] (null)
+0xf28 HardErrorsAreDisabled : 0
+0xf2c Instrumentation : [16] (null)
+0xf6c WinSockData : (null)
+0xf70 GdiBatchCount : 0
+0xf74 InDbgPrint : 0 ''
+0xf75 FreeStackOnTermination : 0 ''
+0xf76 HasFiberData : 0 ''
+0xf77 IdealProcessor : 0 ''
+0xf78 Spare3 : 0
+0xf7c ReservedForPerf : (null)
+0xf80 ReservedForOle : (null)
+0xf84 WaitingOnLoaderLock : 0
+0xf88 Wx86Thread : _Wx86ThreadState
+0xf94 TlsExpansionSlots : (null)
+0xf98 ImpersonationLocale : 0
+0xf9c IsImpersonating : 0
+0xfa0 NlsCache : (null)
+0xfa4 pShimData : (null)
+0xfa8 HeapVirtualAffinity : 0
+0xfac CurrentTransactionHandle : (null)
+0xfb0 ActiveFrame : (null)
+0xfb4 SafeThunkCall : 0 ''
+0xfb5 BooleanSpare : [3] ""

二、PEB结构

由TEB结构可以看到偏移0x30处即指向PEB(fs:[0x30]),即为0x7ffdd000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
kd> dt _PEB 0x7ffdd000
nt!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
+0x003 SpareBool : 0 ''
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x00400000 Void
+0x00c Ldr : 0x00241ea0 _PEB_LDR_DATA
+0x010 ProcessParameters : 0x00020000 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : (null)
+0x018 ProcessHeap : 0x00140000 Void
+0x01c FastPebLock : 0x7c99d600 _RTL_CRITICAL_SECTION
+0x020 FastPebLockRoutine : 0x7c921000 Void
+0x024 FastPebUnlockRoutine : 0x7c9210e0 Void
+0x028 EnvironmentUpdateCount : 1
+0x02c KernelCallbackTable : (null)
+0x030 SystemReserved : [1] 0
+0x034 AtlThunkSListPtr32 : 0
+0x038 FreeList : (null)
+0x03c TlsExpansionCounter : 0
+0x040 TlsBitmap : 0x7c99d5c0 Void
+0x044 TlsBitmapBits : [2] 1
+0x04c ReadOnlySharedMemoryBase : 0x7f6f0000 Void
+0x050 ReadOnlySharedMemoryHeap : 0x7f6f0000 Void
+0x054 ReadOnlyStaticServerData : 0x7f6f0688 -> (null)
+0x058 AnsiCodePageData : 0x7ffa0000 Void
+0x05c OemCodePageData : 0x7ffa0000 Void
+0x060 UnicodeCaseTableData : 0x7ffd1000 Void
+0x064 NumberOfProcessors : 1
+0x068 NtGlobalFlag : 0x70
+0x070 CriticalSectionTimeout : _LARGE_INTEGER 0xffffe86d`079b8000
+0x078 HeapSegmentReserve : 0x100000
+0x07c HeapSegmentCommit : 0x2000
+0x080 HeapDeCommitTotalFreeThreshold : 0x10000
+0x084 HeapDeCommitFreeBlockThreshold : 0x1000
+0x088 NumberOfHeaps : 4
+0x08c MaximumNumberOfHeaps : 0x10
+0x090 ProcessHeaps : 0x7c99cfc0 -> 0x00140000 Void
+0x094 GdiSharedHandleTable : (null)
+0x098 ProcessStarterHelper : (null)
+0x09c GdiDCAttributeList : 0
+0x0a0 LoaderLock : 0x7c99b178 Void
+0x0a4 OSMajorVersion : 5
+0x0a8 OSMinorVersion : 1
+0x0ac OSBuildNumber : 0xa28
+0x0ae OSCSDVersion : 0x300
+0x0b0 OSPlatformId : 2
+0x0b4 ImageSubsystem : 3
+0x0b8 ImageSubsystemMajorVersion : 4
+0x0bc ImageSubsystemMinorVersion : 0
+0x0c0 ImageProcessAffinityMask : 0
+0x0c4 GdiHandleBuffer : [34] 0
+0x14c PostProcessInitRoutine : (null)
+0x150 TlsExpansionBitmap : 0x7c99d5b8 Void
+0x154 TlsExpansionBitmapBits : [32] 0
+0x1d4 SessionId : 0
+0x1d8 AppCompatFlags : _ULARGE_INTEGER 0x0
+0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER 0x0
+0x1e8 pShimData : (null)
+0x1ec AppCompatInfo : (null)
+0x1f0 CSDVersion : _UNICODE_STRING "Service Pack 3"
+0x1f8 ActivationContextData : (null)
+0x1fc ProcessAssemblyStorageMap : (null)
+0x200 SystemDefaultActivationContextData : 0x00130000 Void
+0x204 SystemAssemblyStorageMap : (null)
+0x208 MinimumStackCommit : 0

三、PEB_LDR_DATA结构,该结构的LIST_ENTRY即为模块。

  • PEB_LDR_DATA结构:该结构的LIST_ENTRY即为模块
  • _LDR_DATA_TABLE_ENTRY结构:该结构为模块的详细信息

在PEB偏移0xC得位置指向LDR链,LDR结构为:

1
2
3
4
5
6
7
8
9
10
typedef struct _PEB_LDR_DATA
{
+0x00 ULONG Length;
+0x04 BOOLEAN Initialized;
+0x08 PVOID SsHandle;
+0x0c LIST_ENTRY InLoadOrderModuleList;
+0x14 LIST_ENTRY InMemoryOrderModuleList;
+0x1c LIST_ENTRY InInitializationOrderModuleList;
+0x24 PVOID EntryInProgress;
} PEB_LDR_DATA,*PPEB_LDR_DATA;

其中InLoadOrderModuleListInMemoryOrderModuleListInInitializationOrderModuleList为三个双向链表结构:

  • InLoadOrderModuleList: 模块加载顺序
  • InMemoryOrderModuleList :加载后在内存中的顺序
  • InInitializationOrderModuleList :模块初始化的顺序

指向的结构为:**_LDR_DATA_TABLE_ENTRY**,该结构为模块的详细信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
WORD LoadCount;
WORD TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union
{
ULONG TimeDateStamp;
PVOID LoadedImports;
};
_ACTIVATION_CONTEXT * EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagLinks;
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
  • 在3环:使用了InLoadOrderLinks,使用InMemoryOrderModuleListInInitializationOrderModuleList这三个链表。
  • 在0环:_LDR_DATA_TABLE_ENTRY中只使用了InLoadOrderLinks,并没有使用InMemoryOrderModuleListInInitializationOrderModuleList这两个链表。

使用如下方法查看:

1
2
3
4
5
6
7
8
9
kd> dt _PEB_LDR_DATA 0x00241ea0
nt!_PEB_LDR_DATA
+0x000 Length : 0x28
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x241ee0 - 0x242010 ]
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x241ee8 - 0x242018 ]
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x241f58 - 0x242020 ]
+0x024 EntryInProgress : (null)

[ 0x241ee0 - 0x242010 ]分别为:Flink(当前模块)与Blink(上一个模块)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kd> dt _LDR_DATA_TABLE_ENTRY 0x241ee0 
nt!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x241f48 - 0x241eac ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x241f50 - 0x241eb4 ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x018 DllBase : 0x00400000 Void
+0x01c EntryPoint : 0x00401190 Void
+0x020 SizeOfImage : 0x2c000
+0x024 FullDllName : _UNICODE_STRING "C:\Documents and Settings\A1v1n\桌面\VC6\Microsoft Visual Studio\MyProjects\0119_PEB\Debug\0119_PEB.exe"
+0x02c BaseDllName : _UNICODE_STRING "0119_PEB.exe"
+0x034 Flags : 0x5000
+0x038 LoadCount : 0xffff
+0x03a TlsIndex : 0
+0x03c HashLinks : _LIST_ENTRY [ 0x7c99b2d8 - 0x7c99b2d8 ]
+0x03c SectionPointer : 0x7c99b2d8 Void
+0x040 CheckSum : 0x7c99b2d8
+0x044 TimeDateStamp : 0x61e7f524
+0x044 LoadedImports : 0x61e7f524 Void
+0x048 EntryPointActivationContext : (null)
+0x04c PatchInformation : (null)

有大佬总结在Win XP和Win7下,InLoadOrderModuleList指向的模块按照顺序:第一个是EXE模块本身,第二个是NTDLL.DLL,第三个是KERNEL32.DLL。这样,KERNEL32.DLL的顺序是不是又固定了呢?实际上这样也并非能在任何场景下通用,还是老老实实拿着模块名称来遍历查询的ShellCode才通用。(可参考:PEB及LDR链最后一段。)

25.png

图片来源:Window中的shellcode编写框架(入门篇)

3.2 断链原理

模块断链只是隐藏起来不让API查到,但是正常的功能不受影响。

3环下PEB断链是一种常见的模块隐藏技术,原理是修改_PEB_LDR_DATA中的三个双向链表,删除链表中的项,让 CreateToolhelp32Snapshot之类的API无法通过模块列表枚举DLL。

结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _PEB_LDR_DATA
{
 +0x00 ULONG Length;
 +0x04 BOOLEAN Initialized;
 +0x08 PVOID SsHandle;
 +0x0c LIST_ENTRY InLoadOrderModuleList;
 +0x14 LIST_ENTRY InMemoryOrderModuleList;
 +0x1c LIST_ENTRY InInitializationOrderModuleList;
+0x24 PVOID EntryInProgress;
} PEB_LDR_DATA,*PPEB_LDR_DATA;

typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

需要注意的三个结构:

  • InLoadOrderModuleList :描述当前进程中模块的加载顺序
  • InMemoryOrderModuleList:所有模块在内存中的顺序
  • InInitializationOrderModuleList :所有模块初始化的顺序

3.2 代码:隐藏所有模块

所谓断链,就是把三个链表断掉,让3环的API读不出模块信息。最简单的做法就是让当前进程的Flink和Blink指向它自己。

⚠️注意:要隐藏模块,则这个模块在这三个结构中都要断开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "stdafx.h"
#include <Windows.h>
#include <stdlib.h>
#include <tchar.h>

// 内核Unicode字符串
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
} PEB_LDR_DATA,*PPEB_LDR_DATA;

VOID HideModule();
int _tmain(int argc, _TCHAR* argv[])
{
printf("未断链\n");
system("Pause");
HideModule();
printf("断链后\n");
system("Pause");
return 0;
}
VOID HideModule()
{
PPEB_LDR_DATA Ldr = NULL;
PLIST_ENTRY Head = NULL;
__asm
{
pushad;
mov eax, fs:[30h]; //eax 指向PEB
mov eax, [eax+0xc] //eax 指向PEB_LDR_DATA
mov Ldr, eax;
popad;
}

Head = Ldr->InLoadOrderModuleList.Flink;
Head->Flink = Head;
Head->Blink = Head;

Head = Ldr->InMemoryOrderModuleList.Flink;
Head->Flink = Head;
Head->Blink = Head;

Head = Ldr->InInitializationOrderModuleList.Flink;
Head->Flink = Head;
Head->Blink = Head;

return;
}

运行环境:Windows7 SP1 x64,VC6++

断链前:

26.png

断链后:

27.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//内嵌汇编获取Kernel32的地址
__declspec(naked) DWORD getKernel32()
{
__asm
{
mov eax,fs:[30h] //eax 指向PEB
mov eax,[eax+0ch] //eax 指向PEB_LDR_DATA
mov eax,[eax+14h] //eax 指向InMemoryOrderModuleList
mov eax,[eax] //eax 指向LDR->指向InMemoryOrderModuleList.Flink
mov eax,[eax] //eax 指向当前模块的地址
mov eax,[eax+10h]//eax 指向第五个模块
ret
}
}
int main()
{
//kernel32.dll 基址的动态获取
HMODULE hLoadLibrary = LoadLibraryA("kernel32.dll");
//使用内嵌汇编来获取基址
HMODULE _hLoadLibrary = (HMODULE)getKernel32();
//效果是一样的
printf("LoadLibraryA动态获取的地址: 0x%x\n", hLoadLibrary);
printf("内嵌汇编获取的地址: 0x%x\n", _hLoadLibrary);
return 0;
}

3.3 代码:隐藏指定模块

隐藏本进程的kernel32.dll模块。

方案1:在PEB_LDR_DATA断开:
InLoadOrderModuleList
InMemoryOrderModuleList
InInitializationOrderModuleList
这种和方案3实质是一样的

方案2:在三个List的LDR_DATA_TABLE_ENTRY仅断开InLoadOrderLinks,不可行。

方案3:在LDR_DATA_TABLE_ENTRY里断开对应的:
InLoadOrderLinks
InMemoryOrderLinks
InInitializationOrderLinks

经过实际代码运行分析,有这样的对应关系:

  • InLoadOrderModuleList->Flink == InLoadOrderLinks
  • InMemoryOrderModuleList->Flink == InMemoryOrderLinks
  • InInitializationOrderModuleList->Flink == InInitializationOrderLinks

示意图如下:

30_1.png

由上图可知,InLoadOrderModuleList->Flink == InLoadOrderLinks与其他两个不一定在同一个结构体中。且都是对应指过去。

故如下代码注释可以改为:

1
2
3
Ldte = (PLDR_DATA_TABLE_ENTRY)pTemp;
Ldte = (PLDR_DATA_TABLE_ENTRY)(pTemp - 0x8);
Ldte = (PLDR_DATA_TABLE_ENTRY)(pTemp - 0x10);

因为:InLoadOrderModuleList既是LIST_ENTRY结构,也是LDR_DATA_TABLE_ENTRY结构,而InLoadOrderLinks仅为LIST_ENTRY结构。由于对应结构都是一一指向过去,拿InMemoryOrderList举例,其InMemoryOrderList.Flink == InMemoryOrderLinks,相对于LDR_DATA_TABLE_ENTRY结构偏移量为0x8。

⚠️:InLoadOrderModuleList不同InMemoryOrderLinks。InLoadOrderModuleList.Flink为当前模块的LDR_DATA_TABLE_ENTRY,InMemoryOrderLinks.Flink为下一个模块的LIST_ENTRY

隐藏模块代码如下(如果题目改为隐藏除了kernel32.dll外的所有模块,只需要将UnicodeStringCMP函数比较结果取反即可):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include "stdafx.h"
#include <Windows.h>
#include <stdlib.h>
#include <tchar.h>

// 内核Unicode字符串
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
} PEB_LDR_DATA,*PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
WORD LoadCount;
WORD TlsIndex;
//后面的成员暂时用不到,就不列举了
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

VOID HideOneModule(PWSTR pMoudleName);
BOOL UnicodeStringCMP(PWSTR Sting_1, PWSTR Sting_2);

int _tmain(int argc, _TCHAR* argv[])
{
printf("隐藏模块kernel32.dll前\n");
system("Pause");

HideOneModule(L"kernel32.dll");
printf("隐藏模块kernel32.dll后\n");

system("Pause");
return 0;
}

VOID HideOneModule(PWSTR pMoudleName)
{
PPEB_LDR_DATA Ldr = NULL;
PLIST_ENTRY Head = NULL;
PLIST_ENTRY pTemp = NULL;
PLDR_DATA_TABLE_ENTRY Ldte = NULL;
__asm
{
pushad;
mov eax, fs:[30h]; //eax 指向PEB
mov eax, [eax+0xc] //eax 指向PEB_LDR_DATA
mov Ldr, eax;
popad;
}

Head = &(Ldr->InLoadOrderModuleList);
pTemp = Head->Flink;
do
{
//Ldte = (PLDR_DATA_TABLE_ENTRY)pTemp; //当前模块
Ldte = CONTAINING_RECORD(pTemp, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if(UnicodeStringCMP(Ldte->BaseDllName.Buffer, pMoudleName))
{
Ldte->InLoadOrderLinks.Blink->Flink = Ldte->InLoadOrderLinks.Flink;
Ldte->InLoadOrderLinks.Flink->Blink = Ldte->InLoadOrderLinks.Blink;
}
pTemp = pTemp->Flink;
}
while(Head != pTemp);

Head = &(Ldr->InMemoryOrderModuleList);
pTemp = Head->Flink;
do
{
//Ldte = (PLDR_DATA_TABLE_ENTRY)pTemp;
Ldte = CONTAINING_RECORD(pTemp, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if(UnicodeStringCMP(Ldte->BaseDllName.Buffer, pMoudleName))
{
Ldte->InMemoryOrderLinks.Blink->Flink = Ldte->InMemoryOrderLinks.Flink;
Ldte->InMemoryOrderLinks.Flink->Blink = Ldte->InMemoryOrderLinks.Blink;
}
pTemp = pTemp->Flink;
}
while(Head != pTemp);

Head = &(Ldr->InInitializationOrderModuleList);
pTemp = Head->Flink;
do
{
//Ldte = (PLDR_DATA_TABLE_ENTRY)pTemp;
Ldte = CONTAINING_RECORD(pTemp, LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks);
if(UnicodeStringCMP(Ldte->BaseDllName.Buffer, pMoudleName))
{
Ldte->InInitializationOrderLinks.Blink->Flink = Ldte->InInitializationOrderLinks.Flink;
Ldte->InInitializationOrderLinks.Flink->Blink = Ldte->InInitializationOrderLinks.Blink;
}
pTemp = pTemp->Flink;
}
while(Head != pTemp);

return;
}
//字符串比较函数
BOOL UnicodeStringCMP(PWSTR Sting_1, PWSTR Sting_2)
{
while (*Sting_1)
{
if (*Sting_1 != *Sting_2)
{
return FALSE;
break;
}
Sting_1++;
Sting_2++;
}

return TRUE;
}

运行环境:Windows7 SP1 x64,VC6++

隐藏模块kernel32.dll前:

28.png

隐藏模块kernel32.dll后:

29.png