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