; ahk-hack standalone core. ; ; One-file AutoHotkey v2 scanner for AutoHotkey executables. Copy this file ; anywhere and #Include it, or run ahk_hack_demo.ahk for a self-test. ; The x64 blob below is generated from lib/mcode/scanner.c by ; tools/build_mcode.py; it locates g_BIF, sMdFunc and g_BIV_A inside the ; running interpreter and copies entry metadata into a caller buffer. MC_REMOTE_CALL_STUB_X64 := "564883ec304889ce0f1041284c8b49204c8b4118488b4908488b56100f11442420ff1689463831c04883c4305ec3" MC_REMOTE_EVAL_STUB_X64 := "564881ec800000004889ce0f1041304c8b49284c8b4120488b4910488b56180f11442420ff1689c1894678b80100000085c9755b488b4638488b4e400f10000f104810488b40204c8b96800000000f1056604c8b4e584c8b4650488b56484c8954247048894424680f114c24580f114424480f57c00f114424380f11542420c744243001000000ff560889467c31c04881c4800000005ec3" MC_PE_EXPORT_SCANNER_X64 := "415741564155415456575553504885c90f94c04885d2410f94c04108c0b8010000000f85f00000006681394d5a0f85e5000000448b413c41813c08504500000f85d30000006641817c08180b020f85c5000000418bb40888000000458b8c088c0000004885f6410f94c24585c9410f94c34508d34180fb0175190f57c00f1102be100000004531c94c890c3231c0e985000000458b4408504d85c0747b4439c673764589c94d89ca4901f24d39c2410f97c24183f928410f92c14508d17559448b740e18448b540e144d85d2410f94c14585f6410f95c34584d9753c4181fe0010000041b900100000450f42ce4c8d3c0e458b5f204b8d3c8b4c39c7771a8b7c0e244a8d1c4f4c39c3770d8b5c0e1c4e8d24934d39c476114883c4085b5d5f5e415c415d415e415fc38b740e10897424044c893a48c7421000000000be080000004585f60f843bffffff4801cb4901cb4801cf4e8d34cd000000004f8d34764531ff458b234539c473ae440fb72f4539ea76a5428b2cab4439c5739c4901cc4e89643a184a896c3a2044036c240446896c3a2842c7443a2c000000004983c3044883c7024983c7184d39fe75b5e9d6feffff" MC_BIF_SCANNER_X64 := "4157415641554154565755534881ec780200004885c90f94c04885d2410f94c04108c0be010000000f85060700006681394d5a0f85fb0600008b413c813c08504500000f85eb06000066817c08180b020f85de060000440fb7540806664183fa1041b910000000450f42ca448b5c08504d8d040b4d85db4d0f44c3664585d20f8400030000440fb7540814410fb7f94801c84c01d0448d0cfd000000004f8d0c894c8d5424704531dbbb010000004531f631edeb4c660f7f442450440fb67c2452440fb66424534180f7614180f4744508fc440fb67c24544180f761400fb6ed4508e70f44eb662e0f1f8400000000004983c3284983c2204d39d90f84ed000000468b7c18204585ff460f447c1828468b6418244901cc4d01e74d39c74d89c54d0f42ef4d85c04d0f44ef4d89224d896a084e8b7c181866490f6ec7660f7f442460440fb66424604d897a134180fc2e759e66410f7ec741c1ef084180ff640f8450ffffff450fb6ff4183ff7274414183ff740f8577ffffff660f7f442430440fb67c2432440fb66424334180f7654180f4784508fc440fb67c24344180f774450fb6f64508e7440f44f3e940ffffff660f7f442440807c2442640f852fffffff807c2443610f8524ffffff807c2444740f8519ffffff66440fc5f80241c1ef084180ff61400fb6ede9f5feffff4584f60f95c14084ed0f95c020c889f9c1e1054531c0eb2d662e0f1f8400000000006642c784048000000000004531c946888c04820000004983c0204c39c10f8454010000460fb69404830000004180fa2e753e4280bc04840000007275434280bc04850000007375384280bc048600000072752d4280bc048700000063410f94c184c0746ceb21662e0f1f84000000000084c0758c4531c9eb570f1f80000000004531c984c074494180fa2e0f856fffffff4280bc048400000074754d4280bc04850000006575424280bc04860000007875374280bc048700000074410f94c1eb2b6666666666662e0f1f8400000000004180f10146888c048000000046888c0481000000e925ffffff4531c946888c04800000004280bc04840000007275304280bc04850000006475254280bc048600000061751a4280bc048700000074750f4280bc048800000061410f94c1eb034531c946888c04810000004280bc04840000006475284280bc048500000061751d4280bc04860000007475124280bc048700000061410f94c1e9a1feffff4531c9e999feffff31ff488d420848894424284989d74889542420488d4c247041b92000000089fa4531c0e8a303000085c00f847f0300004d8d7720498d47184c897424284889442420488d4c2470be0100000041b92800000089fa41b801000000e86c03000085c00f8448030000498d5f38498d473048895c24284889442420488d4c247041b91800000089fa41b802000000e83a03000085c00f84160300004c89f849c747102000000049c747282800000049c747401800000049c7474800000000418b570885d27e6dffca4881faff010000b9ff010000480f42ca488b10c1e1054883c1204531c00f1f8400000000004e8b0c024e894c00504e8b4c02084e894c0058460fb64c021046894c0060460fb64c021146894c0064460fb64c021246894c006842c744006c000000004983c0204c39c175ba418b0e85c90f8ea5010000ffc94881f9ff010000baff010000480f42d1488b4818488d1492488d14d5280000004531c0662e0f1f8400000000004e8b0c014e898c00504000004e8b4c01084e898c0058400000460fb64c011046888c0060400000460fb64c011146888c0061400000460fb64c011246888c0062400000460fb64c011346888c0063400000460fb64c011446888c0064400000460fb64c011546888c0065400000460fb64c011646888c0066400000460fb64c011746888c0067400000460fb64c011846888c0068400000460fb64c011946888c0069400000460fb64c011a46888c006a400000460fb64c011b46888c006b400000460fb64c011c46888c006c400000460fb64c011d46888c006d400000460fb64c011e46888c006e400000460fb64c011f46888c006f400000460fb64c012046888c0070400000460fb64c012146888c0071400000460fb64c012246888c0072400000460fb64c012346888c0073400000460fb64c012446888c0074400000460fb64c012546888c0075400000460fb64c012646888c0076400000460fb64c012746888c00774000004983c0284c39c20f858afeffff448b034585c00f8e8c00000041ffc84181f8ff000000baff000000410f42d0488b4830ffc231f64585c074704189d14181e1fe0100004531d24531c04e8b1c114e899c10509000004e8b5c11084e899c10589000004e8b5c11104e899c10609000004e8b5c11184e899c10689000004e8b5c11204e899c10709000004e8b5c11284e899c10789000004983c0024983c2304d39c175a6eb0731f6eb324531c0f6c201742a48055090000049c1e0034b8d14404c8b04114c8904104c8b4411084c89441008488b4c111048894c101089f04881c4780200005b5d5f5e415c415d415e415fc3660f1f4400004157415641554154565755534883ec3885d20f8e270500004c894c241889d64889f748c1e70531db41be4000000048c74424200000000048c744242800000000eb1a66666666662e0f1f84000000000048ffc34839f30f84c80400004889da48c1e205488d0411807c11110075068078120074dc4c8b204c8b7808498d4424104c39f877cb4d8b2c24eb2e4c8b0c24488b4424284939c1490f47c14889442428488b442420480f47c5488944242066904883c5184c39fd77974c89e54c89e84983c4084c8b6d084531d2eb0d0f1f40004983c2204c39d774d742807c111100750842807c11120074e74e8b4c11084929c176dd4a39041177d74983f90272b149d1e94983f9404d0f43ce49ffc94989c2450fb71a664585db741f4183c3814983e9010f92c2664183fba172844983c20284d274dce977ffffff4531c9eb0f66904983c1204c39cf0f8463ffffff42807c09100074eb4e396c090876e44e392c0977de0fb7104183f8010f84de0200004585c00f854d0300006683fa410f852effffff66837802620f8523ffffff41b90600000066837804730f8512ffffff6641833c01000f8506ffffff488b4424184801e84883c01041b9010000004c39f80f87cafeffff488b4424184801e84889ea48895424104889c24c8b10488b400848894424084531dbeb140f1f80000000004983c3204c39df0f8492feffff42807c191100750842807c19120074e34a8b4419084c29d076d94e39141977d34883f8020f8268feffff48895424304c890c2448d1e84883f840490f43c648ffc84d89d36666666666662e0f1f840000000000410fb7136685d2742383c2814883e801410f92c16683faa10f821dfeffff4983c3024584c974d9e90ffeffff31c04c8b0c24488b542408eb140f1f80000000004883c0204839c70f84f2fdffff807c01100074ec483954010876e54839140177df4183f80275406641833a590f85620100006641837a02590f85560100006641837a04590f854a0100006641837a06590f853e0100006641837a08000f8532010000e925020000488b442410488b104531c9eb094983c1204c39cf742542807c091100750842807c09120074e74a8b4409084829d076dd4a39140977d748d1e8eb0231c04531c9eb094983c1204c39cf745542807c091100750842807c09120074e74e8b5c09084d29d376dd4e39140977d74d89d949d1e94889542408488d50ff4883fa3f488b542408490f43c64939c14c0f43c84983fb024c0f42c84c894c24104d85c9eb704989d1488d50ff4883fa3f4c89ca490f43c648894424104c8b0c240fb702450fb71a6685c07451664585db744b4889542408418d53bf458d4b2080fa1a410fb6d1410f43d3448d48bf448d58204180f91a450fb6cb440f43c84138d175254983c202488b5424084883c20248ff4c24104c8b0c2475a5eb15664439d8760fe975fcffff4c8b0c240f836bfcffff49ffc1488b5424184c8b542430498d04124c01d24883c2104c39fa4c89d20f8688fdffffe942fcffff6683fa420f8559fcffff668378026c0f854efcffff668378046f0f8543fcffff66837806630f8538fcffff668378086b0f852dfcffff6683780a490f8522fcffff6683780c6e0f8517fcffff6683780e700f850cfcffff66837810750f8501fcffff41b91400000066837812740f85f0fbffffe9d9fcffff6683fa410f85e1fbffff66837802680f85d6fbffff668378046b0f85cbfbffff66837806500f85c0fbffff66837808610f85b5fbffff6683780a740f85aafbffff41b90e0000006683780c680f8599fbffffe982fcffff49ffc1e96bfbffff4c8b4424204d85c00f94c0488b5424284883fa0a0f92c108c1740431c0eb1b488b8424a00000004c8900488b8424a8000000488910b8010000004883c4385b5d5f5e415c415d415e415fc3" ; Decode a raw hex machine-code blob and return an executable Buffer. MCode(hex) { size := StrLen(hex) // 2 buf := Buffer(size) loop size { byte := Integer("0x" SubStr(hex, 2 * A_Index - 1, 2)) NumPut("UChar", byte, buf, A_Index - 1) } oldProtect := 0 if !DllCall("VirtualProtect", "Ptr", buf.Ptr, "UPtr", size, "UInt", 0x40, "UInt*", oldProtect) throw Error("VirtualProtect failed", -1) return buf } class AhkMagic { static scanner := 0 static exportScanner := 0 static inprocEval := 0 static memScriptLoader := 0 static internalLocator := 0 static internalLocated := false static evalScriptLocated := false static evalLayoutLocated := false static evalMFuncsOff := 0 static evalMFuncsCountOff := 0 static evalMLastLineOff := 0 static evalMJumpLineOff := 0 static evalCurrOff := 0 static evalStructOffsets := 0 static evalLayoutArr := 0 static evalParserLocated := false static evalParser := Map() static evalPreparse := 0 static evalPreprocess := 0 static evalOpenInclude := 0 static evalLoadTs := 0 static evalSrcCount := 0 static evalGptr := 0 static exprToPostfix := 0 static expandSingleArg := 0 static currLineSlot := 0 static crtFree := 0 static gScript := 0 static finalizeExpr := 0 static findOrAddVar := 0 static symInvalid := 73 static moduleBase := 0 static bifTablePtr := 0 static bifCount := 0 static bifStride := 0 static mdfuncTablePtr := 0 static mdfuncCount := 0 static mdfuncStride := 0 static bivTablePtr := 0 static bivCount := 0 static bivStride := 0 static bif := Map() static mdfunc := Map() static biv := Map() static Init() { if AhkMagic.scanner return if A_PtrSize != 8 throw Error("this mcode blob is x64 only", -1) try { AhkMagic._InitTables() } catch as e { ; A half-initialized state would make later calls fail with ; misleading errors; reset so Init can be retried cleanly. AhkMagic.scanner := 0 AhkMagic.bif.Clear() AhkMagic.mdfunc.Clear() AhkMagic.biv.Clear() throw e } } ; Parse the scanner output into bif/mdfunc/biv maps. The scanner copies ; at most 512/512/256 entries into the 64 KiB output buffer; larger ; tables are an explicit unsupported-layout error, never a silent read. static _InitTables() { AhkMagic.scanner := MCode(MC_BIF_SCANNER_X64) AhkMagic.moduleBase := DllCall("GetModuleHandle", "Str", A_AhkPath, "Ptr") if !AhkMagic.moduleBase throw Error("GetModuleHandle failed", -1) out := Buffer(64 * 1024) rc := DllCall(AhkMagic.scanner.Ptr, "Ptr", AhkMagic.moduleBase, "Ptr", out.Ptr, "Int") if rc != 0 throw Error("AhkScanTables failed with rc=" rc, -1) AhkMagic.bifTablePtr := NumGet(out, 0, "Ptr") AhkMagic.bifCount := NumGet(out, 8, "Int64") AhkMagic.bifStride := NumGet(out, 16, "Int64") AhkMagic.mdfuncTablePtr := NumGet(out, 24, "Ptr") AhkMagic.mdfuncCount := NumGet(out, 32, "Int64") AhkMagic.mdfuncStride := NumGet(out, 40, "Int64") AhkMagic.bivTablePtr := NumGet(out, 48, "Ptr") AhkMagic.bivCount := NumGet(out, 56, "Int64") AhkMagic.bivStride := NumGet(out, 64, "Int64") ; The copy loops below run on raw pointer arithmetic without any ; buffer object, so validate every count and stride BEFORE looping. if AhkMagic.bifCount < 1 or AhkMagic.bifCount > 512 throw Error("g_BIF table count out of scanner contract: " . AhkMagic.bifCount, -1) if AhkMagic.mdfuncCount < 1 or AhkMagic.mdfuncCount > 512 throw Error("sMdFunc table count out of scanner contract: " . AhkMagic.mdfuncCount, -1) if AhkMagic.bivCount < 1 or AhkMagic.bivCount > 256 throw Error("g_BIV_A table count out of scanner contract: " . AhkMagic.bivCount, -1) if AhkMagic.bifStride != 0x20 or AhkMagic.mdfuncStride != 0x28 or AhkMagic.bivStride != 0x18 throw Error("unexpected interpreter table strides", -1) bifBase := 80 bifStride := 32 loop AhkMagic.bifCount { p := out.Ptr + bifBase + (A_Index - 1) * bifStride namePtr := NumGet(p, 0, "Ptr") fnPtr := NumGet(p, 8, "Ptr") min := NumGet(p, 16, "UInt") max := NumGet(p, 20, "UInt") fid := NumGet(p, 24, "UInt") if !AhkMagic._Readable(namePtr, 8) throw Error("g_BIF entry " A_Index " has an invalid name pointer", -1) name := StrGet(namePtr, 256, "UTF-16") AhkMagic.bif[name] := Map( "index", A_Index - 1, "rva", fnPtr - AhkMagic.moduleBase, "min", min, "max", max, "fid", fid ) } mdfBase := 80 + 512 * 32 mdfStride := 40 loop AhkMagic.mdfuncCount { p := out.Ptr + mdfBase + (A_Index - 1) * mdfStride namePtr := NumGet(p, 0, "Ptr") fnPtr := NumGet(p, 8, "Ptr") retType := NumGet(p, 16, "UChar") if !AhkMagic._Readable(namePtr, 8) throw Error("sMdFunc entry " A_Index " has an invalid name pointer", -1) name := StrGet(namePtr, 256, "UTF-16") AhkMagic.mdfunc[name] := Map( "index", A_Index - 1, "rva", fnPtr - AhkMagic.moduleBase, "ret", retType ) } bivBase := 80 + 512 * 32 + 512 * 40 bivStride := 24 loop AhkMagic.bivCount { p := out.Ptr + bivBase + (A_Index - 1) * bivStride namePtr := NumGet(p, 0, "Ptr") getter := NumGet(p, 8, "Ptr") setter := NumGet(p, 16, "Ptr") if !AhkMagic._Readable(namePtr, 8) throw Error("g_BIV_A entry " A_Index " has an invalid name pointer", -1) name := StrGet(namePtr, 256, "UTF-16") AhkMagic.biv[name] := Map( "index", A_Index - 1, "getter_rva", getter ? getter - AhkMagic.moduleBase : 0, "setter_rva", setter ? setter - AhkMagic.moduleBase : 0 ) } } ; True when [ptr, ptr+size) falls inside committed readable memory. ; Used before dereferencing pointers that come from scanned tables. static _Readable(ptr, size) { if !ptr or ptr >= 0x7FFFFFFFFFFF return false mbi := Buffer(48) if !DllCall("VirtualQuery", "Ptr", ptr, "Ptr", mbi.Ptr, "UPtr", 48) return false state := NumGet(mbi, 32, "UInt") protect := NumGet(mbi, 36, "UInt") if state != 0x1000 ; MEM_COMMIT return false if protect & 0x100 ; PAGE_GUARD return false readable := (protect & 0x02) or (protect & 0x04) or (protect & 0x08) or (protect & 0x20) or (protect & 0x40) or (protect & 0x80) if !readable return false regionBase := NumGet(mbi, 0, "Ptr") regionSize := NumGet(mbi, 24, "Ptr") return ptr >= regionBase and ptr + size <= regionBase + regionSize } static BifRva(name) { AhkMagic.Init() if !AhkMagic.bif.Has(name) throw Error("builtin not found: " name) return AhkMagic.bif[name]["rva"] } static BifAddr(name) { AhkMagic.Init() return AhkMagic.moduleBase + AhkMagic.BifRva(name) } static PatchBif(name, newName) { AhkMagic.Init() if !AhkMagic.bif.Has(name) throw Error("builtin not found: " name) if !AhkMagic.bif.Has(newName) throw Error("builtin not found: " newName) entryAddr := AhkMagic.bifTablePtr + AhkMagic.bif[name]["index"] * AhkMagic.bifStride + A_PtrSize oldPtr := NumGet(entryAddr, 0, "Ptr") expected := AhkMagic.moduleBase + AhkMagic.bif[name]["rva"] if oldPtr != expected throw Error("table pointer mismatch for " name ": got 0x" Format("{:X}", oldPtr) ", expected 0x" Format("{:X}", expected)) NumPut("Ptr", AhkMagic.moduleBase + AhkMagic.bif[newName]["rva"], entryAddr) return oldPtr } static RestoreBif(name, oldPtr) { AhkMagic.Init() if !AhkMagic.bif.Has(name) throw Error("builtin not found: " name) entryAddr := AhkMagic.bifTablePtr + AhkMagic.bif[name]["index"] * AhkMagic.bifStride + A_PtrSize NumPut("Ptr", oldPtr, entryAddr) } ; Deep patch: change the mBIF field of an already-resolved built-in Func ; object, so even direct calls compiled at load time (e.g. Abs(1)) are ; redirected. Works together with PatchBif on the table itself. static PatchBifObject(fnObj, newName) { AhkMagic.Init() if !(fnObj is Func) throw TypeError("expected a Func object") if !AhkMagic.bif.Has(newName) throw Error("builtin not found: " newName) fnName := fnObj.Name if !AhkMagic.bif.Has(fnName) throw Error("function object is not a built-in: " fnName) oldAddr := AhkMagic.moduleBase + AhkMagic.bif[fnName]["rva"] newAddr := AhkMagic.moduleBase + AhkMagic.bif[newName]["rva"] rawPtr := ObjPtr(fnObj) off := AhkMagic._FindBifPtrOffset(rawPtr, oldAddr) if off < 0 throw Error("could not locate mBIF in function object for " fnName) ; Patch the table first, then the object. If the object write ever ; fails the table patch is rolled back, so callers never observe a ; half-patched state. old := AhkMagic.PatchBif(fnName, newName) try NumPut("Ptr", newAddr, rawPtr, off) catch as e { AhkMagic.RestoreBif(fnName, old) throw e } return Map( "fnName", fnName, "newName", newName, "oldPtr", old, "offset", off ) } static RestoreBifObject(fnObj, state) { AhkMagic.Init() if !(fnObj is Func) throw TypeError("expected a Func object") fnName := fnObj.Name if state["fnName"] != fnName throw Error("state does not match function object " fnName) if !AhkMagic.bif.Has(state["newName"]) throw Error("builtin not found: " state["newName"]) rawPtr := ObjPtr(fnObj) current := AhkMagic.moduleBase + AhkMagic.bif[state["newName"]]["rva"] off := AhkMagic._FindBifPtrOffset(rawPtr, current) if off < 0 throw Error("could not locate mBIF in function object for " fnName) NumPut("Ptr", state["oldPtr"], rawPtr, off) AhkMagic.RestoreBif(fnName, state["oldPtr"]) } static _FindBifPtrOffset(rawPtr, targetAddr) { loop 64 { off := (A_Index - 1) * 8 if NumGet(rawPtr, off, "Ptr") = targetAddr return off } return -1 } ; General PE export-table scanner (works for any loaded DLL/EXE module). static ScanExports(moduleBase) { if !(moduleBase is Integer) or !moduleBase throw TypeError("moduleBase must be a nonzero module address", -1) if !AhkMagic.exportScanner AhkMagic.exportScanner := MCode(MC_PE_EXPORT_SCANNER_X64) out := Buffer(100 * 1024) rc := DllCall( AhkMagic.exportScanner.Ptr, "Ptr", moduleBase, "Ptr", out.Ptr, "Int" ) if rc != 0 throw Error("AhkScanExports failed with rc=" rc) count := NumGet(out, 8, "Int64") ; The scanner writes at most 4096 entries into the 100 KiB buffer. if count < 0 or count > 4096 throw Error("export count out of scanner contract: " count, -1) result := Map() loop count { p := out.Ptr + 24 + (A_Index - 1) * 24 namePtr := NumGet(p, 0, "Ptr") fnRva := NumGet(p, 8, "Int64") ordinal := NumGet(p, 16, "UInt") if !AhkMagic._Readable(namePtr, 8) throw Error("export entry " A_Index " has an invalid name pointer", -1) result[StrGet(namePtr, 1024, "UTF-8")] := Map( "rva", fnRva, "ordinal", ordinal ) } return result } ; ------------------------------------------------------------------ ; True in-process Eval. Locates the interpreter's own expression ; compiler/evaluator, builds a temporary Line/ArgStruct, and calls ; them directly in this process. ; ------------------------------------------------------------------ static _ModuleSections() { base := AhkMagic.moduleBase peOff := NumGet(base, 0x3C, "UInt") numSections := NumGet(base + peOff + 6, "UShort") optSize := NumGet(base + peOff + 20, "UShort") secTable := base + peOff + 24 + optSize result := Map() loop numSections { hdr := secTable + (A_Index - 1) * 40 name := StrGet(hdr, 8, "UTF-8") vsize := NumGet(hdr + 8, "UInt") va := NumGet(hdr + 12, "UInt") result[name] := Map( "rva", va, "size", vsize, "ptr", base + va ) } pdata := result.Has(".pdata") ? result[".pdata"] : 0 for _, sec in result sec["pdata"] := pdata return result } static _TextSection(secs) { if secs.Has(".text") return secs[".text"] for name, sec in secs if name != ".rsrc" return sec throw Error("text section not found") } static _FindUtf16(secs, text) { len := StrLen(text) needle := Buffer(2 * (len + 1), 0) StrPut(text, needle, "UTF-16") hits := [] for name, sec in secs { if name = ".rsrc" continue if !sec["ptr"] or sec["size"] < 2 * len continue p := sec["ptr"] count := sec["size"] >= 2 * len ? Min((sec["size"] - 2 * len) // 2 + 1, 0x100000) : 0 if count <= 0 continue loop count { off := (A_Index - 1) * 2 ok := true loop len { if NumGet(p + off + 2 * (A_Index - 1), "UShort") != NumGet(needle, 2 * (A_Index - 1), "UShort") { ok := false break } } if ok and NumGet(p + off + 2 * len, "UShort") = 0 hits.Push(sec["rva"] + off) } } return hits } static _RipRefs(sec, targetRva) { p := sec["ptr"] size := sec["size"] refs := [] if size < 8 return refs loop size - 7 { i := A_Index - 1 b := NumGet(p + i, "UChar") if b != 0x48 and b != 0x4C continue if NumGet(p + i + 1, "UChar") != 0x8D continue reg := NumGet(p + i + 2, "UChar") if reg != 0x05 and reg != 0x0D and reg != 0x15 and reg != 0x1D and reg != 0x25 and reg != 0x2D and reg != 0x35 and reg != 0x3D continue disp := NumGet(p + i + 3, "Int") next := sec["rva"] + i + 7 if next + disp = targetRva refs.Push(sec["rva"] + i) } return refs } static _PdataFunctionStart(pdata, rva, sec) { if !pdata or pdata["size"] < 12 throw Error("PE .pdata section is missing", -1) p := pdata["ptr"] count := pdata["size"] // 12 lo := 1 hi := count idx := 0 while lo <= hi { mid := (lo + hi) // 2 off := (mid - 1) * 12 begin := NumGet(p + off, "UInt") end := NumGet(p + off + 4, "UInt") if rva < begin { hi := mid - 1 continue } if rva >= end { lo := mid + 1 continue } idx := mid break } if !idx throw Error("function start not found for RVA " . Format("0x{:X}", rva), -1) loop idx { current := idx - A_Index + 1 off := (current - 1) * 12 begin := NumGet(p + off, "UInt") if AhkMagic._IsFunctionEntryCode(sec, begin) return begin } throw Error("logical function entry not found for RVA " . Format("0x{:X}", rva), -1) } static _PdataEntryStart(pdata, rva) { if !pdata or pdata["size"] < 12 return 0 p := pdata["ptr"] count := pdata["size"] // 12 lo := 1 hi := count while lo <= hi { mid := (lo + hi) // 2 off := (mid - 1) * 12 begin := NumGet(p + off, "UInt") end := NumGet(p + off + 4, "UInt") if rva < begin { hi := mid - 1 continue } if rva >= end { lo := mid + 1 continue } return begin } return 0 } static _IsFunctionEntryCode(sec, rva) { off := rva - sec["rva"] if off < 0 or off + 4 > sec["size"] return false p := sec["ptr"] b0 := NumGet(p + off, "UChar") b1 := NumGet(p + off + 1, "UChar") b2 := NumGet(p + off + 2, "UChar") b3 := NumGet(p + off + 3, "UChar") if b0 = 0x48 and b1 = 0x89 and ((b2 = 0x5C and b3 = 0x24) or (b2 = 0x54 and b3 = 0x24) or (b2 = 0x4C and b3 = 0x24)) return true if b0 = 0x4C and b1 = 0x89 and ((b2 = 0x44 and b3 = 0x24) or (b2 = 0x4C and b3 = 0x24)) return true if b0 = 0x40 and b1 >= 0x50 and b1 <= 0x57 return true if b0 = 0x55 or b0 = 0x53 or b0 = 0x56 or b0 = 0x57 or b0 = 0x41 return true return false } static _FnStart(sec, refRva) { if !sec.Has("pdata") throw Error("section has no PE .pdata context", -1) return AhkMagic._PdataFunctionStart(sec["pdata"], refRva, sec) } static _FindCallers(sec, targetRva) { p := sec["ptr"] size := sec["size"] calls := [] if size < 6 return calls loop size - 5 { i := A_Index - 1 if NumGet(p + i, "UChar") != 0xE8 continue disp := NumGet(p + i + 1, "Int") if sec["rva"] + i + 5 + disp = targetRva calls.Push(sec["rva"] + i) } return calls } static _BestStart(sec, refs) { starts := Map() for ref in refs { start := AhkMagic._FnStart(sec, ref) starts[start] := starts.Has(start) ? starts[start] + 1 : 1 } best := 0 bestCount := 0 for start, count in starts { if count > bestCount { best := start bestCount := count } } return best } static _CurrLineSlotAddr() { AhkMagic.Init() if !AhkMagic.biv.Has("LineNumber") throw Error("A_LineNumber getter not found") p := AhkMagic.moduleBase + AhkMagic.biv["LineNumber"]["getter_rva"] if NumGet(p, "UChar") != 0x48 or NumGet(p + 1, "UChar") != 0x8B or NumGet(p + 2, "UChar") != 0x05 throw Error("unexpected A_LineNumber getter code") disp := NumGet(p + 3, "Int") return p + 7 + disp } static _LocateInternalFunctions() { if AhkMagic.internalLocated return secs := AhkMagic._ModuleSections() text := AhkMagic._TextSection(secs) postfixRefs := AhkMagic._RipRefs(text, AhkMagic._FindUtf16(secs, "Missing operand.")[1]) postfix2 := AhkMagic._BestStart(text, postfixRefs) if !postfix2 throw Error("ExpressionToPostfix not found") expandRefs := AhkMagic._RipRefs(text, AhkMagic._FindUtf16(secs, "Error evaluating expression.")[1]) expand := AhkMagic._BestStart(text, expandRefs) if !expand throw Error("ExpandExpression not found") AhkMagic.exprToPostfix := AhkMagic.moduleBase + postfix2 AhkMagic.expandSingleArg := AhkMagic.moduleBase + expand AhkMagic.currLineSlot := AhkMagic._CurrLineSlotAddr() if !AhkMagic.internalLocator AhkMagic.internalLocator := MCode(MC_INTERNAL_LOCATOR_X64) locOut := Buffer(64, 0) rc := DllCall( AhkMagic.internalLocator.Ptr, "Ptr", AhkMagic.moduleBase, "UInt64", text["rva"], "UInt64", text["size"], "UInt64", postfix2, "UInt64", expand, "Ptr", locOut.Ptr, "Int" ) if rc != 0 throw Error("internal locator failed with rc=" rc) AhkMagic.gScript := NumGet(locOut, 0, "Ptr") AhkMagic.finalizeExpr := NumGet(locOut, 8, "Ptr") AhkMagic.findOrAddVar := NumGet(locOut, 16, "Ptr") AhkMagic.crtFree := NumGet(locOut, 24, "Ptr") AhkMagic.symInvalid := NumGet(locOut, 32, "UInt") AhkMagic.internalLocated := true } static _LocateCrtFree() { secs := AhkMagic._ModuleSections() text := AhkMagic._TextSection(secs) postfixRva := AhkMagic.exprToPostfix - AhkMagic.moduleBase callers := AhkMagic._FindCallers(text, postfixRva) for callerRva in callers { off := callerRva - text["rva"] + 5 p := text["ptr"] + off loop 96 { i := A_Index - 1 if NumGet(p + i, "UChar") != 0xE8 continue disp := NumGet(p + i + 1, "Int") target := text["rva"] + off + i + 5 + disp if target != postfixRva return AhkMagic.moduleBase + target } } throw Error("CRT free not found") } static _LocatePreprocessFunc(sec) { p := sec["ptr"] size := sec["size"] count := size - 8 loop count { i := A_Index - 1 if NumGet(p + i, "UChar") != 0x41 or NumGet(p + i + 1, "UChar") != 0x0F or NumGet(p + i + 2, "UChar") != 0xB6 continue m := NumGet(p + i + 3, "UChar") if m != 0x76 and m != 0x7E continue if NumGet(p + i + 4, "UChar") != 0x23 continue limit := Min(size - 4, i + 0x300) j := i + 5 while j < limit { if NumGet(p + j, "UChar") = 0x80 and NumGet(p + j + 1, "UChar") = 0x78 and NumGet(p + j + 2, "UChar") = 0x23 and NumGet(p + j + 3, "UChar") = 0x02 return AhkMagic._FnStart(sec, sec["rva"] + i) j += 1 } } return 0 } static _LocateGptr(sec, preparseRva) { callers := AhkMagic._FindCallers(sec, preparseRva) for caller in callers { start := AhkMagic._FnStart(sec, caller) p := sec["ptr"] off := start - sec["rva"] limit := Min(sec["size"] - 16, off + 0x4000) i := off while i < limit { if NumGet(p + i, "UChar") = 0xCC and NumGet(p + i + 1, "UChar") = 0xCC break if NumGet(p + i, "UChar") = 0x48 and NumGet(p + i + 1, "UChar") = 0x8B and NumGet(p + i + 2, "UChar") = 0x05 { j := i + 7 while j < Min(i + 24, limit) { b0 := NumGet(p + j, "UChar") b1 := NumGet(p + j + 1, "UChar") b2 := NumGet(p + j + 2, "UChar") b3 := NumGet(p + j + 3, "UChar") if b0 = 0x48 and b1 = 0x89 and (b2 = 0x58 or b2 = 0x50) and (b3 = 0x28 or b3 = 0x50) { disp := NumGet(p + i + 3, "Int") return sec["rva"] + i + 7 + disp } j += 1 } } i += 1 } } return 0 } static _LocateLoadTs(sec, openRva) { callers := AhkMagic._FindCallers(sec, openRva) for caller in callers { p := sec["ptr"] base := sec["rva"] off := caller - base + 5 limit := Min(sec["size"] - 5, off + 0x300) i := off while i < limit { isCmp := NumGet(p + i, "UChar") = 0x83 and NumGet(p + i + 1, "UChar") = 0xF8 and NumGet(p + i + 2, "UChar") = 0x03 isCmp2 := NumGet(p + i, "UChar") = 0x3D and NumGet(p + i + 1, "UChar") = 0x03 if isCmp or isCmp2 { j := i + 3 callLimit := Min(sec["size"] - 5, j + 0x200) while j < callLimit { if NumGet(p + j, "UChar") = 0xE8 { disp := NumGet(p + j + 1, "Int") target := base + j + 5 + disp if AhkMagic._PdataEntryStart(sec["pdata"], target) = target return target } j += 1 } break } i += 1 } } return 0 } static _LocateOpenInclude(secs) { text := AhkMagic._TextSection(secs) anchors := [ '%s file "%s" cannot be opened', '#Include' ] for anchor in anchors { hit := AhkMagic._FindUtf16(secs, anchor) if !hit.Length continue refs := AhkMagic._RipRefs(text, hit[1]) start := AhkMagic._BestStart(text, refs) if start return start } return 0 } static _LocateSrcCount(sec, openRva) { callers := AhkMagic._FindCallers(sec, openRva) for caller in callers { p := sec["ptr"] base := sec["rva"] off := caller - base i := off - 5 min := Max(0, off - 0x100) while i >= min { b := NumGet(p + i, "UChar") if b = 0x8B and (NumGet(p + i + 1, "UChar") = 0x2D or NumGet(p + i + 1, "UChar") = 0x1D or NumGet(p + i + 1, "UChar") = 0x35 or NumGet(p + i + 1, "UChar") = 0x3D) { disp := NumGet(p + i + 2, "Int") target := base + i + 6 + disp if target > 0x10000 { val := NumGet(AhkMagic.moduleBase + target, "Int") if val >= 0 and val < 100000 return target } } if b = 0x44 and NumGet(p + i + 1, "UChar") = 0x8B and (NumGet(p + i + 2, "UChar") = 0x2D or NumGet(p + i + 2, "UChar") = 0x3D) { disp := NumGet(p + i + 3, "Int") target := base + i + 7 + disp if target > 0x10000 { val := NumGet(AhkMagic.moduleBase + target, "Int") if val >= 0 and val < 100000 return target } } i -= 1 } } return 0 } static _LocateEvalScriptFunctions() { if AhkMagic.evalScriptLocated return AhkMagic._LocateInternalFunctions() secs := AhkMagic._ModuleSections() text := AhkMagic._TextSection(secs) postfixRva := AhkMagic.exprToPostfix - AhkMagic.moduleBase callers := AhkMagic._FindCallers(text, postfixRva) if !callers.Length throw Error("PreparseExpressions not found") preparse := AhkMagic._FnStart(text, callers[1]) preprocess := AhkMagic._LocatePreprocessFunc(text) if !preprocess throw Error("PreprocessLocalVars not found") open := AhkMagic._LocateOpenInclude(secs) if !open throw Error("OpenIncludedFile not found") loadTs := AhkMagic._LocateLoadTs(text, open) if !loadTs throw Error("LoadIncludedFile(TextStream) not found") srcCount := 0 if !RegExMatch(A_AhkVersion, "^2\.1") srcCount := AhkMagic._LocateSrcCount(text, open) gptr := AhkMagic._LocateGptr(text, preparse) if !gptr throw Error("g pointer not found") AhkMagic.evalPreparse := AhkMagic.moduleBase + preparse AhkMagic.evalPreprocess := AhkMagic.moduleBase + preprocess AhkMagic.evalOpenInclude := AhkMagic.moduleBase + open AhkMagic.evalLoadTs := AhkMagic.moduleBase + loadTs AhkMagic.evalSrcCount := srcCount ? AhkMagic.moduleBase + srcCount : 0 AhkMagic.evalGptr := AhkMagic.moduleBase + gptr AhkMagic.evalScriptLocated := true } static _EvalStructOffsets() { if !AhkMagic.evalStructOffsets AhkMagic._DiscoverEvalLayout() return AhkMagic.evalStructOffsets } ; Assemble the 21-slot layout array consumed by the eval machine-code ; blob. Every slot comes from runtime discovery; slots with no ; discovered equivalent are derived from a discovered anchor and ; validated by _ArgShapeInProc/_RemoteArgShape. static _BuildEvalLayout(structOffsets, shape) { layout := Buffer(21 * 8, 0) put := (slot, val) => NumPut("UInt64", val, layout, slot * 8) put(0, structOffsets["line_action"]) put(1, structOffsets["line_argc"]) put(2, 0) ; line number: never written, purely cosmetic in errors put(3, structOffsets["line_arg"]) put(4, shape["type"]) put(5, structOffsets["arg_expression"]) put(6, shape["len"]) put(7, shape["text"]) put(8, shape["deref"]) put(9, structOffsets["arg_postfix"]) put(10, shape["max_stack"]) put(11, shape["max_alloc"]) put(12, structOffsets["token_value"]) put(13, structOffsets["token_symbol"]) put(14, structOffsets["token_stride"]) put(15, structOffsets["token_usage"]) put(16, structOffsets["deref_marker"]) put(17, structOffsets["deref_var"]) put(18, structOffsets["deref_type"]) put(19, structOffsets["deref_len"]) put(20, Max(24, structOffsets["deref_len"] + 4 , structOffsets["deref_marker"] + 8 , structOffsets["deref_var"] + 8)) return layout } ; Fallback layout for remote evaluation when the target layout has not ; been discovered yet. The blob validates the assumed token layout ; before its first write and reports status 9 on a mismatch, so these ; defaults can never corrupt a target with a different layout. static _DefaultEvalLayout() { layout := Buffer(21 * 8, 0) vals := [0, 1, 0, 8, 0, 1, 4, 8, 16, 24, 32, 36 , 0, 16, 24, 8, 0, 8, 16, 20, 24] loop 21 NumPut("UInt64", vals[A_Index], layout, (A_Index - 1) * 8) return layout } ; Derive the ArgStruct fields from the discovered arg_postfix anchor and ; validate them against the live arg. text=postfix-16, len=postfix-12, ; deref=postfix-8, type=isExpr-1, max_stack/max_alloc=postfix+8/+12. ; Every derived field must be self-consistent on the live arg or ; discovery fails loudly. static _ArgShapeInProc(argPtr, postfixOff, isexprOff) { typeOff := isexprOff - 1 if typeOff < 0 or NumGet(argPtr, typeOff, "UChar") != 0 throw Error("arg type field validation failed", -1) textOff := postfixOff - 16 lenOff := postfixOff - 20 derefOff := postfixOff - 8 if textOff < 0 or lenOff < 0 or derefOff < 0 throw Error("arg field derivation out of range", -1) textPtr := NumGet(argPtr, textOff, "Ptr") if !AhkMagic._Readable(textPtr, 8) throw Error("arg text pointer unreadable", -1) alen := NumGet(argPtr, lenOff, "UInt") if alen = 0 or alen > 4096 throw Error("arg length field implausible: " alen, -1) if StrLen(StrGet(textPtr, alen, "UTF-16")) != alen throw Error("arg text/length mismatch", -1) dref := NumGet(argPtr, derefOff, "Ptr") if dref and !AhkMagic._Readable(dref, 24) throw Error("arg deref pointer unreadable", -1) return Map( "type", typeOff, "len", lenOff, "text", textOff, "deref", derefOff, "max_stack", postfixOff + 8, "max_alloc", postfixOff + 12) } ; Remote mirror of _ArgShapeInProc: find a live expression arg among the ; discovered functions and validate the derived field offsets by content. static _RemoteArgShape(h, layout, s) { for q in layout["funcs"] { try jump := AhkMagic._RPtr(h, q + layout["mjump_line_off"]) catch continue if !jump continue try argc := NumGet(AhkMagic._RemoteRead(h, jump + s["line_argc"], 1) , 0, "UChar") catch continue if !argc continue try argPtr := AhkMagic._RPtr(h, jump + s["line_arg"]) catch continue try postfix := AhkMagic._RPtr(h, argPtr + s["arg_postfix"]) catch continue if !postfix continue try { typeOff := s["arg_expression"] - 1 if typeOff < 0 continue if NumGet(AhkMagic._RemoteRead(h, argPtr + typeOff, 1) , 0, "UChar") != 0 continue textOff := s["arg_postfix"] - 16 lenOff := s["arg_postfix"] - 20 derefOff := s["arg_postfix"] - 8 if textOff < 0 or lenOff < 0 or derefOff < 0 continue textPtr := AhkMagic._RPtr(h, argPtr + textOff) alen := NumGet(AhkMagic._RemoteRead(h, argPtr + lenOff, 4) , 0, "UInt") if alen = 0 or alen > 4096 continue if StrLen(AhkMagic._RemoteReadString(h, textPtr, alen)) != alen continue dref := AhkMagic._RPtr(h, argPtr + derefOff) if dref AhkMagic._RemoteRead(h, dref, 24) return Map( "type", typeOff, "len", lenOff, "text", textOff, "deref", derefOff, "max_stack", s["arg_postfix"] + 8, "max_alloc", s["arg_postfix"] + 12) } catch continue } throw Error("remote arg shape not found", -1) } static _DiscoverCurrOff() { if AhkMagic.evalCurrOff return AhkMagic.evalCurrOff secs := AhkMagic._ModuleSections() text := AhkMagic._TextSection(secs) preparseRva := AhkMagic.evalPreparse - AhkMagic.moduleBase callers := AhkMagic._FindCallers(text, preparseRva) for caller in callers { p := text["ptr"] base := text["rva"] off := AhkMagic._FnStart(text, caller) - base limit := Min(text["size"] - 16, off + 0x4000) i := off while i < limit { if NumGet(p + i, "UChar") = 0xCC and NumGet(p + i + 1, "UChar") = 0xCC break if NumGet(p + i, "UChar") = 0x48 and NumGet(p + i + 1, "UChar") = 0x8B and NumGet(p + i + 2, "UChar") = 0x05 { j := i + 7 while j < Min(i + 24, limit) { if NumGet(p + j, "UChar") = 0x48 and (NumGet(p + j + 1, "UChar") = 0x89) and (NumGet(p + j + 2, "UChar") = 0x58 or NumGet(p + j + 2, "UChar") = 0x50) { AhkMagic.evalCurrOff := NumGet(p + j + 3, "UChar") return AhkMagic.evalCurrOff } j += 1 } } i += 1 } } throw Error("g->curr offset not found") } static _DiscoverEvalLayout() { if AhkMagic.evalLayoutLocated return AhkMagic._LocateEvalScriptFunctions() AhkMagic._DiscoverCurrOff() gScript := AhkMagic.gScript g := NumGet(AhkMagic.evalGptr, "Ptr") savedProbeCur := NumGet(g, AhkMagic.evalCurrOff, "Ptr") gSnap := Buffer(0x100) DllCall("RtlMoveMemory", "Ptr", gSnap.Ptr, "Ptr", g, "UPtr", 0x100) NumPut("Ptr", 0, g, AhkMagic.evalCurrOff) snap := Buffer(0x200) DllCall("RtlMoveMemory", "Ptr", snap.Ptr, "Ptr", gScript, "UPtr", 0x200) oldSrcCount := AhkMagic.evalSrcCount ? NumGet(AhkMagic.evalSrcCount, "Int") : 0 ; The probe function name is randomized so it can never collide with ; a function the host script already defines. probeName := "ahkHackLayoutProbe" Format("{:x}", A_TickCount) probe := probeName "() {`n return A_Args.Length + 1`n}`n" ; The probe really loads a function and mutates interpreter state. ; Every failure below must restore the snapshot, otherwise the host ; interpreter is left with a stray function, a cleared g->curr and a ; modified source count. try { AhkMagic._LoadScriptMemory(probe) countOff := 0 loop 0x200 // 4 { off := (A_Index - 1) * 4 before := NumGet(snap, off, "Int") after := NumGet(gScript, off, "Int") if after = before + 1 and before >= 0 and after > 0 { countOff := off break } } if !countOff throw Error("probe load did not register a function: " . "TextStream layout validation failed (mFuncsCount " . "offset not found after rc=1 load)", -1) oldCount := NumGet(snap, countOff, "Int") funcsOff := 0 loop 0x200 // 8 { off := (A_Index - 1) * 8 ; gScript[0] is the variable array on every known build, and ; funcsOff=0 is also the "not found" sentinel below, so it ; must never participate in the match. if off = 0 continue p := NumGet(gScript, off, "Ptr") if !(p > 0x10000 and p < 0x7fffffffffff) continue if DllCall("IsBadReadPtr", "Ptr", p, "UPtr", 0x100) continue newFunc := NumGet(p, oldCount * 8, "Ptr") ; The new entry plus its two predecessors must all look like ; 8-aligned objects: unrelated heap garbage adjacent to other ; arrays must not impersonate mFuncs. ok := newFunc > 0x10000 and newFunc < 0x7fffffffffff and Mod(newFunc, 8) = 0 if ok and oldCount >= 2 { prev := NumGet(p, (oldCount - 1) * 8, "Ptr") prev2 := NumGet(p, (oldCount - 2) * 8, "Ptr") ok := prev > 0x10000 and prev < 0x7fffffffffff and Mod(prev, 8) = 0 and prev2 > 0x10000 and prev2 < 0x7fffffffffff and Mod(prev2, 8) = 0 } if ok { funcsOff := off break } } if !funcsOff throw Error("mFuncs offset not found") lastOff := -1 loop 0x200 // 8 { off := (A_Index - 1) * 8 if off = funcsOff continue before := NumGet(snap, off, "Ptr") after := NumGet(gScript, off, "Ptr") if before != after and after > 0x10000 { lastOff := off break } } if lastOff < 0 { detail := "count=" countOff " funcs=" funcsOff loop 0x200 // 8 { off := (A_Index - 1) * 8 b := NumGet(snap, off, "Ptr") a := NumGet(gScript, off, "Ptr") if b != a detail .= " @" Format("{:X}", off) " " b "->" a . " bad=" DllCall("IsBadReadPtr", "Ptr", a, "UPtr", 0x40) } throw Error("mLastLine offset not found: " detail) } arrPtr := NumGet(gScript, funcsOff, "Ptr") newFunc := NumGet(arrPtr, oldCount * 8, "Ptr") oldLast := NumGet(snap, lastOff, "Ptr") newLast := NumGet(gScript, lastOff, "Ptr") jumpOff := 0 jumpLine := 0 loop 0x200 // 8 { off := (A_Index - 1) * 8 p := NumGet(newFunc, off, "Ptr") if p <= 0x10000 or p >= 0x7fffffffffff continue try { lineData := Buffer(0x100) DllCall("RtlMoveMemory", "Ptr", lineData.Ptr, "Ptr", p , "UPtr", 0x100) } catch continue found := false loop lineData.Size // 8 { if NumGet(lineData, (A_Index - 1) * 8, "Ptr") = newFunc { found := true break } } if found { jumpOff := off jumpLine := p break } } if !jumpOff throw Error("mJumpLine offset not found") structOffsets := AhkMagic._DiscoverInProcStructs( gScript, arrPtr, oldCount, newFunc, jumpOff) ; Derive and validate the remaining ArgStruct fields from the ; probe's own expression arg, then assemble the layout array the ; eval blob consumes. probeArg := NumGet(jumpLine, structOffsets["line_arg"], "Ptr") if !probeArg throw Error("probe arg not found", -1) argShape := AhkMagic._ArgShapeInProc(probeArg , structOffsets["arg_postfix"] , structOffsets["arg_expression"]) AhkMagic.evalStructOffsets := structOffsets AhkMagic.evalLayoutArr := AhkMagic._BuildEvalLayout( structOffsets, argShape) AhkMagic.evalMFuncsOff := funcsOff AhkMagic.evalMFuncsCountOff := countOff AhkMagic.evalMLastLineOff := lastOff AhkMagic.evalMJumpLineOff := jumpOff } finally { ; Always roll the interpreter back to the pre-probe snapshot, ; whether discovery succeeded or not. DllCall("RtlMoveMemory", "Ptr", gScript, "Ptr", snap.Ptr, "UPtr", 0x200) if AhkMagic.evalSrcCount NumPut("Int", oldSrcCount, AhkMagic.evalSrcCount) DllCall("RtlMoveMemory", "Ptr", g, "Ptr", gSnap.Ptr, "UPtr", 0x100) NumPut("Ptr", savedProbeCur, g, AhkMagic.evalCurrOff) } AhkMagic._DiscoverParserOffsets() AhkMagic.evalLayoutLocated := true } ; Discover the parser-state field offsets in THIS process. Both known ; region candidates (sentinel-anchored and qCmp-anchored) are built and ; validated against the live Script object; the strictly-validating ; candidate wins. No version family is assumed. static _DiscoverParserOffsets() { if AhkMagic.evalParserLocated return secs := AhkMagic._ModuleSections() text := AhkMagic._TextSection(secs) p := text["ptr"] base := text["rva"] off := AhkMagic.evalLoadTs - AhkMagic.moduleBase - base limit := Min(text["size"] - 8, off + 0x100) qCmp := -1 dCmp := -1 i := off while i < limit { b0 := NumGet(p + i, "UChar") if b0 = 0x48 and NumGet(p + i + 1, "UChar") = 0x83 { modrm := NumGet(p + i + 2, "UChar") if modrm = 0x79 and NumGet(p + i + 4, "UChar") = 0 { if qCmp < 0 qCmp := NumGet(p + i + 3, "UChar") } else if modrm = 0xB9 and NumGet(p + i + 7, "UChar") = 0 { if qCmp < 0 qCmp := NumGet(p + i + 3, "Int") } } if b0 = 0x83 and NumGet(p + i + 1, "UChar") = 0xB9 and (i = off or NumGet(p + i - 1, "UChar") != 0x48) and NumGet(p + i + 6, "UChar") = 0 { if dCmp < 0 dCmp := NumGet(p + i + 2, "Int") } if qCmp >= 0 and dCmp >= 0 break i += 1 } if qCmp < 0 or dCmp < 0 throw Error("parser state anchors not found") ; Validate both candidates against the live Script object and pick ; the strictly-validating one. gScript := AhkMagic.gScript buf := Buffer(0x800) DllCall("RtlMoveMemory", "Ptr", buf.Ptr, "Ptr", gScript, "UPtr", 0x800) candidates := [] if dCmp > 0 and dCmp + 4 <= buf.Size and NumGet(buf, dCmp, "Int") >= 0 and NumGet(buf, dCmp, "Int") <= 100000 { exprIndexOff := 0 loop Min(0x40, dCmp) // 4 { coff := dCmp - (A_Index - 1) * 4 if coff + 8 > buf.Size continue if NumGet(buf, coff, "Int") = 0x7fffffff and NumGet(buf, coff + 4, "Int") = 0 { exprIndexOff := coff + 4 break } } if exprIndexOff and exprIndexOff + 8 <= buf.Size candidates.Push(Map( "rich", true, "fields", Map( "mclass_count", dCmp, "mline_parent", exprIndexOff - 0x28, "mpending_related", exprIndexOff - 0x20, "mlast_param_init", exprIndexOff - 0x18, "mpending_hotkey", exprIndexOff - 0x10, "mexpr_func", exprIndexOff - 8, "mexpr_func_index", exprIndexOff, "mnext_func_body", exprIndexOff + 4, "mignore_block", exprIndexOff + 5, "mbackcompat", exprIndexOff + 6, "mcurrent_module", exprIndexOff - 0x48, "mlast_module", exprIndexOff - 0x40))) } if qCmp > 0 and qCmp + 0x28 <= buf.Size candidates.Push(Map( "fields", Map( "mopen", qCmp, "mpending_parent", qCmp + 8, "mpending_related", qCmp + 16, "mlast_param_init", qCmp + 24, "mnext_func_body", qCmp + 32, "mclass_count", dCmp))) best := 0 bestScore := -1 for cand in candidates { fields := cand["fields"] valid := true for key, foff in fields { ; Value-level checks are deliberately absent: the parser ; fields are saved and restored verbatim, so any plausible ; offset round-trips harmlessly. The only hard requirement ; is that every offset stays inside the Script object. if !(foff is Integer) or foff <= 0 or foff >= 0x800 or foff + 8 > buf.Size { valid := false break } } if valid { ; The sentinel-anchored candidate has the richer field set ; and only exists when its anchor was actually found. score := cand.Has("rich") ? 10 : 0 if score > bestScore { bestScore := score best := cand["fields"] } } } if !best throw Error("parser state layout candidates all failed validation" , -1) AhkMagic.evalParser := best AhkMagic.evalParserLocated := true } static EvalNative(expr) { AhkMagic.Init() if !(expr is String) throw TypeError("expr must be a string", -1) if StrLen(expr) > 4096 throw ValueError("expr too long", -1) if !AhkMagic.inprocEval AhkMagic.inprocEval := MCode(MC_INPROC_EVAL_X64) AhkMagic._LocateInternalFunctions() ; The eval blob takes every Line/ArgStruct/token/deref offset from a ; runtime-discovered layout array; discover it once and reuse. AhkMagic._DiscoverEvalLayout() layoutArr := AhkMagic.evalLayoutArr if !layoutArr throw Error("eval layout not discovered", -1) scratch := Buffer(8 * 1024 * 1024, 0) out := Buffer(512, 0) exprBuf := Buffer((StrLen(expr) + 1) * 2, 0) StrPut(expr, exprBuf, "UTF-16") rc := DllCall( AhkMagic.inprocEval.Ptr, "Ptr", AhkMagic.exprToPostfix, "Ptr", AhkMagic.expandSingleArg, "Ptr", AhkMagic.currLineSlot, "Ptr", scratch.Ptr, "Ptr", exprBuf.Ptr, "Ptr", out.Ptr, "Int", 1, "Ptr", 0, "Ptr", 0, "Ptr", AhkMagic.gScript, "Ptr", AhkMagic.finalizeExpr, "Ptr", AhkMagic.findOrAddVar, "Ptr", AhkMagic.crtFree, "Ptr", AhkMagic.symInvalid, "Ptr", layoutArr.Ptr, "Int" ) if rc != 0 throw Error("EvalNative harness failed with rc=" rc) status := NumGet(out, 0, "UInt") if status = 9 throw Error("EvalNative token layout mismatch; " . "unsupported runtime layout", -1) if status = 10 throw Error("EvalNative layout validation failed", -1) if status != 0 throw Error("EvalNative failed with status=" status) type := NumGet(out, 4, "UInt") if type = 1 return NumGet(out, 8, "Int64") if type = 2 return NumGet(out, 8, "Double") if type = 0 return StrGet(NumGet(out, 16, "Ptr"), "UTF-16") if type = 5 { ; SYM_OBJECT: wrap the interpreter's live object. The eval ; harness never finalizes its result token, so the object stays ; alive; the reference returned here belongs to the caller. ptr := NumGet(out, 8, "Ptr") if !ptr throw Error("EvalNative returned a null object", -1) return ObjFromPtrAddRef(ptr) } throw Error("EvalNative returned unknown symbol " type) } static Summary() { AhkMagic.Init() return Format( "module=0x{:X}`nbif={} @0x{:X} (stride 0x{:X})`n" . "mdfunc={} @0x{:X} (stride 0x{:X})`n" . "biv={} @0x{:X} (stride 0x{:X})", AhkMagic.moduleBase, AhkMagic.bifCount, AhkMagic.bifTablePtr, AhkMagic.bifStride, AhkMagic.mdfuncCount, AhkMagic.mdfuncTablePtr, AhkMagic.mdfuncStride, AhkMagic.bivCount, AhkMagic.bivTablePtr, AhkMagic.bivStride ) } static Eval(expr) { AhkMagic.Init() if !(expr is String) throw TypeError("expr must be a string", -1) if expr = "" throw ValueError("expr must not be empty", -1) if RegExMatch(expr, "[\r\n]") return AhkMagic.EvalScript(expr) try { return AhkMagic.EvalNative(expr) } catch as e { ; Expressions the in-process pipeline cannot evaluate yet fall ; back to the explicit subprocess implementation. } return AhkMagic.EvalSubprocess(expr) } static EvalScript(text) { AhkMagic.Init() if !(text is String) throw TypeError("text must be a string", -1) if Trim(text) = "" throw ValueError("text must not be empty", -1) ; Pre-check with the interpreter's own /validate switch so a syntax ; error becomes a clean library error carrying the real parse ; message. The validator captures its output and kills its process ; tree on timeout, so it never leaves a dialog on screen; the loader ; rc check below remains as the fallback guard. A compiled host has ; no separate interpreter binary, so it keeps the fallback only. if !A_IsCompiled AhkMagic._ValidateScript(text, A_AhkPath) AhkMagic._DiscoverEvalLayout() off := AhkMagic._EvalStructOffsets() currOff := AhkMagic.evalCurrOff mfuncsOff := AhkMagic.evalMFuncsOff mfuncsCountOff := AhkMagic.evalMFuncsCountOff mlastLineOff := AhkMagic.evalMLastLineOff mjumpLineOff := AhkMagic.evalMJumpLineOff last := "" for line in StrSplit(text, "`n", "`r") { t := Trim(line) if t = "" continue if RegExMatch(t, "^(if|else|for|while|loop|try|catch|finally|return|break|continue|class|static|global|local|throw)\b") continue if SubStr(t, -1) = "{" continue last := t } if last = "" throw ValueError("no expression result found in script text", -1) gScript := AhkMagic.gScript oldLast := NumGet(gScript, mlastLineOff, "Ptr") oldFuncCount := NumGet(gScript, mfuncsCountOff, "Int") g := NumGet(AhkMagic.evalGptr, "Ptr") savedCur := NumGet(g, currOff, "Ptr") parser := AhkMagic.evalParser savedState := [] for key in ["mopen", "mpending_parent", "mline_parent", "mpending_related" , "mlast_param_init", "mpending_hotkey", "mexpr_func", "mcurrent_module"] { if parser.Has(key) savedState.Push([key, "Ptr", NumGet(gScript, parser[key], "Ptr")]) } for key in ["mexpr_func_index", "mclass_count"] { if parser.Has(key) savedState.Push([key, "Int", NumGet(gScript, parser[key], "Int")]) } for key in ["mnext_func_body", "mignore_block", "mbackcompat"] { if parser.Has(key) savedState.Push([key, "UChar", NumGet(gScript, parser[key], "UChar")]) } try { NumPut("Ptr", 0, g, currOff) if !AhkMagic.memScriptLoader AhkMagic.memScriptLoader := MCode(MC_MEM_SCRIPT_X64) textBuf := Buffer((StrLen(text) + 1) * 2, 0) StrPut(text, textBuf, "UTF-16") scratch := Buffer(0x400, 0) rc := DllCall(AhkMagic.memScriptLoader.Ptr , "Ptr", AhkMagic.evalLoadTs , "Ptr", gScript , "Ptr", AhkMagic.evalSrcCount , "Ptr", textBuf.Ptr , "UInt", StrLen(text) * 2 , "Ptr", scratch.Ptr , "Int") if rc != 0 throw Error("LoadIncludedFile(memory) failed with rc=" rc) funcsItem := NumGet(gScript, mfuncsOff, "Ptr") funcCount := NumGet(gScript, mfuncsCountOff, "Int") ; The count drives raw pointer walks below; refuse values that ; cannot come from one script injection. if funcCount < oldFuncCount or funcCount > oldFuncCount + 4096 throw Error("mFuncs count changed unexpectedly: " . oldFuncCount " -> " funcCount, -1) if funcCount > oldFuncCount { firstNew := oldLast ? NumGet(oldLast, off["line_next"], "Ptr") : 0 if firstNew { rcTail := DllCall(AhkMagic.evalPreparse , "Ptr", gScript , "Ptr", firstNew , "Int") if rcTail != 1 throw Error("PreparseExpressions(tail) failed with rc=" rcTail) } loop funcCount - oldFuncCount { idx := oldFuncCount + A_Index - 1 newFunc := NumGet(funcsItem + idx * 8, "Ptr") jump := NumGet(newFunc, mjumpLineOff, "Ptr") if !jump continue rc2 := DllCall(AhkMagic.evalPreparse , "Ptr", gScript , "Ptr", jump , "Int") if rc2 != 1 throw Error("PreparseExpressions failed with rc=" rc2) NumPut("Ptr", newFunc, g, currOff) line := jump lineSteps := 0 while line { if lineSteps > 100000 throw Error("line chain too long; layout likely wrong", -1) lineSteps += 1 if NumGet(line, off["line_action"], "UChar") = 3 and NumGet(line, off["line_attribute"], "Ptr") NumPut("Ptr", NumGet(line, off["line_attribute"], "Ptr") , g, currOff) argc := NumGet(line, off["line_argc"], "UChar") if argc { arg := NumGet(line, off["line_arg"], "Ptr") if arg and NumGet(arg, off["arg_expression"] , "UChar") { postfix := NumGet(arg, off["arg_postfix"], "Ptr") if postfix { tokenSteps := 0 while NumGet(postfix, off["token_symbol"], "UInt") != AhkMagic.symInvalid { if tokenSteps > 100000 throw Error("postfix token chain too long; layout likely wrong", -1) tokenSteps += 1 if NumGet(postfix, off["token_symbol"], "UInt") = 4 and NumGet(postfix, off["token_usage"], "UInt") < 3 { deref := NumGet(postfix, off["token_value"], "Ptr") if deref { derefType := NumGet(deref , off["deref_type"], "UChar") marker := NumGet(deref, off["deref_marker"], "Ptr") len := NumGet(deref, off["deref_len"], "UInt") if derefType = 7 { NumPut("Ptr", NumGet(deref , off["deref_var"], "Ptr") , postfix, off["token_value"]) } else if derefType = 0 and marker and len > 0 and len <= 64 { var := DllCall(AhkMagic.findOrAddVar , "Ptr", gScript , "Ptr", marker , "UPtr", len , "UInt", 0x103 , "Ptr") if var NumPut("Ptr", var, postfix, off["token_value"]) } } } postfix += off["token_stride"] } } } } line := NumGet(line, off["line_next"], "Ptr") } NumPut("Ptr", savedCur, g, currOff) rc4 := DllCall(AhkMagic.evalPreprocess , "Ptr", gScript , "Ptr", newFunc , "Int") if rc4 != 1 throw Error("PreprocessLocalVars failed with rc=" rc4) } } ; Detach the newly added lines from the running line list so the ; active execution cannot continue into them. The new function ; remains reachable through mFuncs. The tail was already ; preparsed through firstNew above, so detaching now is safe. if oldLast and off.Has("line_next") and mlastLineOff >= 0 { NumPut("Ptr", 0, oldLast, off["line_next"]) NumPut("Ptr", oldLast, gScript, mlastLineOff) } NumPut("Ptr", 0, g, currOff) r := AhkMagic.EvalNative(last) NumPut("Ptr", savedCur, g, currOff) return r } finally { for item in savedState NumPut(item[2], item[3], gScript, parser[item[1]]) NumPut("Ptr", savedCur, g, currOff) } } static _LoadScriptMemory(text) { if !AhkMagic.memScriptLoader AhkMagic.memScriptLoader := MCode(MC_MEM_SCRIPT_X64) gScript := AhkMagic.gScript textBuf := Buffer((StrLen(text) + 1) * 2, 0) StrPut(text, textBuf, "UTF-16") scratch := Buffer(0x400, 0) rc := DllCall(AhkMagic.memScriptLoader.Ptr , "Ptr", AhkMagic.evalLoadTs , "Ptr", gScript , "Ptr", AhkMagic.evalSrcCount , "Ptr", textBuf.Ptr , "UInt", StrLen(text) * 2 , "Ptr", scratch.Ptr , "Int") if rc != 0 throw Error("LoadIncludedFile(memory) failed with rc=" rc) } ; ------------------------------------------------------------------ ; Remote attach: read the three interpreter tables from another ; AutoHotkey process without injecting anything. ; ------------------------------------------------------------------ static _RemoteOpen(pid, write := false) { access := 0x410 if write access := 0x410 | 0x28 h := DllCall("OpenProcess", "UInt", access, "Int", 0, "UInt", pid, "Ptr") if !h throw Error("OpenProcess failed", -1) return h } static _RemoteModuleBase(h, pid) { hSnap := DllCall("CreateToolhelp32Snapshot", "UInt", 0x18, "UInt", pid, "Ptr") if hSnap = -1 or !hSnap throw Error("CreateToolhelp32Snapshot failed", -1) buf := Buffer(1080) NumPut("UInt", 1080, buf, 0) best := Map("base", 0, "path", "") try { if !DllCall("Module32FirstW", "Ptr", hSnap, "Ptr", buf.Ptr) throw Error("Module32FirstW failed", -1) loop { modBase := NumGet(buf, 24, "Ptr") modName := StrGet(buf.Ptr + 48, 256, "UTF-16") modPath := StrGet(buf.Ptr + 560, 260, "UTF-16") if !best["base"] or InStr(modName, "AutoHotkey", false) { best := Map("base", modBase, "path", modPath) if InStr(modName, "AutoHotkey", false) break } if !DllCall("Module32NextW", "Ptr", hSnap, "Ptr", buf.Ptr) break } } finally { DllCall("CloseHandle", "Ptr", hSnap) } if !best["base"] throw Error("module base not found", -1) return best } static _RemotePidByName(name) { hSnap := DllCall("CreateToolhelp32Snapshot", "UInt", 0x2, "UInt", 0, "Ptr") if hSnap = -1 or !hSnap throw Error("CreateToolhelp32Snapshot failed", -1) buf := Buffer(568) NumPut("UInt", 568, buf, 0) pid := 0 try { if !DllCall("Process32FirstW", "Ptr", hSnap, "Ptr", buf.Ptr) throw Error("Process32FirstW failed", -1) loop { exe := StrGet(buf.Ptr + 44, 260, "UTF-16") if StrCompare(exe, name, false) = 0 { pid := NumGet(buf, 8, "UInt") break } if !DllCall("Process32NextW", "Ptr", hSnap, "Ptr", buf.Ptr) break } } finally { DllCall("CloseHandle", "Ptr", hSnap) } if !pid throw Error("process not found: " name, -1) return pid } static _RemoteRead(h, addr, size) { buf := Buffer(size) read := 0 if !DllCall("ReadProcessMemory", "Ptr", h, "Ptr", addr, "Ptr", buf.Ptr , "UPtr", size, "UPtr*", &read) throw Error("ReadProcessMemory failed at 0x" Format("{:X}", addr), -1) ; A partial success silently returns a half-garbage buffer and ; corrupts every downstream parse; fail loudly instead. if read != size throw Error("ReadProcessMemory short read at 0x" . Format("{:X}", addr) ": " read "/" size, -1) return buf } static _RemoteSections(h, base) { dos := AhkMagic._RemoteRead(h, base, 0x40) e_lfanew := NumGet(dos, 0x3C, "UInt") pe := AhkMagic._RemoteRead(h, base + e_lfanew, 0x18) if NumGet(pe, 0, "UInt") != 0x4550 throw Error("PE signature not found", -1) num := NumGet(pe, 6, "UShort") optSize := NumGet(pe, 20, "UShort") secTable := base + e_lfanew + 24 + optSize hdrs := AhkMagic._RemoteRead(h, secTable, num * 40) secs := Map() hasText := false hasData := false hasPdata := false loop num { off := (A_Index - 1) * 40 name := StrGet(hdrs.Ptr + off, 8, "UTF-8") vsize := NumGet(hdrs, off + 8, "UInt") va := NumGet(hdrs, off + 12, "UInt") rawSize := NumGet(hdrs, off + 16, "UInt") size := vsize ? vsize : rawSize if name = ".text" hasText := true if name = ".rdata" or name = ".data" hasData := true if name = ".pdata" hasPdata := true secs[name] := Map("name", name, "rva", va, "size", size , "start", base + va, "end", base + va + size , "data", Buffer(0), "ptr", 0) } strict := hasText and hasData for _, sec in secs { if strict and sec["name"] != ".text" and sec["name"] != ".rdata" and sec["name"] != ".data" continue if sec["name"] = ".rsrc" continue if sec["size"] > 0 and sec["size"] < 0x800000 { try sec["data"] := AhkMagic._RemoteRead(h, sec["start"], sec["size"]) catch sec["data"] := Buffer(0) sec["ptr"] := sec["data"].Ptr } } pdata := 0 if hasPdata { sec := secs[".pdata"] if sec["size"] > 0 and sec["size"] < 0x800000 { try { sec["data"] := AhkMagic._RemoteRead(h, sec["start"] , sec["size"]) sec["ptr"] := sec["data"].Ptr pdata := sec } catch pdata := 0 } } for _, sec in secs sec["pdata"] := pdata return secs } static _RemoteReadUtf16(secs, addr, maxLen := 64) { for _, sec in secs { if addr >= sec["start"] and addr < sec["end"] and sec["data"].Size > 0 { off := addr - sec["start"] if off < sec["data"].Size return StrGet(sec["data"].Ptr + off, maxLen, "UTF-16") } } return "" } static _RemoteStrValid(secs, addr) { text := AhkMagic._RemoteReadUtf16(secs, addr, 64) if text = "" or StrLen(text) > 64 return false return RegExMatch(text, "^[\x20-\x7E]+$") ? true : false } static _RemotePtrInText(secs, ptr) { hasText := false for _, sec in secs if sec["name"] = ".text" hasText := true for _, sec in secs { if ptr < sec["start"] or ptr >= sec["end"] continue if sec["name"] = ".text" return true if !hasText and sec["name"] != ".rsrc" return true } return false } static _RemoteTable(h, secs, base, kind) { if kind = "bif" { stride := 0x20 anchor := "Abs" } else if kind = "mdfunc" { stride := 0x28 anchor := "BlockInput" } else { stride := 0x18 anchor := "AhkPath" } bestStart := 0 bestCount := 0 for _, sec in secs { if sec["data"].Size < 32 continue p := sec["data"].Ptr maxOff := sec["data"].Size - 16 off := 0 while off <= maxOff { namePtr := NumGet(p + off, "Ptr") fnPtr := NumGet(p + off + 8, "Ptr") if AhkMagic._RemoteStrValid(secs, namePtr) and AhkMagic._RemotePtrInText(secs, fnPtr) { name := AhkMagic._RemoteReadUtf16(secs, namePtr, 64) if name = anchor { count := 1 prevName := name q := off + stride while q + 16 <= sec["data"].Size { qNamePtr := NumGet(p + q, "Ptr") qFnPtr := NumGet(p + q + 8, "Ptr") if !AhkMagic._RemoteStrValid(secs, qNamePtr) or !AhkMagic._RemotePtrInText(secs, qFnPtr) break qName := AhkMagic._RemoteReadUtf16(secs, qNamePtr, 64) if StrCompare(qName, prevName, false) < 0 break count += 1 prevName := qName q += stride } if count > bestCount { bestCount := count bestStart := sec["rva"] + off } } } off += 8 } } empty := Map("found", false, "stride", stride, "count", 0 , "entries", Map(), "names", []) if bestCount < 10 return empty sec := 0 for _, s in secs if bestStart >= s["rva"] and bestStart < s["rva"] + s["size"] { sec := s break } if !sec or sec["data"].Size = 0 return empty entries := Map() names := [] off := bestStart - sec["rva"] loop bestCount { p := sec["data"].Ptr + off namePtr := NumGet(p, "Ptr") name := AhkMagic._RemoteReadUtf16(secs, namePtr, 64) if kind = "bif" { entries[name] := Map( "rva", NumGet(p + 8, "Ptr") - base, "min", NumGet(p + 16, "UChar"), "max", NumGet(p + 17, "UChar"), "fid", NumGet(p + 18, "UChar")) } else if kind = "mdfunc" { entries[name] := Map( "rva", NumGet(p + 8, "Ptr") - base, "ret", NumGet(p + 16, "UChar")) } else { getter := NumGet(p + 8, "Ptr") setter := NumGet(p + 16, "Ptr") entries[name] := Map( "getter_rva", getter - base, "setter_rva", setter ? setter - base : 0) } names.Push(name) off += stride } return Map("found", true, "table_rva", bestStart, "stride", stride , "count", bestCount, "entries", entries, "names", names) } static _RemoteLocateInternal(secs, base) { text := AhkMagic._TextSection(secs) if !text["ptr"] throw Error("remote text section not loaded", -1) postfixRefs := AhkMagic._RipRefs(text , AhkMagic._FindUtf16(secs, "Missing operand.")[1]) postfix2 := AhkMagic._BestStart(text, postfixRefs) expandRefs := AhkMagic._RipRefs(text , AhkMagic._FindUtf16(secs, "Error evaluating expression.")[1]) expand := AhkMagic._BestStart(text, expandRefs) if !postfix2 or !expand throw Error("remote expression functions not found", -1) return Map("postfix_rva", postfix2, "expand_rva", expand) } static _RemoteCurrLineSlot(secs, base, getterRva) { text := AhkMagic._TextSection(secs) p := text["ptr"] off := getterRva - text["rva"] if NumGet(p + off, "UChar") != 0x48 or NumGet(p + off + 1, "UChar") != 0x8B or NumGet(p + off + 2, "UChar") != 0x05 throw Error("unexpected remote A_LineNumber getter code", -1) disp := NumGet(p + off + 3, "Int") return base + getterRva + 7 + disp } static _HexBuffer(hex) { size := StrLen(hex) // 2 buf := Buffer(size) loop size { byte := Integer("0x" SubStr(hex, 2 * A_Index - 1, 2)) NumPut("UChar", byte, buf, A_Index - 1) } return buf } static _RemoteWrite(h, addr, buf) { written := 0 if !DllCall("WriteProcessMemory", "Ptr", h, "Ptr", addr , "Ptr", buf.Ptr, "UPtr", buf.Size, "UPtr*", &written) throw Error("WriteProcessMemory failed", -1) if written != buf.Size throw Error("WriteProcessMemory short write at 0x" . Format("{:X}", addr) ": " written "/" buf.Size, -1) } static _RemoteReadString(h, addr, maxLen := 4096) { out := Buffer(0) chunk := 256 loop { if out.Size >= maxLen break try part := AhkMagic._RemoteRead(h, addr + out.Size, chunk) catch break if part.Size = 0 break combined := Buffer(out.Size + part.Size) if out.Size DllCall("RtlMoveMemory", "Ptr", combined.Ptr, "Ptr", out.Ptr , "UPtr", out.Size) DllCall("RtlMoveMemory", "Ptr", combined.Ptr + out.Size , "Ptr", part.Ptr, "UPtr", part.Size) out := combined found := -1 loop out.Size // 2 { if NumGet(out, (A_Index - 1) * 2, "UShort") = 0 { found := A_Index - 1 break } } if found >= 0 return StrGet(out.Ptr, found, "UTF-16") } return StrGet(out.Ptr, "UTF-16") } static AttachRemote(pid) { if !(pid is Integer) throw TypeError("pid must be an integer", -1) h := AhkMagic._RemoteOpen(pid, false) try { mod := AhkMagic._RemoteModuleBase(h, pid) secs := AhkMagic._RemoteSections(h, mod["base"]) bif := AhkMagic._RemoteTable(h, secs, mod["base"], "bif") mdfunc := AhkMagic._RemoteTable(h, secs, mod["base"], "mdfunc") biv := AhkMagic._RemoteTable(h, secs, mod["base"], "biv") internal := Map() try { loc := AhkMagic._RemoteLocateInternal(secs, mod["base"]) textSec := AhkMagic._TextSection(secs) loc["text_rva"] := textSec["rva"] loc["text_size"] := textSec["size"] if biv["found"] and biv["entries"].Has("LineNumber") { loc["curr_line_slot"] := AhkMagic._RemoteCurrLineSlot( secs, mod["base"] , biv["entries"]["LineNumber"]["getter_rva"]) internal := loc } } catch as e { internal := Map("error", e.What " | " e.Message) } return Map( "pid", pid, "module", mod["path"], "image_base", mod["base"], "builtins", bif, "native_functions", mdfunc, "builtin_vars", biv, "internal", internal ) } finally { DllCall("CloseHandle", "Ptr", h) } } static AttachRemoteByName(name) { if !(name is String) throw TypeError("name must be a string", -1) pid := AhkMagic._RemotePidByName(name) return AhkMagic.AttachRemote(pid) } static RemoteRedirect(hook, name, newName) { if !(hook is Map) or !hook.Has("pid") throw TypeError("hook must be an AttachRemote result", -1) bif := hook["builtins"] if !bif["found"] or !bif["entries"].Has(name) or !bif["entries"].Has(newName) throw Error("builtin redirect names not found", -1) idx := 0 for n in bif["names"] { if n = name break idx += 1 } if idx >= bif["names"].Length throw Error("builtin index not found", -1) h := AhkMagic._RemoteOpen(hook["pid"], true) try { fnSlot := hook["image_base"] + bif["table_rva"] + idx * bif["stride"] + 8 newPtr := hook["image_base"] + bif["entries"][newName]["rva"] data := Buffer(8) NumPut("Ptr", newPtr, data, 0) written := 0 if !DllCall("WriteProcessMemory", "Ptr", h, "Ptr", fnSlot , "Ptr", data.Ptr, "UPtr", 8, "UPtr*", &written) throw Error("WriteProcessMemory failed", -1) } finally { DllCall("CloseHandle", "Ptr", h) } return Map("src", name, "dst", newName, "fn_slot", fnSlot) } static RemoteDeepRedirect(hook, name, newName) { if !(hook is Map) or !hook.Has("pid") throw TypeError("hook must be an AttachRemote result", -1) bif := hook["builtins"] if !bif["found"] or !bif["entries"].Has(name) or !bif["entries"].Has(newName) throw Error("builtin redirect names not found", -1) idx := 0 for n in bif["names"] { if n = name break idx += 1 } if idx >= bif["names"].Length throw Error("builtin index not found", -1) h := AhkMagic._RemoteOpen(hook["pid"], true) try { slot := hook["image_base"] + bif["table_rva"] + idx * bif["stride"] slotBuf := AhkMagic._RemoteRead(h, slot, 16) namePtr := NumGet(slotBuf, 0, "Ptr") oldFn := NumGet(slotBuf, 8, "Ptr") newFn := hook["image_base"] + bif["entries"][newName]["rva"] ; The vtable check below needs the module image bounds; read ; SizeOfImage from the target's PE header once. It sits at ; optional-header offset 56 (i.e. 24 + 56 into the PE header). dos := AhkMagic._RemoteRead(h, hook["image_base"], 0x40) peOff := NumGet(dos, 0x3C, "UInt") pe := AhkMagic._RemoteRead(h, hook["image_base"] + peOff, 0x80) imageSize := NumGet(pe, 80, "UInt") imageEnd := hook["image_base"] + imageSize patches := [] addr := 0 mbi := Buffer(48) loop { if !DllCall("VirtualQueryEx", "Ptr", h, "Ptr", addr , "Ptr", mbi.Ptr, "UPtr", 48) break state := NumGet(mbi, 32, "UInt") protect := NumGet(mbi, 36, "UInt") memType := NumGet(mbi, 40, "UInt") regionSize := NumGet(mbi, 24, "Int64") baseAddr := NumGet(mbi, 0, "Ptr") if state = 0x1000 and memType = 0x20000 and (protect & 0x4 or protect & 0x40) and regionSize > 0 and regionSize < 0x8000000 { chunkSize := 0x10000 off := 0 while off < regionSize { want := Min(chunkSize, regionSize - off) try data := AhkMagic._RemoteRead(h, baseAddr + off, want) catch { off += want continue } maxOff := data.Size - 8 pos := 0 while pos <= maxOff { if NumGet(data, pos, "Ptr") = oldFn { ok := false start := Max(0, pos - 0x200) end := Min(data.Size - 8, pos + 0x200) j := start while j <= end { if NumGet(data, j, "Ptr") = namePtr { ok := true break } j += 8 } ; The name window alone can false-positive on ; unrelated structs that happen to hold both ; pointers. A real Func object matches one ; of two version layouts: 2.0 puts the ; vtable pointer right before the function ; pointer; 2.1 packs mMinParams/mMaxParams ; into the qword before it. if ok { vtOk := false for bc in [pos - 0x10, pos - 0x08] { if bc < 0 or bc + 8 > data.Size continue vt := NumGet(data, bc, "Ptr") if vt >= hook["image_base"] and vt < imageEnd { vtOk := true break } } if !vtOk and pos >= 0x10 { minP := bif["entries"][name]["min"] maxP := bif["entries"][name]["max"] packed := (minP << 32) | maxP if NumGet(data, pos - 0x10, "UInt64") = packed vtOk := true } if vtOk patches.Push(baseAddr + off + pos) } } pos += 8 } off += want } } if regionSize <= 0 break nextAddr := baseAddr + regionSize if nextAddr <= addr break addr := nextAddr } if !patches.Length throw Error("no Func object found for " name, -1) data := Buffer(8) NumPut("Ptr", newFn, data, 0) ; Snapshot every slot before writing so a mid-loop failure can ; roll the already-patched entries back instead of leaving the ; target half-redirected. originals := [] for p in patches originals.Push(AhkMagic._RemoteRead(h, p, 8)) written := [] try { loop patches.Length { AhkMagic._RemoteWrite(h, patches[A_Index], data) written.Push(A_Index) } } catch as e { for idx in written { try AhkMagic._RemoteWrite(h, patches[idx], originals[idx]) catch continue } throw e } return Map("src", name, "dst", newName, "count", patches.Length) } finally { DllCall("CloseHandle", "Ptr", h) } } static RemoteEval(hook, expr) { if !(hook is Map) or !hook.Has("pid") or !hook.Has("internal") or !hook["internal"].Has("postfix_rva") throw Error("hook has no remote eval context", -1) if !hook["internal"].Has("curr_line_slot") or !hook["internal"]["curr_line_slot"] throw Error("hook has no current-line slot; attach again", -1) if !(expr is String) throw TypeError("expr must be a string", -1) if StrLen(expr) > 4096 throw ValueError("expr too long", -1) internal := hook["internal"] stub := AhkMagic._HexBuffer(MC_REMOTE_EVAL_STUB_X64) locator := AhkMagic._HexBuffer(MC_INTERNAL_LOCATOR_X64) evalBlob := AhkMagic._HexBuffer(MC_INPROC_EVAL_X64) codeSize := stub.Size + locator.Size + evalBlob.Size paramOff := (codeSize + 15) // 16 * 16 layoutOff := paramOff + 136 locOff := ((layoutOff + 21 * 8 + 15) // 16) * 16 outOff := locOff + 64 exprLen := (StrLen(expr) + 1) * 2 exprOff := outOff + 512 scratchOff := exprOff + exprLen total := scratchOff + 8 * 1024 * 1024 ; Prefer the discovered target layout; fall back to the validated ; defaults when the hook has no layout yet (the blob aborts with a ; clear status before its first write if the assumption is wrong). layoutBuf := 0 if hook.Has("script_layout") and hook["script_layout"].Has("struct") and hook["script_layout"].Has("shape") { layoutBuf := AhkMagic._BuildEvalLayout( hook["script_layout"]["struct"] , hook["script_layout"]["shape"]) } else { layoutBuf := AhkMagic._DefaultEvalLayout() } h := AhkMagic._RemoteOpen(hook["pid"], true) try { block := DllCall("VirtualAllocEx", "Ptr", h, "Ptr", 0 , "UPtr", total, "UInt", 0x3000, "UInt", 0x40, "Ptr") if !block throw Error("VirtualAllocEx failed", -1) threadExited := false try { AhkMagic._RemoteWrite(h, block, stub) AhkMagic._RemoteWrite(h, block + stub.Size, locator) AhkMagic._RemoteWrite(h, block + stub.Size + locator.Size, evalBlob) AhkMagic._RemoteWrite(h, block + layoutOff, layoutBuf) param := Buffer(136) NumPut("Ptr", block + stub.Size, param, 0) NumPut("Ptr", block + stub.Size + locator.Size, param, 8) NumPut("Ptr", hook["image_base"], param, 16) NumPut("UInt64", internal["text_rva"], param, 24) NumPut("UInt64", internal["text_size"], param, 32) NumPut("UInt64", internal["postfix_rva"], param, 40) NumPut("UInt64", internal["expand_rva"], param, 48) NumPut("Ptr", block + locOff, param, 56) NumPut("Ptr", hook["image_base"] + internal["postfix_rva"], param, 64) NumPut("Ptr", hook["image_base"] + internal["expand_rva"], param, 72) NumPut("Ptr", internal["curr_line_slot"], param, 80) NumPut("Ptr", block + scratchOff, param, 88) NumPut("Ptr", block + exprOff, param, 96) NumPut("Ptr", block + outOff, param, 104) NumPut("UInt64", 0, param, 112) NumPut("Int", 0, param, 120) NumPut("Int", 0, param, 124) NumPut("Ptr", block + layoutOff, param, 128) AhkMagic._RemoteWrite(h, block + paramOff, param) exprBuf := Buffer(exprLen) StrPut(expr, exprBuf, "UTF-16") AhkMagic._RemoteWrite(h, block + exprOff, exprBuf) tid := 0 thread := DllCall("CreateRemoteThread", "Ptr", h, "Ptr", 0 , "UPtr", 0, "Ptr", block, "Ptr", block + paramOff , "UInt", 0, "UInt*", &tid, "Ptr") if !thread throw Error("CreateRemoteThread failed", -1) try { wait := DllCall("WaitForSingleObject", "Ptr", thread , "UInt", 10000) if wait = 0x102 { ; The remote thread is still executing the injected ; block. Freeing it would leave the target running ; freed memory; leak the block and fail loudly. throw Error("remote eval timed out after 10s; " . "injected block leaked to protect the target", -1) } if wait = 0xFFFFFFFF throw Error("WaitForSingleObject failed for remote eval", -1) if wait != 0 throw Error("WaitForSingleObject returned 0x" . Format("{:X}", wait) " for remote eval", -1) threadExited := true paramBack := AhkMagic._RemoteRead(h, block + paramOff, 136) locRc := NumGet(paramBack, 120, "Int") evalRc := NumGet(paramBack, 124, "Int") if locRc != 0 throw Error("remote internal locator rc=" locRc, -1) if evalRc != 0 throw Error("remote eval rc=" evalRc, -1) out := AhkMagic._RemoteRead(h, block + outOff, 512) status := NumGet(out, 0, "UInt") type := NumGet(out, 4, "UInt") if status != 0 throw Error("remote eval status=" status, -1) if type = 1 return NumGet(out, 8, "Int64") if type = 2 return NumGet(out, 8, "Double") if type = 0 { ptr := NumGet(out, 16, "Ptr") return ptr ? AhkMagic._RemoteReadString(h, ptr) : "" } if type = 5 { ; Objects cannot cross process boundaries; return the ; object's address in the target so callers can use ; it with the remote primitives. ptr := NumGet(out, 8, "Ptr") if !ptr throw Error("remote eval returned a null object", -1) return ptr } throw Error("remote eval returned unknown type " type, -1) } finally { DllCall("CloseHandle", "Ptr", thread) } } finally { ; Only free the block once the remote thread has exited. ; On timeout the block is deliberately leaked (see above). if threadExited DllCall("VirtualFreeEx", "Ptr", h, "Ptr", block, "UPtr", 0 , "UInt", 0x8000) } } finally { DllCall("CloseHandle", "Ptr", h) } } static _RemoteCall(h, fnAddr, args) { stub := AhkMagic._HexBuffer(MC_REMOTE_CALL_STUB_X64) param := Buffer(72) NumPut("Ptr", fnAddr, param, 0) loop 6 { val := args.Has(A_Index) ? args[A_Index] : 0 NumPut("Ptr", val, param, 8 + 8 * (A_Index - 1)) } NumPut("Int", 0, param, 56) block := DllCall("VirtualAllocEx", "Ptr", h, "Ptr", 0 , "UPtr", stub.Size + 72, "UInt", 0x3000, "UInt", 0x40, "Ptr") if !block throw Error("VirtualAllocEx failed", -1) threadExited := false try { AhkMagic._RemoteWrite(h, block, stub) AhkMagic._RemoteWrite(h, block + stub.Size, param) tid := 0 thread := DllCall("CreateRemoteThread", "Ptr", h, "Ptr", 0 , "UPtr", 0, "Ptr", block, "Ptr", block + stub.Size , "UInt", 0, "UInt*", &tid, "Ptr") if !thread throw Error("CreateRemoteThread failed", -1) try { wait := DllCall("WaitForSingleObject", "Ptr", thread , "UInt", 10000) if wait = 0x102 { ; Thread still executing the injected block; freeing it ; would corrupt the target, so leak it and fail loudly. throw Error("remote call timed out after 10s; " . "injected block leaked to protect the target", -1) } if wait = 0xFFFFFFFF throw Error("WaitForSingleObject failed for remote call", -1) if wait != 0 throw Error("WaitForSingleObject returned 0x" . Format("{:X}", wait) " for remote call", -1) threadExited := true back := AhkMagic._RemoteRead(h, block + stub.Size, 72) return NumGet(back, 56, "Int") } finally { DllCall("CloseHandle", "Ptr", thread) } } finally { if threadExited DllCall("VirtualFreeEx", "Ptr", h, "Ptr", block, "UPtr", 0 , "UInt", 0x8000) } } static _RemoteInternalLocator(h, secs, base, postfixRva, expandRva) { text := AhkMagic._TextSection(secs) locator := AhkMagic._HexBuffer(MC_INTERNAL_LOCATOR_X64) block := DllCall("VirtualAllocEx", "Ptr", h, "Ptr", 0 , "UPtr", locator.Size + 64, "UInt", 0x3000, "UInt", 0x40, "Ptr") if !block throw Error("VirtualAllocEx failed", -1) try { AhkMagic._RemoteWrite(h, block, locator) locOut := block + locator.Size rc := AhkMagic._RemoteCall(h, block , [base, text["rva"], text["size"], postfixRva, expandRva, locOut]) if rc != 0 throw Error("remote internal locator rc=" rc, -1) return AhkMagic._RemoteRead(h, locOut, 64) } finally { DllCall("VirtualFreeEx", "Ptr", h, "Ptr", block, "UPtr", 0 , "UInt", 0x8000) } } static _RemoteLoadScript(h, base, loc, text) { memScript := AhkMagic._HexBuffer(MC_MEM_SCRIPT_X64) textLen := (StrLen(text) + 1) * 2 scratchSize := 0x400 block := DllCall("VirtualAllocEx", "Ptr", h, "Ptr", 0 , "UPtr", memScript.Size + textLen + scratchSize , "UInt", 0x3000, "UInt", 0x40, "Ptr") if !block throw Error("VirtualAllocEx failed", -1) try { AhkMagic._RemoteWrite(h, block, memScript) textAddr := block + memScript.Size scratchAddr := textAddr + textLen textBuf := Buffer(textLen) StrPut(text, textBuf, "UTF-16") AhkMagic._RemoteWrite(h, textAddr, textBuf) gscript := NumGet(loc["loc_out"], 0, "Ptr") loadTs := base + loc["load_ts_rva"] srcCount := loc.Has("src_count_rva") and loc["src_count_rva"] ? base + loc["src_count_rva"] : 0 return AhkMagic._RemoteCall(h, block , [loadTs, gscript, srcCount, textAddr, StrLen(text) * 2 , scratchAddr]) } finally { DllCall("VirtualFreeEx", "Ptr", h, "Ptr", block, "UPtr", 0 , "UInt", 0x8000) } } static _RemoteCurrOff(secs, preparseRva) { text := AhkMagic._TextSection(secs) callers := AhkMagic._FindCallers(text, preparseRva) for caller in callers { p := text["ptr"] base := text["rva"] off := AhkMagic._FnStart(text, caller) - base limit := Min(text["size"] - 16, off + 0x4000) i := off while i < limit { if NumGet(p + i, "UChar") = 0xCC and NumGet(p + i + 1, "UChar") = 0xCC break if NumGet(p + i, "UChar") = 0x48 and NumGet(p + i + 1, "UChar") = 0x8B and NumGet(p + i + 2, "UChar") = 0x05 { j := i + 7 while j < Min(i + 24, limit) { if NumGet(p + j, "UChar") = 0x48 and NumGet(p + j + 1, "UChar") = 0x89 and (NumGet(p + j + 2, "UChar") = 0x58 or NumGet(p + j + 2, "UChar") = 0x50) return NumGet(p + j + 3, "UChar") j += 1 } } i += 1 } } return 0 } static _RemoteParserAnchors(text, loadTsRva) { p := text["ptr"] base := text["rva"] off := loadTsRva - base limit := Min(text["size"] - 8, off + 0x100) qCmp := -1 dCmp := -1 i := off while i < limit { b0 := NumGet(p + i, "UChar") if b0 = 0x48 and NumGet(p + i + 1, "UChar") = 0x83 { modrm := NumGet(p + i + 2, "UChar") if modrm = 0x79 and NumGet(p + i + 4, "UChar") = 0 and qCmp < 0 qCmp := NumGet(p + i + 3, "UChar") else if modrm = 0xB9 and NumGet(p + i + 7, "UChar") = 0 and qCmp < 0 qCmp := NumGet(p + i + 3, "Int") } if b0 = 0x83 and NumGet(p + i + 1, "UChar") = 0xB9 and (i = off or NumGet(p + i - 1, "UChar") != 0x48) and NumGet(p + i + 6, "UChar") = 0 and dCmp < 0 dCmp := NumGet(p + i + 2, "Int") if qCmp >= 0 and dCmp >= 0 break i += 1 } if qCmp < 0 or dCmp < 0 throw Error("parser state anchors not found", -1) return Map("q", qCmp, "d", dCmp) } ; Build BOTH known parser-region candidate layouts and pick the one ; that validates against the live target state. No version family is ; assumed: each candidate must satisfy its own structural checks ; (sentinel value, byte-field plausibility, pointer ranges) and the ; winner is the candidate that strictly validates. Ambiguous or ; failing candidates raise an explicit error. static _RemoteDiscoverParser(h, gscript, qCmp, dCmp) { buf := AhkMagic._RemoteRead(h, gscript, 0x800) candidates := [] scores := [] ; Candidate A: sentinel-anchored region ending at mClassObjectCount. if dCmp > 0 and dCmp + 4 <= buf.Size and NumGet(buf, dCmp, "Int") >= 0 and NumGet(buf, dCmp, "Int") <= 100000 { exprIndexOff := 0 loop Min(0x40, dCmp) // 4 { off := dCmp - (A_Index - 1) * 4 if off + 8 > buf.Size continue if NumGet(buf, off, "Int") = 0x7fffffff and NumGet(buf, off + 4, "Int") = 0 { exprIndexOff := off + 4 break } } if exprIndexOff and exprIndexOff + 8 <= buf.Size candidates.Push(Map( "rich", true, "fields", Map( "mclass_count", dCmp, "mline_parent", exprIndexOff - 0x28, "mpending_related", exprIndexOff - 0x20, "mlast_param_init", exprIndexOff - 0x18, "mpending_hotkey", exprIndexOff - 0x10, "mexpr_func", exprIndexOff - 8, "mexpr_func_index", exprIndexOff, "mnext_func_body", exprIndexOff + 4, "mignore_block", exprIndexOff + 5, "mbackcompat", exprIndexOff + 6, "mcurrent_module", exprIndexOff - 0x48, "mlast_module", exprIndexOff - 0x40))) } ; Candidate B: qCmp-anchored region. if qCmp > 0 and qCmp + 0x28 <= buf.Size candidates.Push(Map( "fields", Map( "mopen", qCmp, "mpending_parent", qCmp + 8, "mpending_related", qCmp + 16, "mlast_param_init", qCmp + 24, "mnext_func_body", qCmp + 32, "mclass_count", dCmp))) best := 0 bestScore := -1 for cand in candidates { fields := cand["fields"] valid := true for key, off in fields { ; Value-level checks are deliberately absent: the parser ; fields are saved and restored verbatim, so any plausible ; offset round-trips harmlessly. The only hard requirement ; is that every offset stays inside the Script object. if !(off is Integer) or off <= 0 or off >= 0x800 or off + 8 > buf.Size { valid := false break } } if valid { ; The sentinel-anchored candidate has the richer field set ; and only exists when its anchor was actually found. score := cand.Has("rich") ? 10 : 0 if score > bestScore { bestScore := score best := cand } } } if !best throw Error("parser state layout candidates all failed validation" , -1) return best["fields"] } static _RemoteLocateEvalScript(secs, base) { text := AhkMagic._TextSection(secs) postfixRefs := AhkMagic._RipRefs(text , AhkMagic._FindUtf16(secs, "Missing operand.")[1]) postfixRva := AhkMagic._BestStart(text, postfixRefs) expandRefs := AhkMagic._RipRefs(text , AhkMagic._FindUtf16(secs, "Error evaluating expression.")[1]) expandRva := AhkMagic._BestStart(text, expandRefs) if !postfixRva or !expandRva throw Error("remote expression functions not found", -1) callers := AhkMagic._FindCallers(text, postfixRva) if !callers.Length throw Error("PreparseExpressions not found", -1) preparse := AhkMagic._FnStart(text, callers[1]) preprocess := AhkMagic._LocatePreprocessFunc(text) if !preprocess throw Error("PreprocessLocalVars not found", -1) open := AhkMagic._LocateOpenInclude(secs) if !open throw Error("OpenIncludedFile not found", -1) loadTs := AhkMagic._LocateLoadTs(text, open) if !loadTs throw Error("LoadIncludedFile(TextStream) not found", -1) gptr := AhkMagic._LocateGptr(text, preparse) if !gptr throw Error("g pointer not found", -1) currOff := AhkMagic._RemoteCurrOff(secs, preparse) if !currOff throw Error("g->curr offset not found", -1) anchors := AhkMagic._RemoteParserAnchors(text, loadTs) return Map( "postfix_rva", postfixRva, "expand_rva", expandRva, "preparse_rva", preparse, "preprocess_rva", preprocess, "open_rva", open, "load_ts_rva", loadTs, "src_count_rva", 0, "gptr_rva", gptr, "curr_off", currOff, "q_cmp", anchors["q"], "d_cmp", anchors["d"], "parser", Map()) } static _RemoteLineRows(h, funcs, funcData, jumpOff, backOff) { rows := [] for index, q in funcs { linePtr := NumGet(funcData[index], jumpOff, "Ptr") if linePtr <= 0x10000 or linePtr >= 0x7fffffffffff continue try lineData := AhkMagic._RemoteRead(h, linePtr, 0x100) catch continue if backOff + 8 <= lineData.Size and NumGet(lineData, backOff, "Ptr") = q rows.Push(Map("func", q, "ptr", linePtr, "data", lineData)) } return rows } static _RemoteLineHasFuncRef(lineData, q) { loop lineData.Size // 8 { if NumGet(lineData, (A_Index - 1) * 8, "Ptr") = q return true } return false } static _RemoteIsArgLike(h, p) { try argData := AhkMagic._RemoteRead(h, p, 40) catch return false if NumGet(argData, 0, "UChar") > 2 or NumGet(argData, 1, "UChar") > 1 return false if NumGet(argData, 4, "UInt") > 0x10000 return false for off in [8, 16, 24] { v := NumGet(argData, off, "Ptr") if v and (v <= 0x10000 or v >= 0x7fffffffffff) return false } return true } static _RemoteTokenValid(data, stride, symOff, sentinel) { if symOff + 4 > stride return 0 count := Min(data.Size // stride, 64) seenVar := false loop count { sym := NumGet(data, (A_Index - 1) * stride + symOff, "UInt") if sym = sentinel return seenVar ? A_Index : 0 if sym > 0x1000 return 0 if sym = 4 seenVar := true } return 0 } static _RemoteCollectPostfixes(h, rows) { postfixes := [] seen := Map() for row in rows { lineData := row["data"] loop lineData.Size // 8 { p := NumGet(lineData, (A_Index - 1) * 8, "Ptr") if p <= 0x10000 or p >= 0x7fffffffffff continue if !AhkMagic._RemoteIsArgLike(h, p) continue try argData := AhkMagic._RemoteRead(h, p, 40) catch continue loop argData.Size // 8 { q := NumGet(argData, (A_Index - 1) * 8, "Ptr") if q <= 0x10000 or q >= 0x7fffffffffff continue if seen.Has(q) continue seen[q] := true postfixes.Push(q) } } } return postfixes } static _RemoteDiscoverTokens(h, postfixes, sentinel) { bestStride := 0 bestSym := 0 bestScore := -1 for stride in [24, 32, 16, 40] { for symOff in [16, 20, 12, 8, 4, 0] { if symOff + 4 > stride continue ok := 0 bestLen := 0 for p in postfixes { try data := AhkMagic._RemoteRead(h, p, stride * 64) catch continue got := AhkMagic._RemoteTokenValid(data, stride, symOff , sentinel) if got { ok += 1 bestLen := Max(bestLen, got) } } score := ok * 10000 - bestLen - stride if score > bestScore { bestScore := score bestStride := stride bestSym := symOff } } } if !bestStride throw Error("dynamic token layout not found", -1) varSym := 4 usageOff := 8 usageScore := -1 for uOff in [8, 12, 4, 20] { if uOff = bestSym continue if uOff + 8 > bestStride continue score := 0 for p in postfixes { try data := AhkMagic._RemoteRead(h, p, bestStride * 64) catch continue loop data.Size // bestStride { off := (A_Index - 1) * bestStride if NumGet(data, off + bestSym, "UInt") = varSym and NumGet(data, off + uOff, "UInt64") < 0x1000 score += 1 } } if score > usageScore { usageScore := score usageOff := uOff } } valueOff := 0 valueScore := -1 for vOff in [0, 8, 16] { if vOff = bestSym continue score := 0 for p in postfixes { try data := AhkMagic._RemoteRead(h, p, bestStride * 64) catch continue loop data.Size // bestStride { off := (A_Index - 1) * bestStride if NumGet(data, off + bestSym, "UInt") = varSym { v := NumGet(data, off + vOff, "Ptr") if v > 0x10000 and v < 0x7fffffffffff score += 1 } } } if score > valueScore { valueScore := score valueOff := vOff } } return Map( "stride", bestStride, "symbol", bestSym, "usage", usageOff, "value", valueOff, "var_symbol", varSym) } static _RemoteLineHeaderOff(rows) { bestAction := 0 bestArgc := 1 bestScore := -1 loop 4 { ao := A_Index - 1 loop 4 { ac := A_Index - 1 if ac = ao continue score := 0 for row in rows { data := row["data"] if ao + 1 >= data.Size or ac + 1 >= data.Size continue if NumGet(data, ao, "UChar") <= 0x40 and NumGet(data, ac, "UChar") <= 0x10 score += 1 } if score > bestScore { bestScore := score bestAction := ao bestArgc := ac } } } if !bestScore throw Error("dynamic Line header layout not found", -1) return Map("action", bestAction, "argc", bestArgc) } static _RemoteLineArgOff(h, rows, tokens, sentinel) { counts := Map() argPostfixCounts := Map() for row in rows { lineData := row["data"] loop lineData.Size // 8 { lineOff := (A_Index - 1) * 8 p := NumGet(lineData, lineOff, "Ptr") if p <= 0x10000 or p >= 0x7fffffffffff continue if !AhkMagic._RemoteIsArgLike(h, p) continue try argData := AhkMagic._RemoteRead(h, p, 40) catch continue loop argData.Size // 8 { argOff := (A_Index - 1) * 8 q := NumGet(argData, argOff, "Ptr") if q <= 0x10000 or q >= 0x7fffffffffff continue try tokenData := AhkMagic._RemoteRead(h, q , tokens["stride"] * 64) catch continue if AhkMagic._RemoteTokenValid(tokenData , tokens["stride"], tokens["symbol"], sentinel) { counts[lineOff] := (counts.Has(lineOff) ? counts[lineOff] : 0) + 1 argPostfixCounts[argOff] := (argPostfixCounts.Has(argOff) ? argPostfixCounts[argOff] : 0) + 1 } } } } bestOff := 0 bestScore := 0 for off, score in counts { if score > bestScore or (score = bestScore and bestOff and off < bestOff) { bestScore := score bestOff := off } } bestArgPostfix := 24 bestArgScore := 0 for off, score in argPostfixCounts { if score > bestArgScore or (score = bestArgScore and bestArgPostfix and off < bestArgPostfix) { bestArgScore := score bestArgPostfix := off } } if !bestOff or !bestArgScore throw Error("dynamic Line.mArg / ArgStruct.postfix not found", -1) return Map("line_arg", bestOff, "arg_postfix", bestArgPostfix) } static _RemoteArgExpressionOff(h, rows, lineArg, argPostfix, tokens , sentinel) { counts := Map() for row in rows { arg := NumGet(row["data"], lineArg, "Ptr") if arg <= 0x10000 or arg >= 0x7fffffffffff continue try argData := AhkMagic._RemoteRead(h, arg, 40) catch continue postfix := NumGet(argData, argPostfix, "Ptr") if postfix <= 0x10000 or postfix >= 0x7fffffffffff continue try tokenData := AhkMagic._RemoteRead(h, postfix , tokens["stride"] * 64) catch continue if !AhkMagic._RemoteTokenValid(tokenData, tokens["stride"] , tokens["symbol"], sentinel) continue loop 8 { off := A_Index - 1 if NumGet(argData, off, "UChar") = 1 counts[off] := (counts.Has(off) ? counts[off] : 0) + 1 } } bestOff := 1 bestScore := 0 for off, score in counts { if score > bestScore or (score = bestScore and off < bestOff) { bestScore := score bestOff := off } } if !bestScore throw Error("dynamic ArgStruct.is_expression not found", -1) return bestOff } static _RemoteLineNextOff(h, rows, lineArg, backOff) { forward := Map() total := Map() loop 0x100 // 8 { off := (A_Index - 1) * 8 if off = lineArg continue for row in rows { p := NumGet(row["data"], off, "Ptr") if p <= 0x10000 or p >= 0x7fffffffffff or p = row["ptr"] continue try lineData := AhkMagic._RemoteRead(h, p, 0x100) catch continue if !AhkMagic._RemoteLineHasFuncRef(lineData, row["func"]) continue total[off] := (total.Has(off) ? total[off] : 0) + 1 if p > row["ptr"] forward[off] := (forward.Has(off) ? forward[off] : 0) + 1 } } bestOff := 0 bestForward := -1 bestTotal := -1 for off, score in forward { t := total.Has(off) ? total[off] : 0 if score > bestForward or (score = bestForward and t > bestTotal) or (score = bestForward and t = bestTotal and bestOff and off < bestOff) { bestForward := score bestTotal := t bestOff := off } } if !bestOff throw Error("dynamic Line.mNextLine not found", -1) return bestOff } static _RemoteLineAttributeOff(h, rows, lineArg, lineNext, backOff , funcs) { known := Map() for q in funcs known[q] := true counts := Map() for row in rows { visited := Map() stack := [row["ptr"]] while stack.Length { line := stack.Pop() if visited.Has(line) continue visited[line] := true try lineData := AhkMagic._RemoteRead(h, line, 0x100) catch continue loop lineData.Size // 8 { off := (A_Index - 1) * 8 if off = lineArg or off = lineNext continue p := NumGet(lineData, off, "Ptr") if known.Has(p) { counts[off] := (counts.Has(off) ? counts[off] : 0) + 1 continue } if p <= 0x10000 or p >= 0x7fffffffffff or p = line continue if AhkMagic._RemoteLineHasFuncRef(lineData, row["func"]) and !visited.Has(p) stack.Push(p) } } } bestOff := 0 bestScore := 0 for off, score in counts { if score > bestScore or (score = bestScore and bestOff and off < bestOff) { bestScore := score bestOff := off } } if !bestOff throw Error("dynamic Line.mAttribute not found", -1) return bestOff } static _RemoteDerefArrayLike(data) { if NumGet(data, 0, "Ptr") <= 0x10000 or NumGet(data, 0, "Ptr") >= 0x7fffffffffff return false loop 24 { to := A_Index - 1 if to < 8 or to + 8 > data.Size continue if NumGet(data, to, "UChar") <= 7 and NumGet(data, to + 4, "UInt") <= 0x10000 return true } return false } static _RemoteDerefLayout(h, rows, lineArg) { arrays := [] argDerefCounts := Map() for row in rows { arg := NumGet(row["data"], lineArg, "Ptr") if arg <= 0x10000 or arg >= 0x7fffffffffff continue try argData := AhkMagic._RemoteRead(h, arg, 40) catch continue loop argData.Size // 8 { off := (A_Index - 1) * 8 p := NumGet(argData, off, "Ptr") if p <= 0x10000 or p >= 0x7fffffffffff continue try data := AhkMagic._RemoteRead(h, p, 0x100) catch continue if AhkMagic._RemoteDerefArrayLike(data) { arrays.Push(data) argDerefCounts[off] := (argDerefCounts.Has(off) ? argDerefCounts[off] : 0) + 1 } } } if arrays.Length < 1 throw Error("dynamic ArgStruct.deref not found", -1) argDerefOff := 0 argDerefScore := -1 for off, score in argDerefCounts { if score > argDerefScore or (score = argDerefScore and off < argDerefOff) { argDerefScore := score argDerefOff := off } } if !argDerefScore throw Error("dynamic ArgStruct.deref offset not found", -1) bestStride := 24 bestType := 16 bestScore := -1 for stride in [24, 32, 16, 40] { for typeOff in [16, 20, 24] { if typeOff + 8 > stride continue score := 0 for data in arrays { loop Min(data.Size // stride, 8) { off := (A_Index - 1) * stride marker := NumGet(data, off, "Ptr") if marker <= 0x10000 or marker >= 0x7fffffffffff break if NumGet(data, off + typeOff, "UChar") <= 7 and NumGet(data, off + typeOff + 1, "UChar") <= 3 and NumGet(data, off + typeOff + 4, "UInt") <= 0x10000 score += 1 } } if score > bestScore or (score = bestScore and typeOff < bestType) or (score = bestScore and typeOff = bestType and stride < bestStride) { bestScore := score bestType := typeOff bestStride := stride } } } if !bestScore throw Error("dynamic DerefType layout not found", -1) markerOff := bestType - 16 varOff := bestType - 8 markerScore := 0 for data in arrays { if NumGet(data, markerOff, "Ptr") > 0x10000 and NumGet(data, markerOff, "Ptr") < 0x7fffffffffff markerScore += 1 } if !markerScore throw Error("dynamic DerefType markers not found", -1) return Map( "arg_deref", argDerefOff, "stride", bestStride, "marker", markerOff, "var", varOff, "type", bestType, "len", bestType + 4) } static _RemoteDiscoverBackOff(h, funcs, funcData, jumpOff) { counts := Map() for index, q in funcs { linePtr := NumGet(funcData[index], jumpOff, "Ptr") if linePtr <= 0x10000 or linePtr >= 0x7fffffffffff continue try lineData := AhkMagic._RemoteRead(h, linePtr, 0x80) catch continue loop lineData.Size // 8 { bo := (A_Index - 1) * 8 if NumGet(lineData, bo, "Ptr") = q counts[bo] := (counts.Has(bo) ? counts[bo] : 0) + 1 } } bestOff := 0 bestCount := 0 for off, count in counts { if count > bestCount or (count = bestCount and off < bestOff) { bestCount := count bestOff := off } } if !bestOff throw Error("back-reference slot not found", -1) return bestOff } static _DiscoverInProcStructs(gScript, arrPtr, oldCount, newFunc , jumpOff) { h := DllCall("OpenProcess", "UInt", 0x1F0FFF, "Int", 0 , "UInt", DllCall("GetCurrentProcessId"), "Ptr") if !h throw Error("OpenProcess(self) failed", -1) try { funcs := [] funcData := [] loop oldCount { q := NumGet(arrPtr + (A_Index - 1) * 8, "Ptr") if q <= 0x10000 or q >= 0x7fffffffffff continue funcs.Push(q) data := Buffer(0x400) DllCall("RtlMoveMemory", "Ptr", data.Ptr, "Ptr", q , "UPtr", 0x400) funcData.Push(data) } funcs.Push(newFunc) data := Buffer(0x400) DllCall("RtlMoveMemory", "Ptr", data.Ptr, "Ptr", newFunc , "UPtr", 0x400) funcData.Push(data) backOff := AhkMagic._RemoteDiscoverBackOff(h, funcs, funcData , jumpOff) locOut := Buffer(64, 0) NumPut("Ptr", AhkMagic.gScript, locOut, 0) NumPut("Ptr", AhkMagic.finalizeExpr, locOut, 8) NumPut("Ptr", AhkMagic.findOrAddVar, locOut, 16) NumPut("Ptr", AhkMagic.crtFree, locOut, 24) NumPut("UInt", AhkMagic.symInvalid, locOut, 32) return AhkMagic._RemoteDiscoverStructs(h, locOut, funcs , funcData, jumpOff, backOff) } finally { DllCall("CloseHandle", "Ptr", h) } } static _RemoteDiscoverStructs(h, locOut, funcs, funcData, jumpOff , backOff) { rows := AhkMagic._RemoteLineRows(h, funcs, funcData, jumpOff, backOff) if rows.Length < 1 throw Error("no ordinary functions for struct discovery" , -1) sentinel := NumGet(locOut, 32, "UInt") postfixes := AhkMagic._RemoteCollectPostfixes(h, rows) tokens := AhkMagic._RemoteDiscoverTokens(h, postfixes, sentinel) header := AhkMagic._RemoteLineHeaderOff(rows) arg := AhkMagic._RemoteLineArgOff(h, rows, tokens, sentinel) argExpr := AhkMagic._RemoteArgExpressionOff(h, rows , arg["line_arg"], arg["arg_postfix"], tokens, sentinel) lineNext := AhkMagic._RemoteLineNextOff(h, rows, arg["line_arg"] , backOff) lineAttr := AhkMagic._RemoteLineAttributeOff(h, rows , arg["line_arg"], lineNext, backOff, funcs) deref := AhkMagic._RemoteDerefLayout(h, rows, arg["line_arg"]) return Map( "line_action", header["action"], "line_argc", header["argc"], "line_arg", arg["line_arg"], "line_attribute", lineAttr, "line_next", lineNext, "arg_expression", argExpr, "arg_postfix", arg["arg_postfix"], "arg_deref", deref["arg_deref"], "token_stride", tokens["stride"], "token_symbol", tokens["symbol"], "token_usage", tokens["usage"], "token_value", tokens["value"], "token_var_symbol", tokens["var_symbol"], "deref_stride", deref["stride"], "deref_marker", deref["marker"], "deref_var", deref["var"], "deref_type", deref["type"], "deref_len", deref["len"]) } ; Discover the mFuncs layout of a target. The validator needs live ; function entries, so a target whose script defines no functions ; (fresh/empty scripts) fails discovery. Inject two probe functions ; through the target's own loader and retry once; the probes stay in ; the target's mFuncs as harmless extras. static _RemoteDiscoverLayoutWithProbe(h, secs, base, loc) { try { return AhkMagic._RemoteDiscoverLayout(h, secs, base, loc) } catch as e { if !InStr(e.Message, "dynamic function layout not found") and !InStr(e.Message, "dynamic token layout not found") throw e tag := Format("{:x}", A_TickCount) probe := "ahkHackLayoutProbeA" tag . "(x) {`n return x + 1`n}`n" . "ahkHackLayoutProbeB" tag . "(y) {`n return y * 2`n}`n" rc := AhkMagic._RemoteLoadScript(h, base, loc, probe) if rc != 0 throw Error("probe injection for layout discovery failed rc=" . rc " (TextStream layout validation failed in target)" , -1) try { ; First pass discovers mFuncs/mjumpLine without structs. layout := AhkMagic._RemoteDiscoverLayout( h, secs, base, loc, false) ; The injected functions were loaded without preparse, so ; their lines have no postfix buffers yet. Preparse them ; through the target's own pipeline, then finish struct ; discovery on the now-complete probe rows. savedCur := AhkMagic._RPtr(h, layout["g"] + layout["curr_off"]) for q in layout["funcs"] { jump := AhkMagic._RPtr(h , q + layout["mjump_line_off"]) if !jump continue AhkMagic._WPtr(h, layout["g"] + layout["curr_off"], q) prc := AhkMagic._RemoteCall(h , base + loc["preparse_rva"] , [layout["gscript"], jump]) AhkMagic._WPtr(h, layout["g"] + layout["curr_off"], savedCur) if prc != 1 throw Error("probe PreparseExpressions rc=" prc, -1) } layout["struct"] := AhkMagic._RemoteDiscoverStructs( h, loc["loc_out"], layout["funcs"], layout["func_data"] , layout["mjump_line_off"], layout["jump_back_off"]) layout["shape"] := AhkMagic._RemoteArgShape(h, layout , layout["struct"]) return layout } catch as e2 { throw Error("layout discovery failed even after probe " . "injection: " e2.Message, -1) } } } static _RemoteDiscoverLayout(h, secs, base, loc, doStructs := true) { locOut := AhkMagic._RemoteInternalLocator(h, secs, base , loc["postfix_rva"], loc["expand_rva"]) loc["loc_out"] := locOut gscript := NumGet(locOut, 0, "Ptr") gAddr := base + loc["gptr_rva"] g := AhkMagic._RPtr(h, gAddr) currOff := loc["curr_off"] snap := AhkMagic._RemoteRead(h, gscript, 0x800) bestScore := -1 best := Map("funcs_off", 0, "count_off", 0, "arr_ptr", 0 , "count", 0, "name_off", 0, "jump_off", 0 , "back_off", 0, "funcs", [], "func_data", []) loop 0x200 // 8 { off := (A_Index - 1) * 8 if off + 12 > snap.Size continue arrPtr := NumGet(snap, off, "Ptr") count := NumGet(snap, off + 8, "Int") if arrPtr <= 0x10000 or arrPtr >= 0x7fffffffffff or count < 1 or count > 100000 continue limit := Min(count, 16) funcs := [] funcData := [] lineCache := Map() valid := true loop limit { try q := AhkMagic._RPtr(h, arrPtr + (A_Index - 1) * 8) catch { valid := false break } if q <= 0x10000 or q >= 0x7fffffffffff { valid := false break } funcs.Push(q) funcData.Push(AhkMagic._RemoteRead(h, q, 0x400)) } if !valid continue jumpOff := 0 jumpMatches := 0 jumpBestSlot := 0 jumpSlotOff := 0 loop 0x200 // 8 { jo := (A_Index - 1) * 8 matches := 0 slotCount := Map() for index, q in funcs { linePtr := NumGet(funcData[index], jo, "Ptr") if linePtr <= 0x10000 or linePtr >= 0x7fffffffffff continue if lineCache.Has(linePtr) { lineData := lineCache[linePtr] } else { try { lineData := AhkMagic._RemoteRead(h, linePtr, 0x80) lineCache[linePtr] := lineData } catch { continue } } foundSlot := 0 loop lineData.Size // 8 { bo := (A_Index - 1) * 8 if NumGet(lineData, bo, "Ptr") = q { foundSlot := bo break } } if foundSlot { matches += 1 slotCount[foundSlot] := (slotCount.Has(foundSlot) ? slotCount[foundSlot] : 0) + 1 } } bestSlot := 0 bestSlotCount := 0 for slot, count in slotCount { if count > bestSlotCount or count = bestSlotCount { bestSlotCount := count bestSlot := slot } } if bestSlotCount > jumpBestSlot or (bestSlotCount = jumpBestSlot and jumpBestSlot and jo < jumpOff) { jumpMatches := matches jumpBestSlot := bestSlotCount jumpSlotOff := bestSlot jumpOff := jo } } minJump := limit < 2 ? 1 : 2 if jumpBestSlot < minJump continue ; A real mFuncs array must have back-reference rows for a ; majority of its entries. Decoy arrays (other object lists in ; the Script object) can contain a few function-like pointers ; and win the name-based score; rejecting sub-majority ; back-reference counts keeps the scan on the real array. if jumpBestSlot * 2 < limit continue nameOff := 0 nameMatches := 0 nameUnique := 0 loop 0x200 // 8 { no := (A_Index - 1) * 8 matches := 0 seen := Map() for index, q in funcs { namePtr := NumGet(funcData[index], no, "Ptr") if namePtr <= 0x10000 or namePtr >= 0x7fffffffffff continue try name := AhkMagic._RemoteReadString(h, namePtr, 128) catch continue if name = "" or StrLen(name) > 127 continue if !RegExMatch(name, "^[A-Za-z_][A-Za-z0-9_.]*$") continue matches += 1 seen[name] := 1 } if matches > nameMatches or (matches = nameMatches and seen.Count > nameUnique) { nameMatches := matches nameUnique := seen.Count nameOff := no } } minName := limit < 2 ? 1 : 2 if nameMatches < minName continue score := nameMatches * 100000 + nameUnique * 1000 + jumpMatches * 100 + limit if score > bestScore { bestScore := score best["funcs_off"] := off best["count_off"] := off + 8 best["arr_ptr"] := arrPtr best["count"] := count best["name_off"] := nameOff best["jump_off"] := jumpOff best["back_off"] := jumpSlotOff best["funcs"] := funcs best["func_data"] := funcData } } if !best["funcs_off"] or !best["name_off"] or !best["jump_off"] throw Error("dynamic function layout not found", -1) loc["parser"] := AhkMagic._RemoteDiscoverParser(h, gscript , loc["q_cmp"], loc["d_cmp"]) layout := Map( "gscript", gscript, "g", g, "curr_off", currOff, "mfuncs_off", best["funcs_off"], "mfuncs_count_off", best["count_off"], "mlast_line_off", 0, "mjump_line_off", best["jump_off"], "name_off", best["name_off"], "jump_back_off", best["back_off"], "funcs", best["funcs"], "func_data", best["func_data"], "parser", loc["parser"]) if doStructs { layout["struct"] := AhkMagic._RemoteDiscoverStructs(h, locOut , best["funcs"], best["func_data"], best["jump_off"] , best["back_off"]) layout["shape"] := AhkMagic._RemoteArgShape(h, layout , layout["struct"]) } return layout } static _RPtr(h, addr) { return NumGet(AhkMagic._RemoteRead(h, addr, 8), 0, "Ptr") } static _RInt(h, addr) { return NumGet(AhkMagic._RemoteRead(h, addr, 4), 0, "Int") } static _WPtr(h, addr, val) { buf := Buffer(8) NumPut("Ptr", val, buf, 0) AhkMagic._RemoteWrite(h, addr, buf) } static _WInt(h, addr, val) { buf := Buffer(4) NumPut("Int", val, buf, 0) AhkMagic._RemoteWrite(h, addr, buf) } static _WByte(h, addr, val) { buf := Buffer(1) NumPut("UChar", val, buf, 0) AhkMagic._RemoteWrite(h, addr, buf) } static _DeclaredFunctionNames(text) { names := [] depth := 0 candidate := "" for raw in StrSplit(text, "`n", "`r") { line := Trim(raw) if candidate = "" and line != "" and SubStr(line, 1, 1) != ";" { if RegExMatch(line , "^([A-Za-z_][A-Za-z0-9_]*)\s*\(", &m) and !RegExMatch(line , "^(if|else|for|while|switch|try|catch|finally" . "|return|break|continue|global|local|static|class" . "|new|throw|until)\b") { candidate := m[1] } } if candidate != "" and InStr(line, "{") { found := false for n in names { if n = candidate { found := true break } } if !found and depth = 0 names.Push(candidate) candidate := "" } opens := 0 closes := 0 loop StrLen(line) { c := SubStr(line, A_Index, 1) if c = "{" opens += 1 else if c = "}" closes += 1 } depth += opens - closes } return names } static RemoteEvalScript(hook, text) { if !(hook is Map) or !hook.Has("pid") throw TypeError("hook must be an AttachRemote result", -1) if !(text is String) or Trim(text) = "" throw TypeError("text must be a non-empty string", -1) ; Same protection as the in-process path, but validated with the ; TARGET's own interpreter when its path is known. vpath := hook.Has("module") and hook["module"] != "" ? hook["module"] : A_AhkPath AhkMagic._ValidateScript(text, vpath) h := AhkMagic._RemoteOpen(hook["pid"], true) try { mod := AhkMagic._RemoteModuleBase(h, hook["pid"]) secs := AhkMagic._RemoteSections(h, mod["base"]) if hook.Has("script_loc") and hook.Has("script_layout") { loc := hook["script_loc"] layout := hook["script_layout"] } else { loc := AhkMagic._RemoteLocateEvalScript(secs, mod["base"]) layout := AhkMagic._RemoteDiscoverLayoutWithProbe( h, secs, mod["base"], loc) hook["script_loc"] := loc hook["script_layout"] := layout } locOut := loc["loc_out"] if !layout.Has("struct") layout["struct"] := AhkMagic._RemoteDiscoverStructs(h, locOut , layout["funcs"], layout["func_data"] , layout["mjump_line_off"], layout["jump_back_off"]) if !layout.Has("shape") layout["shape"] := AhkMagic._RemoteArgShape(h, layout , layout["struct"]) s := layout["struct"] gscript := layout["gscript"] g := layout["g"] currOff := layout["curr_off"] for name in AhkMagic._DeclaredFunctionNames(text) { if AhkMagic._RemoteFindUserFuncs(h, layout, name).Length throw Error("function name already exists in target: " . name, -1) } oldFuncCount := AhkMagic._RInt(h, gscript + layout["mfuncs_count_off"]) savedCur := AhkMagic._RPtr(h, g + currOff) savedCurFunc := AhkMagic._RPtr(h, g + currOff) parser := layout["parser"] savedState := [] for key in ["mopen", "mpending_parent", "mline_parent" , "mpending_related", "mlast_param_init", "mpending_hotkey" , "mexpr_func", "mcurrent_module"] if parser.Has(key) savedState.Push([key, "Ptr" , AhkMagic._RPtr(h, gscript + parser[key])]) for key in ["mexpr_func_index", "mclass_count"] if parser.Has(key) savedState.Push([key, "Int" , AhkMagic._RInt(h, gscript + parser[key])]) for key in ["mnext_func_body", "mignore_block", "mbackcompat"] if parser.Has(key) savedState.Push([key, "UChar" , NumGet(AhkMagic._RemoteRead(h, gscript + parser[key], 1) , 0, "UChar")]) try { for key in ["mopen", "mpending_parent", "mline_parent" , "mpending_related", "mlast_param_init" , "mpending_hotkey", "mexpr_func"] if parser.Has(key) AhkMagic._WPtr(h, gscript + parser[key], 0) if parser.Has("mexpr_func_index") AhkMagic._WInt(h, gscript + parser["mexpr_func_index"] , 0x7fffffff) if parser.Has("mnext_func_body") AhkMagic._WByte(h, gscript + parser["mnext_func_body"], 0) if parser.Has("mignore_block") AhkMagic._WByte(h, gscript + parser["mignore_block"], 0) if parser.Has("mbackcompat") AhkMagic._WByte(h, gscript + parser["mbackcompat"], 1) if parser.Has("mclass_count") AhkMagic._WInt(h, gscript + parser["mclass_count"], 0) AhkMagic._WPtr(h, g + currOff, 0) rc := AhkMagic._RemoteLoadScript(h, mod["base"], loc, text) if rc != 0 throw Error("LoadIncludedFile(memory) rc=" rc, -1) funcsItem := AhkMagic._RPtr(h, gscript + layout["mfuncs_off"]) funcCount := AhkMagic._RInt(h, gscript + layout["mfuncs_count_off"]) if funcCount < oldFuncCount or funcCount > oldFuncCount + 4096 throw Error("mFuncs count changed unexpectedly: " . oldFuncCount " -> " funcCount, -1) if funcCount > oldFuncCount { loop funcCount - oldFuncCount { idx := oldFuncCount + A_Index - 1 newFunc := AhkMagic._RPtr(h, funcsItem + idx * 8) jump := AhkMagic._RPtr(h , newFunc + layout["mjump_line_off"]) if !jump continue AhkMagic._WPtr(h, g + currOff, newFunc) rc := AhkMagic._RemoteCall(h , mod["base"] + loc["preparse_rva"] , [gscript, jump]) if rc != 1 throw Error("PreparseExpressions rc=" rc, -1) AhkMagic._WPtr(h, g + currOff, newFunc) line := jump lineSteps := 0 while line { if lineSteps > 100000 throw Error("remote line chain too long; layout likely wrong", -1) lineSteps += 1 lineSize := Max(0x40, s["line_next"] + 8 , s["line_attribute"] + 8, s["line_arg"] + 8) lineData := AhkMagic._RemoteRead(h, line, lineSize) action := NumGet(lineData, s["line_action"], "UChar") attr := NumGet(lineData, s["line_attribute"], "Ptr") argc := NumGet(lineData, s["line_argc"], "UChar") arg := NumGet(lineData, s["line_arg"], "Ptr") if action = 3 and attr AhkMagic._WPtr(h, g + currOff, attr) if argc and arg and NumGet(AhkMagic._RemoteRead(h , arg + s["arg_expression"], 1) , 0, "UChar") { postfix := AhkMagic._RPtr(h , arg + s["arg_postfix"]) if postfix { tokenSteps := 0 loop { if tokenSteps > 100000 throw Error("remote postfix token chain too long; layout likely wrong", -1) tokenSteps += 1 tokenSize := Max(s["token_stride"] , s["token_symbol"] + 4 , s["token_usage"] + 8 , s["token_value"] + 8) token := AhkMagic._RemoteRead(h , postfix, tokenSize) symbol := NumGet(token , s["token_symbol"], "UInt") if symbol = NumGet(locOut, 32, "UInt") break if symbol = s["token_var_symbol"] and NumGet(token, s["token_usage"] , "UInt") < 3 { deref := NumGet(token , s["token_value"], "Ptr") if deref { derefSize := Max(24 , s["deref_len"] + 4 , s["deref_type"] + 1 , s["deref_marker"] + 8 , s["deref_var"] + 8) derefData := AhkMagic._RemoteRead(h , deref, derefSize) derefType := NumGet(derefData , s["deref_type"], "UChar") marker := NumGet(derefData , s["deref_marker"], "Ptr") derefLen := NumGet(derefData , s["deref_len"], "UInt") if derefType = 7 { AhkMagic._WPtr(h, postfix , AhkMagic._RPtr(h , deref + s["deref_var"])) } else if derefType = 0 and marker and derefLen > 0 and derefLen <= 64 { var := AhkMagic._RemoteCall(h , NumGet(locOut, 16, "Ptr") , [gscript, marker , derefLen, 0x103]) if var AhkMagic._WPtr(h , postfix + s["token_value"] , var) } } } postfix += s["token_stride"] } } } line := AhkMagic._RPtr(h , line + s["line_next"]) } AhkMagic._WPtr(h, g + currOff, savedCur) rc := AhkMagic._RemoteCall(h , mod["base"] + loc["preprocess_rva"] , [gscript, newFunc]) if rc != 1 throw Error("PreprocessLocalVars rc=" rc, -1) AhkMagic._WPtr(h, g + currOff, savedCurFunc) } } AhkMagic._WPtr(h, g + currOff, 0) last := "" for raw in StrSplit(text, "`n", "`r") { t := Trim(raw) if t = "" continue if RegExMatch(t , "^(if|else|for|while|loop|try|catch|finally|return|break|continue|class|static|global|local|throw)\b") continue if SubStr(t, -1) = "{" continue last := t } if last = "" throw ValueError("no expression result found in script text") return AhkMagic.RemoteEval(hook, last) } finally { for item in savedState { if item[2] = "Ptr" AhkMagic._WPtr(h, gscript + parser[item[1]], item[3]) else if item[2] = "Int" AhkMagic._WInt(h, gscript + parser[item[1]], item[3]) else AhkMagic._WByte(h, gscript + parser[item[1]], item[3]) } AhkMagic._WPtr(h, g + currOff, savedCurFunc) AhkMagic._WPtr(h, g + currOff, savedCur) } } finally { DllCall("CloseHandle", "Ptr", h) } } static RemoteReplaceFuncBody(hook, oldName, newName) { if !(hook is Map) or !hook.Has("pid") throw TypeError("hook must be an AttachRemote result", -1) h := AhkMagic._RemoteOpen(hook["pid"], true) try { mod := AhkMagic._RemoteModuleBase(h, hook["pid"]) secs := AhkMagic._RemoteSections(h, mod["base"]) if hook.Has("script_layout") { layout := hook["script_layout"] } else { loc := AhkMagic._RemoteLocateEvalScript(secs, mod["base"]) layout := AhkMagic._RemoteDiscoverLayoutWithProbe( h, secs, mod["base"], loc) hook["script_loc"] := loc hook["script_layout"] := layout } oldList := AhkMagic._RemoteFindUserFuncs(h, layout, oldName) newPtr := AhkMagic._RemoteFindUserFunc(h, layout, newName) if !oldList.Length or !newPtr throw Error("function object not found: " oldName " / " newName, -1) jumpOff := layout["mjump_line_off"] newJump := AhkMagic._RPtr(h, newPtr + jumpOff) if !newJump throw Error("new function body not found", -1) patched := [] for oldPtr in oldList { AhkMagic._WPtr(h, oldPtr + jumpOff, newJump) patched.Push(oldPtr) } return Map("old", oldList, "new", newPtr, "jump_off", jumpOff , "count", patched.Length) } finally { DllCall("CloseHandle", "Ptr", h) } } static _RemoteFindUserFunc(h, layout, name) { list := AhkMagic._RemoteFindUserFuncs(h, layout, name) return list.Length ? list[1] : 0 } static _RemoteFindUserFuncs(h, layout, name) { gscript := layout["gscript"] arr := AhkMagic._RPtr(h, gscript + layout["mfuncs_off"]) count := AhkMagic._RInt(h, gscript + layout["mfuncs_count_off"]) if count < 0 or count > 100000 throw Error("mFuncs count out of range: " count, -1) result := [] shortName := name dot := InStr(name, ".") fullName := "" if dot { shortName := SubStr(name, dot + 1) fullName := SubStr(name, 1, dot - 1) . ".Prototype." shortName } loop count { nf := AhkMagic._RPtr(h, arr + (A_Index - 1) * 8) candidates := [name, shortName] if fullName != "" candidates.Push(fullName) for want in candidates { data := AhkMagic._RemoteRead(h, nf, 0x500) found := false loop data.Size // 2 - StrLen(want) { off := (A_Index - 1) * 2 ok := true loop StrLen(want) { if NumGet(data, off + (A_Index - 1) * 2, "UShort") != Ord(SubStr(want, A_Index, 1)) { ok := false break } } if ok and NumGet(data, off + StrLen(want) * 2, "UShort") = 0 { found := true break } } if found result.Push(nf) } } return result } ; Validate text with the interpreter's own /validate switch before it ; reaches the loader, so a syntax error becomes a clean library error ; carrying the real parse message instead of a dialog or an aborted ; host process (in-process EvalScript) or target (RemoteEvalScript). ; The validator is driven by its captured output and a completion ; marker file, never by process exit codes, and its process tree is ; killed the moment the error text lands so no build can leave a ; dialog on screen. When no signal arrives within six seconds the ; check is treated as inconclusive and the loader's own failure check ; remains the fallback guard. static _ValidateScript(text, ahkPath) { if !ahkPath or !FileExist(ahkPath) return tag := Format("{:x}", A_TickCount) "-" Format("{:x}", Random(1, 0x7fffffff)) vFile := A_Temp "\ahk_hack_validate_" tag ".ahk" errFile := A_Temp "\ahk_hack_validate_" tag ".err" batFile := A_Temp "\ahk_hack_validate_" tag ".bat" doneFile := A_Temp "\ahk_hack_validate_" tag ".done" try { ; #Warn off keeps warning text out of the captured stream so ; only real errors drive the throw below. FileAppend "#Warn All, Off`n" text, vFile, "UTF-8" ; The command line is routed through a batch file because AHK's ; Run re-parses nested quotes and can silently drop switches; ; inside the batch, cmd.exe receives the arguments verbatim. ; Format builds the quoted line (no manual quote escaping), and ; the batch is written without a BOM (cmd would otherwise fold ; the BOM into the first token). /ErrorStdOut routes load ; errors to the captured stream so the validator never needs a ; dialog. batLine := Format('"{1}" /validate /ErrorStdOut "{2}" >"{3}" 2>&1' , ahkPath, vFile, errFile) FileAppend batLine "`n", batFile, "UTF-8-RAW" ; The done marker is only written after the validator exits ; (cmd runs batch lines sequentially), so it is a reliable ; completion signal - process handles/PIDs are not, because ; AHK's Run may surface an intermediate shell whose PID is ; immediately reused. FileAppend Format('echo done >"{1}"`n', doneFile) , batFile, "UTF-8-RAW" Run(Format('"{1}"', batFile), , "Hide", &vpid) ; The capture file is the primary signal: as soon as the error ; text lands, kill the whole validator tree (some builds still ; block on a dialog after writing it) and throw with the real ; parse message. The done marker means the validator finished ; with no captured error: valid text. deadline := A_TickCount + 6000 loop { size := FileExist(errFile) ? FileGetSize(errFile) : 0 if size > 0 { RunWait(Format('taskkill /pid {1} /T /F', vpid) , , "Hide") detail := Trim(FileRead(errFile, "UTF-8")) if StrLen(detail) > 300 detail := SubStr(detail, 1, 300) "..." throw Error("script validation failed: " detail, -1) } if FileExist(doneFile) break if A_TickCount >= deadline { ; No error text and no completion marker: kill the tree ; and treat validation as inconclusive - the loader's ; own rc check still guards the real load. RunWait(Format('taskkill /pid {1} /T /F', vpid) , , "Hide") return } Sleep 20 } } finally { ; The validator tree may still be releasing its file handles ; when this runs (taskkill returns before handles close), so ; cleanup races must never mask the real validation error. try { if FileExist(vFile) FileDelete(vFile) if FileExist(errFile) FileDelete(errFile) if FileExist(batFile) FileDelete(batFile) if FileExist(doneFile) FileDelete(doneFile) } catch { } } } static EvalSubprocess(expr) { AhkMagic.Init() if !(expr is String) throw TypeError("expr must be a string", -1) if expr = "" throw ValueError("expr must not be empty", -1) if StrLen(expr) > 4096 throw ValueError("expr too long", -1) tag := Format("{:x}", A_TickCount) "-" Format("{:x}", Random(1, 0x7fffffff)) scriptFile := A_Temp "\ahk_mcode_eval_" tag ".ahk" resultFile := A_Temp "\ahk_mcode_eval_" tag ".txt" quote := expr script := "try {`n" . " result := (" quote ")`n" . " FileAppend result, `"" resultFile "`", `"UTF-8`"`n" . "} catch as e {`n" . " FileAppend `"ERROR: `" e.Message, `"" resultFile "`", `"UTF-8`"`n" . " ExitApp 1`n" . "}`n" FileAppend script, scriptFile, "UTF-8" try { ; Compiled exes built from the regular v2 runtime can re-enter ; interpreter mode with /script; the SC build does not support it. cmd := A_IsCompiled ? Format('"{1}" /script /ErrorStdOut "{2}"', A_AhkPath, scriptFile) : Format('"{1}" /ErrorStdOut "{2}"', A_AhkPath, scriptFile) RunWait cmd, , "Hide" if !FileExist(resultFile) throw Error("eval child produced no result", -1) result := FileRead(resultFile, "UTF-8") if RegExMatch(result, "^ERROR: (.*)$", &m) throw Error("eval failed: " m[1], -1) return result } finally { try { if FileExist(scriptFile) FileDelete(scriptFile) if FileExist(resultFile) FileDelete(resultFile) } catch { } } } } MC_INTERNAL_LOCATOR_X64 := "4157415641554154565755534881ec38030000488bb424a8030000c744242c000000004885c90f94c04885d2410f94c24108c24d85c0410f94c34508d34885f6410f94c2b8010000004508da0f854a0200004c8b9c24a00300004929d10f92c04929d3410f92c24108c2b8020000000f85270200004d8d9100000200b8030000004d39c20f8712020000498d41074c39d00f87000200004801ca4881c10000010031ff4c89c8eb180f1f840000000000488d58014883c0084c39d04889d8777c803c024875ea488d1c02807b018d75e0807b020d75da4c6373034c01f34883c3074839cb72ca4189ff4531f685ff74154a399cf430010000740b49ffc64d39f775ee4589fe4139fe400f95c583ff40410f93c44108ec75134a899cfc3001000042c744bc3000000000ffc74139fe73804489f3ff449c30e974ffffff85ff0f845301000089fb89d883e00383ff040f832202000031ff4531f631c94885c074384a8d1cf44881c3300100004e8d34b44983c6304531ffeb100f1f84000000000049ffc74c39f87410438b2cbe39cd76f04a8b3cfb89e9ebe84885ff0f84f6000000b8050000004981f8000100000f82e900000031c94889d3eb20660f1f4400004c8d71014881c10101000048ffc34c39c14c89f10f87c2000000803c0a4475e0807c0a018975d9807c0a024c75d2807c0a032475cb807c0a042075c441be04000000eb106666662e0f1f8400000000004983c60242807c33fc66752442807c33fd83751c42807c33fe3a751442807c33ff0074386666662e0f1f8400000000004981fe000100000f8473ffffff42807c33fd6675bb42807c33fe8375b342807c33ff3a75ab42803c330075a44c8d72ff4885c97418410fb6040e3dc3000000740e3dcc000000740748ffc975e831c94801d14531ffeb2eb8040000004881c4380300005b5d5f5e415c415d415e415fc3498d5f014983c706b8080000004d39c74989df77d742803c3ae875e44a8d043a486358014801d84883c0054839c875d04d85ff740f4c89fb41803c1ecc740748ffcb75f431db488d83000100004c39c077ae4801d3b804000000eb044883c002807c03fc667515807c03fd83750e807c03fe3a7507807c03ff007427483d000100000f8478ffffff807c03fd6675cd807c03fe8375c6807c03ff3a75bf803c030075b94d39d34d0f42d3498d41104c39d00f866d0100004531f6488d4a064531ffe99100000083e3fc31ff4531f631c9eb1b66666666662e0f1f8400000000004983c6044c39f30f84befdffff428b6cb43039cd760a4a8bbcf43001000089e9428b6cb43439cd760a4a8bbcf43801000089e9428b6cb43839cd760a4a8bbcf44001000089e9428b6cb43c39cd76b14a8bbcf44801000089e9eba54d8d5f014983c70648ffc1b8060000004d39c74d89df0f8795feffff42803c3ae875dd4a8d043a4c6360014c01e04829d04883c0054c39c875c64d8d5f05498d470a4c39c0400f97c5498d47654939c3410f93c34108eb75a741bb050000004d29e34531e442807c21ffe8750d4e632c214b8d2c234c01ed75264f8d2c274983c5064939c50f8375ffffff4f8d2c274983c50b49ffc44d39c576cae960ffffff4c8d4c242c4889d54889d14c89c24d89d0e80f01000085c00f84f90000004c01ed4c01fd498d042c4883c00a48893e4c89760848895e10488946188b44242c89462031c0e9c8fdffff4d89ceeb14498d46014983c6114d39d64989c60f877afeffff42803c324875e542807c32018975dd42807c32025475d542807c32032475cd42807c32041075c542807c32055575bd42807c32065675b542807c32075775ad42807c32084175a542807c320954759d42807c320a41759542807c320b55758d42807c320c41758542807c320d560f8579ffffff42807c320e410f856dffffff4983fe020f8263ffffff42807c320f570f8557ffffff42807c32ffcc0f854bffffff42807c32fecc0f853fffffff4901d6e9c8fdffffb807000000e9f0fcffff0f1f4000498d80000800004839d0480f42d04983c00431c0eb0d662e0f1f84000000000049ffc04939d0772842807c01fc8375f042807c01fe1075e8460fb65401ff458d5ace4180fb3277d8458911b801000000c3" MC_MEM_SCRIPT_X64 := "5657534883ec204c89c64989ca488b4c24684d85d20f94c04885d2410f94c04108c04d85c9410f94c34508c34885c9410f94c0b8010000004508d80f85c80100008b4424600f57c00f11010f1141100f1141200f1141300f1141400f1141500f1141600f1141700f1181800000000f1181900000000f1181a00000000f1181b00000000f1181c00000000f1181d00000000f1181e00000000f1181f00000004c8d81000100000f1181000100000f1181100100000f1181200100000f1181400100000f1181500100000f1181600100000f1181700100000f1181800100000f1181900100000f1181a00100000f1181b00100000f1181c00100000f1181d00100000f1181e00100000f1181f00100004c8d1d0a0100004c89194c8d1d300100004c8959084c8d1d350100004c8959104c8d1d5a0100004c8959184c8d1d9f0200004c8959204c8d1da40200004c8959284c8d1da90200004c8959304c8d1dae0200004c8959384889890001000048c781080100000c00000049bb00000000b00400004c899910010000c781180100000200000066c7812c01000000000f1181300100004c898940010000898148010000c6814c010000004c898950010000898158010000c6815c010000004c8989600100004885f674048b3eeb0231ff4885f60f94c34889d14c89c24189f841ffd289c131c083f9010f95c108cb740688c801c0eb06ffc7893e31c04883c4205b5f5ec36666666666662e0f1f84000000000048c7414000000000c7414800000000c6414c0048c7415000000000c7415800000000c6415c0048c7416000000000c390b001c3666666662e0f1f84000000000048c7414000000000c7414800000000c6414c0048c7415000000000c7415800000000c6415c0048c7416000000000c390565753488b41404c8b51504c8b4960448b59484c29d04c01d84589c34c39d8410f43c085c074214189c083f808410f92c34889d64c29d64883fe200f92c34408db740d4531dbeb7e4531c0e9e300000083f82073054531dbeb424589c34183e3e031f6666666662e0f1f840000000000410f100432410f104c32100f1104320f114c32104883c6204939f375e34539c30f849d00000041f6c018742a4c89de4589c34183e3f8662e0f1f840000000000498b3c3248893c324883c6084939f375ef4539c3746d4c89c74c89de4883e703741e4c89de66662e0f1f840000000000410fb61c32881c3248ffc648ffcf75f04d29c34983fbfc773a0f1f8000000000450fb61c3244881c32450fb65c320144885c3201450fb65c320244885c3202450fb65c320344885c32034883c6044939f075cd4d01c24c8951504d01c14c8949605b5f5ec366662e0f1f84000000000031c0c3666666662e0f1f84000000000031c0c3666666662e0f1f840000000000488b4150482b4140c30f1f80000000008b4148c3" MC_INPROC_EVAL_X64 := "4157415641554154565755534881ecf80000004c8ba424b00100004c8bbc24680100004c8b94246001000048c78424e00000000000400048c78424c8000000000000004885c90f94c04885d2410f94c34108c34d85c00f94c04d85c90f94c308c34408db4d85d20f94c008d84d85ff410f94c34883bc2488010000000f94c34408db08c34883bc2498010000000f94c04883bc24a001000000410f94c34108c34d85e4400f94c7b8010000004408df4008df40f6c7010f85b10900004d8b6c2470498b7424604d8b7424684983fd100f92c04d8d5e044d39eb410f97c3488d7e084c39ef0f97c34408db08c3b80a0000000f85760900004c89b424980000004889b424b8000000498b7424784d8bb424900000004889b424c00000004c8d5e084d39eb0f97c3498bb424a0000000488d7ebf4883ffcf400f92c74c89b424a000000049ffc64939f6400f97c54d8b9c24980000004008dd498d5b0448897424504839f30f97c34008fb4008eb0f85fb0800004c895c2470488bbc2480010000488bb42478010000498b8424800000004889842480000000498b8424880000004889442478498d81000100004885ff4989fb4c0f44d8498d99800100004d8db1000300004c89b424d80000004c89b424f00000004885f6490f44f10f85980000000f57c0410f1101410f114110410f114120410f114130410f114140410f114150410f114160410f114170410f118180000000410f118190000000410f1181a0000000410f1181b0000000410f1181c0000000410f1181d0000000410f1181e0000000410f1181f00000004d8b342441c60436034d8b74240841c60436014d8b7424104d85f6740841c70436010000004d8b7424184d891c360f57c00f11030f1143100f1143200f1143300f1143400f1143500f1143600f1143706641833a00741e4531f66666662e0f1f8400000000006643837c7202004d8d760175f3eb034531f648898c24900000004885ff4c899c24880000004889b424b00000000f85e0020000498d890008100048894c24680f57c00f11000f1140100f1140200f1140300f1140400f1140500f1140600f114070498b44242042c6041800498b44242842c6041801498b44243046893418498b4424384e891418498b44245042c7041800010000498b44245842c70418000100004531db4c89d54889e931c00f1f40000fb73c0185ff0f841e02000083ff27743e83ff2274398d779f6683fe1a0f82070100006683ff5f0f84fd0000008d77a56683fee50f87f00000000fb7f781fe800000000f83e10000004883c002ebb1488d2c014883c502440fb7740102664585f6410f94c7664139fe400f94c64408fe753966666666662e0f1f840000000000664183fe60750b66837d020074044883c502440fb775024883c502664585f6410f94c77406664139fe75d54584ff4c8bbc24680100000f856e0100004181fbfe000000775c4489de480faf7424504803742468488d3c014883c7024d89de4c8b9c248000000049893c334c8b5c247841c60433014c8b9c24a00000006641c7043303014d89f34889ef4829cf4829c74883c7fe48d1ef488b442470893c3041ffc34883c502e9cdfeffff4c895c24584c8d3c014531f64c89fdeb100f1f4400000fb77d024883c50241ffc68d779f6683fe1a72ec8d77bf6683fe1a400f92c66683ff5f410f94c34108f375d4440fb7df4181fb80000000410f92c38d77c66683fef6400f92c64184f374b54d39d7742f488b7c245881ffff000000410f92c366837c01fe2e400f95c64484de4989fb4c8bbc24680100000f843afeffffeb1a4c8b5c24584181fbfe0000004c8bbc24680100000f871efeffff448b5c24584c0faf5c24504c035c24684801c1488b8424800000004a890c18488b4424784ac7041800000000488b8424a00000006642c704180000488b442470468934184c8b5c245841ffc3e9cdfdffff4585db4c89d84c8b9c2488000000488bb424b0000000742189c0488b4c2450480fafc84c8b5424684ac7041100000000498b4424404e8914184c894c2458488954246849c707010000000f57c0410f1147084d8b304c894424504989304c8d8424c80000004889f14c89daff94249000000089c5488b8c24c80000004885c97407ff9424a001000083fd010f85e30100004883bc247001000000488b5424500f84e3010000498b442448488b8c2488000000488b3c084885ff0f84de0100004c897424604c8b9c24a8010000488b8424980000008b0407be010000004439d8488b4c24584c8b8424900000004c8b5424700f85b5010000488b8424900100004885c00f94c24c39c0410f94c04108d0488bbc2488000000488bac24b000000075164889e94889faffd0488b4c245883f8010f85a8020000488b9424d800000048899424e80000004c8bb4249800000041c7840e80010000ffffffff488b8424c000000048c7840880010000ffffffff4989940d8001000049c7840d88010000000000004881c100033f0048894c2438488d8424e00000004889442430488d8424f00000004889442428488d8424e8000000488944242048c744244000003f004c8d8424ac0000004889e931d24989d9ff542468488b4c2450488b5424604889114885c00f84d601000041c70700000000418b0c1e41894f04488b8c24b8000000488b0c1949894f0849894f10498d4f204889ca4829da4883fa0f0f87ec010000ba05000000440fb64413fb44884411fb440fb64413fc44884411fc440fb64413fd44884411fd440fb64413fe44884411fe440fb64413ff44884411ff440fb60413448804114883c2064883fa3575b6e9ae010000488b4424504c893041c70702000000e9590200004c893248b80000000063000000498907e9440200004c893241c70708000000e9350200004c8b8c24c00000004a8d2c0f4c8b8c24b80000004e8d340f4c01ef4803bc249800000049b9010000000080ffff4981c1010001004c898c24d0000000eb234c8b4c24784a8b04084989068b074c01ed4d01ee4c01ef48ffc64439d80f84eafdffff4881fe010001000f84de0000003d011000000f83a600000083f80475cc837d000277c6498b064885c074be49b9010000000080ffff4901c14c3b8c24d0000000727c4c8b8c24a000000042803c0807748c468b0410488b8c2480000000488b1408488b8c248801000041b903010000ff9424980100004c8b9c24a80100004c8b5424704c8b842490000000488b4c2458488b5424504885c00f8548ffffff488b44246048890241c70705000000e92201000041c70703000000e916010000488b44246048890241c70709000000e902010000488b442450488b4c246048890841c70706000000e9e9000000488b44246048890241c70707000000e9d50000000f10030f11010f1043100f1141100f1043200f114120418b4f0483f905774aba270000000fa3ca7340498b442448488b043849894750498b44244848833c38000f8481000000490faff54885f674784881fe20010000b820010000480f42c64883fe01751231c9eb4841c747040000000049894710ebb289c281e2fe01000031c94d8b4424484d8b0438450fb604084588440f584d8b4424484d8b0438450fb64408014588440f594883c1024839ca75d0a8017412498b442448488b04380fb604084188440f588b8424ac000000418987c800000031c04881c4f80000005b5d5f5e415c415d415e415fc3"