// Adds handles to close after lockdown. // 在target进程被LowerToken()降权时,关闭加入的所有句柄 classHandleCloser { public: HandleCloser(); ~HandleCloser();
// Adds a handle that will be closed in the target process after lockdown. // A nullptr value for handle_name indicates all handles of the specified // type. An empty string for handle_name indicates the handle is unnamed. // 可以看到这个接口需要被提供句柄类型和名称 // 如果name是nullptr,那就表示所有的该类型句柄都要加入 // 如果name是空串,就表示该句柄是匿名的 ResultCode AddHandle(const base::char16* handle_type, const base::char16* handle_name);
// Serializes and copies the closer table into the target process. // 看起来是序列化待关闭句柄表,copy给target进程 // 这个函数就是在PolicyBase::SetupHandleCloser中调用的 boolInitializeTargetHandles(TargetProcess* target);
private: // Calculates the memory needed to copy the serialized handles list (rounded // to the nearest machine-word size). // copy序列化句柄列表时,用于计算buffer尺寸 size_tGetBufferSize();
// Serializes the handle list into the target process. // 序列化句柄列表到target进程 boolSetupHandleList(void* buffer, size_t buffer_bytes);
// Global parameters and a pointer to the list of entries. // 看起来是个全局量,内部维护了一个到handle列表的指针 structHandleCloserInfo { size_t record_bytes; // Rounded to sizeof(size_t) bytes. size_t num_handle_types; //有多少种类型 structHandleListEntry handle_entries[1]; //应该是flexible };
// Type and set of corresponding handle names to close. // 某种类型句柄的所有handle structHandleListEntry { size_t record_bytes; // Rounded to sizeof(size_t) bytes. size_t offset_to_names; // Nul terminated strings of name_count names. size_t name_count; // 有多少个name base::char16 handle_type[1]; // 这个应该不是flexible,表示type类型 };
boolHandleCloser::InitializeTargetHandles(TargetProcess* target){ // Do nothing on an empty list (global pointer already initialized to // nullptr). // 如果没什么需要target关闭的,返回就好了 if (handles_to_close_.empty()) returntrue;
// Windows跨进程写入数据的常见套路VirtualAllocEx->WriteProcessMemory // 然而第二个参数使用的是nullptr,这就表示broker在这里并不是把数据写入到某个固定的内存空间 // Allocate memory in the target process without specifying the address void* remote_data = ::VirtualAllocEx(child, nullptr, bytes_needed, MEM_COMMIT, PAGE_READWRITE); if (!remote_data) returnfalse;
// Copy the handle buffer over. SIZE_T bytes_written; bool result = ::WriteProcessMemory(child, remote_data, local_buffer.get(), bytes_needed, &bytes_written); if (!result || bytes_written != bytes_needed) { ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE); returnfalse; }
// Round up to the nearest multiple of word size. // 因为字符串的单位不规则,为了解构时方便索引下一个HandleListEntry,做了个对齐操作 bytes_entry = RoundUpToWordSize(bytes_entry); // 加到总的尺寸上 bytes_total += bytes_entry; }
base::string16 resolved_name; if (handle_name) { // 这个可以有 resolved_name = handle_name; if (handle_type == base::string16(L"Key")) // 如果handle是Key类型,那么名称要修剪一下,ResolveRegistryName其实是一个Windows注册表相关操作简单的封装 // 这里就不详细研究名称是什么了,想要了解可以用调试器下断点分析 if (!ResolveRegistryName(resolved_name, &resolved_name)) return SBOX_ERROR_BAD_PARAMS; }
// 先在handles_to_close_找一下是否有该类型的句柄,map中type为键 HandleMap::iterator names = handles_to_close_.find(handle_type); if (names == handles_to_close_.end()) { // We have no entries for this type. // 如果没有的话,就插入handle_type, HandleMap::mapped_type()这样的一个entry std::pair<HandleMap::iterator, bool> result = handles_to_close_.insert( HandleMap::value_type(handle_type, HandleMap::mapped_type())); names = result.first;// names索引插入的entry if (handle_name) names->second.insert(resolved_name);// 如果有名字,那就在该type对应的空set中插入该名字 } elseif (!handle_name) { // Now we need to close all handles of this type. // 如果handle_name是nullptr,会清掉当前该type对应set的所有名字 // 原来是clear,看前面注释还以为是把所有该类型句柄都加进来。。。想想也不太可能 names->second.clear(); } elseif (!names->second.empty()) { // Add another name for this type. // 如果当前type对应的set不为空,则插入另一个名字 names->second.insert(resolved_name); } // If we're already closing all handles of type then we're done. // 如果此时已经全都清掉了,那再来的就不管了。 return SBOX_ALL_OK; }
ResultCode PolicyBase::SetDisconnectCsrss(){ // Does not work on 32-bit, and the ASAN runtime falls over with the // CreateThread EAT patch used when this is enabled. // See https://crbug.com/783296#c27. // 未开启ASAN的win10 x64所用,看起来和某个bug有关,以后有空看看 #if defined(_WIN64) && !defined(ADDRESS_SANITIZER) if (base::win::GetVersion() >= base::win::VERSION_WIN10) { is_csrss_connected_ = false; returnAddKernelObjectToClose(L"ALPC Port", nullptr);//但这里是把ALPC Port都clear了 } #endif// !defined(_WIN64) return SBOX_ALL_OK; }
// Target process code to close the handle list copied over from the broker. // 这个是target进程的代码,它关闭broker传过来的句柄列表 classHandleCloserAgent { public: HandleCloserAgent(); ~HandleCloserAgent();
// Reads the serialized list from the broker and creates the lookup map. // Updates is_csrss_connected based on type of handles closed. // 从broker读取序列化列表,创建一个lookup map // 根据关闭的句柄类型,更新is_csrss_connected voidInitializeHandlesToClose(bool* is_csrss_connected);
// Closes any handles matching those in the lookup map. // 关闭lookup map中匹配的句柄 boolCloseHandles();
// True if we have handles waiting to be closed. // 这是个static方法,用于判断当前是否在等待句柄被关闭 staticboolNeedsHandlesClosed();
private: // Attempt to stuff a closed handle with a dummy Event. boolAttemptToStuffHandleSlot(HANDLE closed_handle, const base::string16& type);
boolHandleCloserAgent::CloseHandles(){ DWORD handle_count = UINT_MAX; constint kInvalidHandleThreshold = 100; // 句柄都是4的倍数,可以参考wrk的句柄表设计,PID的设计也复用了句柄表设计,所以都是4的倍数 constsize_t kHandleOffset = 4; // Handles are always a multiple of 4.
if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) returnfalse;
// Set up buffers for the type info and the name. std::vector<BYTE> type_info_buffer(sizeof(OBJECT_TYPE_INFORMATION) + 32 * sizeof(wchar_t)); OBJECT_TYPE_INFORMATION* type_info = reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); base::string16 handle_name; HANDLE handle = nullptr; int invalid_count = 0;
// Keep incrementing until we hit the number of handles reported by // GetProcessHandleCount(). If we hit a very long sequence of invalid // handles we assume that we've run past the end of the table. // 以句柄号从下限值到上限值迭代,通过NtQueryObject来判断某个句柄值是否有效,然后再匹配 while (handle_count && invalid_count < kInvalidHandleThreshold) { reinterpret_cast<size_t&>(handle) += kHandleOffset; NTSTATUS rc;
// Get the type name, reusing the buffer. // 又见二次调用的操作,只不过做了二层封装 ULONG size = static_cast<ULONG>(type_info_buffer.size()); rc = QueryObjectTypeInformation(handle, type_info, &size);//封装了NtQueryObject的一些操作 while (rc == STATUS_INFO_LENGTH_MISMATCH || rc == STATUS_BUFFER_OVERFLOW) { type_info_buffer.resize(size + sizeof(wchar_t)); type_info = reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); rc = QueryObjectTypeInformation(handle, type_info, &size); // Leave padding for the nul terminator. if (NT_SUCCESS(rc) && size == type_info_buffer.size()) rc = STATUS_INFO_LENGTH_MISMATCH; } // 如果这个句柄号是有效的,那么就说明确实有这个句柄,没有的话就不浪费感情lookup匹配了 if (!NT_SUCCESS(rc) || !type_info->Name.Buffer) { ++invalid_count; continue; }
--handle_count; type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = L'\0'; // 这段是查找传过来的handles_to_close_,看看是否需要关闭该句柄 // 如果需要关闭,那就CloseHandle // Check if we're looking for this type of handle. HandleMap::iterator result = handles_to_close_.find(type_info->Name.Buffer); if (result != handles_to_close_.end()) { HandleMap::mapped_type& names = result->second; // Empty set means close all handles of this type; otherwise check name. if (!names.empty()) { // Move on to the next handle if this name doesn't match. // 这个GetHandleName在handle_closer.cc中实现,实际上依然是NtQueryObject的封装 if (!GetHandleName(handle, &handle_name) || !names.count(handle_name)) continue; }
if (!::SetHandleInformation(handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0)) returnfalse; if (!::CloseHandle(handle)) returnfalse; // Attempt to stuff this handle with a new dummy Event. // 尝试填满Handle槽?不懂什么意思,传入了handle和type AttemptToStuffHandleSlot(handle, result->first); } }
returntrue; }
// Returns the object manager's name associated with a handle // 通过handle反查name,实际上用NtQueryObject这个万用API可以做到,查询的class为 // ObjectNameInformation // NtQueryObject很强大,根据传入的OBJECT_INFORMATION_CLASS值可以查询不同的信息 boolGetHandleName(HANDLE handle, base::string16* handle_name){ static NtQueryObject QueryObject = nullptr; if (!QueryObject) ResolveNTFunctionPtr("NtQueryObject", &QueryObject);
do { name.reset(static_cast<UNICODE_STRING*>(malloc(size))); DCHECK(name.get()); result = QueryObject(handle, ObjectNameInformation, name.get(), size, &size); } while (result == STATUS_INFO_LENGTH_MISMATCH || result == STATUS_BUFFER_OVERFLOW);
// Returns type infomation for an NT object. This routine is expected to be // called for invalid handles so it catches STATUS_INVALID_HANDLE exceptions // that can be generated when handle tracing is enabled. // 返回的是handle对应的type而非name NTSTATUS QueryObjectTypeInformation(HANDLE handle, void* buffer, ULONG* size){ static NtQueryObject QueryObject = nullptr; // 还是用NtQueryObject if (!QueryObject) ResolveNTFunctionPtr("NtQueryObject", &QueryObject);
// Attempts to stuff |closed_handle| with a duplicated handle for a dummy Event // with no access. This should allow the handle to be closed, to avoid // generating EXCEPTION_INVALID_HANDLE on shutdown, but nothing else. For now // the only supported |type| is Event or File. // 关闭句柄时防止产生EXCEPTION_INVALID_HANDLE,仅仅支持Event或File类型句柄 // 这东西用途我还不理解,看起来是对于Event和File句柄做的额外操作,复制一个再CloseHandle // 不懂有什么用 boolHandleCloserAgent::AttemptToStuffHandleSlot(HANDLE closed_handle, const base::string16& type){ // Only attempt to stuff Files and Events at the moment. if (type != L"Event" && type != L"File") { returntrue; }
if (!dummy_handle_.IsValid()) returnfalse;
// This should never happen, as g_dummy is created before closing to_stuff. DCHECK(dummy_handle_.Get() != closed_handle);
// Failure here is a breach of security so the process is terminated. voidTargetServicesBase::LowerToken(){ if (ERROR_SUCCESS != SetProcessIntegrityLevel(g_shared_delayed_integrity_level)) ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_INTEGRITY); process_state_.SetRevertedToSelf(); // If the client code as called RegOpenKey, advapi32.dll has cached some // handles. The following code gets rid of them. // 最不起眼的其实是最关键的call,不过本次我们就不理它了 if (!::RevertToSelf()) ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_DROPTOKEN); if (!FlushCachedRegHandles()) ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_FLUSHANDLES); if (ERROR_SUCCESS != ::RegDisablePredefinedCache()) ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_CACHEDISABLE); if (!WarmupWindowsLocales()) ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_WARMUP); bool is_csrss_connected = true; // 就是它,关闭了句柄,并根据ALPC port是否被关闭来设置csrss的连接状态 if (!CloseOpenHandles(&is_csrss_connected)) ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_CLOSEHANDLES); process_state_.SetCsrssConnected(is_csrss_connected); // Enabling mitigations must happen last otherwise handle closing breaks if (g_shared_delayed_mitigations && !ApplyProcessMitigationsToCurrentProcess(g_shared_delayed_mitigations)) ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_MITIGATION); }
// Checks if we have handle entries pending and runs the closer. // Updates is_csrss_connected based on which handle types are closed. boolCloseOpenHandles(bool* is_csrss_connected){ if (HandleCloserAgent::NeedsHandlesClosed()) {//其实就是!!g_handles_to_close,转bool值判断 // 此时broker已经写好了g_handles_to_close,开始吧 HandleCloserAgent handle_closer; // 第一步,InitializeHandlesToClose handle_closer.InitializeHandlesToClose(is_csrss_connected); if (!*is_csrss_connected) { if (!CsrssDisconnectCleanup()) { returnfalse; } } // 第二步,CloseHandles if (!handle_closer.CloseHandles()) returnfalse; } returntrue; }