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