From 2a07ea0bb51d582635301ecdba67a77d7b09af07 Mon Sep 17 00:00:00 2001 From: Manuel Bischof Date: Wed, 4 Oct 2023 11:32:14 +0200 Subject: [PATCH 1/2] Add backing parameter logging --- plugins/apitracing/Readme.md | 88 +++++++++++++++++-- plugins/apitracing/src/lib/CMakeLists.txt | 15 ++++ plugins/apitracing/src/lib/FunctionHook.cpp | 38 +++++--- plugins/apitracing/src/lib/FunctionHook.h | 5 +- .../apitracing/test/FunctionHook_UnitTest.cpp | 5 ++ 5 files changed, 134 insertions(+), 17 deletions(-) diff --git a/plugins/apitracing/Readme.md b/plugins/apitracing/Readme.md index 35c5dcce..1db8fb01 100644 --- a/plugins/apitracing/Readme.md +++ b/plugins/apitracing/Readme.md @@ -55,10 +55,18 @@ Modules: ReturnValue: BOOL Structures: LPPROCESS_INFORMATION: - hProcess: HANDLE - hThread: HANDLE - dwProcessId: DWORD - dwThreadId: DWORD + hProcess: + Type: HANDLE + Offset: 0 + hThread: + Type: HANDLE + Offset: 8 + dwProcessId: + Type: DWORD + Offset: 16 + dwThreadId: + Type: DWORD + Offset: 20 HighLevelParameterTypes: AddressWidth32Bit: DWORD: unsigned long @@ -88,4 +96,74 @@ type *LPPROCESS_INFORMATION*. For example the parameter *hProcess* is of the type *HANDLE*. Depending on the address width of the process it gets resolved to either *unsigned int* or *unsigned _int64*. Those are *BackingParameterTypes* so either 8 or 4 byte are read when -extracting this structure from the heap. \ No newline at end of file +extracting this structure from the heap. + +## Extraction Format + +Function call traces are logged in an unindented json format which is illustrated indented below for better visibility. +The parameter extraction logs are identified by the *key* ```Parameterlist:```. +Its *value* is a list of all function call parameters, whereby the parameter names form the *keys* of the contained *key:value* pairs. +Each *value* is either the value of the parameter as integer or string value, or it is a list containing +the *key:value* pairs of backing parameters if it's a pointer to a struct. + + +```json +{ + "Parameterlist": [ + {"FileHandle":[ + {"HANDLE":45}]}, + {"DesiredAccess":1180063}, + {"ObjectAttributes":[ + {"Length":48}, + {"RootDirectory":0}, + {"ObjectName":"\\Device\\ConDrv\\Server"}, + {"Attributes":66}, + {"SecurityDescriptor":0}, + {"SecurityQualityOfService":0}]}, + {"IoStatusBlock":958106952720}, + {"AllocationSize":0}, + {"FileAttributes":0}, + {"ShareAccess":7}, + {"CreateDisposition":2}, + {"CreateOptions":0}, + {"EaBuffer":0}, + {"EaLength":0}] +} +``` + +The log example above is a function call of *NtCreateFile* with these parameters: + +```c +__kernel_entry NTSTATUS NtCreateFile( + [out] PHANDLE FileHandle, + [in] ACCESS_MASK DesiredAccess, + [in] POBJECT_ATTRIBUTES ObjectAttributes, + [out] PIO_STATUS_BLOCK IoStatusBlock, + [in, optional] PLARGE_INTEGER AllocationSize, + [in] ULONG FileAttributes, + [in] ULONG ShareAccess, + [in] ULONG CreateDisposition, + [in] ULONG CreateOptions, + [in] PVOID EaBuffer, + [in] ULONG EaLength +); +``` + +*FileHandle, DesiredAccess, ObjectAttributues, IOStatusBlocck,AllocationSize, FileAttributes,ShareAccess, CreateDisposition, +CreateOptions, EaBuffer* and *EaLength* are members of the list in parameterlist *value*. + +```c +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES; +``` + +*ObjectAttributes* is a pointer to a struct, so its value is a list containing the backing parameters. +The parameter *ObjectName* is a pointer to a unicode and forms an exception since it is extracted directly. +Both *PVOID* at the end are structs, that are currently not covered by our definitions. +You can find a list under the struct section in the [function definitions file](configuration/functiondefinitions/functionDefinitions.yaml). diff --git a/plugins/apitracing/src/lib/CMakeLists.txt b/plugins/apitracing/src/lib/CMakeLists.txt index 850a05a3..3590a3d9 100644 --- a/plugins/apitracing/src/lib/CMakeLists.txt +++ b/plugins/apitracing/src/lib/CMakeLists.txt @@ -44,7 +44,22 @@ set_property(TARGET yaml-cpp PROPERTY POSITION_INDEPENDENT_CODE TRUE) target_compile_definitions(apitracing-obj PUBLIC YAML_CPP_SUPPORT) target_link_libraries(apitracing-obj PUBLIC yaml-cpp) +# Setup json-cpp + +FetchContent_Declare( + jsoncpp + GIT_REPOSITORY https://github.com/open-source-parsers/jsoncpp.git + GIT_TAG 1.9.5 +) +option(JSONCPP_WITH_TESTS "" OFF) +option(JSONCPP_WITH_POST_BUILD_UNITTEST "" OFF) +option(JSONCPP_WITH_EXAMPLE "" OFF) +FetchContent_MakeAvailable(jsoncpp) +set_property(TARGET jsoncpp_static PROPERTY POSITION_INDEPENDENT_CODE TRUE) +target_link_libraries(apitracing-obj PUBLIC jsoncpp_static) + # Add public vmicore headers add_subdirectory("${VMICORE_DIRECTORY_ROOT}/src/include" "${CMAKE_CURRENT_BINARY_DIR}/vmicore-public-headers") target_link_libraries(apitracing-obj PUBLIC vmicore-public-headers) + diff --git a/plugins/apitracing/src/lib/FunctionHook.cpp b/plugins/apitracing/src/lib/FunctionHook.cpp index ca9cfc8d..1b4087e6 100644 --- a/plugins/apitracing/src/lib/FunctionHook.cpp +++ b/plugins/apitracing/src/lib/FunctionHook.cpp @@ -29,6 +29,7 @@ namespace ApiTracing logger(this->pluginInterface->newNamedLogger(APITRACING_LOGGER_NAME)) { logger->bind({{VmiCore::WRITE_TO_FILE_TAG, LOG_FILENAME}}); + builder["indentation"] = ""; } void FunctionHook::hookFunction(VmiCore::addr_t moduleBaseAddress, @@ -52,25 +53,40 @@ namespace ApiTracing } auto extractedParameters = extractor->extractParameters(event, parameterInformation); - logParameterList(extractedParameters); + auto json = getParameterListAsJson(extractedParameters); + std::string unformattedTraces = Json::writeString(builder, json); + + logger->info("Monitored function called", + {{"FunctionName", functionName}, + {"ModuleName", moduleName}, + {"ProcessDtb", fmt::format("{:x}", event.getCr3())}, + {"ProcessTeb", fmt::format("{:x}", event.getGs())}, + {"Parameterlist", unformattedTraces}}); return BpResponse::Continue; } - void FunctionHook::logParameterList(const std::vector& extractedParameters) + Json::Value + FunctionHook::getParameterListAsJson(const std::vector& extractedParameters) { + Json::Value parameterList; + for (const auto& extractedParameter : extractedParameters) { - std::visit( - [&extractedParameter = extractedParameter, &logger = logger](auto&& arg) - { - logger->info("Parameter", - {{"Name", extractedParameter.name}, - {"Value", std::forward>(arg)}}); - }, - extractedParameter.data); - // TODO: Log backing parameters + Json::Value parameter; + if (!extractedParameter.backingParameters.empty()) + { + parameter[extractedParameter.name] = getParameterListAsJson(extractedParameter.backingParameters); + } + else + { + std::visit([¶meter = parameter, &extractedParameter = extractedParameter](T&& arg) + { parameter[extractedParameter.name] = std::forward(arg); }, + extractedParameter.data); + } + parameterList.append(parameter); } + return parameterList; } void FunctionHook::teardown() const diff --git a/plugins/apitracing/src/lib/FunctionHook.h b/plugins/apitracing/src/lib/FunctionHook.h index 6f265315..e4c39405 100644 --- a/plugins/apitracing/src/lib/FunctionHook.h +++ b/plugins/apitracing/src/lib/FunctionHook.h @@ -4,6 +4,8 @@ #include "ConstantDefinitions.h" #include "config/FunctionDefinitions.h" #include "os/Extractor.h" +#include +#include #include #include #include @@ -36,8 +38,9 @@ namespace ApiTracing std::shared_ptr> parameterInformation; VmiCore::Plugin::PluginInterface* pluginInterface; std::unique_ptr logger; + Json::StreamWriterBuilder builder; - void logParameterList(const std::vector& extractedParameters); + Json::Value getParameterListAsJson(const std::vector& extractedParameters); }; } #endif // APITRACING_FUNCTIONHOOK_H diff --git a/plugins/apitracing/test/FunctionHook_UnitTest.cpp b/plugins/apitracing/test/FunctionHook_UnitTest.cpp index 52dd07bd..5211eee1 100644 --- a/plugins/apitracing/test/FunctionHook_UnitTest.cpp +++ b/plugins/apitracing/test/FunctionHook_UnitTest.cpp @@ -53,6 +53,11 @@ namespace ApiTracing std::shared_ptr introspectionAPI = std::make_shared>(); std::shared_ptr extractor = std::make_shared>(); std::shared_ptr interruptEvent = std::make_shared>(); + std::vector extractedParameterInformation{ + {.name = "TestInt0", .data = 0}, + {.name = "TestInt1", + .data = 3, + .backingParameters = {{.name = "TestInt2", .data = 4}, {.name = "TestString1", .data = "A"}}}}; void SetUp() override { From 18b4dcf73eaa58fdde6fef2c89324d159f62ffcf Mon Sep 17 00:00:00 2001 From: Kevin Mayer Date: Mon, 25 Sep 2023 10:42:23 +0200 Subject: [PATCH 2/2] Add debug logging for missing traces --- plugins/apitracing/src/lib/ApiTracing.cpp | 10 +++- plugins/apitracing/src/lib/config/Config.cpp | 11 ++++- plugins/apitracing/src/lib/config/Config.h | 6 ++- plugins/apitracing/test/Config_UnitTest.cpp | 46 +++++++++++++------ .../apitracing/test/FunctionHook_UnitTest.cpp | 5 -- 5 files changed, 57 insertions(+), 21 deletions(-) diff --git a/plugins/apitracing/src/lib/ApiTracing.cpp b/plugins/apitracing/src/lib/ApiTracing.cpp index dea9769f..ca3fe2b7 100644 --- a/plugins/apitracing/src/lib/ApiTracing.cpp +++ b/plugins/apitracing/src/lib/ApiTracing.cpp @@ -46,12 +46,20 @@ namespace ApiTracing logger->debug("ApiTracing plugin version info", {{"Version", PLUGIN_VERSION}, {"BuildNumber", BUILD_VERSION}}); - auto apiTracingConfig = std::make_unique(config); + auto apiTracingConfig = std::make_unique(pluginInterface, config); + + std::string commandLine; + for (const auto& piece : args) + { + commandLine += " " + piece; + } + logger->info("ApiTracing command line", {{"commandLine:", commandLine}}); if (functionDefinitionsPath.isSet()) { apiTracingConfig->setFunctionDefinitionsPath(functionDefinitionsPath); } + if (traceProcessName.isSet()) { apiTracingConfig->addTracingTarget(traceProcessName); diff --git a/plugins/apitracing/src/lib/config/Config.cpp b/plugins/apitracing/src/lib/config/Config.cpp index 843bf839..30983bb1 100644 --- a/plugins/apitracing/src/lib/config/Config.cpp +++ b/plugins/apitracing/src/lib/config/Config.cpp @@ -1,10 +1,14 @@ #include "Config.h" +#include "../Filenames.h" #include "TracingDefinitions.h" #include +#include namespace ApiTracing { - Config::Config(const VmiCore::Plugin::IPluginConfig& pluginConfig) + Config::Config(const VmiCore::Plugin::PluginInterface* pluginInterface, + const VmiCore::Plugin::IPluginConfig& pluginConfig) + : logger(pluginInterface->newNamedLogger(APITRACING_LOGGER_NAME)) { auto configRootNode = pluginConfig.rootNode(); if (auto configFilePath = pluginConfig.configFilePath()) @@ -96,7 +100,12 @@ namespace ApiTracing void Config::addTracingTarget(const std::string& name) { + logger->debug("addTracingTarget", {{"Name", name}}); processTracingProfiles.emplace(name, profiles["default"]); + for (auto [processName, profile] : processTracingProfiles) + { + logger->debug("tracing profiles", {{"ProcessName", processName}, {"TracingProfile", profile.name}}); + } } void Config::setFunctionDefinitionsPath(const std::filesystem::path& path) diff --git a/plugins/apitracing/src/lib/config/Config.h b/plugins/apitracing/src/lib/config/Config.h index 06e7c2b1..9fad62cf 100644 --- a/plugins/apitracing/src/lib/config/Config.h +++ b/plugins/apitracing/src/lib/config/Config.h @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #include namespace ApiTracing @@ -31,7 +33,8 @@ namespace ApiTracing class Config : public IConfig { public: - explicit Config(const VmiCore::Plugin::IPluginConfig& pluginConfig); + explicit Config(const VmiCore::Plugin::PluginInterface* pluginInterface, + const VmiCore::Plugin::IPluginConfig& pluginConfig); ~Config() override = default; @@ -44,6 +47,7 @@ namespace ApiTracing void setFunctionDefinitionsPath(const std::filesystem::path& path) override; private: + std::unique_ptr logger; std::filesystem::path configFileDir; std::filesystem::path functionDefinitions; std::map> profiles; diff --git a/plugins/apitracing/test/Config_UnitTest.cpp b/plugins/apitracing/test/Config_UnitTest.cpp index c504cb21..f03bec11 100644 --- a/plugins/apitracing/test/Config_UnitTest.cpp +++ b/plugins/apitracing/test/Config_UnitTest.cpp @@ -2,15 +2,32 @@ #include #include #include +#include #include +#include +using testing::_; // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) +using testing::NiceMock; using testing::Return; using VmiCore::Plugin::MockPluginConfig; +using VmiCore::Plugin::MockPluginInterface; namespace ApiTracing { constexpr std::string_view defaultMainConfigFileDir = "/usr/local/var/"; + class ConfigTestFixture : public testing::Test + { + protected: + std::unique_ptr pluginInterface = std::make_unique>(); + + void SetUp() override + { + ON_CALL(*pluginInterface, newNamedLogger(_)) + .WillByDefault([]() { return std::make_unique>(); }); + } + }; + std::unique_ptr createMockPluginConfig(const YAML::Node& configNode) { auto mockPluginConfig = std::make_unique(); @@ -32,17 +49,17 @@ namespace ApiTracing return mockPluginConfig; } - TEST(ConfigTests, constructor_functionDefinitionsKeyMissing_throws) + TEST_F(ConfigTestFixture, constructor_functionDefinitionsKeyMissing_throws) { YAML::Node emptyConfigRootNode; - EXPECT_ANY_THROW(std::make_unique(*createMockPluginConfig(emptyConfigRootNode))); + EXPECT_ANY_THROW(std::make_unique(pluginInterface.get(), *createMockPluginConfig(emptyConfigRootNode))); } - TEST(ConfigTests, getTracingProfile_calcProcess_correctProfile) + TEST_F(ConfigTestFixture, getTracingProfile_calcProcess_correctProfile) { - - auto config = std::make_unique(*createMockPluginConfig("testConfiguration.yaml")); + auto config = + std::make_unique(pluginInterface.get(), *createMockPluginConfig("testConfiguration.yaml")); TracingProfile expectedTracingProfile{ .name = "calc", .traceChildren = true, @@ -54,9 +71,10 @@ namespace ApiTracing EXPECT_EQ(tracingProfile, expectedTracingProfile); } - TEST(ConfigTests, getTracingProfile_processWithoutProfile_defaultProfile) + TEST_F(ConfigTestFixture, getTracingProfile_processWithoutProfile_defaultProfile) { - auto config = std::make_unique(*createMockPluginConfig("testConfiguration.yaml")); + auto config = + std::make_unique(pluginInterface.get(), *createMockPluginConfig("testConfiguration.yaml")); TracingProfile expectedTracingProfile{ .name = "default", .traceChildren = true, .modules = {{.name = "ntdll.dll", .functions = {{"function1"}}}}}; @@ -65,28 +83,30 @@ namespace ApiTracing EXPECT_EQ(tracingProfile, expectedTracingProfile); } - TEST(ConfigTests, getTracingProfile_unknownProcessName_nullopt) + TEST_F(ConfigTestFixture, getTracingProfile_unknownProcessName_nullopt) { - auto config = std::make_unique(*createMockPluginConfig("testConfiguration.yaml")); + auto config = + std::make_unique(pluginInterface.get(), *createMockPluginConfig("testConfiguration.yaml")); auto tracingProfile = config->getTracingProfile("unknown.exe"); EXPECT_FALSE(tracingProfile); } - TEST(ConfigTests, getFunctionDefinitionsPath_configFromVmiCoreConfigFile_correctAbsolutePath) + TEST_F(ConfigTestFixture, getFunctionDefinitionsPath_configFromVmiCoreConfigFile_correctAbsolutePath) { YAML::Node configRootNode; configRootNode["function_definitions"] = "test.yaml"; - auto config = std::make_unique(*createMockPluginConfig(configRootNode)); + auto config = std::make_unique(pluginInterface.get(), *createMockPluginConfig(configRootNode)); EXPECT_EQ(config->getFunctionDefinitionsPath(), std::filesystem::path(defaultMainConfigFileDir) / "test.yaml"); } - TEST(ConfigTests, getFunctionDefinitionsPath_configWithFunctionDefinitionsKey_correctFilePath) + TEST_F(ConfigTestFixture, getFunctionDefinitionsPath_configWithFunctionDefinitionsKey_correctFilePath) { - auto config = std::make_unique(*createMockPluginConfig("testConfiguration.yaml")); + auto config = + std::make_unique(pluginInterface.get(), *createMockPluginConfig("testConfiguration.yaml")); EXPECT_NO_THROW(YAML::LoadFile(config->getFunctionDefinitionsPath())); } diff --git a/plugins/apitracing/test/FunctionHook_UnitTest.cpp b/plugins/apitracing/test/FunctionHook_UnitTest.cpp index 5211eee1..52dd07bd 100644 --- a/plugins/apitracing/test/FunctionHook_UnitTest.cpp +++ b/plugins/apitracing/test/FunctionHook_UnitTest.cpp @@ -53,11 +53,6 @@ namespace ApiTracing std::shared_ptr introspectionAPI = std::make_shared>(); std::shared_ptr extractor = std::make_shared>(); std::shared_ptr interruptEvent = std::make_shared>(); - std::vector extractedParameterInformation{ - {.name = "TestInt0", .data = 0}, - {.name = "TestInt1", - .data = 3, - .backingParameters = {{.name = "TestInt2", .data = 4}, {.name = "TestString1", .data = "A"}}}}; void SetUp() override {