Switch to static zstd library and update build config

Replaces dynamic libzstd.1.dylib with static libzstd.a in CMakeLists.txt and updates architecture to support arm64. Adds zstd headers and static library, updates documentation and credits to reflect zstd usage, and removes unused tinyhook source files after moving headers and libraries.
This commit is contained in:
√(noham)²
2025-12-03 20:51:26 +01:00
parent 0bdda7bdf7
commit 83b12e297e
18 changed files with 3806 additions and 518 deletions

View File

@@ -1,51 +0,0 @@
#ifndef tinyhook_h
#define tinyhook_h
#include <objc/objc-runtime.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define CLASS_METHOD 0
#define INSTANCE_METHOD 1
/* inline hook */
int tiny_hook(void *function, void *destination, void **origin);
int tiny_insert(void *address, void *destination, bool link);
int tiny_insert_far(void *address, void *destination, bool link);
/* objective-c runtime */
int ocrt_hook(const char *cls, const char *sel, void *destination, void **origin);
int ocrt_swap(const char *cls1, const char *sel1, const char *cls2, const char *sel2);
void *ocrt_impl(const char *cls, const char *sel, bool type);
Method ocrt_method(const char *cls, const char *sel, bool type);
/* memory access */
int read_mem(void *destination, const void *source, size_t len);
int write_mem(void *destination, const void *source, size_t len);
/* solve symbol */
void *symtbl_solve(uint32_t image_index, const char *symbol_name);
void *symexp_solve(uint32_t image_index, const char *symbol_name);
/* find in memory */
// int find_code(uint32_t image_index, const unsigned char *code, size_t len, int count, void **out);
int find_data(void *start, void *end, const unsigned char *data, size_t len, int count, void **out);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,39 +0,0 @@
#include <mach/mach_init.h> // mach_task_self()
#include <mach/mach_vm.h> // mach_vm_*
#include <string.h> // memcpy()
#ifndef COMPACT
#include <mach/mach_error.h> // mach_error_string()
#include <printf.h> // fprintf()
#endif
#include "../include/tinyhook.h"
int read_mem(void *destination, const void *source, size_t len) {
int kr = 0;
vm_offset_t data;
mach_msg_type_number_t dataCnt;
kr |= mach_vm_read(mach_task_self(), (mach_vm_address_t)source, len, &data, &dataCnt);
memcpy((unsigned char *)destination, (unsigned char *)data, dataCnt);
kr |= mach_vm_deallocate(mach_task_self(), data, dataCnt);
#ifndef COMPACT
if (kr != 0) {
fprintf(stderr, "read_mem: %s\n", mach_error_string(kr));
}
#endif
return kr;
}
int write_mem(void *destination, const void *source, size_t len) {
int kr = 0;
kr |= mach_vm_protect(mach_task_self(), (mach_vm_address_t)destination, len, FALSE,
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
kr |= mach_vm_write(mach_task_self(), (mach_vm_address_t)destination, (vm_offset_t)source, len);
kr |= mach_vm_protect(mach_task_self(), (mach_vm_address_t)destination, len, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
#ifndef COMPACT
if (kr != 0) {
fprintf(stderr, "write_mem: %s\n", mach_error_string(kr));
}
#endif
return kr;
}

View File

@@ -1,66 +0,0 @@
#include <objc/runtime.h> // objc_*, ...
#ifndef COMPACT
#include <mach/mach_error.h> // mach_error_string()
#include <printf.h> // fprintf()
#endif
#include "../include/tinyhook.h"
Method ocrt_method(const char *cls, const char *sel, bool type) {
Method oc_method = NULL;
Class oc_class = objc_getClass(cls);
SEL oc_selector = sel_registerName(sel);
if (type == CLASS_METHOD) {
oc_method = class_getClassMethod(oc_class, oc_selector);
} else if (type == INSTANCE_METHOD) {
oc_method = class_getInstanceMethod(oc_class, oc_selector);
}
#ifndef COMPACT
else {
fprintf(stderr, "ocrt_method: invalid method type: %d\n", type);
}
#endif
return oc_method;
}
void *ocrt_impl(const char *cls, const char *sel, bool type) {
return method_getImplementation(ocrt_method(cls, sel, type));
}
static Method ensure_method(const char *cls, const char *sel);
int ocrt_swap(const char *cls1, const char *sel1, const char *cls2, const char *sel2) {
Method oc_method1 = ensure_method(cls1, sel1);
Method oc_method2 = ensure_method(cls2, sel2);
if (oc_method1 == NULL || oc_method2 == NULL) {
return 1;
}
method_exchangeImplementations(oc_method1, oc_method2);
return 0;
}
int ocrt_hook(const char *cls, const char *sel, void *destination, void **origin) {
Method oc_method = ensure_method(cls, sel);
if (oc_method == NULL) {
return 1;
}
void *origin_imp = method_setImplementation(oc_method, destination);
if (origin != NULL) {
*origin = origin_imp;
}
return 0;
}
static Method ensure_method(const char *cls, const char *sel) {
Method oc_method = ocrt_method(cls, sel, CLASS_METHOD);
if (oc_method == NULL) {
oc_method = ocrt_method(cls, sel, INSTANCE_METHOD);
}
#ifndef COMPACT
if (oc_method == NULL) {
fprintf(stderr, "ensure_method: method not found!\n");
}
#endif
return oc_method;
}

View File

@@ -1,23 +0,0 @@
#ifndef COMPACT
#include <printf.h> // fprintf()
#endif
#include "skip/skip.h"
#include "../include/tinyhook.h"
int find_data(void *start, void *end, const unsigned char *data, size_t len, int count, void **out) {
int matched;
skipidx_t idx;
skip_init(&idx, len, data);
matched = skip_match(&idx, start, end, count, (offset_t *)out);
skip_release(&idx);
#ifndef COMPACT
if (matched == 0) {
fprintf(stderr, "find_data: data not found!\n");
}
#endif
return matched;
}
// int find_code(uint32_t image_index, const unsigned char *code, size_t len, int count, void **out);

View File

@@ -1,121 +0,0 @@
#include <mach-o/dyld.h> // _dyld_*
#include <mach-o/loader.h> // mach_header_64, load_command...
#include <mach-o/nlist.h> // nlist_64
#include <string.h> // strcmp()
#ifndef COMPACT
#include <printf.h> // fprintf()
#endif
#include "../../include/tinyhook.h"
static void *trie_query(const uint8_t *export, const char *name);
void *symexp_solve(uint32_t image_index, const char *symbol_name) {
void *symbol_address = NULL;
intptr_t image_slide = _dyld_get_image_vmaddr_slide(image_index);
struct mach_header_64 *mh_header = (struct mach_header_64 *)_dyld_get_image_header(image_index);
struct load_command *ld_command = (void *)mh_header + sizeof(struct mach_header_64);
#ifndef COMPACT
if (mh_header == NULL) {
fprintf(stderr, "symexp_solve: image_index out of range!\n");
}
#endif
struct dyld_info_command *dyldinfo_cmd = NULL;
struct segment_command_64 *linkedit_cmd = NULL;
for (int i = 0; i < mh_header->ncmds; i++) {
if (ld_command->cmd == LC_SEGMENT_64) {
const struct segment_command_64 *segment = (struct segment_command_64 *)ld_command;
if (strcmp(segment->segname, "__LINKEDIT") == 0) {
linkedit_cmd = (struct segment_command_64 *)ld_command;
}
} else if (ld_command->cmd == LC_DYLD_INFO_ONLY || ld_command->cmd == LC_DYLD_INFO) {
dyldinfo_cmd = (struct dyld_info_command *)ld_command;
if (linkedit_cmd != NULL) {
break;
}
}
ld_command = (void *)ld_command + ld_command->cmdsize;
}
if (dyldinfo_cmd == NULL) {
#ifndef COMPACT
fprintf(stderr, "symexp_solve: LC_DYLD_INFO_ONLY segment not found!\n");
#endif
return NULL;
}
// stroff and strtbl are in the __LINKEDIT segment
// Its offset will change when loaded into the memory, so we need to add this slide
intptr_t linkedit_slide = linkedit_cmd->vmaddr - linkedit_cmd->fileoff;
uint8_t *export_offset = (uint8_t *)image_slide + linkedit_slide + dyldinfo_cmd->export_off;
symbol_address = trie_query(export_offset, symbol_name);
if (symbol_address != NULL) {
symbol_address += image_slide;
}
#ifndef COMPACT
else {
fprintf(stderr, "symexp_solve: symbol not found!\n");
}
#endif
return symbol_address;
}
inline uint64_t read_uleb128(const uint8_t **p) {
int bit = 0;
uint64_t result = 0;
do {
uint64_t slice = **p & 0x7f;
result |= (slice << bit);
bit += 7;
} while (*(*p)++ & 0x80);
return result;
}
static void *trie_query(const uint8_t *export, const char *name) {
// most comments below are copied from <mach-o/loader.h>, not AI generated :P
// a trie node starts with a uleb128 stored the lenth of the exported symbol information
uint64_t node_off = 0;
const char *rest_name = name;
void *symbol_address = NULL;
bool go_child = true;
while (go_child) {
const uint8_t *cur_pos = export + node_off;
uint64_t info_len = read_uleb128(&cur_pos);
// the exported symbol information is followed by the child edges
const uint8_t *child_off = cur_pos + info_len;
if (rest_name[0] == '\0') {
if (info_len != 0) {
// first is a uleb128 containing flags
uint64_t flag = read_uleb128(&cur_pos);
if (flag == EXPORT_SYMBOL_FLAGS_KIND_REGULAR) {
// normally, it is followed by a uleb128 encoded function offset
uint64_t symbol_off = read_uleb128(&cur_pos);
symbol_address = (void *)symbol_off;
}
}
break;
} else {
go_child = false;
cur_pos = child_off;
// child edges start with a byte of how many edges (0-255) this node has
uint8_t child_count = *(uint8_t *)cur_pos++;
// then followed by each edge.
for (int i = 0; i < child_count; i++) {
// each edge is a zero terminated UTF8 of the addition chars
char *cur_str = (char *)cur_pos;
size_t cur_len = strlen(cur_str);
cur_pos += cur_len + 1;
// then followed by a uleb128 offset for the node that edge points to
uint64_t next_off = read_uleb128(&cur_pos);
if (strncmp(rest_name, cur_str, cur_len) == 0) {
go_child = true;
rest_name += cur_len;
node_off = next_off;
break;
}
}
}
}
return symbol_address;
}

View File

@@ -1,60 +0,0 @@
#include <mach-o/dyld.h> // _dyld_*
#include <mach-o/loader.h> // mach_header_64, load_command...
#include <mach-o/nlist.h> // nlist_64
#include <string.h> // strcmp()
#ifndef COMPACT
#include <printf.h> // fprintf()
#endif
#include "../../include/tinyhook.h"
void *symtbl_solve(uint32_t image_index, const char *symbol_name) {
void *symbol_address = NULL;
intptr_t image_slide = _dyld_get_image_vmaddr_slide(image_index);
struct mach_header_64 *mh_header = (struct mach_header_64 *)_dyld_get_image_header(image_index);
struct load_command *ld_command = (void *)mh_header + sizeof(struct mach_header_64);
#ifndef COMPACT
if (mh_header == NULL) {
fprintf(stderr, "symtbl_solve: image_index out of range!\n");
}
#endif
struct symtab_command *symtab_cmd = NULL;
struct segment_command_64 *linkedit_cmd = NULL;
for (int i = 0; i < mh_header->ncmds; i++) {
if (ld_command->cmd == LC_SEGMENT_64) {
const struct segment_command_64 *segment = (struct segment_command_64 *)ld_command;
if (strcmp(segment->segname, "__LINKEDIT") == 0) {
linkedit_cmd = (struct segment_command_64 *)ld_command;
}
} else if (ld_command->cmd == LC_SYMTAB) {
symtab_cmd = (struct symtab_command *)ld_command;
if (linkedit_cmd != NULL) {
break;
}
}
ld_command = (void *)ld_command + ld_command->cmdsize;
}
// stroff and strtbl are in the __LINKEDIT segment
// Its offset will change when loaded into the memory, so we need to add this slide
intptr_t linkedit_slide = linkedit_cmd->vmaddr - linkedit_cmd->fileoff;
struct nlist_64 *nl_tbl = (void *)image_slide + linkedit_slide + symtab_cmd->symoff;
char *str_tbl = (void *)image_slide + linkedit_slide + symtab_cmd->stroff;
for (int j = 0; j < symtab_cmd->nsyms; j++) {
if ((nl_tbl[j].n_type & N_TYPE) == N_SECT) {
if (strcmp(symbol_name, str_tbl + nl_tbl[j].n_un.n_strx) == 0) {
symbol_address = (void *)nl_tbl[j].n_value;
break;
}
}
}
if (symbol_address != NULL) {
symbol_address += image_slide;
}
#ifndef COMPACT
else {
fprintf(stderr, "symtbl_solve: symbol not found!\n");
}
#endif
return symbol_address;
}

View File

@@ -1,183 +0,0 @@
#include <mach/mach_init.h> // mach_task_self()
#include <mach/mach_vm.h> // mach_vm_*
#include <stdlib.h> // atexit()
#include <string.h> // memcpy()
#ifndef COMPACT
#include <mach/mach_error.h> // mach_error_string()
#include <printf.h> // fprintf()
#endif
#ifdef __x86_64__
#include "fde64/fde64.h"
#endif
#include "../include/tinyhook.h"
#define MB (1ll << 20)
#define GB (1ll << 30)
#ifdef __aarch64__
#define AARCH64_B 0x14000000 // b +0
#define AARCH64_BL 0x94000000 // bl +0
#define AARCH64_ADRP 0x90000011 // adrp x17, 0
#define AARCH64_BR 0xd61f0220 // br x17
#define AARCH64_BLR 0xd63f0220 // blr x17
#define AARCH64_ADD 0x91000231 // add x17, x17, 0
#define AARCH64_SUB 0xd1000231 // sub x17, x17, 0
#define MAX_JUMP_SIZE 12
#elif __x86_64__
#define X86_64_CALL 0xe8 // call
#define X86_64_JMP 0xe9 // jmp
#define X86_64_JMP_RIP 0x000025ff // jmp [rip]
#define X86_64_CALL_RIP 0x000015ff // call [rip]
#define X86_64_MOV_RI64 0xb848 // mov r64, m64
#define X86_64_MOV_RM64 0x8b48 // mov r64, [r64]
#define MAX_JUMP_SIZE 14
#endif
int tiny_insert(void *address, void *destination, bool link) {
size_t jump_size;
int assembly;
unsigned char bytes[MAX_JUMP_SIZE];
#ifdef __aarch64__
// b/bl imm ; go to destination
jump_size = 4;
assembly = (destination - address) >> 2 & 0x3ffffff;
assembly |= link ? AARCH64_BL : AARCH64_B;
*(int *)bytes = assembly;
#elif __x86_64__
// jmp/call imm ; go to destination
jump_size = 5;
*bytes = link ? X86_64_CALL : X86_64_JMP;
assembly = (long)destination - (long)address - 5;
*(int *)(bytes + 1) = assembly;
#endif
write_mem(address, bytes, jump_size);
return jump_size;
}
int tiny_insert_far(void *address, void *destination, bool link) {
size_t jump_size;
unsigned char bytes[MAX_JUMP_SIZE];
#ifdef __aarch64__
// adrp x17, imm
// add x17, x17, imm ; x17 -> destination
// br/blr x17
jump_size = 12;
int assembly;
assembly = (((long)destination >> 12) - ((long)address >> 12)) & 0x1fffff;
assembly = ((assembly & 0x3) << 29) | (assembly >> 2 << 5) | AARCH64_ADRP;
*(int *)bytes = assembly;
assembly = ((long)destination & 0xfff) << 10 | AARCH64_ADD;
*(int *)(bytes + 4) = assembly;
*(int *)(bytes + 8) = link ? AARCH64_BLR : AARCH64_BR;
#elif __x86_64__
jump_size = 14;
// jmp [rip] ; rip stored destination
*(int *)bytes = link ? X86_64_CALL_RIP : X86_64_JMP_RIP;
bytes[5] = bytes[6] = 0;
*(long long *)(bytes + 6) = (long long)destination;
#endif
write_mem(address, bytes, jump_size);
return jump_size;
}
int position = 0;
mach_vm_address_t vm;
static int get_jump_size(void *address, void *destination);
static int insert_jump(void *address, void *destination);
static int save_header(void *address, void *destination, int *skip_len);
int tiny_hook(void *function, void *destination, void **origin) {
int kr = 0;
if (origin == NULL)
insert_jump(function, destination);
else {
if (!position) {
// alloc a vm to store headers and jumps
kr = mach_vm_allocate(mach_task_self(), &vm, PAGE_SIZE, VM_FLAGS_ANYWHERE);
#ifndef COMPACT
if (kr != 0) {
fprintf(stderr, "mach_vm_allocate: %s\n", mach_error_string(kr));
}
#endif
}
int skip_len;
*origin = (void *)(vm + position);
position += save_header(function, (void *)(vm + position), &skip_len);
position += insert_jump((void *)(vm + position), function + skip_len);
insert_jump(function, destination);
}
return kr;
}
static int get_jump_size(void *address, void *destination) {
long long distance = destination > address ? destination - address : address - destination;
#ifdef __aarch64__
return distance < 128 * MB ? 4 : 12;
#elif __x86_64__
return distance < 2 * GB ? 5 : 14;
#endif
}
static int insert_jump(void *address, void *destination) {
if (get_jump_size(address, destination) <= 5)
return tiny_insert(address, destination, false);
else
return tiny_insert_far(address, destination, false);
}
static int save_header(void *address, void *destination, int *skip_len) {
int header_len = 0;
#ifdef __aarch64__
header_len = *skip_len = get_jump_size(address, destination);
unsigned char bytes_out[MAX_JUMP_SIZE];
read_mem(bytes_out, address, MAX_JUMP_SIZE);
for (int i = 0; i < header_len; i += 4) {
int cur_asm = *(int *)(bytes_out + i);
long cur_addr = (long)address + i, cur_dst = (long)destination + i;
if (((cur_asm ^ 0x90000000) & 0x9f000000) == 0) {
// adrp
// modify the immediate
int len = (cur_asm >> 29 & 0x3) | ((cur_asm >> 3) & 0x1ffffc);
len += (cur_addr >> 12) - (cur_dst >> 12);
cur_asm &= 0x9f00001f;
cur_asm = ((len & 0x3) << 29) | (len >> 2 << 5) | cur_asm;
*(int *)(bytes_out + i) = cur_asm;
}
}
#elif __x86_64__
int min_len;
struct fde64s assembly;
unsigned char bytes_in[MAX_JUMP_SIZE * 2], bytes_out[MAX_JUMP_SIZE * 4];
read_mem(bytes_in, address, MAX_JUMP_SIZE * 2);
min_len = get_jump_size(address, destination);
for (*skip_len = 0; *skip_len < min_len; *skip_len += assembly.len) {
long long cur_addr = (long long)address + *skip_len;
decode(bytes_in + *skip_len, &assembly);
if (assembly.opcode == 0x8B && assembly.modrm_rm == 0b101) {
// mov r64, [rip+]
// split it into 2 instructions
// mov r64 $rip+(immediate)
// mov r64 [r64]
*(short *)(bytes_out + header_len) = X86_64_MOV_RI64;
bytes_out[header_len + 1] += assembly.modrm_reg;
*(long long *)(bytes_out + header_len + 2) = assembly.disp32 + cur_addr + assembly.len;
header_len += 10;
*(short *)(bytes_out + header_len) = X86_64_MOV_RM64;
bytes_out[header_len + 2] = assembly.modrm_reg << 3 | assembly.modrm_reg;
header_len += 3;
} else {
memcpy(bytes_out + header_len, bytes_in + *skip_len, assembly.len);
header_len += assembly.len;
}
}
#endif
write_mem(destination, bytes_out, header_len);
return header_len;
}