From f080d1cd1c11ddf2476fa9d9f113dd45ee2a5e45 Mon Sep 17 00:00:00 2001 From: Rusty Wagner Date: Mon, 20 Oct 2025 13:19:09 -0400 Subject: Add C++ encoded strings example --- examples/CMakeLists.txt | 1 + examples/encoded_strings/CMakeLists.txt | 30 +++++ examples/encoded_strings/src/encoded_strings.cpp | 149 +++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 examples/encoded_strings/CMakeLists.txt create mode 100644 examples/encoded_strings/src/encoded_strings.cpp (limited to 'examples') diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 57355026..3e1bedce 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,7 @@ # Personal+ edition (free edition doesn't get plugins due to no API access) add_subdirectory(background_task) add_subdirectory(breakpoint) +add_subdirectory(encoded_strings) add_subdirectory(x86_extension) add_subdirectory(workflows/inliner) add_subdirectory(workflows/tailcall) diff --git a/examples/encoded_strings/CMakeLists.txt b/examples/encoded_strings/CMakeLists.txt new file mode 100644 index 00000000..11e71c45 --- /dev/null +++ b/examples/encoded_strings/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.15 FATAL_ERROR) + +project(encoded_strings CXX C) + +add_library(${PROJECT_NAME} SHARED + src/encoded_strings.cpp) + +if(NOT BN_API_BUILD_EXAMPLES AND NOT BN_INTERNAL_BUILD) + # Out-of-tree build + find_path( + BN_API_PATH + NAMES binaryninjaapi.h + HINTS ../.. binaryninjaapi $ENV{BN_API_PATH} + REQUIRED + ) + add_subdirectory(${BN_API_PATH} api) +endif() + +target_link_libraries(${PROJECT_NAME} + binaryninjaapi) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 20 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/bin) + +bn_install_plugin(${PROJECT_NAME}) diff --git a/examples/encoded_strings/src/encoded_strings.cpp b/examples/encoded_strings/src/encoded_strings.cpp new file mode 100644 index 00000000..62c89bef --- /dev/null +++ b/examples/encoded_strings/src/encoded_strings.cpp @@ -0,0 +1,149 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include +#include +#include +#include +#include "binaryninjaapi.h" +#include "highlevelilinstruction.h" + +using namespace BinaryNinja; +using namespace std; + + +static Ref g_encodedStringType; + + +class EncodedStringRecognizer : public StringRecognizer +{ + typedef function Decoder; + map m_decoders; + +public: + EncodedStringRecognizer() : StringRecognizer("encoded_strings") + { + // Initialize decoders + m_decoders["xor_encoded"] = [](uint8_t encoded, uint8_t key) -> uint8_t { + return encoded ^ key; + }; + m_decoders["sub_encoded"] = [](uint8_t encoded, uint8_t key) -> uint8_t { + return encoded - key; + }; + m_decoders["add_encoded"] = [](uint8_t encoded, uint8_t key) -> uint8_t { + return encoded + key; + }; + } + + bool IsValidForType(HighLevelILFunction*, Type* type) override + { + if (!type || type->GetClass() != PointerTypeClass) + return false; + + auto target = type->GetChildType(); + if (!target.GetValue()) + return false; + + // Check if any decoder attribute exists + for (const auto& decoder : m_decoders) + { + if (target->GetAttribute(decoder.first).has_value()) + return true; + } + + return false; + } + + optional RecognizeConstantPointer( + const HighLevelILInstruction& instr, Type* type, int64_t val) override + { + if (!type || type->GetClass() != PointerTypeClass) + return std::nullopt; + + auto target = type->GetChildType(); + if (!target) + return std::nullopt; + + // Find the decoder and values + vector values; + Decoder chosenDecoder; + + for (const auto& [name, decoder] : m_decoders) + { + if (auto attr = target->GetAttribute(name); attr.has_value()) + { + // Parse hex string + if (attr->length() % 2 != 0) + return std::nullopt; + + for (size_t i = 0; i < attr->length(); i += 2) + { + string byteStr = attr->substr(i, 2); + try + { + values.push_back((uint8_t)stoul(byteStr, nullptr, 16)); + } + catch (...) + { + return std::nullopt; + } + } + + chosenDecoder = decoder; + break; + } + } + + if (values.empty() || !chosenDecoder) + return std::nullopt; + + bool encodedNull = target->GetAttribute("encoded_null").has_value(); + + // Decode the string + vector resultBytes; + size_t i = 0; + Ref view = instr.function->GetFunction()->GetView(); + + while (true) + { + uint8_t byte; + if (view->Read(&byte, val + i, 1) != 1) + return std::nullopt; + + // Check for unencoded null terminator + if (!encodedNull && byte == 0) + break; + + // Decode the byte + byte = chosenDecoder(byte, values[i % values.size()]); + + // Check for encoded null terminator + if (byte == 0) + break; + + resultBytes.push_back(byte); + i++; + } + + // Create the derived string + DerivedStringLocation loc(DataBackedStringLocation, val, i); + return DerivedString(string((char*)resultBytes.data(), resultBytes.size()), loc, g_encodedStringType); + } +}; + + +extern "C" +{ + BN_DECLARE_CORE_ABI_VERSION + + BINARYNINJAPLUGIN void CorePluginDependencies() + { + } + + BINARYNINJAPLUGIN bool CorePluginInit() + { + g_encodedStringType = CustomStringType::Register("Encoded", "", "_enc"); + StringRecognizer::Register(new EncodedStringRecognizer()); + return true; + } +} -- cgit v1.3.1