diff --git a/src/lib/elf.c b/src/lib/elf.c new file mode 100644 index 0000000..09e559d --- /dev/null +++ b/src/lib/elf.c @@ -0,0 +1,246 @@ +// Fast ELF parser for r2unity +#include "lib.h" +#include +#include +#include +#include + +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; +} + diff --git a/src/lib/lib.c b/src/lib/lib.c index f763b20..fea894a 100644 --- a/src/lib/lib.c +++ b/src/lib/lib.c @@ -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; -} diff --git a/src/lib/lib.h b/src/lib/lib.h index 1e8b983..8972800 100644 --- a/src/lib/lib.h +++ b/src/lib/lib.h @@ -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 diff --git a/src/lib/macho.c b/src/lib/macho.c new file mode 100644 index 0000000..62a9747 --- /dev/null +++ b/src/lib/macho.c @@ -0,0 +1,197 @@ +// Fast Mach-O parser for r2unity +#include "lib.h" +#include +#include +#include +#include + +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; +} diff --git a/src/main.c b/src/main.c index 036e1ef..fc73030 100644 --- a/src/main.c +++ b/src/main.c @@ -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] \n", prog_name); + fprintf (stderr, "Usage: %s [--json-one-line] [--quiet] [-l N] [--macho-fast] [--elf-fast] [--gmp-addr 0xADDR] [--gmp-count N] \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); }