Skip to content

Commit

Permalink
Improve parameter extraction
Browse files Browse the repository at this point in the history
  • Loading branch information
Manuel Bischof committed Oct 4, 2023
1 parent 2a07ea0 commit 038f4b3
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 38 deletions.
82 changes: 66 additions & 16 deletions plugins/apitracing/src/lib/os/Extractor.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
#include "Extractor.h"
#include "../ConstantDefinitions.h"
#include "../Filenames.h"
#include <fmt/core.h>
#include <stdexcept>

using VmiCore::addr_t;
using VmiCore::IInterruptEvent;
using VmiCore::IIntrospectionAPI;

namespace ApiTracing
{
Extractor::Extractor(std::shared_ptr<IIntrospectionAPI> introspectionApi, uint8_t addressWidth)
: addressWidth(addressWidth), introspectionAPI(std::move(introspectionApi))
Extractor::Extractor(std::shared_ptr<VmiCore::IIntrospectionAPI> introspectionApi,
VmiCore::Plugin::PluginInterface* pluginInterface,
uint8_t addressWidth)
: addressWidth(addressWidth),
introspectionAPI(std::move(introspectionApi)),
pluginInterface(pluginInterface),
logger(this->pluginInterface->newNamedLogger(APITRACING_LOGGER_NAME))
{
}

Expand Down Expand Up @@ -74,23 +81,41 @@ namespace ApiTracing

for (const auto& parameterInfo : *parameterInformation)
{
if (!parameterInfo.basicType.empty())
if (parameterInfo.basicType.empty())
{
extractedParameterInformation.emplace_back(
extractSingleParameter(shallowParameters.at(parameterIndex), cr3, parameterInfo));
throw std::runtime_error("Malformed parameter information ! Aborting");
}
else if (!parameterInfo.backingParameters.empty())

if (!parameterInfo.backingParameters.empty())
{
addr_t structVA = dereferencePointer(shallowParameters.at(parameterIndex), cr3);
auto pointerAddress = shallowParameters.at(parameterIndex);
// skip optional parameters (e.g. AllocationSize in NtCreateFile)
if (pointerAddress == 0)
{
extractedParameterInformation.emplace_back(
extractSingleParameter(shallowParameters.at(parameterIndex), cr3, parameterInfo));
parameterIndex++;
continue;
}

ExtractedParameterInformation paramStruct{
.name = parameterInfo.name, .data = {}, .backingParameters = {}};
paramStruct.backingParameters =
extractBackingParameters({parameterInfo.backingParameters}, structVA, cr3);
try
{
paramStruct.backingParameters = extractBackingParameters(
{parameterInfo.backingParameters}, shallowParameters.at(parameterIndex), cr3);
}
catch (const std::exception& e)
{
logger->debug("Could not deep extract parameter",
{{"name", parameterInfo.name}, {"exception", e.what()}});
}
extractedParameterInformation.emplace_back(paramStruct);
}
else
{
throw std::runtime_error("Malformed parameter information ! Aborting");
extractedParameterInformation.emplace_back(
extractSingleParameter(shallowParameters.at(parameterIndex), cr3, parameterInfo));
}
parameterIndex++;
}
Expand All @@ -103,8 +128,17 @@ namespace ApiTracing
{
ExtractedParameterInformation result{};
result.name = parameterInfo.name;

switch (basicTypeStringToEnum.at(parameterInfo.basicType))
BasicTypes parameterType;
try
{
parameterType = basicTypeStringToEnum.at(parameterInfo.basicType);
}
catch (...)
{
std::throw_with_nested(
std::invalid_argument(fmt::format("Parameter not defined: {}", parameterInfo.basicType)));
}
switch (parameterType)
{
using enum BasicTypes;

Expand All @@ -123,11 +157,19 @@ namespace ApiTracing
case UNICODE_WSTR_32:
case UNICODE_WSTR_64:
{
result.data = extractUnicodeString(shallowParameter, cr3);
try
{
result.data = extractUnicodeString(shallowParameter, cr3);
}
catch (std::exception& e)
{
result.data = "";
}
break;
}
case __PTR32:
case __PTR64:
case UNSIGNED___INT32:
case UNSIGNED___INT64:
case UNSIGNED_LONG:
case UNSIGNED_INT:
Expand All @@ -145,7 +187,7 @@ namespace ApiTracing
}
default:
{
break;
throw std::invalid_argument(fmt::format("Parameter not defined: {}", parameterInfo.basicType));
}
}
return result;
Expand Down Expand Up @@ -213,8 +255,16 @@ namespace ApiTracing
// to the nexţ iteration
addr_t structPointer = dereferencePointer(address + parameter.offset, cr3);
ExtractedParameterInformation extraction{.name = parameter.name, .data = {}, .backingParameters = {}};
extraction.backingParameters =
extractBackingParameters(parameter.backingParameters, structPointer, cr3);
try
{
extraction.backingParameters =
extractBackingParameters(parameter.backingParameters, structPointer, cr3);
}
catch (const std::exception& e)
{
logger->debug("Could not extract backing parameter",
{{"name", parameter.name}, {"exception", e.what()}});
}
extractedBackingParameters.emplace_back(extraction);
}
}
Expand Down
11 changes: 10 additions & 1 deletion plugins/apitracing/src/lib/os/Extractor.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <ostream>
#include <variant>
#include <vector>
#include <vmicore/plugins/PluginInterface.h>
#include <vmicore/vmi/IIntrospectionAPI.h>
#include <vmicore/vmi/events/IInterruptEvent.h>

Expand All @@ -26,6 +27,7 @@ namespace ApiTracing
INT,
LONG,
__INT64,
UNSIGNED___INT32,
UNSIGNED___INT64,
UNSIGNED_LONG,
UNSIGNED_INT,
Expand Down Expand Up @@ -86,7 +88,9 @@ namespace ApiTracing
class Extractor : public IExtractor
{
public:
Extractor(std::shared_ptr<VmiCore::IIntrospectionAPI> introspectionApi, uint8_t addressWidth);
Extractor(std::shared_ptr<VmiCore::IIntrospectionAPI> introspectionApi,
VmiCore::Plugin::PluginInterface* pluginInterface,
uint8_t addressWidth);

[[nodiscard]] std::vector<ExtractedParameterInformation>
extractParameters(VmiCore::IInterruptEvent& event,
Expand All @@ -105,6 +109,8 @@ namespace ApiTracing
uint8_t addressWidth;
std::variant<std::string, uint64_t, int> extractedParameters;
std::shared_ptr<VmiCore::IIntrospectionAPI> introspectionAPI;
VmiCore::Plugin::PluginInterface* pluginInterface;
std::unique_ptr<VmiCore::ILogger> logger;
std::map<std::string, BasicTypes, std::less<>> basicTypeStringToEnum{
{"LPSTR_32", BasicTypes::LPSTR_32},
{"LPSTR_64", BasicTypes::LPSTR_64},
Expand All @@ -117,6 +123,7 @@ namespace ApiTracing
{"int", BasicTypes::INT},
{"long", BasicTypes::LONG},
{"__int64", BasicTypes::__INT64},
{"unsigned __int32", BasicTypes::UNSIGNED___INT32},
{"unsigned __int64", BasicTypes::UNSIGNED___INT64},
{"unsigned long", BasicTypes::UNSIGNED_LONG},
{"unsigned int", BasicTypes::UNSIGNED_INT},
Expand All @@ -135,6 +142,8 @@ namespace ApiTracing
[[nodiscard]] std::vector<ExtractedParameterInformation> extractBackingParameters(
const std::vector<ParameterInformation>& backingParameters, uint64_t address, uint64_t cr3);

[[nodiscard]] size_t getMissingAlignment(uint64_t currentAddress, uint64_t nextParameterSize) const;

[[nodiscard]] uint64_t readParameter(uint64_t stackPointerVA, uint8_t parameterSize, uint64_t cr3) const;

[[nodiscard]] uint64_t zeroGarbageBytes(uint64_t parameter, uint8_t parameterSize) const;
Expand Down
83 changes: 62 additions & 21 deletions plugins/apitracing/test/Extractor_Unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
#include "TestConstantDefinitions.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <vmicore_test/io/mock_Logger.h>
#include <vmicore_test/plugins/mock_PluginInterface.h>
#include <vmicore_test/vmi/mock_InterruptEvent.h>
#include <vmicore_test/vmi/mock_IntrospectionAPI.h>

using testing::_; // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
using testing::ContainerEq;
using testing::NiceMock;
using testing::Return;
using VmiCore::MockInterruptEvent;
using VmiCore::MockIntrospectionAPI;
using VmiCore::Plugin::MockPluginInterface;

namespace ApiTracing
{
Expand Down Expand Up @@ -61,7 +66,6 @@ namespace ApiTracing
// clang-format off
// @formatter:off
const auto testParams32 = std::vector<TestParameterInformation>{

{.parameterInformation{.name = "param1", .size = TestConstantDefinitions::fourBytes, .backingParameters{}},
.expectedValue = param1Value},
{.parameterInformation{.name = "param2", .size = TestConstantDefinitions::fourBytes, .backingParameters{}},
Expand All @@ -73,53 +77,72 @@ namespace ApiTracing
{.parameterInformation{.name = "param5", .size = TestConstantDefinitions::fourBytes, .backingParameters{}},
.expectedValue = param5Value},
};

const auto testParamUnknownType = std::vector<TestParameterInformation>{
{.parameterInformation{.basicType = "__unknown int96", .name = "UnknownType", .size = TestConstantDefinitions::fourBytes, .backingParameters{}},
.expectedValue = param1Value},

};
// @formatter:on
// clang-format on

const auto objectAttributesBackingParametersLevelTwo =
std::vector<TestParameterInformation>{{.parameterInformation{.basicType = "unsigned long",
.name = "ObjectAttributesTwoContentOne",
.size = TestConstantDefinitions::fourBytes,
.offset = ObjectAttributesTwoContentOneOffset,
.backingParameters{}},
.expectedValue = ObjectAttributesTwoContentOneValue},
{.parameterInformation{.basicType = "LPSTR_64",
.name = "ObjectAttributesTwoContentTwo",
.size = TestConstantDefinitions::eightBytes,
.offset = ObjectAttributesTwoContentTwoOffset,
.backingParameters{}},
.expectedValue = ObjectAttributesTwoContentTwoValue}};
const auto objectAttributesBackingParametersLevelTwo = std::vector<TestParameterInformation>{
// resides on ObjectAttributesTwoValue, value is ObjectAttributesTwoContentOneValue
{.parameterInformation{.basicType = "unsigned long",
.name = "ObjectAttributesTwoContentOne",
.size = TestConstantDefinitions::fourBytes,
.offset = ObjectAttributesTwoContentOneOffset,
.backingParameters{}},
.expectedValue = ObjectAttributesTwoContentOneValue},
// resides on ObjectAttributesTwoValue + ObjectAttributesTwoContentTwoOffset,
// value is ObjectAttributesTwoContentTwoValue which is the address to a string
{.parameterInformation{.basicType = "LPSTR_64",
.name = "ObjectAttributesTwoContentTwo",
.size = TestConstantDefinitions::eightBytes,
.offset = ObjectAttributesTwoContentTwoOffset,
.backingParameters{}},
.expectedValue = ObjectAttributesTwoContentTwoValue}};

const auto objectAttributesBackingParametersLevelOne = std::vector<TestParameterInformation>{
// resides on param2Value, value is ObjectAttributesTwoValue
{.parameterInformation{
.basicType = "LPSTR_64",
.name = "ObjectAttributesTwo",
.size = TestConstantDefinitions::eightBytes,
.backingParameters{{objectAttributesBackingParametersLevelTwo.at(0).parameterInformation},
{objectAttributesBackingParametersLevelTwo.at(1).parameterInformation}}},
.expectedValue = ObjectAttributesTwoValue}};

// Two function parameters in rcx and rdx
const auto testNestedStruct = std::vector<TestParameterInformation>{
// resides in rcx, value is param1Value
{.parameterInformation{.basicType = "unsigned __int64",
.name = "FileHandle",
.size = TestConstantDefinitions::eightBytes,
.backingParameters{}},
.expectedValue = param1Value},
// resides in rdx, value is param2Value
{.parameterInformation{
.basicType = "LPSTR_64",
.name = "ObjectAttributesOne",
.size = TestConstantDefinitions::eightBytes,
.backingParameters{{objectAttributesBackingParametersLevelOne.at(0).parameterInformation}}},
.expectedValue = ObjectAttributesOneValue}};
.expectedValue = param2Value}};
}

class ExtractorFixture : public testing::Test
{
protected:
std::shared_ptr<MockIntrospectionAPI> introspectionAPI = std::make_shared<MockIntrospectionAPI>();
std::shared_ptr<MockInterruptEvent> interruptEvent = std::make_shared<MockInterruptEvent>();
std::unique_ptr<MockPluginInterface> pluginInterface = std::make_unique<NiceMock<MockPluginInterface>>();
std::shared_ptr<std::vector<ParameterInformation>> paramInformation;

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

ON_CALL(*interruptEvent, getCr3()).WillByDefault(Return(testDtb));
ON_CALL(*interruptEvent, getRcx).WillByDefault(Return(testParams64[0].expectedValue));
ON_CALL(*interruptEvent, getRdx).WillByDefault(Return(testParams64[1].expectedValue));
Expand All @@ -139,7 +162,7 @@ namespace ApiTracing

void SetupNestedStructPointerReads()
{
ON_CALL(*introspectionAPI, read64VA(param2Value, testDtb)).WillByDefault(Return(ObjectAttributesOneValue));
ON_CALL(*introspectionAPI, read64VA(param2Value, testDtb)).WillByDefault(Return(ObjectAttributesTwoValue));

ON_CALL(*introspectionAPI, read64VA(ObjectAttributesOneValue, testDtb))
.WillByDefault(Return(ObjectAttributesTwoValue));
Expand Down Expand Up @@ -230,7 +253,8 @@ namespace ApiTracing

TEST_F(ExtractorFixture, getShallowExtractedParams_64Bit0ParametersFunction_CorrectParametersExtracted)
{
auto extractor = std::make_shared<Extractor>(introspectionAPI, ConstantDefinitions::x64AddressWidth);
auto extractor =
std::make_shared<Extractor>(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x64AddressWidth);
auto expectedExtractedParameters = SetupParametersAndStack({}, ConstantDefinitions::x64AddressWidth);

auto extractedParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation);
Expand All @@ -240,7 +264,8 @@ namespace ApiTracing

TEST_F(ExtractorFixture, getShallowExtractedParams_32Bit0ParametersFunction_CorrectParametersExtracted)
{
auto extractor = std::make_shared<Extractor>(introspectionAPI, ConstantDefinitions::x86AddressWidth);
auto extractor =
std::make_shared<Extractor>(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x86AddressWidth);
auto expectedExtractedParameters = SetupParametersAndStack({}, ConstantDefinitions::x86AddressWidth);

auto extractedParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation);
Expand All @@ -250,7 +275,8 @@ namespace ApiTracing

TEST_F(ExtractorFixture, getShallowExtractedParams_32Bit4ParametersFunction_CorrectParametersExtracted)
{
auto extractor = std::make_shared<Extractor>(introspectionAPI, ConstantDefinitions::x86AddressWidth);
auto extractor =
std::make_shared<Extractor>(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x86AddressWidth);
auto expectedExtractedParameters = SetupParametersAndStack(testParams32, ConstantDefinitions::x86AddressWidth);

auto extractedParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation);
Expand All @@ -260,7 +286,8 @@ namespace ApiTracing

TEST_F(ExtractorFixture, getShallowExtractedParams_64Bit6ParametersFunction_CorrectParametersExtracted)
{
auto extractor = std::make_shared<Extractor>(introspectionAPI, ConstantDefinitions::x64AddressWidth);
auto extractor =
std::make_shared<Extractor>(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x64AddressWidth);
auto expectedExtractedParameters = SetupParametersAndStack(testParams64, ConstantDefinitions::x64AddressWidth);

auto extractedParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation);
Expand All @@ -270,7 +297,8 @@ namespace ApiTracing

TEST_F(ExtractorFixture, extractParameters_64Bit6ParametersFunction_CorrectParameters)
{
auto extractor = std::make_shared<Extractor>(introspectionAPI, ConstantDefinitions::x64AddressWidth);
auto extractor =
std::make_shared<Extractor>(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x64AddressWidth);
auto expectedExtractedParameters = SetupExpectedNestedParameters();
auto relevantParameters = std::vector<TestParameterInformation>(testNestedStruct);
SetupParameterInformation(relevantParameters);
Expand All @@ -282,4 +310,17 @@ namespace ApiTracing
ASSERT_EQ(actualParameters.size(), expectedExtractedParameters.size());
EXPECT_THAT(expectedExtractedParameters, ContainerEq(actualParameters));
}

TEST_F(ExtractorFixture, extractParameters_UnknownParameterType_NestedExceptionInvalidArgument)
{
auto extractor =
std::make_shared<Extractor>(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x64AddressWidth);
SetupParameterInformation(std::vector<TestParameterInformation>(testParamUnknownType));

auto shallowParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation);

EXPECT_THROW(auto extraction =
extractor->getDeepExtractParameters(shallowParameters, paramInformation, testDtb),
std::invalid_argument);
}
}

0 comments on commit 038f4b3

Please sign in to comment.