Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: split postject into libpostject and postject_wasm #93

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
166 changes: 54 additions & 112 deletions src/postject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,36 @@
#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,
const std::string& note_name,
const emscripten::val& data,
bool overwrite = false) {
emscripten::val object = emscripten::val::object();
object.set("data", emscripten::val::undefined());

postject::InjectResult postject::inject_into_elf(
const std::vector<uint8_t>& executable,
const std::string& note_name,
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 +44,36 @@ 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,
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());

postject::InjectResult postject::inject_into_macho(
const std::vector<uint8_t>& executable,
const std::string& segment_name,
const std::string& section_name,
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 +83,15 @@ 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 +112,33 @@ 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,
const std::string& resource_name,
const emscripten::val& data,
bool overwrite = false) {
emscripten::val object = emscripten::val::object();
object.set("data", emscripten::val::undefined());
postject::InjectResult postject::inject_into_pe(
const std::vector<uint8_t>& executable,
const std::string& resource_name,
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 +187,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 +229,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