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