]> git.cworth.org Git - apitrace/blob - inject/injector.cpp
inject: Add define to use environment var instead of shared memory.
[apitrace] / inject / injector.cpp
1 /**************************************************************************
2  *
3  * Copyright 2011 Jose Fonseca
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  *
24  **************************************************************************/
25
26
27 /*
28  * Main program to start and inject a DLL into a process via a remote thread.
29  *
30  * For background see:
31  * - http://en.wikipedia.org/wiki/DLL_injection#Approaches_on_Microsoft_Windows
32  * - http://www.codeproject.com/KB/threads/completeinject.aspx
33  * - http://www.codeproject.com/KB/threads/winspy.aspx
34  * - http://www.codeproject.com/KB/DLL/DLL_Injection_tutorial.aspx
35  * - http://www.codeproject.com/KB/threads/APIHooking.aspx
36  *
37  * Other slightly different techniques:
38  * - http://www.fr33project.org/pages/projects/phook.htm
39  * - http://www.hbgary.com/loading-a-dll-without-calling-loadlibrary
40  * - http://securityxploded.com/ntcreatethreadex.php
41  */
42
43 #include <string>
44
45 #include <windows.h>
46 #include <stdio.h>
47
48 #include "inject.h"
49
50
51 /**
52  * Determine whether an argument should be quoted.
53  */
54 static bool
55 needsQuote(const char *arg)
56 {
57     char c;
58     while (true) {
59         c = *arg++;
60         if (c == '\0') {
61             break;
62         }
63         if (c == ' ' || c == '\t' || c == '\"') {
64             return true;
65         }
66         if (c == '\\') {
67             c = *arg++;
68             if (c == '\0') {
69                 break;
70             }
71             if (c == '"') {
72                 return true;
73             }
74         }
75     }
76     return false;
77 }
78
79 static void
80 quoteArg(std::string &s, const char *arg)
81 {
82     char c;
83     unsigned backslashes = 0;
84
85     s.push_back('"');
86     while (true) {
87         c = *arg++;
88         if (c == '\0') {
89             break;
90         } else if (c == '"') {
91             while (backslashes) {
92                 s.push_back('\\');
93                 --backslashes;
94             }
95             s.push_back('\\');
96         } else {
97             if (c == '\\') {
98                 ++backslashes;
99             } else {
100                 backslashes = 0;
101             }
102         }
103         s.push_back(c);
104     }
105     s.push_back('"');
106 }
107
108
109 int
110 main(int argc, char *argv[])
111 {
112
113     if (argc < 3) {
114         fprintf(stderr, "inject dllname.dll command [args] ...\n");
115         return 1;
116     }
117
118     const char *szDll = argv[1];
119 #if !USE_SHARED_MEM
120     SetEnvironmentVariableA("INJECT_DLL", szDll);
121 #else
122     SetSharedMem(szDll);
123 #endif
124
125     PROCESS_INFORMATION processInfo;
126     HANDLE hProcess;
127     BOOL bAttach;
128     if (isdigit(argv[2][0])) {
129         bAttach = TRUE;
130
131         BOOL bRet;
132         HANDLE hToken   = NULL;
133         bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);
134         if (!bRet) {
135             fprintf(stderr, "error: OpenProcessToken returned %u\n", (unsigned)bRet);
136             return 1;
137         }
138
139         LUID Luid;
140         bRet = LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Luid);
141         if (!bRet) {
142             fprintf(stderr, "error: LookupPrivilegeValue returned %u\n", (unsigned)bRet);
143             return 1;
144         }
145
146         TOKEN_PRIVILEGES tp;
147         tp.PrivilegeCount = 1;
148         tp.Privileges[0].Luid = Luid;
149         tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
150         bRet = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof tp, NULL, NULL);
151         if (!bRet) {
152             fprintf(stderr, "error: AdjustTokenPrivileges returned %u\n", (unsigned)bRet);
153             return 1;
154         }
155
156         DWORD dwDesiredAccess =
157             PROCESS_CREATE_THREAD |
158             PROCESS_QUERY_INFORMATION |
159             PROCESS_QUERY_LIMITED_INFORMATION |
160             PROCESS_VM_OPERATION |
161             PROCESS_VM_WRITE |
162             PROCESS_VM_READ;
163         DWORD dwProcessId = atol(argv[2]);
164         hProcess = OpenProcess(
165             dwDesiredAccess,
166             FALSE /* bInheritHandle */,
167             dwProcessId);
168         if (!hProcess) {
169             DWORD dwLastError = GetLastError();
170             fprintf(stderr, "error: failed to open process %lu (%lu)\n", dwProcessId, dwLastError);
171             return 1;
172         }
173     } else {
174         bAttach = FALSE;
175         std::string commandLine;
176         char sep = 0;
177         for (int i = 2; i < argc; ++i) {
178             const char *arg = argv[i];
179
180             if (sep) {
181                 commandLine.push_back(sep);
182             }
183
184             if (needsQuote(arg)) {
185                 quoteArg(commandLine, arg);
186             } else {
187                 commandLine.append(arg);
188             }
189
190             sep = ' ';
191         }
192
193         STARTUPINFO startupInfo;
194         memset(&startupInfo, 0, sizeof startupInfo);
195         startupInfo.cb = sizeof startupInfo;
196
197         // Create the process in suspended state
198         if (!CreateProcessA(
199                NULL,
200                const_cast<char *>(commandLine.c_str()), // only modified by CreateProcessW
201                0, // process attributes
202                0, // thread attributes
203                TRUE, // inherit handles
204                CREATE_SUSPENDED,
205                NULL, // environment
206                NULL, // current directory
207                &startupInfo,
208                &processInfo)) {
209             fprintf(stderr, "error: failed to execute %s\n", commandLine.c_str());
210             return 1;
211         }
212
213         hProcess = processInfo.hProcess;
214     }
215
216     /*
217      * XXX: Mixed architecture don't quite work.  See also
218      * http://www.corsix.org/content/dll-injection-and-wow64
219      */
220     const char *szDllName;
221     szDllName = "inject.dll";
222
223     char szDllPath[MAX_PATH];
224     GetModuleFileNameA(NULL, szDllPath, sizeof szDllPath);
225     getDirName(szDllPath);
226     strncat(szDllPath, szDllName, sizeof szDllPath - strlen(szDllPath) - 1);
227
228     size_t szDllPathLength = strlen(szDllPath) + 1;
229
230     // Allocate memory in the target process to hold the DLL name
231     void *lpMemory = VirtualAllocEx(hProcess, NULL, szDllPathLength, MEM_COMMIT, PAGE_READWRITE);
232     if (!lpMemory) {
233         fprintf(stderr, "error: failed to allocate memory in the process\n");
234         TerminateProcess(hProcess, 1);
235         return 1;
236     }
237
238     // Copy DLL name into the target process
239     if (!WriteProcessMemory(hProcess, lpMemory, szDllPath, szDllPathLength, NULL)) {
240         fprintf(stderr, "error: failed to write into process memory\n");
241         TerminateProcess(hProcess, 1);
242         return 1;
243     }
244
245     /*
246      * Get LoadLibraryA address from kernel32.dll.  It's the same for all the
247      * process (XXX: but only within the same architecture).
248      */
249     PTHREAD_START_ROUTINE lpStartAddress =
250         (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("KERNEL32"), "LoadLibraryA");
251
252     // Create remote thread in another process
253     HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, lpMemory, 0, NULL);
254     if (!hThread) {
255         fprintf(stderr, "error: failed to create remote thread\n");
256         TerminateProcess(hProcess, 1);
257         return 1;
258     }
259
260     // Wait for it to finish
261     WaitForSingleObject(hThread, INFINITE);
262
263     DWORD hModule = 0;
264     GetExitCodeThread(hThread, &hModule);
265     if (!hModule) {
266         fprintf(stderr, "error: failed to inject %s\n", szDllPath);
267         TerminateProcess(hProcess, 1);
268         return 1;
269     }
270
271     if (bAttach) {
272         return 0;
273     }
274
275     // Start main process thread
276     ResumeThread(processInfo.hThread);
277
278     // Wait for it to finish
279     WaitForSingleObject(hProcess, INFINITE);
280
281     DWORD exitCode = ~0;
282     GetExitCodeProcess(hProcess, &exitCode);
283
284     CloseHandle(hProcess);
285     CloseHandle(processInfo.hThread);
286
287     return (int)exitCode;
288
289 }