diff --git a/plugins/apitracing/configuration/functiondefinitions/functionDefinitions.yaml b/plugins/apitracing/configuration/functiondefinitions/functionDefinitions.yaml index 775a302b..2e747788 100644 --- a/plugins/apitracing/configuration/functiondefinitions/functionDefinitions.yaml +++ b/plugins/apitracing/configuration/functiondefinitions/functionDefinitions.yaml @@ -3792,7 +3792,7 @@ Modules: NtSetContextThread: Parameters: ThreadHandle: HANDLE - Context: CONTEXT + Context: LPCONTEXT ReturnValue: NTSTATUS NtSuspendThread: Parameters: @@ -4724,8 +4724,9 @@ Structures: Type: PVOID # TODO Add real definition Offset: 0 LPCONTEXT: # TODO Add definition https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context - Type: PVOID # TODO Add real definition - Offset: 0 + CONTEXT: + Type: PVOID # TODO Add real definition + Offset: 0 LPPROCESS_INFORMATION: hProcess: Type: HANDLE @@ -4866,10 +4867,10 @@ Structures: UniqueThread: Type: HANDLE Offset: 8 - PCONTEXT: # TODO Add definition for CONTEXT https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context - CONTEXT: - Type: PVOID - Offset: 0 + PCONTEXT: # TODO Add definition for CONTEXT https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context + CONTEXT: + Type: PVOID + Offset: 0 PFILE_BASIC_INFORMATION: CreationTime: Type: LARGE_INTEGER @@ -5002,13 +5003,11 @@ HighLevelParameterTypes: LPTHREAD_START_ROUTINE: PVOID LPWSTR: LPWSTR_32 LPBYTE: unsigned int - LPCONTEXT: CONTEXT LPCVOID: unsigned int LPDWORD: unsigned int LPVOID: unsigned int NTSTATUS: unsigned __int32 PANSI_STRING: PVOID - PHANDLE: HANDLE PIO_APC_ROUTINE: PVOID PIO_STATUS_BLOCK: PVOID PINITIAL_TEB: PVOID @@ -5062,14 +5061,11 @@ HighLevelParameterTypes: LPTHREAD_START_ROUTINE: PVOID # Pointer to the starting address of a thread. E.g. https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethreadex LPWSTR: LPWSTR_64 LPBYTE: unsigned __int64 - LPCONTEXT: CONTEXT LPCVOID: unsigned __int64 LPDWORD: unsigned __int64 LPVOID: unsigned __int64 NTSTATUS: unsigned __int32 PANSI_STRING: PVOID # TODO find definition - PCONTEXT: PVOID # TODO Add Context struct definition https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context - PHANDLE: HANDLE PIO_APC_ROUTINE: PVOID # TODO find definition PIO_STATUS_BLOCK: PVOID # TODO add struct https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_io_status_block PINITIAL_TEB: PVOID # TODO add struct definition from http://undocumented.ntinternals.net/ @@ -5108,6 +5104,7 @@ BackingParameterTypes: LPWSTR_64: 8 UNICODE_WSTR_32: 4 UNICODE_WSTR_64: 8 + unsigned __int32: 4 unsigned __int64: 8 unsigned long: 4 unsigned int: 4 diff --git a/plugins/apitracing/src/lib/TracedProcess.cpp b/plugins/apitracing/src/lib/TracedProcess.cpp index f5d18543..15d0373f 100755 --- a/plugins/apitracing/src/lib/TracedProcess.cpp +++ b/plugins/apitracing/src/lib/TracedProcess.cpp @@ -91,7 +91,7 @@ namespace ApiTracing : ConstantDefinitions::x64AddressWidth; auto definitions = functionDefinitions->getFunctionParameterDefinitions( moduleHookTarget.name, functionName, addressWidth); - auto extractor = std::make_shared(introspectionAPI, addressWidth); + auto extractor = std::make_shared(introspectionAPI, pluginInterface, addressWidth); auto functionHook = std::make_shared( moduleHookTarget.name, functionName, extractor, introspectionAPI, definitions, pluginInterface); functionHook->hookFunction(moduleBaseAddress, processInformation); diff --git a/plugins/apitracing/src/lib/os/Extractor.cpp b/plugins/apitracing/src/lib/os/Extractor.cpp index 9963bee5..9f260954 100644 --- a/plugins/apitracing/src/lib/os/Extractor.cpp +++ b/plugins/apitracing/src/lib/os/Extractor.cpp @@ -1,6 +1,8 @@ #include "Extractor.h" #include "../ConstantDefinitions.h" +#include "../Filenames.h" #include +#include using VmiCore::addr_t; using VmiCore::IInterruptEvent; @@ -8,8 +10,13 @@ using VmiCore::IIntrospectionAPI; namespace ApiTracing { - Extractor::Extractor(std::shared_ptr introspectionApi, uint8_t addressWidth) - : addressWidth(addressWidth), introspectionAPI(std::move(introspectionApi)) + Extractor::Extractor(std::shared_ptr introspectionApi, + VmiCore::Plugin::PluginInterface* pluginInterface, + uint8_t addressWidth) + : addressWidth(addressWidth), + introspectionAPI(std::move(introspectionApi)), + pluginInterface(pluginInterface), + logger(this->pluginInterface->newNamedLogger(APITRACING_LOGGER_NAME)) { } @@ -74,23 +81,40 @@ 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); + // skip optional parameters (e.g. AllocationSize in NtCreateFile) + if (auto pointerAddress = shallowParameters.at(parameterIndex); pointerAddress == 0) + { + extractedParameterInformation.emplace_back( + extractSingleParameter(pointerAddress, 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++; } @@ -103,8 +127,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; @@ -123,11 +156,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&) + { + result.data = ""; + } break; } case __PTR32: case __PTR64: + case UNSIGNED___INT32: case UNSIGNED___INT64: case UNSIGNED_LONG: case UNSIGNED_INT: @@ -145,7 +186,7 @@ namespace ApiTracing } default: { - break; + throw std::invalid_argument(fmt::format("Parameter not defined: {}", parameterInfo.basicType)); } } return result; @@ -202,21 +243,31 @@ namespace ApiTracing std::vector extractedBackingParameters; for (const auto& parameter : backingParameters) { - if (parameter.backingParameters.empty()) + ExtractedParameterInformation extraction{.name = parameter.name, .data = {}, .backingParameters = {}}; + try { - auto parameterValue = introspectionAPI->readVA(address + parameter.offset, cr3, parameter.size); - extractedBackingParameters.emplace_back(extractSingleParameter(parameterValue, cr3, parameter)); + if (parameter.backingParameters.empty()) + { + auto parameterValue = introspectionAPI->readVA(address + parameter.offset, cr3, parameter.size); + extraction = extractSingleParameter(parameterValue, cr3, parameter); + } + else + { + // Extract shallow parameters (based on size) from shallowParameters.at(parameterIndex). Then pass + // those to the nexţ iteration + addr_t structPointer = dereferencePointer(address + parameter.offset, cr3); + + extraction.backingParameters = + extractBackingParameters(parameter.backingParameters, structPointer, cr3); + } } - else + // Don't stop extraction on first failed parameter. Instead, insert default element try to extract the rest. + catch (const std::exception& e) { - // Extract shallow parameters (based on size) from shallowParameters.at(parameterIndex). Then pass those - // 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); - extractedBackingParameters.emplace_back(extraction); + logger->debug("Could not extract backing parameter", + {{"name", parameter.name}, {"exception", e.what()}}); } + extractedBackingParameters.emplace_back(extraction); } return extractedBackingParameters; } diff --git a/plugins/apitracing/src/lib/os/Extractor.h b/plugins/apitracing/src/lib/os/Extractor.h index d8e337cb..4832499a 100644 --- a/plugins/apitracing/src/lib/os/Extractor.h +++ b/plugins/apitracing/src/lib/os/Extractor.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,7 @@ namespace ApiTracing INT, LONG, __INT64, + UNSIGNED___INT32, UNSIGNED___INT64, UNSIGNED_LONG, UNSIGNED_INT, @@ -86,7 +88,9 @@ namespace ApiTracing class Extractor : public IExtractor { public: - Extractor(std::shared_ptr introspectionApi, uint8_t addressWidth); + Extractor(std::shared_ptr introspectionApi, + VmiCore::Plugin::PluginInterface* pluginInterface, + uint8_t addressWidth); [[nodiscard]] std::vector extractParameters(VmiCore::IInterruptEvent& event, @@ -105,6 +109,8 @@ namespace ApiTracing uint8_t addressWidth; std::variant extractedParameters; std::shared_ptr introspectionAPI; + VmiCore::Plugin::PluginInterface* pluginInterface; + std::unique_ptr logger; std::map> basicTypeStringToEnum{ {"LPSTR_32", BasicTypes::LPSTR_32}, {"LPSTR_64", BasicTypes::LPSTR_64}, @@ -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}, diff --git a/plugins/apitracing/test/Extractor_Unittest.cpp b/plugins/apitracing/test/Extractor_Unittest.cpp index b60d31f1..2e75244c 100644 --- a/plugins/apitracing/test/Extractor_Unittest.cpp +++ b/plugins/apitracing/test/Extractor_Unittest.cpp @@ -1,15 +1,22 @@ +#include "../../../vmicore/src/lib/vmi/VmiException.h" #include "../src/lib/os/Extractor.h" #include "ConstantDefinitions.h" #include "TestConstantDefinitions.h" #include #include +#include +#include #include #include +using testing::_; // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) using testing::ContainerEq; +using testing::NiceMock; using testing::Return; +using testing::Throw; using VmiCore::MockInterruptEvent; using VmiCore::MockIntrospectionAPI; +using VmiCore::Plugin::MockPluginInterface; namespace ApiTracing { @@ -61,7 +68,6 @@ namespace ApiTracing // clang-format off // @formatter:off const auto testParams32 = std::vector{ - {.parameterInformation{.name = "param1", .size = TestConstantDefinitions::fourBytes, .backingParameters{}}, .expectedValue = param1Value}, {.parameterInformation{.name = "param2", .size = TestConstantDefinitions::fourBytes, .backingParameters{}}, @@ -73,42 +79,57 @@ namespace ApiTracing {.parameterInformation{.name = "param5", .size = TestConstantDefinitions::fourBytes, .backingParameters{}}, .expectedValue = param5Value}, }; + + const auto testParamUnknownType = std::vector{ + {.parameterInformation{.basicType = "__unknown int96", .name = "UnknownType", .size = TestConstantDefinitions::fourBytes, .backingParameters{}}, + .expectedValue = param1Value}, + +}; // @formatter:on // clang-format on - const auto objectAttributesBackingParametersLevelTwo = - std::vector{{.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{ + // 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{ + // 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{ + // 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 @@ -116,10 +137,14 @@ namespace ApiTracing protected: std::shared_ptr introspectionAPI = std::make_shared(); std::shared_ptr interruptEvent = std::make_shared(); + std::unique_ptr pluginInterface = std::make_unique>(); std::shared_ptr> paramInformation; void SetUp() override { + ON_CALL(*pluginInterface, newNamedLogger(_)) + .WillByDefault([]() { return std::make_unique>(); }); + 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)); @@ -139,13 +164,15 @@ 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)); - ON_CALL(*introspectionAPI, readVA(ObjectAttributesTwoValue, testDtb, sizeof(uint32_t))) + ON_CALL(*introspectionAPI, + readVA(ObjectAttributesTwoValue + ObjectAttributesTwoContentOneOffset, testDtb, sizeof(uint32_t))) .WillByDefault(Return(ObjectAttributesTwoContentOneValue)); + ON_CALL(*introspectionAPI, readVA(ObjectAttributesTwoValue + ObjectAttributesTwoContentTwoOffset, testDtb, sizeof(uint64_t))) .WillByDefault(Return(ExtractedStringAddress)); @@ -230,7 +257,8 @@ namespace ApiTracing TEST_F(ExtractorFixture, getShallowExtractedParams_64Bit0ParametersFunction_CorrectParametersExtracted) { - auto extractor = std::make_shared(introspectionAPI, ConstantDefinitions::x64AddressWidth); + auto extractor = + std::make_shared(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x64AddressWidth); auto expectedExtractedParameters = SetupParametersAndStack({}, ConstantDefinitions::x64AddressWidth); auto extractedParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation); @@ -240,7 +268,8 @@ namespace ApiTracing TEST_F(ExtractorFixture, getShallowExtractedParams_32Bit0ParametersFunction_CorrectParametersExtracted) { - auto extractor = std::make_shared(introspectionAPI, ConstantDefinitions::x86AddressWidth); + auto extractor = + std::make_shared(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x86AddressWidth); auto expectedExtractedParameters = SetupParametersAndStack({}, ConstantDefinitions::x86AddressWidth); auto extractedParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation); @@ -250,7 +279,8 @@ namespace ApiTracing TEST_F(ExtractorFixture, getShallowExtractedParams_32Bit4ParametersFunction_CorrectParametersExtracted) { - auto extractor = std::make_shared(introspectionAPI, ConstantDefinitions::x86AddressWidth); + auto extractor = + std::make_shared(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x86AddressWidth); auto expectedExtractedParameters = SetupParametersAndStack(testParams32, ConstantDefinitions::x86AddressWidth); auto extractedParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation); @@ -260,7 +290,8 @@ namespace ApiTracing TEST_F(ExtractorFixture, getShallowExtractedParams_64Bit6ParametersFunction_CorrectParametersExtracted) { - auto extractor = std::make_shared(introspectionAPI, ConstantDefinitions::x64AddressWidth); + auto extractor = + std::make_shared(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x64AddressWidth); auto expectedExtractedParameters = SetupParametersAndStack(testParams64, ConstantDefinitions::x64AddressWidth); auto extractedParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation); @@ -270,16 +301,55 @@ namespace ApiTracing TEST_F(ExtractorFixture, extractParameters_64Bit6ParametersFunction_CorrectParameters) { - auto extractor = std::make_shared(introspectionAPI, ConstantDefinitions::x64AddressWidth); + auto extractor = + std::make_shared(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x64AddressWidth); + auto expectedExtractedParameters = SetupExpectedNestedParameters(); + auto relevantParameters = std::vector(testNestedStruct); + SetupParameterInformation(relevantParameters); + SetupNestedStructPointerReads(); + + auto shallowParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation); + auto actualParameters = extractor->getDeepExtractParameters(shallowParameters, paramInformation, testDtb); + + ASSERT_EQ(actualParameters.size(), expectedExtractedParameters.size()); + EXPECT_THAT(actualParameters, ContainerEq(expectedExtractedParameters)); + } + + TEST_F(ExtractorFixture, extractParameters_UnknownParameterType_NestedExceptionInvalidArgument) + { + auto extractor = + std::make_shared(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x64AddressWidth); + SetupParameterInformation(std::vector(testParamUnknownType)); + + auto shallowParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation); + + EXPECT_THROW(auto extraction = + extractor->getDeepExtractParameters(shallowParameters, paramInformation, testDtb), + std::invalid_argument); + } + + TEST_F(ExtractorFixture, extractParameters_OneFailedRead_RemainingParametersExtracted) + { + auto extractor = + std::make_shared(introspectionAPI, pluginInterface.get(), ConstantDefinitions::x64AddressWidth); auto expectedExtractedParameters = SetupExpectedNestedParameters(); auto relevantParameters = std::vector(testNestedStruct); SetupParameterInformation(relevantParameters); SetupNestedStructPointerReads(); + // Remove first ObjectAttributesTwo element and replace it with the default element + ON_CALL(*introspectionAPI, readVA(ObjectAttributesTwoValue, testDtb, sizeof(uint32_t))) + .WillByDefault(Throw(VmiCore::VmiException(("Unable to read bytes from VA")))); + auto* objectAttributesTwo = &expectedExtractedParameters[1].backingParameters[0].backingParameters; + auto removedElementName = objectAttributesTwo->begin()->name; + objectAttributesTwo->erase(objectAttributesTwo->begin()); + objectAttributesTwo->emplace( + objectAttributesTwo->begin(), + ExtractedParameterInformation{.name = removedElementName, .data = {}, .backingParameters = {}}); auto shallowParameters = extractor->getShallowExtractedParams(*interruptEvent, paramInformation); auto actualParameters = extractor->getDeepExtractParameters(shallowParameters, paramInformation, testDtb); ASSERT_EQ(actualParameters.size(), expectedExtractedParameters.size()); - EXPECT_THAT(expectedExtractedParameters, ContainerEq(actualParameters)); + EXPECT_THAT(actualParameters, ContainerEq(expectedExtractedParameters)); } }