Last active
March 19, 2025 15:24
-
-
Save EthanArbuckle/6b3ce401dd1f98caadb29f0580a419df to your computer and use it in GitHub Desktop.
find function by string xref
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Found string at address: 0x1048aa84d | |
Found reference in function: hidden_function | |
Found function that references string: 0x1048a89ec | |
*/ | |
#import <Foundation/Foundation.h> | |
#import <UIKit/UIKit.h> | |
#import "AppDelegate.h" | |
#import <mach-o/loader.h> | |
#import <mach-o/dyld.h> | |
#import <dlfcn.h> | |
#define is_adrp(insn) ((insn & 0x9F000000) == 0x90000000) | |
#define is_add_imm(insn) ((insn & 0xFFC00000) == 0x91000000) | |
#define is_adr(insn) ((insn & 0x9F000000) == 0x10000000) | |
extern struct mach_header_64 *_dyld_get_prog_image_header(void); | |
int main(int argc, char *argv[]); | |
static uint64_t get_slide(void) { | |
static int main_index = -1; | |
if (main_index == -1) { | |
for (int i = 0; i < _dyld_image_count(); i++) { | |
if ((const struct mach_header_64 *)_dyld_get_image_header(i) == _dyld_get_prog_image_header()) { | |
main_index = i; | |
break; | |
} | |
} | |
} | |
return _dyld_get_image_vmaddr_slide(main_index); | |
} | |
static const struct segment_command_64 *find_text_segment(const struct mach_header_64 *mh) { | |
const struct load_command *lc = (const struct load_command *)((const char *)mh + sizeof(struct mach_header_64)); | |
for (uint32_t i = 0; i < mh->ncmds; i++) { | |
if (lc->cmd == LC_SEGMENT_64) { | |
const struct segment_command_64 *seg = (const struct segment_command_64 *)lc; | |
if (strcmp(seg->segname, "__TEXT") == 0) { | |
return seg; | |
} | |
} | |
lc = (const struct load_command*)((uintptr_t)lc + lc->cmdsize); | |
} | |
return NULL; | |
} | |
static const struct section_64 *find_cstring_section(const struct segment_command_64 *text_seg) { | |
const struct section_64 *sect = (const struct section_64*)((uintptr_t)text_seg + sizeof(struct segment_command_64)); | |
for (uint32_t i = 0; i < text_seg->nsects; i++) { | |
if (strcmp(sect[i].sectname, "__cstring") == 0) { | |
return §[i]; | |
} | |
} | |
return NULL; | |
} | |
uint64_t find_string_address(const char *target_str) { | |
const struct mach_header_64 *mh = _dyld_get_prog_image_header(); | |
const struct segment_command_64 *text_seg = find_text_segment(mh); | |
if (text_seg == NULL) { | |
return 0; | |
} | |
const struct section_64 *cstring_sect = find_cstring_section(text_seg); | |
if (cstring_sect == NULL) { | |
return 0; | |
} | |
intptr_t slide = get_slide(); | |
const char *string_base = (const char *)(cstring_sect->addr + slide); | |
uint64_t section_size = cstring_sect->size; | |
if (string_base == NULL || section_size == 0) { | |
return 0; | |
} | |
const char *string_end = string_base + section_size; | |
const char *current = string_base; | |
while (current < string_end && current >= string_base) { | |
if (current && *current != '\0') { | |
if (strcmp(current, target_str) == 0) { | |
return (uint64_t)current; | |
} | |
size_t len = strnlen(current, string_end - current); | |
current += len + 1; | |
} | |
else { | |
current++; | |
} | |
} | |
return 0; | |
} | |
static void hidden_function(void) { | |
printf("abc123 %d\n", getpid()); | |
} | |
uint64_t find_string_refs(uint64_t string_addr, uint64_t search_start, uint64_t search_end) { | |
uint32_t *current = (uint32_t *)search_start; | |
struct mach_header_64 *mh = (struct mach_header_64 *)_dyld_get_prog_image_header(); | |
uint32_t *end = (uint32_t *)((uintptr_t)mh + (search_end - search_start)); | |
uint64_t last_adrp_target = 0; | |
int last_adrp_reg = -1; | |
int main_index = -1; | |
for (int i = 0; i < _dyld_image_count(); i++) { | |
const struct mach_header_64 *image_header = (const struct mach_header_64 *)_dyld_get_image_header(i); | |
if (image_header == mh) { | |
main_index = i; | |
break; | |
} | |
} | |
while (current < end) { | |
uint32_t insn = *current; | |
if (is_adrp(insn)) { | |
int rd = (insn & 0x1F); | |
uint64_t immhi = (insn >> 5) & 0x7FFFF; | |
uint64_t immlo = (insn >> 29) & 0x3; | |
int64_t imm = (immhi << 2) | immlo; | |
if (imm & (1ULL << 20)) { | |
imm |= ~((1ULL << 21) - 1); | |
} | |
uint64_t pc = (uint64_t)current & ~0xFFF; | |
last_adrp_target = pc + (imm << 12); | |
last_adrp_reg = rd; | |
} | |
if (is_add_imm(insn)) { | |
int rn = (insn >> 5) & 0x1F; | |
uint64_t imm = ((insn >> 10) & 0xFFF); | |
if (rn == last_adrp_reg) { | |
uint64_t target = last_adrp_target + imm; | |
if (target == string_addr) { | |
Dl_info info; | |
dladdr((void *)current, &info); | |
// needed because our lookup itself is a ref to the str | |
if ((uint64_t)info.dli_saddr == (uint64_t)main) { | |
current++; | |
continue; | |
} | |
printf("Found reference in function: %s\n", info.dli_sname); | |
return (uint64_t)info.dli_saddr; | |
} | |
} | |
} | |
current++; | |
} | |
return 0; | |
} | |
int main(int argc, char *argv[]) { | |
hidden_function(); | |
const struct mach_header_64 *mh = _dyld_get_prog_image_header(); | |
if (mh == NULL) { | |
return 1; | |
} | |
const char *target_str = "abc123 %d\n"; | |
uint64_t str_addr = find_string_address(target_str); | |
if (str_addr) { | |
printf("Found string at address: 0x%llx\n", str_addr); | |
const struct segment_command_64 *text_seg = find_text_segment(mh); | |
if (text_seg) { | |
uint64_t search_start = (uint64_t)mh; | |
uint64_t search_end = search_start + text_seg->vmsize; | |
uint64_t target_function = find_string_refs(str_addr, search_start, search_end); | |
if (target_function) { | |
printf("Found function that references string: 0x%llx\n", target_function); | |
} | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment