#!/usr/bin/env python3
"""Read what SyncReply actually compares for the device-lock check.

Hooks Gu3.DeviceHelper::GetDeviceHash(arg) and dumps arg + return value, plus
the devhash the client received from us. IL2CPP strings are
System.String: [0]=klass [1]=monitor [2]=int32 length [3]=utf16 chars.
"""
import json, sys, time, frida

ADDRS = json.load(open("/root/ants/devhash_addr.json"))

JS = r"""
var addrs = %s;
function readCSharpString(p) {
    if (p.isNull()) return "<null>";
    try {
        var len = p.add(8).readS32();
        if (len < 0 || len > 4096) return "<len=" + len + ">";
        return p.add(12).readUtf16String(len);
    } catch (e) { return "<err:" + e + ">"; }
}
function install() {
    var m = Process.findModuleByName("libil2cpp.so");
    if (m === null) return false;
    var base = m.base;
    send({t:"info", m:"libil2cpp @ " + base});
    for (var name in addrs) {
        (function (name, rva) {
            try {
                Interceptor.attach(base.add(rva), {
                    onEnter: function (args) {
                        this.name = name;
                        // GetDeviceHash(int) -> arg0 is the int; instance method
                        // so ecx/this is implicit. args[0] = first stack/reg arg.
                        this.a0 = args[0];
                        this.a1 = args[1];
                    },
                    onLeave: function (rv) {
                        var out = readCSharpString(rv);
                        var a0s = this.a0.isNull() ? "<null>" : readCSharpString(this.a0);
                        send({t:"ret", n:this.name,
                              a0:"" + this.a0, a0s:a0s, a1:"" + this.a1,
                              ret:out});
                    }
                });
                send({t:"info", m:"hooked " + name});
            } catch (e) { send({t:"err", m:name + ": " + e}); }
        })(name, addrs[name]);
    }
    return true;
}
if (!install()) {
    var iv = setInterval(function(){ if (install()) clearInterval(iv); }, 200);
}
"""

def main():
    dev = frida.get_device_manager().add_remote_device("127.0.0.1:27042")
    pid = dev.spawn(["se.foglo.svt"])
    session = dev.attach(pid)
    script = session.create_script(JS % json.dumps(ADDRS))
    def on_message(msg, data):
        if msg["type"] != "send":
            print("!!", msg); return
        p = msg["payload"]
        if p["t"] == "ret":
            print("[%6.2fs] %s" % (time.time()-t0, p["n"]))
            print("          arg0=%s  arg0_as_string=%r" % (p["a0"], p.get("a0s")))
            print("          RET = %r" % p["ret"])
        else:
            print("[%s] %s" % (p["t"], p.get("m")))
    script.on("message", on_message)
    script.load()
    t0 = time.time()
    dev.resume(pid)
    time.sleep(int(sys.argv[1]) if len(sys.argv) > 1 else 30)

if __name__ == "__main__":
    main()
