1.查看一共有那些dll.
2.dump
import frida
import sys
import json
import os
from pathlib import Path
ROOT_OUTPUT_DIR = Path(r"E:\work\JigsawChaiBao\classInfo")
ROOT_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
ASSEMBLIES = [
"Assembly-CSharp",
"Coffee.UIEffect",
"Coffee.UIParticle",
"DOTween",
"Facebook.Unity.Android",
"Facebook.Unity.Settings",
"Facebook.Unity",
"Firebase.App",
"Firebase.Crashlytics",
"Firebase.Platform",
"Gilzoide.SqliteNet",
"Google.FlatBuffers",
"Google.Play.Common",
"Newtonsoft.Json",
"System.Core",
"System.Data",
"System.Numerics",
"System.Runtime.Serialization",
"System",
"Unity.TextMeshPro",
"Unity.Timeline",
"UnityEngine.AndroidJNIModule",
"UnityEngine.AnimationModule",
"UnityEngine.AssetBundleModule",
"UnityEngine.AudioModule",
"UnityEngine.CoreModule",
"UnityEngine.DirectorModule",
"UnityEngine.IMGUIModule",
"UnityEngine.InputLegacyModule",
"UnityEngine.ParticleSystemModule",
"UnityEngine.Physics2DModule",
"UnityEngine.PhysicsModule",
"UnityEngine.PropertiesModule",
"UnityEngine.SpriteShapeModule",
"UnityEngine.TextCoreFontEngineModule",
"UnityEngine.TextRenderingModule",
"UnityEngine.UI",
"UnityEngine.UIElementsModule",
"UnityEngine.UIModule",
"UnityEngine.UnityWebRequestModule",
"UnityEngine.VideoModule",
"com.beatles.unity.ui",
"com.learnings.download-unity",
"com.learnings.unity.assetbundle",
"com.learnings.unity.log",
"com.learnings.unity.nativeutil-unity",
"com.learnings.unity.storage.flatbuffers",
"com.learnings.unity.unikit.runtime",
"i2",
"mscorlib",
"spine-csharp",
"spine-timeline",
"spine-unity"
]
PACKAGE_NAME = "jigsawcard"
JS_FILE = "dump_class_il2cpp.js"
TYPE_MAP = {
"System.Void": "void",
"System.Boolean": "bool",
"System.Byte": "uint8",
"System.SByte": "int8",
"System.Int16": "int16",
"System.UInt16": "uint16",
"System.Int32": "int32",
"System.UInt32": "uint32",
"System.Int64": "int64",
"System.UInt64": "uint64",
"System.Single": "float",
"System.Double": "double",
"System.IntPtr": "void *",
"System.UIntPtr": "void *",
"System.String": "void *",
"System.Object": "void *",
}
def to_c_type(il2cpp_type: str) -> str:
if not il2cpp_type:
return "void *"
t = il2cpp_type.strip()
if "<" in t or ">" in t:
return "void *"
if t.endswith("[]"):
return "void *"
if t.endswith("&"):
base = t[:-1]
return to_c_type(base) + " *"
return TYPE_MAP.get(t, "void *")
def escape_name(name: str) -> str:
if not name:
return ""
return (
name.replace("<", "_")
.replace(">", "_")
.replace(" ", "_")
.replace(":", "_")
.replace(",", "_")
)
def safe_dir_name(name: str) -> str:
bad = '<>:"/\\|?*'
for ch in bad:
name = name.replace(ch, "_")
return name
def build_ida_prototype(item: dict) -> str:
func_name = escape_name(item.get("name", "")) or "sub_unknown"
ret = to_c_type(item.get("returnType", ""))
params = item.get("params", []) or []
is_static = bool(item.get("isStatic", False))
c_params = []
if not is_static:
c_params.append("void *this")
for i, p in enumerate(params):
if isinstance(p, dict):
p_name = p.get("name", f"arg{i}")
p_type = p.get("type", "")
else:
p_name = f"arg{i}"
p_type = ""
ctype = to_c_type(p_type)
p_name = escape_name(p_name) or f"arg{i}"
c_params.append(f"{ctype} {p_name}")
if not c_params:
c_params = ["void"]
return f"{ret} {func_name}({', '.join(c_params)});"
def write_ida_script(dump_data, ida_path: Path):
with open(ida_path, "w", encoding="utf-8") as f:
f.write("import idaapi\n")
f.write("import idc\n")
f.write("import ida_funcs\n")
f.write("import ida_typeinf\n\n")
f.write("base = idaapi.get_imagebase()\n")
f.write("renamed = 0\n")
f.write("typed = 0\n")
f.write("fields_applied = 0\n\n")
for item in dump_data:
rva = item.get("rva")
name = item.get("name")
if rva is None or not name:
continue
safe_name = escape_name(name)
f.write(f"ea = base + 0x{int(rva):x}\n")
f.write("fn = ida_funcs.get_func(ea)\n")
f.write("if fn:\n")
# =========================
# 1. rename function
# =========================
f.write(f" if idc.set_name(ea, \"{safe_name}\", idc.SN_NOWARN):\n")
f.write(" renamed += 1\n")
# =========================
# 2. set prototype
# =========================
proto = build_ida_prototype(item).replace("\\", "\\\\").replace("\"", "\\\"")
f.write(" try:\n")
f.write(f" proto = \"{proto}\"\n")
f.write(" if idc.SetType(ea, proto):\n")
f.write(" typed += 1\n")
f.write(" except:\n")
f.write(" pass\n")
# =========================
# 3. fields comment attach
# =========================
fields = item.get("fields", [])
if fields:
f.write(" # ===== Fields =====\n")
for fd in fields:
fname = escape_name(fd.get("name", ""))
ftype = fd.get("type", "")
offset = fd.get("offset", 0)
f.write(
f" # {fname} : {ftype} @0x{int(offset):x}\n"
)
f.write(" # ==================\n")
f.write("\n")
f.write("print('[+] renamed =', renamed)\n")
f.write("print('[+] typed =', typed)\n")
def save_dump_result(assembly_name: str, payload: dict):
out_dir = ROOT_OUTPUT_DIR / safe_dir_name(assembly_name)
out_dir.mkdir(parents=True, exist_ok=True)
json_path = out_dir / "il2cpp_dump_v2.json"
with open(json_path, "w", encoding="utf-8") as f:
json.dump(payload["data"], f, indent=2, ensure_ascii=False)
ida_path = out_dir / "ida_apply_il2cpp_dump_v2.py"
write_ida_script(payload["data"], ida_path)
print(f"[+] Saved JSON: {json_path}")
print(f"[+] Saved IDA : {ida_path}")
def main():
device = frida.get_usb_device()
session = device.attach(PACKAGE_NAME)
with open(JS_FILE, "r", encoding="utf-8") as f:
script_code = f.read()
script = session.create_script(script_code)
script.load()
print("[*] Attached and script loaded.")
for asm in ASSEMBLIES:
print(f"\n[*] Dumping assembly: {asm}")
try:
payload = script.exports_sync.dumpassembly(asm)
except Exception as e:
print(f"[!] RPC failed for {asm}: {e}")
continue
if not payload.get("ok"):
print(f"[!] Dump failed: {asm}")
print(f" error = {payload.get('error')}")
continue
print(f"[+] OK: input={payload.get('input')} used={payload.get('usedName')} count={payload.get('count')}")
save_dump_result(asm, payload)
print("\n[+] All done.")
session.detach()
if __name__ == "__main__":
main()
脚本写法
import "frida-il2cpp-bridge";
type DumpParam = {
name: string;
type: string;
};
type DumpField = {
name: string;
type: string;
isStatic: boolean;
offset: number;
};
type DumpItem = {
module: string;
namespace: string;
class: string;
method: string;
name: string;
rva: number;
returnType: string;
params: DumpParam[];
isStatic: boolean;
fields: DumpField[];
assembly: string;
image: string;
};
type DumpOkResult = {
ok: true;
input: string;
usedName: string;
imageName: string;
count: number;
data: DumpItem[];
};
type DumpFailResult = {
ok: false;
input: string;
error: string;
stack: string;
};
type DumpResult = DumpOkResult | DumpFailResult;
function sanitizeAssemblyName(name: string): string {
if (!name) return "";
return name.endsWith(".dll") ? name.slice(0, -4) : name;
}
function errorToString(e: unknown): { error: string; stack: string } {
if (e instanceof Error) {
return {
error: e.message || String(e),
stack: e.stack || ""
};
}
return {
error: String(e),
stack: ""
};
}
rpc.exports = {
dumpassembly(name: string): Promise<DumpResult> {
return new Promise((resolve) => {
try {
Il2Cpp.perform(() => {
try {
const result: DumpItem[] = [];
const exported = new Set<string>();
const rawName = String(name || "").trim();
const candidates = [...new Set([
rawName,
sanitizeAssemblyName(rawName),
rawName + ".dll"
].filter(Boolean))];
let assembly: Il2Cpp.Assembly | null = null;
let usedName: string | null = null;
for (const n of candidates) {
try {
const a = Il2Cpp.domain.assembly(n);
if (a) {
assembly = a;
usedName = n;
break;
}
} catch (_) {}
}
if (!assembly || !usedName) {
resolve({
ok: false,
input: rawName,
error: "assembly not found",
stack: ""
});
return;
}
const image = assembly.image;
image.classes.forEach((c: Il2Cpp.Class) => {
// =========================
// 1. Fields dump
// =========================
const fields: DumpField[] = [];
try {
c.fields.forEach((f: Il2Cpp.Field) => {
let typeName = "";
try {
typeName = String(f.type?.name ?? "");
} catch (_) {}
let offset = 0;
try {
offset = Number(f.offset ?? 0);
} catch (_) {}
let isStatic = false;
try {
isStatic = !!f.isStatic;
} catch (_) {}
fields.push({
name: String(f.name ?? ""),
type: typeName,
isStatic,
offset
});
});
} catch (_) {}
// =========================
// 2. Methods dump
// =========================
c.methods.forEach((m: Il2Cpp.Method) => {
try {
if (!m.virtualAddress) return;
const va = m.virtualAddress;
const module = Process.findModuleByAddress(va);
if (!module) return;
const key = va.toString();
if (exported.has(key)) return;
exported.add(key);
const rva = va.sub(module.base);
const cleanClass = String(c.name ?? "").replace(/[^a-zA-Z0-9_]/g, "_");
const cleanMethod = String(m.name ?? "").replace(/[^a-zA-Z0-9_]/g, "_");
const newName = `${cleanClass}__${cleanMethod}`;
const params: DumpParam[] = [];
try {
for (const p of m.parameters) {
params.push({
name: String(p.name ?? ""),
type: String(p.type?.name ?? "")
});
}
} catch (_) {}
let returnType = "";
try {
returnType = String(m.returnType?.name ?? "");
} catch (_) {}
let isStatic = false;
try {
isStatic = !!m.isStatic;
} catch (_) {}
result.push({
module: String(module.name ?? ""),
namespace: String(c.namespace ?? ""),
class: String(c.name ?? ""),
method: String(m.name ?? ""),
name: newName,
rva: Number(rva),
returnType,
params,
isStatic,
fields, // ✅ 加在 class 级别
assembly: String(image.name ?? ""),
image: String(image.name ?? "")
});
} catch (_) {}
});
});
resolve({
ok: true,
input: rawName,
usedName: String(usedName),
imageName: String(image.name ?? ""),
count: result.length,
data: result
});
} catch (e: unknown) {
const err = errorToString(e);
resolve({
ok: false,
input: String(name || ""),
error: err.error,
stack: err.stack
});
}
});
} catch (e: unknown) {
const err = errorToString(e);
resolve({
ok: false,
input: String(name || ""),
error: err.error,
stack: err.stack
});
}
});
}
};
dump so
import frida
PACKAGE = "jigsawcard"
JS_FILE = "dump_so_memory.js"
OUT_FILE = "libil2cpp_dump.so"
def main():
device = frida.get_usb_device()
session = device.attach(PACKAGE)
file = open(OUT_FILE, "wb")
def on_message(message, data):
if message["type"] == "send":
payload = message["payload"]
if payload["type"] == "chunk":
file.write(data) # ✅ 真正二进制在 data 里
elif payload["type"] == "info":
print("[*] base:", payload["base"])
print("[*] size:", payload["size"])
elif payload["type"] == "done":
print("[+] dump finished")
file.close()
session.detach()
with open(JS_FILE, "r", encoding="utf-8") as f:
script = session.create_script(f.read())
script.on("message", on_message)
script.load()
print("[*] dumping...")
script.exports_sync.dumpso()
import sys
sys.stdin.read()
if __name__ == "__main__":
main()
ts
rpc.exports = {
dumpso() {
const mod = Process.findModuleByName("libil2cpp.so");
if (!mod) return { ok: false };
const base = mod.base;
const size = mod.size;
const chunk = 0x10000;
send({ type: "info", base: base.toString(), size });
for (let off = 0; off < size; off += chunk) {
const len = Math.min(chunk, size - off);
const ptr = base.add(off);
const buf = Memory.readByteArray(ptr, len);
// ✅ 正确:第二个参数传 binary
send({
type: "chunk",
offset: off,
size: len
}, buf);
}
send({ type: "done" });
return { ok: true };
}
};