内核线程注入(x64)

阅读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

你可能感兴趣的:(注入系列,Ring0,x64,ShellCode)