mirror of
https://github.com/ApfelTeeSaft/OpenRoyale.git
synced 2026-08-26 19:33:32 +00:00
65 lines
3.1 KiB
Bash
65 lines
3.1 KiB
Bash
#!/usr/bin/env bash
|
|
# analyze_libg_callgraph.sh - build the FULL call graph of libg.so from its 60 JNI roots using
|
|
# radare2 in a SINGLE session (analyzing each root with `af` then `pdf`), then parse the disassembly
|
|
# by pdf's "/ <size>: sym.<name>" headers. This is ~1000x faster than reloading the binary per
|
|
# function (the whole 60-root sweep runs in ~2s instead of ~80min)
|
|
#
|
|
# Requires: radare2 + python3. Reads original_reference/native_libs_original/libg.so.
|
|
set -euo pipefail
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJ="$(cd "$HERE/../.." && pwd)"
|
|
LIB="$PROJ/original_reference/native_libs_original/libg.so"
|
|
OUT="$PROJ/analysis/rawdata/libg_callgraph.txt"
|
|
command -v r2 >/dev/null || { echo "radare2 not installed (apt install radare2)"; exit 2; }
|
|
[ -f "$LIB" ] || { echo "libg.so missing (run extract_apk.sh)"; exit 2; }
|
|
|
|
python3 - "$LIB" "$OUT" <<'PY'
|
|
import subprocess, re, sys
|
|
lib, out = sys.argv[1], sys.argv[2]
|
|
# JNI root addresses
|
|
rab = subprocess.run(["rabin2","-E",lib], capture_output=True, text=True).stdout.splitlines()
|
|
roots = {}
|
|
for l in rab:
|
|
if "Java_com_supercell_titan_" in l:
|
|
p = l.split()
|
|
roots[p[-1].split("Java_com_supercell_titan_")[-1]] = p[2]
|
|
# one r2 session: seek+af+pdf each root
|
|
cmds = "".join(f"s {a}; af; pdf;" for a in roots.values())
|
|
o = subprocess.run(["r2","-q","-e","scr.color=0","-e","asm.arch=arm","-e","asm.bits=16",
|
|
"-c", cmds, lib], capture_output=True, text=True, timeout=600).stdout.splitlines()
|
|
hdr = re.compile(r'^/ (\d+): sym\.Java_com_supercell_titan_(\S+?) ')
|
|
call = re.compile(r'\b(bl|blx) (sym\.imp\.[A-Za-z0-9_:]+|0x[0-9a-f]+)')
|
|
blocks, cur = {}, None
|
|
for ln in o:
|
|
m = hdr.match(ln)
|
|
if m:
|
|
cur = {"size": m.group(1), "callees": {}}; blocks[m.group(2)] = cur
|
|
elif cur:
|
|
cm = call.search(ln)
|
|
if cm:
|
|
t = cm.group(2); t = t if t.startswith("sym.imp.") else "sub_"+t[2:]
|
|
cur["callees"][t] = cur["callees"].get(t,0)+1
|
|
# any root whose af merged/anon -> analyze standalone
|
|
for name, a in roots.items():
|
|
if name in blocks: continue
|
|
oo = subprocess.run(["r2","-q","-e","scr.color=0","-e","asm.arch=arm","-e","asm.bits=16",
|
|
"-c", f"s {a}; af; afi~size:; pdf", lib], capture_output=True, text=True).stdout.splitlines()
|
|
size = next((x.split()[1] for x in oo if x.strip().startswith("size:")), "?")
|
|
cs = {}
|
|
for l in oo:
|
|
cm = call.search(l)
|
|
if cm:
|
|
t = cm.group(2); t = t if t.startswith("sym.imp.") else "sub_"+t[2:]
|
|
cs[t] = cs.get(t,0)+1
|
|
blocks[name] = {"size": size, "callees": cs}
|
|
L = ["# libg.so call graph from all 60 JNI roots (radare2 single-session af+pdf).",
|
|
"# format: <jni_method> @ <addr> size=<bytes> -> callees (sub_<addr> internal, sym.imp.* imports, xN)"]
|
|
for name in sorted(blocks):
|
|
b = blocks[name]
|
|
cs = " ".join(f"{k}(x{v})" for k,v in sorted(b["callees"].items(), key=lambda kv:-kv[1]))
|
|
L.append(f"{name:<42} @ {roots.get(name,'?')} size={b['size']}")
|
|
L.append(f" -> {cs or '<none>'}")
|
|
open(out,"w").write("\n".join(L)+"\n")
|
|
print(f"[+] wrote {out} ({len(blocks)}/60 roots)")
|
|
PY
|