阅读BlackBone源码从里面扣出来的关于内核线程注入方法的使用。
经过自己修改后做成的Demo,功能主要通过Ring0层驱动Attach到目标进程(目标进程可以是32位进程也可以是64位进程,使用不同的ShellCode进行注入操作),然后调用NtCreateThreadEx来执行ShellCode,ShellCode做了一个注入Dll的简单行为(加载Dll使用的是Ntdll模块下的LdrLoadDll函数)。
关键函数如下:
//切换到目标进程创建内核线程进行注入
NTSTATUS AttachAndInjectProcess(IN HANDLE ProcessID)
{
PEPROCESS EProcess = NULL;
KAPC_STATE ApcState;
NTSTATUS Status = STATUS_SUCCESS;
if (ProcessID == NULL)
{
Status = STATUS_UNSUCCESSFUL;
return Status;
}
//获取EProcess
Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
if (Status != STATUS_SUCCESS)
{
DbgPrint(("PsLookupProcessByProcessId函数失败\n"));
return Status;
}
//判断目标进程x86 or x64
BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;
//KeStackAttachProcess例程 将当前线程连接到目标进程的地址空间。
KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);
__try
{
PVOID NtdllAddress = NULL;
PVOID LdrLoadDll = NULL;
UNICODE_STRING NtdllUnicodeString = { 0 };
UNICODE_STRING DllFullPath = { 0 };
//获取ntdll模块基地址
RtlInitUnicodeString(&NtdllUnicodeString, L"Ntdll.dll");
NtdllAddress = GetUserModule(EProcess, &NtdllUnicodeString,IsWow64);
if (!NtdllAddress)
{
DbgPrint("%s: Failed to get Ntdll base\n", __FUNCTION__);
Status = STATUS_NOT_FOUND;
}
//获取LdrLoadDll
if (NT_SUCCESS(Status))
{
LdrLoadDll = GetModuleExport(NtdllAddress, "LdrLoadDll", EProcess, NULL);
/*
64位LdrLoadDll
kd> u 0x00000000`77c77ac0
00000000`77c77ac0 48895c2410 mov qword ptr [rsp+10h],rbx
00000000`77c77ac5 48896c2418 mov qword ptr [rsp+18h],rbp
00000000`77c77aca 56 push rsi
00000000`77c77acb 57 push rdi
00000000`77c77acc 4154 push r12
00000000`77c77ace 4883ec50 sub rsp,50h
00000000`77c77ad2 f605b72e110009 test byte ptr [00000000`77d8a990],9
00000000`77c77ad9 498bf1 mov rsi,r9
*/
if (!LdrLoadDll)
{
DbgPrint("%s: Failed to get LdrLoadDll address\n", __FUNCTION__);
Status = STATUS_NOT_FOUND;
}
}
PINJECT_BUFFER InjectBuffer = NULL;
if (IsWow64)
{
RtlInitUnicodeString(&DllFullPath, L"C:\\Windows\\Dllx86.dll");
InjectBuffer = GetWow64Code(LdrLoadDll, &DllFullPath);
}
else
{
RtlInitUnicodeString(&DllFullPath, L"C:\\Windows\\Dllx64.dll");
InjectBuffer = GetNativeCode(LdrLoadDll, &DllFullPath);
}
/*
kd> u 0x00000000`002b0000
00000000`002b0000 4883ec28 sub rsp,28h
00000000`002b0004 4831c9 xor rcx,rcx
00000000`002b0007 4831d2 xor rdx,rdx
00000000`002b000a 49b800022b0000000000 mov r8,2B0200h
00000000`002b0014 49b9e0052b0000000000 mov r9,2B05E0h
00000000`002b001e 48b8c07ac77700000000 mov rax,77C77AC0h
00000000`002b0028 ffd0 call rax
00000000`002b002a 48bae8052b0000000000 mov rdx,2B05E8h
*/
ExecuteInNewThread(InjectBuffer, NULL, THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, TRUE, &Status);
if (!NT_SUCCESS(Status))
{
DbgPrint(("ExecuteInNewThread函数失败\n"));
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
Status = STATUS_UNSUCCESSFUL;
}
//将当前线程返回原进程的地址空间。
KeUnstackDetachProcess(&ApcState);
//释放EProcess
ObDereferenceObject(EProcess);
return Status;
}
这个代码与我之前所写的内核线程注入x86的代码相差不大,附加的主要是对目标进程作了判断是否位32位进程,然后根据是32位还是64位选择构建不同的ShellCode。
构建ShellCode的代码如下:
//创建注入代码 64位目标进程
PINJECT_BUFFER GetNativeCode(IN PVOID LdrLoadDll, IN PUNICODE_STRING DllFullPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PINJECT_BUFFER InjectBuffer = NULL;
SIZE_T Size = PAGE_SIZE;
UCHAR Code[] =
{
0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28
0x48, 0x31, 0xC9, // xor rcx, rcx
0x48, 0x31, 0xD2, // xor rdx, rdx
0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov r8, ModuleFileName offset +12
0x49, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0, // mov r9, ModuleHandle offset +28
0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov rax, LdrLoadDll offset +32
0xFF, 0xD0, // call rax
0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdx, COMPLETE_OFFSET offset +44
0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [rdx], CALL_COMPLETE
0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdx, STATUS_OFFSET offset +60
0x89, 0x02, // mov [rdx], eax
0x48, 0x83, 0xC4, 0x28, // add rsp, 0x28
0xC3 // ret
};
Status = ZwAllocateVirtualMemory(ZwCurrentProcess(), &InjectBuffer, 0, &Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (NT_SUCCESS(Status))
{
PUNICODE_STRING UserPath = &InjectBuffer->Path;
UserPath->Length = 0;
UserPath->MaximumLength = sizeof(InjectBuffer->Buffer);
UserPath->Buffer = InjectBuffer->Buffer;
RtlUnicodeStringCopy(UserPath, DllFullPath);
// Copy code
memcpy(InjectBuffer, Code, sizeof(Code));
// Fill stubs
*(ULONGLONG*)((PUCHAR)InjectBuffer + 12) = (ULONGLONG)UserPath;
*(ULONGLONG*)((PUCHAR)InjectBuffer + 22) = (ULONGLONG)&InjectBuffer->ModuleHandle;
*(ULONGLONG*)((PUCHAR)InjectBuffer + 32) = (ULONGLONG)LdrLoadDll;
*(ULONGLONG*)((PUCHAR)InjectBuffer + 44) = (ULONGLONG)&InjectBuffer->Complete;
*(ULONGLONG*)((PUCHAR)InjectBuffer + 60) = (ULONGLONG)&InjectBuffer->Status;
return InjectBuffer;
}
UNREFERENCED_PARAMETER(DllFullPath);
return NULL;
}
//创建注入代码 32位目标进程
PINJECT_BUFFER GetWow64Code(IN PVOID LdrLoadDll, IN PUNICODE_STRING DllFullPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PINJECT_BUFFER InjectBuffer = NULL;
SIZE_T Size = PAGE_SIZE;
// Code
UCHAR Code[] =
{
0x68, 0, 0, 0, 0, // push ModuleHandle offset +1
0x68, 0, 0, 0, 0, // push ModuleFileName offset +6
0x6A, 0, // push Flags
0x6A, 0, // push PathToFile
0xE8, 0, 0, 0, 0, // call LdrLoadDll offset +15
0xBA, 0, 0, 0, 0, // mov edx, COMPLETE_OFFSET offset +20
0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [edx], CALL_COMPLETE
0xBA, 0, 0, 0, 0, // mov edx, STATUS_OFFSET offset +31
0x89, 0x02, // mov [edx], eax
0xC2, 0x04, 0x00 // ret 4
};
Status = ZwAllocateVirtualMemory(ZwCurrentProcess(), &InjectBuffer, 0, &Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (NT_SUCCESS(Status))
{
// Copy path
PUNICODE_STRING32 pUserPath = &InjectBuffer->Path32;
pUserPath->Length = DllFullPath->Length;
pUserPath->MaximumLength = DllFullPath->MaximumLength;
pUserPath->Buffer = (ULONG)(ULONG_PTR)InjectBuffer->Buffer;
// Copy path
memcpy((PVOID)pUserPath->Buffer, DllFullPath->Buffer, DllFullPath->Length);
// Copy code
memcpy(InjectBuffer, Code, sizeof(Code));
// Fill stubs
*(ULONG*)((PUCHAR)InjectBuffer + 1) = (ULONG)(ULONG_PTR)&InjectBuffer->ModuleHandle;
*(ULONG*)((PUCHAR)InjectBuffer + 6) = (ULONG)(ULONG_PTR)pUserPath;
*(ULONG*)((PUCHAR)InjectBuffer + 15) = (ULONG)((ULONG_PTR)LdrLoadDll - ((ULONG_PTR)InjectBuffer + 15) - 5 + 1);
*(ULONG*)((PUCHAR)InjectBuffer + 20) = (ULONG)(ULONG_PTR)&InjectBuffer->Complete;
*(ULONG*)((PUCHAR)InjectBuffer + 31) = (ULONG)(ULONG_PTR)&InjectBuffer->Status;
return InjectBuffer;
}
UNREFERENCED_PARAMETER(DllFullPath);
return NULL;
}
构建完shellcode之后,我们便从SSDT中获取NtCreateThreadEx函数来创建新的线程执行ShellCode。
NTSTATUS ExecuteInNewThread(
IN PVOID BaseAddress,
IN PVOID Parameter,
IN ULONG Flags,
IN BOOLEAN Wait,
OUT PNTSTATUS ExitStatus
)
{
HANDLE ThreadHandle = NULL;
OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
//指定一个对象句柄的属性 句柄只能在内核模式访问。
InitializeObjectAttributes(&ObjectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
//创建线程
NTSTATUS Status = SeCreateThreadEx(
&ThreadHandle, THREAD_QUERY_LIMITED_INFORMATION, &ObjectAttributes,
ZwCurrentProcess(), BaseAddress, Parameter, Flags,
0, 0x1000, 0x100000, NULL
);
// 等待线程完成
if (NT_SUCCESS(Status) && Wait != FALSE)
{
//60s
LARGE_INTEGER Timeout = { 0 };
Timeout.QuadPart = -(60ll * 10 * 1000 * 1000);
Status = ZwWaitForSingleObject(ThreadHandle, TRUE, &Timeout);
if (NT_SUCCESS(Status))
{
//查询线程退出码
THREAD_BASIC_INFORMATION ThreadBasicInfo = { 0 };
ULONG ReturnLength = 0;
Status = ZwQueryInformationThread(ThreadHandle, ThreadBasicInformation, &ThreadBasicInfo, sizeof(ThreadBasicInfo), &ReturnLength);
if (NT_SUCCESS(Status) && ExitStatus)
{
*ExitStatus = ThreadBasicInfo.ExitStatus;
}
else if (!NT_SUCCESS(Status))
{
DbgPrint("%s: ZwQueryInformationThread failed with status 0x%X\n", __FUNCTION__, Status);
}
}
else
DbgPrint("%s: ZwWaitForSingleObject failed with status 0x%X\n", __FUNCTION__, Status);
}
else
{
DbgPrint("%s: ZwCreateThreadEx failed with status 0x%X\n", __FUNCTION__, Status);
}
if (ThreadHandle)
{
ZwClose(ThreadHandle);
}
return Status;
}
NTSTATUS
NTAPI
SeCreateThreadEx(
OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle, //目标进程句柄
IN PVOID StartAddress, //线程起始地址
IN PVOID Parameter, //线程参数
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
IN PNT_PROC_THREAD_ATTRIBUTE_LIST AttributeList
)
{
NTSTATUS Status = STATUS_SUCCESS;
//从SSDT中获取NtCreateThreadEx函数地址
//Windows7 64位 NtCreateThdIndex = 0x0A5
/*Windows7 32位 NtCreateThdIndex = 0x058
0:001> u ntdll!NtCreateThreadEx
ntdll!ZwCreateThreadEx:
77a35768 b858000000 mov eax,58h
77a3576d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
77a35772 ff12 call dword ptr [edx]
77a35774 c22c00 ret 2Ch
77a35777 90 nop
*/
LPFN_NTCREATETHREADEX NtCreateThreadEx = (LPFN_NTCREATETHREADEX)(ULONG_PTR)GetSSDTEntry(0xA5);
/*
kd> u 0xfffff800`041c730c l 20
nt!NtCreateThreadEx:
fffff800`041c730c fff3 push rbx
fffff800`041c730e 56 push rsi
fffff800`041c730f 57 push rdi
fffff800`041c7310 4154 push r12
fffff800`041c7312 4155 push r13
fffff800`041c7314 4156 push r14
fffff800`041c7316 4157 push r15
fffff800`041c7318 4881ec10070000 sub rsp,710h
fffff800`041c731f 488b05bab6e8ff mov rax,qword ptr [nt!_security_cookie (fffff800`040529e0)]
fffff800`041c7326 4833c4 xor rax,rsp
……
fffff800`041c735d e82e2ad1ff call nt!memset (fffff800`03ed9d90)
……
fffff800`041c737a e8112ad1ff call nt!memset (fffff800`03ed9d90)
……
fffff800`041c738e 0f859a1b0600 jne nt! ?? ::NNGAKEGL::`string'+0x3c040 (fffff800`04228f2e)
fffff800`041c739d 3898f6010000 cmp byte ptr [rax+1F6h],bl
*/
if (NtCreateThreadEx)
{
//如果之前的模式是用户模式,地址传递到ZwCreateThreadEx必须在用户模式空间
//切换到内核模式允许使用内核模式地址
//Windows7 PrevMode = 0x1F6
PUCHAR pPrevMode = (PUCHAR)PsGetCurrentThread() + 0x1F6;
//64位 pPrevMode = 01
UCHAR prevMode = *pPrevMode;
*pPrevMode = KernelMode;//内核模式
//创建线程
Status = NtCreateThreadEx(
ThreadHandle, DesiredAccess, ObjectAttributes,
ProcessHandle, StartAddress, Parameter,
Flags, StackZeroBits, SizeOfStackCommit,
SizeOfStackReserve, AttributeList
);
//恢复之前的线程模式
*pPrevMode = prevMode;
}
else
Status = STATUS_NOT_FOUND;
return Status;
}
获取NtCreateThreadEx函数需要知道它在SSDT中的索引号,并且获取SSDT基地址,64位下SSDT并没有导出,需要从Kernel模块中手动获取,代码在此就不列出,可以从下面分享的源码中查看。
源码下载地址:https://download.csdn.net/download/qq_37957965/10800753