Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logging for backing parameters #94

Merged
merged 2 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 83 additions & 5 deletions plugins/apitracing/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
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).
10 changes: 9 additions & 1 deletion plugins/apitracing/src/lib/ApiTracing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,20 @@ namespace ApiTracing

logger->debug("ApiTracing plugin version info", {{"Version", PLUGIN_VERSION}, {"BuildNumber", BUILD_VERSION}});

auto apiTracingConfig = std::make_unique<Config>(config);
auto apiTracingConfig = std::make_unique<Config>(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);
Expand Down
15 changes: 15 additions & 0 deletions plugins/apitracing/src/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

cakeless marked this conversation as resolved.
Show resolved Hide resolved
38 changes: 27 additions & 11 deletions plugins/apitracing/src/lib/FunctionHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<ExtractedParameterInformation>& extractedParameters)
Json::Value
FunctionHook::getParameterListAsJson(const std::vector<ExtractedParameterInformation>& 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<std::remove_reference_t<decltype(arg)>>(arg)}});
},
extractedParameter.data);
// TODO: Log backing parameters
Json::Value parameter;
if (!extractedParameter.backingParameters.empty())
{
parameter[extractedParameter.name] = getParameterListAsJson(extractedParameter.backingParameters);
}
else
{
std::visit([&parameter = parameter, &extractedParameter = extractedParameter]<typename T>(T&& arg)
{ parameter[extractedParameter.name] = std::forward<T>(arg); },
extractedParameter.data);
}
parameterList.append(parameter);
}
return parameterList;
}

void FunctionHook::teardown() const
Expand Down
5 changes: 4 additions & 1 deletion plugins/apitracing/src/lib/FunctionHook.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include "ConstantDefinitions.h"
#include "config/FunctionDefinitions.h"
#include "os/Extractor.h"
#include <json/value.h>
#include <json/writer.h>
#include <vmicore/io/ILogger.h>
#include <vmicore/plugins/PluginInterface.h>
#include <vmicore/vmi/IBreakpoint.h>
Expand Down Expand Up @@ -36,8 +38,9 @@ namespace ApiTracing
std::shared_ptr<std::vector<ParameterInformation>> parameterInformation;
VmiCore::Plugin::PluginInterface* pluginInterface;
std::unique_ptr<VmiCore::ILogger> logger;
Json::StreamWriterBuilder builder;

void logParameterList(const std::vector<ExtractedParameterInformation>& extractedParameters);
Json::Value getParameterListAsJson(const std::vector<ExtractedParameterInformation>& extractedParameters);
};
}
#endif // APITRACING_FUNCTIONHOOK_H
11 changes: 10 additions & 1 deletion plugins/apitracing/src/lib/config/Config.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
#include "Config.h"
#include "../Filenames.h"
#include "TracingDefinitions.h"
#include <vmicore/plugins/IPluginConfig.h>
#include <vmicore/plugins/PluginInterface.h>

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())
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion plugins/apitracing/src/lib/config/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#include <map>
#include <optional>
#include <string_view>
#include <vmicore/io/ILogger.h>
#include <vmicore/plugins/IPluginConfig.h>
#include <vmicore/plugins/PluginInterface.h>
#include <yaml-cpp/yaml.h>

namespace ApiTracing
Expand All @@ -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;

Expand All @@ -44,6 +47,7 @@ namespace ApiTracing
void setFunctionDefinitionsPath(const std::filesystem::path& path) override;

private:
std::unique_ptr<VmiCore::ILogger> logger;
std::filesystem::path configFileDir;
std::filesystem::path functionDefinitions;
std::map<std::string, TracingProfile, std::less<>> profiles;
Expand Down
46 changes: 33 additions & 13 deletions plugins/apitracing/test/Config_UnitTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,32 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <string_view>
#include <vmicore_test/io/mock_Logger.h>
#include <vmicore_test/plugins/mock_PluginConfig.h>
#include <vmicore_test/plugins/mock_PluginInterface.h>

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<MockPluginInterface> pluginInterface = std::make_unique<NiceMock<MockPluginInterface>>();

void SetUp() override
{
ON_CALL(*pluginInterface, newNamedLogger(_))
.WillByDefault([]() { return std::make_unique<NiceMock<VmiCore::MockLogger>>(); });
}
};

std::unique_ptr<MockPluginConfig> createMockPluginConfig(const YAML::Node& configNode)
{
auto mockPluginConfig = std::make_unique<MockPluginConfig>();
Expand All @@ -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<Config>(*createMockPluginConfig(emptyConfigRootNode)));
EXPECT_ANY_THROW(std::make_unique<Config>(pluginInterface.get(), *createMockPluginConfig(emptyConfigRootNode)));
}

TEST(ConfigTests, getTracingProfile_calcProcess_correctProfile)
TEST_F(ConfigTestFixture, getTracingProfile_calcProcess_correctProfile)
{

auto config = std::make_unique<Config>(*createMockPluginConfig("testConfiguration.yaml"));
auto config =
std::make_unique<Config>(pluginInterface.get(), *createMockPluginConfig("testConfiguration.yaml"));
TracingProfile expectedTracingProfile{
.name = "calc",
.traceChildren = true,
Expand All @@ -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<Config>(*createMockPluginConfig("testConfiguration.yaml"));
auto config =
std::make_unique<Config>(pluginInterface.get(), *createMockPluginConfig("testConfiguration.yaml"));
TracingProfile expectedTracingProfile{
.name = "default", .traceChildren = true, .modules = {{.name = "ntdll.dll", .functions = {{"function1"}}}}};

Expand All @@ -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<Config>(*createMockPluginConfig("testConfiguration.yaml"));
auto config =
std::make_unique<Config>(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<Config>(*createMockPluginConfig(configRootNode));
auto config = std::make_unique<Config>(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<Config>(*createMockPluginConfig("testConfiguration.yaml"));
auto config =
std::make_unique<Config>(pluginInterface.get(), *createMockPluginConfig("testConfiguration.yaml"));

EXPECT_NO_THROW(YAML::LoadFile(config->getFunctionDefinitionsPath()));
}
Expand Down