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