|
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 "base/memory/scoped_ptr.h" |
|
8 #include "sandbox/win/src/win_utils.h" |
|
9 |
|
10 namespace { |
|
11 #pragma pack(push, 1) |
|
12 |
|
13 const BYTE kMovEax = 0xB8; |
|
14 const BYTE kMovEdx = 0xBA; |
|
15 const USHORT kMovEdxEsp = 0xD48B; |
|
16 const USHORT kCallPtrEdx = 0x12FF; |
|
17 const USHORT kCallEdx = 0xD2FF; |
|
18 const BYTE kCallEip = 0xE8; |
|
19 const BYTE kRet = 0xC2; |
|
20 const BYTE kRet2 = 0xC3; |
|
21 const BYTE kNop = 0x90; |
|
22 const USHORT kJmpEdx = 0xE2FF; |
|
23 const USHORT kXorEcx = 0xC933; |
|
24 const ULONG kLeaEdx = 0x0424548D; |
|
25 const ULONG kCallFs1 = 0xC015FF64; |
|
26 const USHORT kCallFs2 = 0; |
|
27 const BYTE kCallFs3 = 0; |
|
28 const BYTE kAddEsp1 = 0x83; |
|
29 const USHORT kAddEsp2 = 0x4C4; |
|
30 const BYTE kJmp32 = 0xE9; |
|
31 const USHORT kSysenter = 0x340F; |
|
32 |
|
33 const int kMaxService = 1000; |
|
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 // Make sure that relaxed patching works as expected. |
|
125 const size_t kMinServiceSize = offsetof(ServiceEntry, ret); |
|
126 COMPILE_ASSERT(sizeof(ServiceEntryW8) >= kMinServiceSize, wrong_service_len); |
|
127 COMPILE_ASSERT(sizeof(Wow64Entry) >= kMinServiceSize, wrong_service_len); |
|
128 COMPILE_ASSERT(sizeof(Wow64EntryW8) >= kMinServiceSize, wrong_service_len); |
|
129 |
|
130 struct ServiceFullThunk { |
|
131 union { |
|
132 ServiceEntry original; |
|
133 ServiceEntryW8 original_w8; |
|
134 Wow64Entry wow_64; |
|
135 Wow64EntryW8 wow_64_w8; |
|
136 }; |
|
137 int internal_thunk; // Dummy member to the beginning of the internal thunk. |
|
138 }; |
|
139 |
|
140 #pragma pack(pop) |
|
141 |
|
142 }; // namespace |
|
143 |
|
144 namespace sandbox { |
|
145 |
|
146 NTSTATUS ServiceResolverThunk::Setup(const void* target_module, |
|
147 const void* interceptor_module, |
|
148 const char* target_name, |
|
149 const char* interceptor_name, |
|
150 const void* interceptor_entry_point, |
|
151 void* thunk_storage, |
|
152 size_t storage_bytes, |
|
153 size_t* storage_used) { |
|
154 NTSTATUS ret = Init(target_module, interceptor_module, target_name, |
|
155 interceptor_name, interceptor_entry_point, |
|
156 thunk_storage, storage_bytes); |
|
157 if (!NT_SUCCESS(ret)) |
|
158 return ret; |
|
159 |
|
160 relative_jump_ = 0; |
|
161 size_t thunk_bytes = GetThunkSize(); |
|
162 scoped_ptr<char[]> thunk_buffer(new char[thunk_bytes]); |
|
163 ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>( |
|
164 thunk_buffer.get()); |
|
165 |
|
166 if (!IsFunctionAService(&thunk->original) && |
|
167 (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) |
|
168 return STATUS_UNSUCCESSFUL; |
|
169 |
|
170 ret = PerformPatch(thunk, thunk_storage); |
|
171 |
|
172 if (NULL != storage_used) |
|
173 *storage_used = thunk_bytes; |
|
174 |
|
175 return ret; |
|
176 } |
|
177 |
|
178 size_t ServiceResolverThunk::GetThunkSize() const { |
|
179 return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize(); |
|
180 } |
|
181 |
|
182 bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { |
|
183 ServiceEntry function_code; |
|
184 SIZE_T read; |
|
185 if (!::ReadProcessMemory(process_, target_, &function_code, |
|
186 sizeof(function_code), &read)) |
|
187 return false; |
|
188 |
|
189 if (sizeof(function_code) != read) |
|
190 return false; |
|
191 |
|
192 if (kMovEax != function_code.mov_eax || |
|
193 kMovEdx != function_code.mov_edx || |
|
194 (kCallPtrEdx != function_code.call_ptr_edx && |
|
195 kCallEdx != function_code.call_ptr_edx) || |
|
196 kRet != function_code.ret) |
|
197 return false; |
|
198 |
|
199 // Find the system call pointer if we don't already have it. |
|
200 if (kCallEdx != function_code.call_ptr_edx) { |
|
201 DWORD ki_system_call; |
|
202 if (!::ReadProcessMemory(process_, |
|
203 bit_cast<const void*>(function_code.stub), |
|
204 &ki_system_call, sizeof(ki_system_call), &read)) |
|
205 return false; |
|
206 |
|
207 if (sizeof(ki_system_call) != read) |
|
208 return false; |
|
209 |
|
210 HMODULE module_1, module_2; |
|
211 // last check, call_stub should point to a KiXXSystemCall function on ntdll |
|
212 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | |
|
213 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, |
|
214 bit_cast<const wchar_t*>(ki_system_call), &module_1)) |
|
215 return false; |
|
216 |
|
217 if (NULL != ntdll_base_) { |
|
218 // This path is only taken when running the unit tests. We want to be |
|
219 // able to patch a buffer in memory, so target_ is not inside ntdll. |
|
220 module_2 = ntdll_base_; |
|
221 } else { |
|
222 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | |
|
223 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, |
|
224 reinterpret_cast<const wchar_t*>(target_), |
|
225 &module_2)) |
|
226 return false; |
|
227 } |
|
228 |
|
229 if (module_1 != module_2) |
|
230 return false; |
|
231 } |
|
232 |
|
233 // Save the verified code |
|
234 memcpy(local_thunk, &function_code, sizeof(function_code)); |
|
235 |
|
236 return true; |
|
237 } |
|
238 |
|
239 NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, |
|
240 void* remote_thunk) { |
|
241 ServiceEntry intercepted_code; |
|
242 size_t bytes_to_write = sizeof(intercepted_code); |
|
243 ServiceFullThunk *full_local_thunk = reinterpret_cast<ServiceFullThunk*>( |
|
244 local_thunk); |
|
245 ServiceFullThunk *full_remote_thunk = reinterpret_cast<ServiceFullThunk*>( |
|
246 remote_thunk); |
|
247 |
|
248 // patch the original code |
|
249 memcpy(&intercepted_code, &full_local_thunk->original, |
|
250 sizeof(intercepted_code)); |
|
251 intercepted_code.mov_eax = kMovEax; |
|
252 intercepted_code.service_id = full_local_thunk->original.service_id; |
|
253 intercepted_code.mov_edx = kMovEdx; |
|
254 intercepted_code.stub = bit_cast<ULONG>(&full_remote_thunk->internal_thunk); |
|
255 intercepted_code.call_ptr_edx = kJmpEdx; |
|
256 bytes_to_write = kMinServiceSize; |
|
257 |
|
258 if (relative_jump_) { |
|
259 intercepted_code.mov_eax = kJmp32; |
|
260 intercepted_code.service_id = relative_jump_; |
|
261 bytes_to_write = offsetof(ServiceEntry, mov_edx); |
|
262 } |
|
263 |
|
264 // setup the thunk |
|
265 SetInternalThunk(&full_local_thunk->internal_thunk, GetInternalThunkSize(), |
|
266 remote_thunk, interceptor_); |
|
267 |
|
268 size_t thunk_size = GetThunkSize(); |
|
269 |
|
270 // copy the local thunk buffer to the child |
|
271 SIZE_T written; |
|
272 if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, |
|
273 thunk_size, &written)) |
|
274 return STATUS_UNSUCCESSFUL; |
|
275 |
|
276 if (thunk_size != written) |
|
277 return STATUS_UNSUCCESSFUL; |
|
278 |
|
279 // and now change the function to intercept, on the child |
|
280 if (NULL != ntdll_base_) { |
|
281 // running a unit test |
|
282 if (!::WriteProcessMemory(process_, target_, &intercepted_code, |
|
283 bytes_to_write, &written)) |
|
284 return STATUS_UNSUCCESSFUL; |
|
285 } else { |
|
286 if (!WriteProtectedChildMemory(process_, target_, &intercepted_code, |
|
287 bytes_to_write)) |
|
288 return STATUS_UNSUCCESSFUL; |
|
289 } |
|
290 |
|
291 return STATUS_SUCCESS; |
|
292 } |
|
293 |
|
294 bool ServiceResolverThunk::SaveOriginalFunction(void* local_thunk, |
|
295 void* remote_thunk) { |
|
296 ServiceEntry function_code; |
|
297 SIZE_T read; |
|
298 if (!::ReadProcessMemory(process_, target_, &function_code, |
|
299 sizeof(function_code), &read)) |
|
300 return false; |
|
301 |
|
302 if (sizeof(function_code) != read) |
|
303 return false; |
|
304 |
|
305 if (kJmp32 == function_code.mov_eax) { |
|
306 // Plain old entry point patch. The relative jump address follows it. |
|
307 ULONG relative = function_code.service_id; |
|
308 |
|
309 // First, fix our copy of their patch. |
|
310 relative += bit_cast<ULONG>(target_) - bit_cast<ULONG>(remote_thunk); |
|
311 |
|
312 function_code.service_id = relative; |
|
313 |
|
314 // And now, remember how to re-patch it. |
|
315 ServiceFullThunk *full_thunk = |
|
316 reinterpret_cast<ServiceFullThunk*>(remote_thunk); |
|
317 |
|
318 const ULONG kJmp32Size = 5; |
|
319 |
|
320 relative_jump_ = bit_cast<ULONG>(&full_thunk->internal_thunk) - |
|
321 bit_cast<ULONG>(target_) - kJmp32Size; |
|
322 } |
|
323 |
|
324 // Save the verified code |
|
325 memcpy(local_thunk, &function_code, sizeof(function_code)); |
|
326 |
|
327 return true; |
|
328 } |
|
329 |
|
330 bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { |
|
331 Wow64Entry function_code; |
|
332 SIZE_T read; |
|
333 if (!::ReadProcessMemory(process_, target_, &function_code, |
|
334 sizeof(function_code), &read)) |
|
335 return false; |
|
336 |
|
337 if (sizeof(function_code) != read) |
|
338 return false; |
|
339 |
|
340 if (kMovEax != function_code.mov_eax || kXorEcx != function_code.xor_ecx || |
|
341 kLeaEdx != function_code.lea_edx || kCallFs1 != function_code.call_fs1 || |
|
342 kCallFs2 != function_code.call_fs2 || kCallFs3 != function_code.call_fs3) |
|
343 return false; |
|
344 |
|
345 if ((kAddEsp1 == function_code.add_esp1 && |
|
346 kAddEsp2 == function_code.add_esp2 && |
|
347 kRet == function_code.ret) || kRet == function_code.add_esp1) { |
|
348 // Save the verified code |
|
349 memcpy(local_thunk, &function_code, sizeof(function_code)); |
|
350 return true; |
|
351 } |
|
352 |
|
353 return false; |
|
354 } |
|
355 |
|
356 bool Wow64W8ResolverThunk::IsFunctionAService(void* local_thunk) const { |
|
357 Wow64EntryW8 function_code; |
|
358 SIZE_T read; |
|
359 if (!::ReadProcessMemory(process_, target_, &function_code, |
|
360 sizeof(function_code), &read)) |
|
361 return false; |
|
362 |
|
363 if (sizeof(function_code) != read) |
|
364 return false; |
|
365 |
|
366 if (kMovEax != function_code.mov_eax || kCallFs1 != function_code.call_fs1 || |
|
367 kCallFs2 != function_code.call_fs2 || |
|
368 kCallFs3 != function_code.call_fs3 || kRet != function_code.ret) { |
|
369 return false; |
|
370 } |
|
371 |
|
372 // Save the verified code |
|
373 memcpy(local_thunk, &function_code, sizeof(function_code)); |
|
374 return true; |
|
375 } |
|
376 |
|
377 bool Win2kResolverThunk::IsFunctionAService(void* local_thunk) const { |
|
378 ServiceEntry function_code; |
|
379 SIZE_T read; |
|
380 if (!::ReadProcessMemory(process_, target_, &function_code, |
|
381 sizeof(function_code), &read)) |
|
382 return false; |
|
383 |
|
384 if (sizeof(function_code) != read) |
|
385 return false; |
|
386 |
|
387 if (kMovEax != function_code.mov_eax || |
|
388 function_code.service_id > kMaxService) |
|
389 return false; |
|
390 |
|
391 // Save the verified code |
|
392 memcpy(local_thunk, &function_code, sizeof(function_code)); |
|
393 |
|
394 return true; |
|
395 } |
|
396 |
|
397 bool Win8ResolverThunk::IsFunctionAService(void* local_thunk) const { |
|
398 ServiceEntryW8 function_code; |
|
399 SIZE_T read; |
|
400 if (!::ReadProcessMemory(process_, target_, &function_code, |
|
401 sizeof(function_code), &read)) |
|
402 return false; |
|
403 |
|
404 if (sizeof(function_code) != read) |
|
405 return false; |
|
406 |
|
407 if (kMovEax != function_code.mov_eax || kCallEip != function_code.call_eip || |
|
408 function_code.call_offset != 3 || kRet != function_code.ret_p || |
|
409 kMovEdxEsp != function_code.mov_edx_esp || |
|
410 kSysenter != function_code.sysenter || kRet2 != function_code.ret) { |
|
411 return false; |
|
412 } |
|
413 |
|
414 // Save the verified code |
|
415 memcpy(local_thunk, &function_code, sizeof(function_code)); |
|
416 |
|
417 return true; |
|
418 } |
|
419 |
|
420 } // namespace sandbox |