"""
Dump AnyDesk's main module from memory — uses Frida's module info directly.
"""
import frida
import sys
import subprocess
import time

AGENT = r"""
var mainMod = Process.enumerateModules()[0];
send({t:'info', msg: 'Module: ' + mainMod.name + ' @ ' + mainMod.base + ' size: ' + mainMod.size});

// Check if the base has a PE header
var mz = mainMod.base.readU16();
send({t:'info', msg: 'MZ check: 0x' + mz.toString(16) + (mz === 0x5A4D ? ' (valid)' : ' (invalid)')});

if (mz === 0x5A4D) {
    var peOff = mainMod.base.add(0x3C).readU32();
    var peSig = mainMod.base.add(peOff).readU32();
    send({t:'info', msg: 'PE sig: 0x' + peSig.toString(16)});

    var numSections = mainMod.base.add(peOff + 6).readU16();
    var sizeOfImage = mainMod.base.add(peOff + 0x50).readU32();
    var sizeOfHeaders = mainMod.base.add(peOff + 0x54).readU32();
    send({t:'info', msg: 'Sections: ' + numSections + ', SizeOfImage: ' + sizeOfImage + ', HeaderSize: ' + sizeOfHeaders});

    // Read section table to find real extent
    var optHeaderSize = mainMod.base.add(peOff + 0x14).readU16();
    var sectionTableOff = peOff + 0x18 + optHeaderSize;
    var maxEnd = 0;
    for (var i = 0; i < numSections; i++) {
        var secBase = mainMod.base.add(sectionTableOff + i * 40);
        var nameBytes = secBase.readByteArray(8);
        var nameArr = new Uint8Array(nameBytes);
        var name = '';
        for (var j = 0; j < 8; j++) { if (nameArr[j] === 0) break; name += String.fromCharCode(nameArr[j]); }
        var virtualSize = secBase.add(8).readU32();
        var virtualAddr = secBase.add(12).readU32();
        var rawSize = secBase.add(16).readU32();
        var chars = secBase.add(36).readU32();
        var end = virtualAddr + virtualSize;
        if (end > maxEnd) maxEnd = end;
        send({t:'section', name: name.replace(/\0/g,''), va: '0x'+virtualAddr.toString(16),
              vs: virtualSize, rs: rawSize, chars: '0x'+chars.toString(16)});
    }
    send({t:'info', msg: 'Max section end: 0x' + maxEnd.toString(16) + ' (' + maxEnd + ' bytes)'});
}

// Dump the full module
var dumpSize = mainMod.size;
send({t:'info', msg: 'Dumping ' + dumpSize + ' bytes...'});

var chunkSize = 512 * 1024; // 512KB chunks
var offset = 0;
while (offset < dumpSize) {
    var readSize = Math.min(chunkSize, dumpSize - offset);
    try {
        var data = mainMod.base.add(offset).readByteArray(readSize);
        send({t:'chunk', offset: offset}, data);
    } catch(e) {
        send({t:'info', msg: 'Failed at offset 0x' + offset.toString(16) + ': ' + e});
        var zeros = new ArrayBuffer(readSize);
        send({t:'chunk', offset: offset}, zeros);
    }
    offset += readSize;
}
send({t:'done', size: dumpSize});
"""

def find_pid():
    out = subprocess.check_output(
        ['tasklist', '/FI', 'IMAGENAME eq AnyDesk.exe', '/FO', 'CSV', '/NH'],
        text=True, stderr=subprocess.DEVNULL)
    for line in out.strip().split('\n'):
        if 'AnyDesk' in line:
            parts = line.strip().strip('"').split('","')
            if len(parts) >= 2:
                return int(parts[1].strip('"'))
    return None

def main():
    print("=" * 60)
    print("  AnyDesk Memory Dumper v2")
    print("=" * 60)

    pid = find_pid()
    if not pid:
        print("[!] AnyDesk not running")
        sys.exit(1)

    print(f"[+] Attaching to PID {pid}")

    dump_data = bytearray()
    dump_size = [0]
    done = [False]

    def on_msg(msg, data):
        if msg['type'] == 'send':
            p = msg['payload']
            if p['t'] == 'info':
                print(f"  [*] {p['msg']}")
            elif p['t'] == 'section':
                print(f"  [SEC] {p['name']:8s} VA={p['va']} VSize={p['vs']:>10,} RawSize={p['rs']:>10,} Chars={p['chars']}")
            elif p['t'] == 'chunk' and data:
                offset = p['offset']
                if len(dump_data) < offset + len(data):
                    dump_data.extend(b'\x00' * (offset + len(data) - len(dump_data)))
                dump_data[offset:offset+len(data)] = data
                mb = (offset + len(data)) / 1024 / 1024
                print(f"  [DUMP] {mb:.1f} MB...", end='\r')
            elif p['t'] == 'done':
                dump_size[0] = p['size']
                done[0] = True
                print(f"\n  [+] Dump complete: {p['size']:,} bytes")
        elif msg['type'] == 'error':
            print(f"  [!] {msg['description']}")

    session = frida.attach(pid)
    script = session.create_script(AGENT, runtime='v8')
    script.on('message', on_msg)
    script.load()

    for _ in range(1200):  # 2 min timeout
        if done[0]: break
        time.sleep(0.1)

    script.unload()
    session.detach()

    if dump_data:
        outpath = f"anydesk_dump_{pid}.bin"
        with open(outpath, 'wb') as f:
            f.write(dump_data)
        print(f"\n[+] Saved: {outpath} ({len(dump_data):,} bytes)")
        print(f"[+] Load in Ghidra: mcp__ghidra__analyze_binary with this file")
    else:
        print("[!] No data dumped")

if __name__ == '__main__':
    main()
