驱动层捕获无模块shellcode。

这里学一手扫描 shellcode 的方法,题目来源于 2024 年的腾讯游戏安全竞赛的决赛。

扫描shellcode

主要方法是插中断扫栈和扫内存。这里调几个复现了的方法去讲,中断有 DPC,NMI,IPI 这几类。扫内存可以扫 BigPool,扫页表,扫物理内存。

NMI

NMI (Non Maskable Interrupt)——不可屏蔽中断(即CPU不能屏蔽)无论状态寄存器中 IF 位的状态如何,CPU收到有效的NMI必须进行响应,它在被响应时无中断响应周期。

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include <ntifs.h>
#include <ntdef.h>
#include <ntstatus.h>
#include <ntddk.h>
#include <ntstrsafe.h>

#define kprintf(format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, ##__VA_ARGS__)
#define MAX_BACKTRACE_DEPTH 20
ULONG64 num = 0;
NTSTATUS EnumerateKernelThreads();

typedef NTSTATUS (*ZWQUERYSYSTEMINFORMATION)(ULONG, PVOID, ULONG, PULONG);

typedef void (__fastcall *_HalSendNMI)(ULONG64 a1);
typedef struct _KAFFINITY_EX {
SHORT Count;
SHORT Size;
ULONG Padding;
ULONG64 bitmap[20];
} KAFFINITYEX, * PKAFFINITYEX;

typedef void (__fastcall* _KeInitializeAffinityEx)(PKAFFINITYEX pkaff);
typedef void (__fastcall* _KeAddProcessorAffinityEx)(PKAFFINITYEX pkaff, ULONG nmu);

PDRIVER_OBJECT g_Object = NULL;
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
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;
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY
{
ULONG Unknow1;
ULONG Unknow2;
ULONG Unknow3;
ULONG Unknow4;
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT NameLength;
USHORT LoadCount;
USHORT ModuleNameOffset;
char ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;

typedef struct _SYSTEM_MODULE_INFORMATION
{
ULONG Count;//内核中以加载的模块的个数
SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;


PVOID callers[20];
ULONG count = 20;
ULONG numFrames = 0;
PVOID ModuleBase[2000];
ULONG ModuleSize[2000];
ULONG CountOfModule = 0;

VOID DRIVERUNLOAD(_In_ struct _DRIVER_OBJECT* DriverObject)
{
kprintf(("unload\n"));

}


void NmiCallbackForCheck(PVOID Context,BOOLEAN handule) {
numFrames = RtlWalkFrameChain(callers,count,0);
return 1;
}

NTSTATUS NMISearch() {
NTSTATUS status;
PVOID NmiCallbackHandle;
ULONG ProcessorCount;
KAFFINITYEX NmiAffinity;

UNICODE_STRING SystemRoutineName;
ULONG numCors = KeQueryActiveProcessorCountEx(0);

RtlInitUnicodeString(&SystemRoutineName, L"HalSendNMI");
_HalSendNMI HalSendNMI = MmGetSystemRoutineAddress(&SystemRoutineName);
if (!HalSendNMI) {
kprintf(("Failed to get HalSendNMI address\n"));
return STATUS_UNSUCCESSFUL;
}

RtlInitUnicodeString(&SystemRoutineName, L"KeInitializeAffinityEx");
_KeInitializeAffinityEx KeInitializeAffinityEx = MmGetSystemRoutineAddress(&SystemRoutineName);
if (!KeInitializeAffinityEx) {
kprintf(("Failed to get KeInitializeAffinityEx address\n"));
return STATUS_UNSUCCESSFUL;
}

RtlInitUnicodeString(&SystemRoutineName, L"KeAddProcessorAffinityEx");
_KeAddProcessorAffinityEx KeAddProcessorAffinityEx = MmGetSystemRoutineAddress(&SystemRoutineName);
if (!KeAddProcessorAffinityEx) {
kprintf(("Failed to get KeAddProcessorAffinityEx address\n"));
return STATUS_UNSUCCESSFUL;
}

// 获取CPU逻辑核心数量
ProcessorCount = KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS);

// 注册NMI回调函数
NmiCallbackHandle = KeRegisterNmiCallback(NmiCallbackForCheck, NULL);
if (!NmiCallbackHandle) {
kprintf(("Failed to register NMI callback\n"));
return STATUS_UNSUCCESSFUL;
}
int found = 0;
PVOID shellcodeaddr = NULL;
while (!found) {
for (CCHAR i = 0; i < (CCHAR)ProcessorCount && !found; ++i) {
KeInitializeAffinityEx(&NmiAffinity);
KeAddProcessorAffinityEx(&NmiAffinity, i);
kprintf(("Registered NMI for processor %d\n", i));
HalSendNMI(&NmiAffinity);
LARGE_INTEGER inTime;
inTime.QuadPart = 10 * -10000; // 1 second delay
KeDelayExecutionThread(KernelMode, FALSE, &inTime);
for (ULONG i = 0; i < numFrames; i++) {
int flag = 1;
for (ULONG j = 0; j < CountOfModule; j++) {
if ((ULONG)callers[i] >= (ULONG)ModuleBase[j] && (ULONG)callers[i] < ((ULONG)ModuleBase[j] + ModuleSize[j])) {
flag = 0;
break;
}
}
if (flag) {
found = 1;
shellcodeaddr = callers[i];
}
}
if (!found) {
kprintf(("shellcode not found in current NMI\n"));
}
}
}
kprintf(("shellcode found in %p\n"), shellcodeaddr);

KeDeregisterNmiCallback(NmiCallbackHandle);
return STATUS_SUCCESS;
}
void GetAllModule() {
LDR_DATA_TABLE_ENTRY *TE, *Tmp;
TE = (LDR_DATA_TABLE_ENTRY*)g_Object->DriverSection;
PLIST_ENTRY LinkList;
;
int i = 0;
LinkList = TE->InLoadOrderLinks.Flink;
while (LinkList != &TE->InLoadOrderLinks)
{
Tmp = (LDR_DATA_TABLE_ENTRY*)LinkList;
ModuleBase[i] = (UINT64)(Tmp->DllBase);
ModuleSize[i] = (UINT64)(Tmp->SizeOfImage);
//kprintf(("%S:%p~%p\n"),Tmp->BaseDllName.Buffer,Tmp->DllBase,(ULONG)(Tmp->DllBase)+(ULONG)(Tmp->SizeOfImage));
LinkList = LinkList->Flink;
i++;
}
CountOfModule = i;
ULONG Size = (ULONG)(g_Object->DriverSize);//把自身的地址和大小也加进去
ModuleBase[CountOfModule] = g_Object->DriverStart;
ModuleSize[CountOfModule++] = Size;

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {
kprintf(("Hello xia0ji233\n"));
pDriver->DriverUnload = DRIVERUNLOAD;
g_Object = pDriver;
GetAllModule();
NTSTATUS status = NMISearch();
if (!NT_SUCCESS(status)) {
kprintf(("NMICallBack failed with status 0x%x\n", status));
}
return status;
}

向所有 CPU 发送 NMI,打断CPU执行,执行栈回溯,保存下栈的值,NMI 响应完成之后马上去分析栈的值,是否有不在模块范围内的值,如果有就直接输出,说明找到了 shellcode。

需要注意的是,因为自身驱动加载的过程中,还没有加载进去,因此遍历模块的时候需要额外加上自身模块的地址,否则容易找到正在执行的 DriverEntry。自身模块的地址和大小可以使用 pDriver->StartpDriver->DriverSize 获得。

执行结果:

NMI 回调中,返回 0 会直接蓝屏,返回 1 则会继续处理。并且 NMI 中断优先级很高,保存栈情况即可,不要调用 DbgPrint 和其它一些操作。

IPI

处理器间中断(英语:Inter-Processor Interrupt,IPI)是一种特殊类型的中断,即在多处理器系统中,如果中断处理器需要来自其它处理器的动作,一个处理器向另一个处理器发出的中断行为。

IPI 同样是一种高优先级的中断,方法也几乎是一模一样的,只需要调用 API KeIpiGenericCall 就可以让所有的核心都打断执行同一个函数。

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
ULONG_PTR IpiBroadcastFunction(ULONG_PTR Argument) {
numFrames = RtlWalkFrameChain(callers,count,0);
return NULL;
}

NTSTATUS IPISearch() {
int found = 0;
PVOID shellcodeaddr = NULL;
while (!found) {
KeIpiGenericCall(IpiBroadcastFunction, NULL);
LARGE_INTEGER inTime;
inTime.QuadPart = 10 * -10000; // 10 ms delay
KeDelayExecutionThread(KernelMode, FALSE, &inTime);
for (ULONG i = 0; i < numFrames; i++) {
int flag = 1;
for (ULONG j = 0; j < CountOfModule; j++) {
if ((ULONG)callers[i] >= (ULONG)ModuleBase[j] && (ULONG)callers[i] < ((ULONG)ModuleBase[j] + ModuleSize[j])) {
flag = 0;
break;
}
}
if (flag) {
found = 1;
shellcodeaddr = callers[i];
}
}
if (!found) {
kprintf(("shellcode not found in current IPI\n"));
}
}
kprintf(("shellcode found in %p\n"), shellcodeaddr);
return 0;
}

DPC

即延时过程调用,它同样可以打断正在执行的CPU。

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
ULONG_PTR DPCRountine(ULONG_PTR Argument) {
numFrames = RtlWalkFrameChain(callers,count,0);
return NULL;
}

NTSTATUS DPC1Search() {
ULONG ProcessorCount = 0;
KDPC Dpc;
int found = 0;
PVOID shellcodeaddr = NULL;
ProcessorCount = KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS);
while (!found) {

for (CCHAR i = 0; i < (CCHAR)ProcessorCount && !found; ++i) {
KeInitializeDpc(&Dpc, DPCRountine, NULL);
KeSetTargetProcessorDpc(&Dpc, i);
KeInsertQueueDpc(&Dpc, NULL, NULL);
KeFlushQueuedDpcs();
for (ULONG i = 0; i < numFrames; i++) {
int flag = 1;
for (ULONG j = 0; j < CountOfModule; j++) {
if ((ULONG)callers[i] >= (ULONG)ModuleBase[j] && (ULONG)callers[i] < ((ULONG)ModuleBase[j] + ModuleSize[j])) {
flag = 0;
break;
}
}
if (flag) {
found = 1;
shellcodeaddr = callers[i];
}
}
if (!found) {
kprintf(("shellcode not found in current DPC\n"));
}
LARGE_INTEGER inTime;
inTime.QuadPart = 10 * -10000; // 1 second delay
KeDelayExecutionThread(KernelMode, FALSE, &inTime);
}
}
kprintf(("shellcode found in %p\n"), shellcodeaddr);
return 0;
}

BigPool扫描

通过字节特征码去比对 tag 为 ‘ace0’ 的内存。

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
#include <ntifs.h>

PDRIVER_OBJECT g_Object;

#define kprintf(format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, ##__VA_ARGS__)
#define SystemBigPoolInformation 66

typedef NTSTATUS (*ZWQUERYSYSTEMINFORMATION)(ULONG, PVOID, ULONG, PULONG);

typedef struct _SYSTEM_BIGPOOL_ENTRY {
union {
PVOID VirtualAddress;
ULONG_PTR NonPaged : 1;
};
ULONG_PTR SizeInBytes;
union {
UCHAR Tag[4];
ULONG TagUlong;
};
} SYSTEM_BIGPOOL_ENTRY, *PSYSTEM_BIGPOOL_ENTRY;

typedef struct _SYSTEM_BIGPOOL_INFORMATION {
ULONG Count;
SYSTEM_BIGPOOL_ENTRY AllocatedInfo[1];
} SYSTEM_BIGPOOL_INFORMATION, *PSYSTEM_BIGPOOL_INFORMATION;

VOID DRIVERUNLOAD(_In_ struct _DRIVER_OBJECT* DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
kprintf("unload\n");
}


PVOID FindPattern(PUCHAR base, ULONG length, PCUCHAR pattern, ULONG patternLength) {
for (ULONG i = 0; i < length - patternLength; i++) {
BOOLEAN found = TRUE;
for (ULONG j = 0; j < patternLength; j++) {
if (pattern[j] != base[i + j]) {
found = FALSE;
break;
}
}
if (found) {
return &base[i];
}
}
return NULL;
}


NTSTATUS BigPoolSearch() {
ULONG poolInformationLength = 0x50000;
UNICODE_STRING routineName;
RtlInitUnicodeString(&routineName, L"ZwQuerySystemInformation");

ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)MmGetSystemRoutineAddress(&routineName);
if (!ZwQuerySystemInformation) {
kprintf("Failed to get ZwQuerySystemInformation address\n");
return STATUS_UNSUCCESSFUL;
}

NTSTATUS status = 0;

PVOID poolInformation = ExAllocatePoolWithTag(NonPagedPool, poolInformationLength, 'ace0');
if (!poolInformation) {
kprintf("Failed to allocate pool information buffer\n");
return STATUS_INSUFFICIENT_RESOURCES;
}

status = ZwQuerySystemInformation(SystemBigPoolInformation, poolInformation, poolInformationLength, &poolInformationLength);
if (!NT_SUCCESS(status)) {
kprintf("Failed to query pool information\n");
ExFreePoolWithTag(poolInformation, 'ace0');
return status;
}

PSYSTEM_BIGPOOL_INFORMATION bigPoolInfo = (PSYSTEM_BIGPOOL_INFORMATION)poolInformation;

for (ULONG i = 0; i < bigPoolInfo->Count; i++) {
PSYSTEM_BIGPOOL_ENTRY entry = &bigPoolInfo->AllocatedInfo[i];
if (entry->TagUlong == 'ace0') {
ULONG_PTR lpAddress = (ULONG_PTR)(entry->VirtualAddress) & (~1ull);
kprintf("Pool Entry: Address=%p, Size=%llu, Tag='%c%c%c%c'\n",
lpAddress,
entry->SizeInBytes,
entry->Tag[3],
entry->Tag[2],
entry->Tag[1],
entry->Tag[0]);
ULONG SizeCopied;
MM_COPY_ADDRESS MmCopyAddress;
PVOID Buffer = ExAllocatePool(NonPagedPoolNx, entry->SizeInBytes);
MmCopyAddress.VirtualAddress = Buffer;
status = MmCopyMemory(Buffer, MmCopyAddress, entry->SizeInBytes, MM_COPY_MEMORY_VIRTUAL, &SizeCopied);
if (NT_SUCCESS(status)) {
UCHAR pattern[] = { 0x41, 0xB8, 0xCE, 0x0A, 0x00, 0x00};
PVOID res=FindPattern(lpAddress, entry->SizeInBytes, pattern, 6);
if (res) {
kprintf(("shellcode Found in address %p\n"), lpAddress);
return STATUS_SUCCESS;
}
}
}
}
ExFreePoolWithTag(poolInformation, 'ace0');
return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {
UNREFERENCED_PARAMETER(pReg);
kprintf("Hello xia0ji233\n");

pDriver->DriverUnload = DRIVERUNLOAD;
g_Object = pDriver;

NTSTATUS status = BigPoolSearch();
if (!NT_SUCCESS(status)) {
kprintf("BigPoolSearch failed with status 0x%x\n", status);
}
return status;
}

参考文献