#!/usr/bin/env python3
"""Read the live device-hash comparison in SyncReply.

The client parks behind OnPlayingOnAnotherDevice at the moment SyncReply runs.
SyncReply's disassembly is: GetDeviceHash(...) -> Substring -> op_Inequality
-> OnPlayingOnAnotherDevice. So it compares our echoed devhash against a locally
computed one. This hooks GetDeviceHash to read its argument and return value,
so we learn what the client actually expects instead of guessing.

IL2CPP System.String layout on x86 (32-bit):
  +0x8 = length (int32), +0xC = UTF-16 char data.
"""
import json
import sys
import time
import frida

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

JS = r"""
var addrs = %s;
var base = null;

function readCSString(ptr) {
    if (ptr === null || ptr.isNull()) return "<null>";
    try {
        var len = ptr.add(8).readS32();
        if (len < 0 || len > 512) return "<len="+len+">";
        return ptr.add(12).readUtf16String(len);
    } catch (e) { return "<err>"; }
}

function install() {
    var m = Process.findModuleByName("libil2cpp.so");
    if (m === null) return false;
    base = m.base;
    for (var name in addrs) {
        (function(name, rva){
            Interceptor.attach(base.add(rva), {
                onEnter: function(a){
                    this.name = name;
                    this.arg1 = a[1];
                },
                onLeave: function(ret){
                    var args = "";
                    try { args = readCSString(this.arg1); } catch(e){ args="<e>"; }
                    var rv = readCSString(ret);
                    send({t:"ret", n:this.name, arg:args, ret:rv});
                }
            });
        })(name, addrs[name]);
    }
    send({t:"info", m:"hooks installed: "+Object.keys(addrs).length+" @ "+base});
    return true;
}
if (!install()) {
    var iv = setInterval(function(){ if (install()) clearInterval(iv); }, 100);
}
"""


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(ADDR))

    def on_message(msg, data):
        if msg["type"] != "send":
            print("!!", msg.get("description", msg)); return
        p = msg["payload"]
        if p["t"] == "ret":
            print("%7.2fs  %s  arg=%r  ret=%r" %
                  (time.time() - t0, p["n"], p["arg"], p["ret"]))
        else:
            print("[info]", p["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 40)


if __name__ == "__main__":
    main()
