2026-01-06 09:48:38 +08:00
|
|
|
#include "common/binary.h"
|
|
|
|
|
#include "common/io.h"
|
|
|
|
|
#include "common/stringUtils.h"
|
|
|
|
|
#include "common/mallocHelper.h"
|
2024-08-21 15:31:47 +08:00
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <mach-o/loader.h>
|
|
|
|
|
#include <mach-o/swap.h>
|
|
|
|
|
#include <mach-o/fat.h>
|
|
|
|
|
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" // swap_fat_arch
|
|
|
|
|
|
|
|
|
|
// Ref: https://github.com/AlexDenisov/segment_dumper/blob/master/main.c
|
|
|
|
|
|
2025-04-02 10:27:25 +08:00
|
|
|
/**
|
|
|
|
|
* Helper function to read data from a file at a specific offset
|
|
|
|
|
*/
|
2026-03-29 09:28:49 +08:00
|
|
|
static inline bool readData(FILE* objFile, void* buf, size_t size, off_t offset) {
|
2024-08-21 15:31:47 +08:00
|
|
|
fseek(objFile, offset, SEEK_SET);
|
|
|
|
|
return fread(buf, 1, size, objFile) == size;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-02 10:27:25 +08:00
|
|
|
/**
|
|
|
|
|
* Handles a Mach-O section by extracting strings from the __cstring section
|
|
|
|
|
*
|
|
|
|
|
* @param objFile File handle to the Mach-O object file
|
|
|
|
|
* @param name Section name to check
|
|
|
|
|
* @param offset Offset of the section in the file
|
|
|
|
|
* @param size Size of the section
|
|
|
|
|
* @param cb Callback function to process strings
|
|
|
|
|
* @param userdata User data for the callback
|
|
|
|
|
* @param minLength Minimum string length to extract
|
|
|
|
|
*
|
|
|
|
|
* @return true to continue processing, false to stop
|
|
|
|
|
*/
|
2026-03-29 09:28:49 +08:00
|
|
|
static bool handleMachSection(FILE* objFile, const char* name, off_t offset, size_t size, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
|
|
|
|
|
if (!ffStrEquals(name, "__cstring")) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
|
|
|
|
FF_AUTO_FREE char* data = (char*) malloc(size);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, data, size, offset)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
return true;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
for (size_t off = 0; off < size; ++off) {
|
2024-08-21 15:31:47 +08:00
|
|
|
const char* p = (const char*) data + off;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (*p == '\0') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
uint32_t len = (uint32_t) strlen(p);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (len < minLength) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (*p >= ' ' && *p <= '~') { // Ignore control characters
|
|
|
|
|
if (!cb(p, len, userdata)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
}
|
|
|
|
|
off += len;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-02 10:27:25 +08:00
|
|
|
/**
|
|
|
|
|
* Processes a Mach-O header (32-bit or 64-bit)
|
|
|
|
|
*
|
|
|
|
|
* This function parses the load commands in a Mach-O header, looking for
|
|
|
|
|
* LC_SEGMENT or LC_SEGMENT_64 commands that contain the __TEXT segment.
|
|
|
|
|
* It then processes the sections within that segment to extract strings.
|
|
|
|
|
*
|
|
|
|
|
* @param objFile File handle to the Mach-O object file
|
|
|
|
|
* @param offset Offset of the Mach header in the file
|
|
|
|
|
* @param is_64 Whether this is a 64-bit Mach-O header
|
|
|
|
|
* @param cb Callback function to process strings
|
|
|
|
|
* @param userdata User data for the callback
|
|
|
|
|
* @param minLength Minimum string length to extract
|
|
|
|
|
*
|
|
|
|
|
* @return NULL on success, error message on failure
|
|
|
|
|
*/
|
2026-03-29 09:28:49 +08:00
|
|
|
static const char* dumpMachHeader(FILE* objFile, off_t offset, bool is_64, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
|
2024-08-21 15:31:47 +08:00
|
|
|
uint32_t ncmds;
|
|
|
|
|
off_t loadCommandsOffset = offset;
|
|
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (is_64) {
|
2024-08-21 15:31:47 +08:00
|
|
|
struct mach_header_64 header;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, &header, sizeof(header), offset)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
return "read mach header failed";
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
|
|
|
|
ncmds = header.ncmds;
|
|
|
|
|
loadCommandsOffset += sizeof(header);
|
2026-03-29 09:28:49 +08:00
|
|
|
} else {
|
2024-08-21 15:31:47 +08:00
|
|
|
struct mach_header header;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, &header, sizeof(header), offset)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
return "read mach header failed";
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
|
|
|
|
ncmds = header.ncmds;
|
|
|
|
|
loadCommandsOffset += sizeof(header);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
off_t commandOffset = loadCommandsOffset;
|
|
|
|
|
struct load_command cmd = {};
|
2026-03-29 09:28:49 +08:00
|
|
|
for (uint32_t i = 0U; i < ncmds; i++, commandOffset += cmd.cmdsize) {
|
|
|
|
|
if (!readData(objFile, &cmd, sizeof(cmd), commandOffset)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
continue;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (cmd.cmd == LC_SEGMENT_64) {
|
2024-08-21 15:31:47 +08:00
|
|
|
struct segment_command_64 segment;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, &segment, sizeof(segment), commandOffset)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
continue;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!ffStrEquals(segment.segname, "__TEXT")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
for (uint32_t j = 0U; j < segment.nsects; j++) {
|
2024-08-21 15:31:47 +08:00
|
|
|
struct section_64 section;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, §ion, sizeof(section), (off_t) ((size_t) commandOffset + sizeof(segment) + j * sizeof(section)))) {
|
2024-08-21 15:31:47 +08:00
|
|
|
continue;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!handleMachSection(objFile, section.sectname, section.offset, section.size, cb, userdata, minLength)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
return NULL;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
} else if (cmd.cmd == LC_SEGMENT) {
|
2024-08-21 15:31:47 +08:00
|
|
|
struct segment_command segment;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, &segment, sizeof(segment), commandOffset)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
continue;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!ffStrEquals(segment.segname, "__TEXT")) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
for (uint32_t j = 0; j < segment.nsects; j++) {
|
2024-08-21 15:31:47 +08:00
|
|
|
struct section section;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, §ion, sizeof(section), (off_t) ((size_t) commandOffset + sizeof(segment) + j * sizeof(section)))) {
|
2024-08-21 15:31:47 +08:00
|
|
|
continue;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!handleMachSection(objFile, section.sectname, section.offset, section.size, cb, userdata, minLength)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
return NULL;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-02 10:27:25 +08:00
|
|
|
/**
|
|
|
|
|
* Processes a Fat binary header (Universal binary)
|
|
|
|
|
*
|
|
|
|
|
* This function handles the fat header of a universal binary, which can contain
|
|
|
|
|
* multiple Mach-O binaries for different architectures. It extracts and processes
|
|
|
|
|
* each embedded Mach-O file.
|
|
|
|
|
*
|
|
|
|
|
* @param objFile File handle to the universal binary
|
|
|
|
|
* @param cb Callback function to process strings
|
|
|
|
|
* @param userdata User data for the callback
|
|
|
|
|
* @param minLength Minimum string length to extract
|
|
|
|
|
*
|
|
|
|
|
* @return NULL on success, error message on failure
|
|
|
|
|
*/
|
2026-03-29 09:28:49 +08:00
|
|
|
static const char* dumpFatHeader(FILE* objFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
|
2024-08-21 15:31:47 +08:00
|
|
|
struct fat_header header;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, &header, sizeof(header), 0)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
return "read fat header failed";
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
|
|
|
|
bool needSwap = header.magic == FAT_CIGAM || header.magic == FAT_CIGAM_64;
|
|
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (needSwap) {
|
|
|
|
|
swap_fat_header(&header, NX_UnknownByteOrder);
|
|
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
for (uint32_t i = 0U; i < header.nfat_arch; i++) {
|
2024-08-21 15:31:47 +08:00
|
|
|
off_t machHeaderOffset = 0;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (header.magic == FAT_MAGIC) {
|
2024-08-21 15:31:47 +08:00
|
|
|
struct fat_arch arch;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, &arch, sizeof(arch), (off_t) (sizeof(header) + i * sizeof(arch)))) {
|
2024-08-21 15:31:47 +08:00
|
|
|
continue;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (needSwap) {
|
2024-08-21 15:31:47 +08:00
|
|
|
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
|
|
|
|
machHeaderOffset = (off_t) arch.offset;
|
|
|
|
|
} else {
|
2024-08-21 15:31:47 +08:00
|
|
|
struct fat_arch_64 arch;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, &arch, sizeof(arch), (off_t) (sizeof(header) + i * sizeof(arch)))) {
|
2024-08-21 15:31:47 +08:00
|
|
|
continue;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (needSwap) {
|
2024-08-21 15:31:47 +08:00
|
|
|
swap_fat_arch_64(&arch, 1, NX_UnknownByteOrder);
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
machHeaderOffset = (off_t) arch.offset;
|
2024-08-21 15:31:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t magic;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, &magic, sizeof(magic), machHeaderOffset)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
continue;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (magic == MH_MAGIC_64 || magic == MH_MAGIC) {
|
2024-08-27 16:19:05 +08:00
|
|
|
dumpMachHeader(objFile, machHeaderOffset, magic == MH_MAGIC_64, cb, userdata, minLength);
|
2024-08-21 15:31:47 +08:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return "Unsupported fat header";
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-02 10:27:25 +08:00
|
|
|
/**
|
|
|
|
|
* Extracts string literals from a Mach-O (Apple) binary file
|
|
|
|
|
*
|
|
|
|
|
* This function supports both single-architecture Mach-O files and
|
|
|
|
|
* universal binaries (fat binaries) containing multiple architectures.
|
|
|
|
|
* It locates the __cstring section in the __TEXT segment which contains
|
|
|
|
|
* the string literals used in the program.
|
|
|
|
|
*/
|
2026-03-29 09:28:49 +08:00
|
|
|
const char* ffBinaryExtractStrings(const char* machoFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
|
|
|
|
|
FF_AUTO_CLOSE_FILE FILE* objFile = fopen(machoFile, "rb");
|
|
|
|
|
if (objFile == NULL) {
|
2024-08-21 15:31:47 +08:00
|
|
|
return "File could not be opened";
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2025-04-02 10:27:25 +08:00
|
|
|
// Read the magic number to determine the type of binary
|
2024-08-21 15:31:47 +08:00
|
|
|
uint32_t magic;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!readData(objFile, &magic, sizeof(magic), 0)) {
|
2024-08-21 15:31:47 +08:00
|
|
|
return "read magic number failed";
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2025-04-02 10:27:25 +08:00
|
|
|
// Check for supported formats
|
2024-08-21 15:31:47 +08:00
|
|
|
// MH_CIGAM and MH_CIGAM_64 seem to be no longer used, as `swap_mach_header` is marked as deprecated.
|
|
|
|
|
// However FAT_CIGAM and FAT_CIGAM_64 are still used (/usr/bin/vim).
|
2026-03-29 09:28:49 +08:00
|
|
|
if (magic != MH_MAGIC && magic != MH_MAGIC_64 && magic != FAT_CIGAM && magic != FAT_CIGAM_64 && magic != FAT_MAGIC && magic != FAT_MAGIC_64) {
|
2024-08-21 15:31:47 +08:00
|
|
|
return "Unsupported format or big endian mach-o file";
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
|
2025-04-02 10:27:25 +08:00
|
|
|
// Process either a fat binary or a regular Mach-O binary
|
2026-03-29 09:28:49 +08:00
|
|
|
if (magic == FAT_MAGIC || magic == FAT_MAGIC_64 || magic == FAT_CIGAM || magic == FAT_CIGAM_64) {
|
2024-08-27 16:19:05 +08:00
|
|
|
return dumpFatHeader(objFile, cb, userdata, minLength);
|
2026-03-29 09:28:49 +08:00
|
|
|
} else {
|
2024-08-27 16:19:05 +08:00
|
|
|
return dumpMachHeader(objFile, 0, magic == MH_MAGIC_64, cb, userdata, minLength);
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-21 15:31:47 +08:00
|
|
|
}
|