Move elf/macho parsers into separate files

This commit is contained in:
pancake committed 2025-09-18 15:43:37 +02:00
1 parent 2c8352928f
commit 3af512645b
5 files changed
+510 -234

No files matched your search

+246
View File
@@ -0,0 +1,246 @@
// Fast ELF parser for r2unity
#include "lib.h"
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
static inline ut32 RD_LE32 (const ut8 *p) {
return ((ut32)p[0]) | ((ut32)p[1] << 8) | ((ut32)p[2] << 16) | ((ut32)p[3] << 24);
}
static inline ut64 RD_LE64 (const ut8 *p) {
return ((ut64)p[0]) | ((ut64)p[1] << 8) | ((ut64)p[2] << 16) | ((ut64)p[3] << 24) |
((ut64)p[4] << 32) | ((ut64)p[5] << 40) | ((ut64)p[6] << 48) | ((ut64)p[7] << 56);
}
static inline ut16 RD_LE16 (const ut8 *p) {
return (ut16)((ut16)p[0] | ((ut16)p[1] << 8));
}
typedef struct {
ut64 vaddr;
ut64 memsz;
ut64 offset;
ut64 filesz;
ut32 flags;
ut32 p_type;
} ElfSeg;
typedef struct {
ut8 *file;
ut64 filesize;
int is64;
int le;
ElfSeg segs[128];
int nsegs;
} ElfImg;
static bool elf_load (const char *path, ElfImg *e) {
memset (e, 0, sizeof (*e));
int fd = open (path, O_RDONLY);
if (fd < 0) return false;
struct stat st;
if (fstat (fd, &st) != 0 || st.st_size <= 0) { close (fd); return false; }
e->filesize = (ut64) st.st_size;
e->file = R_NEWS (ut8, e->filesize);
if (!e->file) { close (fd); return false; }
ssize_t rd = read (fd, e->file, (size_t) e->filesize);
close (fd);
if (rd < 0 || (ut64) rd != e->filesize) { R_FREE (e->file); return false; }
const ut8 *p = e->file;
if (!(p[0] == 0x7f && p[1] == 'E' && p[2] == 'L' && p[3] == 'F')) { R_FREE (e->file); return false; }
e->is64 = (p[4] == 2);
e->le = (p[5] == 1);
if (!e->le) { R_FREE (e->file); return false; }
if (e->is64) {
ut64 phoff = RD_LE64 (p + 0x20);
ut16 phentsize = RD_LE16 (p + 0x36);
ut16 phnum = RD_LE16 (p + 0x38);
for (ut16 i = 0; i < phnum && e->nsegs < (int)(sizeof (e->segs)/sizeof (e->segs[0])); i++) {
ut64 off = phoff + (ut64) i * phentsize;
if (off + 56 > e->filesize) break;
const ut8 *ph = p + off;
ut32 p_type = RD_LE32 (ph + 0);
ut32 p_flags = RD_LE32 (ph + 4);
ut64 p_offset = RD_LE64 (ph + 8);
ut64 p_vaddr = RD_LE64 (ph + 16);
ut64 p_filesz = RD_LE64 (ph + 32);
ut64 p_memsz = RD_LE64 (ph + 40);
if (p_type == 1 || p_type == 2) {
ElfSeg *s = &e->segs[e->nsegs++];
s->vaddr = p_vaddr; s->memsz = p_memsz; s->offset = p_offset; s->filesz = p_filesz; s->flags = p_flags; s->p_type = p_type;
}
}
} else {
ut32 phoff = RD_LE32 (p + 0x1C);
ut16 phentsize = RD_LE16 (p + 0x2A);
ut16 phnum = RD_LE16 (p + 0x2C);
for (ut16 i = 0; i < phnum && e->nsegs < (int)(sizeof (e->segs)/sizeof (e->segs[0])); i++) {
ut64 off = (ut64) phoff + (ut64) i * phentsize;
if (off + 32 > e->filesize) break;
const ut8 *ph = p + off;
ut32 p_type = RD_LE32 (ph + 0);
ut32 p_offset = RD_LE32 (ph + 4);
ut32 p_vaddr = RD_LE32 (ph + 8);
ut32 p_filesz = RD_LE32 (ph + 16);
ut32 p_memsz = RD_LE32 (ph + 20);
ut32 p_flags = RD_LE32 (ph + 24);
if (p_type == 1 || p_type == 2) {
ElfSeg *s = &e->segs[e->nsegs++];
s->vaddr = p_vaddr; s->memsz = p_memsz; s->offset = p_offset; s->filesz = p_filesz; s->flags = p_flags; s->p_type = p_type;
}
}
}
return true;
}
static void elf_free (ElfImg *e) { R_FREE (e->file); }
static inline const ut8 *elf_vm_to_ptr (ElfImg *e, ut64 vaddr) {
for (int i = 0; i < e->nsegs; i++) {
const ElfSeg *s = &e->segs[i];
if (vaddr >= s->vaddr && vaddr < s->vaddr + s->memsz) {
ut64 delta = vaddr - s->vaddr;
if (delta < s->filesz && s->offset + delta < e->filesize) {
return e->file + s->offset + delta;
}
return NULL;
}
}
return NULL;
}
R_API bool r2unity_find_method_pointers_elf (R2UnityMetadata *meta, const char *elf_path, ut64 **out_ptrs) {
if (!meta || !elf_path || !out_ptrs) return false;
*out_ptrs = NULL;
ElfImg e;
if (!elf_load (elf_path, &e)) return false;
size_t method_count = meta->header.v24.methodsSize / sizeof (Il2CppMethodDefinition);
if (!method_count) { elf_free (&e); return false; }
ut64 base_vaddr = UT64_MAX, text_lo = UT64_MAX, text_hi = 0;
for (int i = 0; i < e.nsegs; i++) {
const ElfSeg *s = &e.segs[i];
if (s->vaddr < base_vaddr) base_vaddr = s->vaddr;
if ((s->flags & 0x1) && (s->flags & 0x4)) {
if (s->vaddr < text_lo) text_lo = s->vaddr;
if (s->vaddr + s->memsz > text_hi) text_hi = s->vaddr + s->memsz;
}
}
if (text_lo == UT64_MAX || text_hi <= text_lo) { elf_free (&e); return false; }
ut64 *candidates = R_NEWS (ut64, method_count);
if (!candidates) { elf_free (&e); return false; }
bool found = false;
int ptrsz = e.is64 ? 8 : 4;
// Apply REL(A)/RELR (relative only)
ut64 dyn_off = 0, dyn_sz = 0;
for (int i = 0; i < e.nsegs; i++) { const ElfSeg *s = &e.segs[i]; if (s->p_type == 2) { dyn_off = s->offset; dyn_sz = s->filesz; break; } }
ut64 rela_off = 0, rela_sz = 0, rela_ent = e.is64? 24: 12;
ut64 rel_off = 0, rel_sz = 0, rel_ent = e.is64? 16: 8;
ut64 relr_off = 0, relr_sz = 0, relr_ent = e.is64? 8: 4;
ut64 base = base_vaddr;
if (dyn_off && dyn_sz) {
const ut8 *d = e.file + dyn_off;
for (ut64 off = 0; off + (e.is64? 16: 8) <= dyn_sz; off += (e.is64? 16: 8)) {
ut64 tag = e.is64 ? RD_LE64 (d + off) : (ut64) RD_LE32 (d + off);
ut64 val = e.is64 ? RD_LE64 (d + off + 8) : (ut64) RD_LE32 (d + off + 4);
if (tag == 0) break;
if (tag == 7) rela_off = val; else if (tag == 8) rela_sz = val; else if (tag == 9) rela_ent = val;
else if (tag == 17) rel_off = val; else if (tag == 18) rel_sz = val; else if (tag == 19) rel_ent = val;
else if (tag == 36) relr_off = val; else if (tag == 35) relr_sz = val; else if (tag == 37) relr_ent = val;
}
}
if (rela_off && rela_sz && rela_ent) {
for (ut64 i = 0; i + rela_ent <= rela_sz; i += rela_ent) {
const ut8 *rp = elf_vm_to_ptr (&e, rela_off + i); if (!rp) break;
ut64 r_offset = e.is64 ? RD_LE64 (rp + 0) : (ut64) RD_LE32 (rp + 0);
ut64 r_info = e.is64 ? RD_LE64 (rp + 8) : (ut64) RD_LE32 (rp + 4);
ut64 r_addend = e.is64 ? RD_LE64 (rp + 16) : (ut64) RD_LE32 (rp + 8);
ut64 type = e.is64 ? (r_info & 0xffffffffULL) : (r_info & 0xffULL);
bool is_relative = (type == 8) || (type == 23) || (type == 1027) || (type == 8);
if (!is_relative) continue;
ut8 *loc = (ut8 *) elf_vm_to_ptr (&e, r_offset); if (!loc) continue;
ut64 newv = base + r_addend;
if (e.is64) { for (int b = 0; b < 8; b++) loc[b] = (ut8)((newv >> (8*b)) & 0xff); }
else { ut32 v32 = (ut32) newv; for (int b = 0; b < 4; b++) loc[b] = (ut8)((v32 >> (8*b)) & 0xff); }
}
}
if (rel_off && rel_sz && rel_ent) {
for (ut64 i = 0; i + rel_ent <= rel_sz; i += rel_ent) {
const ut8 *rp = elf_vm_to_ptr (&e, rel_off + i); if (!rp) break;
ut64 r_offset = e.is64 ? RD_LE64 (rp + 0) : (ut64) RD_LE32 (rp + 0);
ut64 r_info = e.is64 ? RD_LE64 (rp + 8) : (ut64) RD_LE32 (rp + 4);
ut64 type = e.is64 ? (r_info & 0xffffffffULL) : (r_info & 0xffULL);
bool is_relative = (type == 8) || (type == 23) || (type == 1027) || (type == 8);
if (!is_relative) continue;
ut8 *loc = (ut8 *) elf_vm_to_ptr (&e, r_offset); if (!loc) continue;
ut64 add = e.is64 ? RD_LE64 (loc) : (ut64) RD_LE32 (loc);
ut64 newv = base + add;
if (e.is64) { for (int b = 0; b < 8; b++) loc[b] = (ut8)((newv >> (8*b)) & 0xff); }
else { ut32 v32 = (ut32) newv; for (int b = 0; b < 4; b++) loc[b] = (ut8)((v32 >> (8*b)) & 0xff); }
}
}
if (e.is64 && relr_off && relr_sz && relr_ent) {
ut64 curr = 0;
for (ut64 i = 0; i + relr_ent <= relr_sz; i += relr_ent) {
const ut8 *rp = elf_vm_to_ptr (&e, relr_off + i); if (!rp) break;
ut64 R = RD_LE64 (rp);
if ((R & 1ULL) == 0) {
ut64 addr = R; ut8 *loc = (ut8 *) elf_vm_to_ptr (&e, addr);
if (loc) { ut64 add = RD_LE64 (loc); ut64 newv = base + add; for (int b = 0; b < 8; b++) loc[b] = (ut8)((newv >> (8*b)) & 0xff); }
curr = addr + 8;
} else {
ut64 bitmap = R >> 1;
for (int bit = 0; bit < 63; bit++) if (bitmap & (1ULL << bit)) {
ut64 addr = curr + (ut64)(bit + 1) * 8ULL;
ut8 *loc = (ut8 *) elf_vm_to_ptr (&e, addr); if (!loc) continue;
ut64 add = RD_LE64 (loc); ut64 newv = base + add; for (int b = 0; b < 8; b++) loc[b] = (ut8)((newv >> (8*b)) & 0xff);
}
curr += 8ULL * 63ULL;
}
}
}
// Scan for [count][ptr]
ut32 expected = (ut32) (meta->header.v24.methodsSize / 32);
for (int i = 0; i < e.nsegs && !found; i++) {
const ElfSeg *s = &e.segs[i];
bool is_data = (s->flags & 0x1) && !(s->flags & 0x4);
if (!is_data || s->filesz < (ut64)(8 + ptrsz)) continue;
const ut8 *buf = e.file + s->offset;
ut64 sz = s->filesz;
for (ut64 off = 0; off + (ut64)(8 + ptrsz) <= sz; off += 4) {
ut32 cnt32 = RD_LE32 (buf + off);
if (cnt32 == 0) continue;
if (expected) { if (cnt32 < expected / 4 || cnt32 > expected * 2) continue; } else { if (cnt32 < 64) continue; }
ut64 arrptr = (ptrsz == 8) ? RD_LE64 (buf + off + 8) : (ut64) RD_LE32 (buf + off + 4);
ut32 good = 0, seen = 0;
ut32 sample = R_MIN ((ut32)128, cnt32);
for (ut32 k = 0; k < sample; k++) {
const ut8 *pp = elf_vm_to_ptr (&e, arrptr + (ut64)k * (ut64)ptrsz);
if (!pp && base_vaddr != UT64_MAX) { pp = elf_vm_to_ptr (&e, base_vaddr + arrptr + (ut64)k * (ut64)ptrsz); }
if (!pp) break;
ut64 val = (ptrsz == 8) ? RD_LE64 (pp) : (ut64) RD_LE32 (pp);
if (val) seen++;
if ((val >= text_lo && val < text_hi) || (base_vaddr != UT64_MAX && (val + base_vaddr) >= text_lo && (val + base_vaddr) < text_hi)) good++;
}
if (seen >= 8 && good >= 8) {
memset (candidates, 0, method_count * sizeof (ut64));
size_t tocopy = R_MIN ((size_t) cnt32, method_count);
size_t in_text = 0;
for (size_t m = 0; m < tocopy; m++) {
const ut8 *pp = elf_vm_to_ptr (&e, arrptr + (ut64)m * (ut64)ptrsz);
if (!pp && base_vaddr != UT64_MAX) { pp = elf_vm_to_ptr (&e, base_vaddr + arrptr + (ut64)m * (ut64)ptrsz); }
if (!pp) break;
ut64 val = (ptrsz == 8) ? RD_LE64 (pp) : (ut64) RD_LE32 (pp);
ut64 abs = (val >= text_lo && val < text_hi)? val: (base_vaddr != UT64_MAX? val + base_vaddr: val);
if (abs >= text_lo && abs < text_hi) { candidates[m] = abs; in_text++; }
}
if (in_text >= 8) { found = true; }
break;
}
}
}
if (!found) {
R_FREE (candidates); elf_free (&e); return false;
}
*out_ptrs = candidates; elf_free (&e); return true;
}
+58 -233
View File
@@ -283,7 +283,7 @@ R_API bool r2unity_find_method_pointers (R2UnityMetadata *meta, const char *exe_
r_core_free (core);
return false;
}
// Try strict match first: full method_count array
// Try fast sampled window scan first instead of full sliding O(N^2)
ut64 *candidates = R_NEWS (ut64, method_count);
if (!candidates) {
r_core_free (core);
@@ -294,32 +294,47 @@ R_API bool r2unity_find_method_pointers (R2UnityMetadata *meta, const char *exe_
ut64 start = data_ranges[r].from;
ut64 end = data_ranges[r].to;
ut64 rsize = end - start;
if (rsize < (ut64)method_count * (ut64)ptrsz) {
if (rsize < (ut64)ptrsz * 256) {
continue;
}
ut8 *rbuf = R_NEWS (ut8, rsize);
if (!rbuf) {
break;
}
if (!rbuf) break;
r_io_read_at (core->io, start, rbuf, rsize);
for (ut64 off = 0; off + ((ut64)method_count * (ut64)ptrsz) <= rsize; off += ptrsz) {
const ut64 sampleN = R_MIN ((ut64)512, (ut64)method_count);
const ut64 step = (ut64)ptrsz * 4; // stride to reduce checks
for (ut64 off = 0; off + sampleN * (ut64)ptrsz <= rsize; off += step) {
ut32 good = 0, zero = 0;
for (size_t i = 0; i < method_count; i++) {
for (ut64 i = 0; i < sampleN; i++) {
ut64 val = (ptrsz == 8)
? RD_LE64 (rbuf + off + (ut64)i * 8)
: (ut64) RD_LE32 (rbuf + off + (ut64)i * 4);
candidates[i] = val;
if (!val) {
zero++;
continue;
}
if (val >= text_lo && val < text_hi) {
good++;
}
? RD_LE64 (rbuf + off + i * 8)
: (ut64) RD_LE32 (rbuf + off + i * 4);
if (!val) { zero++; continue; }
if (val >= text_lo && val < text_hi) { good++; }
}
if (good > 0 && good + zero >= (ut32)(method_count * 9 / 10)) {
found = true;
break;
if (good > 0 && good + zero >= (ut32)(sampleN * 9 / 10)) {
// Likely candidate; fetch full array from IO once
ut64 addr = start + off;
ut64 bytes = (ut64)method_count * (ut64)ptrsz;
ut8 *tmp = R_NEWS (ut8, bytes);
if (!tmp) { continue; }
st64 nr = r_io_read_at (core->io, addr, tmp, bytes);
if (nr > 0) {
ut32 g2 = 0, z2 = 0;
for (size_t i = 0; i < method_count; i++) {
ut64 val = (ptrsz == 8)
? RD_LE64 (tmp + (ut64)i * 8)
: (ut64) RD_LE32 (tmp + (ut64)i * 4);
candidates[i] = val;
if (!val) { z2++; continue; }
if (val >= text_lo && val < text_hi) { g2++; }
}
if (g2 > 0 && g2 + z2 >= (ut32)(method_count * 9 / 10)) {
found = true;
R_FREE (tmp);
break;
}
}
R_FREE (tmp);
}
}
R_FREE (rbuf);
@@ -352,21 +367,22 @@ R_API bool r2unity_find_method_pointers (R2UnityMetadata *meta, const char *exe_
}
}
if (!in_r) continue;
// Sample first up to 256 entries
// Sample first up to 256 entries with one bulk read
ut32 good = 0, seen = 0;
ut32 sample = R_MIN ((ut32)256, cnt32);
ut64 sbytes = (ut64)sample * (ut64)ptrsz;
ut8 *sbuf = R_NEWS (ut8, sbytes);
if (!sbuf) continue;
st64 sread = r_io_read_at (core->io, arrptr, sbuf, sbytes);
if (sread <= 0) { R_FREE (sbuf); continue; }
for (ut32 i = 0; i < sample; i++) {
ut64 val = 0;
if (ptrsz == 8) {
r_io_read_at (core->io, arrptr + (ut64)i * 8, (ut8 *)&val, 8);
} else {
ut32 v32 = 0;
r_io_read_at (core->io, arrptr + (ut64)i * 4, (ut8 *)&v32, 4);
val = v32;
}
ut64 val = (ptrsz == 8)
? RD_LE64 (sbuf + (ut64)i * 8)
: (ut64) RD_LE32 (sbuf + (ut64)i * 4);
if (val) seen++;
if (val >= text_lo && val < text_hi) good++;
}
R_FREE (sbuf);
if (good >= 32 && seen >= 32) {
gmp_ptr = arrptr;
gmp_cnt = cnt32;
@@ -427,18 +443,20 @@ R_API bool r2unity_find_method_pointers (R2UnityMetadata *meta, const char *exe_
R_FREE (rbuf);
}
if (best_len > 0) {
// Read best_len pointers into prefix of candidates, rest zeros
// Read best_len pointers in one shot into prefix of candidates, rest zeros
memset (candidates, 0, method_count * sizeof (ut64));
for (size_t i = 0; i < best_len; i++) {
ut64 val = 0;
if (ptrsz == 8) {
r_io_read_at (core->io, best_start + i * 8, (ut8 *)&val, 8);
} else {
ut32 v32 = 0;
r_io_read_at (core->io, best_start + i * 4, (ut8 *)&v32, 4);
val = v32;
ut64 bytes = (ut64)best_len * (ut64)ptrsz;
ut8 *tmp = R_NEWS (ut8, bytes);
if (tmp) {
st64 nr = r_io_read_at (core->io, best_start, tmp, bytes);
if (nr > 0) {
for (size_t i = 0; i < best_len; i++) {
candidates[i] = (ptrsz == 8)
? RD_LE64 (tmp + (ut64)i * 8)
: (ut64) RD_LE32 (tmp + (ut64)i * 4);
}
}
candidates[i] = val;
R_FREE (tmp);
}
found = true;
}
@@ -546,200 +564,7 @@ R_API bool r2unity_read_method_pointers_at (R2UnityMetadata *meta, const char *e
if (!(v >= text_lo && v < text_hi)) ptrs[i] = 0;
}
}
*out_ptrs = ptrs;
*out_ptrs = ptrs;
r_core_free (core);
return true;
}
// Minimal Mach-O 64-bit + FAT parser to locate g_MethodPointers quickly
typedef struct {
ut64 vmaddr;
ut64 vmsize;
ut64 fileoff;
ut64 filesize;
ut32 maxprot;
char segname[16];
} MachSeg;
typedef struct {
ut8 *file;
ut64 filesize;
ut64 base; // slice base offset
ut32 ncmds;
ut64 cmd_off;
MachSeg segs[128];
int nsegs;
} MachO;
static inline ut32 RD_BE32 (const ut8 *p) {
return ((ut32)p[3]) | ((ut32)p[2] << 8) | ((ut32)p[1] << 16) | ((ut32)p[0] << 24);
}
static bool macho_load (const char *path, MachO *mo) {
memset (mo, 0, sizeof (*mo));
int fd = open (path, O_RDONLY);
if (fd < 0) return false;
struct stat st;
if (fstat (fd, &st) != 0 || st.st_size <= 0) {
close (fd);
return false;
}
mo->filesize = (ut64) st.st_size;
mo->file = R_NEWS (ut8, mo->filesize);
if (!mo->file) {
close (fd);
return false;
}
ssize_t rd = read (fd, mo->file, (size_t) mo->filesize);
close (fd);
if (rd < 0 || (ut64) rd != mo->filesize) {
R_FREE (mo->file);
return false;
}
// Handle FAT
ut32 magic = RD_BE32 (mo->file);
ut64 off = 0;
if (magic == 0xcafebabe) {
ut32 nfat = RD_BE32 (mo->file + 4);
// Choose first arm64 (cputype 0x100000c) or first slice
ut32 best = 0;
for (ut32 i = 0; i < nfat; i++) {
const ut8 *fa = mo->file + 8 + i * 20; // fat_arch (not _64) is 20 bytes, but common on iOS
ut32 cputype = RD_BE32 (fa + 0);
if (cputype == 0x0100000c /* ARM64 */) { best = i; break; }
}
const ut8 *fa = mo->file + 8 + best * 20;
off = RD_BE32 (fa + 8);
// size = RD_BE32 (fa + 12);
}
mo->base = off;
const ut8 *p = mo->file + off;
ut32 mh_magic = *(const ut32 *) p;
if (mh_magic != 0xFEEDFACF) {
// Not 64-bit LE Mach-O
R_FREE (mo->file);
return false;
}
// mach_header_64
ut32 ncmds = *(const ut32 *)(p + 0x10);
mo->ncmds = ncmds;
mo->cmd_off = off + 0x20;
ut64 co = mo->cmd_off;
for (ut32 i = 0; i < ncmds && (co + 8 <= mo->filesize); i++) {
ut32 cmd = *(const ut32 *)(mo->file + co);
ut32 cmdsize = *(const ut32 *)(mo->file + co + 4);
if (cmd == 0x19 /* LC_SEGMENT_64 */ && cmdsize >= 72) {
if (mo->nsegs < (int)(sizeof (mo->segs)/sizeof (mo->segs[0]))) {
MachSeg *s = &mo->segs[mo->nsegs++];
const ut8 *sp = mo->file + co + 8;
memcpy (s->segname, sp, 16);
s->vmaddr = *(const ut64 *)(sp + 16);
s->vmsize = *(const ut64 *)(sp + 24);
s->fileoff = *(const ut64 *)(sp + 32);
s->filesize = *(const ut64 *)(sp + 40);
s->maxprot = *(const ut32 *)(sp + 48);
}
}
co += cmdsize ? cmdsize : 8;
}
return true;
}
static void macho_free (MachO *mo) {
R_FREE (mo->file);
}
static inline bool macho_vm_in_text (MachO *mo, ut64 addr, ut64 *text_lo, ut64 *text_hi) {
ut64 lo = UT64_MAX, hi = 0;
for (int i = 0; i < mo->nsegs; i++) {
MachSeg *s = &mo->segs[i];
bool is_text = (s->maxprot & 0x4) || !strncmp (s->segname, "__TEXT", 6);
if (is_text) {
if (s->vmaddr < lo) lo = s->vmaddr;
if (s->vmaddr + s->vmsize > hi) hi = s->vmaddr + s->vmsize;
}
}
if (text_lo) *text_lo = lo;
if (text_hi) *text_hi = hi;
return (addr >= lo && addr < hi);
}
static inline const ut8 *macho_vm_to_ptr (MachO *mo, ut64 vmaddr) {
for (int i = 0; i < mo->nsegs; i++) {
MachSeg *s = &mo->segs[i];
if (vmaddr >= s->vmaddr && vmaddr < s->vmaddr + s->vmsize) {
ut64 delta = vmaddr - s->vmaddr;
if (delta < s->filesize) {
return mo->file + mo->base + s->fileoff + delta;
}
return NULL;
}
}
return NULL;
}
R_API bool r2unity_find_method_pointers_macho (R2UnityMetadata *meta, const char *macho_path, ut64 **out_ptrs) {
if (!meta || !macho_path || !out_ptrs) return false;
*out_ptrs = NULL;
MachO mo;
if (!macho_load (macho_path, &mo)) {
return false;
}
size_t method_count = meta->header.v24.methodsSize / sizeof (Il2CppMethodDefinition);
if (!method_count) {
macho_free (&mo);
return false;
}
ut64 text_lo = 0, text_hi = 0;
macho_vm_in_text (&mo, 0, &text_lo, &text_hi);
ut64 *candidates = R_NEWS (ut64, method_count);
if (!candidates) {
macho_free (&mo);
return false;
}
bool found = false;
int ptrsz = 8; // 64-bit
// Fast CodeRegistration-like scan across readable non-exec segments
for (int i = 0; i < mo.nsegs && !found; i++) {
MachSeg *s = &mo.segs[i];
bool is_data = (s->maxprot & 0x1) && !(s->maxprot & 0x4);
if (!is_data || s->filesize < (8 + 8)) continue;
const ut8 *buf = mo.file + mo.base + s->fileoff;
ut64 sz = s->filesize;
for (ut64 off = 0; off + 16 <= sz; off += 4) {
ut32 cnt32 = RD_LE32 (buf + off);
if (cnt32 < 1024 || cnt32 > (ut32)(method_count * 2)) continue;
ut64 arrptr = RD_LE64 (buf + off + 8);
// Sample within arrptr
ut32 good = 0, seen = 0;
ut32 sample = R_MIN ((ut32)256, cnt32);
for (ut32 k = 0; k < sample; k++) {
const ut8 *p = macho_vm_to_ptr (&mo, arrptr + (ut64)k * (ut64)ptrsz);
if (!p) break;
ut64 val = RD_LE64 (p);
if (val) seen++;
if (val >= text_lo && val < text_hi) good++;
}
if (seen >= 32 && good >= 32) {
// Read full array
memset (candidates, 0, method_count * sizeof (ut64));
for (size_t m = 0; m < method_count && m < cnt32; m++) {
const ut8 *p = macho_vm_to_ptr (&mo, arrptr + (ut64)m * (ut64)ptrsz);
if (!p) break;
ut64 val = RD_LE64 (p);
if (val >= text_lo && val < text_hi) candidates[m] = val;
}
found = true;
break;
}
}
}
if (!found) {
R_FREE (candidates);
macho_free (&mo);
return false;
}
*out_ptrs = candidates;
macho_free (&mo);
return true;
}
+1
View File
@@ -276,5 +276,6 @@ R_API Il2CppMethodDefinition *r2unity_get_method_definitions (R2UnityMetadata *m
R_API bool r2unity_find_method_pointers (R2UnityMetadata *meta, const char *exe_path, ut64 **out_ptrs);
R_API bool r2unity_read_method_pointers_at (R2UnityMetadata *meta, const char *exe_path, ut64 addr, size_t count, ut64 **out_ptrs);
R_API bool r2unity_find_method_pointers_macho (R2UnityMetadata *meta, const char *macho_path, ut64 **out_ptrs);
R_API bool r2unity_find_method_pointers_elf (R2UnityMetadata *meta, const char *elf_path, ut64 **out_ptrs);
#endif
+197
View File
@@ -0,0 +1,197 @@
// Fast Mach-O parser for r2unity
#include "lib.h"
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
static inline ut32 RD_LE32 (const ut8 *p) {
return ((ut32)p[0]) | ((ut32)p[1] << 8) | ((ut32)p[2] << 16) | ((ut32)p[3] << 24);
}
static inline ut32 RD_BE32 (const ut8 *p) {
return ((ut32)p[3]) | ((ut32)p[2] << 8) | ((ut32)p[1] << 16) | ((ut32)p[0] << 24);
}
static inline ut64 RD_LE64 (const ut8 *p) {
return ((ut64)p[0]) | ((ut64)p[1] << 8) | ((ut64)p[2] << 16) | ((ut64)p[3] << 24) |
((ut64)p[4] << 32) | ((ut64)p[5] << 40) | ((ut64)p[6] << 48) | ((ut64)p[7] << 56);
}
typedef struct {
char segname[16];
ut64 vmaddr;
ut64 vmsize;
ut64 fileoff;
ut64 filesize;
ut32 maxprot;
} MachSeg;
typedef struct {
ut8 *file;
ut64 filesize;
ut64 base;
ut32 ncmds;
ut64 cmd_off;
MachSeg segs[128];
int nsegs;
} MachO;
static bool macho_load (const char *path, MachO *mo) {
memset (mo, 0, sizeof (*mo));
int fd = open (path, O_RDONLY);
if (fd < 0) return false;
struct stat st;
if (fstat (fd, &st) != 0 || st.st_size <= 0) {
close (fd);
return false;
}
mo->filesize = (ut64) st.st_size;
mo->file = R_NEWS (ut8, mo->filesize);
if (!mo->file) {
close (fd);
return false;
}
ssize_t rd = read (fd, mo->file, (size_t) mo->filesize);
close (fd);
if (rd < 0 || (ut64) rd != mo->filesize) {
R_FREE (mo->file);
return false;
}
ut32 magic = RD_BE32 (mo->file);
ut64 off = 0;
if (magic == 0xcafebabe) {
ut32 nfat = RD_BE32 (mo->file + 4);
ut32 best = 0;
for (ut32 i = 0; i < nfat; i++) {
const ut8 *fa = mo->file + 8 + i * 20;
ut32 cputype = RD_BE32 (fa + 0);
if (cputype == 0x0100000c) { best = i; break; }
}
const ut8 *fa = mo->file + 8 + best * 20;
off = RD_BE32 (fa + 8);
}
mo->base = off;
const ut8 *p = mo->file + off;
ut32 mh_magic = RD_LE32 (p);
if (mh_magic != 0xFEEDFACF) {
R_FREE (mo->file);
return false;
}
ut32 ncmds = RD_LE32 (p + 0x10);
mo->ncmds = ncmds;
mo->cmd_off = off + 0x20;
ut64 co = mo->cmd_off;
for (ut32 i = 0; i < ncmds && (co + 8 <= mo->filesize); i++) {
ut32 cmd = RD_LE32 (mo->file + co);
ut32 cmdsize = RD_LE32 (mo->file + co + 4);
if (cmd == 0x19 && cmdsize >= 72) {
if (mo->nsegs < (int)(sizeof (mo->segs)/sizeof (mo->segs[0]))) {
MachSeg *s = &mo->segs[mo->nsegs++];
const ut8 *sp = mo->file + co + 8;
memcpy (s->segname, sp, 16);
s->vmaddr = RD_LE64 (sp + 16);
s->vmsize = RD_LE64 (sp + 24);
s->fileoff = RD_LE64 (sp + 32);
s->filesize = RD_LE64 (sp + 40);
s->maxprot = RD_LE32 (sp + 48);
}
}
co += cmdsize ? cmdsize : 8;
}
return true;
}
static void macho_free (MachO *mo) {
R_FREE (mo->file);
}
static inline bool macho_vm_in_text (MachO *mo, ut64 addr, ut64 *text_lo, ut64 *text_hi) {
ut64 lo = UT64_MAX, hi = 0;
for (int i = 0; i < mo->nsegs; i++) {
MachSeg *s = &mo->segs[i];
bool is_text = (s->maxprot & 0x4) || !strncmp (s->segname, "__TEXT", 6);
if (is_text) {
if (s->vmaddr < lo) lo = s->vmaddr;
if (s->vmaddr + s->vmsize > hi) hi = s->vmaddr + s->vmsize;
}
}
if (text_lo) *text_lo = lo;
if (text_hi) *text_hi = hi;
return (addr >= lo && addr < hi);
}
static inline const ut8 *macho_vm_to_ptr (MachO *mo, ut64 vmaddr) {
for (int i = 0; i < mo->nsegs; i++) {
MachSeg *s = &mo->segs[i];
if (vmaddr >= s->vmaddr && vmaddr < s->vmaddr + s->vmsize) {
ut64 delta = vmaddr - s->vmaddr;
if (delta < s->filesize) {
return mo->file + mo->base + s->fileoff + delta;
}
return NULL;
}
}
return NULL;
}
R_API bool r2unity_find_method_pointers_macho (R2UnityMetadata *meta, const char *macho_path, ut64 **out_ptrs) {
if (!meta || !macho_path || !out_ptrs) return false;
*out_ptrs = NULL;
MachO mo;
if (!macho_load (macho_path, &mo)) {
return false;
}
size_t method_count = meta->header.v24.methodsSize / sizeof (Il2CppMethodDefinition);
if (!method_count) {
macho_free (&mo);
return false;
}
ut64 text_lo = 0, text_hi = 0;
macho_vm_in_text (&mo, 0, &text_lo, &text_hi);
ut64 *candidates = R_NEWS (ut64, method_count);
if (!candidates) {
macho_free (&mo);
return false;
}
bool found = false;
int ptrsz = 8;
for (int i = 0; i < mo.nsegs && !found; i++) {
MachSeg *s = &mo.segs[i];
bool is_data = (s->maxprot & 0x1) && !(s->maxprot & 0x4);
if (!is_data || s->filesize < (8 + 8)) continue;
const ut8 *buf = mo.file + mo.base + s->fileoff;
ut64 sz = s->filesize;
for (ut64 off = 0; off + 16 <= sz; off += 4) {
ut32 cnt32 = RD_LE32 (buf + off);
if (cnt32 < 1024 || cnt32 > (ut32)(method_count * 2)) continue;
ut64 arrptr = RD_LE64 (buf + off + 8);
ut32 good = 0, seen = 0;
ut32 sample = R_MIN ((ut32)128, cnt32);
for (ut32 k = 0; k < sample; k++) {
const ut8 *p = macho_vm_to_ptr (&mo, arrptr + (ut64)k * (ut64)ptrsz);
if (!p) break;
ut64 val = RD_LE64 (p);
if (val) seen++;
if (val >= text_lo && val < text_hi) good++;
}
if (seen >= 8 && good >= 8) {
memset (candidates, 0, method_count * sizeof (ut64));
for (size_t m = 0; m < method_count && m < cnt32; m++) {
const ut8 *p = macho_vm_to_ptr (&mo, arrptr + (ut64)m * (ut64)ptrsz);
if (!p) break;
ut64 val = RD_LE64 (p);
if (val >= text_lo && val < text_hi) candidates[m] = val;
}
found = true;
break;
}
}
}
if (!found) {
R_FREE (candidates);
macho_free (&mo);
return false;
}
*out_ptrs = candidates;
macho_free (&mo);
return true;
}
+8 -1
View File
@@ -4,7 +4,7 @@
#include "lib/lib.h"
static void print_usage (const char *prog_name) {
fprintf (stderr, "Usage: %s [--json-one-line] [--quiet] [-l N] [--macho-fast] [--gmp-addr 0xADDR] [--gmp-count N] <executable> <path/to/global-metadata.dat>\n", prog_name);
fprintf (stderr, "Usage: %s [--json-one-line] [--quiet] [-l N] [--macho-fast] [--elf-fast] [--gmp-addr 0xADDR] [--gmp-count N] <executable> <path/to/global-metadata.dat>\n", prog_name);
}
int main (int argc, char *argv[]) {
@@ -14,6 +14,7 @@ int main (int argc, char *argv[]) {
ut64 gmp_addr = 0;
size_t gmp_count = 0;
bool macho_fast = false;
bool elf_fast = false;
int i = 1;
for (; i < argc; i++) {
if (!strcmp (argv[i], "--json-one-line")) {
@@ -28,6 +29,10 @@ int main (int argc, char *argv[]) {
macho_fast = true;
continue;
}
if (!strcmp (argv[i], "--elf-fast")) {
elf_fast = true;
continue;
}
if (!strcmp (argv[i], "-l") && i + 1 < argc) {
limit = strtol (argv[++i], NULL, 0);
continue;
@@ -75,6 +80,8 @@ int main (int argc, char *argv[]) {
has_ptrs = r2unity_read_method_pointers_at (meta, exe_path, gmp_addr, gmp_count, &method_ptrs);
} else if (macho_fast) {
has_ptrs = r2unity_find_method_pointers_macho (meta, exe_path, &method_ptrs);
} else if (elf_fast) {
has_ptrs = r2unity_find_method_pointers_elf (meta, exe_path, &method_ptrs);
} else {
has_ptrs = r2unity_find_method_pointers (meta, exe_path, &method_ptrs);
}