// Service-side Frida hooks for NmxSvc.exe. // Usage: frida -p -l analysis/frida/nmxsvc-trace.js const maxDump = 4096; const installed = {}; function now() { return new Date().toISOString(); } function emit(event) { event.time = now(); console.log(JSON.stringify(event)); } function toHex(arrayBuffer) { if (arrayBuffer === null || arrayBuffer === "") return ""; const bytes = new Uint8Array(arrayBuffer); const out = []; for (let i = 0; i < bytes.length; i++) out.push(bytes[i].toString(16).padStart(2, "0")); return out.join(" "); } function hexDumpSafe(ptrValue, length) { try { if (ptrValue.isNull() || length <= 0) return ""; const capped = Math.min(length, maxDump); return ptrValue.readByteArray(capped); } catch (e) { return null; } } function argValue(args, index) { try { return args[index].toString(); } catch (e) { return ""; } } function uintArg(args, index) { try { return args[index].toUInt32(); } catch (e) { return null; } } function ptrArg(args, index) { try { return args[index]; } catch (e) { return ptr("0"); } } function readStackWord(context, index) { try { return context.esp.add(index * Process.pointerSize).readPointer(); } catch (e) { return ptr("0"); } } function readUtf16(ptrValue) { try { if (ptrValue.isNull()) return ""; const text = ptrValue.readUtf16String(96); if (text === null) return ""; return text.replace(/\u0000.*$/g, ""); } catch (e) { return ""; } } function looksLikeSize(value) { return value !== null && value > 0 && value <= 65536; } function addCandidate(candidates, source, sizeIndex, ptrIndex, size, dataPtr) { try { if (!looksLikeSize(size) || dataPtr.isNull()) return; const hex = toHex(hexDumpSafe(dataPtr, size)); if (hex === "") return; candidates.push({ source, sizeIndex, ptrIndex, size, ptr: dataPtr.toString(), hex }); } catch (e) { } } function candidateBuffers(args, context) { const candidates = []; for (let sizeIndex = 0; sizeIndex <= 12; sizeIndex++) { const size = uintArg(args, sizeIndex); const directPtr = ptrArg(args, sizeIndex + 1); addCandidate(candidates, "args.direct", sizeIndex, sizeIndex + 1, size, directPtr); try { if (!directPtr.isNull()) { addCandidate(candidates, "args.byref", sizeIndex, sizeIndex + 1, size, directPtr.readPointer()); } } catch (e) { } } for (let sizeIndex = 0; sizeIndex <= 16; sizeIndex++) { const sizeWord = readStackWord(context, sizeIndex); let size = null; try { size = sizeWord.toUInt32(); } catch (e) { } const directPtr = readStackWord(context, sizeIndex + 1); addCandidate(candidates, "stack.direct", sizeIndex, sizeIndex + 1, size, directPtr); try { if (!directPtr.isNull()) { addCandidate(candidates, "stack.byref", sizeIndex, sizeIndex + 1, size, directPtr.readPointer()); } } catch (e) { } } return candidates; } function stackWords(context, count) { const words = []; for (let i = 0; i < count; i++) words.push(readStackWord(context, i).toString()); return words; } function hook(moduleName, rva, name, handlers) { const key = moduleName + "!" + name; if (installed[key]) return; const module = Process.findModuleByName(moduleName); if (module === null) return; const address = module.base.add(rva); Interceptor.attach(address, handlers(address, module)); installed[key] = true; emit({ event: "hook.installed", module: moduleName, name, base: module.base.toString(), rva: "0x" + rva.toString(16), address: address.toString() }); } function hookNmxServiceFunction(rva, name, argCount) { hook("NmxSvc.exe", rva, name, function (address, module) { return { onEnter(args) { const argList = []; for (let i = 0; i < argCount; i++) argList.push(argValue(args, i)); emit({ event: "nmxsvc.enter", module: "NmxSvc.exe", name, address: address.toString(), ecx: this.context.ecx ? this.context.ecx.toString() : "", esp: this.context.esp ? this.context.esp.toString() : "", args: argList, stack: stackWords(this.context, 18), candidates: candidateBuffers(args, this.context) }); }, onLeave(retval) { emit({ event: "nmxsvc.leave", module: "NmxSvc.exe", name, retval: retval.toString() }); } }; }); } function hookWinsock(name) { let address = null; try { if (typeof Module.findExportByName === "function") { address = Module.findExportByName("ws2_32.dll", name); } else { const ws2 = Process.findModuleByName("ws2_32.dll"); if (ws2 !== null && typeof ws2.findExportByName === "function") { address = ws2.findExportByName(name); } } } catch (e) { address = null; } if (address === null) return; const key = "ws2_32.dll!" + name; if (installed[key]) return; Interceptor.attach(address, { onEnter(args) { const len = uintArg(args, 2); emit({ event: "winsock.enter", module: "ws2_32.dll", name, socket: argValue(args, 0), buf: argValue(args, 1), len, flags: argValue(args, 3), hex: looksLikeSize(len) ? toHex(hexDumpSafe(ptrArg(args, 1), len)) : "" }); }, onLeave(retval) { emit({ event: "winsock.leave", module: "ws2_32.dll", name, retval: retval.toString() }); } }); installed[key] = true; emit({ event: "hook.installed", module: "ws2_32.dll", name, address: address.toString() }); } function installKnownHooks() { hookNmxServiceFunction(0x05be1, "CFMCCallback.DataReceived", 8); hookNmxServiceFunction(0x1807f, "CNmxControler.ProcessDataReceivedForEngine", 10); hookNmxServiceFunction(0x1d910, "CNmxControler.DataReceived", 10); hookNmxServiceFunction(0x1dcb5, "CNmxControler.TransferData", 10); hookNmxServiceFunction(0x1eea5, "CNmxControler.LocalCallbackDataReceived", 10); hookNmxServiceFunction(0x21b20, "CNmxService.TransferData", 10); hookWinsock("send"); hookWinsock("recv"); hookWinsock("sendto"); hookWinsock("recvfrom"); } emit({ event: "script.loaded", process: Process.id, arch: Process.arch, pointerSize: Process.pointerSize }); installKnownHooks();