OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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/wow_helper/service64_resolver.h" | |
6 | |
7 #include <limits.h> | |
8 #include <stddef.h> | |
9 | |
10 #include <memory> | |
11 | |
12 #include "base/bit_cast.h" | |
13 #include "sandbox/win/wow_helper/target_code.h" | |
14 | |
15 namespace { | |
16 #pragma pack(push, 1) | |
17 | |
18 const BYTE kMovEax = 0xB8; | |
19 const BYTE kMovEdx = 0xBA; | |
20 const USHORT kCallPtrEdx = 0x12FF; | |
21 const BYTE kRet = 0xC2; | |
22 const BYTE kNop = 0x90; | |
23 const USHORT kJmpEdx = 0xE2FF; | |
24 const USHORT kXorEcx = 0xC933; | |
25 const ULONG kLeaEdx = 0x0424548D; | |
26 const ULONG kCallFs1 = 0xC015FF64; | |
27 const ULONG kCallFs2Ret = 0xC2000000; | |
28 const BYTE kPopEdx = 0x5A; | |
29 const BYTE kPushEdx = 0x52; | |
30 const BYTE kPush32 = 0x68; | |
31 | |
32 const ULONG kMmovR10EcxMovEax = 0xB8D18B4C; | |
33 const USHORT kSyscall = 0x050F; | |
34 const BYTE kRetNp = 0xC3; | |
35 const BYTE kPad = 0x66; | |
36 const USHORT kNop16 = 0x9066; | |
37 const BYTE kRelJmp = 0xE9; | |
38 | |
39 const ULONG kXorRaxMovEax = 0xB8C03148; | |
40 const ULONG kSaveRcx = 0x10488948; | |
41 const ULONG kMovRcxRaxJmp = 0xE9C88B48; | |
42 | |
43 // Service code for 64 bit systems. | |
44 struct ServiceEntry { | |
45 // this struct contains roughly the following code: | |
46 // mov r10,rcx | |
47 // mov eax,52h | |
48 // syscall | |
49 // ret | |
50 // xchg ax,ax | |
51 // xchg ax,ax | |
52 | |
53 ULONG mov_r10_ecx_mov_eax; // = 4C 8B D1 B8 | |
54 ULONG service_id; | |
55 USHORT syscall; // = 0F 05 | |
56 BYTE ret; // = C3 | |
57 BYTE pad; // = 66 | |
58 USHORT xchg_ax_ax1; // = 66 90 | |
59 USHORT xchg_ax_ax2; // = 66 90 | |
60 }; | |
61 | |
62 struct Redirected { | |
63 // this struct contains roughly the following code: | |
64 // jmp relative_32 | |
65 // xchg ax,ax // 3 byte nop | |
66 | |
67 Redirected() { | |
68 jmp = kRelJmp; | |
69 relative = 0; | |
70 pad = kPad; | |
71 xchg_ax_ax = kNop16; | |
72 }; | |
73 BYTE jmp; // = E9 | |
74 ULONG relative; | |
75 BYTE pad; // = 66 | |
76 USHORT xchg_ax_ax; // = 66 90 | |
77 }; | |
78 | |
79 struct InternalThunk { | |
80 // this struct contains roughly the following code: | |
81 // xor rax,rax | |
82 // mov eax, 0x00080000 // Thunk storage. | |
83 // mov [rax]PatchInfo.service, rcx // Save first argument. | |
84 // mov rcx, rax | |
85 // jmp relative_to_interceptor | |
86 | |
87 InternalThunk() { | |
88 xor_rax_mov_eax = kXorRaxMovEax; | |
89 patch_info = 0; | |
90 save_rcx = kSaveRcx; | |
91 mov_rcx_rax_jmp = kMovRcxRaxJmp; | |
92 relative = 0; | |
93 }; | |
94 ULONG xor_rax_mov_eax; // = 48 31 C0 B8 | |
95 ULONG patch_info; | |
96 ULONG save_rcx; // = 48 89 48 10 | |
97 ULONG mov_rcx_rax_jmp; // = 48 8b c8 e9 | |
98 ULONG relative; | |
99 }; | |
100 | |
101 struct ServiceFullThunk { | |
102 sandbox::PatchInfo patch_info; | |
103 ServiceEntry original; | |
104 InternalThunk internal_thunk; | |
105 }; | |
106 | |
107 #pragma pack(pop) | |
108 | |
109 // Simple utility function to write to a buffer on the child, if the memery has | |
110 // write protection attributes. | |
111 // Arguments: | |
112 // child_process (in): process to write to. | |
113 // address (out): memory position on the child to write to. | |
114 // buffer (in): local buffer with the data to write . | |
115 // length (in): number of bytes to write. | |
116 // Returns true on success. | |
117 bool WriteProtectedChildMemory(HANDLE child_process, | |
118 void* address, | |
119 const void* buffer, | |
120 size_t length) { | |
121 // first, remove the protections | |
122 DWORD old_protection; | |
123 if (!::VirtualProtectEx(child_process, address, length, | |
124 PAGE_WRITECOPY, &old_protection)) | |
125 return false; | |
126 | |
127 SIZE_T written; | |
128 bool ok = ::WriteProcessMemory(child_process, address, buffer, length, | |
129 &written) && (length == written); | |
130 | |
131 // always attempt to restore the original protection | |
132 if (!::VirtualProtectEx(child_process, address, length, | |
133 old_protection, &old_protection)) | |
134 return false; | |
135 | |
136 return ok; | |
137 } | |
138 | |
139 // Get pointers to the functions that we need from ntdll.dll. | |
140 NTSTATUS ResolveNtdll(sandbox::PatchInfo* patch_info) { | |
141 wchar_t* ntdll_name = L"ntdll.dll"; | |
142 HMODULE ntdll = ::GetModuleHandle(ntdll_name); | |
143 if (!ntdll) | |
144 return STATUS_PROCEDURE_NOT_FOUND; | |
145 | |
146 void* signal = ::GetProcAddress(ntdll, "NtSignalAndWaitForSingleObject"); | |
147 if (!signal) | |
148 return STATUS_PROCEDURE_NOT_FOUND; | |
149 | |
150 patch_info->signal_and_wait = | |
151 reinterpret_cast<NtSignalAndWaitForSingleObjectFunction>(signal); | |
152 | |
153 return STATUS_SUCCESS; | |
154 } | |
155 | |
156 }; // namespace | |
157 | |
158 namespace sandbox { | |
159 | |
160 NTSTATUS ResolverThunk::Init(const void* target_module, | |
161 const void* interceptor_module, | |
162 const char* target_name, | |
163 const char* interceptor_name, | |
164 const void* interceptor_entry_point, | |
165 void* thunk_storage, | |
166 size_t storage_bytes) { | |
167 if (NULL == thunk_storage || 0 == storage_bytes || | |
168 NULL == target_module || NULL == target_name) | |
169 return STATUS_INVALID_PARAMETER; | |
170 | |
171 if (storage_bytes < GetThunkSize()) | |
172 return STATUS_BUFFER_TOO_SMALL; | |
173 | |
174 NTSTATUS ret = STATUS_SUCCESS; | |
175 if (NULL == interceptor_entry_point) { | |
176 ret = ResolveInterceptor(interceptor_module, interceptor_name, | |
177 &interceptor_entry_point); | |
178 if (!NT_SUCCESS(ret)) | |
179 return ret; | |
180 } | |
181 | |
182 ret = ResolveTarget(target_module, target_name, &target_); | |
183 if (!NT_SUCCESS(ret)) | |
184 return ret; | |
185 | |
186 interceptor_ = interceptor_entry_point; | |
187 | |
188 return ret; | |
189 } | |
190 | |
191 NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module, | |
192 const char* interceptor_name, | |
193 const void** address) { | |
194 return STATUS_NOT_IMPLEMENTED; | |
195 } | |
196 | |
197 NTSTATUS ResolverThunk::ResolveTarget(const void* module, | |
198 const char* function_name, | |
199 void** address) { | |
200 return STATUS_NOT_IMPLEMENTED; | |
201 } | |
202 | |
203 NTSTATUS Service64ResolverThunk::Setup(const void* target_module, | |
204 const void* interceptor_module, | |
205 const char* target_name, | |
206 const char* interceptor_name, | |
207 const void* interceptor_entry_point, | |
208 void* thunk_storage, | |
209 size_t storage_bytes, | |
210 size_t* storage_used) { | |
211 NTSTATUS ret = Init(target_module, interceptor_module, target_name, | |
212 interceptor_name, interceptor_entry_point, | |
213 thunk_storage, storage_bytes); | |
214 if (!NT_SUCCESS(ret)) | |
215 return ret; | |
216 | |
217 size_t thunk_bytes = GetThunkSize(); | |
218 std::unique_ptr<char[]> thunk_buffer(new char[thunk_bytes]); | |
219 ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>( | |
220 thunk_buffer.get()); | |
221 | |
222 if (!IsFunctionAService(&thunk->original)) | |
223 return STATUS_UNSUCCESSFUL; | |
224 | |
225 ret = PerformPatch(thunk, thunk_storage); | |
226 | |
227 if (NULL != storage_used) | |
228 *storage_used = thunk_bytes; | |
229 | |
230 return ret; | |
231 } | |
232 | |
233 NTSTATUS Service64ResolverThunk::ResolveInterceptor( | |
234 const void* interceptor_module, | |
235 const char* interceptor_name, | |
236 const void** address) { | |
237 // After all, we are using a locally mapped version of the exe, so the | |
238 // action is the same as for a target function. | |
239 return ResolveTarget(interceptor_module, interceptor_name, | |
240 const_cast<void**>(address)); | |
241 } | |
242 | |
243 // In this case all the work is done from the parent, so resolve is | |
244 // just a simple GetProcAddress. | |
245 NTSTATUS Service64ResolverThunk::ResolveTarget(const void* module, | |
246 const char* function_name, | |
247 void** address) { | |
248 if (NULL == module) | |
249 return STATUS_UNSUCCESSFUL; | |
250 | |
251 *address = ::GetProcAddress(bit_cast<HMODULE>(module), function_name); | |
252 | |
253 if (NULL == *address) | |
254 return STATUS_UNSUCCESSFUL; | |
255 | |
256 return STATUS_SUCCESS; | |
257 } | |
258 | |
259 size_t Service64ResolverThunk::GetThunkSize() const { | |
260 return sizeof(ServiceFullThunk); | |
261 } | |
262 | |
263 bool Service64ResolverThunk::IsFunctionAService(void* local_thunk) const { | |
264 ServiceEntry function_code; | |
265 SIZE_T read; | |
266 if (!::ReadProcessMemory(process_, target_, &function_code, | |
267 sizeof(function_code), &read)) | |
268 return false; | |
269 | |
270 if (sizeof(function_code) != read) | |
271 return false; | |
272 | |
273 if (kMmovR10EcxMovEax != function_code.mov_r10_ecx_mov_eax || | |
274 kSyscall != function_code.syscall || kRetNp != function_code.ret) | |
275 return false; | |
276 | |
277 // Save the verified code | |
278 memcpy(local_thunk, &function_code, sizeof(function_code)); | |
279 | |
280 return true; | |
281 } | |
282 | |
283 NTSTATUS Service64ResolverThunk::PerformPatch(void* local_thunk, | |
284 void* remote_thunk) { | |
285 ServiceFullThunk* full_local_thunk = reinterpret_cast<ServiceFullThunk*>( | |
286 local_thunk); | |
287 ServiceFullThunk* full_remote_thunk = reinterpret_cast<ServiceFullThunk*>( | |
288 remote_thunk); | |
289 | |
290 // If the source or target are above 4GB we cannot do this relative jump. | |
291 if (reinterpret_cast<ULONG_PTR>(full_remote_thunk) > | |
292 static_cast<ULONG_PTR>(ULONG_MAX)) | |
293 return STATUS_CONFLICTING_ADDRESSES; | |
294 | |
295 if (reinterpret_cast<ULONG_PTR>(target_) > static_cast<ULONG_PTR>(ULONG_MAX)) | |
296 return STATUS_CONFLICTING_ADDRESSES; | |
297 | |
298 // Patch the original code. | |
299 Redirected local_service; | |
300 Redirected* remote_service = reinterpret_cast<Redirected*>(target_); | |
301 ULONG_PTR diff = reinterpret_cast<BYTE*>(&full_remote_thunk->internal_thunk) - | |
302 &remote_service->pad; | |
303 local_service.relative = static_cast<ULONG>(diff); | |
304 | |
305 // Setup the PatchInfo structure. | |
306 SIZE_T actual; | |
307 if (!::ReadProcessMemory(process_, remote_thunk, local_thunk, | |
308 sizeof(PatchInfo), &actual)) | |
309 return STATUS_UNSUCCESSFUL; | |
310 if (sizeof(PatchInfo) != actual) | |
311 return STATUS_UNSUCCESSFUL; | |
312 | |
313 full_local_thunk->patch_info.orig_MapViewOfSection = reinterpret_cast< | |
314 NtMapViewOfSectionFunction>(&full_remote_thunk->original); | |
315 full_local_thunk->patch_info.patch_location = target_; | |
316 NTSTATUS ret = ResolveNtdll(&full_local_thunk->patch_info); | |
317 if (!NT_SUCCESS(ret)) | |
318 return ret; | |
319 | |
320 // Setup the thunk. The jump out is performed from right after the end of the | |
321 // thunk (full_remote_thunk + 1). | |
322 InternalThunk my_thunk; | |
323 ULONG_PTR patch_info = reinterpret_cast<ULONG_PTR>(remote_thunk); | |
324 my_thunk.patch_info = static_cast<ULONG>(patch_info); | |
325 diff = reinterpret_cast<const BYTE*>(interceptor_) - | |
326 reinterpret_cast<BYTE*>(full_remote_thunk + 1); | |
327 my_thunk.relative = static_cast<ULONG>(diff); | |
328 | |
329 memcpy(&full_local_thunk->internal_thunk, &my_thunk, sizeof(my_thunk)); | |
330 | |
331 // copy the local thunk buffer to the child | |
332 if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, | |
333 sizeof(ServiceFullThunk), &actual)) | |
334 return STATUS_UNSUCCESSFUL; | |
335 | |
336 if (sizeof(ServiceFullThunk) != actual) | |
337 return STATUS_UNSUCCESSFUL; | |
338 | |
339 // and now change the function to intercept, on the child | |
340 if (!::WriteProtectedChildMemory(process_, target_, &local_service, | |
341 sizeof(local_service))) | |
342 return STATUS_UNSUCCESSFUL; | |
343 | |
344 return STATUS_SUCCESS; | |
345 } | |
346 | |
347 } // namespace sandbox | |
OLD | NEW |