mirror of
https://github.com/radareorg/r2unity.git
synced 2026-11-07 06:36:00 -10:00
390 lines
11 KiB
C
390 lines
11 KiB
C
/* r2unity - MIT - Copyright 2025-2026 - pancake */
|
|
|
|
// Fast ELF parser for r2unity
|
|
#define R_LOG_ORIGIN "r2unity.elf"
|
|
#include "lib.h"
|
|
#include <string.h>
|
|
|
|
static inline void elf_write_word(ut8 *loc, ut64 val, bool is64) {
|
|
if (is64) {
|
|
r_write_le64 (loc, val);
|
|
} else {
|
|
r_write_le32 (loc, (ut32)val);
|
|
}
|
|
}
|
|
|
|
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));
|
|
size_t sz = 0;
|
|
e->file = (ut8 *)r_file_slurp (path, &sz);
|
|
if (!e->file || sz == 0) {
|
|
R_FREE (e->file);
|
|
return false;
|
|
}
|
|
e->filesize = (ut64)sz;
|
|
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 = r_read_le64 (p + 0x20);
|
|
ut16 phentsize = r_read_le16 (p + 0x36);
|
|
ut16 phnum = r_read_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 = r_read_le32 (ph + 0);
|
|
ut32 p_flags = r_read_le32 (ph + 4);
|
|
ut64 p_offset = r_read_le64 (ph + 8);
|
|
ut64 p_vaddr = r_read_le64 (ph + 16);
|
|
ut64 p_filesz = r_read_le64 (ph + 32);
|
|
ut64 p_memsz = r_read_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 = r_read_le32 (p + 0x1C);
|
|
ut16 phentsize = r_read_le16 (p + 0x2A);
|
|
ut16 phnum = r_read_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 = r_read_le32 (ph + 0);
|
|
ut32 p_offset = r_read_le32 (ph + 4);
|
|
ut32 p_vaddr = r_read_le32 (ph + 8);
|
|
ut32 p_filesz = r_read_le32 (ph + 16);
|
|
ut32 p_memsz = r_read_le32 (ph + 20);
|
|
ut32 p_flags = r_read_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;
|
|
}
|
|
|
|
/* Walk a candidate method pointer table at VA `arrptr`. Entries below text_lo
|
|
* are retried with base_vaddr added (RVA→VA fallback). On match, copy up to
|
|
* method_count entries into candidates and return true. */
|
|
static bool elf_probe_table(ElfImg *e, ut64 arrptr, ut32 count, int ptrsz, ut64 base_vaddr, ut64 text_lo, ut64 text_hi, ut32 min_seen, ut32 min_good, size_t method_count, ut64 *candidates) {
|
|
ut32 sample = R_MIN ((ut32)128, count);
|
|
ut32 good = 0, seen = 0;
|
|
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) {
|
|
return false;
|
|
}
|
|
ut64 val = (ptrsz == 8)? r_read_le64 (pp): (ut64)r_read_le32 (pp);
|
|
if (val) {
|
|
seen++;
|
|
}
|
|
if ((val >= text_lo && val < text_hi) || (val && base_vaddr != UT64_MAX && (val + base_vaddr) >= text_lo && (val + base_vaddr) < text_hi)) {
|
|
good++;
|
|
}
|
|
}
|
|
if (seen < min_seen || good < min_good) {
|
|
return false;
|
|
}
|
|
memset (candidates, 0, method_count * sizeof (ut64));
|
|
size_t tocopy = R_MIN ((size_t)count, 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)? r_read_le64 (pp): (ut64)r_read_le32 (pp);
|
|
ut64 abs = (val >= text_lo && val < text_hi)
|
|
? val
|
|
: ((val && base_vaddr != UT64_MAX)? val + base_vaddr: val);
|
|
if (abs >= text_lo && abs < text_hi) {
|
|
candidates[m] = abs;
|
|
in_text++;
|
|
}
|
|
}
|
|
return in_text >= 8;
|
|
}
|
|
|
|
R_API bool r2unity_find_method_pointers_elf(R2UnityMetadata *meta, const char *elf_path, ut64 **out_ptrs) {
|
|
R_RETURN_VAL_IF_FAIL (meta && elf_path && out_ptrs, false);
|
|
*out_ptrs = NULL;
|
|
ElfImg e;
|
|
if (!elf_load (elf_path, &e)) {
|
|
return false;
|
|
}
|
|
size_t method_count = (ut64)meta->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;
|
|
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? r_read_le64 (d + off): (ut64)r_read_le32 (d + off);
|
|
ut64 val = e.is64? r_read_le64 (d + off + 8): (ut64)r_read_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;
|
|
}
|
|
}
|
|
}
|
|
const ut64 base = (base_vaddr != UT64_MAX)? base_vaddr: 0;
|
|
const ut64 type_mask = e.is64? 0xffffffffULL: 0xffULL;
|
|
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? r_read_le64 (rp + 0): (ut64)r_read_le32 (rp + 0);
|
|
ut64 r_info = e.is64? r_read_le64 (rp + 8): (ut64)r_read_le32 (rp + 4);
|
|
ut64 r_addend = e.is64? r_read_le64 (rp + 16): (ut64)r_read_le32 (rp + 8);
|
|
ut64 type = r_info & type_mask;
|
|
bool is_relative = (type == 8) || (type == 23) || (type == 1027);
|
|
if (!is_relative) {
|
|
continue;
|
|
}
|
|
ut8 *loc = (ut8 *)elf_vm_to_ptr (&e, r_offset);
|
|
if (loc) {
|
|
elf_write_word (loc, base + r_addend, e.is64);
|
|
}
|
|
}
|
|
}
|
|
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? r_read_le64 (rp + 0): (ut64)r_read_le32 (rp + 0);
|
|
ut64 r_info = e.is64? r_read_le64 (rp + 8): (ut64)r_read_le32 (rp + 4);
|
|
ut64 type = r_info & type_mask;
|
|
bool is_relative = (type == 8) || (type == 23) || (type == 1027);
|
|
if (!is_relative) {
|
|
continue;
|
|
}
|
|
ut8 *loc = (ut8 *)elf_vm_to_ptr (&e, r_offset);
|
|
if (!loc) {
|
|
continue;
|
|
}
|
|
ut64 add = e.is64? r_read_le64 (loc): (ut64)r_read_le32 (loc);
|
|
elf_write_word (loc, base + add, e.is64);
|
|
}
|
|
}
|
|
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 = r_read_le64 (rp);
|
|
if ((R & 1ULL) == 0) {
|
|
ut64 addr = R;
|
|
ut8 *loc = (ut8 *)elf_vm_to_ptr (&e, addr);
|
|
if (loc) {
|
|
elf_write_word (loc, base + r_read_le64 (loc), true);
|
|
}
|
|
curr = addr + 8;
|
|
} else {
|
|
ut64 bitmap = R >> 1;
|
|
for (int bit = 0; bit < 63; bit++) {
|
|
if (! (bitmap & (1ULL << bit))) {
|
|
continue;
|
|
}
|
|
ut64 addr = curr + (ut64) (bit + 1) * 8ULL;
|
|
ut8 *loc = (ut8 *)elf_vm_to_ptr (&e, addr);
|
|
if (loc) {
|
|
elf_write_word (loc, base + r_read_le64 (loc), true);
|
|
}
|
|
}
|
|
curr += 8ULL * 63ULL;
|
|
}
|
|
}
|
|
}
|
|
// Scan for [count][ptr]
|
|
/* expected number of entries in a possible method pointer array
|
|
* use method_count computed earlier (methodsSize / sizeof (Il2CppMethodDefinition))
|
|
*/
|
|
ut32 expected = (ut32)R_MAX ((ut64)64, (ut64)method_count);
|
|
/* Pass 0: CodeRegistration-shape (pair of count/ptr tuples), loose floor.
|
|
* Pass 1: generic {count32, ptr} with expected-count bounds and stricter
|
|
* sample-fraction thresholds. */
|
|
for (int pass = 0; pass < 2 && !found; pass++) {
|
|
const ut32 min_count = (pass == 0)? 32: 64;
|
|
const ut64 min_secsize = (pass == 0)
|
|
? (ut64) (16 + (ut64)ptrsz * 2)
|
|
: (ut64) (8 + ptrsz);
|
|
const ut64 min_step = (pass == 0)
|
|
? (ut64) (8 + ptrsz) * 3
|
|
: (ut64) (8 + ptrsz);
|
|
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 < min_secsize) {
|
|
continue;
|
|
}
|
|
const ut8 *buf = e.file + s->offset;
|
|
for (ut64 off = 0; off + min_step <= s->filesz; off += 4) {
|
|
ut32 cnt = r_read_le32 (buf + off);
|
|
if (cnt < min_count) {
|
|
continue;
|
|
}
|
|
if (pass == 0) {
|
|
/* Require a second plausible count immediately after the ptr. */
|
|
ut32 cnt2 = r_read_le32 (buf + off + (ut64) (8 + ptrsz));
|
|
if (cnt2 < 16) {
|
|
continue;
|
|
}
|
|
} else if (expected && (cnt > expected * 2 || cnt < expected / 2)) {
|
|
continue;
|
|
}
|
|
ut64 arrptr = (ptrsz == 8)
|
|
? r_read_le64 (buf + off + 8)
|
|
: (ut64)r_read_le32 (buf + off + 4);
|
|
ut32 sample = R_MIN ((ut32)128, cnt);
|
|
ut32 min_seen = (pass == 0)? 8: sample / 2;
|
|
ut32 min_good = (pass == 0)? 8: (sample * 3) / 4;
|
|
if (elf_probe_table (&e, arrptr, cnt, ptrsz, base_vaddr, text_lo, text_hi, min_seen, min_good, method_count, candidates)) {
|
|
R_LOG_DEBUG ("[elf] pass=%d arrptr=0x%" PFMT64x " cnt=%u", pass, arrptr, cnt);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!found) {
|
|
R_FREE (candidates);
|
|
elf_free (&e);
|
|
return false;
|
|
}
|
|
*out_ptrs = candidates;
|
|
elf_free (&e);
|
|
return true;
|
|
}
|