| OLD | NEW |
| (Empty) |
| 1 // Copyright 2008-2009 Google Inc. | |
| 2 // | |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 // you may not use this file except in compliance with the License. | |
| 5 // You may obtain a copy of the License at | |
| 6 // | |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 // | |
| 9 // Unless required by applicable law or agreed to in writing, software | |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 // See the License for the specific language governing permissions and | |
| 13 // limitations under the License. | |
| 14 // ======================================================================== | |
| 15 | |
| 16 #include "omaha/tools/goopdump/process_commandline.h" | |
| 17 | |
| 18 #include "omaha/common/debug.h" | |
| 19 #include "omaha/common/error.h" | |
| 20 #include "omaha/common/scoped_any.h" | |
| 21 #include "omaha/common/utils.h" | |
| 22 | |
| 23 namespace omaha { | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 typedef const wchar_t* (__stdcall *GetCommandLineWFunc)(); | |
| 28 | |
| 29 const DWORD kMaxInjectSize = 4096; | |
| 30 const DWORD kCmdLineBufferSize = 2000; | |
| 31 | |
| 32 struct SharedBlock { | |
| 33 DWORD last_error; // Last error from remote thread. | |
| 34 GetCommandLineWFunc get_commandline_w_ptr; // Address of GetCommandLineW(). | |
| 35 wchar_t cmd_line_buffer[kCmdLineBufferSize]; // The command line buffer. | |
| 36 }; | |
| 37 | |
| 38 // A number of assumptions are made: | |
| 39 // * The target process is a Win32 process. | |
| 40 // * Kernel32.dll is loaded at same address in each process (safe). | |
| 41 // * InjectFunction() is shorter than kMaxInjectSize | |
| 42 // * InjectFunction() does not rely on the C/C++ runtime. | |
| 43 // * Compiler option /GZ is not used. (If it is, the compiler generates calls to | |
| 44 // functions which are not injected into the target. The runtime_checks() | |
| 45 // pragma below removes this for debug builds which will have /GZ enabled by | |
| 46 // default. | |
| 47 | |
| 48 #pragma runtime_checks("", off) | |
| 49 | |
| 50 DWORD __stdcall InjectFunction(SharedBlock* shared_block) { | |
| 51 const wchar_t* source = shared_block->get_commandline_w_ptr(); | |
| 52 wchar_t* target = &shared_block->cmd_line_buffer[0]; | |
| 53 wchar_t* end = &shared_block->cmd_line_buffer[ | |
| 54 arraysize(shared_block->cmd_line_buffer) - 1]; | |
| 55 if (source == 0 || target == 0 || end == 0) { | |
| 56 shared_block->last_error = ERROR_INVALID_DATA; | |
| 57 return 0; | |
| 58 } | |
| 59 | |
| 60 // This is effectively a wcscpy but we can't use library functions since this | |
| 61 // code will be injected into the target process space and we can't make | |
| 62 // assumptions about what's linked into that process. | |
| 63 for (; *source != 0 && target < end; ++source, ++target) { | |
| 64 *target = *source; | |
| 65 } | |
| 66 | |
| 67 *target = 0; | |
| 68 shared_block->last_error = 0; | |
| 69 | |
| 70 return 0; | |
| 71 } | |
| 72 | |
| 73 #pragma runtime_checks("", restore) | |
| 74 | |
| 75 // Internal helper function to deal with the logic of injecting the | |
| 76 // function/data into the process without the memory alloc/free since we don't | |
| 77 // have smart pointers to handle VirtualAllocEx. | |
| 78 HRESULT GetCommandLineFromHandleWithMemory(HANDLE process_handle, | |
| 79 void* function_memory, | |
| 80 SharedBlock* shared_block, | |
| 81 CString* command_line) { | |
| 82 ASSERT1(process_handle); | |
| 83 ASSERT1(function_memory); | |
| 84 ASSERT1(shared_block); | |
| 85 | |
| 86 // Copy function into other process. | |
| 87 if (!::WriteProcessMemory(process_handle, | |
| 88 function_memory, | |
| 89 &InjectFunction, | |
| 90 kMaxInjectSize, | |
| 91 0)) { | |
| 92 return HRESULTFromLastError(); | |
| 93 } | |
| 94 | |
| 95 // Initialize data area for remote process. | |
| 96 scoped_library hmodule_kernel32(::LoadLibrary(L"kernel32.dll")); | |
| 97 if (!hmodule_kernel32) { | |
| 98 return HRESULTFromLastError(); | |
| 99 } | |
| 100 | |
| 101 SharedBlock shared_block_local; | |
| 102 shared_block_local.last_error = 0; | |
| 103 if (!GPA(get(hmodule_kernel32), | |
| 104 "GetCommandLineW", | |
| 105 &shared_block_local.get_commandline_w_ptr)) { | |
| 106 return HRESULTFromLastError(); | |
| 107 } | |
| 108 | |
| 109 shared_block_local.cmd_line_buffer[0] = L'\0'; | |
| 110 | |
| 111 if (!::WriteProcessMemory(process_handle, | |
| 112 shared_block, | |
| 113 &shared_block_local, | |
| 114 sizeof(shared_block_local), | |
| 115 0)) { | |
| 116 return HRESULTFromLastError(); | |
| 117 } | |
| 118 | |
| 119 // Create the remote thread. | |
| 120 scoped_handle remote_thread; | |
| 121 reset(remote_thread, ::CreateRemoteThread( | |
| 122 process_handle, | |
| 123 0, | |
| 124 0, | |
| 125 reinterpret_cast<LPTHREAD_START_ROUTINE>(function_memory), | |
| 126 shared_block, | |
| 127 0, | |
| 128 NULL)); | |
| 129 if (!remote_thread) { | |
| 130 return HRESULTFromLastError(); | |
| 131 } | |
| 132 | |
| 133 const DWORD kWaitTimeoutMs = 3000; | |
| 134 DWORD wait_result = ::WaitForSingleObject(get(remote_thread), kWaitTimeoutMs); | |
| 135 switch (wait_result) { | |
| 136 case WAIT_TIMEOUT: | |
| 137 return HRESULT_FROM_WIN32(ERROR_TIMEOUT); | |
| 138 case WAIT_FAILED: | |
| 139 return HRESULTFromLastError(); | |
| 140 case WAIT_OBJECT_0: | |
| 141 // This might just have worked, pick up the result. | |
| 142 if (!::ReadProcessMemory(process_handle, | |
| 143 shared_block, | |
| 144 &shared_block_local, | |
| 145 sizeof(shared_block_local), | |
| 146 0)) { | |
| 147 return HRESULTFromLastError(); | |
| 148 } | |
| 149 if (shared_block_local.last_error == 0) { | |
| 150 *command_line = shared_block_local.cmd_line_buffer; | |
| 151 } else { | |
| 152 return HRESULT_FROM_WIN32(shared_block_local.last_error); | |
| 153 } | |
| 154 break; | |
| 155 default: | |
| 156 return HRESULTFromLastError(); | |
| 157 } | |
| 158 | |
| 159 return S_OK; | |
| 160 } | |
| 161 | |
| 162 // Allocates memory in a remote process given the process handle. | |
| 163 // Returns a block of memory for the function that will be injected and a block | |
| 164 // of memory to store results in that we copy back out. | |
| 165 HRESULT AllocateProcessMemory(HANDLE process_handle, | |
| 166 void** function_memory, | |
| 167 void** shared_block) { | |
| 168 ASSERT1(process_handle); | |
| 169 ASSERT1(function_memory); | |
| 170 ASSERT1(shared_block); | |
| 171 | |
| 172 *function_memory = ::VirtualAllocEx(process_handle, | |
| 173 0, | |
| 174 kMaxInjectSize, | |
| 175 MEM_COMMIT, | |
| 176 PAGE_EXECUTE_READWRITE); | |
| 177 if (!(*function_memory)) { | |
| 178 return HRESULTFromLastError(); | |
| 179 } | |
| 180 | |
| 181 *shared_block = ::VirtualAllocEx(process_handle, | |
| 182 0, | |
| 183 sizeof(SharedBlock), | |
| 184 MEM_COMMIT, | |
| 185 PAGE_READWRITE); | |
| 186 if (!(*shared_block)) { | |
| 187 ::VirtualFreeEx(process_handle, *function_memory, 0, MEM_RELEASE); | |
| 188 *function_memory = NULL; | |
| 189 return HRESULTFromLastError(); | |
| 190 } | |
| 191 | |
| 192 return S_OK; | |
| 193 } | |
| 194 | |
| 195 HRESULT GetCommandLineFromHandle(HANDLE process_handle, CString* command_line) { | |
| 196 ASSERT1(command_line); | |
| 197 ASSERT1(process_handle != NULL); | |
| 198 | |
| 199 void* function_memory = NULL; | |
| 200 void* shared_block = NULL; | |
| 201 | |
| 202 HRESULT hr = AllocateProcessMemory(process_handle, | |
| 203 &function_memory, | |
| 204 &shared_block); | |
| 205 | |
| 206 if (SUCCEEDED(hr) && function_memory && shared_block) { | |
| 207 hr = GetCommandLineFromHandleWithMemory( | |
| 208 process_handle, | |
| 209 function_memory, | |
| 210 reinterpret_cast<SharedBlock*>(shared_block), | |
| 211 command_line); | |
| 212 } | |
| 213 | |
| 214 if (function_memory) { | |
| 215 ::VirtualFreeEx(process_handle, function_memory, 0, MEM_RELEASE); | |
| 216 function_memory = NULL; | |
| 217 } | |
| 218 | |
| 219 if (shared_block) { | |
| 220 ::VirtualFreeEx(process_handle, shared_block, 0, MEM_RELEASE); | |
| 221 shared_block = NULL; | |
| 222 } | |
| 223 | |
| 224 return hr; | |
| 225 } | |
| 226 | |
| 227 } // namespace | |
| 228 | |
| 229 HRESULT GetProcessCommandLine(DWORD process_id, CString* command_line) { | |
| 230 ASSERT1(command_line); | |
| 231 | |
| 232 EnableDebugPrivilege(); | |
| 233 scoped_handle process_handle; | |
| 234 reset(process_handle, ::OpenProcess(PROCESS_CREATE_THREAD | | |
| 235 PROCESS_VM_OPERATION | | |
| 236 PROCESS_VM_WRITE | | |
| 237 PROCESS_VM_READ, | |
| 238 FALSE, | |
| 239 process_id)); | |
| 240 if (!valid(process_handle)) { | |
| 241 return HRESULTFromLastError(); | |
| 242 } | |
| 243 return GetCommandLineFromHandle(get(process_handle), command_line); | |
| 244 } | |
| 245 | |
| 246 bool EnableDebugPrivilege() { | |
| 247 scoped_handle token; | |
| 248 | |
| 249 if (!::OpenProcessToken(::GetCurrentProcess(), | |
| 250 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, | |
| 251 address(token))) { | |
| 252 return false; | |
| 253 } | |
| 254 | |
| 255 LUID se_debug_name_value = {0}; | |
| 256 if (!::LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &se_debug_name_value)) { | |
| 257 return false; | |
| 258 } | |
| 259 | |
| 260 TOKEN_PRIVILEGES token_privs = {0}; | |
| 261 token_privs.PrivilegeCount = 1; | |
| 262 token_privs.Privileges[0].Luid = se_debug_name_value; | |
| 263 token_privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
| 264 | |
| 265 if (!::AdjustTokenPrivileges(get(token), | |
| 266 FALSE, | |
| 267 &token_privs, | |
| 268 sizeof(token_privs), | |
| 269 NULL, | |
| 270 NULL)) { | |
| 271 return false; | |
| 272 } | |
| 273 | |
| 274 return true; | |
| 275 } | |
| 276 | |
| 277 } // namespace omaha | |
| 278 | |
| OLD | NEW |