// A resolver is the object in charge of performing the actual interception of // a function. There should be a concrete implementation of a resolver roughly // per type of interception. // resolver负责处理某个函数具体的拦截,每种类型的interception都应该有一个具体的resolver对象 // 这也就是上一节看到的,resolver对应每个的3个类型都有着一个派生类 classResolverThunk { public: ResolverThunk() {} virtual ~ResolverThunk() {}
// Performs the actual interception of a function. // target_name is an exported function from the module loaded at // target_module, and must be replaced by interceptor_name, exported from // interceptor_module. interceptor_entry_point can be provided instead of // interceptor_name / interceptor_module. // thunk_storage must point to a buffer on the child's address space, to hold // the patch thunk, and related data. If provided, storage_used will receive // the number of bytes used from thunk_storage. // // Example: (without error checking) // // size_t size = resolver.GetThunkSize(); // char* buffer = ::VirtualAllocEx(child_process, nullptr, size, // MEM_COMMIT, PAGE_READWRITE); // resolver.Setup(ntdll_module, nullptr, L"NtCreateFile", nullptr, // &MyReplacementFunction, buffer, size, nullptr); // // In general, the idea is to allocate a single big buffer for all // interceptions on the same dll, and call Setup n times. // WARNING: This means that any data member that is specific to a single // interception must be reset within this method. virtual NTSTATUS Setup(constvoid* target_module,//函数所在dll constvoid* interceptor_module,//hook函数所在dll constchar* target_name,//original函数名称 constchar* interceptor_name,//hook函数名称 constvoid* interceptor_entry_point,//hook入口地址 void* thunk_storage,//存储thunk的buffer size_t storage_bytes,//buffer的大小 size_t* storage_used)= 0;
// 下面两个函数用于确定hook和original函数的地址 // Gets the address of function_name inside module (main exe). virtual NTSTATUS ResolveInterceptor(constvoid* module, constchar* function_name, constvoid** address);
// Gets the address of an exported function_name inside module. virtual NTSTATUS ResolveTarget(constvoid* module, constchar* function_name, void** address);
// Gets the required buffer size for this type of thunk. // 计算该thunk需要的buffer大小 virtualsize_tGetThunkSize()const= 0;
protected: // Performs basic initialization on behalf of a concrete instance of a // resolver. That is, parameter validation and resolution of the target // and the interceptor into the member variables. // // target_name is an exported function from the module loaded at // target_module, and must be replaced by interceptor_name, exported from // interceptor_module. interceptor_entry_point can be provided instead of // interceptor_name / interceptor_module. // thunk_storage must point to a buffer on the child's address space, to hold // the patch thunk, and related data. // 常规套路init virtual NTSTATUS Init(constvoid* target_module, constvoid* interceptor_module, constchar* target_name, constchar* interceptor_name, constvoid* interceptor_entry_point, void* thunk_storage, size_t storage_bytes);
// Gets the required buffer size for the internal part of the thunk. size_tGetInternalThunkSize()const;
// Initializes the internal part of the thunk. // interceptor is the function to be called instead of original_function. // 调用此接口来用interceptor替换original的调用 boolSetInternalThunk(void* storage, size_t storage_bytes, constvoid* original_function, constvoid* interceptor);
// Holds the resolved interception target. void* target_; //这个对应original function地址 // Holds the resolved interception interceptor. constvoid* interceptor_; //对应hook函数地址
// 在thunk buffer上部署InternalThunk InternalThunk* thunk = new (storage) InternalThunk;
#pragmawarning(push) #pragmawarning(disable : 4311) // These casts generate warnings because they are 32 bit specific. // 这两个一填充,就达成了interceptor(original_function, xxx) -> ret addr的效果 // 那么关键的就在于interceptor内部要如何处理这个extra参数了 thunk->interceptor_function = reinterpret_cast<ULONG>(interceptor); thunk->extra_argument = reinterpret_cast<ULONG>(original_function); #pragmawarning(pop)
structInternalThunk { // This struct contains roughly the following code: // 01 48b8f0debc9a78563412 mov rax,123456789ABCDEF0h // ff e0 jmp rax // // The code modifies rax, but that's fine for x64 ABI.
// This is the concrete resolver used to perform exports table interceptions. // Eat类型是指对导出表函数的拦截?看来此前的猜想有误。 classEatResolverThunk : public ResolverThunk { public: EatResolverThunk() : eat_entry_(nullptr) {} ~EatResolverThunk() override {}
#if defined(_WIN64) // We have two thunks, in order: the return path and the forward path. // x64有两个thunk,一来一回 // x64用不到第三个参数,在x86的设计中它表示original地址 // 这里在ThunkData[i]处先放置了第一个跳转到original地址的thunk data if (!SetInternalThunk(thunk_storage, storage_bytes, nullptr, target_)) return STATUS_BUFFER_TOO_SMALL;
// This is the concrete resolver used to perform sidestep interceptions. classSidestepResolverThunk : public ResolverThunk { public: SidestepResolverThunk() {} ~SidestepResolverThunk() override {}
// Maximum size of the preamble stub. We overwrite at least the first 5 // bytes of the function. Considering the worst case scenario, we need 4 // bytes + the max instruction size + 5 more bytes for our jump back to // the original code. With that in mind, 32 is a good number :) // 所以看起来sidestep是inline hook,它会覆盖original头至少5个字节。 // 最坏的情况下,我们需要4个字节 + 最大指令长度 + 用于跳回到original代码的5个额外字节 // 为了对齐就用了32. constsize_t kMaxPreambleStubSize = 32;
// sidestep类型的resolver,把传入的ThunkData[i]存储空间看成SidestepThunk // 为什么要设计个类型呢?从GetThunkSize中我们就可以看到,sidestep的ThunkData由两部分构成 // 前32个字节是inline stub,ThunkData[i]的32个字节之后存储InternalThunk /* struct SidestepThunk { char sidestep[kSizeOfSidestepStub]; // Storage for the sidestep stub. int internal_thunk; // Dummy member to the beginning of the internal thunk. }; */ SidestepThunk* thunk = reinterpret_cast<SidestepThunk*>(thunk_storage);
// Implements a patching mechanism that overwrites the first few bytes of // a function preamble with a jump to our hook function, which is then // able to call the original function via a specially-made preamble-stub // that imitates the action of the original preamble. // // inline hook的局限性与危险性说明: // Note that there are a number of ways that this method of patching can // fail. The most common are: // - If there is a jump (jxx) instruction in the first 5 bytes of // the function being patched, we cannot patch it because in the // current implementation we do not know how to rewrite relative // jumps after relocating them to the preamble-stub. Note that // if you really really need to patch a function like this, it // would be possible to add this functionality (but at some cost). // - If there is a return (ret) instruction in the first 5 bytes // we cannot patch the function because it may not be long enough // for the jmp instruction we use to inject our patch. // - If there is another thread currently executing within the bytes // that are copied to the preamble stub, it will crash in an undefined // way. // // If you get any other error than the above, you're either pointing the // patcher at an invalid instruction (e.g. into the middle of a multi- // byte instruction, or not at memory containing executable instructions) // or, there may be a bug in the disassembler we use to find // instruction boundaries. classPreamblePatcher { public: // Patches target_function to point to replacement_function using a provided // preamble_stub of stub_size bytes. // Returns An error code indicating the result of patching. // 函数模板,其实target_function和replacement_function都设void *应该就可以 // 我没有get到为什么要设一个模板在这里,为了便于扩展? template <classT> static SideStepError Patch(T target_function, T replacement_function, void* preamble_stub, size_t stub_size){ // 而且这个函数不是模板,是明确的参数void * returnRawPatchWithStub(target_function, replacement_function, reinterpret_cast<unsignedchar*>(preamble_stub), stub_size, nullptr); }
private: // Patches a function by overwriting its first few bytes with // a jump to a different function. This is similar to the RawPatch // function except that it uses the stub allocated by the caller // instead of allocating it. // // To use this function, you first have to call VirtualProtect to make the // target function writable at least for the duration of the call. // // target_function: A pointer to the function that should be // patched. // // replacement_function: A pointer to the function that should // replace the target function. The replacement function must have // exactly the same calling convention and parameters as the original // function. // // preamble_stub: A pointer to a buffer where the preamble stub // should be copied. The size of the buffer should be sufficient to // hold the preamble bytes. // // stub_size: Size in bytes of the buffer allocated for the // preamble_stub // // bytes_needed: Pointer to a variable that receives the minimum // number of bytes required for the stub. Can be set to nullptr if you're // not interested. // // Returns An error code indicating the result of patching. static SideStepError RawPatchWithStub(void* target_function, void* replacement_function, unsignedchar* preamble_stub, size_t stub_size, size_t* bytes_needed); };
SideStepError PreamblePatcher::RawPatchWithStub( void* target_function, void* replacement_function, unsignedchar* preamble_stub, size_t stub_size, size_t* bytes_needed){ if ((NULL == target_function) || (NULL == replacement_function) || (NULL == preamble_stub)) { ASSERT(false, (L"Invalid parameters - either pTargetFunction or " L"pReplacementFunction or pPreambleStub were NULL.")); return SIDESTEP_INVALID_PARAMETER; }
// TODO(V7:joi) Siggi and I just had a discussion and decided that both // patching and unpatching are actually unsafe. We also discussed a // method of making it safe, which is to freeze all other threads in the // process, check their thread context to see if their eip is currently // inside the block of instructions we need to copy to the stub, and if so // wait a bit and try again, then unfreeze all threads once we've patched. // Not implementing this for now since we're only using SideStep for unit // testing, but if we ever use it for production code this is what we // should do. // 这个sidestep目前还没有投入到产品,所以线程的安全性处理还没做,开发者还是很谨小慎微的 // 说不定这个东西问世的时候会带来安全隐患 // // NOTE: Stoyan suggests we can write 8 or even 10 bytes atomically using // FPU instructions, and on newer processors we could use cmpxchg8b or // cmpxchg16b. So it might be possible to do the patching/unpatching // atomically and avoid having to freeze other threads. Note though, that // doing it atomically does not help if one of the other threads happens // to have its eip in the middle of the bytes you change while you change // them. // original函数地址 unsignedchar* target = reinterpret_cast<unsignedchar*>(target_function);
// Let's disassemble the preamble of the target function to see if we can // patch, and to see how much of the preamble we need to take. We need 5 // bytes for our jmp instruction, so let's find the minimum number of // instructions to get 5 bytes. // 通过反汇编original函数来看看是否能够patch,需要patch多少个字节。 // 注意jmp指令需要5个字节 MiniDisassembler disassembler; unsignedint preamble_bytes = 0; while (preamble_bytes < 5) { InstructionType instruction_type = disassembler.Disassemble(target + preamble_bytes, &preamble_bytes); if (IT_JUMP == instruction_type) { // 如果前5个字节有jmp系列指令,是没办法hook的 ASSERT(false, (L"Unable to patch because there is a jump instruction " L"in the first 5 bytes.")); return SIDESTEP_JUMP_INSTRUCTION; } elseif (IT_RETURN == instruction_type) { // 如果前5个字节有ret系列指令,那么函数太短了,不够patch一个jmp ASSERT(false, (L"Unable to patch because function is too short")); return SIDESTEP_FUNCTION_TOO_SMALL; } elseif (IT_GENERIC != instruction_type) { // 这种是异端的情况,指令不认识。。。 ASSERT(false, (L"Disassembler encountered unsupported instruction " L"(either unused or unknown")); return SIDESTEP_UNSUPPORTED_INSTRUCTION; } }
// Inv: preamble_bytes is the number of bytes (at least 5) that we need to // take from the preamble to have whole instructions that are 5 bytes or more // in size total. The size of the stub required is cbPreamble + size of // jmp (5) if (preamble_bytes + 5 > stub_size) { NOTREACHED_NT(); return SIDESTEP_INSUFFICIENT_BUFFER; }
// First, copy the preamble that we will overwrite. // 把要被覆盖的首字节序列copy出来,这个RawMemcpy是逐字节copy,没有用crt RawMemcpy(reinterpret_cast<void*>(preamble_stub), reinterpret_cast<void*>(target), preamble_bytes);
// Now, make a jmp instruction to the rest of the target function (minus the // preamble bytes we moved into the stub) and copy it into our preamble-stub. // find address to jump to, relative to next address after jmp instruction #pragmawarning(push) #pragmawarning(disable:4244) // This assignment generates a warning because it is 32 bit specific. // 计算original剩下的指令相对preamble_stub+preamble_bytes+5的偏移 int relative_offset_to_target_rest = ((reinterpret_cast<unsignedchar*>(target) + preamble_bytes) - (preamble_stub + preamble_bytes + 5)); #pragmawarning(pop) // jmp (Jump near, relative, displacement relative to next instruction) // 这里放上jmp preamble_stub[preamble_bytes] = ASM_JMP32REL; // copy the address // jmp后跟上4个字节的相对跳转地址,此时jmp就跳到了overwrite之后的rest code RawMemcpy(reinterpret_cast<void*>(preamble_stub + preamble_bytes + 1), reinterpret_cast<void*>(&relative_offset_to_target_rest), 4);
// Inv: preamble_stub points to assembly code that will execute the // original function by first executing the first cbPreamble bytes of the // preamble, then jumping to the rest of the function.
// Overwrite the first 5 bytes of the target function with a jump to our // replacement function. // (Jump near, relative, displacement relative to next instruction) // 修改original起始地址,做出一个jmp target[0] = ASM_JMP32REL;
// Find offset from instruction after jmp, to the replacement function. #pragmawarning(push) #pragmawarning(disable:4244) int offset_to_replacement_function = reinterpret_cast<unsignedchar*>(replacement_function) - reinterpret_cast<unsignedchar*>(target) - 5; #pragmawarning(pop) // complete the jmp instruction RawMemcpy(reinterpret_cast<void*>(target + 1), reinterpret_cast<void*>(&offset_to_replacement_function), 4); // 此时original的入口就变成了jmp到InternalThunk处 // Set any remaining bytes that were moved to the preamble-stub to INT3 so // as not to cause confusion (otherwise you might see some strange // instructions if you look at the disassembly, or even invalid // instructions). Also, by doing this, we will break into the debugger if // some code calls into this portion of the code. If this happens, it // means that this function cannot be patched using this patcher without // further thought. // 如果overwrite的字节数比5要多,就用0xcc填充剩下的部分 if (preamble_bytes > 5) { RawMemset(reinterpret_cast<void*>(target + 5), ASM_INT3, preamble_bytes - 5); }
// Inv: The memory pointed to by target_function now points to a relative // jump instruction that jumps over to the preamble_stub. The preamble // stub contains the first stub_size bytes of the original target // function's preamble code, followed by a relative jump back to the next // instruction after the first cbPreamble bytes.
// This is the concrete resolver used to perform smart sidestep interceptions. // This means basically a sidestep interception that skips the interceptor when // the caller resides on the same dll being intercepted. It is intended as // a helper only, because that determination is not infallible. // SidestepResolverThunk的派生类 // 看起来是当call的发起者与被拦截的dll是同一个时,会跳过interceptor的执行 // 也就是说dll本体上的调用不会触发hook // 好吧,我们此前猜测的完全不对。。。 classSmartSidestepResolverThunk : public SidestepResolverThunk { public: SmartSidestepResolverThunk() {} ~SmartSidestepResolverThunk() override {}
// 其实很简单,找找address是否在该PE上就知道了 base::win::PEImage pe(base); if (pe.GetImageSectionFromAddr(return_address)) returntrue; returnfalse; }
// This code must basically either call the intended interceptor or skip the // call and invoke instead the original function. In any case, we are saving // the registers that may be trashed by our c++ code. // // This function is called with a first parameter inserted by us, that points // to our SmartThunk. When we call the interceptor we have to replace this // parameter with the one expected by that function (stored inside our // structure); on the other hand, when we skip the interceptor we have to remove // that extra argument before calling the original function. // // When we skip the interceptor, the transformation of the stack looks like: // On Entry: On Use: On Exit: // [param 2] = first real argument [param 2] (esp+1c) [param 2] // [param 1] = our SmartThunk [param 1] (esp+18) [ret address] // [ret address] = real caller [ret address] (esp+14) [xxx] // [xxx] [addr to jump to] (esp+10) [xxx] // [xxx] [saved eax] [xxx] // [xxx] [saved ebx] [xxx] // [xxx] [saved ecx] [xxx] // [xxx] [saved edx] [xxx] __declspec(naked) voidSmartSidestepResolverThunk::SmartStub(){ __asm { push eax // Space for the jump. push eax // Save registers. push ebx push ecx push edx mov ebx, [esp + 0x18] // First parameter = SmartThunk. mov edx, [esp + 0x14] // Get the return address. mov eax, [ebx]SmartThunk.module_base push edx push eax call SmartSidestepResolverThunk::IsInternalCall // 这里判断一下是否是internal add esp, 8
test eax, eax // 如果是的话,就直接call original就行了 lea edx, [ebx]SmartThunk.sidestep // The original function. 盯住这个edx jz call_interceptor // 如果不是internal,就得部署interceptor
// Skip this call mov ecx, [esp + 0x14] // Return address. mov [esp + 0x18], ecx // Remove first parameter. mov [esp + 0x10], edx // edx是original function,这个位置作为ret时的返回地址 pop edx // Restore registers. pop ecx pop ebx pop eax ret 4// Jump to original function.
call_interceptor: mov ecx, [ebx]SmartThunk.interceptor mov [esp + 0x18], edx // Replace first parameter. orignal地址成了第一个参数 mov [esp + 0x10], ecx // interceptor地址变成了ret addr pop edx // Restore registers. pop ecx pop ebx pop eax ret // Jump to original function. 这个理应是jmp interceptor } }
// This is basically a wrapper around the normal sidestep patch that extends // the thunk to use a chained interceptor. It uses the fact that // SetInternalThunk generates the code to pass as the first parameter whatever // it receives as original_function; we let SidestepResolverThunk set this value // to its saved code, and then we change it to our thunk data. NTSTATUS SmartSidestepResolverThunk::Setup(constvoid* target_module, constvoid* interceptor_module, constchar* target_name, constchar* interceptor_name, constvoid* interceptor_entry_point, void* thunk_storage, size_t storage_bytes, size_t* storage_used){ if (storage_bytes < GetThunkSize()) return STATUS_BUFFER_TOO_SMALL;
NTSTATUS ret; // 填充interceptor,SmartStub中会用到 if (interceptor_entry_point) { thunk->interceptor = interceptor_entry_point; } else { ret = ResolveInterceptor(interceptor_module, interceptor_name, &thunk->interceptor); if (!NT_SUCCESS(ret)) return ret; }
// Perform a standard sidestep patch on the last part of the thunk, but point // to our internal smart interceptor. size_t standard_bytes = storage_bytes - offsetof(SmartThunk, sidestep); // SmartThunk的SodestepThunk的填充和基类SidestepResolverThunk一致 // 但注意interceptor_entry_point此时不再是interceptor的地址,而是SmartStub地址 // 相当于在interceptor的基础上用SmartStub又拦了一次,而SmartStub会根据internal caller // 的判断跳转到interceptor的分支或original分支,interceptor在thunk_storage中记录了 ret = SidestepResolverThunk::Setup(target_module, interceptor_module, target_name, nullptr, reinterpret_cast<void*>(&SmartStub), &thunk->sidestep, standard_bytes, nullptr); if (!NT_SUCCESS(ret)) return ret;
// Fix the internal thunk to pass the whole buffer to the interceptor. // 这里SmartStub地址作为了拦截器,而原本的一大坨thunk_storage作为original,它相当于一个代理 SetInternalThunk(&thunk->sidestep.internal_thunk, GetInternalThunkSize(), thunk_storage, reinterpret_cast<void*>(&SmartStub));
// This is the concrete resolver used to perform service-call type functions // inside ntdll.dll. // 用于执行ntdll中的系统调用类型函数的拦截 classServiceResolverThunk : public ResolverThunk { public: // The service resolver needs a child process to write to. ServiceResolverThunk(HANDLE process, bool relaxed) : ntdll_base_(nullptr), process_(process), relaxed_(relaxed), relative_jump_(0) {} ~ServiceResolverThunk() override {}
// Implementation of Resolver::GetThunkSize. size_tGetThunkSize()constoverride;
// 这几个居然还是virtual,看来还有派生类 // Call this to set up ntdll_base_ which will allow for local patches. virtualvoidAllowLocalPatches();
// Verifies that the function specified by |target_name| in |target_module| is // a service and copies the data from that function into |thunk_storage|. If // |storage_bytes| is too small, then the method fails. virtual NTSTATUS CopyThunk(constvoid* target_module, constchar* target_name, BYTE* thunk_storage, size_t storage_bytes, size_t* storage_used);
protected: // The unit test will use this member to allow local patch on a buffer. HMODULE ntdll_base_;
// Handle of the child process. HANDLE process_;
private: // Returns true if the code pointer by target_ corresponds to the expected // type of function. Saves that code on the first part of the thunk pointed // by local_thunk (should be directly accessible from the parent). virtualboolIsFunctionAService(void* local_thunk)const;
// Performs the actual patch of target_. // local_thunk must be already fully initialized, and the first part must // contain the original code. The real type of this buffer is ServiceFullThunk // (yes, private). remote_thunk (real type ServiceFullThunk), must be // allocated on the child, and will contain the thunk data, after this call. // Returns the apropriate status code. virtual NTSTATUS PerformPatch(void* local_thunk, void* remote_thunk);
// Provides basically the same functionality as IsFunctionAService but it // continues even if it does not recognize the function code. remote_thunk // is the address of our memory on the child. boolSaveOriginalFunction(void* local_thunk, void* remote_thunk);
// true if we are allowed to patch already-patched functions. bool relaxed_; ULONG relative_jump_;
// This is the concrete resolver used to perform service-call type functions // inside ntdll.dll on WOW64 (32 bit ntdll on 64 bit Vista). classWow64ResolverThunk : public ServiceResolverThunk { public: // The service resolver needs a child process to write to. Wow64ResolverThunk(HANDLE process, bool relaxed) : ServiceResolverThunk(process, relaxed) {} ~Wow64ResolverThunk() override {}
// This is the concrete resolver used to perform service-call type functions // inside ntdll.dll on WOW64 for Windows 8. classWow64W8ResolverThunk : public ServiceResolverThunk { public: // The service resolver needs a child process to write to. Wow64W8ResolverThunk(HANDLE process, bool relaxed) : ServiceResolverThunk(process, relaxed) {} ~Wow64W8ResolverThunk() override {}
// This is the concrete resolver used to perform service-call type functions // inside ntdll.dll on Windows 8. classWin8ResolverThunk : public ServiceResolverThunk { public: // The service resolver needs a child process to write to. Win8ResolverThunk(HANDLE process, bool relaxed) : ServiceResolverThunk(process, relaxed) {} ~Win8ResolverThunk() override {}
// This is the concrete resolver used to perform service-call type functions // inside ntdll.dll on WOW64 for Windows 10. classWow64W10ResolverThunk : public ServiceResolverThunk { public: // The service resolver needs a child process to write to. Wow64W10ResolverThunk(HANDLE process, bool relaxed) : ServiceResolverThunk(process, relaxed) {} ~Wow64W10ResolverThunk() override {}
NTSTATUS ServiceResolverThunk::ResolveInterceptor( constvoid* interceptor_module, constchar* interceptor_name, constvoid** address){ // After all, we are using a locally mapped version of the exe, so the // action is the same as for a target function. returnResolveTarget(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 ServiceResolverThunk::ResolveTarget(constvoid* module, constchar* function_name, void** address){ if (!module) return STATUS_UNSUCCESSFUL;
structServiceFullThunk { union { ServiceEntry original; ServiceEntryW8 original_w8; Wow64Entry wow_64; Wow64EntryW8 wow_64_w8; }; int internal_thunk; // Dummy member to the beginning of the internal thunk. };
// We don't have an internal thunk for x64. // x64看来不需要internal thunk structServiceFullThunk { union { ServiceEntry original; ServiceEntryW8 original_w8; ServiceEntryWithInt2E original_int2e_fallback; }; };
// Service code for 64 bit systems. structServiceEntry { // This struct contains roughly the following code: // 00 mov r10,rcx // 03 mov eax,52h // 08 syscall // 0a ret // 0b xchg ax,ax // 0e xchg ax,ax
// Service code for 64 bit systems with int 2e fallback. structServiceEntryWithInt2E { // This struct contains roughly the following code: // 00 4c8bd1 mov r10,rcx // 03 b855000000 mov eax,52h // 08 f604250803fe7f01 test byte ptr SharedUserData!308, 1 // 10 7503 jne [over syscall] // 12 0f05 syscall // 14 c3 ret // 15 cd2e int 2e // 17 c3 ret
// Find the system call pointer if we don't already have it. // 如果是call dword ptr [edx]的话,要读取出[edx] if (kCallEdx != function_code.call_ptr_edx) { DWORD ki_system_call; if (!::ReadProcessMemory(process_, bit_cast<constvoid*>(function_code.stub), &ki_system_call, sizeof(ki_system_call), &read)) { returnfalse; }
if (sizeof(ki_system_call) != read) returnfalse;
HMODULE module_1, module_2; // 检查一下读取到的ki_system_call的module和original函数的module是否一致 // last check, call_stub should point to a KiXXSystemCall function on ntdll if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, bit_cast<constwchar_t*>(ki_system_call), &module_1)) { returnfalse; }
if (ntdll_base_) { // This path is only taken when running the unit tests. We want to be // able to patch a buffer in memory, so target_ is not inside ntdll. module_2 = ntdll_base_; } else { if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<constwchar_t*>(target_), &module_2)) returnfalse; }
if (module_1 != module_2) returnfalse; }
// Save the verified code // 此时Setup中的ServiceFullThunk的ServiceEntry成员就正确填充了 memcpy(local_thunk, &function_code, sizeof(function_code));
// 进到这个函数就表示,Resolver是允许repatch的,那么第一次patch之后其后再次的patch // 首字节就不再是mov eax,xx而是jmp xxx了 if (kJmp32 == function_code.mov_eax) { // Plain old entry point patch. The relative jump address follows it. // 读出来上一次patch的jmp到的地址 ULONG relative = function_code.service_id;
// First, fix our copy of their patch. // 修正地址 relative += bit_cast<ULONG>(target_) - bit_cast<ULONG>(remote_thunk);
function_code.service_id = relative;
// And now, remember how to re-patch it. // 这里处理repatch,remote_thunk是个ServiceFullThunk结构 ServiceFullThunk* full_thunk = reinterpret_cast<ServiceFullThunk*>(remote_thunk);
// copy the local thunk buffer to the child // local buffer给了remote_thunk的ServiceEntry // 所以当interceptor_跳到这里时会执行jmp xxx(存在repatch)或者执行original原本的代码序列(没有repatch) SIZE_T written; if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, thunk_size, &written)) { return STATUS_UNSUCCESSFUL; }
if (thunk_size != written) return STATUS_UNSUCCESSFUL;
// and now change the function to intercept, on the child // 现在要处理的就是把原本的original函数开头的那一段内容修改成interceptor_ // 实现call original时实际上是call interceptor_,以此完成了整个hook链 if (ntdll_base_) { // running a unit test if (!::WriteProcessMemory(process_, target_, &intercepted_code, bytes_to_write, &written)) return STATUS_UNSUCCESSFUL; } else { if (!WriteProtectedChildMemory(process_, target_, &intercepted_code, bytes_to_write)) return STATUS_UNSUCCESSFUL; }
structInternalThunk { // This struct contains roughly the following code: // 01 48b8f0debc9a78563412 mov rax,123456789ABCDEF0h // ff e0 jmp rax // // The code modifies rax, but that's fine for x64 ABI.
if (!IsFunctionAService(&thunk->original)) return STATUS_OBJECT_NAME_COLLISION;
ret = PerformPatch(thunk, thunk_storage);
if (storage_used) *storage_used = thunk_bytes;
return ret; }
NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, void* remote_thunk){ // Patch the original code. ServiceEntry local_service; DCHECK_NT(GetInternalThunkSize() <= sizeof(local_service)); if (!SetInternalThunk(&local_service, sizeof(local_service), nullptr, interceptor_)) return STATUS_UNSUCCESSFUL;
// Copy the local thunk buffer to the child. // local thunk buffer存储的是original函数原本的指令序列,拷贝到了远端的remote_thunk SIZE_T actual; 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 (ntdll_base_) { // Running a unit test. if (!::WriteProcessMemory(process_, target_, &local_service, sizeof(local_service), &actual)) return STATUS_UNSUCCESSFUL; } else { // 此时original函数面目全非,变成了InternalThunk的mov rax,interceptor;jmp rax; // 至于如何从interceptor内部跳回remote_thunk,那就是interceptor的事了(g_orignal) // 还记得InterceptionManager::PatchClientFunctions吗? if (!WriteProtectedChildMemory(process_, target_, &local_service, sizeof(local_service))) return STATUS_UNSUCCESSFUL; }
for (auto interception : interceptions_) { const base::string16 ntdll(kNtdllName); if (interception.dll != ntdll) return SBOX_ERROR_BAD_PARAMS;
if (INTERCEPTION_SERVICE_CALL != interception.type) return SBOX_ERROR_BAD_PARAMS;
// 对每个service call类型的ntdll的拦截函数进行处理 #if defined(SANDBOX_EXPORTS) // We may be trying to patch by function name. if (!interception.interceptor_address) { constchar* address; NTSTATUS ret = thunk->ResolveInterceptor( local_interceptor.get(), interception.interceptor.c_str(), reinterpret_cast<constvoid**>(&address)); if (!NT_SUCCESS(ret)) { ::SetLastError(GetLastErrorFromNtStatus(ret)); return SBOX_ERROR_CANNOT_RESOLVE_INTERCEPTION_THUNK; }
// Translate the local address to an address on the child. interception.interceptor_address = interceptor_base + (address - reinterpret_cast<char*>(local_interceptor.get())); } #endif// defined(SANDBOX_EXPORTS) // 这里对&thunks->thunks[dll_data->num_thunks]进行了实装 // 它是broker在target进程中开辟的内存空间,空间起始是DllInterceptionData // 这里是某一个拦截函数的ThunkData NTSTATUS ret = thunk->Setup( ntdll_base, interceptor_base, interception.function.c_str(), interception.interceptor.c_str(), interception.interceptor_address, &thunks->thunks[dll_data->num_thunks], thunk_bytes - dll_data->used_bytes, nullptr); if (!NT_SUCCESS(ret)) { ::SetLastError(GetLastErrorFromNtStatus(ret)); return SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_THUNK; }