diff --git a/src/libdrakvuf/libdrakvuf.h b/src/libdrakvuf/libdrakvuf.h index 779d5a5dc..ecd7741c8 100644 --- a/src/libdrakvuf/libdrakvuf.h +++ b/src/libdrakvuf/libdrakvuf.h @@ -648,6 +648,12 @@ typedef struct _module_info bool is_wow_process ; /* Is WoW64 process? */ } module_info_t ; +typedef struct _object_info +{ + addr_t base_addr; + unicode_string_t* name; +} object_info_t; + bool drakvuf_enumerate_processes(drakvuf_t drakvuf, void (*visitor_func)(drakvuf_t drakvuf, addr_t process, void* visitor_ctx), void* visitor_ctx) NOEXCEPT; @@ -666,6 +672,10 @@ bool drakvuf_enumerate_process_modules(drakvuf_t drakvuf, bool (*visitor_func)(drakvuf_t drakvuf, const module_info_t* module_info, bool* need_free, bool* need_stop, void* visitor_ctx), void* visitor_ctx) NOEXCEPT; +bool drakvuf_enumerate_object_directory(drakvuf_t drakvuf, + void (*visitor_func)(drakvuf_t drakvuf, const object_info_t* object_info, void* visitor_ctx), + void* visitor_ctx) NOEXCEPT; + bool drakvuf_is_crashreporter(drakvuf_t drakvuf, drakvuf_trap_info_t* info, vmi_pid_t* pid) NOEXCEPT; @@ -702,6 +712,10 @@ unicode_string_t* drakvuf_read_unicode32(drakvuf_t drakvuf, drakvuf_trap_info_t* unicode_string_t* drakvuf_read_unicode32_va(drakvuf_t drakvuf, addr_t vaddr, vmi_pid_t pid) NOEXCEPT; +unicode_string_t* drakvuf_get_object_type_name(drakvuf_t drakvuf, addr_t object) NOEXCEPT; + +unicode_string_t* drakvuf_get_object_name(drakvuf_t drakvuf, addr_t object) NOEXCEPT; + bool drakvuf_get_module_base_addr( drakvuf_t drakvuf, addr_t module_list_head, const char* module_name, diff --git a/src/libdrakvuf/os.c b/src/libdrakvuf/os.c index f7261056d..4031c21da 100644 --- a/src/libdrakvuf/os.c +++ b/src/libdrakvuf/os.c @@ -915,6 +915,20 @@ bool drakvuf_enumerate_process_modules(drakvuf_t drakvuf, addr_t eprocess, bool return ret; } +bool drakvuf_enumerate_object_directory(drakvuf_t drakvuf, void (*visitor_func)(drakvuf_t drakvuf, const object_info_t* object_info, void* visitor_ctx), void* visitor_ctx) +{ + bool ret = false; + + if (drakvuf->osi.enumerate_object_directory) + { + drakvuf_lock_and_get_vmi(drakvuf); + ret = drakvuf->osi.enumerate_object_directory(drakvuf, visitor_func, visitor_ctx); + drakvuf_release_vmi(drakvuf); + } + + return ret; +} + bool drakvuf_is_crashreporter(drakvuf_t drakvuf, drakvuf_trap_info_t* info, vmi_pid_t* pid) { bool ret = false; @@ -1191,3 +1205,31 @@ const kernel_version_t* drakvuf_get_kernel_version(drakvuf_t drakvuf, drakvuf_tr return ret; } + +unicode_string_t* drakvuf_get_object_name(drakvuf_t drakvuf, addr_t object) +{ + unicode_string_t* ret = NULL; + + if (drakvuf->osi.get_object_name) + { + drakvuf_lock_and_get_vmi(drakvuf); + ret = drakvuf->osi.get_object_name(drakvuf, object); + drakvuf_release_vmi(drakvuf); + } + + return ret; +} + +unicode_string_t* drakvuf_get_object_type_name(drakvuf_t drakvuf, addr_t object) +{ + unicode_string_t* ret = NULL; + + if (drakvuf->osi.get_object_type_name) + { + drakvuf_lock_and_get_vmi(drakvuf); + ret = drakvuf->osi.get_object_type_name(drakvuf, object); + drakvuf_release_vmi(drakvuf); + } + + return ret; +} diff --git a/src/libdrakvuf/os.h b/src/libdrakvuf/os.h index 31dc250bc..b7eef8807 100644 --- a/src/libdrakvuf/os.h +++ b/src/libdrakvuf/os.h @@ -246,6 +246,9 @@ typedef struct os_interface bool (*enumerate_process_modules) (drakvuf_t drakvuf, addr_t eprocess, bool (*visitor_func)(drakvuf_t drakvuf, const module_info_t* module_info, bool* need_free, bool* need_stop, void* visitor_ctx), void* visitor_ctx); + bool (*enumerate_object_directory) + (drakvuf_t drakvuf, void (*visitor_func)(drakvuf_t drakvuf, const object_info_t* object_info, void* visitor_ctx), void* visitor_ctx); + bool (*is_crashreporter) (drakvuf_t drakvuf, drakvuf_trap_info_t* info, vmi_pid_t* pid); @@ -314,6 +317,12 @@ typedef struct os_interface bool (*get_kernel_symbol_va) (drakvuf_t drakvuf, const char* function, addr_t* va); + unicode_string_t* (*get_object_name) + (drakvuf_t drakvuf, addr_t object); + + unicode_string_t* (*get_object_type_name) + (drakvuf_t drakvuf, addr_t object); + } os_interface_t; bool set_os_windows(drakvuf_t drakvuf); diff --git a/src/libdrakvuf/private.h b/src/libdrakvuf/private.h index 9e2971bcf..b38521b9e 100644 --- a/src/libdrakvuf/private.h +++ b/src/libdrakvuf/private.h @@ -225,6 +225,9 @@ struct drakvuf xen_pfn_t max_gpfn; addr_t kernbase; addr_t kpgd; + uint8_t ob_header_cookie; + addr_t ob_infomask2off; + addr_t ob_type_table; size_t address_width; diff --git a/src/libdrakvuf/win-offsets-map.h b/src/libdrakvuf/win-offsets-map.h index 31a57f40a..8672e2067 100644 --- a/src/libdrakvuf/win-offsets-map.h +++ b/src/libdrakvuf/win-offsets-map.h @@ -174,6 +174,7 @@ static const char* win_offset_names[__WIN_OFFSETS_MAX][2] = [KPCR_PRCBDATA] = {"_KPCR", "PrcbData" }, [KPCR_IRQL] = { "_KPCR", "Irql" }, [KPRCB_CURRENTTHREAD] = { "_KPRCB", "CurrentThread" }, + [KPRCB_RSPBASE] = { "_KPRCB", "RspBase" }, [KTHREAD_APCSTATE] = {"_KTHREAD", "ApcState" }, [KTHREAD_APCSTATEINDEX] = {"_KTHREAD", "ApcStateIndex" }, [KTHREAD_PROCESS] = {"_KTHREAD", "Process" }, @@ -195,6 +196,11 @@ static const char* win_offset_names[__WIN_OFFSETS_MAX][2] = [CLIENT_ID_UNIQUETHREAD] = {"_CLIENT_ID", "UniqueThread" }, [OBJECT_HEADER_TYPEINDEX] = { "_OBJECT_HEADER", "TypeIndex" }, [OBJECT_HEADER_BODY] = { "_OBJECT_HEADER", "Body" }, + [OBJECT_HEADER_INFOMASK] = { "_OBJECT_HEADER", "InfoMask" }, + [OBJECT_HEADER_NAME_INFO_NAME] = { "_OBJECT_HEADER_NAME_INFO", "Name" }, + [OBJECT_DIRECTORY_ENTRY_CHAINLINK] = { "_OBJECT_DIRECTORY_ENTRY", "ChainLink" }, + [OBJECT_DIRECTORY_ENTRY_OBJECT] = { "_OBJECT_DIRECTORY_ENTRY", "Object" }, + [OBJECT_TYPE_NAME] = { "_OBJECT_TYPE", "Name" }, [POOL_HEADER_BLOCKSIZE] = {"_POOL_HEADER", "BlockSize" }, [POOL_HEADER_POOLTYPE] = {"_POOL_HEADER", "PoolType" }, [POOL_HEADER_POOLTAG] = {"_POOL_HEADER", "PoolTag" }, diff --git a/src/libdrakvuf/win-offsets.h b/src/libdrakvuf/win-offsets.h index 97a3f6d9d..1d97fc8d4 100644 --- a/src/libdrakvuf/win-offsets.h +++ b/src/libdrakvuf/win-offsets.h @@ -206,7 +206,11 @@ enum win_offsets OBJECT_HEADER_TYPEINDEX, OBJECT_HEADER_BODY, - + OBJECT_HEADER_INFOMASK, + OBJECT_HEADER_NAME_INFO_NAME, + OBJECT_DIRECTORY_ENTRY_CHAINLINK, + OBJECT_DIRECTORY_ENTRY_OBJECT, + OBJECT_TYPE_NAME, POOL_HEADER_BLOCKSIZE, POOL_HEADER_POOLTYPE, POOL_HEADER_POOLTAG, @@ -259,6 +263,7 @@ enum win_sizes { EPROCESS, HANDLE_TABLE_ENTRY, + OBJECT_HEADER, __WIN_SIZES_MAX }; diff --git a/src/libdrakvuf/win.c b/src/libdrakvuf/win.c index e7e2eb9b9..ad757ab1f 100644 --- a/src/libdrakvuf/win.c +++ b/src/libdrakvuf/win.c @@ -424,6 +424,107 @@ static bool find_kernbase(drakvuf_t drakvuf) return 1; } +unicode_string_t* win_get_object_name(drakvuf_t drakvuf, addr_t object) +{ + // Object header is always present before actual object. + // + size_t ptrsize = drakvuf_get_address_width(drakvuf); + addr_t header = object - drakvuf->sizes[OBJECT_HEADER] + ptrsize; + + uint8_t infomask = 0, name_info_off = 0; + + if (VMI_SUCCESS != vmi_read_8_va(drakvuf->vmi, header + drakvuf->offsets[OBJECT_HEADER_INFOMASK], 0, &infomask)) + return NULL; + // Get object name. Some objects are anonymous. See ObQueryNameInfo for more info. + // + if (infomask & 2) + { + if (VMI_SUCCESS != vmi_read_8_va(drakvuf->vmi, drakvuf->ob_infomask2off + (infomask & 3), 0, &name_info_off)) + return NULL; + return drakvuf_read_unicode_va(drakvuf, header - name_info_off + drakvuf->offsets[OBJECT_HEADER_NAME_INFO_NAME], 0); + } + return NULL; +} + +unicode_string_t* win_get_object_type_name(drakvuf_t drakvuf, addr_t object) +{ + // Object header is always present before actual object. + // + size_t ptrsize = drakvuf_get_address_width(drakvuf); + addr_t header = object - drakvuf->sizes[OBJECT_HEADER] + ptrsize; + + uint8_t index = 0; + if (VMI_SUCCESS != vmi_read_8_va(drakvuf->vmi, header + drakvuf->offsets[OBJECT_HEADER_TYPEINDEX], 0, &index)) + { + return NULL; + } + // https://medium.com/@ashabdalhalim/a-light-on-windows-10s-object-header-typeindex-value-e8f907e7073a + // Due to security mitigations type_index is no longer equals to index in ObTypeIndexTable array on win 10 + // but calculated as following: + if (vmi_get_winver(drakvuf->vmi) == VMI_OS_WINDOWS_10) + { + index = index ^ ((header >> 8) & 0xff) ^ drakvuf->ob_header_cookie; + } + + addr_t type = 0; + if (VMI_SUCCESS != vmi_read_addr_va(drakvuf->vmi, drakvuf->ob_type_table + index * ptrsize, 0, &type)) + { + return NULL; + } + return drakvuf_read_unicode_va(drakvuf, type + drakvuf->offsets[OBJECT_TYPE_NAME], 0); +} + +static bool enumerate_directory(drakvuf_t drakvuf, addr_t directory, void (*visitor_func)(drakvuf_t drakvuf, const object_info_t* object_info, void* visitor_ctx), void* visitor_ctx) +{ + // There is only 37 _OBJECT_DIRECTORY_ENTRY entries in object directory: + // 0: kd> dt nt!_OBJECT_DIRECTORY + // +0x000 HashBuckets : [37] Ptr64 _OBJECT_DIRECTORY_ENTRY + // +0x128 Lock : _EX_PUSH_LOCK + // ... + for (int i = 0; i < 37; i++) + { + addr_t bucket = 0; + if (VMI_SUCCESS != vmi_read_addr_va(drakvuf->vmi, directory + drakvuf_get_address_width(drakvuf) * i, 0, &bucket) || !bucket) + continue; + + while (true) + { + addr_t object = 0; + if (VMI_SUCCESS != vmi_read_addr_va(drakvuf->vmi, bucket + drakvuf->offsets[OBJECT_DIRECTORY_ENTRY_OBJECT], 0, &object) || !object) + break; + + unicode_string_t* name = win_get_object_type_name(drakvuf, object); + if (!name) + { + return false; + } + + object_info_t object_info = { .base_addr = object, .name = name }; + visitor_func(drakvuf, &object_info, visitor_ctx); + + if (!strcmp((const char*)name->contents, "Directory")) + { + enumerate_directory(drakvuf, object, visitor_func, visitor_ctx); + } + vmi_free_unicode_str(name); + + if (VMI_SUCCESS != vmi_read_addr_va(drakvuf->vmi, bucket + drakvuf->offsets[OBJECT_DIRECTORY_ENTRY_CHAINLINK], 0, &bucket) || !bucket) + break; + } + } + return true; +} + +bool win_enumerate_object_directory(drakvuf_t drakvuf, void (*visitor_func)(drakvuf_t drakvuf, const object_info_t* object_info, void* visitor_ctx), void* visitor_ctx) +{ + addr_t root_directory_object = 0; + if (VMI_SUCCESS != vmi_read_addr_ksym(drakvuf->vmi, "ObpRootDirectoryObject", &root_directory_object)) + { + return false; + } + return enumerate_directory(drakvuf, root_directory_object, visitor_func, visitor_ctx); +} + bool win_is_wow64(drakvuf_t drakvuf, drakvuf_trap_info_t* info) { // check if we're in kernel mode @@ -554,11 +655,19 @@ bool set_os_windows(drakvuf_t drakvuf) PRINT_DEBUG("Loaded WoW64 offsets...\n"); } - if ( VMI_FAILURE == vmi_get_struct_size_from_json(drakvuf->vmi, vmi_get_kernel_json(drakvuf->vmi), "_HANDLE_TABLE_ENTRY", &drakvuf->sizes[HANDLE_TABLE_ENTRY]) ) + if (VMI_FAILURE == vmi_get_struct_size_from_json(drakvuf->vmi, vmi_get_kernel_json(drakvuf->vmi), "_HANDLE_TABLE_ENTRY", &drakvuf->sizes[HANDLE_TABLE_ENTRY] ) || + VMI_FAILURE == vmi_get_struct_size_from_json(drakvuf->vmi, vmi_get_kernel_json(drakvuf->vmi), "_OBJECT_HEADER", &drakvuf->sizes[OBJECT_HEADER])) + { + return 0; + } + + if (VMI_FAILURE == vmi_translate_ksym2v(drakvuf->vmi, "ObpInfoMaskToOffset", &drakvuf->ob_infomask2off) || + VMI_FAILURE == vmi_translate_ksym2v(drakvuf->vmi, "ObTypeIndexTable", &drakvuf->ob_type_table)) { return 0; } - if ( VMI_FAILURE == vmi_get_struct_size_from_json(drakvuf->vmi, vmi_get_kernel_json(drakvuf->vmi), "_EPROCESS", &drakvuf->sizes[EPROCESS]) ) + + if (vmi_get_winver(drakvuf->vmi) == VMI_OS_WINDOWS_10 && VMI_FAILURE == vmi_read_8_ksym(drakvuf->vmi, "ObHeaderCookie", &drakvuf->ob_header_cookie)) { return 0; } @@ -606,6 +715,7 @@ bool set_os_windows(drakvuf_t drakvuf) drakvuf->osi.enumerate_processes_with_module = win_enumerate_processes_with_module; drakvuf->osi.enumerate_drivers = win_enumerate_drivers; drakvuf->osi.enumerate_process_modules = win_enumerate_process_modules; + drakvuf->osi.enumerate_object_directory = win_enumerate_object_directory; drakvuf->osi.is_crashreporter = win_is_crashreporter; drakvuf->osi.find_mmvad = win_find_mmvad; drakvuf->osi.traverse_mmvad = win_traverse_mmvad; @@ -625,6 +735,8 @@ bool set_os_windows(drakvuf_t drakvuf) drakvuf->osi.get_rspbase = win_get_rspbase; drakvuf->osi.get_kernel_symbol_rva = win_get_kernel_symbol_rva; drakvuf->osi.get_kernel_symbol_va = win_get_kernel_symbol_va; + drakvuf->osi.get_object_type_name = win_get_object_type_name; + drakvuf->osi.get_object_name = win_get_object_name; return true; } diff --git a/src/libdrakvuf/win.h b/src/libdrakvuf/win.h index d047df3cc..3c24b9ddc 100644 --- a/src/libdrakvuf/win.h +++ b/src/libdrakvuf/win.h @@ -176,6 +176,7 @@ bool win_find_eprocess(drakvuf_t drakvuf, vmi_pid_t find_pid, const char* find_p bool win_enumerate_processes(drakvuf_t drakvuf, void (*visitor_func)(drakvuf_t drakvuf, addr_t eprocess, void* visitor_ctx), void* visitor_ctx); bool win_enumerate_processes_with_module(drakvuf_t drakvuf, const char* module_name, bool (*visitor_func)(drakvuf_t drakvuf, const module_info_t* module_info, void* visitor_ctx), void* visitor_ctx); bool win_enumerate_drivers(drakvuf_t drakvuf, bool (*visitor_func)(drakvuf_t drakvuf, const module_info_t* module_info, bool* need_free, bool* need_stop, void* visitor_ctx), void* visitor_ctx); +bool win_enumerate_object_directory(drakvuf_t drakvuf, void (*visitor_func)(drakvuf_t drakvuf, const object_info_t* object_info, void* visitor_ctx), void* visitor_ctx); bool win_is_crashreporter(drakvuf_t drakvuf, drakvuf_trap_info_t* info, vmi_pid_t* pid); @@ -221,4 +222,7 @@ bool win_get_kernel_symbol_rva(drakvuf_t drakvuf, const char* function, addr_t* bool win_get_kernel_symbol_va(drakvuf_t drakvuf, const char* function, addr_t* va); +unicode_string_t* win_get_object_name(drakvuf_t drakvuf, addr_t object); +unicode_string_t* win_get_object_type_name(drakvuf_t drakvuf, addr_t object); + #endif diff --git a/src/plugins/callbackmon/callbackmon.cpp b/src/plugins/callbackmon/callbackmon.cpp index f1295e7e9..5abf96297 100644 --- a/src/plugins/callbackmon/callbackmon.cpp +++ b/src/plugins/callbackmon/callbackmon.cpp @@ -109,6 +109,8 @@ #include "callbackmon.h" #include "private.h" +using namespace callbackmon_ns; + static constexpr uint16_t win_vista_ver = 6000; static constexpr uint16_t win_vista_sp1_ver = 6001; static constexpr uint16_t win_7_sp1_ver = 7601; @@ -116,7 +118,7 @@ static constexpr uint16_t win_8_1_ver = 9600; static constexpr uint16_t win_10_rs1_ver = 14393; static constexpr uint16_t win_10_1803_ver = 17134; -static const std::vector callout_syms = +static const std::vector callout_symbols = { "PspW32ProcessCallout", "PspW32ThreadCallout", @@ -145,21 +147,6 @@ static const std::unordered_map> wf { win_10_1803_ver, { 0x50, 0x190, 0x198 } }, }; -namespace -{ -struct pass_ctx -{ - addr_t cb_va; - std::string name = ""; - addr_t base_va = 0; - callbackmon const* plugin; - - pass_ctx(callbackmon const* plugin, addr_t cb_va) : cb_va(cb_va), plugin{plugin} - { - } -}; -} - static inline size_t get_process_cb_table_size(uint16_t winver) { if (winver >= win_vista_sp1_ver) @@ -201,31 +188,6 @@ static inline size_t get_power_cb_offset(vmi_instance_t vmi) return vmi_get_address_width(vmi) == 8 ? 0x40 : 0x28; } -static bool driver_visitor(drakvuf_t drakvuf, const module_info_t* module_info, bool* need_free, bool* need_stop, void* ctx) -{ - auto data = static_cast(ctx); - - vmi_lock_guard vmi(drakvuf); - - if (data->cb_va >= module_info->base_addr && data->cb_va < module_info->base_addr + module_info->size) - { - if (module_info->full_name) - { - data->name.assign(reinterpret_cast(module_info->full_name->contents)); - } - - data->base_va = module_info->base_addr; - } - return true; -} - -static inline std::pair get_module_by_addr(drakvuf_t drakvuf, callbackmon* plugin, addr_t addr) -{ - pass_ctx ctx{ plugin, addr }; - drakvuf_enumerate_drivers(drakvuf, driver_visitor, &ctx); - return { ctx.name, ctx.base_va }; -} - static protocol_cb_t collect_protocol_callbacks(drakvuf_t drakvuf, vmi_instance_t vmi, callbackmon* plugin, addr_t protocol_block) { protocol_cb_t out; @@ -307,8 +269,8 @@ static bool consume_ndis_protocols(drakvuf_t drakvuf, vmi_instance_t vmi, callba // if (winver == VMI_OS_WINDOWS_10) { - if (!json_get_struct_members_array_rva(drakvuf, profile_json, offset_generic_names_w10, __OFFSET_GENERIC_MAX, plugin->generic_offsets) || - !json_get_struct_members_array_rva(drakvuf, profile_json, offset_open_names_w10, __OFFSET_OPEN_MAX, plugin->open_offsets)) + if (!json_get_struct_members_array_rva(drakvuf, profile_json, offset_generic_names_w10, plugin->generic_offsets.size(), plugin->generic_offsets.data()) || + !json_get_struct_members_array_rva(drakvuf, profile_json, offset_open_names_w10, plugin->open_offsets.size(), plugin->open_offsets.data())) { json_object_put(profile_json); return false; @@ -316,15 +278,15 @@ static bool consume_ndis_protocols(drakvuf_t drakvuf, vmi_instance_t vmi, callba } else { - if (!json_get_struct_members_array_rva(drakvuf, profile_json, offset_generic_names_w7, __OFFSET_GENERIC_MAX, plugin->generic_offsets) || - !json_get_struct_members_array_rva(drakvuf, profile_json, offset_open_names_w7, __OFFSET_OPEN_MAX, plugin->open_offsets)) + if (!json_get_struct_members_array_rva(drakvuf, profile_json, offset_generic_names_w7, plugin->generic_offsets.size(), plugin->generic_offsets.data()) || + !json_get_struct_members_array_rva(drakvuf, profile_json, offset_open_names_w7, plugin->open_offsets.size(), plugin->open_offsets.data())) { json_object_put(profile_json); return false; } } - if (!json_get_struct_members_array_rva(drakvuf, profile_json, offset_miniport_names, __OFFSET_MINIPORT_MAX, plugin->miniport_offsets) || + if (!json_get_struct_members_array_rva(drakvuf, profile_json, offset_miniport_names, plugin->miniport_offsets.size(), plugin->miniport_offsets.data()) || !json_get_symbol_rva(drakvuf, profile_json, protocol_symname, &ndis_protocol_list_rva)) { json_object_put(profile_json); @@ -350,39 +312,224 @@ static bool consume_ndis_protocols(drakvuf_t drakvuf, vmi_instance_t vmi, callba if (VMI_SUCCESS != vmi_read_addr_va(vmi, protocol_head + plugin->generic_offsets[NDIS_PROTOCOL_BLOCK_NEXTPROTOCOL], 0, &protocol_head)) { PRINT_DEBUG("[CALLBACKMON] Failed to read next protocol\n"); - throw -1; + return false; + } + } + return true; +} + +static std::vector get_callback_object_callbacks(drakvuf_t drakvuf, callbackmon* plugin, addr_t object) +{ + std::vector out; + // typedef struct _CALLBACK_OBJECT <- undocumented + // { + // ULONG Signature; // 0x00 + // KSPIN_LOCK Lock; // 0x08 + // LIST_ENTRY RegisteredCallbacks; // 0x10 + // BOOLEAN AllowMultipleCallbacks; + // UCHAR reserved[3]; + // } CALLBACK_OBJECT, *PCALLBACK_OBJECT; + const addr_t callbacks_off = drakvuf_get_address_width(drakvuf) * 2; + // typedef struct _CALLBACK_REGISTRATION <- undocumented + // { + // LIST_ENTRY Link; // 0x00 + // PCALLBACK_OBJECT CallbackObject; // 0x10 + // PCALLBACK_FUNCTION CallbackFunction; // 0x18 + // PVOID CallbackContext; + // ULONG Busy; + // BOOLEAN UnregisterWaiting; + // } CALLBACK_REGISTRATION, *PCALLBACK_REGISTRATION; + const addr_t callback_fn_off = drakvuf_get_address_width(drakvuf) * 3; + // Read flink entry. + // + addr_t head = object + callbacks_off; + addr_t entry = 0; + + vmi_lock_guard vmi(drakvuf); + + if (VMI_SUCCESS != vmi_read_addr_va(vmi, head, 0, &entry)) + return out; + + while (entry != head && entry) + { + addr_t callback{}; + if (VMI_SUCCESS != vmi_read_addr_va(vmi, entry + callback_fn_off, 0, &callback) || + VMI_SUCCESS != vmi_read_addr_va(vmi, entry, 0, &entry)) + return out; + if (callback) + { + out.push_back(callback); + } + } + return out; +} + +static bool consume_object_callbacks(drakvuf_t drakvuf, vmi_instance_t vmi, callbackmon* plugin) +{ + if (drakvuf_get_address_width(drakvuf) != 8) + return true; + // Enumerate callback objects. + // + if (!drakvuf_enumerate_object_directory(drakvuf, [](drakvuf_t drakvuf, const object_info_t* info, void* ctx) +{ + auto plugin = static_cast(ctx); + + if (!strcmp((const char*)info->name->contents, "Callback")) + { + auto name = drakvuf_get_object_name(drakvuf, info->base_addr); + plugin->object_cb.push_back( + { + .base = info->base_addr, + .name = name ? (const char*)name->contents : "Anonymous", + .callbacks = get_callback_object_callbacks(drakvuf, plugin, info->base_addr) + }); + if (name) vmi_free_unicode_str(name); + } + }, plugin)) + { + return false; + } + // Enumerate every object type. First 2 entries are not used. + // + for (size_t i = 2; ; i++) + { + object_type_t ob_type{}; + if (VMI_SUCCESS != vmi_read_addr_va(vmi, plugin->ob_type_table_va + i * drakvuf_get_address_width(drakvuf), 0, &ob_type.base)) + return false; + // if reached the end. + // + if (!ob_type.base) + break; + // Collect type initializer callbacks. + // + for (size_t cb_n = 0; cb_n < 8; cb_n++) + { + addr_t callback{}; + if (VMI_SUCCESS != vmi_read_addr_va(vmi, ob_type.base + plugin->offsets[OBJECT_TYPE_TYPEINFO] + plugin->offsets[OBJECT_TYPE_INITIALIZER_DUMP_CB] + cb_n * drakvuf_get_address_width(drakvuf), 0, &callback)) + { + return false; + } + if (callback) + { + ob_type.initializer.push_back(callback); + } } + // Collect object type callbacks. + // + addr_t head = ob_type.base + plugin->offsets[OBJECT_TYPE_CALLBACKLIST]; + addr_t entry{}; + if (VMI_SUCCESS != vmi_read_addr_va(vmi, head, 0, &entry)) + return false; + + while (entry != head && entry) + { + uint32_t active{}; + addr_t pre_cb{}, post_cb{}; + // Undocumented structure. No symbols. + // typedef struct _CALLBACK_ENTRY_ITEM + // { + // LIST_ENTRY CallbackList; // 0x0 + // OB_OPERATION Operations; // 0x10 + // DWORD Active; // 0x14 + // CALLBACK_ENTRY *CallbackEntry; // 0x18 + // PVOID ObjectType; // 0x20 + // POB_PRE_OPERATION_CALLBACK PreOperation; // 0x28 + // POB_POST_OPERATION_CALLBACK PostOperation; // 0x30 + // QWORD unk1; // 0x38 + // } CALLBACK_ENTRY_ITEM, *PCALLBACK_ENTRY_ITEM; // size: 0x40 + if (VMI_SUCCESS != vmi_read_32_va (vmi, entry + 0x14, 0, &active) || + VMI_SUCCESS != vmi_read_addr_va(vmi, entry + 0x28, 0, &pre_cb) || + VMI_SUCCESS != vmi_read_addr_va(vmi, entry + 0x30, 0, &post_cb) || + VMI_SUCCESS != vmi_read_addr_va(vmi, entry, 0, &entry)) + { + return false; + } + + if (active) + { + if (pre_cb) + ob_type.callbacks.push_back({ .base = entry, .callback = pre_cb }); + if (post_cb) + ob_type.callbacks.push_back({ .base = entry, .callback = post_cb }); + } + } + // Fill object type name. + // + if (auto name = drakvuf_get_object_name(drakvuf, ob_type.base)) + { + ob_type.name = (const char*)name->contents; + vmi_free_unicode_str(name); + } + else + { + ob_type.name = "Anonymous"; + } + plugin->object_type.push_back(std::move(ob_type)); } return true; } +event_response_t callbackmon::load_unload_cb(drakvuf_t drakvuf, drakvuf_trap_info_t* info) +{ + auto entry = drakvuf_get_function_argument(drakvuf, info, 1); + + vmi_lock_guard vmi(drakvuf); + + if (drakvuf_get_function_argument(drakvuf, info, 2)) + { + callbackmon_module_t module_info{}; + if (VMI_SUCCESS == vmi_read_addr_va(vmi, entry + offsets[LDR_TABLE_ENTRY_DLLBASE], info->proc_data.pid, &module_info.base) && + VMI_SUCCESS == vmi_read_addr_va(vmi, entry + offsets[LDR_TABLE_ENTRY_SIZEOFIMAGE], info->proc_data.pid, &module_info.size)) + { + if (auto name = vmi_read_unicode_str_va(vmi, entry + offsets[LDR_TABLE_ENTRY_FULLDLLNAME], info->proc_data.pid)) + { + module_info.name.assign((const char*)name->contents); + drivers.push_back(std::move(module_info)); + vmi_free_unicode_str(name); + } + } + } + return VMI_EVENT_RESPONSE_NONE; +} + void callbackmon::report(drakvuf_t drakvuf, const char* list_name, addr_t addr, const char* action) { - const auto& [name, base] = get_module_by_addr(drakvuf, this, addr); + auto get_module_by_addr = [&]() -> callbackmon_module_t + { + for (const auto& module : this->drivers) + { + if (addr >= module.base && addr < module.base + module.size) + { + return module; + } + } + return { .base = 0, .size = 0, .name = "" }; + }; + + const auto& module = get_module_by_addr(); fmt::print(format, "callbackmon", drakvuf, nullptr, - keyval("Type", fmt::Qstr("Callback")), - keyval("ListName", fmt::Qstr(list_name)), - keyval("Module", fmt::Qstr(name)), - keyval("RVA", fmt::Xval(base ? addr - base : 0)), - keyval("Action", fmt::Qstr(action)) + keyval("Type", fmt::Rstr("Callback")), + keyval("ListName", fmt::Estr(list_name)), + keyval("Module", fmt::Estr(module.name)), + keyval("RVA", fmt::Xval(module.base ? addr - module.base : 0)), + keyval("Action", fmt::Estr(action)) ); } callbackmon::callbackmon(drakvuf_t drakvuf, const callbackmon_config* config, output_format_t output) - : pluginex(drakvuf, output), config{ *config }, format{ output }, - generic_offsets(new size_t[__OFFSET_GENERIC_MAX]), - open_offsets(new size_t[__OFFSET_OPEN_MAX]), - miniport_offsets(new size_t[__OFFSET_MINIPORT_MAX]) + : pluginex(drakvuf, output), config{ *config }, format{ output } { const addr_t krnl_base = drakvuf_get_kernel_base(drakvuf); const size_t ptrsize = drakvuf_get_address_width(drakvuf); const size_t fast_ref = (ptrsize == 8 ? 15 : 7); - addr_t drv_obj_rva, mj_array_rva; - if (!drakvuf_get_kernel_struct_member_rva(drakvuf, "_DEVICE_OBJECT", "DriverObject", &drv_obj_rva) || - !drakvuf_get_kernel_struct_member_rva(drakvuf, "_DRIVER_OBJECT", "MajorFunction", &mj_array_rva) || - !drakvuf_get_kernel_struct_member_rva(drakvuf, "_LDR_DATA_TABLE_ENTRY", "FullDllName", &ldr_data_name_rva) || - !drakvuf_get_kernel_struct_member_rva(drakvuf, "_LDR_DATA_TABLE_ENTRY", "DllBase", &ldr_data_base_rva) || - !drakvuf_get_kernel_struct_member_rva(drakvuf, "_LDR_DATA_TABLE_ENTRY", "SizeOfImage", &ldr_data_size_rva)) + + if (!drakvuf_get_kernel_struct_members_array_rva(drakvuf, offset_names, offsets.size(), offsets.data())) + { + PRINT_DEBUG("[CALLBACKMON] Failed to get kernel struct member offsets\n"); + throw -1; + } + + if (!drakvuf_get_kernel_symbol_va(drakvuf, "ObTypeIndexTable", &ob_type_table_va)) { throw -1; } @@ -440,17 +587,17 @@ callbackmon::callbackmon(drakvuf_t drakvuf, const callbackmon_config* config, ou return out; }; // Extract IRP_MJ_SHUTDOWN from device objects - auto extract_cb = [&](std::vector dev_objs) -> std::vector + auto extract_cb = [&](std::vector devices) -> std::vector { constexpr size_t irp_mj_shutdown = 0x10; - for (auto& dev_obj : dev_objs) + for (auto& device : devices) { - addr_t drv_obj = 0; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, dev_obj + drv_obj_rva, 4, &drv_obj) || - VMI_SUCCESS != vmi_read_addr_va(vmi, drv_obj + mj_array_rva + irp_mj_shutdown * ptrsize, 4, &dev_obj)) + addr_t driver{}; + if (VMI_SUCCESS != vmi_read_addr_va(vmi, device + offsets[DEVICE_OBJECT_DRIVER_OBJECT], 0, &driver) || + VMI_SUCCESS != vmi_read_addr_va(vmi, device + offsets[DRIVER_OBJECT_MAJOR_FUNCTION] + irp_mj_shutdown * ptrsize, 0, &device)) throw -1; } - return dev_objs; + return devices; }; // Extract PsWin32Callouts auto consume_w32callouts = [&]() -> std::vector @@ -458,10 +605,10 @@ callbackmon::callbackmon(drakvuf_t drakvuf, const callbackmon_config* config, ou std::vector out; if (vmi_get_win_buildnumber(vmi) < win_8_1_ver) { - for (const auto& sym : callout_syms) + for (const auto& symbol : callout_symbols) { addr_t fn = 0; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, get_ksymbol_va(sym), 4, &fn)) + if (VMI_SUCCESS != vmi_read_addr_va(vmi, get_ksymbol_va(symbol), 4, &fn)) throw -1; out.push_back(fn); } @@ -492,8 +639,10 @@ callbackmon::callbackmon(drakvuf_t drakvuf, const callbackmon_config* config, ou throw -1; addr_t gwfp_rva; - json_get_symbol_rva(drakvuf, profile_json, "gWfpGlobal", &gwfp_rva); + bool res = json_get_symbol_rva(drakvuf, profile_json, "gWfpGlobal", &gwfp_rva); json_object_put(profile_json); + if (!res) + throw -1; addr_t list_head; if (VMI_SUCCESS != vmi_read_addr_ksym(vmi, "PsLoadedModuleList", &list_head)) @@ -533,6 +682,13 @@ callbackmon::callbackmon(drakvuf_t drakvuf, const callbackmon_config* config, ou throw -1; } } + + if (!consume_object_callbacks(drakvuf, vmi, this)) + { + PRINT_DEBUG("[CALLBACKMON] Failed to process object callbacks\n"); + throw -1; + } + this->process_cb = consume_callbacks_ex("PspCreateProcessNotifyRoutine", get_cb_table_size(vmi, "process")); this->thread_cb = consume_callbacks_ex("PspCreateThreadNotifyRoutine", get_cb_table_size(vmi, "thread")); this->image_cb = consume_callbacks_ex("PspLoadImageNotifyRoutine", get_cb_table_size(vmi, "image")); @@ -554,6 +710,18 @@ callbackmon::callbackmon(drakvuf_t drakvuf, const callbackmon_config* config, ou this->emp_cb = consume_callbacks("EmpCallbackListHead", -3 * ptrsize); this->w32callouts = consume_w32callouts(); this->wfpcallouts = consume_wfpcallouts(this->config.netio_profile); + + drakvuf_enumerate_drivers(drakvuf, [](drakvuf_t drakvuf, const module_info_t* info, bool*, bool*, void* ctx) + { + static_cast(ctx)->drivers.push_back( + { + .base = info->base_addr, + .size = info->size, + .name = (const char*)info->full_name->contents + }); + return true; + }, this); + this->driver_hook = createSyscallHook("MiProcessLoaderEntry", &callbackmon::load_unload_cb); } bool callbackmon::stop_impl() @@ -591,9 +759,9 @@ bool callbackmon::stop_impl() { if (winver < win_8_1_ver) { - for (size_t i = 0; i < callout_syms.size(); i++) + for (size_t i = 0; i < callout_symbols.size(); i++) if (previous[i] != current[i]) - report(drakvuf, callout_syms[i], current[i], "Replaced"); + report(drakvuf, callout_symbols[i], current[i], "Replaced"); } else { @@ -650,12 +818,33 @@ bool callbackmon::stop_impl() check_callouts (this->w32callouts, snapshot->w32callouts); check_ndis_cbs (this->ndis_protocol_cb, snapshot->ndis_protocol_cb); - return true; -} + for (const auto& past_object : this->object_cb) + { + const auto& new_object = std::find_if(snapshot->object_cb.begin(), snapshot->object_cb.end(), + [&past_object](const auto& object) + { + return object.base == past_object.base; + }); -callbackmon::~callbackmon() -{ - delete[] generic_offsets; - delete[] open_offsets; - delete[] miniport_offsets; + if (new_object != snapshot->object_cb.end()) + { + check_callbacks(past_object.callbacks, new_object->callbacks, past_object.name.c_str()); + } + } + + for (const auto& past_object : this->object_type) + { + const auto& new_object = std::find_if(snapshot->object_type.begin(), snapshot->object_type.end(), + [&past_object](const auto& object) + { + return object.base == past_object.base; + }); + + if (new_object != snapshot->object_type.end()) + { + check_callbacks(past_object.callbacks, new_object->callbacks, past_object.name.c_str()); + check_callbacks(past_object.initializer, new_object->initializer, past_object.name.c_str()); + } + } + return true; } diff --git a/src/plugins/callbackmon/callbackmon.h b/src/plugins/callbackmon/callbackmon.h index 399548520..f71b4d519 100644 --- a/src/plugins/callbackmon/callbackmon.h +++ b/src/plugins/callbackmon/callbackmon.h @@ -104,6 +104,7 @@ #pragma once #include "plugins/plugins_ex.h" +#include "private.h" struct callbackmon_config { @@ -118,20 +119,20 @@ class callbackmon : public pluginex { public: callbackmon(drakvuf_t drakvuf, const callbackmon_config* config, output_format_t output); - ~callbackmon(); void report(drakvuf_t drakvuf, const char* list_name, addr_t addr, const char* action); + event_response_t load_unload_cb(drakvuf_t drakvuf, drakvuf_trap_info_t* info); + const callbackmon_config config; const output_format_t format; - addr_t ldr_data_name_rva; - addr_t ldr_data_base_rva; - addr_t ldr_data_size_rva; + std::array offsets; + std::array open_offsets; + std::array generic_offsets; + std::array miniport_offsets; - size_t* generic_offsets; - size_t* open_offsets; - size_t* miniport_offsets; + addr_t ob_type_table_va; std::vector process_cb; std::vector thread_cb; @@ -155,8 +156,13 @@ class callbackmon : public pluginex std::vector w32callouts; std::vector wfpcallouts; + std::vector object_type; + std::vector object_cb; std::unordered_map ndis_protocol_cb; + std::vector drivers; + std::unique_ptr driver_hook; + virtual bool stop_impl() override; }; diff --git a/src/plugins/callbackmon/private.h b/src/plugins/callbackmon/private.h index 5af45cd3e..a01945700 100644 --- a/src/plugins/callbackmon/private.h +++ b/src/plugins/callbackmon/private.h @@ -103,6 +103,66 @@ ***************************************************************************/ #pragma once +namespace callbackmon_ns +{ +struct type_cb_t +{ + addr_t base; + addr_t callback; + + bool operator==(const type_cb_t& o) const + { + return base == o.base && callback == o.callback; + } + + operator addr_t() const + { + return callback; + } +}; + +struct callbackmon_module_t +{ + addr_t base; + size_t size; + std::string name; +}; + +struct object_type_t +{ + addr_t base; + std::string name; + std::vector callbacks; + std::vector initializer; +}; + +struct object_t +{ + addr_t base; + std::string name; + std::vector callbacks; +}; + +enum +{ + OBJECT_TYPE_CALLBACKLIST, + OBJECT_TYPE_TYPEINFO, + OBJECT_TYPE_INITIALIZER_DUMP_CB, + OBJECT_TYPE_INITIALIZER_OPEN_CB, + OBJECT_TYPE_INITIALIZER_CLOSE_CB, + OBJECT_TYPE_INITIALIZER_DELETE_CB, + OBJECT_TYPE_INITIALIZER_PARSE_CB, + OBJECT_TYPE_INITIALIZER_SECURITY_CB, + OBJECT_TYPE_INITIALIZER_QUERY_CB, + OBJECT_TYPE_INITIALIZER_OK2CLOSE_CB, + DEVICE_OBJECT_DRIVER_OBJECT, + DRIVER_OBJECT_MAJOR_FUNCTION, + LDR_TABLE_ENTRY_FULLDLLNAME, + LDR_TABLE_ENTRY_DLLBASE, + LDR_TABLE_ENTRY_SIZEOFIMAGE, + __OFFSET_MAX +}; + enum { NDIS_PROTOCOL_BLOCK_NAME, @@ -181,6 +241,25 @@ enum __OFFSET_MINIPORT_MAX }; +static const char* offset_names[__OFFSET_MAX][2] = +{ + [OBJECT_TYPE_CALLBACKLIST] = { "_OBJECT_TYPE", "CallbackList" }, + [OBJECT_TYPE_TYPEINFO] = { "_OBJECT_TYPE", "TypeInfo" }, + [OBJECT_TYPE_INITIALIZER_DUMP_CB] = { "_OBJECT_TYPE_INITIALIZER", "DumpProcedure"}, + [OBJECT_TYPE_INITIALIZER_OPEN_CB] = { "_OBJECT_TYPE_INITIALIZER", "OpenProcedure"}, + [OBJECT_TYPE_INITIALIZER_CLOSE_CB] = { "_OBJECT_TYPE_INITIALIZER", "CloseProcedure"}, + [OBJECT_TYPE_INITIALIZER_DELETE_CB] = { "_OBJECT_TYPE_INITIALIZER", "DeleteProcedure"}, + [OBJECT_TYPE_INITIALIZER_PARSE_CB] = { "_OBJECT_TYPE_INITIALIZER", "ParseProcedure"}, + [OBJECT_TYPE_INITIALIZER_SECURITY_CB] = { "_OBJECT_TYPE_INITIALIZER", "SecurityProcedure"}, + [OBJECT_TYPE_INITIALIZER_QUERY_CB] = { "_OBJECT_TYPE_INITIALIZER", "QueryNameProcedure"}, + [OBJECT_TYPE_INITIALIZER_OK2CLOSE_CB] = { "_OBJECT_TYPE_INITIALIZER", "OkayToCloseProcedure"}, + [DEVICE_OBJECT_DRIVER_OBJECT] = { "_DEVICE_OBJECT", "DriverObject" }, + [DRIVER_OBJECT_MAJOR_FUNCTION] = { "_DRIVER_OBJECT", "MajorFunction" }, + [LDR_TABLE_ENTRY_FULLDLLNAME] = { "_LDR_DATA_TABLE_ENTRY", "FullDllName" }, + [LDR_TABLE_ENTRY_DLLBASE] = { "_LDR_DATA_TABLE_ENTRY", "DllBase" }, + [LDR_TABLE_ENTRY_SIZEOFIMAGE] = { "_LDR_DATA_TABLE_ENTRY", "SizeOfImage" }, +}; + static const char* offset_generic_names_w7[__OFFSET_GENERIC_MAX][2] = { @@ -295,4 +374,5 @@ static const char* offset_miniport_names[__OFFSET_MINIPORT_MAX][2] = [NDIS_MINIPORT_BLOCK_MINIPORTRETURNPACKETHANDLER] = { "_NDIS_MINIPORT_BLOCK", "MiniportReturnPacketHandler" }, [NDIS_MINIPORT_BLOCK_SYNCHRONOUSRETURNPACKETHANDLER] = { "_NDIS_MINIPORT_BLOCK", "SynchronousReturnPacketHandler" }, [NDIS_MINIPORT_BLOCK_TOPNDIS5PACKETINDICATEHANDLER] = { "_NDIS_MINIPORT_BLOCK", "TopNdis5PacketIndicateHandler" } -}; \ No newline at end of file +}; +}; diff --git a/src/plugins/rootkitmon/private.h b/src/plugins/rootkitmon/private.h index e62c7a137..660b0dafd 100644 --- a/src/plugins/rootkitmon/private.h +++ b/src/plugins/rootkitmon/private.h @@ -117,21 +117,10 @@ using sha256_checksum_t = std::array; enum { - EPROCESS_UNIQUE_PROCESS_ID, - LDR_DATA_TABLE_ENTRY_DLLBASE, - LDR_DATA_TABLE_ENTRY_SIZEOFIMAGE, - LDR_DATA_TABLE_ENTRY_BASEDLLNAME, - OBJECT_DIRECTORY_ENTRY_CHAINLINK, - OBJECT_DIRECTORY_ENTRY_OBJECT, - OBJECT_HEADER_TYPEINDEX, - OBJECT_HEADER_INFOMASK, - OBJECT_HEADER_NAME_INFO_NAME, - OBJECT_TYPE_NAME, - OBJECT_TYPE_TYPE_INFO, - OBJECT_TYPE_CALLBACKLIST, DRIVER_OBJECT_DEVICEOBJECT, DRIVER_OBJECT_STARTIO, DRIVER_OBJECT_DRIVERNAME, + DRIVER_OBJECT_DRIVERSTART, DRIVER_OBJECT_FASTIODISPATCH, DEVICE_OBJECT_ATTACHEDDEVICE, DEVICE_OBJECT_DRIVEROBJECT, @@ -141,25 +130,14 @@ enum static const char* offset_names[__OFFSET_MAX][2] = { - [EPROCESS_UNIQUE_PROCESS_ID] = {"_EPROCESS", "UniqueProcessId"}, - [LDR_DATA_TABLE_ENTRY_DLLBASE] = { "_LDR_DATA_TABLE_ENTRY", "DllBase" }, - [LDR_DATA_TABLE_ENTRY_SIZEOFIMAGE] = { "_LDR_DATA_TABLE_ENTRY", "SizeOfImage" }, - [LDR_DATA_TABLE_ENTRY_BASEDLLNAME] = { "_LDR_DATA_TABLE_ENTRY", "BaseDllName" }, - [OBJECT_DIRECTORY_ENTRY_CHAINLINK] = { "_OBJECT_DIRECTORY_ENTRY", "ChainLink" }, - [OBJECT_DIRECTORY_ENTRY_OBJECT] = { "_OBJECT_DIRECTORY_ENTRY", "Object" }, - [OBJECT_HEADER_TYPEINDEX] = { "_OBJECT_HEADER", "TypeIndex" }, - [OBJECT_HEADER_INFOMASK] = { "_OBJECT_HEADER", "InfoMask" }, - [OBJECT_HEADER_NAME_INFO_NAME] = { "_OBJECT_HEADER_NAME_INFO", "Name" }, - [OBJECT_TYPE_NAME] = { "_OBJECT_TYPE", "Name" }, - [OBJECT_TYPE_TYPE_INFO] = { "_OBJECT_TYPE", "TypeInfo" }, - [OBJECT_TYPE_CALLBACKLIST] = { "_OBJECT_TYPE", "CallbackList" }, - [DRIVER_OBJECT_DEVICEOBJECT] = { "_DRIVER_OBJECT", "DeviceObject" }, - [DRIVER_OBJECT_STARTIO] = { "_DRIVER_OBJECT", "DriverStartIo" }, - [DRIVER_OBJECT_DRIVERNAME] = { "_DRIVER_OBJECT", "DriverName" }, + [DRIVER_OBJECT_DEVICEOBJECT] = { "_DRIVER_OBJECT", "DeviceObject" }, + [DRIVER_OBJECT_STARTIO] = { "_DRIVER_OBJECT", "DriverStartIo" }, + [DRIVER_OBJECT_DRIVERNAME] = { "_DRIVER_OBJECT", "DriverName" }, + [DRIVER_OBJECT_DRIVERSTART] = { "_DRIVER_OBJECT", "DriverStart" }, [DRIVER_OBJECT_FASTIODISPATCH] = { "_DRIVER_OBJECT", "FastIoDispatch" }, [DEVICE_OBJECT_ATTACHEDDEVICE] = { "_DEVICE_OBJECT", "AttachedDevice" }, - [DEVICE_OBJECT_DRIVEROBJECT] = { "_DEVICE_OBJECT", "DriverObject" }, - [DEVICE_OBJECT_NEXTDEVICE] = { "_DEVICE_OBJECT", "NextDevice" }, + [DEVICE_OBJECT_DRIVEROBJECT] = { "_DEVICE_OBJECT", "DriverObject" }, + [DEVICE_OBJECT_NEXTDEVICE] = { "_DEVICE_OBJECT", "NextDevice" }, }; enum diff --git a/src/plugins/rootkitmon/rootkitmon.cpp b/src/plugins/rootkitmon/rootkitmon.cpp index ee60b755e..50cb84e59 100644 --- a/src/plugins/rootkitmon/rootkitmon.cpp +++ b/src/plugins/rootkitmon/rootkitmon.cpp @@ -127,12 +127,38 @@ static inline size_t get_ci_table_size(vmi_instance_t vmi) return 0; } -static inline void report(drakvuf_t drakvuf, const output_format_t format, const char* type, const char* name, const char* action) +static inline void report(drakvuf_t drakvuf, const output_format_t format, const char* type, const char* action, + const char* name = nullptr, const addr_t* value = nullptr, const addr_t* prev_value = nullptr, + const char* module = nullptr) { + std::optional> name_opt, module_opt; + std::optional> value_opt, prev_value_opt; + + if (name) + { + name_opt = fmt::Estr(name); + } + if (value) + { + value_opt = fmt::Xval(*value); + } + if (prev_value) + { + prev_value_opt = fmt::Xval(*prev_value); + } + if (module) + { + module_opt = fmt::Estr(module); + } + fmt::print(format, "rootkitmon", drakvuf, nullptr, - keyval("Type", fmt::Qstr(type)), - keyval("Name", fmt::Qstr(name)), - keyval("Action", fmt::Qstr(action))); + keyval("Type", fmt::Estr(type)), + keyval("Action", fmt::Estr(action)), + keyval("Name", name_opt), + keyval("Value", value_opt), + keyval("PreviousValue", prev_value_opt), + keyval("Module", module_opt) + ); } static bool translate_ksym2p(vmi_instance_t vmi, const char* symbol, addr_t* addr) @@ -286,7 +312,7 @@ static std::vector> enumerate_gdt(vmi_instance_t static event_response_t wfp_cb(drakvuf_t drakvuf, drakvuf_trap_info_t* info) { auto plugin = static_cast(info->trap->data); - report(drakvuf, plugin->format, "Function", "FwpmCalloutAdd0", "Called"); + report(drakvuf, plugin->format, "Function", "Called", "FwpmCalloutAdd0"); return VMI_EVENT_RESPONSE_NONE; } @@ -300,7 +326,7 @@ static event_response_t halprivatetable_overwrite_cb(drakvuf_t drakvuf, drakvuf_ // Table size is unknown, assume 0x100 bytes if (info->trap_pa >= plugin->halprivatetable && info->trap_pa < plugin->halprivatetable + 0x100) { - report(drakvuf, plugin->format, "SystemStruct", "HalPrivateDispatchTable", "Modified"); + report(drakvuf, plugin->format, "SystemStruct", "Modified", "HalPrivateDispatchTable"); } return VMI_EVENT_RESPONSE_NONE; } @@ -369,117 +395,40 @@ static void initialize_ci_checks(drakvuf_t drakvuf, rootkitmon* plugin, const ro } } // Fill initial values - vmi_read_8_va(vmi, plugin->ci_enabled_va, 4, &plugin->ci_enabled); + if (VMI_SUCCESS != vmi_read_8_va(vmi, plugin->ci_enabled_va, 4, &plugin->ci_enabled) ) + throw -1; plugin->ci_callbacks = calc_checksum(vmi, plugin->ci_callbacks_va, get_ci_table_size(vmi)); plugin->syscall_hooks.push_back(plugin->createSyscallHook("SeValidateImageHeader", check_cb)); plugin->syscall_hooks.push_back(plugin->createSyscallHook("SeValidateImageData", check_cb)); } -static void initialize_ob_checks(vmi_instance_t vmi, rootkitmon* plugin) +static void initialize_drv_checks(drakvuf_t drakvuf, rootkitmon* plugin) { - auto consume_callbacks = [&](addr_t object) -> std::vector + if (!plugin->is32bit) { - std::vector out; - // typedef struct _CALLBACK_OBJECT <- undocumented - // { - // ULONG Signature; // 0x00 - // KSPIN_LOCK Lock; // 0x08 - // LIST_ENTRY RegisteredCallbacks; // 0x10 - // BOOLEAN AllowMultipleCallbacks; - // UCHAR reserved[3]; - // } CALLBACK_OBJECT, *PCALLBACK_OBJECT; - const addr_t callbacks_off = plugin->guest_ptr_size * 2; - // typedef struct _CALLBACK_REGISTRATION <- undocumented - // { - // LIST_ENTRY Link; // 0x00 - // PCALLBACK_OBJECT CallbackObject; // 0x10 - // PCALLBACK_FUNCTION CallbackFunction; // 0x18 - // PVOID CallbackContext; - // ULONG Busy; - // BOOLEAN UnregisterWaiting; - // } CALLBACK_REGISTRATION, *PCALLBACK_REGISTRATION; - const addr_t callback_fn_off = plugin->guest_ptr_size * 3; - // Read list head - addr_t head = object + callbacks_off; - // Read flink entry - addr_t entry{ 0 }; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, head, 4, &entry)) - return out; - while (entry != head && entry) + drakvuf_enumerate_object_directory(drakvuf, [](drakvuf_t drakvuf, const object_info_t* info, void* ctx) { - addr_t callback{ 0 }; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, entry + callback_fn_off, 0, &callback) || - VMI_SUCCESS != vmi_read_addr_va(vmi, entry, 0, &entry)) - return out; - if (callback) + auto plugin = static_cast(ctx); + if (!strcmp((const char*)info->name->contents, "Driver")) { - out.push_back(callback); - } - } - return out; - }; - - auto consume_callbacklist = [&](addr_t object) -> std::vector - { - // // CALLBACK_ENTRY_ITEM - // typedef struct _CALLBACK_ENTRY_ITEM { - // LIST_ENTRY CallbackList; // 0x0 - // OB_OPERATION Operations; // 0x10 - // DWORD Active; // 0x14 - // CALLBACK_ENTRY *CallbackEntry; // 0x18 - // PVOID ObjectType; // 0x20 - // POB_PRE_OPERATION_CALLBACK PreOperation; // 0x28 - // POB_POST_OPERATION_CALLBACK PostOperation; // 0x30 - // QWORD unk1; // 0x38 - // } CALLBACK_ENTRY_ITEM, *PCALLBACK_ENTRY_ITEM; // size: 0x40 - std::vector out; - if (plugin->guest_ptr_size != 8) - return out; - addr_t head{ 0 }; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, object + plugin->offsets[OBJECT_TYPE_CALLBACKLIST], 4, & head) || - VMI_SUCCESS != vmi_read_addr_va(vmi, head, 4, & head)) - return out; - // Read flink entry - addr_t entry{ 0 }; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, head, 4, &entry)) - return out; - while (entry != head && entry) - { - addr_t pre_cb{ 0 }, post_cb{ 0 }; - uint32_t active{ 0 }; - if (VMI_SUCCESS != vmi_read_32_va(vmi, entry + 0x14, 4, &active) || - VMI_SUCCESS != vmi_read_addr_va(vmi, entry + 0x28, 4, &pre_cb) || - VMI_SUCCESS != vmi_read_addr_va(vmi, entry + 0x30, 4, &post_cb) || - VMI_SUCCESS != vmi_read_addr_va(vmi, entry, 4, &entry)) - return out; - - if (active) - { - if (pre_cb) out.push_back(pre_cb); - if (post_cb) out.push_back(post_cb); + vmi_lock_guard vmi(drakvuf); + // 28 Major functions + DriverUnload + DriverStartIo = 30 pointers + auto drv_obj_crc = calc_checksum(vmi, info->base_addr + plugin->offsets[DRIVER_OBJECT_STARTIO], plugin->guest_ptr_size * 30); + // Calculate FASTIO_DISPATCH array as well if present + addr_t fastio_addr = 0; + if (VMI_SUCCESS != vmi_read_addr_va(vmi, info->base_addr + plugin->offsets[DRIVER_OBJECT_FASTIODISPATCH], 0, &fastio_addr)) + { + PRINT_DEBUG("[ROOTKITMON] Failed to read DRIVER_OBJECT_FASTIODISPATCH pointer\n"); + throw -1; + } + if (fastio_addr) + drv_obj_crc = merge(drv_obj_crc, calc_checksum(vmi, fastio_addr, plugin->fastio_size)); + plugin->driver_object_checksums[info->base_addr] = drv_obj_crc; + // Enumerate all device_stacks of a particular driver + plugin->driver_stacks[info->base_addr] = plugin->enumerate_driver_stacks(vmi, info->base_addr); } - } - return out; - }; - - for (const auto& callback_object : plugin->enumerate_object_directory(vmi, "Callback")) - { - plugin->ob_callbacks[callback_object] = consume_callbacks(callback_object); - } - - // Enumerate every object type. First 2 entries are not used - for (size_t i = 2; ; i++) - { - addr_t ob_type{ 0 }; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, plugin->type_idx_table + i * plugin->guest_ptr_size, 4, &ob_type)) - continue; - // if reached the end - if (!ob_type) - break; - // CRC whole _OBJECT_TYPE_INITIALIZER structure - plugin->ob_type_initiliazer_crc[i] = calc_checksum(vmi, ob_type + plugin->offsets[OBJECT_TYPE_TYPE_INFO], plugin->ob_type_init_size); - plugin->ob_type_callbacks[i] = consume_callbacklist(ob_type); + }, plugin); } } @@ -534,9 +483,9 @@ bool rootkitmon::enumerate_cores(vmi_instance_t vmi) * It is used to calculate checksums of driver sections. * @driver - LDR_DATA_TABLE_ENTRY pointer. */ -static bool driver_visitor(drakvuf_t drakvuf, const module_info_t* module_info, bool* need_free, bool* need_stop, void* visitor_ctx) +static bool driver_visitor(drakvuf_t drakvuf, const module_info_t* module_info, bool* need_free, bool* need_stop, void* ctx) { - auto plugin = static_cast(visitor_ctx); + auto plugin = static_cast(ctx); vmi_lock_guard vmi(drakvuf); ACCESS_CONTEXT(a_ctx, @@ -562,11 +511,28 @@ static bool driver_visitor(drakvuf_t drakvuf, const module_info_t* module_info, auto section_hash = calc_checksum(vmi, virt_addr, aligned_size); driver_hash = merge(driver_hash, section_hash); } - plugin->driver_sections_checksums[module_info->base_addr] = std::move(driver_hash); + plugin->driver_sections_checksums[module_info->base_addr] = { std::move(driver_hash), (const char*)module_info->full_name->contents }; munmap(module, VMI_PS_4KB); return true; } +static std::string get_driver_name_by_addr(drakvuf_t drakvuf, addr_t addr) +{ + std::pair context{ "", addr }; + + drakvuf_enumerate_drivers(drakvuf, [](drakvuf_t drakvuf, const module_info_t* info, bool*, bool* stop, void* ctx) + { + auto pass_context = reinterpret_cast*>(ctx); + if (pass_context->second >= info->base_addr && pass_context->second < info->base_addr + info->size) + { + pass_context->first.assign((const char*)info->full_name->contents); + *stop = true; + } + return true; + }, &context); + return context.first; +} + void rootkitmon::check_driver_integrity(drakvuf_t drakvuf) { auto past_drivers_checksums = std::move(this->driver_sections_checksums); @@ -574,28 +540,17 @@ void rootkitmon::check_driver_integrity(drakvuf_t drakvuf) // Collect new checksums drakvuf_enumerate_drivers(drakvuf, driver_visitor, static_cast(this)); // Compare - for (const auto& [driver, checksum] : this->driver_sections_checksums) + for (const auto& [driver, data] : this->driver_sections_checksums) { + const auto& [checksum, name] = data; // Find driver object if (past_drivers_checksums.find(driver) == past_drivers_checksums.end()) continue; - const auto& p_checksum = past_drivers_checksums[driver]; + const auto& [p_checksum, p_name] = past_drivers_checksums[driver]; if (checksum != p_checksum) { - { - vmi_lock_guard vmi(drakvuf); - unicode_string_t* drvname = drakvuf_read_unicode_va(drakvuf, driver + this->offsets[LDR_DATA_TABLE_ENTRY_BASEDLLNAME], 4); - if (drvname) - { - report(drakvuf, this->format, "DriverCRC", (const char*)drvname->contents, "Modified"); - vmi_free_unicode_str(drvname); - } - else - { - report(drakvuf, this->format, "DriverCRC", "Unknown", "Modified"); - } - } + report(drakvuf, this->format, "DriverCRC", "Modified", nullptr, nullptr, nullptr, name.c_str()); } } } @@ -607,23 +562,19 @@ void rootkitmon::check_driver_objects(drakvuf_t drakvuf) this->driver_object_checksums.clear(); this->driver_stacks.clear(); // Collect new info + initialize_drv_checks(drakvuf, this); + + auto report_modification = [&](const char* type, addr_t driver_object) { vmi_lock_guard vmi(drakvuf); - if (!this->is32bit) - for (const auto& drv_object : this->enumerate_object_directory(vmi, "Driver")) - { - auto drv_obj_crc = calc_checksum(vmi, drv_object + this->offsets[DRIVER_OBJECT_STARTIO], this->guest_ptr_size * 30); - addr_t fastio_addr = 0; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, drv_object + this->offsets[DRIVER_OBJECT_FASTIODISPATCH], 4, &fastio_addr)) - { - PRINT_DEBUG("[ROOTKITMON] Failed to read DRIVER_OBJECT_FASTIODISPATCH pointer\n"); - } - if (fastio_addr) - drv_obj_crc = merge(drv_obj_crc, calc_checksum(vmi, fastio_addr, this->fastio_size)); - this->driver_object_checksums[drv_object] = drv_obj_crc; - this->driver_stacks[drv_object] = this->enumerate_driver_stacks(vmi, drv_object); - } - } + addr_t driver_base{}; + if (VMI_SUCCESS == vmi_read_addr_va(vmi, driver_object + this->offsets[DRIVER_OBJECT_DRIVERSTART], 0, &driver_base)) + { + auto name = get_driver_name_by_addr(drakvuf, driver_base); + report(drakvuf, this->format, type, "Modified", nullptr, nullptr, nullptr, name.c_str()); + } + }; + // Compare dispatch table checksums for (const auto& [drv_object, checksum] : this->driver_object_checksums) { @@ -635,7 +586,7 @@ void rootkitmon::check_driver_objects(drakvuf_t drakvuf) if (checksum != p_checksum) { - report(drakvuf, this->format, "DriverObject", "Uknonwn", "Modified"); + report_modification("DriverObject", drv_object); } } // Compare driver stacks @@ -658,8 +609,7 @@ void rootkitmon::check_driver_objects(drakvuf_t drakvuf) // Size mismatch == stack modification if (p_dev_stack.size() != dev_stack.size()) { - report(drakvuf, this->format, "DriverStack", "Uknonwn", "Modified"); - continue; + report_modification("DriverStack", drv_object); } for (size_t i = 0; i < dev_stack.size(); i++) @@ -667,7 +617,7 @@ void rootkitmon::check_driver_objects(drakvuf_t drakvuf) // Dev object hijack if (dev_stack[i] != p_dev_stack[i]) { - report(drakvuf, this->format, "DriverStack", "Uknonwn", "Modified"); + report_modification("DriverStack", drv_object); break; } } @@ -694,12 +644,12 @@ void rootkitmon::check_descriptors(drakvuf_t drakvuf) const auto& t_desc_info = past_descriptors[vcpu]; if (desc_info.idtr_base != t_desc_info.idtr_base) { - report(drakvuf, this->format, "SystemRegister", "IDTR", "Modified"); + report(drakvuf, this->format, "SystemRegister", "Modified", "IDTR", &desc_info.idtr_base, &t_desc_info.idtr_base); break; } if (desc_info.idt_checksum != t_desc_info.idt_checksum) { - report(drakvuf, this->format, "SystemStruct", "IDT", "Modified"); + report(drakvuf, this->format, "SystemStruct", "Modified", "IDT"); break; } } @@ -709,12 +659,12 @@ void rootkitmon::check_descriptors(drakvuf_t drakvuf) const auto& t_desc_info = past_descriptors[vcpu]; if (desc_info.gdtr_base != t_desc_info.gdtr_base) { - report(drakvuf, this->format, "SystemRegister", "GDTR", "Modified"); + report(drakvuf, this->format, "SystemRegister", "Modified", "GDTR", &desc_info.gdtr_base, &t_desc_info.gdtr_base); break; } if (desc_info.gdt.size() != t_desc_info.gdt.size()) { - report(drakvuf, this->format, "SystemStruct", "GDT", "Modified"); + report(drakvuf, this->format, "SystemStruct", "Modified", "GDT"); break; } else @@ -726,7 +676,7 @@ void rootkitmon::check_descriptors(drakvuf_t drakvuf) if (addr != t_addr) { - report(drakvuf, this->format, "SystemStruct", "GDT", "Modified"); + report(drakvuf, this->format, "SystemStruct", "Modified", "GDT", &addr, &t_addr); break; } } @@ -734,80 +684,6 @@ void rootkitmon::check_descriptors(drakvuf_t drakvuf) } } -void rootkitmon::check_objects(drakvuf_t drakvuf) -{ - auto p_ob_type_initializer_crc = std::move(this->ob_type_initiliazer_crc); - auto p_ob_type_callbacks = std::move(this->ob_type_callbacks); - auto p_ob_callbacks = std::move(this->ob_callbacks); - this->ob_type_initiliazer_crc.clear(); - this->ob_type_callbacks.clear(); - this->ob_callbacks.clear(); - - vmi_lock_guard vmi(drakvuf); - initialize_ob_checks(vmi, this); - - auto compare_callbacks = [&](const auto& previous, const auto& current, const char* action) - { - for (const auto& [ob, prev_cbs] : previous) - { - auto obj_name = get_object_name(vmi, ob); - const char* name = obj_name ? (const char*)obj_name->contents : ""; - if (current.find(ob) == current.end()) - { - report(drakvuf, format, "ObjectCallbacks", name, action); - } - else - { - const auto& cur_cbs = current.at(ob); - for (const auto& cb : prev_cbs) - { - if (std::find(cur_cbs.begin(), cur_cbs.end(), cb) == cur_cbs.end()) - { - report(drakvuf, format, "ObjectCallbacks", name, action); - break; - } - } - } - if (obj_name) vmi_free_unicode_str(obj_name); - } - }; - compare_callbacks(p_ob_callbacks, this->ob_callbacks, "Removed"); - compare_callbacks(this->ob_callbacks, p_ob_callbacks, "Added"); - // Check type initializer - for (const auto& [idx, crc] : p_ob_type_initializer_crc) - { - // Should never happen but better safe than sorry - if (this->ob_type_initiliazer_crc.find(idx) == this->ob_type_initiliazer_crc.end()) - continue; - addr_t ob_type; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, this->type_idx_table + idx * this->guest_ptr_size, 4, &ob_type)) - continue; - - const auto& ob_ty_init_crc = calc_checksum(vmi, ob_type + this->offsets[OBJECT_TYPE_TYPE_INFO], this->ob_type_init_size); - if (this->ob_type_initiliazer_crc[idx] != ob_ty_init_crc) - { - auto type_name = drakvuf_read_unicode_va(drakvuf, ob_type + this->offsets[OBJECT_TYPE_NAME], 4); - report(drakvuf, format, "ObjectType", type_name ? (const char*)type_name->contents : "", "Modified"); - if (type_name) vmi_free_unicode_str(type_name); - } - } - // Check type callbacks - for (const auto& [idx, cbs] : p_ob_type_callbacks) - { - if (this->ob_type_callbacks.find(idx) == this->ob_type_callbacks.end()) - continue; - addr_t ob_type; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, this->type_idx_table + idx * this->guest_ptr_size, 4, &ob_type)) - continue; - if (!std::equal(p_ob_type_callbacks.begin(), p_ob_type_callbacks.end(), this->ob_type_callbacks.begin())) - { - auto type_name = drakvuf_read_unicode_va(drakvuf, ob_type + this->offsets[OBJECT_TYPE_NAME], 4); - report(drakvuf, format, "ObjectTypeCallbacks", type_name ? (const char*)type_name->contents : "", "Modified"); - if (type_name) vmi_free_unicode_str(type_name); - } - } -} - void rootkitmon::check_ci(drakvuf_t drakvuf, drakvuf_trap_info_t* info) { vmi_lock_guard vmi(drakvuf); @@ -824,12 +700,12 @@ void rootkitmon::check_ci(drakvuf_t drakvuf, drakvuf_trap_info_t* info) if (this->ci_enabled != ci_flag) { - report(drakvuf, format, "SystemStruct", "g_CiEnabled", "Modified"); + report(drakvuf, format, "SystemStruct", "Modified", "g_CiEnabled"); } if (this->ci_callbacks != calc_checksum(vmi, this->ci_callbacks_va, get_ci_table_size(vmi))) { - report(drakvuf, format, "SystemStruct", "g_CiCallbacks", "Modified"); + report(drakvuf, format, "SystemStruct", "Modified", "g_CiCallbacks"); } } @@ -852,7 +728,7 @@ void rootkitmon::check_filter_callbacks(drakvuf_t drakvuf) const auto& new_callbacks = this->flt_callbacks[volume]; if (callbacks != new_callbacks) { - report(drakvuf, format, "SystemStruct", "VolumeFilterCallbacks", "Modified"); + report(drakvuf, format, "SystemStruct", "Modified", "VolumeFilterCallbacks"); } } } @@ -872,7 +748,7 @@ event_response_t rootkitmon::rop_callback(drakvuf_t drakvuf, drakvuf_trap_info_t if (rflag & ac_smap_mask) { - report(drakvuf, plugin->format, "SecurityFeature", "EFLAGS.SMAP", "Disabled"); + report(drakvuf, plugin->format, "SecurityFeature", "Disabled", "EFLAGS.SMAP"); } // Release memory hook. If EFLAGS.SMAP wasn't set at this point, we don't need this bp anymore plugin->rop_hooks.erase(info->trap->breakpoint.addr); @@ -888,11 +764,40 @@ event_response_t rootkitmon::rop_callback(drakvuf_t drakvuf, drakvuf_trap_info_t event_response_t rootkitmon::msr_callback(drakvuf_t drakvuf, drakvuf_trap_info_t* info) { rootkitmon* plugin = static_cast(info->trap->data); + + PRINT_DEBUG("[ROOTKITMON] LSTAR: %lx -> %lx\n", plugin->msr_lstar[info->vcpu], info->reg->value); if (plugin->msr_lstar[info->vcpu] == info->reg->value) { return VMI_EVENT_RESPONSE_NONE; } - report(drakvuf, plugin->format, "SystemRegister", "LSTAR", "Modified"); + + { + // PatchGuard changes LSTAR inside FsRtlMdlReadCompleteDevEx: + // [...] + // 41 BE 82 00 00 C0 mov r14d, 0C0000082h + // 41 8B CE mov ecx, r14d + // 0F 32 rdmsr + // 48 C1 E2 20 shl rdx, 20h + // 48 0B C2 or rax, rdx + // 48 8D 96 7A 08 00 00 lea rdx, [rsi+87Ah] + // 48 8B D8 mov rbx, rax + // 48 8B C2 mov rax, rdx + // 48 C1 EA 20 shr rdx, 20h + // 0F 30 wrmsr + // [...] + auto vmi = vmi_lock_guard(drakvuf); + + uint8_t instr{}; + if (VMI_SUCCESS == vmi_read_va(vmi, info->reg->value, 4, sizeof(instr), (void*)&instr, nullptr) && instr == 0xc3) // ret (C3) + { + PRINT_DEBUG("[ROOTKITMON] LSTAR: Skip modification by PatchGuard\n"); + return VMI_EVENT_RESPONSE_NONE; + } + } + + auto name = get_driver_name_by_addr(drakvuf, info->reg->value); + report(drakvuf, plugin->format, "SystemRegister", "Modified", "LSTAR", &info->reg->value, &plugin->msr_lstar[info->vcpu], name.empty() ? nullptr : name.c_str()); + auto trap = new drakvuf_trap_t { .type = BREAKPOINT, @@ -918,12 +823,12 @@ event_response_t rootkitmon::cr4_callback(drakvuf_t drakvuf, drakvuf_trap_info_t PRINT_DEBUG("[ROOTKITMON] CR4: %lx -> %lx\n", info->reg->previous, info->reg->value); if (VMI_GET_BIT(info->reg->previous, cr4_smep_mask_bitoffset) == 1 && VMI_GET_BIT(info->reg->value, cr4_smep_mask_bitoffset) == 0) { - report(drakvuf, plugin->format, "SecurityFeature", "CR4.SMEP", "Disabled"); + report(drakvuf, plugin->format, "SecurityFeature", "Disabled", "CR4.SMEP"); } if (VMI_GET_BIT(info->reg->previous, cr4_smap_mask_bitoffset) == 1 && VMI_GET_BIT(info->reg->value, cr4_smap_mask_bitoffset) == 0) { - report(drakvuf, plugin->format, "SecurityFeature", "CR4.SMAP", "Disabled"); + report(drakvuf, plugin->format, "SecurityFeature", "Disabled", "CR4.SMAP"); } return VMI_EVENT_RESPONSE_NONE; @@ -1003,103 +908,6 @@ std::unique_ptr rootkitmon::register_mem_hook(hook_cb_t cal } } -unicode_string_t* rootkitmon::get_object_type_name(vmi_instance_t vmi, addr_t object) -{ - addr_t ob_header = object - this->object_header_size + this->guest_ptr_size; - uint8_t type_index; - - // Object header is always present before actual object - if (VMI_SUCCESS != vmi_read_8_va(vmi, ob_header + this->offsets[OBJECT_HEADER_TYPEINDEX], 4, &type_index)) - return nullptr; - - // https://medium.com/@ashabdalhalim/a-light-on-windows-10s-object-header-typeindex-value-e8f907e7073a - // Due to security mitigations type_index is no longer equals to index in ObTypeIndexTable array on win 10 - // but calculated as following: - if (this->winver == VMI_OS_WINDOWS_10) - type_index = type_index ^ ((ob_header >> 8) & 0xff) ^ this->ob_header_cookie; - - addr_t ob_type; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, this->type_idx_table + type_index * this->guest_ptr_size, 4, &ob_type)) - return nullptr; - - return drakvuf_read_unicode_va(drakvuf, ob_type + this->offsets[OBJECT_TYPE_NAME], 4); -} - -unicode_string_t* rootkitmon::get_object_name(vmi_instance_t vmi, addr_t object) -{ - // Get object header - addr_t object_header = object - this->object_header_size + this->guest_ptr_size; - // Get InfoMask - uint8_t infomask{ 0 }; - if (vmi_read_8_va(vmi, object_header + this->offsets[OBJECT_HEADER_INFOMASK], 4, &infomask)) - throw -1; - // Get object name. Some objects are anonymous. See ObQueryNameInfo for more info - if (infomask & 2) - { - uint8_t name_info_off{ 0 }; - if (VMI_SUCCESS != vmi_read_8_va(vmi, this->ob_infomask2off + (infomask & 3), 4, &name_info_off)) - throw -1; - addr_t object_name_info = object_header - name_info_off; - return drakvuf_read_unicode_va(drakvuf, object_name_info + this->offsets[OBJECT_HEADER_NAME_INFO_NAME], 4); - } - return nullptr; -} - -std::set rootkitmon::enumerate_object_directory(vmi_instance_t vmi, const char* name) -{ - // Get root directory object VA - addr_t root_directory_object; - if (VMI_SUCCESS != vmi_read_addr_ksym(vmi, "ObpRootDirectoryObject", &root_directory_object)) - { - PRINT_DEBUG("[ROOTKITMON] Failed to translate ObpRootDirectoryObject to VA\n"); - throw -1; - } - - std::function(vmi_instance_t vmi, addr_t directory, const char* name)> enumerate_directory; - enumerate_directory = [&](vmi_instance_t vmi, addr_t directory, const char* name) - { - std::set out; - // There is only 37 _OBJECT_DIRECTORY_ENTRY entries in object directory: - // 0: kd> dt nt!_OBJECT_DIRECTORY - // +0x000 HashBuckets : [37] Ptr64 _OBJECT_DIRECTORY_ENTRY - // +0x128 Lock : _EX_PUSH_LOCK - // ... - for (int i = 0; i < 37; i++) - { - addr_t hashbucket = 0; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, directory + this->guest_ptr_size * i, 4, &hashbucket) || !hashbucket) - continue; - - while (true) - { - addr_t object = 0; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, hashbucket + this->offsets[OBJECT_DIRECTORY_ENTRY_OBJECT], 4, &object) || !object) - break; - - unicode_string_t* obj_name = get_object_type_name(vmi, object); - if (obj_name) - { - if (!strcmp((const char*)obj_name->contents, name)) - out.insert(object); - - if (!strcmp((const char*)obj_name->contents, "Directory")) - for (auto obj : enumerate_directory(vmi, object, name)) - out.insert(obj); - - vmi_free_unicode_str(obj_name); - } - - if (VMI_SUCCESS != vmi_read_addr_va(vmi, hashbucket + this->offsets[OBJECT_DIRECTORY_ENTRY_CHAINLINK], 4, &hashbucket) || !hashbucket) - break; - } - } - return out; - }; - - // Enumerate directories recursively - return enumerate_directory(vmi, root_directory_object, name); -} - /** * Every driver can have N number of devices. Every device can be attached to a different device of a different driver, * hence the name driver stack. @@ -1297,65 +1105,31 @@ rootkitmon::rootkitmon(drakvuf_t drakvuf, const rootkitmon_config* config, outpu drakvuf_enumerate_drivers(drakvuf, driver_visitor, static_cast(this)); - vmi_lock_guard vmi(drakvuf); - - // Hook HalPrivateDispatchTable on write - if (!translate_ksym2p(vmi, "HalPrivateDispatchTable", &(this->halprivatetable))) - { - PRINT_DEBUG("[ROOTKITMON] Failed to translate symbol to physical address\n"); - throw -1; - } - manual_hooks.push_back(register_mem_hook(halprivatetable_overwrite_cb, this->halprivatetable, VMI_MEMACCESS_W)); - - if (!drakvuf_get_kernel_struct_size(drakvuf, "_OBJECT_HEADER", &this->object_header_size) || - !drakvuf_get_kernel_struct_size(drakvuf, "_FAST_IO_DISPATCH", &this->fastio_size) || - !drakvuf_get_kernel_struct_size(drakvuf, "_OBJECT_TYPE_INITIALIZER", &this->ob_type_init_size)) - { - throw -1; - } - - if (VMI_SUCCESS != vmi_translate_ksym2v(vmi, "ObpInfoMaskToOffset", &this->ob_infomask2off) || - VMI_SUCCESS != vmi_translate_ksym2v(vmi, "ObTypeIndexTable", &this->type_idx_table)) - { - throw -1; - } - - if (this->winver == VMI_OS_WINDOWS_10 && VMI_SUCCESS != vmi_read_8_ksym(vmi, "ObHeaderCookie", &this->ob_header_cookie)) { - PRINT_DEBUG("[ROOTKITMON] Failed to locate header cookie\n"); - throw -1; - } + vmi_lock_guard vmi(drakvuf); - if (!this->is32bit) - { - for (const auto& drv_object : enumerate_object_directory(vmi, "Driver")) + // Hook HalPrivateDispatchTable on write + if (!translate_ksym2p(vmi, "HalPrivateDispatchTable", &(this->halprivatetable))) { - // 28 Major functions + DriverUnload + DriverStartIo = 30 pointers - auto drv_obj_crc = calc_checksum(vmi, drv_object + offsets[DRIVER_OBJECT_STARTIO], this->guest_ptr_size * 30); - // Calculate FASTIO_DISPATCH array as well if present - addr_t fastio_addr = 0; - if (VMI_SUCCESS != vmi_read_addr_va(vmi, drv_object + offsets[DRIVER_OBJECT_FASTIODISPATCH], 4, &fastio_addr)) - { - PRINT_DEBUG("[ROOTKITMON] Failed to read DRIVER_OBJECT_FASTIODISPATCH pointer\n"); - throw -1; - } - if (fastio_addr) - drv_obj_crc = merge(drv_obj_crc, calc_checksum(vmi, fastio_addr, this->fastio_size)); - driver_object_checksums[drv_object] = drv_obj_crc; - // Enumerate all device_stacks of a particular driver - driver_stacks[drv_object] = enumerate_driver_stacks(vmi, drv_object); + PRINT_DEBUG("[ROOTKITMON] Failed to translate symbol to physical address\n"); + throw -1; } - } + manual_hooks.push_back(register_mem_hook(halprivatetable_overwrite_cb, this->halprivatetable, VMI_MEMACCESS_W)); - initialize_ob_checks(vmi, this); + if (!drakvuf_get_kernel_struct_size(drakvuf, "_FAST_IO_DISPATCH", &this->fastio_size)) + { + throw -1; + } - // Enumerate descriptors on all cores - if (!enumerate_cores(vmi)) - { - PRINT_DEBUG("[ROOTKITMON] Failed to enumerate descriptors\n"); - throw -1; + // Enumerate descriptors on all cores + if (!enumerate_cores(vmi)) + { + PRINT_DEBUG("[ROOTKITMON] Failed to enumerate descriptors\n"); + throw -1; + } } + initialize_drv_checks(drakvuf, this); // MSR hook auto trap = new drakvuf_trap_t(); trap->type = REGISTER; @@ -1387,7 +1161,6 @@ bool rootkitmon::stop_impl() check_driver_integrity(drakvuf); check_driver_objects(drakvuf); check_descriptors(drakvuf); - check_objects(drakvuf); check_ci(drakvuf, nullptr); check_filter_callbacks(drakvuf); return pluginex::stop_impl(); diff --git a/src/plugins/rootkitmon/rootkitmon.h b/src/plugins/rootkitmon/rootkitmon.h index 85ca42e23..8fac271f8 100644 --- a/src/plugins/rootkitmon/rootkitmon.h +++ b/src/plugins/rootkitmon/rootkitmon.h @@ -127,7 +127,6 @@ class rootkitmon : public pluginex const char* func_name, hook_cb_t callback); std::unique_ptr register_mem_hook(hook_cb_t callback, addr_t pa, vmi_mem_access_t access); - std::set enumerate_object_directory(vmi_instance_t vmi, const char* name); unicode_string_t* get_object_type_name(vmi_instance_t vmi, addr_t object); unicode_string_t* get_object_name(vmi_instance_t vmi, addr_t object); rootkitmon_ns::device_stack_t enumerate_driver_stacks(vmi_instance_t vmi, addr_t driver_object); @@ -172,11 +171,8 @@ class rootkitmon : public pluginex bool do_flt_checks; // map of volumes and their callbacks std::unordered_map flt_callbacks; - std::unordered_map driver_sections_checksums; + std::unordered_map> driver_sections_checksums; std::unordered_map driver_object_checksums; - std::unordered_map ob_type_initiliazer_crc; - std::unordered_map> ob_type_callbacks; - std::unordered_map> ob_callbacks; // _DRIVER_OBJECT -> _DEVICE_OBJECT -> [_DEVICE_OBJECT, ...] std::unordered_map driver_stacks; // VCPU -> Descriptor