截取系统 API 调用
在很多情况下,为了测试代码或扩展操作系统的功能,软件开发人员或测试人员必须截取系统函数调用。有一些软件包能够提供该功能,如微软公司的 Detours* 库,或 OK Thinking Software 的 Syringe*。但是从另一个角度而言,开发人员可能希望不需借助第三方软件,自己就能实现该功能。
本文描述了函数截取的几种不同方式,并详细介绍了无需使用商业软件包,也不需受 GNU*(通用公共许可证)许可的约束,就能够实现该功能的一种通用方法。本文所有材料由英特尔公司开发,或根据 MSDN* 样本代码修改而来。
本文描述了函数截取的几种不同方式,并详细介绍了无需使用商业软件包,也不需受 GNU*(通用公共许可证)许可的约束,就能够实现该功能的一种通用方法。本文所有材料由英特尔公司开发,或根据 MSDN* 样本代码修改而来。
截取系统函数调用的两项基本技术
大部分截取任意函数调用的方法都是准备一个
DLL,用来替代将被截取的目标函数,然后将
DLL 注入至目标进程;在与目标进程连接的基础上,
DLL 将自己与目标函数相连。这种技术之所以适合此任务,是因为在大多数情况下我们无法获得目标应用程序的源代码,而这种技术只需相对简单地编写一个包含代换函数的
DLL,就可将其与软件的其它部分分离开来。
两种截取方法已经过研究和分析。Syringe 通过修改函数输入条目( thunking 表)运行。而Detours 库则直接修改目标函数( 在目标进程空间内),并无条件地跳转至代换函数。此外,它还提供能够调用原始函数的 trampoline 函数。
Detours 技术之所以采用后一种方法,是因为在许多情况下,Syringe 无法找到 thunk,并且它不能提供 trampoline功能来调用原始函数。在这两种方法下,注入 DLL 的工作方式相同。
截取系统函数调用的全部工作流程如下所示:
两种截取方法已经过研究和分析。Syringe 通过修改函数输入条目( thunking 表)运行。而Detours 库则直接修改目标函数( 在目标进程空间内),并无条件地跳转至代换函数。此外,它还提供能够调用原始函数的 trampoline 函数。
Detours 技术之所以采用后一种方法,是因为在许多情况下,Syringe 无法找到 thunk,并且它不能提供 trampoline功能来调用原始函数。在这两种方法下,注入 DLL 的工作方式相同。
截取系统函数调用的全部工作流程如下所示:
DLL 注入 — 首先,主软件打开目标进程,并使其加载包含代换函数的 DLL。 |
|
目标函数修改 — 当 DLL 连接至进程时,它在目标进程空间内修改目标函数,从而直接跳转至 DLL 中的代换函数。Trampoline 函数能够随意调用原始函数。 |
|
目标函数截取 — 当调用目标函数时,它直接跳转至 DLL 中的代换函数。如果开发人员希望调用原始的功能,则他或她就可以调用 trampoline 函数。 |
DLL 注入
本节内容完全以 MSDN 文章“ 定制调试诊断工具和实用程序 — 摆脱
DLL“地狱”
(DLL Hell)*”为基础,该文章还包括可下载的源代码。在本文附录中可获得
Inject.cpp 和
Inject.h 。已对它们进行了定制以便于集成——仅需将其包括在项目中然后调用
InjectLib 即可。使目标进程加载
DLL 的算法按如下步骤工作:
Inject.cpp 融合了包括稳固的安全特性等大量其它功能,但上述步骤已足够阐明其核心概念。
通过调用 OpenProcess 打开目标进程。 |
|
通过调用 VirtualAllocEx 在目标进程中分配内存。利用 WriteProcessMemory 将要被注入的 DLL 名称写入分配的内存。 |
|
通过调用 GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW") 来获取 LoadLibrary 的地址; |
|
调用 CreateRemoteThread,指定 LoadLibrary 的入口点,并将 DLL (第 2 步中) 的名称作为其自变量。目标进程将加载 DLL。 |
|
利用 VirtualFreeEx 释放分配的内存。已不再需要该内存。 |
Inject.cpp 融合了包括稳固的安全特性等大量其它功能,但上述步骤已足够阐明其核心概念。
目标函数修改
目标函数修改为自我修改代码,尽管在将 jmp 注入进程内存的过程中存在一些缺陷,但它在 MSDN*
上具有完善的文件证明。为避免混淆,本节列出了几乎全部的样本代码。
目标函数修改的两个主要方面为代换函数和 trampoline 函数。下面的代码片断为截取 GetSystemPowerStatus API 的 DLL 示例:
目标函数修改的两个主要方面为代换函数和 trampoline 函数。下面的代码片断为截取 GetSystemPowerStatus API 的 DLL 示例:
这段代码为连接所做的第一件事就是调用
InterceptAPI。需要使用包含目标函数的模块名称、目标函数的名称以及代换函数的地址。
GetSystemPowerStatus 位于
kernel32.dll 中。其它基本的 Win32* API,如
MessageBox 和
PeekMessage,都能够在
user32.dll 中获得。MSDN 指定每个 API 所属的模块;未来的增强版中,将自动为给定的 API 找到正确的模块。
InterceptAPI 将目标函数的前五个字节覆盖为无条件跳转( opcode 0xE9),后面为四个字节的带符号整数(向代换函数的位移)。位移从下一个指令开始;因此需要使用 pbReplaced - (pbTargetCode +4)。进行该代码操作时,需要注意以下两点:
现在,当调用
GetSystemPowerStatus 函数时,它跳转至代换函数,然后直接返回调用方,成功截取调用。
InterceptAPI 将目标函数的前五个字节覆盖为无条件跳转( opcode 0xE9),后面为四个字节的带符号整数(向代换函数的位移)。位移从下一个指令开始;因此需要使用 pbReplaced - (pbTargetCode +4)。进行该代码操作时,需要注意以下两点:
将区域覆盖的保护模式改为 VirtualProtect。否则,将发生非法访问错误。 |
|
必须使用 FlushInstructionCache 来支持指令已存在于高速缓存中的情况。否则,即使内存中的指令已经有所变化,旧代码仍将在高速缓存中运行。 |
Trampoline 函数
在很多情况下,代换函数除使用自身代码外,还需调用原始目标函数,这样就能够扩展 API 的功能,而不是替换整个 API。Trampoline 函数可以提供该功能。Trampoline 函数的原理如下所示:
可能存在一种复杂的情况,即原始代码的第六个字节可能是先前指令的一部分。在这种情况下,函数会覆盖部分先前指令,然后崩溃。在
GetSystemPowerStatus 的情况中,前五个字节后的新指令开始于第七个字节。因此,对于这种工作机制,需要将六个字节复制到 trampoline,并且代码必须相应地调整这个偏移量。
代码需要复制的字节数取决于 API。查看原始目标代码( 利用调试器或反汇编器)并计算需要复制的字节数是非常必要的。未来的增强版将自动检测正确的偏移量。假设我们已经知道正确的偏移量,下面的代码则显示出可建立 trampoline 函数的可扩展 InterceptAPI 函数:
编写一个具有相同声明的哑元函数(dummy function),将作为 trampoline 使用。确保哑元函数的长度超过 10 个字节。 |
|
在覆盖目标函数的前五个字节之前,将它们复制到 trampoline 函数的起始处。 |
|
利用无条件跳转,将trampoline的第六个字节覆盖为目标函数的第六个字节。 |
|
与之前一样覆盖目标函数。 |
|
当从代换函数或其它地方调用 trampoline 函数时,它执行复制出的原始代码的前五个字节,然后跳转至实际原始代码的第六个字节。控制返回至 trampoline 的调用方。当完成其它任务时,控制返回至 API 的控制方。 |
代码需要复制的字节数取决于 API。查看原始目标代码( 利用调试器或反汇编器)并计算需要复制的字节数是非常必要的。未来的增强版将自动检测正确的偏移量。假设我们已经知道正确的偏移量,下面的代码则显示出可建立 trampoline 函数的可扩展 InterceptAPI 函数:
1
BOOL InterceptAPI(HMODULE hLocalModule,
const
char
*
c_szDllName,
const
char
*
c_szApiName,
2 DWORD dwReplaced, DWORD dwTrampoline, int offset)
3 {
4 int i;
5 DWORD dwOldProtect;
6 DWORD dwAddressToIntercept = (DWORD)GetProcAddress(
7 GetModuleHandle(( char * )c_szDllName), ( char * )c_szApiName);
8
9 BYTE * pbTargetCode = (BYTE * ) dwAddressToIntercept;
10 BYTE * pbReplaced = (BYTE * ) dwReplaced;
11 BYTE * pbTrampoline = (BYTE * ) dwTrampoline;
12
13 // Change the protection of the trampoline region
14 // so that we can overwrite the first 5 + offset bytes.
15 VirtualProtect(( void * ) dwTrampoline, 5 + offset, PAGE_WRITECOPY, & dwOldProtect);
16 for (i = 0 ;i < offset;i ++ )
17 * pbTrampoline ++ = * pbTargetCode ++ ;
18 pbTargetCode = (BYTE * ) dwAddressToIntercept;
19
20 // Insert unconditional jump in the trampoline.
21 * pbTrampoline ++ = 0xE9 ; // jump rel32
22 * ((signed int * )(pbTrampoline)) = (pbTargetCode + offset) - (pbTrampoline + 4 );
23 VirtualProtect(( void * ) dwTrampoline, 5 + offset, PAGE_EXECUTE, & dwOldProtect);
24
25 // Overwrite the first 5 bytes of the target function
26 VirtualProtect(( void * ) dwAddressToIntercept, 5 , PAGE_WRITECOPY, & dwOldProtect);
27 * pbTargetCode ++ = 0xE9 ; // jump rel32
28 * ((signed int * )(pbTargetCode)) = pbReplaced - (pbTargetCode + 4 );
29 VirtualProtect(( void * ) dwAddressToIntercept, 5 , PAGE_EXECUTE, & dwOldProtect);
30
31 // Flush the instruction cache to make sure
32 // the modified code is executed.
33 FlushInstructionCache(GetCurrentProcess(), NULL, NULL);
34 return TRUE;
35 }
36
2 DWORD dwReplaced, DWORD dwTrampoline, int offset)
3 {
4 int i;
5 DWORD dwOldProtect;
6 DWORD dwAddressToIntercept = (DWORD)GetProcAddress(
7 GetModuleHandle(( char * )c_szDllName), ( char * )c_szApiName);
8
9 BYTE * pbTargetCode = (BYTE * ) dwAddressToIntercept;
10 BYTE * pbReplaced = (BYTE * ) dwReplaced;
11 BYTE * pbTrampoline = (BYTE * ) dwTrampoline;
12
13 // Change the protection of the trampoline region
14 // so that we can overwrite the first 5 + offset bytes.
15 VirtualProtect(( void * ) dwTrampoline, 5 + offset, PAGE_WRITECOPY, & dwOldProtect);
16 for (i = 0 ;i < offset;i ++ )
17 * pbTrampoline ++ = * pbTargetCode ++ ;
18 pbTargetCode = (BYTE * ) dwAddressToIntercept;
19
20 // Insert unconditional jump in the trampoline.
21 * pbTrampoline ++ = 0xE9 ; // jump rel32
22 * ((signed int * )(pbTrampoline)) = (pbTargetCode + offset) - (pbTrampoline + 4 );
23 VirtualProtect(( void * ) dwTrampoline, 5 + offset, PAGE_EXECUTE, & dwOldProtect);
24
25 // Overwrite the first 5 bytes of the target function
26 VirtualProtect(( void * ) dwAddressToIntercept, 5 , PAGE_WRITECOPY, & dwOldProtect);
27 * pbTargetCode ++ = 0xE9 ; // jump rel32
28 * ((signed int * )(pbTargetCode)) = pbReplaced - (pbTargetCode + 4 );
29 VirtualProtect(( void * ) dwAddressToIntercept, 5 , PAGE_EXECUTE, & dwOldProtect);
30
31 // Flush the instruction cache to make sure
32 // the modified code is executed.
33 FlushInstructionCache(GetCurrentProcess(), NULL, NULL);
34 return TRUE;
35 }
36
结论
本文描述了截取系统函数调用的一种通用方法,同时还提供了 trampoline 函数,从而保留了原始功能。本文仅对方法进行简要描述,并未对完整的软件包作出说明,因此如下一些细节并没有实现:
然而,对于开发人员而言,无需依赖第三方软件包,执行截取任意系统函数调用的软件,本文中涉及的技术、说明及源代码已经足够。
自动检测包含目标 API 的模块。 |
|
自动检测 trampoline 函数的偏移量。 |
|
删除代换函数,并注入 DLL。(到目前为止,清空代换函数的唯一方法是关闭应用程序。) |
1
#include
"
stdafx.h
"
2 #include " Inject.h "
3
4 #include < tchar.h >
5 #include < malloc.h > // For alloca
6 #include < pi.h >
7
8 #ifdef UNICODE
9 #define InjectLib InjectLibW
10 #else
11 #define InjectLib InjectLibA
12 #endif // !UNICODE
13
14 BOOL AdjustDacl(HANDLE h, DWORD DesiredAccess)
15 {
16 // the WORLD Sid is trivial to form programmatically (S-1-1-0)
17 SID world = { SID_REVISION, 1 , SECURITY_WORLD_SID_AUTHORITY, 0 };
18
19 EXPLICIT_ACCESS ea =
20 {
21 DesiredAccess,
22 SET_ACCESS,
23 NO_INHERITANCE,
24 {
25 0 , NO_MULTIPLE_TRUSTEE,
26 TRUSTEE_IS_SID,
27 TRUSTEE_IS_USER,
28 reinterpret_cast < LPTSTR > ( & world)
29 }
30 };
31 ACL * pdacl = 0 ;
32 DWORD err = SetEntriesInAcl( 1 , & ea, 0 , & pdacl);
33 if (err == ERROR_SUCCESS)
34 {
35 err = SetSecurityInfo(h, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, 0 , 0 , pdacl, 0 );
36 LocalFree(pdacl);
37 return (err == ERROR_SUCCESS);
38 }
39 else
40 return (FALSE);
41 }
42
43 // Useful helper function for enabling a single privilege
44 BOOL EnableTokenPrivilege(HANDLE htok, LPCTSTR szPrivilege, TOKEN_PRIVILEGES & tpOld)
45 {
46 TOKEN_PRIVILEGES tp;
47 tp.PrivilegeCount = 1 ;
48 tp.Privileges[ 0 ].Attributes = SE_PRIVILEGE_ENABLED;
49 if (LookupPrivilegeValue( 0 , szPrivilege, & tp.Privileges[ 0 ].Luid))
50 {
51 // htok must have been opened with the following permissions:
52 // TOKEN_QUERY (to get the old priv setting)
53 // TOKEN_ADJUST_PRIVILEGES (to adjust the priv)
54 DWORD cbOld = sizeof tpOld;
55 if (AdjustTokenPrivileges(htok, FALSE, & tp, cbOld, & tpOld, & cbOld))
56 // Note that AdjustTokenPrivileges may succeed, and yet
57 // some privileges weren't actually adjusted.
58 // You've got to check GetLastError() to be sure!
59 return (ERROR_NOT_ALL_ASSIGNED != GetLastError());
60 else
61 return (FALSE);
62 }
63 else
64 return (FALSE);
65 }
66
67
68 // Corresponding restoration helper function
69 BOOL RestoreTokenPrivilege(HANDLE htok, const TOKEN_PRIVILEGES & tpOld)
70 {
71 return (AdjustTokenPrivileges(htok, FALSE, const_cast < TOKEN_PRIVILEGES *> ( & tpOld), 0 , 0 , 0 ));
72 }
73
74 HANDLE GetProcessHandleWithEnoughRights(DWORD PID, DWORD AccessRights)
75 {
76 HANDLE hProcess = ::OpenProcess(AccessRights, FALSE, PID);
77 if (hProcess == NULL)
78 {
79 HANDLE hpWriteDAC = OpenProcess(WRITE_DAC, FALSE, PID);
80 if (hpWriteDAC == NULL)
81 {
82 // hmm, we don't have permissions to modify the DACL
83 // time to take ownership
84 HANDLE htok;
85 if ( ! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, & htok))
86 return (FALSE);
87
88 TOKEN_PRIVILEGES tpOld;
89 if (EnableTokenPrivilege(htok, SE_TAKE_OWNERSHIP_NAME, tpOld))
90 {
91 // SeTakeOwnershipPrivilege allows us to open objects with
92 // WRITE_OWNER, but that's about it, so we'll update the owner,
93 // and dup the handle so we can get WRITE_DAC permissions.
94 HANDLE hpWriteOwner = OpenProcess(WRITE_OWNER, FALSE, PID);
95 if (hpWriteOwner != NULL)
96 {
97 BYTE buf[ 512 ]; // this should always be big enough
98 DWORD cb = sizeof buf;
99 if (GetTokenInformation(htok, TokenUser, buf, cb, & cb))
100 {
101 DWORD err =
102 SetSecurityInfo(
103 hpWriteOwner,
104 SE_KERNEL_OBJECT,
105 OWNER_SECURITY_INFORMATION,
106 reinterpret_cast < TOKEN_USER *> (buf) -> User.Sid,
107 0 , 0 , 0
108 );
109 if (err == ERROR_SUCCESS)
110 {
111 // now that we're the owner, we've implicitly got WRITE_DAC
112 // permissions, so ask the system to reevaluate our request,
113 // giving us a handle with WRITE_DAC permissions
114 if (
115 ! DuplicateHandle(
116 GetCurrentProcess(),
117 hpWriteOwner,
118 GetCurrentProcess(),
119 & hpWriteDAC,
120 WRITE_DAC, FALSE, 0
121 )
122 )
123 hpWriteDAC = NULL;
124 }
125 }
126
127 // don't forget to close handle
128 ::CloseHandle(hpWriteOwner);
129 }
130
131 // not truly necessary in this app,
132 // but included for completeness
133 RestoreTokenPrivilege(htok, tpOld);
134 }
135
136 // don't forget to close the token handle
137 ::CloseHandle(htok);
138 }
139
140 if (hpWriteDAC)
141 {
142 // we've now got a handle that allows us WRITE_DAC permission
143 AdjustDacl(hpWriteDAC, AccessRights);
144
145 // now that we've granted ourselves permission to access
146 // the process, ask the system to reevaluate our request,
147 // giving us a handle with right permissions
148 if (
149 ! DuplicateHandle(
150 GetCurrentProcess(),
151 hpWriteDAC,
152 GetCurrentProcess(),
153 & hProcess,
154 AccessRights,
155 FALSE,
156 0
157 )
158 )
159 hProcess = NULL;
160
161 CloseHandle(hpWriteDAC);
162 }
163 }
164
165 return (hProcess);
166 }
167
168 BOOL WINAPI InjectLibW(DWORD dwProcessId, PCWSTR pszLibFile)
169 {
170 BOOL fOk = FALSE; // Assume that the function fails
171 HANDLE hProcess = NULL, hThread = NULL;
172 PWSTR pszLibFileRemote = NULL;
173
174 // Get a handle for the target process.
175 hProcess =
176 GetProcessHandleWithEnoughRights(
177 dwProcessId,
178 PROCESS_QUERY_INFORMATION | // Required by Alpha
179 PROCESS_CREATE_THREAD | // For CreateRemoteThread
180 PROCESS_VM_OPERATION | // For VirtualAllocEx/VirtualFreeEx
181 PROCESS_VM_WRITE // For WriteProcessMemory
182 );
183 if (hProcess == NULL)
184 return (FALSE);
185
186 // Calculate the number of bytes needed for the DLL's pathname
187 int cch = 1 + lstrlenW(pszLibFile);
188 int cb = cch * sizeof (WCHAR);
189
190 // Allocate space in the remote process for the pathname
191 pszLibFileRemote =
192 (PWSTR) VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
193
194 if (pszLibFileRemote != NULL)
195 {
196 // Copy the DLL's pathname to the remote process's address space
197 if (WriteProcessMemory(hProcess, pszLibFileRemote,
198 (PVOID) pszLibFile, cb, NULL))
199 {
200 // Get the real address of LoadLibraryW in Kernel32.dll
201 PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
202 GetProcAddress(GetModuleHandle(TEXT( " Kernel32 " )), " LoadLibraryW " );
203 if (pfnThreadRtn != NULL)
204 {
205 // Create a remote thread that calls LoadLibraryW(DLLPathname)
206 hThread = CreateRemoteThread(hProcess, NULL, 0 ,
207 pfnThreadRtn, pszLibFileRemote, 0 , NULL);
208 if (hThread != NULL)
209 {
210 // Wait for the remote thread to terminate
211 WaitForSingleObject(hThread, INFINITE);
212
213 fOk = TRUE; // Everything executed successfully
214
215 CloseHandle(hThread);
216 }
217 }
218 }
219 // Free the remote memory that contained the DLL's pathname
220 VirtualFreeEx(hProcess, pszLibFileRemote, 0 , MEM_RELEASE);
221 }
222
223 CloseHandle(hProcess);
224
225 return (fOk);
226 }
227
228
229 BOOL WINAPI InjectLibA(DWORD dwProcessId, PCSTR pszLibFile) {
230
231 // Allocate a (stack) buffer for the Unicode version of the pathname
232 PWSTR pszLibFileW = (PWSTR)
233 _alloca((lstrlenA(pszLibFile) + 1 ) * sizeof (WCHAR));
234
235 // Convert the ANSI pathname to its Unicode equivalent
236 wsprintfW(pszLibFileW, L " %S " , pszLibFile);
237
238 // Call the Unicode version of the function to actually do the work.
239 return (InjectLibW(dwProcessId, pszLibFileW));
240 }
241
2 #include " Inject.h "
3
4 #include < tchar.h >
5 #include < malloc.h > // For alloca
6 #include < pi.h >
7
8 #ifdef UNICODE
9 #define InjectLib InjectLibW
10 #else
11 #define InjectLib InjectLibA
12 #endif // !UNICODE
13
14 BOOL AdjustDacl(HANDLE h, DWORD DesiredAccess)
15 {
16 // the WORLD Sid is trivial to form programmatically (S-1-1-0)
17 SID world = { SID_REVISION, 1 , SECURITY_WORLD_SID_AUTHORITY, 0 };
18
19 EXPLICIT_ACCESS ea =
20 {
21 DesiredAccess,
22 SET_ACCESS,
23 NO_INHERITANCE,
24 {
25 0 , NO_MULTIPLE_TRUSTEE,
26 TRUSTEE_IS_SID,
27 TRUSTEE_IS_USER,
28 reinterpret_cast < LPTSTR > ( & world)
29 }
30 };
31 ACL * pdacl = 0 ;
32 DWORD err = SetEntriesInAcl( 1 , & ea, 0 , & pdacl);
33 if (err == ERROR_SUCCESS)
34 {
35 err = SetSecurityInfo(h, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, 0 , 0 , pdacl, 0 );
36 LocalFree(pdacl);
37 return (err == ERROR_SUCCESS);
38 }
39 else
40 return (FALSE);
41 }
42
43 // Useful helper function for enabling a single privilege
44 BOOL EnableTokenPrivilege(HANDLE htok, LPCTSTR szPrivilege, TOKEN_PRIVILEGES & tpOld)
45 {
46 TOKEN_PRIVILEGES tp;
47 tp.PrivilegeCount = 1 ;
48 tp.Privileges[ 0 ].Attributes = SE_PRIVILEGE_ENABLED;
49 if (LookupPrivilegeValue( 0 , szPrivilege, & tp.Privileges[ 0 ].Luid))
50 {
51 // htok must have been opened with the following permissions:
52 // TOKEN_QUERY (to get the old priv setting)
53 // TOKEN_ADJUST_PRIVILEGES (to adjust the priv)
54 DWORD cbOld = sizeof tpOld;
55 if (AdjustTokenPrivileges(htok, FALSE, & tp, cbOld, & tpOld, & cbOld))
56 // Note that AdjustTokenPrivileges may succeed, and yet
57 // some privileges weren't actually adjusted.
58 // You've got to check GetLastError() to be sure!
59 return (ERROR_NOT_ALL_ASSIGNED != GetLastError());
60 else
61 return (FALSE);
62 }
63 else
64 return (FALSE);
65 }
66
67
68 // Corresponding restoration helper function
69 BOOL RestoreTokenPrivilege(HANDLE htok, const TOKEN_PRIVILEGES & tpOld)
70 {
71 return (AdjustTokenPrivileges(htok, FALSE, const_cast < TOKEN_PRIVILEGES *> ( & tpOld), 0 , 0 , 0 ));
72 }
73
74 HANDLE GetProcessHandleWithEnoughRights(DWORD PID, DWORD AccessRights)
75 {
76 HANDLE hProcess = ::OpenProcess(AccessRights, FALSE, PID);
77 if (hProcess == NULL)
78 {
79 HANDLE hpWriteDAC = OpenProcess(WRITE_DAC, FALSE, PID);
80 if (hpWriteDAC == NULL)
81 {
82 // hmm, we don't have permissions to modify the DACL
83 // time to take ownership
84 HANDLE htok;
85 if ( ! OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, & htok))
86 return (FALSE);
87
88 TOKEN_PRIVILEGES tpOld;
89 if (EnableTokenPrivilege(htok, SE_TAKE_OWNERSHIP_NAME, tpOld))
90 {
91 // SeTakeOwnershipPrivilege allows us to open objects with
92 // WRITE_OWNER, but that's about it, so we'll update the owner,
93 // and dup the handle so we can get WRITE_DAC permissions.
94 HANDLE hpWriteOwner = OpenProcess(WRITE_OWNER, FALSE, PID);
95 if (hpWriteOwner != NULL)
96 {
97 BYTE buf[ 512 ]; // this should always be big enough
98 DWORD cb = sizeof buf;
99 if (GetTokenInformation(htok, TokenUser, buf, cb, & cb))
100 {
101 DWORD err =
102 SetSecurityInfo(
103 hpWriteOwner,
104 SE_KERNEL_OBJECT,
105 OWNER_SECURITY_INFORMATION,
106 reinterpret_cast < TOKEN_USER *> (buf) -> User.Sid,
107 0 , 0 , 0
108 );
109 if (err == ERROR_SUCCESS)
110 {
111 // now that we're the owner, we've implicitly got WRITE_DAC
112 // permissions, so ask the system to reevaluate our request,
113 // giving us a handle with WRITE_DAC permissions
114 if (
115 ! DuplicateHandle(
116 GetCurrentProcess(),
117 hpWriteOwner,
118 GetCurrentProcess(),
119 & hpWriteDAC,
120 WRITE_DAC, FALSE, 0
121 )
122 )
123 hpWriteDAC = NULL;
124 }
125 }
126
127 // don't forget to close handle
128 ::CloseHandle(hpWriteOwner);
129 }
130
131 // not truly necessary in this app,
132 // but included for completeness
133 RestoreTokenPrivilege(htok, tpOld);
134 }
135
136 // don't forget to close the token handle
137 ::CloseHandle(htok);
138 }
139
140 if (hpWriteDAC)
141 {
142 // we've now got a handle that allows us WRITE_DAC permission
143 AdjustDacl(hpWriteDAC, AccessRights);
144
145 // now that we've granted ourselves permission to access
146 // the process, ask the system to reevaluate our request,
147 // giving us a handle with right permissions
148 if (
149 ! DuplicateHandle(
150 GetCurrentProcess(),
151 hpWriteDAC,
152 GetCurrentProcess(),
153 & hProcess,
154 AccessRights,
155 FALSE,
156 0
157 )
158 )
159 hProcess = NULL;
160
161 CloseHandle(hpWriteDAC);
162 }
163 }
164
165 return (hProcess);
166 }
167
168 BOOL WINAPI InjectLibW(DWORD dwProcessId, PCWSTR pszLibFile)
169 {
170 BOOL fOk = FALSE; // Assume that the function fails
171 HANDLE hProcess = NULL, hThread = NULL;
172 PWSTR pszLibFileRemote = NULL;
173
174 // Get a handle for the target process.
175 hProcess =
176 GetProcessHandleWithEnoughRights(
177 dwProcessId,
178 PROCESS_QUERY_INFORMATION | // Required by Alpha
179 PROCESS_CREATE_THREAD | // For CreateRemoteThread
180 PROCESS_VM_OPERATION | // For VirtualAllocEx/VirtualFreeEx
181 PROCESS_VM_WRITE // For WriteProcessMemory
182 );
183 if (hProcess == NULL)
184 return (FALSE);
185
186 // Calculate the number of bytes needed for the DLL's pathname
187 int cch = 1 + lstrlenW(pszLibFile);
188 int cb = cch * sizeof (WCHAR);
189
190 // Allocate space in the remote process for the pathname
191 pszLibFileRemote =
192 (PWSTR) VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
193
194 if (pszLibFileRemote != NULL)
195 {
196 // Copy the DLL's pathname to the remote process's address space
197 if (WriteProcessMemory(hProcess, pszLibFileRemote,
198 (PVOID) pszLibFile, cb, NULL))
199 {
200 // Get the real address of LoadLibraryW in Kernel32.dll
201 PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
202 GetProcAddress(GetModuleHandle(TEXT( " Kernel32 " )), " LoadLibraryW " );
203 if (pfnThreadRtn != NULL)
204 {
205 // Create a remote thread that calls LoadLibraryW(DLLPathname)
206 hThread = CreateRemoteThread(hProcess, NULL, 0 ,
207 pfnThreadRtn, pszLibFileRemote, 0 , NULL);
208 if (hThread != NULL)
209 {
210 // Wait for the remote thread to terminate
211 WaitForSingleObject(hThread, INFINITE);
212
213 fOk = TRUE; // Everything executed successfully
214
215 CloseHandle(hThread);
216 }
217 }
218 }
219 // Free the remote memory that contained the DLL's pathname
220 VirtualFreeEx(hProcess, pszLibFileRemote, 0 , MEM_RELEASE);
221 }
222
223 CloseHandle(hProcess);
224
225 return (fOk);
226 }
227
228
229 BOOL WINAPI InjectLibA(DWORD dwProcessId, PCSTR pszLibFile) {
230
231 // Allocate a (stack) buffer for the Unicode version of the pathname
232 PWSTR pszLibFileW = (PWSTR)
233 _alloca((lstrlenA(pszLibFile) + 1 ) * sizeof (WCHAR));
234
235 // Convert the ANSI pathname to its Unicode equivalent
236 wsprintfW(pszLibFileW, L " %S " , pszLibFile);
237
238 // Call the Unicode version of the function to actually do the work.
239 return (InjectLibW(dwProcessId, pszLibFileW));
240 }
241