From 2c3e9d15330676c5f8ad3bcf91dfb5404587dc4e Mon Sep 17 00:00:00 2001 From: patr0nus Date: Wed, 10 Jan 2024 00:40:38 +0800 Subject: [PATCH] Split postject into libpostject and postject_wam --- .gitignore | 1 + CMakeLists.txt | 17 +++-- src/api.js | 18 ++--- src/postject.cpp | 155 +++++++++++++----------------------------- src/postject.hpp | 35 ++++++++++ src/postject_wasm.cpp | 89 ++++++++++++++++++++++++ 6 files changed, 194 insertions(+), 121 deletions(-) create mode 100644 src/postject.hpp create mode 100644 src/postject_wasm.cpp diff --git a/.gitignore b/.gitignore index 57fa773..939ac85 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ dist/ # JetBrains IDE .idea/ +/cmake-* # Unit test reports TEST*.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index f5a47ca..a0a06b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,11 +21,20 @@ endif() add_subdirectory(vendor/lief) -add_executable(postject src/postject.cpp) -set_target_properties(postject PROPERTIES LINK_FLAGS "-sSINGLE_FILE -sMODULARIZE=1 -sALLOW_MEMORY_GROWTH -sINITIAL_MEMORY=268435456 -sMAXIMUM_MEMORY=4294967296 --bind") +add_library(postject + src/postject.cpp + src/postject.hpp +) +target_link_libraries(postject PUBLIC LIEF::LIEF) + +add_executable(postject_wasm + src/postject_wasm.cpp +) + +set_target_properties(postject_wasm PROPERTIES LINK_FLAGS "-sSINGLE_FILE -sMODULARIZE=1 -sALLOW_MEMORY_GROWTH -sINITIAL_MEMORY=268435456 -sMAXIMUM_MEMORY=4294967296 --bind") if(MSVC) - set_property(TARGET postject PROPERTY LINK_FLAGS /NODEFAULTLIB:MSVCRT) + set_property(TARGET postject_wasm PROPERTY LINK_FLAGS /NODEFAULTLIB:MSVCRT) endif() -target_link_libraries(postject PUBLIC LIEF::LIEF) +target_link_libraries(postject_wasm postject) diff --git a/src/api.js b/src/api.js index ed833cf..3ace279 100644 --- a/src/api.js +++ b/src/api.js @@ -1,7 +1,7 @@ const { constants, promises: fs } = require("fs"); const path = require("path"); -const loadPostjectModule = require("./postject.js"); +const loadPostjectModule = require("./postject_wasm.js"); async function inject(filename, resourceName, resourceData, options) { const machoSegmentName = options?.machoSegmentName || "__POSTJECT"; @@ -38,7 +38,7 @@ async function inject(filename, resourceName, resourceData, options) { } let data; - let result; + let type; switch (executableFormat) { case postject.ExecutableFormat.kMachO: @@ -50,7 +50,7 @@ async function inject(filename, resourceName, resourceData, options) { sectionName = `__${sectionName}`; } - ({ result, data } = postject.injectIntoMachO( + ({ type, data } = postject.injectIntoMachO( executable, machoSegmentName, sectionName, @@ -58,7 +58,7 @@ async function inject(filename, resourceName, resourceData, options) { overwrite )); - if (result === postject.InjectResult.kAlreadyExists) { + if (type === postject.InjectResultType.kAlreadyExists) { throw new Error( `Segment and section with that name already exists: ${machoSegmentName}/${sectionName}\n` + "Use --overwrite to overwrite the existing content" @@ -73,14 +73,14 @@ async function inject(filename, resourceName, resourceData, options) { // technically reserved for the system, so don't transform let sectionName = resourceName; - ({ result, data } = postject.injectIntoELF( + ({ type, data } = postject.injectIntoELF( executable, sectionName, resourceData, overwrite )); - if (result === postject.InjectResult.kAlreadyExists) { + if (type === postject.InjectResultType.kAlreadyExists) { throw new Error( `Section with that name already exists: ${sectionName}` + "Use --overwrite to overwrite the existing content" @@ -94,14 +94,14 @@ async function inject(filename, resourceName, resourceData, options) { // PE resource names appear to only work if uppercase resourceName = resourceName.toUpperCase(); - ({ result, data } = postject.injectIntoPE( + ({ type, data } = postject.injectIntoPE( executable, resourceName, resourceData, overwrite )); - if (result === postject.InjectResult.kAlreadyExists) { + if (type === postject.InjectResultType.kAlreadyExists) { throw new Error( `Resource with that name already exists: ${resourceName}\n` + "Use --overwrite to overwrite the existing content" @@ -111,7 +111,7 @@ async function inject(filename, resourceName, resourceData, options) { break; } - if (result !== postject.InjectResult.kSuccess) { + if (type !== postject.InjectResultType.kSuccess) { throw new Error("Error when injecting resource"); } diff --git a/src/postject.cpp b/src/postject.cpp index 730058c..f47c855 100644 --- a/src/postject.cpp +++ b/src/postject.cpp @@ -2,53 +2,34 @@ #include #include #include -#include - -#include -#include #include -enum class ExecutableFormat { kELF, kMachO, kPE, kUnknown }; - -enum class InjectResult { kAlreadyExists, kError, kSuccess }; - -std::vector vec_from_val(const emscripten::val& value) { - // We are using `convertJSArrayToNumberVector()` instead of `vecFromJSArray()` - // because it is faster. It is okay if we use it without additional type - // checking because this function is only called on Node.js Buffer instances - // which is expected to contain elements that are safe to pass to the JS - // function, `Number()`. - return emscripten::convertJSArrayToNumberVector(value); -} - -ExecutableFormat get_executable_format(const emscripten::val& executable) { - std::vector buffer = vec_from_val(executable); +#include "./postject.hpp" - if (LIEF::ELF::is_elf(buffer)) { +postject::ExecutableFormat postject::get_executable_format(const std::vector& executable) { + if (LIEF::ELF::is_elf(executable)) { return ExecutableFormat::kELF; - } else if (LIEF::MachO::is_macho(buffer)) { + } else if (LIEF::MachO::is_macho(executable)) { return ExecutableFormat::kMachO; - } else if (LIEF::PE::is_pe(buffer)) { + } else if (LIEF::PE::is_pe(executable)) { return ExecutableFormat::kPE; } return ExecutableFormat::kUnknown; } -emscripten::val inject_into_elf(const emscripten::val& executable, +postject::InjectResult postject::inject_into_elf(const std::vector& executable, const std::string& note_name, - const emscripten::val& data, - bool overwrite = false) { - emscripten::val object = emscripten::val::object(); - object.set("data", emscripten::val::undefined()); - + const std::vector& data, + bool overwrite) { + InjectResult result; std::unique_ptr binary = - LIEF::ELF::Parser::parse(vec_from_val(executable)); + LIEF::ELF::Parser::parse(executable); if (!binary) { - object.set("result", emscripten::val(InjectResult::kError)); - return object; + result.type = InjectResultType::kError; + return result; } LIEF::ELF::Note* existing_note = nullptr; @@ -61,8 +42,8 @@ emscripten::val inject_into_elf(const emscripten::val& executable, if (existing_note) { if (!overwrite) { - object.set("result", emscripten::val(InjectResult::kAlreadyExists)); - return object; + result.type = InjectResultType::kAlreadyExists; + return result; } else { binary->remove(*existing_note); } @@ -70,36 +51,26 @@ emscripten::val inject_into_elf(const emscripten::val& executable, LIEF::ELF::Note note; note.name(note_name); - note.description(vec_from_val(data)); + note.description(data); binary->add(note); - // Construct a new Uint8Array in JS - std::vector output = binary->raw(); - emscripten::val view{ - emscripten::typed_memory_view(output.size(), output.data())}; - auto output_data = emscripten::val::global("Uint8Array").new_(output.size()); - output_data.call("set", view); - - object.set("data", output_data); - object.set("result", emscripten::val(InjectResult::kSuccess)); - - return object; + result.type = InjectResultType::kSuccess; + result.output = binary->raw(); + return result; } -emscripten::val inject_into_macho(const emscripten::val& executable, +postject::InjectResult postject::inject_into_macho(const std::vector& executable, const std::string& segment_name, const std::string& section_name, - const emscripten::val& data, - bool overwrite = false) { - emscripten::val object = emscripten::val::object(); - object.set("data", emscripten::val::undefined()); - + const std::vector& data, + bool overwrite) { + InjectResult result; std::unique_ptr fat_binary = - LIEF::MachO::Parser::parse(vec_from_val(executable)); + LIEF::MachO::Parser::parse(executable); if (!fat_binary) { - object.set("result", emscripten::val(InjectResult::kError)); - return object; + result.type = InjectResultType::kError; + return result; } // Inject into all Mach-O binaries if there's more than one in a fat binary @@ -109,15 +80,16 @@ emscripten::val inject_into_macho(const emscripten::val& executable, if (existing_section) { if (!overwrite) { - object.set("result", emscripten::val(InjectResult::kAlreadyExists)); - return object; + + result.type = InjectResultType::kAlreadyExists; + return result; } binary.remove_section(segment_name, section_name, true); } LIEF::MachO::SegmentCommand* segment = binary.get_segment(segment_name); - LIEF::MachO::Section section(section_name, vec_from_val(data)); + LIEF::MachO::Section section(section_name, data); if (!segment) { // Create the segment and mark it read-only @@ -138,32 +110,23 @@ emscripten::val inject_into_macho(const emscripten::val& executable, } } - // Construct a new Uint8Array in JS - std::vector output = fat_binary->raw(); - emscripten::val view{ - emscripten::typed_memory_view(output.size(), output.data())}; - auto output_data = emscripten::val::global("Uint8Array").new_(output.size()); - output_data.call("set", view); - - object.set("data", output_data); - object.set("result", emscripten::val(InjectResult::kSuccess)); - - return object; + result.type = InjectResultType::kSuccess; + result.output = fat_binary->raw(); + return result; } -emscripten::val inject_into_pe(const emscripten::val& executable, +postject::InjectResult postject::inject_into_pe(const std::vector& executable, const std::string& resource_name, - const emscripten::val& data, - bool overwrite = false) { - emscripten::val object = emscripten::val::object(); - object.set("data", emscripten::val::undefined()); + const std::vector& data, + bool overwrite) { + InjectResult result; std::unique_ptr binary = - LIEF::PE::Parser::parse(vec_from_val(executable)); + LIEF::PE::Parser::parse(executable); if (!binary) { - object.set("result", emscripten::val(InjectResult::kError)); - return object; + result.type = InjectResultType::kError; + return result; } // TODO - lief.PE.ResourcesManager doesn't support RCDATA it seems, add @@ -171,8 +134,8 @@ emscripten::val inject_into_pe(const emscripten::val& executable, if (!binary->has_resources()) { // TODO - Handle this edge case by creating the resource tree - object.set("result", emscripten::val(InjectResult::kError)); - return object; + result.type = InjectResultType::kError; + return result; } LIEF::PE::ResourceNode* resources = binary->resources(); @@ -221,15 +184,15 @@ emscripten::val inject_into_pe(const emscripten::val& executable, // Third level => Lang (ResourceData node) if (id_node->childs() != std::end(id_node->childs())) { if (!overwrite) { - object.set("result", emscripten::val(InjectResult::kAlreadyExists)); - return object; + result.type = InjectResultType::kAlreadyExists; + return result; } id_node->delete_child(*id_node->childs()); } LIEF::PE::ResourceData lang_node; - lang_node.content(vec_from_val(data)); + lang_node.content(data); id_node->add_child(lang_node); binary->remove_section(".rsrc", true); @@ -263,31 +226,7 @@ emscripten::val inject_into_pe(const emscripten::val& executable, builder2.build_tls(false); builder2.build(); - // Construct a new Uint8Array in JS - const std::vector& output = builder2.get_build(); - emscripten::val view{ - emscripten::typed_memory_view(output.size(), output.data())}; - auto output_data = emscripten::val::global("Uint8Array").new_(output.size()); - output_data.call("set", view); - - object.set("data", output_data); - object.set("result", emscripten::val(InjectResult::kSuccess)); - - return object; -} - -EMSCRIPTEN_BINDINGS(postject) { - emscripten::enum_("ExecutableFormat") - .value("kELF", ExecutableFormat::kELF) - .value("kMachO", ExecutableFormat::kMachO) - .value("kPE", ExecutableFormat::kPE) - .value("kUnknown", ExecutableFormat::kUnknown); - emscripten::enum_("InjectResult") - .value("kAlreadyExists", InjectResult::kAlreadyExists) - .value("kError", InjectResult::kError) - .value("kSuccess", InjectResult::kSuccess); - emscripten::function("getExecutableFormat", &get_executable_format); - emscripten::function("injectIntoELF", &inject_into_elf); - emscripten::function("injectIntoMachO", &inject_into_macho); - emscripten::function("injectIntoPE", &inject_into_pe); + result.type = InjectResultType::kSuccess; + result.output = builder2.get_build(); + return result; } diff --git a/src/postject.hpp b/src/postject.hpp new file mode 100644 index 0000000..8b7c7db --- /dev/null +++ b/src/postject.hpp @@ -0,0 +1,35 @@ +#ifndef POSTJECT_POSTJECT_HPP +#define POSTJECT_POSTJECT_HPP + +#include + +namespace postject { + +enum class ExecutableFormat { kELF, kMachO, kPE, kUnknown }; + +enum class InjectResultType { kAlreadyExists, kError, kSuccess }; +struct InjectResult { + InjectResultType type; + std::vector output; +}; + +ExecutableFormat get_executable_format(const std::vector &buffer); + +InjectResult inject_into_elf(const std::vector &executable, + const std::string ¬e_name, + const std::vector &data, + bool overwrite = false); + +InjectResult inject_into_macho(const std::vector& executable, + const std::string& segment_name, + const std::string& section_name, + const std::vector& data, + bool overwrite = false); + +InjectResult inject_into_pe(const std::vector& executable, + const std::string& resource_name, + const std::vector& data, + bool overwrite = false); +} + +#endif // POSTJECT_POSTJECT_HPP diff --git a/src/postject_wasm.cpp b/src/postject_wasm.cpp new file mode 100644 index 0000000..f39cff4 --- /dev/null +++ b/src/postject_wasm.cpp @@ -0,0 +1,89 @@ +#include +#include + +#include "./postject.hpp" + + +std::vector vec_from_val(const emscripten::val& value) { + // We are using `convertJSArrayToNumberVector()` instead of `vecFromJSArray()` + // because it is faster. It is okay if we use it without additional type + // checking because this function is only called on Node.js Buffer instances + // which is expected to contain elements that are safe to pass to the JS + // function, `Number()`. + return emscripten::convertJSArrayToNumberVector(value); +} + +postject::ExecutableFormat get_executable_format(const emscripten::val& executable) { + return postject::get_executable_format(vec_from_val(executable)); +} + +emscripten::val inject_result_to_val(postject::InjectResult injectResult) { + emscripten::val object = emscripten::val::object(); + object.set("type", emscripten::val(injectResult.type)); + if (injectResult.type == postject::InjectResultType::kSuccess) { + std::vector output = std::move(injectResult.output); + emscripten::val view{ + emscripten::typed_memory_view(output.size(), output.data())}; + auto output_data = emscripten::val::global("Uint8Array").new_(output.size()); + output_data.call("set", view); + object.set("data", emscripten::val(output_data)); + } else { + object.set("data", emscripten::val::undefined()); + } + return object; + +} + +emscripten::val inject_into_elf(const emscripten::val& executable, + const std::string& note_name, + const emscripten::val& data, + bool overwrite) { + return inject_result_to_val(postject::inject_into_elf( + vec_from_val(executable), + note_name, + vec_from_val(data), + overwrite + )); +} + +emscripten::val inject_into_macho(const emscripten::val& executable, + const std::string& segment_name, + const std::string& section_name, + const emscripten::val& data, + bool overwrite) { + return inject_result_to_val(postject::inject_into_macho( + vec_from_val(executable), + segment_name, + section_name, + vec_from_val(data), + overwrite + )); +} + +emscripten::val inject_into_pe(const emscripten::val& executable, + const std::string& resource_name, + const emscripten::val& data, + bool overwrite) { + return inject_result_to_val(postject::inject_into_pe( + vec_from_val(executable), + resource_name, + vec_from_val(data), + overwrite + )); +} + +EMSCRIPTEN_BINDINGS(postject) { + emscripten::enum_("ExecutableFormat") + .value("kELF", postject::ExecutableFormat::kELF) + .value("kMachO", postject::ExecutableFormat::kMachO) + .value("kPE", postject::ExecutableFormat::kPE) + .value("kUnknown", postject::ExecutableFormat::kUnknown); + emscripten::enum_("InjectResultType") + .value("kAlreadyExists", postject::InjectResultType::kAlreadyExists) + .value("kError", postject::InjectResultType::kError) + .value("kSuccess", postject::InjectResultType::kSuccess); + emscripten::function("getExecutableFormat", &get_executable_format); + emscripten::function("injectIntoELF", &inject_into_elf); + emscripten::function("injectIntoMachO", &inject_into_macho); + emscripten::function("injectIntoPE", &inject_into_pe); +}