diff --git a/README.markdown b/README.markdown index 7892f37..c1c9f7c 100644 --- a/README.markdown +++ b/README.markdown @@ -39,7 +39,7 @@ Options: ```js const { inject } = require('postject'); -await inject('a.out', 'lol', Buffer.from('Hello, world!')); +await inject('a.out', 'lol', '/path/to/resource/file'); ``` ## Building diff --git a/src/api.js b/src/api.js index f9c7a98..65f371e 100644 --- a/src/api.js +++ b/src/api.js @@ -1,14 +1,19 @@ const { constants, promises: fs } = require("fs"); const path = require("path"); +const util = require("util"); +const execFile = util.promisify(require("child_process").execFile); const loadPostjectModule = require("./postject.js"); -async function inject(filename, resourceName, resourceData, options) { +async function inject(filename, resourceName, resource, options) { const machoSegmentName = options?.machoSegmentName || "__POSTJECT"; const overwrite = options?.overwrite || false; - if (!Buffer.isBuffer(resourceData)) { - throw new TypeError("resourceData must be a buffer"); + let resourceData; + try { + resourceData = await fs.readFile(resource); + } catch { + throw new Error("Can't access resource file"); } try { @@ -39,30 +44,12 @@ async function inject(filename, resourceName, resourceData, options) { switch (executableFormat) { case postject.ExecutableFormat.kMachO: - { - let sectionName = resourceName; - - // Mach-O section names are conventionally of the style __foo - if (!sectionName.startsWith("__")) { - sectionName = `__${sectionName}`; - } - - ({ result, data } = postject.injectIntoMachO( - executable, - machoSegmentName, - sectionName, - resourceData, - overwrite - )); - - if (result === postject.InjectResult.kAlreadyExists) { - throw new Error( - `Segment and section with that name already exists: ${machoSegmentName}/${sectionName}\n` + - "Use --overwrite to overwrite the existing content" - ); - } - } - break; + await execFile("/usr/local/opt/llvm/bin/llvm-objcopy", [ + "--add-section", + `${machoSegmentName},__${resourceName}=${resource}`, + filename, + ]); + return; case postject.ExecutableFormat.kELF: { diff --git a/src/cli.js b/src/cli.js index b68f17c..8cd5939 100755 --- a/src/cli.js +++ b/src/cli.js @@ -14,18 +14,15 @@ async function main(filename, resourceName, resource, options) { process.exit(); } - let resourceData; - try { await fs.access(resource, constants.R_OK); - resourceData = await fs.readFile(resource); } catch { - console.log("Can't read resource file"); + console.log("Can't access resource file"); process.exit(1); } try { - await inject(filename, resourceName, resourceData, { + await inject(filename, resourceName, resource, { machoSegmentName: options.machoSegmentName, overwrite: options.overwrite, }); diff --git a/src/postject.cpp b/src/postject.cpp index cf4ea4a..1e124b3 100644 --- a/src/postject.cpp +++ b/src/postject.cpp @@ -84,71 +84,6 @@ emscripten::val inject_into_elf(const emscripten::val& executable, return object; } -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()); - - std::unique_ptr fat_binary = - LIEF::MachO::Parser::parse(vec_from_val(executable)); - - if (!fat_binary) { - object.set("result", emscripten::val(InjectResult::kError)); - return object; - } - - // Inject into all Mach-O binaries if there's more than one in a fat binary - for (LIEF::MachO::Binary& binary : *fat_binary) { - LIEF::MachO::Section* existing_section = - binary.get_section(segment_name, section_name); - - if (existing_section) { - if (!overwrite) { - object.set("result", emscripten::val(InjectResult::kAlreadyExists)); - return object; - } - - 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)); - - if (!segment) { - // Create the segment and mark it read-only - LIEF::MachO::SegmentCommand new_segment(segment_name); - new_segment.max_protection( - static_cast(LIEF::MachO::VM_PROTECTIONS::VM_PROT_READ)); - new_segment.init_protection( - static_cast(LIEF::MachO::VM_PROTECTIONS::VM_PROT_READ)); - new_segment.add_section(section); - binary.add(new_segment); - } else { - binary.add_section(*segment, section); - } - - // It will need to be signed again anyway, so remove the signature - if (binary.has_code_signature()) { - binary.remove_signature(); - } - } - - // 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; -} - emscripten::val inject_into_pe(const emscripten::val& executable, const std::string& resource_name, const emscripten::val& data, @@ -286,6 +221,5 @@ EMSCRIPTEN_BINDINGS(postject) { .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); } diff --git a/test/cli.mjs b/test/cli.mjs index 302bde2..f7d1144 100644 --- a/test/cli.mjs +++ b/test/cli.mjs @@ -72,6 +72,23 @@ describe("postject CLI", () => { expect(stdout).to.have.string("Hello world"); } + // Code signing using a self-signed certificate. + { + if (process.platform === "darwin") { + let codesignFound = false; + try { + execSync("command -v codesign"); + codesignFound = true; + } catch (err) { + console.log(err.message); + } + if (codesignFound) { + execSync(`codesign --sign - ${filename}`); + } + } + // TODO(RaisinTen): Test code signing on Windows. + } + { const { status, stdout, stderr } = spawnSync( "node", @@ -95,7 +112,6 @@ describe("postject CLI", () => { console.log(err.message); } if (codesignFound) { - execSync(`codesign --sign - ${filename}`); execSync(`codesign --verify ${filename}`); } } @@ -151,9 +167,25 @@ describe("postject API", () => { expect(stdout).to.have.string("Hello world"); } + // Code signing using a self-signed certificate. { - const resourceData = await fs.readFile(resourceFilename); - await inject(filename, "foobar", resourceData); + if (process.platform === "darwin") { + let codesignFound = false; + try { + execSync("command -v codesign"); + codesignFound = true; + } catch (err) { + console.log(err.message); + } + if (codesignFound) { + execSync(`codesign --sign - ${filename}`); + } + } + // TODO(RaisinTen): Test code signing on Windows. + } + + { + await inject(filename, "foobar", resourceFilename); } // Verifying code signing using a self-signed certificate. @@ -167,7 +199,6 @@ describe("postject API", () => { console.log(err.message); } if (codesignFound) { - execSync(`codesign --sign - ${filename}`); execSync(`codesign --verify ${filename}`); } }