avatar

Catalog
特征码搜索

本篇进行一个小实验,编写一个函数,通过特征码搜索一个未导出函数(PspTerminateProcess),并调用。看着不复杂,但是涉及到了很多细节,这里逐步分析。

什么是未导出函数

先介绍一个概念,未导出函数。什么是未导出函数呢?

这个举个简单的例子,Ntoskrnl.exe中有很多内核函数,其中包括我们之前分析过的NtReadVirtualMemory函数。这个函数是ReadProcessMemory在0环的实现,但它是一个未导出函数,用IDA打开Ntoskrnl.exe,在Exports(导出表)中搜索NtReadVirtualMemory 结果发现,并没有搜索到NtReadVirtualMemory,原因是它并没有被导出,所以导出表里面,没有该函数,但是这个函数又的确存在在Ntoskrnl.exe这个模块中。

未导出函数的主要目的,是不想提供给别人使用,官方文档也不会写入相关内容。因此,我们是不能通过函数名直接调用该函数的。

如何调用未导出函数

调用未导出函数主要有两种办法:

  1. 特征码搜索,这也是本篇会着重介绍的。在前一篇文章中,提到过可以通过驱动结构体的DriverSection字段,找到一个_LDR_DATA_TABLE_ENTRY结构体,然后即可遍历内核的全部模块。这时,如果我们确定了未导出函数所在的模块,便可以通过在链表中找到模块对应的描述信息,例如模块基址,大小等。这时,我们再利用这些信息,结合未导出函数的特征码搜索这个模块的内存,便能找到所需的未导出函数。这时只需定义一个函数指针指向咱们找到的函数首地址,就可以实现对未导出函数的调用。
  2. 外层函数调用,如果有一个函数A调用了未导出函数B,那么函数A中一定有未导出函数B的函数地址,我们只要找到这个调用点,就可以确定函数B的地址了。然后再定义一个函数指针,就可实现这个过程。

特征码选择

在特征码的选择上也是有讲究的,拿本次实验的PspTerminateProcess函数来举例 结合图片来看。

  • 橙色方框圈住的地方就不适合作为特征码,这部分的语句,比较常见,在很多内核函数中都容易看到,因此不适合作为特征码,有可能会因此找到了别的内核函数。
  • 红色方框圈住的地方相对来说就比较适合作为特征码,当然,连续四行完全相同的语句,还是比较少见的,因此,选中的代码段越多,就越合适作为特征码

有一点需要说明的是,特征码不是汇编,而是硬编码,具体应用时,可以将这些特征码放入到一个字符串中,例如:

c
1
UCHAR AttrCode[6] = {0x8b, 0x75, 0x08, 0x3b, 0x70, 0x44}

然后与指定的内存进行逐一比较,若发现相同的地方,很有可能就找对了对方,然后再减去函数内的偏移,得到函数的首地址,就可以获取该函数了

代码实现

这部分的代码实现不是非常难,并且用到了前一篇用到的知识,这里仅再重述一遍思路:

  1. 通过当前驱动结构体找到内核模块结构体链表
  2. 遍历内核模块,找到Ntoskrnl.exe(PspTerminateProcess所在模块)模块结构体
  3. 获取模块基址模块大小
  4. 确定特征码
  5. 从模块基址开始的地方,与特征码依次进行比对,从而找到未导出函数PspTerminateProcess
  6. 用一个函数指针指向未导出函数
  7. 调用函数PspTerminateProcess
c
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
#include "ntifs.h"

typedef NTSTATUS NTKERNELAPI(*PspTerminateProcess)(PEPROCESS Process, NTSTATUS ExitStatus);

VOID Driver_Unload(PDRIVER_OBJECT driver);
UINT32 FindKernelModule(PDRIVER_OBJECT driver);
PVOID FindAttrCode(PDRIVER_OBJECT driver);
VOID TestFunc(PDRIVER_OBJECT driver);

NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING RegistryPath) {

DbgPrint("Driver is running!\n");
TestFunc(driver);

driver->DriverUnload = Driver_Unload;

return STATUS_SUCCESS;
}

VOID TestFunc(PDRIVER_OBJECT driver) {
PspTerminateProcess pFun;
PEPROCESS pEprocess;

pFun = FindAttrCode(driver);
if (pFun != NULL) {
// DbgPrint("Get Success\n");
//PID的值根据具体进程而定
PsLookupProcessByProcessId((HANDLE)1752, &pEprocess);
pFun(pEprocess, 1);
DbgPrint("Close notepad success!");
}
else {
DbgPrint("Get Failed\n");
}
}

PVOID FindAttrCode(PDRIVER_OBJECT driver) {
UINT32 i = 0;
UINT32 ds = FindKernelModule(driver);
UINT32 ModuleBase = *(PUINT32)(ds + 0x18);
UINT32 ModuleSize = *(PUINT32)(ds + 0x20);
//+0xC
UCHAR Offset[15] = {
0x8b, 0x75, 0x08, 0x3b, 0x70, 0x44,
0x75, 0x07, 0xb8, 0x0d, 0x00, 0x00,
0xc0, 0xeb, 0x5a };
PUCHAR pModuleMemory = NULL;

__try {
DbgPrint("Name: %wZ, Base: %x, Size: %x\n", (PUINT32)(ds + 0x2c), ModuleBase, ModuleSize);
while (i < (ModuleSize - 0xf)) {
pModuleMemory = (PUCHAR)(ModuleBase + i);
if (RtlCompareMemory(Offset, pModuleMemory, 0xf) == 0xf) {
// DbgPrint("%016x", *pModuleMemory);
return pModuleMemory - 0xc;
}
i++;
}
}
__except (1) {

}

return NULL;
}


UINT32 FindKernelModule(PDRIVER_OBJECT driver) {
UINT32 ds, dsCurrent, dsDest;
UNICODE_STRING dllStr;

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

// DbgPrint("_Driver_Object: %x\n", driver);
// DbgPrint("_LDR_DATA_TABLE_ENTRY: %x\n", driver->DriverSection);

ds = (UINT32)(driver->DriverSection);
dsCurrent = ds;
// DbgPrint("Name: %wZ, Base: %x, Size: %x\n", (PUINT32)(ds + 0x2c), *(PUINT32)(ds + 0x18), *(PUINT32)(ds + 0x20));

while (1) {
ds = *(PUINT32)ds;
if (ds == dsCurrent) {
break;
}
if (RtlCompareUnicodeString(&dllStr, (PUINT32)(ds + 0x2c), TRUE) == 0) {
// DbgPrint("Name: %wZ, Base: %x, Size: %x\n", (PUINT32)(ds + 0x2c), *(PUINT32)(ds + 0x18), *(PUINT32)(ds + 0x20));
break;
}

}
dsDest = ds;
return dsDest;
}

VOID Driver_Unload(PDRIVER_OBJECT driver) {
DbgPrint("Unload Success!");
}

结果验证

  1. 本次实验,计划通过调用未导出函数PspTerminateProcess关掉指定进程(本次实验采用记事本)
  2. 在PC Hunter确定记事本的Pid
  3. 写入到代码中
  4. 生成.sys驱动文件,复制到虚拟机。通过Kmd Manager注册
  5. 运行该驱动,查看结果 发现成功的关掉了记事本这个进程

总结

本次实验,通过在内存中搜索特征码,调用了一个未导出函数PspTerminateProcess,并关掉了记事本这个进程,当然我们是手动将Pid填入代码后才实现的指定进程,随着进一步学习,会逐渐改进并完善这份代码

参考文章:https://blog.csdn.net/jjjyu123/article/details/13616277

参考代码:上善若水,葫芦娃救爷爷,Joney,张嘉杰

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2020/04/11/%E7%89%B9%E5%BE%81%E7%A0%81%E6%90%9C%E7%B4%A2/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶