0%

在内核中创建线程注入DLL到R3进程(3)

简介

使用通知的方式只能在进程创建的时候注入,而我们想注入一个已经运行的进程时,
可以使用与应用层类似的,申请注入shellcode的内存,并创建线程来运行的方式。
这里参照 Blackbone 的部分代码进行修改:https://github.com/DarthTon/Blackbone

工作原理

推荐的方法是获取SSDT表,找到 NtCreateThreadEx 函数来创建线程。在64位系统中获取SSDT的方法,
通常是搜索特征码,可以从 Blackbone 中查看,这里暂不讨论。另外 RtlCreateUserThread 函数也
可以创建线程,但好像还涉及 PrevMode 等信息,虽然测试未出现蓝屏现象,这种方法待议
参见: https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/previousmode

1
2
3
4
5
6
7
8
9
10
11
NTSTATUS NTAPI RtlCreateUserThread(
IN HANDLE ProcessHandle, // 目标进程
IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
IN BOOLEAN CreateSuspended, // 创建后是否挂起
IN ULONG StackZeroBits OPTIONAL,
IN SIZE_T StackReserve OPTIONAL,
IN SIZE_T StackCommit OPTIONAL,
IN PVOID StartAddress, // 线程函数地址
IN PVOID Parameter OPTIONAL,
OUT PHANDLE ThreadHandle OPTIONAL,
OUT PCLIENT_ID ClientId OPTIONAL);

这里使用 在内核中获取进程的DLL和导出函数(1) 文章中获取的 WinExec 导出函数为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 以调用WinExec函数为目标生成的ShellCode
#if AMD64
unsigned char ShellCode[] = {
0x48, 0x83, 0xEC, 0x28, // sub rsp,28h
0x48, 0xBA, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rdx,5
0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rcx,xx
0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax,xx
0xFF, 0xD0, // call rax
0x48, 0x31, 0xC0, // xor rax,rax
0x48, 0x83, 0xC4, 0x28, // add rsp,28h
0xC3 }; // ret
#else
unsigned char ShellCode[] = {
0x68, 0x05, 0x00, 0x00, 0x00, // push 5
0x68, 0x00, 0x00, 0x00, 0x00, // push xx
0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax,xx
0xFF, 0xD0, // call eax
0x33, 0xC0, // xor eax,eax
0xC2, 0x04, 0x00 }; // ret 4
#endif

调用的函数如下所示

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
NTSTATUS InjectExplorerProcess(IN PCCHAR RunCmd)
{
NTSTATUS Status = STATUS_SUCCESS;
HANDLE ProcessId = NULL;
PEPROCESS Process = NULL;
KAPC_STATE ApcState = { 0 };
PEPROCESS CurrProcess = NULL;
PUCHAR Buffer = NULL;
SIZE_T BufferSize = 0;
SIZE_T RunCmdSize = 0;
HANDLE ThreadHandle = NULL;
LARGE_INTEGER Timeout = { 0 };
PVOID Kernel32Addr = NULL;
PVOID WinExecAddr = NULL;
// 检查参数的有效性
if (!RunCmd) return STATUS_INVALID_PARAMETER;
RunCmdSize = strlen(RunCmd) + 1;
// 获取Explorer进程ID
Status = GetExplorerProcessId(&ProcessId);
if (!NT_SUCCESS(Status)) return Status;
// 检查是否是当前进程
CurrProcess = PsGetCurrentProcess();
PsLookupProcessByProcessId(ProcessId, &Process);
// 检查是否是受保护的进程
if (PsIsProtectedProcess(Process))
return STATUS_ACCESS_DENIED;
// 附加到目标进程中
if (Process != CurrProcess)
KeStackAttachProcess(Process, &ApcState);
do
{
// 获取Kernel32的地址
Kernel32Addr = GetKernel32Address(Process, FALSE);
if (!Kernel32Addr) break;
// 获取WinExec导出函数
WinExecAddr = GetExportFuncAddr(Kernel32Addr, "WinExec");
if (!WinExecAddr) break;
// 在目标进程中申请内存
BufferSize = sizeof(ShellCode) + RunCmdSize;
Status = ZwAllocateVirtualMemory(
NtCurrentProcess(), (PVOID)&Buffer, 0, &BufferSize,
MEM_TOP_DOWN | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!NT_SUCCESS(Status)) break;
// 拷贝ShellCode和RunCmd信息
RtlCopyMemory(Buffer, ShellCode, sizeof(ShellCode));
RtlCopyMemory(Buffer + sizeof(ShellCode), RunCmd, RunCmdSize);
#ifdef AMD64
*(PULONG_PTR)(Buffer + 16) = (ULONG_PTR)(Buffer + sizeof(ShellCode));
*(PULONG_PTR)(Buffer + 26) = (ULONG_PTR)WinExecAddr;
#else
*(PULONG_PTR)(Buffer + 6) = (ULONG_PTR)(Buffer + sizeof(ShellCode));
*(PULONG_PTR)(Buffer + 11) = (ULONG_PTR)WinExecAddr;
#endif
// 创建用户线程(推荐使用NtCreateThreadEx函数)
Status = RtlCreateUserThread(NtCurrentProcess(),
NULL, FALSE, 0, 0, 0, Buffer, NULL, &ThreadHandle, NULL);
if (NT_SUCCESS(Status))
{
// 等待线程结束
Timeout.QuadPart = -10LL * 1000 * 1000 * 3; // 3秒
ZwWaitForSingleObject(ThreadHandle, FALSE, &Timeout);
ZwClose(ThreadHandle);
}
} while (0);
// 释放申请的空间
if (Buffer)
{
ZwFreeVirtualMemory(
NtCurrentProcess(), &Buffer, &BufferSize, MEM_RELEASE);
}
// 分离目标进程
if (Process != CurrProcess)
KeUnstackDetachProcess(&ApcState);
return Status;
}