summaryrefslogtreecommitdiff
path: root/chromium/sandbox/win/wow_helper/service64_resolver.cc
blob: 033b9d771e1519c3c18edc4365c515b261a72fe5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sandbox/win/wow_helper/service64_resolver.h"

#include "base/memory/scoped_ptr.h"
#include "sandbox/win/wow_helper/target_code.h"

namespace {
#pragma pack(push, 1)

const BYTE kMovEax = 0xB8;
const BYTE kMovEdx = 0xBA;
const USHORT kCallPtrEdx = 0x12FF;
const BYTE kRet = 0xC2;
const BYTE kNop = 0x90;
const USHORT kJmpEdx = 0xE2FF;
const USHORT kXorEcx = 0xC933;
const ULONG kLeaEdx = 0x0424548D;
const ULONG kCallFs1 = 0xC015FF64;
const ULONG kCallFs2Ret = 0xC2000000;
const BYTE kPopEdx = 0x5A;
const BYTE kPushEdx = 0x52;
const BYTE kPush32 = 0x68;

const ULONG kMmovR10EcxMovEax = 0xB8D18B4C;
const USHORT kSyscall = 0x050F;
const BYTE kRetNp = 0xC3;
const BYTE kPad = 0x66;
const USHORT kNop16 = 0x9066;
const BYTE kRelJmp = 0xE9;

const ULONG kXorRaxMovEax = 0xB8C03148;
const ULONG kSaveRcx = 0x10488948;
const ULONG kMovRcxRaxJmp = 0xE9C88B48;

// Service code for 64 bit systems.
struct ServiceEntry {
  // this struct contains roughly the following code:
  // mov     r10,rcx
  // mov     eax,52h
  // syscall
  // ret
  // xchg    ax,ax
  // xchg    ax,ax

  ULONG mov_r10_ecx_mov_eax;  // = 4C 8B D1 B8
  ULONG service_id;
  USHORT syscall;             // = 0F 05
  BYTE ret;                   // = C3
  BYTE pad;                   // = 66
  USHORT xchg_ax_ax1;         // = 66 90
  USHORT xchg_ax_ax2;         // = 66 90
};

struct Redirected {
  // this struct contains roughly the following code:
  // jmp    relative_32
  // xchg   ax,ax       // 3 byte nop

  Redirected() {
    jmp = kRelJmp;
    relative = 0;
    pad = kPad;
    xchg_ax_ax = kNop16;
  };
  BYTE jmp;             // = E9
  ULONG relative;
  BYTE pad;             // = 66
  USHORT xchg_ax_ax;    // = 66 90
};

struct InternalThunk {
  // this struct contains roughly the following code:
  // xor rax,rax
  // mov eax, 0x00080000              // Thunk storage.
  // mov [rax]PatchInfo.service, rcx  // Save first argument.
  // mov rcx, rax
  // jmp relative_to_interceptor

  InternalThunk() {
    xor_rax_mov_eax = kXorRaxMovEax;
    patch_info = 0;
    save_rcx = kSaveRcx;
    mov_rcx_rax_jmp = kMovRcxRaxJmp;
    relative = 0;
  };
  ULONG xor_rax_mov_eax;  // = 48 31 C0 B8
  ULONG patch_info;
  ULONG save_rcx;         // = 48 89 48 10
  ULONG mov_rcx_rax_jmp;  // = 48 8b c8 e9
  ULONG relative;
};

struct ServiceFullThunk {
  sandbox::PatchInfo patch_info;
  ServiceEntry original;
  InternalThunk internal_thunk;
};

#pragma pack(pop)

// Simple utility function to write to a buffer on the child, if the memery has
// write protection attributes.
// Arguments:
// child_process (in): process to write to.
// address (out): memory position on the child to write to.
// buffer (in): local buffer with the data to write .
// length (in): number of bytes to write.
// Returns true on success.
bool WriteProtectedChildMemory(HANDLE child_process,
                               void* address,
                               const void* buffer,
                               size_t length) {
  // first, remove the protections
  DWORD old_protection;
  if (!::VirtualProtectEx(child_process, address, length,
                          PAGE_WRITECOPY, &old_protection))
    return false;

  SIZE_T written;
  bool ok = ::WriteProcessMemory(child_process, address, buffer, length,
                                 &written) && (length == written);

  // always attempt to restore the original protection
  if (!::VirtualProtectEx(child_process, address, length,
                          old_protection, &old_protection))
    return false;

  return ok;
}

// Get pointers to the functions that we need from ntdll.dll.
NTSTATUS ResolveNtdll(sandbox::PatchInfo* patch_info) {
  wchar_t* ntdll_name = L"ntdll.dll";
  HMODULE ntdll = ::GetModuleHandle(ntdll_name);
  if (!ntdll)
    return STATUS_PROCEDURE_NOT_FOUND;

  void* signal = ::GetProcAddress(ntdll, "NtSignalAndWaitForSingleObject");
  if (!signal)
    return STATUS_PROCEDURE_NOT_FOUND;

  patch_info->signal_and_wait =
      reinterpret_cast<NtSignalAndWaitForSingleObjectFunction>(signal);

  return STATUS_SUCCESS;
}

};  // namespace

namespace sandbox {

NTSTATUS ResolverThunk::Init(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) {
  if (NULL == thunk_storage || 0 == storage_bytes ||
      NULL == target_module || NULL == target_name)
    return STATUS_INVALID_PARAMETER;

  if (storage_bytes < GetThunkSize())
    return STATUS_BUFFER_TOO_SMALL;

  NTSTATUS ret = STATUS_SUCCESS;
  if (NULL == interceptor_entry_point) {
    ret = ResolveInterceptor(interceptor_module, interceptor_name,
                             &interceptor_entry_point);
    if (!NT_SUCCESS(ret))
      return ret;
  }

  ret = ResolveTarget(target_module, target_name, &target_);
  if (!NT_SUCCESS(ret))
    return ret;

  interceptor_ = interceptor_entry_point;

  return ret;
}

NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module,
                                           const char* interceptor_name,
                                           const void** address) {
  return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS ResolverThunk::ResolveTarget(const void* module,
                                      const char* function_name,
                                      void** address) {
  return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS Service64ResolverThunk::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) {
  NTSTATUS ret = Init(target_module, interceptor_module, target_name,
                      interceptor_name, interceptor_entry_point,
                      thunk_storage, storage_bytes);
  if (!NT_SUCCESS(ret))
    return ret;

  size_t thunk_bytes = GetThunkSize();
  scoped_ptr<char[]> thunk_buffer(new char[thunk_bytes]);
  ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(
                                thunk_buffer.get());

  if (!IsFunctionAService(&thunk->original))
    return STATUS_UNSUCCESSFUL;

  ret = PerformPatch(thunk, thunk_storage);

  if (NULL != storage_used)
    *storage_used = thunk_bytes;

  return ret;
}

NTSTATUS Service64ResolverThunk::ResolveInterceptor(
    const void* interceptor_module,
    const char* interceptor_name,
    const void** address) {
  // After all, we are using a locally mapped version of the exe, so the
  // action is the same as for a target function.
  return ResolveTarget(interceptor_module, interceptor_name,
                       const_cast<void**>(address));
}

// In this case all the work is done from the parent, so resolve is
// just a simple GetProcAddress.
NTSTATUS Service64ResolverThunk::ResolveTarget(const void* module,
                                             const char* function_name,
                                             void** address) {
  if (NULL == module)
    return STATUS_UNSUCCESSFUL;

  *address = ::GetProcAddress(bit_cast<HMODULE>(module), function_name);

  if (NULL == *address)
    return STATUS_UNSUCCESSFUL;

  return STATUS_SUCCESS;
}

size_t Service64ResolverThunk::GetThunkSize() const {
  return sizeof(ServiceFullThunk);
}

bool Service64ResolverThunk::IsFunctionAService(void* local_thunk) const {
  ServiceEntry function_code;
  SIZE_T read;
  if (!::ReadProcessMemory(process_, target_, &function_code,
                           sizeof(function_code), &read))
    return false;

  if (sizeof(function_code) != read)
    return false;

  if (kMmovR10EcxMovEax != function_code.mov_r10_ecx_mov_eax ||
      kSyscall != function_code.syscall || kRetNp != function_code.ret)
    return false;

  // Save the verified code
  memcpy(local_thunk, &function_code, sizeof(function_code));

  return true;
}

NTSTATUS Service64ResolverThunk::PerformPatch(void* local_thunk,
                                              void* remote_thunk) {
  ServiceFullThunk* full_local_thunk = reinterpret_cast<ServiceFullThunk*>(
                                           local_thunk);
  ServiceFullThunk* full_remote_thunk = reinterpret_cast<ServiceFullThunk*>(
                                           remote_thunk);

  // If the source or target are above 4GB we cannot do this relative jump.
  if (reinterpret_cast<ULONG_PTR>(full_remote_thunk) >
      static_cast<ULONG_PTR>(ULONG_MAX))
    return STATUS_CONFLICTING_ADDRESSES;

  if (reinterpret_cast<ULONG_PTR>(target_) > static_cast<ULONG_PTR>(ULONG_MAX))
    return STATUS_CONFLICTING_ADDRESSES;

  // Patch the original code.
  Redirected local_service;
  Redirected* remote_service = reinterpret_cast<Redirected*>(target_);
  ULONG_PTR diff = reinterpret_cast<BYTE*>(&full_remote_thunk->internal_thunk) -
                   &remote_service->pad;
  local_service.relative = static_cast<ULONG>(diff);

  // Setup the PatchInfo structure.
  SIZE_T actual;
  if (!::ReadProcessMemory(process_, remote_thunk, local_thunk,
                           sizeof(PatchInfo), &actual))
    return STATUS_UNSUCCESSFUL;
  if (sizeof(PatchInfo) != actual)
    return STATUS_UNSUCCESSFUL;

  full_local_thunk->patch_info.orig_MapViewOfSection = reinterpret_cast<
      NtMapViewOfSectionFunction>(&full_remote_thunk->original);
  full_local_thunk->patch_info.patch_location = target_;
  NTSTATUS ret = ResolveNtdll(&full_local_thunk->patch_info);
  if (!NT_SUCCESS(ret))
    return ret;

  // Setup the thunk. The jump out is performed from right after the end of the
  // thunk (full_remote_thunk + 1).
  InternalThunk my_thunk;
  ULONG_PTR patch_info = reinterpret_cast<ULONG_PTR>(remote_thunk);
  my_thunk.patch_info = static_cast<ULONG>(patch_info);
  diff = reinterpret_cast<const BYTE*>(interceptor_) -
         reinterpret_cast<BYTE*>(full_remote_thunk + 1);
  my_thunk.relative = static_cast<ULONG>(diff);

  memcpy(&full_local_thunk->internal_thunk, &my_thunk, sizeof(my_thunk));

  // copy the local thunk buffer to the child
  if (!::WriteProcessMemory(process_, remote_thunk, local_thunk,
                            sizeof(ServiceFullThunk), &actual))
    return STATUS_UNSUCCESSFUL;

  if (sizeof(ServiceFullThunk) != actual)
    return STATUS_UNSUCCESSFUL;

  // and now change the function to intercept, on the child
  if (!::WriteProtectedChildMemory(process_, target_, &local_service,
                                   sizeof(local_service)))
    return STATUS_UNSUCCESSFUL;

  return STATUS_SUCCESS;
}

}  // namespace sandbox