just_inject 第一天: CreateRemoteThreadInject

代码总览

(just_inject.cpp)

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#pragma once

#ifndef _JUST_INJECT_H_
#define _JUST_INJECT_H_

#include <Windows.h>
#include <wchar.h>

BOOL SetPrivilege(HANDLE, LPCWSTR, BOOL);

BOOL SetDebugPrivilege();

BOOL RemoteThreadInject(DWORD, DWORD);

BOOL RemoteThreadInject(DWORD, LPCWSTR);

BOOL SetDebugPrivilege()
{
HANDLE hToken;

OpenProcessToken(
GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken
);
return SetPrivilege(hToken, SE_DEBUG_NAME, TRUE);
}

BOOL SetPrivilege(HANDLE hToken, LPCWSTR lpszPrivilege, BOOL bEnablePrivilege)
{
LUID luid;
TOKEN_PRIVILEGES tp;

if (!LookupPrivilegeValueW(NULL, lpszPrivilege, &luid))
{
wprintf_s(L"LookupPrivilegeValueW failed %u\n", GetLastError());
return FALSE;
}

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;

if (bEnablePrivilege)
{
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
}
else
{
tp.Privileges[0].Attributes = 0;
}

if (!AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(tp),
NULL,
NULL))
{
wprintf_s(L"AdjustTokenPrivileges failed %u\n", GetLastError());
return FALSE;
}

return TRUE;
}

BOOL RemoteThreadInject(DWORD dwTargetProcessId, DWORD dwFuncAddr)
{
SetDebugPrivilege();

HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetProcessId);

if (hTargetProcess == NULL || hTargetProcess == INVALID_HANDLE_VALUE)
{
wprintf_s(L"OpenProcess failed.\n");
return FALSE;
}

DWORD dwThreadId = 0;

HANDLE hRemoteThread = CreateRemoteThread(hTargetProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)dwFuncAddr,
NULL,
NULL,
&dwThreadId);

if (hRemoteThread == NULL || hRemoteThread == INVALID_HANDLE_VALUE)
{
wprintf_s(L"CreateRemoteThread failed.\n");
CloseHandle(hTargetProcess);
return FALSE;
}

WaitForSingleObject(hRemoteThread, INFINITE);
CloseHandle(hRemoteThread);
hRemoteThread = NULL;
return TRUE;
}

BOOL RemoteThreadInject(DWORD dwTargetProcessId, LPCWSTR lpszDllPath)
{
SetDebugPrivilege();

HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetProcessId);

if (hTargetProcess == NULL)
{
wprintf_s(L"OpenProcess failed.\n");
return FALSE;
}

ULONG32 ulDllPathLen = wcsnlen_s(lpszDllPath, MAX_PATH);
LPVOID lpvVaDllPath = VirtualAllocEx(hTargetProcess, NULL, ulDllPathLen * sizeof(WCHAR) + 16, MEM_COMMIT, PAGE_READWRITE);

if (lpvVaDllPath == NULL)
{
wprintf_s(L"VirtualAllocEx failed.\n");
CloseHandle(hTargetProcess);
return FALSE;
}

if (!WriteProcessMemory(hTargetProcess, lpvVaDllPath, (LPCVOID)lpszDllPath, ulDllPathLen * sizeof(WCHAR) + 16, NULL))
{
wprintf_s(L"WriteProcessMemory failed.\n");
VirtualFreeEx(hTargetProcess, lpvVaDllPath, ulDllPathLen * sizeof(WCHAR) + 16, MEM_RELEASE);
CloseHandle(hTargetProcess);
return FALSE;
}

LPTHREAD_START_ROUTINE lptsrLoadLibraryW = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleW(L"kernel32"), "LoadLibraryW");
DWORD dwThreadId = 0;
HANDLE hThread = CreateRemoteThread(hTargetProcess, NULL, 0, lptsrLoadLibraryW, lpvVaDllPath, 0, &dwThreadId);
if (hThread == NULL)
{
wprintf_s(L"CreateRemoteThread failed.\n");
VirtualFreeEx(hTargetProcess, lpvVaDllPath, ulDllPathLen * sizeof(WCHAR) + 16, MEM_RELEASE);
CloseHandle(hTargetProcess);
return FALSE;
}

wprintf_s(L"CreateRemoteThread 0x%x.\n", dwThreadId);

WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hTargetProcess, lpvVaDllPath, ulDllPathLen * sizeof(WCHAR) + 16, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hTargetProcess);
return TRUE;
}

#endif

代码解释

前言

众所周知在现代操作系统(Windows、Linux、MAC)中,每个进程是相对独立的,一个进程的资源分配和运行直接由操作系统处理。操作系统将内存空间分为用户空间与内核空间,其中,操作系统及驱动程序运行在内核空间,拥有较高权限,甚至对硬件都有极大影响,用户空间则用来给普通进程使用,拥有较低的权限,只能使用一些操作系统、驱动开放的接口来实现功能(一般EXE、DLL类程序运行在用户空间,SYS类程序运行在内核空间)。当一个程序成为了进程,操作系统为其在内存中分配资源,来存放机器指令和数据,并为其映射了虚拟的内存地址,从而实现进程之间的相互独立性,这也是为什么一个进程无法直接通过内存地址访问另一个进程的数据,或者说同样一个内存地址对于不同进程,(极大)可能存储着不同的数据。

但是想实现操作其他进程的内存也不是毫无办法,Windows提供了一系列相关API,本文中使用了VirtualAllocEx,该函数可以在某一进程中分配内存,若分配成功,我们可以使用另一函数WriteProcessMemory来修改这块内存空间的内容。

DLL为何物

本文作为DLL注入的第一章,那就不得不先介绍一下DLL了。DLL为动态链接库的英文缩写,“库”字,顾名思义,我们可以在里面存放一些东西,也就是指令和数据;“链接”是一个与编译原理有关的名词,因为DLL主要存放函数,而程序运行可以通过链接信息,找到某一函数的所在地址从而调用该函数;“动态”表示DLL是在程序运行时才加载的(静态链接库在程序编译时进行加载,程序运行时就无需加载静态链接库了)。由此我们可以总结,DLL是一个在程序运行时,可以由程序自己加载的函数、数据库。另外需要补充很重要的一点,操作系统中一个动态链接库可能有很多进程来使用,但是操作系统在内存中只会保留一份这个动态链接库供所有进程使用。

DLL注入?

既然我们知道了DLL可以由程序自己加载,那么就不难想到,如果我们利用CreateRemoteThread函数在另一个进程中创建一个线程,让这个线程通过某种方式加载DLL到进程中,那么我们是不是就可以将我们写好的DLL“挂”到任意一个进程中了呢?

没错,这也就是这种DLL注入方法的思想。CreateRemoteThread注入DLL的核心函数就是CreateRemoteThread,这个函数可以用来在其他进程中创建线程,(在上一篇文章《远程线程『 RemoteThread 』》也提到了,大家可以看一下),我们将线程执行的函数就设为LoadLibraryW,将参数设为DLL在文件系统中的绝对地址,线程启动后便可成功注入了。在这里,DLL在文件系统中的绝对地址以宽字符串类型(LPCWSTR)来存储,我们利用VirtualAllocEx、WriteProcessMemory来在目标进程中分配空间并写入字符串。还有一点要注意,虽然操作系统为每个进程映射了不同的虚拟内存地址,但是每个进程的kernel32.dll的映像起始地址都是相同的,所以LoadLibraryW函数在每个进程中的地址也都一样。

附:示例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
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <wchar.h>

// 动态链接库每次被加载、卸载都会执行DllMain
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
wprintf_s(L"DLL_PROCESS_ATTACH\n");
case DLL_THREAD_ATTACH:
wprintf_s(L"DLL_THREAD_ATTACH\n");
case DLL_THREAD_DETACH:
wprintf_s(L"DLL_THREAD_DETACH\n");
case DLL_PROCESS_DETACH:
wprintf_s(L"DLL_PROCESS_DETACH\n");
break;
}
return TRUE;
}

Reference

[1] Gality369/Process-Injection: 汇总了目前可以找到的所有的进程注入的方式,完成了x86/x64下的测试,不断更新中 https://github.com/Gality369/Process-Injection