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
10namespace {
11#pragma pack(push, 1)
12
13const BYTE kMovEax = 0xB8;
14const BYTE kMovEdx = 0xBA;
15const USHORT kMovEdxEsp = 0xD48B;
16const USHORT kCallPtrEdx = 0x12FF;
17const USHORT kCallEdx = 0xD2FF;
18const BYTE kCallEip = 0xE8;
19const BYTE kRet = 0xC2;
20const BYTE kRet2 = 0xC3;
21const BYTE kNop = 0x90;
22const USHORT kJmpEdx = 0xE2FF;
23const USHORT kXorEcx = 0xC933;
24const ULONG kLeaEdx = 0x0424548D;
25const ULONG kCallFs1 = 0xC015FF64;
26const USHORT kCallFs2 = 0;
27const BYTE kCallFs3 = 0;
28const BYTE kAddEsp1 = 0x83;
29const USHORT kAddEsp2 = 0x4C4;
30const BYTE kJmp32 = 0xE9;
31const USHORT kSysenter = 0x340F;
32
33const int kMaxService = 1000;
34
35// Service code for 32 bit systems.
36// NOTE: on win2003 "call dword ptr [edx]" is "call edx".
37struct 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.
55struct 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.
77struct 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.
109struct 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.
125const size_t kMinServiceSize = offsetof(ServiceEntry, ret);
126COMPILE_ASSERT(sizeof(ServiceEntryW8) >= kMinServiceSize, wrong_service_len);
127COMPILE_ASSERT(sizeof(Wow64Entry) >= kMinServiceSize, wrong_service_len);
128COMPILE_ASSERT(sizeof(Wow64EntryW8) >= kMinServiceSize, wrong_service_len);
129
130struct 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
144namespace sandbox {
145
146NTSTATUS 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
178size_t ServiceResolverThunk::GetThunkSize() const {
179  return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize();
180}
181
182NTSTATUS 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
208bool 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
265NTSTATUS 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
320bool 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
356bool 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
382bool 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
403bool 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