Skip to content

Commit

Permalink
feat: add a fuse to speed up the case where no resource is present (#59)
Browse files Browse the repository at this point in the history
Fixes: #58
Signed-off-by: Darshan Sen <[email protected]>
  • Loading branch information
RaisinTen authored Jan 10, 2023
1 parent 30989e7 commit f6b171c
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 9 deletions.
9 changes: 9 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ ability to inject the resources into pre-built executables, with the
goal that the end result should be as close as possible to what is
obtained by embedding them at build-time.
Note: Other runtime injection implementers should search the binary
compiled with `postject-api.h` for the
`POSTJECT_SENTINEL_fce680ab2cc467b6e072b8b5df1996b2:0` fuse and
flip the last character to `1` to indicate that a resource has been
injected. A different fuse can also be used by defining the
`POSTJECT_SENTINEL_FUSE` macro before including `postject-api.h` and
passing the same string to postject with
`--sentinel-fuse <sentinel_fuse>`.
### Windows
For PE executables, the resources are added into the `.rsrc` section,
Expand Down
11 changes: 11 additions & 0 deletions postject-api.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef POSTJECT_API_H_
#define POSTJECT_API_H_

#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
Expand All @@ -17,6 +18,11 @@
#include <windows.h>
#endif

#ifndef POSTJECT_SENTINEL_FUSE
#define POSTJECT_SENTINEL_FUSE \
"POSTJECT_SENTINEL_fce680ab2cc467b6e072b8b5df1996b2"
#endif

struct postject_options {
const char* elf_section_name;
const char* macho_framework_name;
Expand All @@ -33,6 +39,11 @@ inline void postject_options_init(struct postject_options* options) {
options->pe_resource_name = NULL;
}

static inline bool postject_has_resource() {
static const volatile char* sentinel = POSTJECT_SENTINEL_FUSE ":0";
return sentinel[sizeof(POSTJECT_SENTINEL_FUSE)] == '1';
}

static const void* postject_find_resource(
const char* name,
size_t* size,
Expand Down
43 changes: 42 additions & 1 deletion src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const loadPostjectModule = require("./postject.js");
async function inject(filename, resourceName, resourceData, options) {
const machoSegmentName = options?.machoSegmentName || "__POSTJECT";
const overwrite = options?.overwrite || false;
let sentinelFuse =
options?.sentinelFuse ||
"POSTJECT_SENTINEL_fce680ab2cc467b6e072b8b5df1996b2";

if (!Buffer.isBuffer(resourceData)) {
throw new TypeError("resourceData must be a buffer");
Expand Down Expand Up @@ -112,8 +115,46 @@ async function inject(filename, resourceName, resourceData, options) {
throw new Error("Error when injecting resource");
}

const buffer = Buffer.from(data.buffer);
const firstSentinel = buffer.indexOf(sentinelFuse);

if (firstSentinel === -1) {
throw new Error(
`Could not find the sentinel ${sentinelFuse} in the binary`
);
}

const lastSentinel = buffer.lastIndexOf(sentinelFuse);

if (firstSentinel !== lastSentinel) {
throw new Error(
`Multiple occurences of sentinel "${sentinelFuse}" found in the binary`
);
}

const colonIndex = firstSentinel + sentinelFuse.length;
if (buffer[colonIndex] !== ":".charCodeAt(0)) {
throw new Error(
`Value at index ${colonIndex} must be ':' but '${buffer[
colonIndex
].charCodeAt(0)}' was found`
);
}

const hasResourceIndex = firstSentinel + sentinelFuse.length + 1;
const hasResourceValue = buffer[hasResourceIndex];
if (hasResourceValue === "0".charCodeAt(0)) {
buffer[hasResourceIndex] = "1".charCodeAt(0);
} else if (hasResourceValue != "1".charCodeAt(0)) {
throw new Error(
`Value at index ${hasResourceIndex} must be '0' or '1' but '${hasResourceValue.charCodeAt(
0
)}' was found`
);
}

try {
await fs.writeFile(filename, data);
await fs.writeFile(filename, buffer);
} catch {
throw new Error("Couldn't write executable");
}
Expand Down
6 changes: 6 additions & 0 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ async function main(filename, resourceName, resource, options) {
await inject(filename, resourceName, resourceData, {
machoSegmentName: options.machoSegmentName,
overwrite: options.overwrite,
sentinelFuse: options.sentinelFuse,
});
} catch (err) {
console.log(err.message);
Expand All @@ -52,6 +53,11 @@ if (require.main === module) {
"Name for the Mach-O segment",
"__POSTJECT"
)
.option(
"--sentinel-fuse <sentinel_fuse>",
"Sentinel fuse for resource presence detection",
"POSTJECT_SENTINEL_fce680ab2cc467b6e072b8b5df1996b2"
)
.option("--output-api-header", "Output the API header to stdout")
.option("--overwrite", "Overwrite the resource if it already exists")
.action(main)
Expand Down
13 changes: 11 additions & 2 deletions test/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ describe("postject CLI", () => {
{
const { status, stdout, stderr } = spawnSync(
"node",
["./dist/cli.js", filename, "foobar", resourceFilename],
[
"./dist/cli.js",
filename,
"foobar",
resourceFilename,
"--sentinel-fuse",
"NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2",
],
{ encoding: "utf-8" }
);
// TODO(dsanders11) - Enable this once we squelch LIEF warnings
Expand Down Expand Up @@ -153,7 +160,9 @@ describe("postject API", () => {

{
const resourceData = await fs.readFile(resourceFilename);
await inject(filename, "foobar", resourceData);
await inject(filename, "foobar", resourceData, {
sentinelFuse: "NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2",
});
}

// Verifying code signing using a self-signed certificate.
Expand Down
23 changes: 20 additions & 3 deletions test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@
#include "../dist/postject-api.h"

int main() {
size_t size;
const void* ptr = postject_find_resource("foobar", &size, NULL);
size_t size = 0;

if (ptr && size > 0) {
if (postject_has_resource()) {
const void* ptr = postject_find_resource("foobar", &size, NULL);
if (ptr == NULL) {
fprintf(stderr, "ptr must not be NULL.\n");
exit(1);
}
if (size == 0) {
fprintf(stderr, "size must not be 0.\n");
exit(1);
}
char* str = (char*)malloc(size + 1);
memset(str, 0, size + 1);
#if defined(_WIN32)
Expand All @@ -17,6 +25,15 @@ int main() {
#endif
printf("%s\n", str);
} else {
const void* ptr = postject_find_resource("foobar", &size, NULL);
if (ptr != NULL) {
fprintf(stderr, "ptr must be NULL.\n");
exit(1);
}
if (size > 0) {
fprintf(stderr, "size must not be greater than 0.\n");
exit(1);
}
printf("Hello world\n");
}

Expand Down
24 changes: 21 additions & 3 deletions test/test.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
#include <iostream>
#include <string>

#define POSTJECT_SENTINEL_FUSE "NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2"
#include "../dist/postject-api.h"

int main() {
size_t size;
const void* ptr = postject_find_resource("foobar", &size, nullptr);
size_t size = 0;

if (ptr && size > 0) {
if (postject_has_resource()) {
const void* ptr = postject_find_resource("foobar", &size, nullptr);
if (ptr == NULL) {
std::cerr << "ptr must not be NULL." << std::endl;
exit(1);
}
if (size == 0) {
std::cerr << "size must not be 0." << std::endl;
exit(1);
}
std::cout << std::string(static_cast<const char*>(ptr), size) << std::endl;
} else {
const void* ptr = postject_find_resource("foobar", &size, nullptr);
if (ptr != nullptr) {
std::cerr << "ptr must be nullptr." << std::endl;
exit(1);
}
if (size > 0) {
std::cerr << "size must not be greater than 0." << std::endl;
exit(1);
}
std::cout << "Hello world" << std::endl;
}

Expand Down

0 comments on commit f6b171c

Please sign in to comment.