Skip to content

Commit

Permalink
Split postject into libpostject and postject_wam
Browse files Browse the repository at this point in the history
  • Loading branch information
branchseer committed Jan 10, 2024
1 parent 3c4f208 commit 2c3e9d1
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 121 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dist/

# JetBrains IDE
.idea/
/cmake-*

# Unit test reports
TEST*.xml
Expand Down
17 changes: 13 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
18 changes: 9 additions & 9 deletions src/api.js
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -38,7 +38,7 @@ async function inject(filename, resourceName, resourceData, options) {
}

let data;
let result;
let type;

switch (executableFormat) {
case postject.ExecutableFormat.kMachO:
Expand All @@ -50,15 +50,15 @@ async function inject(filename, resourceName, resourceData, options) {
sectionName = `__${sectionName}`;
}

({ result, data } = postject.injectIntoMachO(
({ type, data } = postject.injectIntoMachO(
executable,
machoSegmentName,
sectionName,
resourceData,
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"
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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");
}

Expand Down
155 changes: 47 additions & 108 deletions src/postject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,34 @@
#include <codecvt>
#include <locale>
#include <memory>
#include <vector>

#include <emscripten/bind.h>
#include <emscripten/val.h>

#include <LIEF/LIEF.hpp>

enum class ExecutableFormat { kELF, kMachO, kPE, kUnknown };

enum class InjectResult { kAlreadyExists, kError, kSuccess };

std::vector<uint8_t> 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<uint8_t>(value);
}

ExecutableFormat get_executable_format(const emscripten::val& executable) {
std::vector<uint8_t> buffer = vec_from_val(executable);
#include "./postject.hpp"

if (LIEF::ELF::is_elf(buffer)) {
postject::ExecutableFormat postject::get_executable_format(const std::vector<uint8_t>& 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<uint8_t>& 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<uint8_t>& data,
bool overwrite) {
InjectResult result;
std::unique_ptr<LIEF::ELF::Binary> 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;
Expand All @@ -61,45 +42,35 @@ 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);
}
}

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<uint8_t> 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<void>("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<uint8_t>& 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<uint8_t>& data,
bool overwrite) {
InjectResult result;
std::unique_ptr<LIEF::MachO::FatBinary> 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
Expand All @@ -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
Expand All @@ -138,41 +110,32 @@ emscripten::val inject_into_macho(const emscripten::val& executable,
}
}

// Construct a new Uint8Array in JS
std::vector<uint8_t> 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<void>("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<uint8_t>& 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<uint8_t>& data,
bool overwrite) {
InjectResult result;

std::unique_ptr<LIEF::PE::Binary> 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
// support so this is simpler?

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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<uint8_t>& 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<void>("set", view);

object.set("data", output_data);
object.set("result", emscripten::val(InjectResult::kSuccess));

return object;
}

EMSCRIPTEN_BINDINGS(postject) {
emscripten::enum_<ExecutableFormat>("ExecutableFormat")
.value("kELF", ExecutableFormat::kELF)
.value("kMachO", ExecutableFormat::kMachO)
.value("kPE", ExecutableFormat::kPE)
.value("kUnknown", ExecutableFormat::kUnknown);
emscripten::enum_<InjectResult>("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;
}
Loading

0 comments on commit 2c3e9d1

Please sign in to comment.