José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 1 | /************************************************************************** |
| 2 | * |
| 3 | * Copyright 2011-2012 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 | * Code for the DLL that will be injected in the target process. |
| 29 | * |
| 30 | * The injected DLL will manipulate the import tables to hook the |
| 31 | * modules/functions of interest. |
| 32 | * |
| 33 | * See also: |
| 34 | * - http://www.codeproject.com/KB/system/api_spying_hack.aspx |
| 35 | * - http://www.codeproject.com/KB/threads/APIHooking.aspx |
| 36 | * - http://msdn.microsoft.com/en-us/magazine/cc301808.aspx |
| 37 | */ |
| 38 | |
| 39 | |
| 40 | #include <windows.h> |
José Fonseca | 6d7bbef | 2014-09-12 09:17:15 +0100 | [diff] [blame] | 41 | #include <sddl.h> |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 42 | |
| 43 | |
José Fonseca | 420d557 | 2014-09-12 09:12:58 +0100 | [diff] [blame] | 44 | static void |
Jose Fonseca | 685d532 | 2018-12-07 11:58:49 +0000 | [diff] [blame] | 45 | #if defined(__MINGW32__) |
| 46 | __attribute__ ((format (__MINGW_PRINTF_FORMAT, 1, 2))) |
| 47 | #elif defined(__GNUC__) |
José Fonseca | 420d557 | 2014-09-12 09:12:58 +0100 | [diff] [blame] | 48 | __attribute__ ((format (printf, 1, 2))) |
| 49 | #endif |
| 50 | debugPrintf(const char *format, ...); |
| 51 | |
| 52 | |
| 53 | static void |
| 54 | logLastError(const char *szMsg) |
| 55 | { |
| 56 | DWORD dwLastError = GetLastError(); |
| 57 | |
| 58 | // http://msdn.microsoft.com/en-gb/library/windows/desktop/ms680582.aspx |
| 59 | LPSTR lpErrorMsg = NULL; |
| 60 | DWORD cbWritten; |
| 61 | cbWritten = FormatMessage( |
| 62 | FORMAT_MESSAGE_ALLOCATE_BUFFER | |
| 63 | FORMAT_MESSAGE_FROM_SYSTEM | |
| 64 | FORMAT_MESSAGE_IGNORE_INSERTS, |
| 65 | NULL, |
| 66 | dwLastError, |
| 67 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| 68 | (LPSTR) &lpErrorMsg, |
| 69 | 0, NULL); |
| 70 | |
| 71 | if (cbWritten) { |
| 72 | debugPrintf("inject: error: %s: %s", szMsg, lpErrorMsg); |
| 73 | } else { |
| 74 | debugPrintf("inject: error: %s: %lu\n", szMsg, dwLastError); |
| 75 | } |
| 76 | |
| 77 | LocalFree(lpErrorMsg); |
| 78 | } |
| 79 | |
| 80 | |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 81 | static inline const char * |
| 82 | getSeparator(const char *szFilename) { |
| 83 | const char *p, *q; |
| 84 | p = NULL; |
| 85 | q = szFilename; |
| 86 | char c; |
| 87 | do { |
| 88 | c = *q++; |
| 89 | if (c == '\\' || c == '/' || c == ':') { |
| 90 | p = q; |
| 91 | } |
| 92 | } while (c); |
| 93 | return p; |
| 94 | } |
| 95 | |
| 96 | |
| 97 | static inline const char * |
| 98 | getBaseName(const char *szFilename) { |
| 99 | const char *pSeparator = getSeparator(szFilename); |
| 100 | if (!pSeparator) { |
| 101 | return szFilename; |
| 102 | } |
| 103 | return pSeparator; |
| 104 | } |
| 105 | |
| 106 | |
| 107 | static inline void |
| 108 | getDirName(char *szFilename) { |
| 109 | char *pSeparator = const_cast<char *>(getSeparator(szFilename)); |
| 110 | if (pSeparator) { |
| 111 | *pSeparator = '\0'; |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | |
| 116 | static inline void |
| 117 | getModuleName(char *szModuleName, size_t n, const char *szFilename) { |
| 118 | char *p = szModuleName; |
| 119 | const char *q = getBaseName(szFilename); |
| 120 | char c; |
| 121 | while (--n) { |
| 122 | c = *q++; |
| 123 | if (c == '.' || c == '\0') { |
| 124 | break; |
| 125 | } |
| 126 | *p++ = c; |
| 127 | }; |
| 128 | *p++ = '\0'; |
| 129 | } |
| 130 | |
| 131 | |
José Fonseca | 1405a3e | 2013-02-22 09:40:30 +0000 | [diff] [blame] | 132 | #define USE_SHARED_MEM 1 |
| 133 | |
| 134 | |
José Fonseca | 867d652 | 2013-10-23 17:52:59 +0100 | [diff] [blame] | 135 | struct SharedMem |
| 136 | { |
| 137 | BOOL bReplaced; |
Jose Fonseca | 59f21c4 | 2015-07-06 16:41:55 +0100 | [diff] [blame] | 138 | char cVerbosity; |
José Fonseca | 867d652 | 2013-10-23 17:52:59 +0100 | [diff] [blame] | 139 | char szDllName[4096 - sizeof(BOOL)]; |
| 140 | }; |
José Fonseca | 1405a3e | 2013-02-22 09:40:30 +0000 | [diff] [blame] | 141 | |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 142 | |
José Fonseca | 867d652 | 2013-10-23 17:52:59 +0100 | [diff] [blame] | 143 | static SharedMem *pSharedMem = NULL; |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 144 | static HANDLE hFileMapping = NULL; |
| 145 | |
| 146 | |
José Fonseca | 867d652 | 2013-10-23 17:52:59 +0100 | [diff] [blame] | 147 | static SharedMem * |
Jose Fonseca | 498710d | 2015-07-15 16:07:05 +0100 | [diff] [blame] | 148 | OpenSharedMemory(SECURITY_DESCRIPTOR *lpSecurityDescriptor) |
| 149 | { |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 150 | if (pSharedMem) { |
José Fonseca | 867d652 | 2013-10-23 17:52:59 +0100 | [diff] [blame] | 151 | return pSharedMem; |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 152 | } |
| 153 | |
José Fonseca | 6d7bbef | 2014-09-12 09:17:15 +0100 | [diff] [blame] | 154 | SECURITY_ATTRIBUTES sa; |
Jose Fonseca | 498710d | 2015-07-15 16:07:05 +0100 | [diff] [blame] | 155 | ZeroMemory(&sa, sizeof sa); |
| 156 | sa.nLength = sizeof sa; |
| 157 | sa.bInheritHandle = TRUE; |
| 158 | sa.lpSecurityDescriptor = lpSecurityDescriptor; |
José Fonseca | 6d7bbef | 2014-09-12 09:17:15 +0100 | [diff] [blame] | 159 | |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 160 | hFileMapping = CreateFileMapping( |
| 161 | INVALID_HANDLE_VALUE, // system paging file |
Jose Fonseca | 498710d | 2015-07-15 16:07:05 +0100 | [diff] [blame] | 162 | &sa, // lpAttributes |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 163 | PAGE_READWRITE, // read/write access |
| 164 | 0, // dwMaximumSizeHigh |
José Fonseca | 867d652 | 2013-10-23 17:52:59 +0100 | [diff] [blame] | 165 | sizeof(SharedMem), // dwMaximumSizeLow |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 166 | TEXT("injectfilemap")); // name of map object |
José Fonseca | 6d7bbef | 2014-09-12 09:17:15 +0100 | [diff] [blame] | 167 | |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 168 | if (hFileMapping == NULL) { |
José Fonseca | 420d557 | 2014-09-12 09:12:58 +0100 | [diff] [blame] | 169 | logLastError("failed to create file mapping"); |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 170 | return NULL; |
| 171 | } |
| 172 | |
| 173 | BOOL bAlreadyExists = (GetLastError() == ERROR_ALREADY_EXISTS); |
| 174 | |
José Fonseca | 867d652 | 2013-10-23 17:52:59 +0100 | [diff] [blame] | 175 | pSharedMem = (SharedMem *)MapViewOfFile( |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 176 | hFileMapping, |
| 177 | FILE_MAP_WRITE, // read/write access |
| 178 | 0, // dwFileOffsetHigh |
| 179 | 0, // dwFileOffsetLow |
| 180 | 0); // dwNumberOfBytesToMap (entire file) |
| 181 | if (pSharedMem == NULL) { |
José Fonseca | 420d557 | 2014-09-12 09:12:58 +0100 | [diff] [blame] | 182 | logLastError("failed to map view"); |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 183 | return NULL; |
| 184 | } |
| 185 | |
| 186 | if (!bAlreadyExists) { |
José Fonseca | 867d652 | 2013-10-23 17:52:59 +0100 | [diff] [blame] | 187 | memset(pSharedMem, 0, sizeof *pSharedMem); |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 188 | } |
| 189 | |
José Fonseca | 867d652 | 2013-10-23 17:52:59 +0100 | [diff] [blame] | 190 | return pSharedMem; |
José Fonseca | bd4937e | 2012-12-04 13:23:03 +0000 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | |
| 194 | static inline VOID |
| 195 | CloseSharedMem(void) { |
| 196 | if (!pSharedMem) { |
| 197 | return; |
| 198 | } |
| 199 | |
| 200 | UnmapViewOfFile(pSharedMem); |
| 201 | pSharedMem = NULL; |
| 202 | |
| 203 | CloseHandle(hFileMapping); |
| 204 | hFileMapping = NULL; |
| 205 | } |
| 206 | |
| 207 | |
Jose Fonseca | 6ddfd98 | 2015-08-03 11:58:35 +0100 | [diff] [blame] | 208 | /* |
| 209 | * XXX: Mixed architecture don't quite work. See also |
| 210 | * http://www.corsix.org/content/dll-injection-and-wow64 |
| 211 | */ |
| 212 | static BOOL |
| 213 | isDifferentArch(HANDLE hProcess) |
| 214 | { |
| 215 | typedef BOOL (WINAPI *PFNISWOW64PROCESS)(HANDLE, PBOOL); |
| 216 | PFNISWOW64PROCESS pfnIsWow64Process; |
| 217 | pfnIsWow64Process = (PFNISWOW64PROCESS) |
| 218 | GetProcAddress(GetModuleHandleA("kernel32"), "IsWow64Process"); |
| 219 | if (!pfnIsWow64Process) { |
| 220 | return FALSE; |
| 221 | } |
| 222 | |
Jose Fonseca | bafc842 | 2015-08-25 11:36:09 +0100 | [diff] [blame] | 223 | // NOTE: IsWow64Process will return false on 32-bits Windows |
| 224 | BOOL isThisWow64; |
| 225 | BOOL isOtherWow64; |
| 226 | if (!pfnIsWow64Process(GetCurrentProcess(), &isThisWow64) || |
| 227 | !pfnIsWow64Process(hProcess, &isOtherWow64)) { |
Jose Fonseca | 6ddfd98 | 2015-08-03 11:58:35 +0100 | [diff] [blame] | 228 | logLastError("IsWow64Process failed"); |
| 229 | return FALSE; |
| 230 | } |
| 231 | |
| 232 | return bool(isThisWow64) != bool(isOtherWow64); |
| 233 | } |
| 234 | |
| 235 | |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 236 | static BOOL |
José Fonseca | 420d557 | 2014-09-12 09:12:58 +0100 | [diff] [blame] | 237 | injectDll(HANDLE hProcess, const char *szDllPath) |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 238 | { |
José Fonseca | 209a66d | 2015-03-12 14:13:03 +0000 | [diff] [blame] | 239 | BOOL bRet = FALSE; |
| 240 | PTHREAD_START_ROUTINE lpStartAddress; |
| 241 | HANDLE hThread; |
| 242 | DWORD hModule; |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 243 | |
| 244 | // Allocate memory in the target process to hold the DLL name |
José Fonseca | 209a66d | 2015-03-12 14:13:03 +0000 | [diff] [blame] | 245 | size_t szDllPathLength = strlen(szDllPath) + 1; |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 246 | void *lpMemory = VirtualAllocEx(hProcess, NULL, szDllPathLength, MEM_COMMIT, PAGE_READWRITE); |
| 247 | if (!lpMemory) { |
José Fonseca | 420d557 | 2014-09-12 09:12:58 +0100 | [diff] [blame] | 248 | logLastError("failed to allocate memory in the process"); |
José Fonseca | 209a66d | 2015-03-12 14:13:03 +0000 | [diff] [blame] | 249 | goto no_memory; |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 250 | } |
| 251 | |
| 252 | // Copy DLL name into the target process |
| 253 | if (!WriteProcessMemory(hProcess, lpMemory, szDllPath, szDllPathLength, NULL)) { |
José Fonseca | 420d557 | 2014-09-12 09:12:58 +0100 | [diff] [blame] | 254 | logLastError("failed to write into process memory"); |
José Fonseca | 209a66d | 2015-03-12 14:13:03 +0000 | [diff] [blame] | 255 | goto no_thread; |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 256 | } |
| 257 | |
| 258 | /* |
| 259 | * Get LoadLibraryA address from kernel32.dll. It's the same for all the |
| 260 | * process (XXX: but only within the same architecture). |
| 261 | */ |
José Fonseca | 209a66d | 2015-03-12 14:13:03 +0000 | [diff] [blame] | 262 | lpStartAddress = |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 263 | (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("KERNEL32"), "LoadLibraryA"); |
| 264 | |
| 265 | // Create remote thread in another process |
José Fonseca | 209a66d | 2015-03-12 14:13:03 +0000 | [diff] [blame] | 266 | hThread = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, lpMemory, 0, NULL); |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 267 | if (!hThread) { |
José Fonseca | 420d557 | 2014-09-12 09:12:58 +0100 | [diff] [blame] | 268 | logLastError("failed to create remote thread"); |
José Fonseca | 209a66d | 2015-03-12 14:13:03 +0000 | [diff] [blame] | 269 | goto no_thread; |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 270 | } |
| 271 | |
| 272 | // Wait for it to finish |
| 273 | WaitForSingleObject(hThread, INFINITE); |
| 274 | |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 275 | GetExitCodeThread(hThread, &hModule); |
| 276 | if (!hModule) { |
Jose Fonseca | 1ec8346 | 2015-07-30 15:14:44 +0100 | [diff] [blame] | 277 | debugPrintf("inject: error: failed to load %s into the remote process %lu\n", |
| 278 | szDllPath, GetProcessId(hProcess)); |
José Fonseca | 209a66d | 2015-03-12 14:13:03 +0000 | [diff] [blame] | 279 | } else { |
| 280 | bRet = TRUE; |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 281 | } |
| 282 | |
José Fonseca | 209a66d | 2015-03-12 14:13:03 +0000 | [diff] [blame] | 283 | CloseHandle(hThread); |
| 284 | no_thread: |
| 285 | VirtualFreeEx(hProcess, lpMemory, 0, MEM_RELEASE); |
| 286 | no_memory: |
| 287 | return bRet; |
José Fonseca | 51a7017 | 2014-06-19 13:23:14 +0100 | [diff] [blame] | 288 | } |