// Frida hooks generated from headless Ghidra RVAs. // Usage: frida -f -l analysis/frida/mx-nmx-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 hexDumpSafe(ptr, length) { try { if (ptr.isNull() || length <= 0) return ""; const capped = Math.min(length, maxDump); return ptr.readByteArray(capped); } catch (e) { return null; } } function toHex(arrayBuffer) { if (arrayBuffer === null || arrayBuffer === "") return ""; const bytes = new Uint8Array(arrayBuffer); let out = []; for (let i = 0; i < bytes.length; i++) out.push(bytes[i].toString(16).padStart(2, "0")); return out.join(" "); } function dumpBytes(ptrValue, length) { return toHex(hexDumpSafe(ptrValue, length)); } function readStdWString(base, offset) { try { const obj = base.add(offset); const length = obj.add(0x10).readU32(); const capacity = obj.add(0x14).readU32(); const data = capacity < 8 ? obj : obj.readPointer(); if (length > 1024 || data.isNull()) { return { length, capacity, value: "" }; } return { length, capacity, value: data.readUtf16String(length) }; } catch (e) { return { error: e.message }; } } function readBstr(ptrValue) { try { if (ptrValue.isNull()) return ""; return ptrValue.readUtf16String(); } catch (e) { return ""; } } function readMxHandle(ptrValue) { try { if (ptrValue.isNull()) return null; return { raw: dumpBytes(ptrValue, 20), w0: ptrValue.add(0).readU32(), w1: ptrValue.add(4).readU32(), w2: ptrValue.add(8).readU32(), w3: ptrValue.add(12).readU32(), w4: ptrValue.add(16).readU32() }; } catch (e) { return { error: e.message }; } } function readPreboundReference(ptrValue) { try { if (ptrValue.isNull()) return null; const err = ptrValue.add(0xac).readPointer(); return { ptr: ptrValue.toString(), referenceString: readStdWString(ptrValue, 0x18), contextString: readStdWString(ptrValue, 0x34), auxString: readStdWString(ptrValue, 0x70), mxReference: ptrValue.add(0x50).readPointer().toString(), flags10: ptrValue.add(0x10).readU32(), word14: ptrValue.add(0x14).readU32(), word4c: ptrValue.add(0x4c).readU32(), word54: ptrValue.add(0x54).readU32(), word58: ptrValue.add(0x58).readU32(), word5c: ptrValue.add(0x5c).readU32(), word60: ptrValue.add(0x60).readU32(), word64: ptrValue.add(0x64).readU32(), word68: ptrValue.add(0x68).readU32(), word6c: ptrValue.add(0x6c).readU32(), worda0: ptrValue.add(0xa0).readU32(), worda4: ptrValue.add(0xa4).readU32(), status: ptrValue.add(0xa8).readU32(), flagb0: ptrValue.add(0xb0).readU8(), errorText: readBstr(err), raw: dumpBytes(ptrValue, 0xb4) }; } catch (e) { return { error: e.message, ptr: ptrValue.toString() }; } } function argValue(args, index) { try { return args[index].toString(); } catch (e) { return ""; } } function intArg(args, index) { try { return args[index].toInt32(); } catch (e) { return null; } } 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 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 hookPlainArgs(moduleName, rva, name, argCount) { hook(moduleName, rva, name, function (address, module) { return { onEnter(args) { let values = []; for (let i = 0; i < argCount; i++) values.push(argValue(args, i)); emit({ event: "call.enter", module: moduleName, name, address: address.toString(), ecx: this.context.ecx ? this.context.ecx.toString() : "", args: values }); }, onLeave(retval) { emit({ event: "call.leave", module: moduleName, name, retval: retval.toString() }); } }; }); } function hookAuthenticateUser() { hook("LmxProxy.dll", 0x1399f, "CLMXProxyServer.AuthenticateUser", function (address, module) { return { onEnter(args) { this.userIdOut = ptrArg(args, 4); const password = readBstr(ptrArg(args, 3)); emit({ event: "call.enter", module: "LmxProxy.dll", name: "CLMXProxyServer.AuthenticateUser", address: address.toString(), ecx: this.context.ecx ? this.context.ecx.toString() : "", serverHandle: intArg(args, 1), user: readBstr(ptrArg(args, 2)), passwordLength: password.length, userIdOut: this.userIdOut.toString() }); }, onLeave(retval) { let userId = null; try { if (!this.userIdOut.isNull()) userId = this.userIdOut.readS32(); } catch (e) { userId = null; } emit({ event: "call.leave", module: "LmxProxy.dll", name: "CLMXProxyServer.AuthenticateUser", retval: retval.toString(), userId }); } }; }); } function hookLmxPrebindReference() { hook("Lmx.dll", 0xea780, "MxConnection.PrebindReference", function (address, module) { return { onEnter(args) { this.self = ptrArg(args, 0); this.out = ptrArg(args, 2); emit({ event: "lmx.prebind.enter", module: "Lmx.dll", name: "MxConnection.PrebindReference", self: this.self.toString(), outPtr: this.out.toString(), referencePtr: ptrArg(args, 1).toString(), reference: ptrArg(args, 1).readUtf16String() }); }, onLeave(retval) { let handle = null; try { if (!this.out.isNull()) handle = this.out.readS32(); } catch (e) { handle = null; } emit({ event: "lmx.prebind.leave", module: "Lmx.dll", name: "MxConnection.PrebindReference", handle }); } }; }); } function hookLmxUserRegisterPreboundReference() { hook("Lmx.dll", 0xe1920, "MxConnection.UserRegisterPreboundReference", function (address, module) { return { onEnter(args) { this.self = ptrArg(args, 0); this.out = ptrArg(args, 4); emit({ event: "lmx.user-register-prebound.enter", module: "Lmx.dll", name: "MxConnection.UserRegisterPreboundReference", self: this.self.toString(), preboundHandle: intArg(args, 1), callback: argValue(args, 2), userData: intArg(args, 3), outPtr: this.out.toString() }); }, onLeave(retval) { let mxReferenceHandle = null; try { if (!this.out.isNull()) mxReferenceHandle = this.out.readS32(); } catch (e) { mxReferenceHandle = null; } emit({ event: "lmx.user-register-prebound.leave", module: "Lmx.dll", name: "MxConnection.UserRegisterPreboundReference", retval: retval.toString(), mxReferenceHandle }); } }; }); } function hookLmxReferenceHandleReader() { hook("Lmx.dll", 0x5f730, "IMxReference.GetMxHandle", function (address, module) { return { onEnter(args) { this.out = ptrArg(args, 0); this.ref = this.context.ecx ? this.context.ecx : ptr("0"); }, onLeave(retval) { emit({ event: "lmx.mxhandle.read", module: "Lmx.dll", name: "IMxReference.GetMxHandle", referencePtr: this.ref.toString(), outPtr: this.out.toString(), handle: readMxHandle(this.out), retval: retval.toString() }); } }; }); } function hookLmxFixupMxHandle() { hook("Lmx.dll", 0x8f8b0, "AccessManager.FixUpMxHandle", function (address, module) { return { onEnter(args) { this.out = ptrArg(args, 0); emit({ event: "lmx.fixup-mxhandle.enter", module: "Lmx.dll", name: "AccessManager.FixUpMxHandle", accessManager: this.context.ecx ? this.context.ecx.toString() : "", outPtr: this.out.toString(), inWords: [uintArg(args, 1), uintArg(args, 2), uintArg(args, 3), uintArg(args, 4), uintArg(args, 5), uintArg(args, 6)] }); }, onLeave(retval) { emit({ event: "lmx.fixup-mxhandle.leave", module: "Lmx.dll", name: "AccessManager.FixUpMxHandle", outPtr: this.out.toString(), handle: readMxHandle(this.out), retval: retval.toString() }); } }; }); } function hookLmxResolveReference() { hook("Lmx.dll", 0x113d40, "PreboundReference.Resolve", function (address, module) { return { onEnter(args) { this.prebound = this.context.ecx ? this.context.ecx : ptr("0"); emit({ event: "lmx.prebound-resolve.enter", module: "Lmx.dll", name: "PreboundReference.Resolve", prebound: readPreboundReference(this.prebound) }); }, onLeave(retval) { emit({ event: "lmx.prebound-resolve.leave", module: "Lmx.dll", name: "PreboundReference.Resolve", prebound: readPreboundReference(this.prebound), retval: retval.toString() }); } }; }); } function hookLmxResolveCallbacks() { hook("Lmx.dll", 0x1155a0, "PreboundReference.OnPlatformResolveReferenceResults", function (address, module) { return { onEnter(args) { this.prebound = this.context.ecx ? this.context.ecx : ptr("0"); this.reference = this.context.edx ? this.context.edx : ptr("0"); emit({ event: "lmx.platform-resolve-results.enter", module: "Lmx.dll", name: "PreboundReference.OnPlatformResolveReferenceResults", prebound: readPreboundReference(this.prebound), referencePtr: this.reference.toString() }); }, onLeave(retval) { emit({ event: "lmx.platform-resolve-results.leave", module: "Lmx.dll", name: "PreboundReference.OnPlatformResolveReferenceResults", prebound: readPreboundReference(this.prebound), retval: retval.toString() }); } }; }); hook("Lmx.dll", 0x114a90, "PreboundReference.OnSetAttributeResult", function (address, module) { return { onEnter(args) { this.prebound = this.context.ecx ? this.context.ecx : ptr("0"); emit({ event: "lmx.set-attribute-result.enter", module: "Lmx.dll", name: "PreboundReference.OnSetAttributeResult", prebound: readPreboundReference(this.prebound), correlationId: this.context.edx ? this.context.edx.toString() : "", pValue: argValue(args, 0), status: argValue(args, 3) }); }, onLeave(retval) { emit({ event: "lmx.set-attribute-result.leave", module: "Lmx.dll", name: "PreboundReference.OnSetAttributeResult", prebound: readPreboundReference(this.prebound), retval: retval.toString() }); } }; }); } function hookNmxPutRequest(moduleName, rva, name, ex) { hook(moduleName, rva, name, function (address, module) { return { onEnter(args) { // Ghidra sees this as a C++ method. On x86 thiscall, ECX is likely `this` // and args[0] is the first stack argument. We log broad argument state // and dump plausible size/payload pairs for later alignment. const candidates = []; for (let sizeIndex = 3; sizeIndex <= 8; sizeIndex++) { const size = uintArg(args, sizeIndex); const dataPtr = ptrArg(args, sizeIndex + 1); if (size !== null && size > 0 && size <= 65536 && !dataPtr.isNull()) { candidates.push({ sizeIndex, ptrIndex: sizeIndex + 1, size, ptr: dataPtr.toString(), hex: toHex(hexDumpSafe(dataPtr, size)) }); } } let values = []; for (let i = 0; i < 10; i++) values.push(argValue(args, i)); emit({ event: "nmx.enter", module: moduleName, name, address: address.toString(), ecx: this.context.ecx ? this.context.ecx.toString() : "", args: values, candidates }); }, onLeave(retval) { emit({ event: "nmx.leave", module: moduleName, name, retval: retval.toString() }); } }; }); } function installKnownHooks() { hookPlainArgs("LmxProxy.dll", 0x12c0c, "CLMXProxyServer.Write.variantA", 10); hookPlainArgs("LmxProxy.dll", 0x13280, "CLMXProxyServer.Write.variantB", 13); hookPlainArgs("LmxProxy.dll", 0x12f24, "CLMXProxyServer.WriteSecured.variantA", 10); hookPlainArgs("LmxProxy.dll", 0x135fe, "CLMXProxyServer.WriteSecured.variantB", 14); hookPlainArgs("LmxProxy.dll", 0x1121d, "CLMXProxyServer.AddBufferedItem", 5); hookPlainArgs("LmxProxy.dll", 0x0fc80, "CLMXProxyServer.SetBufferedUpdateInterval", 3); hookPlainArgs("LmxProxy.dll", 0x142b4, "CLMXProxyServer.AdviseSupervisory", 5); hookPlainArgs("LmxProxy.dll", 0x163c0, "CProxy_ILMXProxyServerEvents2.Fire_OnBufferedDataChange", 8); hookPlainArgs("LmxProxy.dll", 0x16b50, "CUserConnectionCallback.OnSetAttributeResult", 4); hookPlainArgs("LmxProxy.dll", 0x16d4b, "CUserConnectionCallback.OperationComplete", 4); hookAuthenticateUser(); hookNmxPutRequest("NmxAdptr.dll", 0x10996, "CNmxAdapter.TransferData", false); hookNmxPutRequest("NmxAdptr.dll", 0x112da, "CNmxAdapter.ProcessDataReceived", false); hookNmxPutRequest("NmxAdptr.dll", 0x15169, "CNmxAdapter.PutRequest", false); hookNmxPutRequest("NmxAdptr.dll", 0x159c3, "CNmxAdapter.PutRequestEx", true); hookLmxPrebindReference(); hookLmxUserRegisterPreboundReference(); hookLmxReferenceHandleReader(); hookLmxFixupMxHandle(); hookLmxResolveReference(); hookLmxResolveCallbacks(); } const timer = setInterval(function () { installKnownHooks(); if (installed["LmxProxy.dll!CLMXProxyServer.Write.variantA"] && installed["NmxAdptr.dll!CNmxAdapter.PutRequest"]) { clearInterval(timer); } }, 100); installKnownHooks();