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

Wow64(32位程序)注入DLL到64位程序

http://blog.poxiao.me/p/wow64-process-inject-dll-into-x64-process/#Wow64環境下32位程序注入64位程序

DLL注入

向其他程序注入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 反彙編的結果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;
}

這段程式碼在建立遠端執行緒成功即認為注入成功,為了更加準確的判斷是否注入成功可以在注入的機器碼增加額外的程式碼來判斷是否注入成功。