Initial commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+5180
File diff suppressed because one or more lines are too long
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const program = require("commander");
|
||||
const { constants, promises: fs } = require("fs");
|
||||
const path = require("path");
|
||||
const { inject } = require("./api.js");
|
||||
|
||||
const logger = {
|
||||
info: (message) => console.log("\x1b[36m%s\x1b[0m", message),
|
||||
success: (message) => console.log("\x1b[32m%s\x1b[0m", message),
|
||||
error: (message) => console.log("\x1b[31mError: %s\x1b[0m", message),
|
||||
};
|
||||
|
||||
async function main(filename, resourceName, resource, options) {
|
||||
if (options.outputApiHeader) {
|
||||
// Handles --output-api-header.
|
||||
console.log(
|
||||
await fs.readFile(path.join(__dirname, "postject-api.h"), "utf-8")
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
let resourceData;
|
||||
|
||||
try {
|
||||
await fs.access(resource, constants.R_OK);
|
||||
resourceData = await fs.readFile(resource);
|
||||
} catch {
|
||||
logger.error("Can't read resource file");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(
|
||||
"Start injection of " + resourceName + " in " + filename + "..."
|
||||
);
|
||||
await inject(filename, resourceName, resourceData, {
|
||||
machoSegmentName: options.machoSegmentName,
|
||||
overwrite: options.overwrite,
|
||||
sentinelFuse: options.sentinelFuse,
|
||||
});
|
||||
logger.success("💉 Injection done!");
|
||||
} catch (err) {
|
||||
logger.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
program
|
||||
.name("postject")
|
||||
.description(
|
||||
"Inject arbitrary read-only resources into an executable for use at runtime"
|
||||
)
|
||||
.argument("<filename>", "The executable to inject into")
|
||||
.argument(
|
||||
"<resource_name>",
|
||||
"The resource name to use (section name on Mach-O and ELF, resource name for PE)"
|
||||
)
|
||||
.argument("<resource>", "The resource to inject")
|
||||
.option(
|
||||
"--macho-segment-name <segment_name>",
|
||||
"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)
|
||||
.parse(process.argv);
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
#ifndef POSTJECT_API_H_
|
||||
#define POSTJECT_API_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/getsect.h>
|
||||
#elif defined(__linux__)
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
#include <sys/param.h>
|
||||
#elif defined(_WIN32)
|
||||
#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;
|
||||
const char* macho_section_name;
|
||||
const char* macho_segment_name;
|
||||
const char* pe_resource_name;
|
||||
};
|
||||
|
||||
inline void postject_options_init(struct postject_options* options) {
|
||||
options->elf_section_name = NULL;
|
||||
options->macho_framework_name = NULL;
|
||||
options->macho_section_name = NULL;
|
||||
options->macho_segment_name = NULL;
|
||||
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';
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
static int postject__dl_iterate_phdr_callback(struct dl_phdr_info* info,
|
||||
size_t size,
|
||||
void* data) {
|
||||
// Snag the dl_phdr_info struct for the main program, then stop iterating
|
||||
*((struct dl_phdr_info*)data) = *info;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const void* postject_find_resource(
|
||||
const char* name,
|
||||
size_t* size,
|
||||
const struct postject_options* options) {
|
||||
// Always zero out the size pointer to start
|
||||
if (size != NULL) {
|
||||
*size = 0;
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
char* section_name = NULL;
|
||||
const char* segment_name = "__POSTJECT";
|
||||
|
||||
if (options != NULL && options->macho_segment_name != NULL) {
|
||||
segment_name = options->macho_segment_name;
|
||||
}
|
||||
|
||||
if (options != NULL && options->macho_section_name != NULL) {
|
||||
name = options->macho_section_name;
|
||||
} else if (strncmp(name, "__", 2) != 0) {
|
||||
// Automatically prepend __ to match naming convention
|
||||
section_name = (char*)malloc(strlen(name) + 3);
|
||||
if (section_name == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
strcpy(section_name, "__");
|
||||
strcat(section_name, name);
|
||||
}
|
||||
|
||||
unsigned long section_size;
|
||||
char* ptr = NULL;
|
||||
if (options != NULL && options->macho_framework_name != NULL) {
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
ptr = getsectdatafromFramework(options->macho_framework_name, segment_name,
|
||||
section_name != NULL ? section_name : name,
|
||||
§ion_size);
|
||||
} else {
|
||||
ptr = getsectdata(segment_name, section_name != NULL ? section_name : name,
|
||||
§ion_size);
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
if (ptr != NULL) {
|
||||
// Add the "virtual memory address slide" amount to ensure a valid pointer
|
||||
// in cases where the virtual memory address have been adjusted by the OS.
|
||||
//
|
||||
// NOTE - `getsectdataFromFramework` already handles this adjustment for
|
||||
// us, which is why we only do it for `getsectdata`, see:
|
||||
// https://web.archive.org/web/20220613234007/https://opensource.apple.com/source/cctools/cctools-590/libmacho/getsecbyname.c.auto.html
|
||||
ptr += _dyld_get_image_vmaddr_slide(0);
|
||||
}
|
||||
}
|
||||
|
||||
free(section_name);
|
||||
|
||||
if (size != NULL) {
|
||||
*size = (size_t)section_size;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
#elif defined(__linux__)
|
||||
|
||||
if (options != NULL && options->elf_section_name != NULL) {
|
||||
name = options->elf_section_name;
|
||||
}
|
||||
|
||||
struct dl_phdr_info main_program_info;
|
||||
dl_iterate_phdr(postject__dl_iterate_phdr_callback, &main_program_info);
|
||||
|
||||
uintptr_t p = (uintptr_t)main_program_info.dlpi_phdr;
|
||||
size_t n = main_program_info.dlpi_phnum;
|
||||
uintptr_t base_addr = main_program_info.dlpi_addr;
|
||||
|
||||
// iterate program header
|
||||
for (; n > 0; n--, p += sizeof(ElfW(Phdr))) {
|
||||
ElfW(Phdr)* phdr = (ElfW(Phdr)*)p;
|
||||
|
||||
// skip everything but notes
|
||||
if (phdr->p_type != PT_NOTE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// note segment starts at base address + segment virtual address
|
||||
uintptr_t pos = (base_addr + phdr->p_vaddr);
|
||||
uintptr_t end = (pos + phdr->p_memsz);
|
||||
|
||||
// iterate through segment until we reach the end
|
||||
while (pos < end) {
|
||||
if (pos + sizeof(ElfW(Nhdr)) > end) {
|
||||
break; // invalid
|
||||
}
|
||||
|
||||
ElfW(Nhdr)* note = (ElfW(Nhdr)*)(uintptr_t)pos;
|
||||
if (note->n_namesz != 0 && note->n_descsz != 0 &&
|
||||
strncmp((char*)(pos + sizeof(ElfW(Nhdr))), (char*)name,
|
||||
sizeof(name)) == 0) {
|
||||
*size = note->n_descsz;
|
||||
// advance past note header and aligned name
|
||||
// to get to description data
|
||||
return (void*)((uintptr_t)note + sizeof(ElfW(Nhdr)) +
|
||||
roundup(note->n_namesz, 4));
|
||||
}
|
||||
|
||||
pos += (sizeof(ElfW(Nhdr)) + roundup(note->n_namesz, 4) +
|
||||
roundup(note->n_descsz, 4));
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
#elif defined(_WIN32)
|
||||
void* ptr = NULL;
|
||||
char* resource_name = NULL;
|
||||
|
||||
if (options != NULL && options->pe_resource_name != NULL) {
|
||||
name = options->pe_resource_name;
|
||||
} else {
|
||||
// Automatically uppercase the resource name or it won't be found
|
||||
resource_name = (char*)malloc(strlen(name) + 1);
|
||||
if (resource_name == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
strcpy_s(resource_name, strlen(name) + 1, name);
|
||||
CharUpperA(resource_name); // Uppercases inplace
|
||||
}
|
||||
|
||||
HRSRC resource_handle =
|
||||
FindResourceA(NULL, resource_name != NULL ? resource_name : name,
|
||||
MAKEINTRESOURCEA(10) /* RT_RCDATA */);
|
||||
|
||||
if (resource_handle) {
|
||||
HGLOBAL global_resource_handle = LoadResource(NULL, resource_handle);
|
||||
|
||||
if (global_resource_handle) {
|
||||
if (size != NULL) {
|
||||
*size = SizeofResource(NULL, resource_handle);
|
||||
}
|
||||
|
||||
ptr = LockResource(global_resource_handle);
|
||||
}
|
||||
}
|
||||
|
||||
free(resource_name);
|
||||
|
||||
return ptr;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // POSTJECT_API_H_
|
||||
Reference in New Issue
Block a user