1. 程式人生 > >Wow64(32位進程)註入DLL到64位進程

Wow64(32位進程)註入DLL到64位進程

cast UNC null 數通 pan thread 簡單的 ear wow64

轉載自: https://blog.poxiao.me/p/wow64-process-inject-dll-into-x64-process/

向其他進程註入DLL通常的做法是通過調用CreateRemoteThread這個API在目標進程內創建一個遠程線程,用這個線程來調用LoadLibraryALoadLibraryW(下文統稱LoadLibrary)以實現讓目標進程加載指定的DLL文件。使用CreateRemoteThread創建一個遠程線程需要傳入一個線程過程函數的地址,並且這個函數地址是需要在目標進程中有效的。由於LoadLibrary是kernel32.dll的導出函數,所以對於運行在同一個系統上的同為32位的進程或同為64位的進程可以假定彼此進程內的LoadLibrary函數的地址是相同的。並且CreateRemoteThread的線程過程函數和LoadLibrary的參數個數相同,且參數都是指針,因此通常都是直接將LoadLibrary作為CreateRemoteThread的過程函數。然後使用VirtualAllocEx

在目標進程中分配內存,使用WriteProcessMemory往這塊內存中寫入DLL文件路徑,將這塊內存的地址作為線程過程函數(LoadLibrary)的參數。

在64位的Windows操作系統上32位進程中的LoadLibrary函數地址與64位進程的函數地址不同,因此如果想對64位進程註入DLL,簡單的做法就是使用64位進程來執行註入工作。但是如果能讓32位進程註入DLL到64位進程顯然更好。

在一番Google之後找到了這篇文章。這篇文章的作者研究出來一種在Wow64進程中執行x64代碼的方法,並且將其封裝成了這個庫。
本文就是介紹如何使用這個庫實現Wow64環境下32位進程向64位進程註入DLL。

Wow64環境下32位進程註入64位進程

32位進程難以註入DLL進64位進程是由於兩個進程內LoadLibrary的地址不同,32位進程無法知道64位進程的LoadLibrary函數地址。使用wow64ext這個庫在Wow64環境下可以讓32位進程獲取到64位的ntdll.dll的導出函數(得到的地址與64進程的地址是一樣的)。

本文使用ntdll中的這3個未文檔的函數來註入DLL。

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
NTSTATUS
NTAPI
RtlCreateUserThread(
_In_ HANDLE processHandle,
_In_ SECURITY_DESCRIPTOR* securityDescriptor,
_In_ BOOLEAN createSuspended,
_In_ ULONG stackZeroBits,
_Inout_opt_ size_t* stackReserved,
_Inout_opt_ size_t* stackCommit,
_In_ const void* startAddress,
_In_ void* startParameter,
_Inout_ HANDLE* threadHandle,
_Inout_opt_ CLIENT_ID* clientID
);

NTSTATUS
NTAPI
LdrLoadDll(
_In_opt_ PWSTR SearchPath,
_In_opt_ PULONG LoadFlags,
_In_ PUNICODE_STRING Name,
_Out_opt_ PVOID *BaseAddress
);

VOID
NTAPI
RtlExitUserThread(
_In_ NTSTATUS Status
);

使用RtlCreateUserThread創建遠程線程,在遠程線程中調用LdrLoadDll加載要註入的DLL文件,最後在遠程線程中調用RtlExitUserThread退出線程。

為了在遠程線程中調用兩個函數(LdrLoadDll、RtlExitUserThread),需要將要執行的x64代碼寫入目標進程,然後讓遠程線程執行這段代碼,在這之前需要了解一些預備知識。可以看MSDN中的這篇文章。通過這個篇文章我們知道了。

  • 在調用約定上Windows在x64進行了統一,也就是說不管你有沒有顯式指定調用約定,指定了何種調用約定,最終編譯後都使用__fastcall這一種調用約定。
  • 在參數傳遞上對於Integer類型(含指針)前4個參數通過RCXRDXR8R9寄存器傳遞,其他參數通過棧傳遞。

LdrLoadDll有4個參數都是指針,RtlExitUserThread只有1個參數是Integer類型。為這兩個函數傳遞參數只通過寄存器就足夠了。
當然我們不需要自己去寫匯編代碼再將匯編代碼轉成機器碼,首先先寫下面這樣一段代碼。

1
2
3
4
5
6
7
8
9
10
typedef unsigned long long DWORD64;

typedef DWORD64 (*Func4_Type)(DWORD64, DWORD64, DWORD64, DWORD64);
typedef DWORD64 (*Func1_Type)(DWORD64);

void ThreadProc(void*)
{
((Func4_Type)(0x1234567890123456))(0x1111111111111111, 0x2222222222222222, 0x3333333333333333, 0x4444444444444444);
((Func1_Type)(0x6543210987654321))(0x5555555555555555);
}

然後使用VC編譯器將其編譯成x64的代碼,再反匯編它。

技術分享圖片VS2013 Debug 反匯編的結果

跟據內存地址,容易得到下面的機器碼與匯編代碼的對應關系。

1
0x48 0x89 0x4c 0x24 0x08                           mov       qword ptr [rsp+8],rcx
0x57                                               push      rdi
0x48 0x83 0xec 0x20                                sub       rsp,20h
0x48 0x8b 0xfc                                     mov       rdi,rsp
0xb9 0x08 0x00 0x00 0x00                           mov       ecx,8
0xb8 0xcc 0xcc 0xcc 0xcc                           mov       eac,0CCCCCCCCh
0xf3 0xab                                          rep stos  dword ptr [rdi]
0x48 0x8b 0x4c 0x24 0x30                           mov       rcx,qword ptr [__formal]
0x49 0xb9 0x44 0x44 0x44 0x44 0x44 0x44 0x44 0x44  mov       r9,4444444444444444h
0x49 0xb8 0x33 0x33 0x33 0x33 0x33 0x33 0x33 0x33  mov       r8,3333333333333333h
0x48 0xba 0x22 0x22 0x22 0x22 0x22 0x22 0x22 0x22  mov       rdx,2222222222222222h
0x48 0xb9 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11  mov       rcx,1111111111111111h
0x48 0xb8 0x56 0x34 0x12 0x90 0x78 0x56 0x34 0x12  mov       rax,1234567890123456h
0xff 0xd0                                          call      rax
0x48 0xb9 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55  mov       rcx,5555555555555555h
0x48 0xb8 0x21 0x43 0x65 0x87 0x09 0x21 0x43 0x65  mov       rax,6543210987654321h
0xff 0xd0                                          call      rax

只要在運行的時候根據獲取到的函數地址和參數地址替換對應機器碼然後將機器碼寫入目標進程,創建線程執行這段代碼就能夠實現Wow64進程註入DLL到64位進程了。
完整的實現代碼如下(VS2012編譯通過,Windows 8 x64測試註入成功)。

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
#include <memory>
#include <string>
#include <Windows.h>

#include "wow64ext.h"

enum class InjectResult {
OK,
Error_OpenProcess,
Error_VirtualAllocEx,
Error_GetProcAddress,
Error_WriteProcessMemory,
Error_CreateRemoteThread
};

template<typename Res, typename Deleter>
class ScopeResource {
Res res;
Deleter deleter;
ScopeResource(const ScopeResource&) {}
public:
Res get() const {
return this->res;
}
ScopeResource(Res res, Deleter deleter) : res(res), deleter(deleter) {}
~ScopeResource() {
this->deleter(this->res);
}
};

InjectResult Wow64InjectWin64(DWORD dwProcessId, const std::wstring& filename)
{
DWORD dwDesiredAccess = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ;
auto closeProcessHandle = [](HANDLE hProcess) {
if(hProcess != NULL) CloseHandle(hProcess);
};
ScopeResource<HANDLE, decltype(closeProcessHandle)> targetProcessHandle(OpenProcess(dwDesiredAccess, FALSE, dwProcessId), closeProcessHandle);
if(targetProcessHandle.get() == NULL) {
return InjectResult::Error_OpenProcess;
}
unsigned char injectCode[] = {
0x48, 0x89, 0x4c, 0x24, 0x08, // mov qword ptr [rsp+8],rcx
0x57, // push rdi
0x48, 0x83, 0xec, 0x20, // sub rsp,20h
0x48, 0x8b, 0xfc, // mov rdi,rsp
0xb9, 0x08, 0x00, 0x00, 0x00, // mov ecx,8
0xb8, 0xcc, 0xcc, 0xcc, 0xcc, // mov eac,0CCCCCCCCh
0xf3, 0xab, // rep stos dword ptr [rdi]
0x48, 0x8b, 0x4c, 0x24, 0x30, // mov rcx,qword ptr [__formal]
0x49, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r9,0
0x49, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r8,0
0x48, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rdx,0
0x48, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rcx,0
0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax,0
0xff, 0xd0, // call rax
0x48, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rcx,0
0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax,0
0xff, 0xd0 // call rax
};

size_t parametersMemSize = sizeof(DWORD64) + sizeof(_UNICODE_STRING_T<DWORD64>) + (filename.size() + 1) * sizeof(wchar_t);
auto freeInjectCodeMem = [&targetProcessHandle, &injectCode](DWORD64 address) {
if(address != 0) VirtualFreeEx64(targetProcessHandle.get(), address, sizeof(injectCode), MEM_COMMIT | MEM_RESERVE);
};
ScopeResource<DWORD64, decltype(freeInjectCodeMem)> injectCodeMem(VirtualAllocEx64(targetProcessHandle.get(), NULL, sizeof(injectCode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE), freeInjectCodeMem);
auto freeParametersMem = [&targetProcessHandle, parametersMemSize](DWORD64 address) {
if(address != 0) VirtualFreeEx64(targetProcessHandle.get(), address, parametersMemSize, MEM_COMMIT | MEM_RESERVE);
};
ScopeResource<DWORD64, decltype(freeParametersMem)> parametersMem(VirtualAllocEx64(targetProcessHandle.get(), NULL, parametersMemSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE), freeParametersMem);
if (injectCodeMem.get() == 0 || parametersMem.get() == 0) {
return InjectResult::Error_VirtualAllocEx;
}
DWORD64 ntdll64 = GetModuleHandle64(L"ntdll.dll");
DWORD64 ntdll_LdrLoadDll = GetProcAddress64(ntdll64, "LdrLoadDll");
DWORD64 ntdll_RtlExitUserThread = GetProcAddress64(ntdll64, "RtlExitUserThread");
DWORD64 ntdll_RtlCreateUserThread = GetProcAddress64(ntdll64, "RtlCreateUserThread");
if(ntdll_LdrLoadDll == 0 || ntdll_RtlExitUserThread == 0 || ntdll_RtlCreateUserThread == 0) {
return InjectResult::Error_GetProcAddress;
}
std::unique_ptr<unsigned char[]> parameters(new unsigned char[parametersMemSize]);
std::memset(parameters.get(), 0, parametersMemSize);
_UNICODE_STRING_T<DWORD64>* upath = reinterpret_cast<_UNICODE_STRING_T<DWORD64>*>(parameters.get() + sizeof(DWORD64));
upath->Length = filename.size() * sizeof(wchar_t);
upath->MaximumLength = (filename.size() + 1) * sizeof(wchar_t);
wchar_t* path = reinterpret_cast<wchar_t*>(parameters.get() + sizeof(DWORD64) + sizeof(_UNICODE_STRING_T<DWORD64>));
std::copy(filename.begin(), filename.end(), path);
upath->Buffer = parametersMem.get() + sizeof(DWORD64) + sizeof(_UNICODE_STRING_T<DWORD64>);

union {
DWORD64 from;
unsigned char to[8];
} cvt;

// r9
cvt.from = parametersMem.get();
std::memcpy(injectCode + 32, cvt.to, sizeof(cvt.to));

// r8
cvt.from = parametersMem.get() + sizeof(DWORD64);
std::memcpy(injectCode + 42, cvt.to, sizeof(cvt.to));

// rax = LdrLoadDll
cvt.from = ntdll_LdrLoadDll;
std::memcpy(injectCode + 72, cvt.to, sizeof(cvt.to));

// rax = RtlExitUserThread
cvt.from = ntdll_RtlExitUserThread;
std::memcpy(injectCode + 94, cvt.to, sizeof(cvt.to));

if(FALSE == WriteProcessMemory64(targetProcessHandle.get(), injectCodeMem.get(), injectCode, sizeof(injectCode), NULL)
|| FALSE == WriteProcessMemory64(targetProcessHandle.get(), parametersMem.get(), parameters.get(), parametersMemSize, NULL)) {
return InjectResult::Error_WriteProcessMemory;
}

DWORD64 hRemoteThread = 0;
struct {
DWORD64 UniqueProcess;
DWORD64 UniqueThread;
} client_id;

X64Call(ntdll_RtlCreateUserThread, 10,
(DWORD64)targetProcessHandle.get(), // ProcessHandle
(DWORD64)NULL, // SecurityDescriptor
(DWORD64)FALSE, // CreateSuspended
(DWORD64)0, // StackZeroBits
(DWORD64)NULL, // StackReserved
(DWORD64)NULL, // StackCommit
injectCodeMem.get(), // StartAddress
(DWORD64)NULL, // StartParameter
(DWORD64)&hRemoteThread, // ThreadHandle
(DWORD64)&client_id); // ClientID
if(hRemoteThread != 0) {
CloseHandle((HANDLE)hRemoteThread);
return InjectResult::OK;
}
return InjectResult::Error_CreateRemoteThread;
}

這段代碼在創建遠程線程成功即認為註入成功,為了更加準確的判斷是否註入成功可以在註入的機器碼增加額外的代碼來判斷是否註入成功。

Wow64(32位進程)註入DLL到64位進程