OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "sandbox/src/service_resolver.h" | |
6 | |
7 #include "base/memory/scoped_ptr.h" | |
8 #include "sandbox/src/sandbox_utils.h" | |
9 #include "sandbox/src/win_utils.h" | |
10 | |
11 namespace { | |
12 #pragma pack(push, 1) | |
13 | |
14 const BYTE kMovEax = 0xB8; | |
15 const BYTE kMovEdx = 0xBA; | |
16 const USHORT kMovEdxEsp = 0xD48B; | |
17 const USHORT kCallPtrEdx = 0x12FF; | |
18 const USHORT kCallEdx = 0xD2FF; | |
19 const BYTE kCallEip = 0xE8; | |
20 const BYTE kRet = 0xC2; | |
21 const BYTE kRet2 = 0xC3; | |
22 const BYTE kNop = 0x90; | |
23 const USHORT kJmpEdx = 0xE2FF; | |
24 const USHORT kXorEcx = 0xC933; | |
25 const ULONG kLeaEdx = 0x0424548D; | |
26 const ULONG kCallFs1 = 0xC015FF64; | |
27 const USHORT kCallFs2 = 0; | |
28 const BYTE kCallFs3 = 0; | |
29 const BYTE kAddEsp1 = 0x83; | |
30 const USHORT kAddEsp2 = 0x4C4; | |
31 const BYTE kJmp32 = 0xE9; | |
32 const USHORT kSysenter = 0x340F; | |
33 | |
34 const int kMaxService = 1000; | |
35 | |
36 // Service code for 32 bit systems. | |
37 // NOTE: on win2003 "call dword ptr [edx]" is "call edx". | |
38 struct ServiceEntry { | |
39 // This struct contains roughly the following code: | |
40 // 00 mov eax,25h | |
41 // 05 mov edx,offset SharedUserData!SystemCallStub (7ffe0300) | |
42 // 0a call dword ptr [edx] | |
43 // 0c ret 2Ch | |
44 // 0f nop | |
45 BYTE mov_eax; // = B8 | |
46 ULONG service_id; | |
47 BYTE mov_edx; // = BA | |
48 ULONG stub; | |
49 USHORT call_ptr_edx; // = FF 12 | |
50 BYTE ret; // = C2 | |
51 USHORT num_params; | |
52 BYTE nop; | |
53 }; | |
54 | |
55 // Service code for 32 bit Windows 8. | |
56 struct ServiceEntryW8 { | |
57 // This struct contains the following code: | |
58 // 00 b825000000 mov eax,25h | |
59 // 05 e803000000 call eip+3 | |
60 // 0a c22c00 ret 2Ch | |
61 // 0d 8bd4 mov edx,esp | |
62 // 0f 0f34 sysenter | |
63 // 11 c3 ret | |
64 // 12 8bff mov edi,edi | |
65 BYTE mov_eax; // = B8 | |
66 ULONG service_id; | |
67 BYTE call_eip; // = E8 | |
68 ULONG call_offset; | |
69 BYTE ret_p; // = C2 | |
70 USHORT num_params; | |
71 USHORT mov_edx_esp; // = BD D4 | |
72 USHORT sysenter; // = 0F 34 | |
73 BYTE ret; // = C3 | |
74 USHORT nop; | |
75 }; | |
76 | |
77 // Service code for a 32 bit process running on a 64 bit os. | |
78 struct Wow64Entry { | |
79 // This struct may contain one of two versions of code: | |
80 // 1. For XP, Vista and 2K3: | |
81 // 00 b825000000 mov eax, 25h | |
82 // 05 33c9 xor ecx, ecx | |
83 // 07 8d542404 lea edx, [esp + 4] | |
84 // 0b 64ff15c0000000 call dword ptr fs:[0C0h] | |
85 // 12 c22c00 ret 2Ch | |
86 // | |
87 // 2. For Windows 7: | |
88 // 00 b825000000 mov eax, 25h | |
89 // 05 33c9 xor ecx, ecx | |
90 // 07 8d542404 lea edx, [esp + 4] | |
91 // 0b 64ff15c0000000 call dword ptr fs:[0C0h] | |
92 // 12 83c404 add esp, 4 | |
93 // 15 c22c00 ret 2Ch | |
94 // | |
95 // So we base the structure on the bigger one: | |
96 BYTE mov_eax; // = B8 | |
97 ULONG service_id; | |
98 USHORT xor_ecx; // = 33 C9 | |
99 ULONG lea_edx; // = 8D 54 24 04 | |
100 ULONG call_fs1; // = 64 FF 15 C0 | |
101 USHORT call_fs2; // = 00 00 | |
102 BYTE call_fs3; // = 00 | |
103 BYTE add_esp1; // = 83 or ret | |
104 USHORT add_esp2; // = C4 04 or num_params | |
105 BYTE ret; // = C2 | |
106 USHORT num_params; | |
107 }; | |
108 | |
109 // Service code for a 32 bit process running on 64 bit Windows 8. | |
110 struct Wow64EntryW8 { | |
111 // 00 b825000000 mov eax, 25h | |
112 // 05 64ff15c0000000 call dword ptr fs:[0C0h] | |
113 // 0b c22c00 ret 2Ch | |
114 // 0f 90 nop | |
115 BYTE mov_eax; // = B8 | |
116 ULONG service_id; | |
117 ULONG call_fs1; // = 64 FF 15 C0 | |
118 USHORT call_fs2; // = 00 00 | |
119 BYTE call_fs3; // = 00 | |
120 BYTE ret; // = C2 | |
121 USHORT num_params; | |
122 BYTE nop; | |
123 }; | |
124 | |
125 // Make sure that relaxed patching works as expected. | |
126 const size_t kMinServiceSize = offsetof(ServiceEntry, ret); | |
127 COMPILE_ASSERT(sizeof(ServiceEntryW8) >= kMinServiceSize, wrong_service_len); | |
128 COMPILE_ASSERT(sizeof(Wow64Entry) >= kMinServiceSize, wrong_service_len); | |
129 COMPILE_ASSERT(sizeof(Wow64EntryW8) >= kMinServiceSize, wrong_service_len); | |
130 | |
131 struct ServiceFullThunk { | |
132 union { | |
133 ServiceEntry original; | |
134 ServiceEntryW8 original_w8; | |
135 Wow64Entry wow_64; | |
136 Wow64EntryW8 wow_64_w8; | |
137 }; | |
138 int internal_thunk; // Dummy member to the beginning of the internal thunk. | |
139 }; | |
140 | |
141 #pragma pack(pop) | |
142 | |
143 }; // namespace | |
144 | |
145 namespace sandbox { | |
146 | |
147 NTSTATUS ServiceResolverThunk::Setup(const void* target_module, | |
148 const void* interceptor_module, | |
149 const char* target_name, | |
150 const char* interceptor_name, | |
151 const void* interceptor_entry_point, | |
152 void* thunk_storage, | |
153 size_t storage_bytes, | |
154 size_t* storage_used) { | |
155 NTSTATUS ret = Init(target_module, interceptor_module, target_name, | |
156 interceptor_name, interceptor_entry_point, | |
157 thunk_storage, storage_bytes); | |
158 if (!NT_SUCCESS(ret)) | |
159 return ret; | |
160 | |
161 relative_jump_ = 0; | |
162 size_t thunk_bytes = GetThunkSize(); | |
163 scoped_array<char> thunk_buffer(new char[thunk_bytes]); | |
164 ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>( | |
165 thunk_buffer.get()); | |
166 | |
167 if (!IsFunctionAService(&thunk->original) && | |
168 (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) | |
169 return STATUS_UNSUCCESSFUL; | |
170 | |
171 ret = PerformPatch(thunk, thunk_storage); | |
172 | |
173 if (NULL != storage_used) | |
174 *storage_used = thunk_bytes; | |
175 | |
176 return ret; | |
177 } | |
178 | |
179 size_t ServiceResolverThunk::GetThunkSize() const { | |
180 return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize(); | |
181 } | |
182 | |
183 bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { | |
184 ServiceEntry function_code; | |
185 SIZE_T read; | |
186 if (!::ReadProcessMemory(process_, target_, &function_code, | |
187 sizeof(function_code), &read)) | |
188 return false; | |
189 | |
190 if (sizeof(function_code) != read) | |
191 return false; | |
192 | |
193 if (kMovEax != function_code.mov_eax || | |
194 kMovEdx != function_code.mov_edx || | |
195 (kCallPtrEdx != function_code.call_ptr_edx && | |
196 kCallEdx != function_code.call_ptr_edx) || | |
197 kRet != function_code.ret) | |
198 return false; | |
199 | |
200 // Find the system call pointer if we don't already have it. | |
201 if (kCallEdx != function_code.call_ptr_edx) { | |
202 DWORD ki_system_call; | |
203 if (!::ReadProcessMemory(process_, | |
204 bit_cast<const void*>(function_code.stub), | |
205 &ki_system_call, sizeof(ki_system_call), &read)) | |
206 return false; | |
207 | |
208 if (sizeof(ki_system_call) != read) | |
209 return false; | |
210 | |
211 HMODULE module_1, module_2; | |
212 // last check, call_stub should point to a KiXXSystemCall function on ntdll | |
213 if (!GetModuleHandleHelper(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | | |
214 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, | |
215 bit_cast<const wchar_t*>(ki_system_call), | |
216 &module_1)) | |
217 return false; | |
218 | |
219 if (NULL != ntdll_base_) { | |
220 // This path is only taken when running the unit tests. We want to be | |
221 // able to patch a buffer in memory, so target_ is not inside ntdll. | |
222 module_2 = ntdll_base_; | |
223 } else { | |
224 if (!GetModuleHandleHelper( | |
225 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | | |
226 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, | |
227 reinterpret_cast<const wchar_t*>(target_), | |
228 &module_2)) { | |
229 return false; | |
230 } | |
231 } | |
232 | |
233 if (module_1 != module_2) | |
234 return false; | |
235 } | |
236 | |
237 // Save the verified code | |
238 memcpy(local_thunk, &function_code, sizeof(function_code)); | |
239 | |
240 return true; | |
241 } | |
242 | |
243 NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, | |
244 void* remote_thunk) { | |
245 ServiceEntry intercepted_code; | |
246 size_t bytes_to_write = sizeof(intercepted_code); | |
247 ServiceFullThunk *full_local_thunk = reinterpret_cast<ServiceFullThunk*>( | |
248 local_thunk); | |
249 ServiceFullThunk *full_remote_thunk = reinterpret_cast<ServiceFullThunk*>( | |
250 remote_thunk); | |
251 | |
252 // patch the original code | |
253 memcpy(&intercepted_code, &full_local_thunk->original, | |
254 sizeof(intercepted_code)); | |
255 intercepted_code.mov_eax = kMovEax; | |
256 intercepted_code.service_id = full_local_thunk->original.service_id; | |
257 intercepted_code.mov_edx = kMovEdx; | |
258 intercepted_code.stub = bit_cast<ULONG>(&full_remote_thunk->internal_thunk); | |
259 intercepted_code.call_ptr_edx = kJmpEdx; | |
260 bytes_to_write = kMinServiceSize; | |
261 | |
262 if (relative_jump_) { | |
263 intercepted_code.mov_eax = kJmp32; | |
264 intercepted_code.service_id = relative_jump_; | |
265 bytes_to_write = offsetof(ServiceEntry, mov_edx); | |
266 } | |
267 | |
268 // setup the thunk | |
269 SetInternalThunk(&full_local_thunk->internal_thunk, GetInternalThunkSize(), | |
270 remote_thunk, interceptor_); | |
271 | |
272 size_t thunk_size = GetThunkSize(); | |
273 | |
274 // copy the local thunk buffer to the child | |
275 SIZE_T written; | |
276 if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, | |
277 thunk_size, &written)) | |
278 return STATUS_UNSUCCESSFUL; | |
279 | |
280 if (thunk_size != written) | |
281 return STATUS_UNSUCCESSFUL; | |
282 | |
283 // and now change the function to intercept, on the child | |
284 if (NULL != ntdll_base_) { | |
285 // running a unit test | |
286 if (!::WriteProcessMemory(process_, target_, &intercepted_code, | |
287 bytes_to_write, &written)) | |
288 return STATUS_UNSUCCESSFUL; | |
289 } else { | |
290 if (!WriteProtectedChildMemory(process_, target_, &intercepted_code, | |
291 bytes_to_write)) | |
292 return STATUS_UNSUCCESSFUL; | |
293 } | |
294 | |
295 return STATUS_SUCCESS; | |
296 } | |
297 | |
298 bool ServiceResolverThunk::SaveOriginalFunction(void* local_thunk, | |
299 void* remote_thunk) { | |
300 ServiceEntry function_code; | |
301 SIZE_T read; | |
302 if (!::ReadProcessMemory(process_, target_, &function_code, | |
303 sizeof(function_code), &read)) | |
304 return false; | |
305 | |
306 if (sizeof(function_code) != read) | |
307 return false; | |
308 | |
309 if (kJmp32 == function_code.mov_eax) { | |
310 // Plain old entry point patch. The relative jump address follows it. | |
311 ULONG relative = function_code.service_id; | |
312 | |
313 // First, fix our copy of their patch. | |
314 relative += bit_cast<ULONG>(target_) - bit_cast<ULONG>(remote_thunk); | |
315 | |
316 function_code.service_id = relative; | |
317 | |
318 // And now, remember how to re-patch it. | |
319 ServiceFullThunk *full_thunk = | |
320 reinterpret_cast<ServiceFullThunk*>(remote_thunk); | |
321 | |
322 const ULONG kJmp32Size = 5; | |
323 | |
324 relative_jump_ = bit_cast<ULONG>(&full_thunk->internal_thunk) - | |
325 bit_cast<ULONG>(target_) - kJmp32Size; | |
326 } | |
327 | |
328 // Save the verified code | |
329 memcpy(local_thunk, &function_code, sizeof(function_code)); | |
330 | |
331 return true; | |
332 } | |
333 | |
334 bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { | |
335 Wow64Entry function_code; | |
336 SIZE_T read; | |
337 if (!::ReadProcessMemory(process_, target_, &function_code, | |
338 sizeof(function_code), &read)) | |
339 return false; | |
340 | |
341 if (sizeof(function_code) != read) | |
342 return false; | |
343 | |
344 if (kMovEax != function_code.mov_eax || kXorEcx != function_code.xor_ecx || | |
345 kLeaEdx != function_code.lea_edx || kCallFs1 != function_code.call_fs1 || | |
346 kCallFs2 != function_code.call_fs2 || kCallFs3 != function_code.call_fs3) | |
347 return false; | |
348 | |
349 if ((kAddEsp1 == function_code.add_esp1 && | |
350 kAddEsp2 == function_code.add_esp2 && | |
351 kRet == function_code.ret) || kRet == function_code.add_esp1) { | |
352 // Save the verified code | |
353 memcpy(local_thunk, &function_code, sizeof(function_code)); | |
354 return true; | |
355 } | |
356 | |
357 return false; | |
358 } | |
359 | |
360 bool Wow64W8ResolverThunk::IsFunctionAService(void* local_thunk) const { | |
361 Wow64EntryW8 function_code; | |
362 SIZE_T read; | |
363 if (!::ReadProcessMemory(process_, target_, &function_code, | |
364 sizeof(function_code), &read)) | |
365 return false; | |
366 | |
367 if (sizeof(function_code) != read) | |
368 return false; | |
369 | |
370 if (kMovEax != function_code.mov_eax || kCallFs1 != function_code.call_fs1 || | |
371 kCallFs2 != function_code.call_fs2 || | |
372 kCallFs3 != function_code.call_fs3 || kRet != function_code.ret) { | |
373 return false; | |
374 } | |
375 | |
376 // Save the verified code | |
377 memcpy(local_thunk, &function_code, sizeof(function_code)); | |
378 return true; | |
379 } | |
380 | |
381 bool Win2kResolverThunk::IsFunctionAService(void* local_thunk) const { | |
382 ServiceEntry function_code; | |
383 SIZE_T read; | |
384 if (!::ReadProcessMemory(process_, target_, &function_code, | |
385 sizeof(function_code), &read)) | |
386 return false; | |
387 | |
388 if (sizeof(function_code) != read) | |
389 return false; | |
390 | |
391 if (kMovEax != function_code.mov_eax || | |
392 function_code.service_id > kMaxService) | |
393 return false; | |
394 | |
395 // Save the verified code | |
396 memcpy(local_thunk, &function_code, sizeof(function_code)); | |
397 | |
398 return true; | |
399 } | |
400 | |
401 bool Win8ResolverThunk::IsFunctionAService(void* local_thunk) const { | |
402 ServiceEntryW8 function_code; | |
403 SIZE_T read; | |
404 if (!::ReadProcessMemory(process_, target_, &function_code, | |
405 sizeof(function_code), &read)) | |
406 return false; | |
407 | |
408 if (sizeof(function_code) != read) | |
409 return false; | |
410 | |
411 if (kMovEax != function_code.mov_eax || kCallEip != function_code.call_eip || | |
412 function_code.call_offset != 3 || kRet != function_code.ret_p || | |
413 kMovEdxEsp != function_code.mov_edx_esp || | |
414 kSysenter != function_code.sysenter || kRet2 != function_code.ret) { | |
415 return false; | |
416 } | |
417 | |
418 // Save the verified code | |
419 memcpy(local_thunk, &function_code, sizeof(function_code)); | |
420 | |
421 return true; | |
422 } | |
423 | |
424 } // namespace sandbox | |
OLD | NEW |