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 |