• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
Setup(const void * target_module,const void * interceptor_module,const char * target_name,const char * interceptor_name,const void * interceptor_entry_point,void * thunk_storage,size_t storage_bytes,size_t * storage_used)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 
GetThunkSize() const178 size_t ServiceResolverThunk::GetThunkSize() const {
179   return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize();
180 }
181 
CopyThunk(const void * target_module,const char * target_name,BYTE * thunk_storage,size_t storage_bytes,size_t * storage_used)182 NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module,
183                                          const char* target_name,
184                                          BYTE* thunk_storage,
185                                          size_t storage_bytes,
186                                          size_t* storage_used) {
187   NTSTATUS ret = ResolveTarget(target_module, target_name, &target_);
188   if (!NT_SUCCESS(ret))
189     return ret;
190 
191   size_t thunk_bytes = GetThunkSize();
192   if (storage_bytes < thunk_bytes)
193     return STATUS_UNSUCCESSFUL;
194 
195   ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage);
196 
197   if (!IsFunctionAService(&thunk->original) &&
198       (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) {
199     return STATUS_UNSUCCESSFUL;
200   }
201 
202   if (NULL != storage_used)
203     *storage_used = thunk_bytes;
204 
205   return ret;
206 }
207 
IsFunctionAService(void * local_thunk) const208 bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const {
209   ServiceEntry function_code;
210   SIZE_T read;
211   if (!::ReadProcessMemory(process_, target_, &function_code,
212                            sizeof(function_code), &read))
213     return false;
214 
215   if (sizeof(function_code) != read)
216     return false;
217 
218   if (kMovEax != function_code.mov_eax ||
219       kMovEdx != function_code.mov_edx ||
220       (kCallPtrEdx != function_code.call_ptr_edx &&
221        kCallEdx != function_code.call_ptr_edx) ||
222       kRet != function_code.ret)
223     return false;
224 
225   // Find the system call pointer if we don't already have it.
226   if (kCallEdx != function_code.call_ptr_edx) {
227     DWORD ki_system_call;
228     if (!::ReadProcessMemory(process_,
229                              bit_cast<const void*>(function_code.stub),
230                              &ki_system_call, sizeof(ki_system_call), &read))
231       return false;
232 
233     if (sizeof(ki_system_call) != read)
234       return false;
235 
236     HMODULE module_1, module_2;
237     // last check, call_stub should point to a KiXXSystemCall function on ntdll
238     if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
239                                GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
240                            bit_cast<const wchar_t*>(ki_system_call), &module_1))
241       return false;
242 
243     if (NULL != ntdll_base_) {
244       // This path is only taken when running the unit tests. We want to be
245       // able to patch a buffer in memory, so target_ is not inside ntdll.
246       module_2 = ntdll_base_;
247     } else {
248       if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
249                                  GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
250                              reinterpret_cast<const wchar_t*>(target_),
251                              &module_2))
252         return false;
253     }
254 
255     if (module_1 != module_2)
256       return false;
257   }
258 
259   // Save the verified code
260   memcpy(local_thunk, &function_code, sizeof(function_code));
261 
262   return true;
263 }
264 
PerformPatch(void * local_thunk,void * remote_thunk)265 NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk,
266                                             void* remote_thunk) {
267   ServiceEntry intercepted_code;
268   size_t bytes_to_write = sizeof(intercepted_code);
269   ServiceFullThunk *full_local_thunk = reinterpret_cast<ServiceFullThunk*>(
270       local_thunk);
271   ServiceFullThunk *full_remote_thunk = reinterpret_cast<ServiceFullThunk*>(
272       remote_thunk);
273 
274   // patch the original code
275   memcpy(&intercepted_code, &full_local_thunk->original,
276          sizeof(intercepted_code));
277   intercepted_code.mov_eax = kMovEax;
278   intercepted_code.service_id = full_local_thunk->original.service_id;
279   intercepted_code.mov_edx = kMovEdx;
280   intercepted_code.stub = bit_cast<ULONG>(&full_remote_thunk->internal_thunk);
281   intercepted_code.call_ptr_edx = kJmpEdx;
282   bytes_to_write = kMinServiceSize;
283 
284   if (relative_jump_) {
285     intercepted_code.mov_eax = kJmp32;
286     intercepted_code.service_id = relative_jump_;
287     bytes_to_write = offsetof(ServiceEntry, mov_edx);
288   }
289 
290   // setup the thunk
291   SetInternalThunk(&full_local_thunk->internal_thunk, GetInternalThunkSize(),
292                    remote_thunk, interceptor_);
293 
294   size_t thunk_size = GetThunkSize();
295 
296   // copy the local thunk buffer to the child
297   SIZE_T written;
298   if (!::WriteProcessMemory(process_, remote_thunk, local_thunk,
299                             thunk_size, &written))
300     return STATUS_UNSUCCESSFUL;
301 
302   if (thunk_size != written)
303     return STATUS_UNSUCCESSFUL;
304 
305   // and now change the function to intercept, on the child
306   if (NULL != ntdll_base_) {
307     // running a unit test
308     if (!::WriteProcessMemory(process_, target_, &intercepted_code,
309                               bytes_to_write, &written))
310       return STATUS_UNSUCCESSFUL;
311   } else {
312     if (!WriteProtectedChildMemory(process_, target_, &intercepted_code,
313                                    bytes_to_write))
314       return STATUS_UNSUCCESSFUL;
315   }
316 
317   return STATUS_SUCCESS;
318 }
319 
SaveOriginalFunction(void * local_thunk,void * remote_thunk)320 bool ServiceResolverThunk::SaveOriginalFunction(void* local_thunk,
321                                                 void* remote_thunk) {
322   ServiceEntry function_code;
323   SIZE_T read;
324   if (!::ReadProcessMemory(process_, target_, &function_code,
325                            sizeof(function_code), &read))
326     return false;
327 
328   if (sizeof(function_code) != read)
329     return false;
330 
331   if (kJmp32 == function_code.mov_eax) {
332     // Plain old entry point patch. The relative jump address follows it.
333     ULONG relative = function_code.service_id;
334 
335     // First, fix our copy of their patch.
336     relative += bit_cast<ULONG>(target_) - bit_cast<ULONG>(remote_thunk);
337 
338     function_code.service_id = relative;
339 
340     // And now, remember how to re-patch it.
341     ServiceFullThunk *full_thunk =
342         reinterpret_cast<ServiceFullThunk*>(remote_thunk);
343 
344     const ULONG kJmp32Size = 5;
345 
346     relative_jump_ = bit_cast<ULONG>(&full_thunk->internal_thunk) -
347                      bit_cast<ULONG>(target_) - kJmp32Size;
348   }
349 
350   // Save the verified code
351   memcpy(local_thunk, &function_code, sizeof(function_code));
352 
353   return true;
354 }
355 
IsFunctionAService(void * local_thunk) const356 bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const {
357   Wow64Entry 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 || kXorEcx != function_code.xor_ecx ||
367       kLeaEdx != function_code.lea_edx || kCallFs1 != function_code.call_fs1 ||
368       kCallFs2 != function_code.call_fs2 || kCallFs3 != function_code.call_fs3)
369     return false;
370 
371   if ((kAddEsp1 == function_code.add_esp1 &&
372        kAddEsp2 == function_code.add_esp2 &&
373        kRet == function_code.ret) || kRet == function_code.add_esp1) {
374     // Save the verified code
375     memcpy(local_thunk, &function_code, sizeof(function_code));
376     return true;
377   }
378 
379   return false;
380 }
381 
IsFunctionAService(void * local_thunk) const382 bool Wow64W8ResolverThunk::IsFunctionAService(void* local_thunk) const {
383   Wow64EntryW8 function_code;
384   SIZE_T read;
385   if (!::ReadProcessMemory(process_, target_, &function_code,
386                            sizeof(function_code), &read))
387     return false;
388 
389   if (sizeof(function_code) != read)
390     return false;
391 
392   if (kMovEax != function_code.mov_eax || kCallFs1 != function_code.call_fs1 ||
393       kCallFs2 != function_code.call_fs2 ||
394       kCallFs3 != function_code.call_fs3 || kRet != function_code.ret) {
395     return false;
396   }
397 
398   // Save the verified code
399   memcpy(local_thunk, &function_code, sizeof(function_code));
400   return true;
401 }
402 
IsFunctionAService(void * local_thunk) const403 bool Win8ResolverThunk::IsFunctionAService(void* local_thunk) const {
404   ServiceEntryW8 function_code;
405   SIZE_T read;
406   if (!::ReadProcessMemory(process_, target_, &function_code,
407                            sizeof(function_code), &read))
408     return false;
409 
410   if (sizeof(function_code) != read)
411     return false;
412 
413   if (kMovEax != function_code.mov_eax || kCallEip != function_code.call_eip ||
414       function_code.call_offset != 3 || kRet != function_code.ret_p ||
415       kMovEdxEsp != function_code.mov_edx_esp ||
416       kSysenter != function_code.sysenter || kRet2 != function_code.ret) {
417     return false;
418   }
419 
420   // Save the verified code
421   memcpy(local_thunk, &function_code, sizeof(function_code));
422 
423   return true;
424 }
425 
426 }  // namespace sandbox
427