32位程序注入64位DLL到64位进程

向其它进程注入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位进程注入64位DLL到64位进程显然更好。

在一番Google之后找到了这篇文章

这篇文章的作者研究出来一种在Wow64进程中运行x64代码的方法,而且将其封装成了这个库
本文就是介绍怎样使用这个库实现Wow64环境下32位进程向64位进程注入DLL。

Wow64环境下32位程序注入64位DLL到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 反汇编的结果

跟据内存地址。easy得到以下的机器码与汇编代码的相应关系。

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;
}

这段代码在创建远程线程成功即觉得注入成功,为了更加准确的推断是否注入成功能够在注入的机器码添加额外的代码来推断是否注入成功。

转载于:https://www.cnblogs.com/jzdwajue/p/7029145.html

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
今天更新一下, 解决之前贴子中提到的一些问题: 1. 封装了几个自定义的函数, 例如 move_to_root, array_get_length, array_move_to_index, 这样可以少调用一些 X64Call; 2. 简单实现了对于类似 [0].A.B[0].C 的路径的解析取值. 接下来说一下遇到的问题和一些体验: 1. 我构造的测试数据大小是大约是 96MB, 在我的机器上可以正常解析, 再大一些(例如 128MB)会崩溃, 崩溃位于 ParsedJson.allocateCapacity , 琢磨了下没琢磨明白 (温馨提示: 真要是这种大小级别了还是建议各位用 SAX 方式); 2. 除了上面这点, 还有个已知的比较隐蔽 BUG, 貌似是 print_ 这个函数的锅: 静态编译之后, 在 demo 中如果 print_ 递归打印了一个 Object 例如 [0], 再点击解析就会在 iterator_free 崩溃. 如果只是取值就不崩溃. 3. 这个库会拷贝数据, 在针对过长的数据的时候这不是好做法, 感觉这个库更像是科研性质, 和那些千锤百炼的老牌库相比, 目前可能只有速度占优势了; 4. 机器或者其它方面的限制, 我用易语言跑不出宣传文章中的千兆字节每秒, 不过几百 MB/s 还是有的; 5. 由于解析的时候它会拷贝数据, 我不清楚有没有可能会产生 64-bit 的内存地址, 暂时就是指针到文本当 32-bit 用, 但心里很没底, 希望 eWOW64Ext 作者有空可以帮忙看一下... @shier2817  谢谢! 6. 库用的是 10.0.17134.0 版本的 SDK /MT 编译的, 但已经无法支持 WindowXP, 低版本的 SDK 编译不过去, 对这些指令不熟悉所以没有去探究原因(也许就是不支持, 详情请翻阅 MSDN); 7. 关于编译模式: 用 MinSizeRel 生成的话, 会导致 double 取值异常, 具体原因未深究, 所以默认使用了 Release . 我将会在附件中附上三种编译模式生成的文件供各位研究: RelWithDebInfo, MinSizeRel, Release; 8. 我对于 WOW64Ext 方面的知识不了解, 所以无法保证代码的稳定性, 抛砖引玉, 所以如果你希望封装完整的模块和工具, 可以进群与我交流.
注入DLL通常需要在目标进程中创建一个远程线程,然后在远程线程中调用LoadLibrary函数加载DLL,最后调用DLL中的导出函数。以下是一个简单的C++注入DLL的示例代码: ``` #include <Windows.h> #include <TlHelp32.h> #include <iostream> int main() { // 获取目标进程的句柄 DWORD processId = 1234; // 目标进程ID HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); if (processHandle == NULL) { std::cout << "Failed to open process" << std::endl; return 1; } // 获取LoadLibrary函数的地址 LPVOID loadLibraryAddress = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); if (loadLibraryAddress == NULL) { std::cout << "Failed to get LoadLibrary address" << std::endl; return 1; } // 分配内存 LPVOID remoteMemory = VirtualAllocEx(processHandle, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE); if (remoteMemory == NULL) { std::cout << "Failed to allocate remote memory" << std::endl; return 1; } // 写入DLL路径到目标进程中 const char* dllPath = "C:\\path\\to\\your\\dll.dll"; if (!WriteProcessMemory(processHandle, remoteMemory, dllPath, strlen(dllPath) + 1, NULL)) { std::cout << "Failed to write DLL path to remote memory" << std::endl; return 1; } // 创建远程线程并调用LoadLibrary函数 HANDLE remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryAddress, remoteMemory, 0, NULL); if (remoteThread == NULL) { std::cout << "Failed to create remote thread" << std::endl; return 1; } // 等待远程线程结束 WaitForSingleObject(remoteThread, INFINITE); // 获取导出函数地址并调用 const char* functionName = "YourExportFunction"; FARPROC functionAddress = GetProcAddress(GetModuleHandle("yourdll.dll"), functionName); if (functionAddress == NULL) { std::cout << "Failed to get function address" << std::endl; return 1; } // 调用导出函数 typedef int (*YourExportFunction)(); YourExportFunction yourFunction = (YourExportFunction)functionAddress; int result = yourFunction(); std::cout << "Result: " << result << std::endl; // 关闭句柄 CloseHandle(remoteThread); VirtualFreeEx(processHandle, remoteMemory, 0, MEM_RELEASE); CloseHandle(processHandle); return 0; } ``` 在上面的示例中,我们首先使用OpenProcess函数打开目标进程句柄,然后使用GetProcAddress函数获取LoadLibrary函数的地址。接下来,我们使用VirtualAllocEx函数在目标进程中分配内存,并使用WriteProcessMemory函数将DLL路径写入目标进程中。然后,我们使用CreateRemoteThread函数创建远程线程,并在远程线程中调用LoadLibrary函数来加载DLL。最后,我们使用GetProcAddress函数获取导出函数地址,并调用导出函数。 需要注意的是,在实际的注入过程中,你还需要处理一些异常情况,例如目标进程拥有较高的权限,或者DLL文件不存在等等。此外,在使用CreateRemoteThread函数创建远程线程时,你还需要指定一个适当的起始地址,并在远程线程中调用ExitThread函数结束远程线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值