From 7b3be0f275bb1c9b1653cb003cbe98fc2cf5acdc Mon Sep 17 00:00:00 2001 From: Dmitry Isaykin Date: Fri, 29 Dec 2023 18:40:54 +0300 Subject: [PATCH] apimon: optimize plugin by adding no-retval dll hook modifier * if we don't need return status or output parameter values, we need install return hook for this hook entry * for such entrues we may add no-retval modificator, i.e. 'name.dll,FuncName,no-retval,log,type1,type2' * add some unit-tests for parse_entry function --- src/libusermode/Makefile.am | 2 +- src/libusermode/check.cpp | 82 ++++++++++++++++- src/libusermode/userhook.cpp | 164 +--------------------------------- src/libusermode/userhook.hpp | 92 +++++++------------ src/libusermode/utils.cpp | 164 ++++++++++++++++++++++++++++++++++ src/libusermode/utils.hpp | 61 +++++++++++++ src/plugins/apimon/apimon.cpp | 47 ++++++---- src/plugins/apimon/apimon.h | 2 + 8 files changed, 373 insertions(+), 241 deletions(-) diff --git a/src/libusermode/Makefile.am b/src/libusermode/Makefile.am index d9c9ace17..7bb4d0d02 100644 --- a/src/libusermode/Makefile.am +++ b/src/libusermode/Makefile.am @@ -165,7 +165,7 @@ printers_check_LDADD = $(CHECK_LIBS) $(ZLIB_LIBS) printers/utils.lo check_PROGRAMS += userhook_check userhook_check_SOURCES = check.cpp userhook_check_CFLAGS = $(CHECK_CFLAGS) $(ZLIB_CFLAGS) -userhook_check_LDADD = $(CHECK_LIBS) $(ZLIB_LIBS) utils.lo +userhook_check_LDADD = $(CHECK_LIBS) $(ZLIB_LIBS) libusermode.la TESTS = $(check_PROGRAMS) diff --git a/src/libusermode/check.cpp b/src/libusermode/check.cpp index 284de64b6..f0eb0ded6 100644 --- a/src/libusermode/check.cpp +++ b/src/libusermode/check.cpp @@ -102,9 +102,14 @@ * * ***************************************************************************/ -#include - #include "utils.hpp" +#include "printers/printers.hpp" +#include "userhook.hpp" + +#include +#include + +#include START_TEST(test_match_dll_name) { @@ -133,6 +138,78 @@ static Suite* dll_matching_suite(void) return s; } +static plugin_target_config_entry_t test_parse_dll_entry(const std::string& entry) +{ + PrinterConfig config; + std::stringstream ss(entry); + return parse_entry(ss, config); +} + +START_TEST(test_parse_dll_hook) +{ + auto entry_str = "combase.dll,CoCreateInstance,log,rclsid:refclsid,punkOuter:lpvoid,dwClsContext:dword,riid:refiid,ppv:void**"; + auto entry = test_parse_dll_entry(entry_str); + + ck_assert(entry.dll_name == "combase.dll"); + ck_assert(entry.function_name == "CoCreateInstance"); + ck_assert(entry.type == HOOK_BY_NAME); + ck_assert(entry.clsid.empty()); + ck_assert(entry.offset == 0); + ck_assert(!entry.no_retval); + ck_assert(entry.actions.log && !entry.actions.stack); + ck_assert(entry.argument_printers.size() == 5); +} +END_TEST + +START_TEST(test_parse_dll_hook_with_offset) +{ + auto entry_str = "taskschd.dll,ITaskFolder::RegisterTaskDefinition,clsid,0F87369F-A4E5-4CFC-BD3E-73E6154572DD,13cd3,log,lpvoid,bstr"; + auto entry = test_parse_dll_entry(entry_str); + + ck_assert(entry.dll_name == "taskschd.dll"); + ck_assert(entry.function_name == "ITaskFolder::RegisterTaskDefinition"); + ck_assert(entry.type == HOOK_BY_OFFSET); + ck_assert(entry.clsid == "0F87369F-A4E5-4CFC-BD3E-73E6154572DD"); + ck_assert(entry.offset == 0x13cd3); + ck_assert(!entry.no_retval); + ck_assert(entry.actions.log && !entry.actions.stack); + ck_assert(entry.argument_printers.size() == 2); +} +END_TEST + +START_TEST(test_parse_dll_hook_with_empty_args) +{ + auto entry_str = "combase.dll,CoCreateInstance,log"; + auto entry = test_parse_dll_entry(entry_str); + + ck_assert(entry.dll_name == "combase.dll"); + ck_assert(entry.function_name == "CoCreateInstance"); + ck_assert(entry.type == HOOK_BY_NAME); + ck_assert(entry.clsid.empty()); + ck_assert(entry.offset == 0); + ck_assert(!entry.no_retval); + ck_assert(entry.actions.log && !entry.actions.stack); + ck_assert(entry.argument_printers.empty()); +} +END_TEST + +static Suite* dll_hooks_parsing_suite(void) +{ + Suite* s; + TCase* tc_core; + + s = suite_create("Parse DLL hooks"); + + tc_core = tcase_create("Core"); + + tcase_add_test(tc_core, test_parse_dll_hook); + tcase_add_test(tc_core, test_parse_dll_hook_with_offset); + tcase_add_test(tc_core, test_parse_dll_hook_with_empty_args); + suite_add_tcase(s, tc_core); + + return s; +} + int main(void) { int number_failed; @@ -141,6 +218,7 @@ int main(void) s = dll_matching_suite(); sr = srunner_create(s); + srunner_add_suite(sr, dll_hooks_parsing_suite()); srunner_run_all(sr, CK_NORMAL); number_failed = srunner_ntests_failed(sr); diff --git a/src/libusermode/userhook.cpp b/src/libusermode/userhook.cpp index 4e7d16fe1..034e45202 100644 --- a/src/libusermode/userhook.cpp +++ b/src/libusermode/userhook.cpp @@ -771,9 +771,9 @@ void userhook::request_usermode_hook(drakvuf_t drakvuf, const dll_view_t* dll, c dll_t* p_dll = reinterpret_cast(const_cast(dll)); if (target->type == HOOK_BY_NAME) - p_dll->targets.emplace_back(target->function_name, target->clsid, callback, target->argument_printers, extra); + p_dll->targets.emplace_back(target->function_name, target->clsid, target->no_retval, callback, target->argument_printers, extra); else // HOOK_BY_OFFSET - p_dll->targets.emplace_back(target->function_name, target->clsid, target->offset, callback, target->argument_printers, extra); + p_dll->targets.emplace_back(target->function_name, target->clsid, target->offset, target->no_retval, callback, target->argument_printers, extra); } void userhook::register_plugin(drakvuf_t drakvuf, usermode_cb_registration reg) @@ -927,166 +927,6 @@ bool drakvuf_stop_userhooks(drakvuf_t drakvuf) return !userhook::is_supported(drakvuf) || userhook::get_instance(drakvuf).stop(); } -std::optional get_hook_actions(const std::string& str) -{ - if (str == "log") - { - return HookActions::empty().set_log(); - } - else if (str == "log+stack") - { - return HookActions::empty().set_log().set_stack(); - } - - return std::nullopt; -} - -namespace -{ -std::optional try_parse_token(std::stringstream& ss) -{ - const char SEPARATOR = ','; - std::string result; - if (!std::getline(ss, result, SEPARATOR) || result.empty()) - { - return std::nullopt; - } - return result; -} - -std::string parse_token(std::stringstream& ss) -{ - auto maybe_token = try_parse_token(ss); - if (!maybe_token) - { - throw std::runtime_error{"Expected a token"}; - } - return *maybe_token; -} - -std::unique_ptr make_arg_printer( - const PrinterConfig& config, - const std::string& type, - const std::string& name) -{ - if (type == "lpstr" || type == "lpcstr" || type == "lpctstr") - { - return std::make_unique(name, config); - } - else if (type == "lpcwstr" || type == "lpwstr" || type == "bstr") - { - return std::make_unique(name, config); - } - else if (type == "punicode_string") - { - return std::make_unique(name, config); - } - else if (type == "pulong") - { - return std::make_unique(name, config); - } - else if (type == "pulonglong") - { - return std::make_unique(name, config); - } - else if (type == "lpvoid*") - { - return std::make_unique(name, config); - } - else if (type == "refclsid" || type == "refiid") - { - return std::make_unique(name, config); - } - else if (type == "binary16") - { - return std::make_unique(name, config); - } - - return std::make_unique(name, config); -} - - -std::vector> parse_arguments( - const PrinterConfig& config, - std::stringstream& ss) -{ - std::vector> argument_printers; - - for (size_t arg_idx = 0; ; arg_idx++) - { - auto maybe_arg = try_parse_token(ss); - if (!maybe_arg) break; - - const std::string arg = *maybe_arg; - std::string arg_name; - std::string arg_type; - const auto pos = arg.find_first_of(':'); - - if (pos == std::string::npos) - { - arg_name = std::string("Arg") + std::to_string(arg_idx); - arg_type = arg; - } - else - { - arg_name = arg.substr(0, pos); - arg_type = arg.substr(pos + 1); - } - - argument_printers.emplace_back(make_arg_printer(config, arg_type, arg_name)); - } - return argument_printers; -} - -plugin_target_config_entry_t parse_entry( - std::stringstream& ss, - PrinterConfig& config) -{ - plugin_target_config_entry_t entry{}; - - entry.dll_name = parse_token(ss); - entry.function_name = parse_token(ss); - entry.type = HOOK_BY_NAME; - - std::string log_strategy_or_offset; - std::string token = parse_token(ss); - if (token == "clsid") - { - entry.clsid = parse_token(ss); - log_strategy_or_offset = parse_token(ss); - } - else - { - log_strategy_or_offset = token; - } - - std::optional actions = get_hook_actions(log_strategy_or_offset); - if (!actions) - { - entry.type = HOOK_BY_OFFSET; - try - { - entry.offset = std::stoull(log_strategy_or_offset, 0, 16); - } - catch (const std::logic_error& exc) - { - throw std::runtime_error{"Invalid offset"}; - } - - std::string strategy_name = parse_token(ss); - actions = get_hook_actions(strategy_name); - if (!actions) - throw std::runtime_error{"Invalid hook action"}; - } - - entry.actions = *actions; - entry.argument_printers = parse_arguments(config, ss); - - return entry; -} -} // namespace - - void drakvuf_load_dll_hook_config(drakvuf_t drakvuf, const char* dll_hooks_list_path, bool print_no_addr, const hook_filter_t& hook_filter, wanted_hooks_t& wanted_hooks) { PrinterConfig config{}; diff --git a/src/libusermode/userhook.hpp b/src/libusermode/userhook.hpp index 7088bfbc9..edd0a6932 100644 --- a/src/libusermode/userhook.hpp +++ b/src/libusermode/userhook.hpp @@ -113,62 +113,11 @@ #include #include "plugins/plugins_ex.h" +#include "utils.hpp" #include "printers/printers.hpp" typedef event_response_t (*callback_t)(drakvuf_t drakvuf, drakvuf_trap_info* info); -enum target_hook_type -{ - HOOK_BY_NAME, - HOOK_BY_OFFSET -}; - -struct HookActions -{ - bool log; - bool stack; - - HookActions() : log{false}, stack{false} {} - - static HookActions empty() - { - return HookActions{}; - } - HookActions& set_log() - { - this->log = true; - return *this; - } - HookActions& set_stack() - { - this->stack = true; - return *this; - } -}; - -struct plugin_target_config_entry_t -{ - std::string dll_name; - target_hook_type type; - std::string function_name; - std::string clsid; - addr_t offset; - HookActions actions; - std::vector< std::unique_ptr< ArgumentPrinter > > argument_printers; - - plugin_target_config_entry_t() - : dll_name(), type(), function_name(), offset(), actions(), argument_printers() - {} - - plugin_target_config_entry_t(std::string&& dll_name, std::string&& function_name, addr_t offset, HookActions hook_actions, std::vector< std::unique_ptr< ArgumentPrinter > >&& argument_printers) - : dll_name(std::move(dll_name)), type(HOOK_BY_OFFSET), function_name(std::move(function_name)), offset(offset), actions(hook_actions), argument_printers(std::move(argument_printers)) - {} - - plugin_target_config_entry_t(std::string&& dll_name, std::string&& function_name, HookActions hook_actions, std::vector< std::unique_ptr< ArgumentPrinter > >&& argument_printers) - : dll_name(std::move(dll_name)), type(HOOK_BY_NAME), function_name(std::move(function_name)), offset(), actions(hook_actions), argument_printers(std::move(argument_printers)) - {} -}; - enum target_hook_state { HOOK_FIRST_TRY, @@ -179,23 +128,50 @@ enum target_hook_state struct hook_target_entry_t { - vmi_pid_t pid; + vmi_pid_t pid = 0; target_hook_type type; std::string target_name; std::string clsid; addr_t offset; + bool no_retval{false}; callback_t callback; const std::vector < std::unique_ptr < ArgumentPrinter > >& argument_printers; target_hook_state state; - drakvuf_trap_t* trap; + drakvuf_trap_t* trap = nullptr; void* plugin; - hook_target_entry_t(std::string target_name, std::string clsid, callback_t callback, const std::vector < std::unique_ptr < ArgumentPrinter > >& argument_printers, void* plugin) - : pid(0), type(HOOK_BY_NAME), target_name(target_name), clsid(clsid), offset(0), callback(callback), argument_printers(argument_printers), state(HOOK_FIRST_TRY), trap(nullptr), plugin(plugin) + hook_target_entry_t(std::string target_name, + std::string clsid, + bool no_retval, + callback_t callback, + const std::vector < std::unique_ptr < ArgumentPrinter > >& argument_printers, + void* plugin) + : type(HOOK_BY_NAME) + , target_name(std::move(target_name)) + , clsid(std::move(clsid)) + , offset(0) + , no_retval(no_retval) + , callback(callback) + , argument_printers(argument_printers) + , state(HOOK_FIRST_TRY) + , plugin(plugin) {} - hook_target_entry_t(std::string target_name, std::string clsid, addr_t offset, callback_t callback, const std::vector < std::unique_ptr < ArgumentPrinter > >& argument_printers, void* plugin) - : pid(0), type(HOOK_BY_OFFSET), target_name(target_name), clsid(clsid), offset(offset), callback(callback), argument_printers(argument_printers), state(HOOK_FIRST_TRY), trap(nullptr), plugin(plugin) + hook_target_entry_t(std::string target_name, + std::string clsid, + addr_t offset, + bool no_retval, + callback_t callback, + const std::vector < std::unique_ptr < ArgumentPrinter > >& argument_printers, + void* plugin) + : type(HOOK_BY_OFFSET) + , target_name(std::move(target_name)) + , clsid(std::move(clsid)) + , offset(offset) + , no_retval(no_retval) + , callback(callback) + , argument_printers(argument_printers) + , state(HOOK_FIRST_TRY), plugin(plugin) {} }; diff --git a/src/libusermode/utils.cpp b/src/libusermode/utils.cpp index 4741810d6..b5de31ae5 100644 --- a/src/libusermode/utils.cpp +++ b/src/libusermode/utils.cpp @@ -103,9 +103,11 @@ ***************************************************************************/ #include "utils.hpp" +#include "printers/printers.hpp" #include #include +#include #include bool is_dll_name_matched(const std::string& dll_name, const std::string& pattern) @@ -119,3 +121,165 @@ bool is_dll_name_matched(const std::string& dll_name, const std::string& pattern bool match_end = static_cast(std::distance(it, dll_name.end())) == pattern.size(); return match_end && (it == dll_name.begin() || *(it - 1) == '\\'); } + +namespace +{ + +std::optional get_hook_actions(const std::string& str) +{ + if (str == "log") + { + return HookActions::empty().set_log(); + } + else if (str == "log+stack") + { + return HookActions::empty().set_log().set_stack(); + } + + return std::nullopt; +} + +std::optional try_parse_token(std::stringstream& ss) +{ + const char SEPARATOR = ','; + std::string result; + if (!std::getline(ss, result, SEPARATOR) || result.empty()) + { + return std::nullopt; + } + return result; +} + +std::string parse_token(std::stringstream& ss) +{ + auto maybe_token = try_parse_token(ss); + if (!maybe_token) + { + throw std::runtime_error{"Expected a token"}; + } + return *maybe_token; +} + +std::unique_ptr make_arg_printer( + const PrinterConfig& config, + const std::string& type, + const std::string& name) +{ + if (type == "lpstr" || type == "lpcstr" || type == "lpctstr") + { + return std::make_unique(name, config); + } + else if (type == "lpcwstr" || type == "lpwstr" || type == "bstr") + { + return std::make_unique(name, config); + } + else if (type == "punicode_string") + { + return std::make_unique(name, config); + } + else if (type == "pulong") + { + return std::make_unique(name, config); + } + else if (type == "pulonglong") + { + return std::make_unique(name, config); + } + else if (type == "lpvoid*") + { + return std::make_unique(name, config); + } + else if (type == "refclsid" || type == "refiid") + { + return std::make_unique(name, config); + } + else if (type == "binary16") + { + return std::make_unique(name, config); + } + + return std::make_unique(name, config); +} + +std::vector> parse_arguments( + const PrinterConfig& config, + std::stringstream& ss) +{ + std::vector> argument_printers; + + for (size_t arg_idx = 0; ; arg_idx++) + { + auto maybe_arg = try_parse_token(ss); + if (!maybe_arg) break; + + const std::string arg = *maybe_arg; + std::string arg_name; + std::string arg_type; + const auto pos = arg.find_first_of(':'); + + if (pos == std::string::npos) + { + arg_name = std::string("Arg") + std::to_string(arg_idx); + arg_type = arg; + } + else + { + arg_name = arg.substr(0, pos); + arg_type = arg.substr(pos + 1); + } + + argument_printers.emplace_back(make_arg_printer(config, arg_type, arg_name)); + } + return argument_printers; +} + +} // namespace + +plugin_target_config_entry_t parse_entry( + std::stringstream& ss, + PrinterConfig& config) +{ + plugin_target_config_entry_t entry{}; + + entry.dll_name = parse_token(ss); + entry.function_name = parse_token(ss); + entry.type = HOOK_BY_NAME; + + for (;;) + { + std::string token = parse_token(ss); + + if (token == "clsid") + { + entry.clsid = parse_token(ss); + } + else if (token == "no-retval") + { + entry.no_retval = true; + } + else + { + std::optional actions = get_hook_actions(token); + if (actions) + { + entry.actions = *actions; + break; + } + + // offset found + entry.type = HOOK_BY_OFFSET; + try + { + entry.offset = std::stoull(token, 0, 16); + } + catch (const std::logic_error& exc) + { + throw std::runtime_error{"Invalid offset"}; + } + } + } + + entry.argument_printers = parse_arguments(config, ss); + + return entry; +} diff --git a/src/libusermode/utils.hpp b/src/libusermode/utils.hpp index 2c918a6ec..61a775508 100644 --- a/src/libusermode/utils.hpp +++ b/src/libusermode/utils.hpp @@ -105,5 +105,66 @@ #pragma once #include +#include +#include +#include bool is_dll_name_matched(const std::string& dll_name, const std::string& pattern); + +enum target_hook_type +{ + HOOK_BY_NAME, + HOOK_BY_OFFSET +}; + +struct HookActions +{ + bool log; + bool stack; + + HookActions() : log{false}, stack{false} {} + + static HookActions empty() + { + return HookActions{}; + } + HookActions& set_log() + { + this->log = true; + return *this; + } + HookActions& set_stack() + { + this->stack = true; + return *this; + } +}; + +class ArgumentPrinter; +struct PrinterConfig; + +struct plugin_target_config_entry_t +{ + std::string dll_name; + target_hook_type type; + std::string function_name; + std::string clsid; + uint64_t offset; + bool no_retval{false}; + HookActions actions; + std::vector> argument_printers; + + plugin_target_config_entry_t() + : dll_name(), type(), function_name(), offset(), actions(), argument_printers() + {} + + plugin_target_config_entry_t(std::string dll_name, std::string function_name, uint64_t offset, HookActions hook_actions, std::vector> argument_printers) + : dll_name(std::move(dll_name)), type(HOOK_BY_OFFSET), function_name(std::move(function_name)), offset(offset), actions(hook_actions), argument_printers(std::move(argument_printers)) + {} + + plugin_target_config_entry_t(std::string dll_name, std::string function_name, HookActions hook_actions, std::vector> argument_printers) + : dll_name(std::move(dll_name)), type(HOOK_BY_NAME), function_name(std::move(function_name)), offset(), actions(hook_actions), argument_printers(std::move(argument_printers)) + {} +}; + +plugin_target_config_entry_t parse_entry(std::stringstream& ss, PrinterConfig& config); diff --git a/src/plugins/apimon/apimon.cpp b/src/plugins/apimon/apimon.cpp index af869abf6..90592c59f 100644 --- a/src/plugins/apimon/apimon.cpp +++ b/src/plugins/apimon/apimon.cpp @@ -146,27 +146,21 @@ static event_response_t delete_process_cb(drakvuf_t drakvuf, drakvuf_trap_info_t return VMI_EVENT_RESPONSE_NONE; } -event_response_t apimon::usermode_return_hook_cb(drakvuf_t drakvuf, drakvuf_trap_info* info) +void apimon::usermode_print(drakvuf_trap_info* info, std::vector& args, hook_target_entry_t* target) { - auto params = libhook::GetTrapParams(info); - - if (!params->verifyResultCallParams(drakvuf, info)) - return VMI_EVENT_RESPONSE_NONE; - std::map < std::string, std::string > extra_data; if (!strcmp(info->trap->name, "CryptGenKey")) - extra_data = CryptGenKey_hook(drakvuf, info, params->arguments); + extra_data = CryptGenKey_hook(drakvuf, info, args); std::optional> clsid; - if (!params->target->clsid.empty()) - clsid = fmt::Qstr(params->target->clsid); + if (!target->clsid.empty()) + clsid = fmt::Qstr(target->clsid); std::vector> fmt_args{}; { - const auto& args = params->arguments; - const auto& printers = params->target->argument_printers; + const auto& printers = target->argument_printers; for (auto [arg, printer] = std::tuple(std::cbegin(args), std::cbegin(printers)); arg != std::cend(args) && printer != std::cend(printers); ++arg, ++printer) @@ -198,6 +192,16 @@ event_response_t apimon::usermode_return_hook_cb(drakvuf_t drakvuf, drakvuf_trap keyval("Arguments", fmt_args), keyval("Extra", fmt_extra) ); +} + +event_response_t apimon::usermode_return_hook_cb(drakvuf_t drakvuf, drakvuf_trap_info* info) +{ + auto params = libhook::GetTrapParams(info); + + if (!params->verifyResultCallParams(drakvuf, info)) + return VMI_EVENT_RESPONSE_NONE; + + usermode_print(info, params->arguments, params->target); uint64_t hookID = make_hook_id(info); ret_hooks.erase(hookID); @@ -241,15 +245,22 @@ static event_response_t usermode_hook_cb(drakvuf_t drakvuf, drakvuf_trap_info* i arguments.push_back(argument); } - uint64_t hookID = make_hook_id(info); - auto hook = plugin->createReturnHook(info, - &apimon::usermode_return_hook_cb, target->target_name.data(), drakvuf_get_limited_traps_ttl(drakvuf)); - auto params = libhook::GetTrapParams(hook->trap_); + if (target->no_retval) + { + plugin->usermode_print(info, arguments, target); + } + else + { + uint64_t hookID = make_hook_id(info); + auto hook = plugin->createReturnHook(info, + &apimon::usermode_return_hook_cb, target->target_name.data(), drakvuf_get_limited_traps_ttl(drakvuf)); + auto params = libhook::GetTrapParams(hook->trap_); - params->arguments = std::move(arguments); - params->target = target; + params->arguments = std::move(arguments); + params->target = target; - plugin->ret_hooks[hookID] = std::move(hook); + plugin->ret_hooks[hookID] = std::move(hook); + } return VMI_EVENT_RESPONSE_NONE; } diff --git a/src/plugins/apimon/apimon.h b/src/plugins/apimon/apimon.h index 0433b90ad..eb8d4275e 100644 --- a/src/plugins/apimon/apimon.h +++ b/src/plugins/apimon/apimon.h @@ -143,6 +143,8 @@ class apimon: public pluginex event_response_t usermode_return_hook_cb(drakvuf_t drakvuf, drakvuf_trap_info* info); + void usermode_print(drakvuf_trap_info*, std::vector&, hook_target_entry_t*); + std::map> ret_hooks; };