@eyeauras/insight-cli 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.cli-package-build.json +1 -1
  2. package/README.md +34 -62
  3. package/SKILL.md +4 -1
  4. package/dist/src/arguments.js +9 -4
  5. package/dist/src/cli.js +45 -37
  6. package/dist/src/code-references.d.ts +48 -0
  7. package/dist/src/code-references.js +154 -0
  8. package/dist/src/context-diagnostics.d.ts +25 -0
  9. package/dist/src/context-diagnostics.js +82 -0
  10. package/dist/src/contexts.d.ts +13 -1
  11. package/dist/src/contexts.js +38 -9
  12. package/dist/src/function-boundaries.d.ts +9 -0
  13. package/dist/src/function-boundaries.js +68 -0
  14. package/dist/src/function-entries.d.ts +33 -0
  15. package/dist/src/function-entries.js +84 -0
  16. package/dist/src/function-inspection.d.ts +4 -2
  17. package/dist/src/function-inspection.js +94 -59
  18. package/dist/src/function-results.d.ts +3 -1
  19. package/dist/src/function-results.js +9 -3
  20. package/dist/src/inline-script.d.ts +15 -0
  21. package/dist/src/inline-script.js +62 -0
  22. package/dist/src/internal-timing.d.ts +3 -0
  23. package/dist/src/internal-timing.js +52 -0
  24. package/dist/src/lifecycle.js +14 -4
  25. package/dist/src/native-inputs.d.ts +1 -1
  26. package/dist/src/native-inputs.js +1 -1
  27. package/dist/src/native-queries.js +1 -1
  28. package/dist/src/persistent-client.d.ts +30 -0
  29. package/dist/src/persistent-client.js +125 -0
  30. package/dist/src/queries.js +4 -1
  31. package/dist/src/query-policy.js +9 -1
  32. package/dist/src/research-view.d.ts +6 -0
  33. package/dist/src/research-view.js +9 -0
  34. package/dist/src/research.d.ts +38 -0
  35. package/dist/src/research.js +78 -8
  36. package/dist/src/string-anchors.d.ts +43 -0
  37. package/dist/src/string-anchors.js +176 -0
  38. package/dist/src/sync-policy.js +3 -0
  39. package/dist/src/timed-backend.d.ts +3 -0
  40. package/dist/src/timed-backend.js +16 -0
  41. package/dist/src/transport.d.ts +1 -0
  42. package/dist/src/transport.js +4 -0
  43. package/dist/src/workspace.js +5 -2
  44. package/package.json +12 -1
  45. package/references/native/InsightAnchorScan.java +135 -0
  46. package/references/native/InsightCodeReferences.java +129 -0
  47. package/references/native/InsightFunctionBoundaries.java +56 -0
  48. package/references/native/InsightInlineExecutor.java +143 -0
  49. package/references/native/InsightInlineScript.java +14 -0
  50. package/references/native/InsightPeRanges.java +92 -0
  51. package/references/native/InsightResearchQueries.java +22 -1
  52. package/references/native/InsightResearchView.java +69 -0
  53. package/references/native/InsightStringAnchors.java +49 -0
  54. package/references/persistent-client.mjs +125 -0
  55. package/references/persistent.md +50 -0
  56. package/references/query-semantics.md +12 -0
  57. package/references/recovery.md +36 -0
  58. package/references/research.md +87 -0
  59. package/references/setup.md +1 -1
  60. package/references/sparse-navigation.md +173 -0
  61. package/runtime/GhidraMCP-7.0.0.jar +0 -0
  62. package/runtime/GhidraMCP-NOTICE +5 -0
  63. package/runtime/provenance.json +17 -3
  64. package/runtime/research-view.provenance.json +28 -0
  65. package/runtime/toolbox.json +10 -3
  66. package/skill-bundle/insight-artifacts/SKILL.md +1 -1
  67. package/skill-bundle/insight-artifacts/references/recovery.md +36 -0
  68. package/skill-bundle/insight-binary/SKILL.md +1 -1
  69. package/skill-bundle/insight-capture/SKILL.md +1 -1
  70. package/skill-bundle/insight-cli/SKILL.md +4 -1
  71. package/skill-bundle/insight-cli/references/persistent-client.mjs +125 -0
  72. package/skill-bundle/insight-cli/references/persistent.md +50 -0
  73. package/skill-bundle/insight-cli/references/recovery.md +36 -0
  74. package/skill-bundle/insight-cli/references/setup.md +1 -1
  75. package/skill-bundle/insight-die/SKILL.md +1 -1
  76. package/skill-bundle/insight-dnspy/SKILL.md +1 -1
  77. package/skill-bundle/insight-dotnet-dump/SKILL.md +1 -1
  78. package/skill-bundle/insight-dotnet-stack/SKILL.md +1 -1
  79. package/skill-bundle/insight-dotnet-trace/SKILL.md +1 -1
  80. package/skill-bundle/insight-frida/SKILL.md +1 -1
  81. package/skill-bundle/insight-ghidra/SKILL.md +14 -1
  82. package/skill-bundle/insight-ghidra/references/persistent-client.mjs +125 -0
  83. package/skill-bundle/insight-ghidra/references/persistent.md +50 -0
  84. package/skill-bundle/insight-ghidra/references/query-semantics.md +12 -0
  85. package/skill-bundle/insight-ghidra/references/recovery.md +36 -0
  86. package/skill-bundle/insight-ghidra/references/research.md +87 -0
  87. package/skill-bundle/insight-ghidra/references/setup.md +1 -1
  88. package/skill-bundle/insight-ghidra/references/sparse-navigation.md +173 -0
  89. package/skill-bundle/insight-ilspy/SKILL.md +1 -1
  90. package/skill-bundle/insight-interop/SKILL.md +1 -1
  91. package/skill-bundle/insight-lldb/SKILL.md +1 -1
  92. package/skill-bundle/insight-llvm/SKILL.md +1 -1
  93. package/skill-bundle/insight-managed-heap/SKILL.md +1 -1
  94. package/skill-bundle/insight-managed-metadata/SKILL.md +1 -1
  95. package/skill-bundle/insight-mitmproxy/SKILL.md +1 -1
  96. package/skill-bundle/insight-overview/SKILL.md +1 -1
  97. package/skill-bundle/insight-procmon/SKILL.md +1 -1
  98. package/skill-bundle/insight-re/SKILL.md +4 -1
  99. package/skill-bundle/insight-re/references/persistent-client.mjs +125 -0
  100. package/skill-bundle/insight-re/references/persistent.md +50 -0
  101. package/skill-bundle/insight-re/references/recovery.md +36 -0
  102. package/skill-bundle/insight-sevenzip/SKILL.md +1 -1
  103. package/skill-bundle/insight-strace/SKILL.md +1 -1
  104. package/skill-bundle/insight-tshark/SKILL.md +1 -1
  105. package/skill-bundle/insight-windbg/SKILL.md +1 -1
  106. package/skill-bundle/manifest.json +1 -1
  107. package/skills/insight-artifacts/SKILL.md +1 -1
  108. package/skills/insight-artifacts/references/recovery.md +36 -0
  109. package/skills/insight-binary/SKILL.md +1 -1
  110. package/skills/insight-capture/SKILL.md +1 -1
  111. package/skills/insight-die/SKILL.md +1 -1
  112. package/skills/insight-dnspy/SKILL.md +1 -1
  113. package/skills/insight-dotnet-dump/SKILL.md +1 -1
  114. package/skills/insight-dotnet-stack/SKILL.md +1 -1
  115. package/skills/insight-dotnet-trace/SKILL.md +1 -1
  116. package/skills/insight-frida/SKILL.md +1 -1
  117. package/skills/insight-ghidra/SKILL.md +14 -1
  118. package/skills/insight-ghidra/references/persistent-client.mjs +125 -0
  119. package/skills/insight-ghidra/references/persistent.md +50 -0
  120. package/skills/insight-ghidra/references/query-semantics.md +12 -0
  121. package/skills/insight-ghidra/references/recovery.md +36 -0
  122. package/skills/insight-ghidra/references/research.md +87 -0
  123. package/skills/insight-ghidra/references/setup.md +1 -1
  124. package/skills/insight-ghidra/references/sparse-navigation.md +173 -0
  125. package/skills/insight-ilspy/SKILL.md +1 -1
  126. package/skills/insight-interop/SKILL.md +1 -1
  127. package/skills/insight-lldb/SKILL.md +1 -1
  128. package/skills/insight-llvm/SKILL.md +1 -1
  129. package/skills/insight-managed-heap/SKILL.md +1 -1
  130. package/skills/insight-managed-metadata/SKILL.md +1 -1
  131. package/skills/insight-mitmproxy/SKILL.md +1 -1
  132. package/skills/insight-overview/SKILL.md +1 -1
  133. package/skills/insight-procmon/SKILL.md +1 -1
  134. package/skills/insight-re/SKILL.md +4 -1
  135. package/skills/insight-re/references/persistent-client.mjs +125 -0
  136. package/skills/insight-re/references/persistent.md +50 -0
  137. package/skills/insight-re/references/recovery.md +36 -0
  138. package/skills/insight-sevenzip/SKILL.md +1 -1
  139. package/skills/insight-strace/SKILL.md +1 -1
  140. package/skills/insight-tshark/SKILL.md +1 -1
  141. package/skills/insight-windbg/SKILL.md +1 -1
@@ -0,0 +1,129 @@
1
+ import ghidra.app.script.GhidraScript;
2
+ import ghidra.app.util.PseudoDisassembler;
3
+ import ghidra.app.util.PseudoInstruction;
4
+ import ghidra.program.model.address.*;
5
+ import ghidra.program.model.mem.MemoryBlock;
6
+ import com.google.gson.*;
7
+ import java.nio.charset.StandardCharsets;
8
+ import java.util.*;
9
+
10
+ /** Bounded candidate discovery only. Never creates instructions, functions or references. */
11
+ public class InsightCodeReferences extends GhidraScript {
12
+ static final String ALGORITHM="x86-64-code-reference-candidates-v1";
13
+ static final List<String> FORMS=List.of("call-rel32","lea-rip","mov-rip","call-rip-slot");
14
+ JsonObject p; long deadline,started; int bytesRead,tested,visited,maxBytes,maxCandidates,limit;
15
+ String reason="exhausted"; Address next; Set<Address> targets=new HashSet<>(); List<String> forms=new ArrayList<>();
16
+ List<Object> matches=new ArrayList<>();
17
+ Map<String,Object> map(Object... pairs){Map<String,Object> m=new LinkedHashMap<>();for(int i=0;i<pairs.length;i+=2)m.put((String)pairs[i],pairs[i+1]);return m;}
18
+ int number(String k,int d,int min,int max){int v=p.has(k)?p.get(k).getAsInt():d;if(v<min||v>max)throw new IllegalArgumentException(k+" outside supported bounds");return v;}
19
+ Address address(String s){if(!s.matches("[a-fA-F0-9]{1,16}"))throw new IllegalArgumentException("Default-space 64-bit hexadecimal address required");return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(Long.parseUnsignedLong(s,16));}
20
+ String hex(Address a){return Long.toUnsignedString(a.getOffset(),16);}
21
+ boolean expired()throws Exception{monitor.checkCancelled();return System.nanoTime()>=deadline;}
22
+ record Encoding(String form,int length,int operand,int bits,int delta,String role){}
23
+ Encoding encoding(byte[] b,int start){
24
+ int i=start,rex=0;boolean word=false;
25
+ while(i<b.length&&i-start<15){int v=b[i]&255;if(v==0x66||v==0x26||v==0x2e||v==0x36||v==0x3e){word|=v==0x66;rex=0;i++;}else if(v>=0x40&&v<=0x4f){rex=v;i++;}else break;}
26
+ if(i>=b.length)return null;int op=b[i]&255,length,disp,operand,bits;String form,role;
27
+ if(op==0xe8){form="call-rel32";role="direct-call-target";length=i-start+5;disp=i+1;operand=0;bits=64;}
28
+ else{
29
+ if(i+1>=b.length)return null;int m=b[i+1]&255;if((m&0xc7)!=5)return null;
30
+ if(op==0x8d){form="lea-rip";operand=1;}
31
+ else if(op==0x88||op==0x89||op==0x8a||op==0x8b){form="mov-rip";operand=op==0x88||op==0x89?0:1;}
32
+ else if(op==0xff&&((m>>3)&7)==2){form="call-rip-slot";operand=0;}
33
+ else return null;
34
+ role=form.equals("call-rip-slot")?"pointer-slot":"effective-address";
35
+ length=i-start+6;disp=i+2;bits=form.equals("call-rip-slot")?64:op==0x88||op==0x8a?8:(rex&8)!=0?64:word?16:32;
36
+ }
37
+ if(length>15||start+length>b.length)return null;
38
+ int delta=(b[disp]&255)|((b[disp+1]&255)<<8)|((b[disp+2]&255)<<16)|(b[disp+3]<<24);
39
+ return new Encoding(form,length,operand,bits,delta,role);
40
+ }
41
+ boolean compatible(PseudoInstruction i,Encoding e,Address target){
42
+ if(i==null||i.getLength()!=e.length)return false;
43
+ if(e.form.equals("call-rel32"))return i.getMnemonicString().equalsIgnoreCase("CALL")&&i.getFlowType().isCall()&&!i.getFlowType().isComputed()&&Arrays.asList(i.getFlows()).contains(target);
44
+ if(e.form.equals("call-rip-slot"))return i.getMnemonicString().equalsIgnoreCase("CALL")&&i.getFlowType().isCall()&&i.getFlowType().isComputed();
45
+ if(!i.getMnemonicString().equalsIgnoreCase(e.form.equals("lea-rip")?"LEA":"MOV"))return false;
46
+ var register=i.getRegister(e.operand==0?1:0);return register!=null&&register.getBitLength()==e.bits;
47
+ }
48
+ String boundary(Address at,PseudoInstruction decoded,byte[] bytes)throws Exception{
49
+ var listing=currentProgram.getListing();var stored=listing.getInstructionContaining(at);
50
+ if(stored==null){var after=listing.getInstructionAfter(at);return after!=null&&after.getAddress().compareTo(at.addNoWrap(bytes.length-1))<=0?"listing-conflict":"unconfirmed";}
51
+ if(!stored.getAddress().equals(at)||stored.getLength()!=decoded.getLength()||!Arrays.equals(stored.getBytes(),bytes)||!stored.getMnemonicString().equals(decoded.getMnemonicString())||stored.getNumOperands()!=decoded.getNumOperands())return "listing-conflict";
52
+ for(int i=0;i<stored.getNumOperands();i++)if(!stored.getDefaultOperandRepresentation(i).equals(decoded.getDefaultOperandRepresentation(i)))return "listing-conflict";
53
+ return "listing-confirmed";
54
+ }
55
+ Object scan()throws Exception{
56
+ if(!currentProgram.getLanguageID().toString().startsWith("x86:LE:64:")||currentProgram.getAddressFactory().getDefaultAddressSpace().getSize()!=64)throw new UnsupportedOperationException("Code references V1 requires x86-64 little-endian default memory space");
57
+ var mode=currentProgram.getRegister("longMode");if(mode==null)throw new UnsupportedOperationException("64-bit decode mode is unavailable");
58
+ Address from=address(p.get("scan_from").getAsString()),through=address(p.get("scan_through").getAsString());if(from.compareTo(through)>0)throw new IllegalArgumentException("Inverted scan range");
59
+ var ts=p.getAsJsonArray("targets");if(ts.size()<1||ts.size()>64)throw new IllegalArgumentException("Select 1..64 targets");for(var t:ts)if(!targets.add(address(t.getAsString())))throw new IllegalArgumentException("Duplicate target");
60
+ var fs=p.getAsJsonArray("forms");for(String f:FORMS)for(var v:fs)if(f.equals(v.getAsString()))forms.add(f);if(forms.isEmpty()||new HashSet<>(forms).size()!=fs.size()||forms.size()!=fs.size())throw new IllegalArgumentException("Unsupported or duplicate form");
61
+ maxBytes=number("max_bytes",4194304,15,16777216);maxCandidates=number("max_candidates",200000,1,1000000);limit=number("limit",100,1,1000);
62
+ started=System.nanoTime();deadline=started+number("timeout_seconds",5,1,30)*1000000000L;
63
+ List<Address[]> runs=new ArrayList<>();var blocks=currentProgram.getMemory().getBlocks();Arrays.sort(blocks,Comparator.comparing(MemoryBlock::getStart));
64
+ for(var block:blocks){
65
+ if(expired()){reason="timeout";next=from;return result(from,through);}
66
+ if(!block.isInitialized()||!block.isExecute()||!block.getStart().getAddressSpace().equals(from.getAddressSpace())||block.getEnd().compareTo(from)<0||block.getStart().compareTo(through)>0)continue;
67
+ Address a=block.getStart().compareTo(from)<0?from:block.getStart(),z=block.getEnd().compareTo(through)>0?through:block.getEnd();
68
+ if(!runs.isEmpty()&&a.equals(runs.getLast()[1].next()))runs.getLast()[1]=z;
69
+ else runs.add(new Address[]{a,z});
70
+ }
71
+ var decoder=new PseudoDisassembler(currentProgram);
72
+ outer:for(int runIndex=0;runIndex<runs.size();runIndex++){
73
+ var run=runs.get(runIndex);Address at=run[0];
74
+ while(at.compareTo(run[1])<=0){
75
+ next=at;
76
+ if(expired()){reason="timeout";break outer;}
77
+ int remaining=maxBytes-bytesRead;long available=run[1].subtract(at)+1;
78
+ if(remaining<Math.min(15,available)){reason="byte_limit";break outer;}
79
+ int count=(int)Math.min(Math.min(65550,available),remaining);byte[] data=new byte[count];
80
+ if(currentProgram.getMemory().getBytes(at,data)!=count)throw new IllegalStateException("Incomplete initialized executable memory read");bytesRead+=count;
81
+ int positions=count==available?count:count-14;
82
+ for(int i=0;i<positions;i++){
83
+ Address here=at.addNoWrap(i);next=here;
84
+ if(expired()){reason="timeout";break outer;}
85
+ Encoding e=encoding(data,i);
86
+ if(e!=null&&forms.contains(e.form)){
87
+ if(visited==maxCandidates){reason="candidate_limit";break outer;}
88
+ var modeValue=currentProgram.getProgramContext().getValue(mode,here,false);
89
+ if(modeValue==null||!modeValue.equals(java.math.BigInteger.ONE))throw new UnsupportedOperationException("Candidate encountered outside known 64-bit decode mode");
90
+ visited++;
91
+ Address target=here.addWrap(e.length+(long)e.delta);
92
+ if(targets.contains(target)){
93
+ PseudoInstruction instruction=null;
94
+ try{instruction=decoder.disassemble(here,Arrays.copyOfRange(data,i,Math.min(data.length,i+15)));}
95
+ catch(ghidra.program.model.lang.InsufficientBytesException|ghidra.program.model.lang.UnknownInstructionException ignored){}
96
+ catch(ghidra.program.model.lang.UnknownContextException error){throw new UnsupportedOperationException("Native decoder context unavailable");}
97
+ if(compatible(instruction,e,target)){
98
+ byte[] bytes=Arrays.copyOfRange(data,i,i+e.length);String display=instruction.toString();
99
+ matches.add(map("from",hex(here),"to",hex(target),"form",e.form,"bytes",HexFormat.of().formatHex(bytes),"length",e.length,"operand_index",e.operand,"operand_bits",e.bits,"target_role",e.role,"validation","decoded","boundary",boundary(here,instruction,bytes),"instruction",display.substring(0,Math.min(512,display.length()))));
100
+ }
101
+ }
102
+ }
103
+ tested++;
104
+ if(here.equals(through)||here.equals(run[1])&&runIndex==runs.size()-1)next=null;
105
+ else next=here.addNoWrap(1);
106
+ if(matches.size()==limit&&next!=null){reason="result_limit";break outer;}
107
+ }
108
+ if(positions==available)break;
109
+ at=at.addNoWrap(positions);
110
+ }
111
+ }
112
+ if(reason.equals("exhausted"))next=null;
113
+ return result(from,through);
114
+ }
115
+ Object result(Address from,Address through){return map("algorithm",ALGORITHM,"language",currentProgram.getLanguageID().toString(),"forms",forms,"matches",matches,
116
+ "scan",map("from",hex(from),"through",hex(through),"next_start",next==null?null:hex(next),"complete",reason.equals("exhausted"),"reason",reason,"bytes_read",bytesRead,"tested_starts",tested,"visited_candidates",visited,"elapsed_ms",(System.nanoTime()-started)/1000000.0),
117
+ "coverage","Every byte start in initialized executable default-space memory, in address order; adjacent eligible blocks merge. Instructions fit entirely within the requested range. Overlapping decodes are separate candidates.",
118
+ "limitations",List.of("Decoded byte candidates do not prove instruction boundaries, execution or callgraph membership.","Indirect CALL targets identify pointer slots; pointees are never followed.","Only explicit V1 encodings; 67, FS/GS, LOCK/REP, SIMD and immediate MOV encodings are excluded at that start. A later start inside their bytes may still be a candidate."));}
119
+ public void run()throws Exception{
120
+ Map<String,Object> output;long modification=currentProgram.getModificationNumber();
121
+ try{
122
+ var request=JsonParser.parseString(new String(Base64.getDecoder().decode(getScriptArgs()[0]),StandardCharsets.UTF_8)).getAsJsonObject();
123
+ if(!request.get("operation").getAsString().equals("/code_references"))throw new IllegalArgumentException("Unknown code-reference operation");p=request.getAsJsonObject("parameters");
124
+ var value=(Map<String,Object>)scan();if(currentProgram.getModificationNumber()!=modification)throw new IllegalStateException("Read-only scan changed program modification counter");
125
+ value.put("read_only",map("modification_unchanged",true,"modification",Long.toString(modification)));output=map("value",value);
126
+ }catch(Exception error){output=map("error",map("code",error instanceof UnsupportedOperationException?"unsupported":error instanceof IllegalArgumentException?"invalid":"partial","message",Objects.toString(error.getMessage(),"Native code-reference scan failed")));}
127
+ println("INSIGHT_QUERY:"+new GsonBuilder().serializeNulls().create().toJson(output));
128
+ }
129
+ }
@@ -0,0 +1,56 @@
1
+ package com.eyeauras.insight;
2
+
3
+ import com.xebyte.core.*;
4
+ import com.google.gson.Gson;
5
+ import com.google.gson.GsonBuilder;
6
+ import ghidra.program.model.listing.Program;
7
+ import java.math.BigInteger;
8
+ import java.util.*;
9
+
10
+ /** Authenticated bounded runtime-region evidence for explicitly selected open programs. */
11
+ public final class InsightFunctionBoundaries {
12
+ private static final Gson JSON=new GsonBuilder().disableHtmlEscaping().serializeNulls().create();
13
+ private final ProgramProvider programs;private final ThreadingStrategy threading;
14
+ public InsightFunctionBoundaries(ProgramProvider programs,ThreadingStrategy threading){this.programs=programs;this.threading=threading;}
15
+ @McpTool(path="/insight/function-boundaries/v1",method="GET",description="Compare recorded function membership with current mapped PE32+ AMD64 runtime regions; no semantic reconstruction",access=ToolAccess.READ_ONLY)
16
+ public Response boundaries(@Param(value="program",source=ParamSource.QUERY,allowEmpty=false) String path,
17
+ @Param(value="addresses",source=ParamSource.QUERY,allowEmpty=false) String addresses,
18
+ @Param(value="max_unwind_bytes",source=ParamSource.QUERY,allowEmpty=false) int maximum)throws Exception {
19
+ if(maximum<1||maximum>16777216||addresses==null||addresses.length()>339)throw new IllegalArgumentException("Expected 1–20 addresses and byte budget 1–16777216");
20
+ String[] words=addresses.split(",",-1);if(words.length<1||words.length>20)throw new IllegalArgumentException("Expected 1–20 addresses");
21
+ Set<String> unique=new HashSet<>();for(String word:words)if(!word.matches("0|[1-9a-f][a-f0-9]{0,15}")||!unique.add(word))throw new IllegalArgumentException("Expected distinct canonical addresses");
22
+ return threading.executeRead(()->{
23
+ if(programs==null||path==null||!path.startsWith("/")||path.length()>4096)throw new IllegalArgumentException("Explicit open program path required");
24
+ Program program=null;for(Program p:programs.getAllOpenPrograms())if(p!=null&&!p.isClosed()&&p.getDomainFile()!=null&&path.equals(p.getDomainFile().getPathname())){if(program!=null&&program!=p)throw new IllegalStateException("Ambiguous program path");program=p;}
25
+ if(program==null)throw new IllegalArgumentException("Program is not open");
26
+ var space=program.getAddressFactory().getDefaultAddressSpace();var memory=program.getMemory();var functions=program.getFunctionManager();long modification=program.getModificationNumber();
27
+ long lookupStarted=System.nanoTime();List<InsightPeRanges.Span> spans=new ArrayList<>();
28
+ for(var block:memory.getBlocks())if(block.isInitialized()&&block.getStart().getAddressSpace().equals(space))spans.add(new InsightPeRanges.Span(new BigInteger(Long.toUnsignedString(block.getStart().getOffset(),16),16),new BigInteger(Long.toUnsignedString(block.getEnd().getOffset(),16),16).add(BigInteger.ONE),block.isExecute()));
29
+ InsightPeRanges.Result scan;
30
+ if(space.getSize()!=64||space.getAddressableUnitSize()!=1||!program.getLanguageID().toString().equals("x86:LE:64:default"))scan=new InsightPeRanges.Result(Map.of("state","unavailable","complete",false,"reason","unsupported_language","headersSource","current-mapped-memory","recordCount",0,"recordsValidated",0,"bytesRead",0,"maximumBytes",maximum,"deadlineMilliseconds",5000),List.of());
31
+ else scan=new InsightPeRanges(new BigInteger(Long.toUnsignedString(program.getImageBase().getOffset(),16),16),spans,(at,out)->memory.getBytes(space.getAddress(at.longValue()),out),maximum,System::nanoTime,Arrays.stream(words).map(w->new BigInteger(w,16)).toList(),lookupStarted).run();
32
+ Map<String,Object> items=new LinkedHashMap<>();boolean complete=Boolean.TRUE.equals(scan.lookup().get("complete"));
33
+ for(String word:words){
34
+ BigInteger query=new BigInteger(word,16);Map<String,Object> row=new LinkedHashMap<>();Map<String,Object> database=null;
35
+ if(query.bitLength()<=space.getSize()){
36
+ var at=space.getAddress(query.longValue());var f=functions.getFunctionAt(at);String kind="exact-entry";if(f==null){f=functions.getFunctionContaining(at);kind="recorded-containing";}
37
+ if(f!=null)database=Map.of("entryPoint",Long.toUnsignedString(f.getEntryPoint().getOffset(),16),"kind",kind,"recordedBodyContainsAddress",f.getBody().contains(at));
38
+ }
39
+ row.put("database",database);InsightPeRanges.Range range=null;
40
+ for(var candidate:scan.ranges())if(query.compareTo(candidate.begin())>=0&&query.compareTo(candidate.endExclusive())<0){range=candidate;break;}
41
+ row.put("boundary",range==null?null:range.facts());row.put("status","unresolved");
42
+ if(!complete)row.put("reason","boundary_lookup_"+scan.lookup().get("state"));
43
+ else if(range!=null&&database!=null&&!database.get("entryPoint").equals(range.begin().toString(16)))row.put("reason","boundary_conflict");
44
+ else if(range!=null){
45
+ String entry=range.begin().toString(16);
46
+ if(functions.getFunctionAt(space.getAddress(range.begin().longValue()))==null)row.put("reason","runtime_entry_not_defined");
47
+ else{row.put("status","resolved");row.put("entryPoint",entry);row.put("kind","pe-runtime-range");row.put("recordedBodyContainsAddress",database!=null&&Boolean.TRUE.equals(database.get("recordedBodyContainsAddress")));}
48
+ }else if(database!=null){row.put("status","resolved");row.put("entryPoint",database.get("entryPoint"));row.put("kind",database.get("kind"));row.put("recordedBodyContainsAddress",database.get("recordedBodyContainsAddress"));}
49
+ else row.put("reason","no_runtime_range_or_recorded_function");
50
+ items.put(word,row);
51
+ }
52
+ if(modification!=program.getModificationNumber())throw new IllegalStateException("Program changed during function-boundary lookup");
53
+ return Response.text(JSON.toJson(Map.of("protocol","insight-function-boundaries-v1","modification",Long.toString(modification),"boundaryLookup",scan.lookup(),"items",items)));
54
+ });
55
+ }
56
+ }
@@ -0,0 +1,143 @@
1
+ package insight;
2
+
3
+ import com.google.gson.*;
4
+ import generic.jar.ResourceFile;
5
+ import ghidra.app.script.*;
6
+ import ghidra.util.task.TaskMonitor;
7
+ import java.io.*;
8
+ import java.lang.reflect.*;
9
+ import java.net.*;
10
+ import java.nio.charset.StandardCharsets;
11
+ import java.nio.channels.FileChannel;
12
+ import java.nio.file.*;
13
+ import java.security.MessageDigest;
14
+ import java.util.*;
15
+ import java.util.regex.*;
16
+ import javax.tools.*;
17
+
18
+ /** Owned Java-only inline executor. Never infers non-execution from exception text or stdout. */
19
+ public final class InsightInlineExecutor {
20
+ static final int OUTPUT_BYTES=32768, MAX_DIAGNOSTICS=100;
21
+ static final Gson JSON=new GsonBuilder().serializeNulls().create();
22
+ static final class BoundedOutput extends OutputStream {
23
+ final ByteArrayOutputStream bytes=new ByteArrayOutputStream(); boolean truncated;
24
+ public synchronized void write(int b){if(bytes.size()<OUTPUT_BYTES)bytes.write(b);else truncated=true;}
25
+ public synchronized void write(byte[] b,int start,int length){int n=Math.min(length,OUTPUT_BYTES-bytes.size());bytes.write(b,start,n);if(n<length)truncated=true;}
26
+ synchronized String text(){return bytes.toString(StandardCharsets.UTF_8);}
27
+ synchronized Map<String,Object> value(){return Map.of("text",text(),"truncated",truncated,"maximumBytes",OUTPUT_BYTES);}
28
+ }
29
+ record Source(String name,String text,int prefixLines,int userLines,boolean wrapped) {}
30
+ /** Keep offsets/newlines, but hide comments and literals from declaration/import recognition. */
31
+ static String lexical(String s){
32
+ char[] out=s.toCharArray();int state=0;
33
+ for(int i=0;i<s.length();i++){
34
+ char c=s.charAt(i),n=i+1<s.length()?s.charAt(i+1):0;
35
+ if(state==0){
36
+ if(c=='/'&&n=='/'){out[i]=out[++i]=' ';state=1;}
37
+ else if(c=='/'&&n=='*'){out[i]=out[++i]=' ';state=2;}
38
+ else if(c=='"'&&i+2<s.length()&&s.startsWith("\"\"\"",i)){out[i]=out[i+1]=out[i+2]=' ';i+=2;state=5;}
39
+ else if(c=='"'||c=='\''){out[i]=' ';state=c=='"'?3:4;}
40
+ }else{
41
+ if(c!='\n'&&c!='\r')out[i]=' ';
42
+ if(state==1&&c=='\n')state=0;
43
+ else if(state==2&&c=='*'&&n=='/'){out[++i]=' ';state=0;}
44
+ else if((state==3||state==4)&&c=='\\'&&i+1<s.length()){if(s.charAt(i+1)!='\n')out[++i]=' ';}
45
+ else if((state==3&&c=='"')||(state==4&&c=='\''))state=0;
46
+ else if(state==5&&s.startsWith("\"\"\"",i)){out[i]=out[i+1]=out[i+2]=' ';i+=2;state=0;}
47
+ }
48
+ }
49
+ return new String(out);
50
+ }
51
+ static Source source(String code,String attempt){
52
+ String lex=lexical(code);
53
+ Matcher type=Pattern.compile("\\bpublic\\s+(?:(?:abstract|final|strictfp|sealed|non-sealed)\\s+)*(?:class|interface|enum|record)\\s+([A-Za-z_$][\\w$]*)").matcher(lex);
54
+ if(type.find()){
55
+ Matcher pkg=Pattern.compile("\\bpackage\\s+([A-Za-z_$][\\w$]*(?:\\s*\\.\\s*[A-Za-z_$][\\w$]*)*)\\s*;").matcher(lex);
56
+ String prefix=pkg.find()?pkg.group(1).replaceAll("\\s","")+".":"";
57
+ return new Source(prefix+type.group(1),code,0,code.split("\n",-1).length,false);
58
+ }
59
+ StringBuilder imports=new StringBuilder("import ghidra.app.script.GhidraScript;\n");char[] body=code.toCharArray();int lines=1;
60
+ Matcher im=Pattern.compile("(?m)^\\h*import\\s+(?:static\\s+)?[\\w.$*]+\\s*;").matcher(lex);
61
+ while(im.find()){
62
+ imports.append(code,im.start(),im.end()).append('\n');lines++;
63
+ for(int i=im.start();i<im.end();i++)if(body[i]!='\n'&&body[i]!='\r')body[i]=' ';
64
+ }
65
+ String name="InsightUser_"+attempt.replace("-","");
66
+ return new Source(name,imports+"public class "+name+" extends GhidraScript {\npublic void run() throws Exception {\n"+new String(body)+"\n}\n}\n",lines+2,code.split("\n",-1).length,true);
67
+ }
68
+ static String sha(String s)throws Exception{return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)));}
69
+ static Map<String,Object> execution(String stage,boolean mayRun,Object invoked){
70
+ Map<String,Object> m=new LinkedHashMap<>();m.put("stage",stage);m.put("userCode",mayRun?"may_have_started":"not_started");m.put("entrypointInvoked",invoked);return m;
71
+ }
72
+ static List<Map<String,Object>> diagnostics(List<Diagnostic<? extends JavaFileObject>> all,Source source){
73
+ List<Map<String,Object>> out=new ArrayList<>();
74
+ for(var d:all){
75
+ if(out.size()==MAX_DIAGNOSTICS)break;
76
+ Map<String,Object> v=new LinkedHashMap<>();v.put("severity",d.getKind().name().toLowerCase(Locale.ROOT));v.put("code",d.getCode());
77
+ String message=d.getMessage(Locale.ROOT);v.put("message",message.substring(0,Math.min(message.length(),512)));v.put("messageTruncated",message.length()>512);
78
+ v.put("line",d.getLineNumber()>0?d.getLineNumber():null);v.put("column",d.getColumnNumber()>0?d.getColumnNumber():null);
79
+ long userLine=d.getLineNumber()-source.prefixLines();v.put("sourceLine",userLine>0&&userLine<=source.userLines()?userLine:null);v.put("generatedWrapper",source.wrapped());out.add(v);
80
+ }
81
+ return out;
82
+ }
83
+ static void failure(Map<String,Object> out,String stage,Throwable error,String fallback){
84
+ out.put("success",false);
85
+ Throwable cause=error;
86
+ if(cause instanceof InvocationTargetException e&&e.getTargetException()!=null)cause=e.getTargetException();
87
+ String message=cause==null?fallback:cause.getClass().getSimpleName()+": "+Objects.toString(cause.getMessage(),"");
88
+ out.put("diagnostic",Map.of("kind",stage.equals("compile")?"java-compile":stage.equals("load")?"java-load":"java-"+stage,"message",message.substring(0,Math.min(message.length(),2048)),"messageTruncated",message.length()>2048));
89
+ }
90
+ public static void execute(Path requestFile,GhidraState state,TaskMonitor monitor)throws Exception {
91
+ JsonObject p=JsonParser.parseString(Files.readString(requestFile)).getAsJsonObject();
92
+ Path root=requestFile.toAbsolutePath().getParent();String code=p.get("code").getAsString(),attempt=p.get("attemptId").getAsString();
93
+ if(!sha(code).equals(p.get("sourceSha256").getAsString())||!attempt.matches("[a-f0-9-]{36}"))throw new IllegalArgumentException("Inline attempt binding differs");
94
+ Source source=source(code,attempt);BoundedOutput build=new BoundedOutput(),console=new BoundedOutput();
95
+ Map<String,Object> out=new LinkedHashMap<>();out.put("protocol","insight-inline-v1");out.put("attemptId",attempt);out.put("requestId",p.get("requestId").getAsString());out.put("sourceSha256",sha(code));out.put("success",false);
96
+ String stage="prepare";boolean mayRun=false;Object invoked=false;
97
+ PrintStream originalOut=System.out,originalErr=System.err;
98
+ PrintWriter scriptWriter=new PrintWriter(new OutputStreamWriter(console,StandardCharsets.UTF_8),true);
99
+ try {
100
+ JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();if(compiler==null)throw new IllegalStateException("JDK Java compiler is unavailable");
101
+ Path file=root.resolve("source").resolve(source.name().replace('.','/')+".java"),classes=root.resolve("classes");
102
+ Files.createDirectories(file.getParent());Files.createDirectory(classes);Files.writeString(file,source.text(),StandardOpenOption.CREATE_NEW);
103
+ stage="compile";DiagnosticCollector<JavaFileObject> ds=new DiagnosticCollector<>();
104
+ try(StandardJavaFileManager manager=compiler.getStandardFileManager(ds,Locale.ROOT,StandardCharsets.UTF_8)){
105
+ // Compile only this immutable attempt. No source discovery or annotation/plugin execution.
106
+ List<String> options=List.of("-proc:none","-encoding","UTF-8","-classpath",System.getProperty("java.class.path"),"-sourcepath",root.resolve("source").toString(),"-d",classes.toString());
107
+ boolean ok=Boolean.TRUE.equals(compiler.getTask(new OutputStreamWriter(build,StandardCharsets.UTF_8),manager,ds,options,null,manager.getJavaFileObjects(file)).call());
108
+ out.put("diagnostics",diagnostics(ds.getDiagnostics(),source));out.put("diagnosticsTruncated",ds.getDiagnostics().size()>MAX_DIAGNOSTICS);
109
+ if(!ok){
110
+ PrintWriter messages=new PrintWriter(new OutputStreamWriter(build,StandardCharsets.UTF_8));
111
+ for(var d:ds.getDiagnostics())messages.println("Inline.java:"+d.getLineNumber()+":"+d.getColumnNumber()+": "+d.getKind()+": "+d.getMessage(Locale.ROOT));messages.flush();
112
+ failure(out,stage,null,ds.getDiagnostics().stream().filter(d->d.getKind()==Diagnostic.Kind.ERROR).findFirst().map(d->d.getMessage(Locale.ROOT)).orElse("Java compilation failed"));return;
113
+ }
114
+ }
115
+ stage="load";
116
+ try(URLClassLoader loader=new URLClassLoader(new URL[]{classes.toUri().toURL()},InsightInlineExecutor.class.getClassLoader())){
117
+ // Resolution and reflection are performed without class initialization.
118
+ Class<?> type=Class.forName(source.name(),false,loader);
119
+ if(type.getClassLoader()!=loader)throw new IllegalArgumentException("Source class collides with a parent class");
120
+ if(!GhidraScript.class.isAssignableFrom(type)||Modifier.isAbstract(type.getModifiers())||!Modifier.isPublic(type.getModifiers()))throw new IllegalArgumentException("Inline class must be a public concrete GhidraScript");
121
+ Constructor<?> constructor=type.getDeclaredConstructor();if(!Modifier.isPublic(constructor.getModifiers()))throw new IllegalAccessException("Inline class needs a public default constructor");
122
+ // Set the conservative barrier BEFORE initialization or any caller-controlled method.
123
+ System.setOut(new PrintStream(console,true,StandardCharsets.UTF_8));System.setErr(System.out);
124
+ stage="initialize";mayRun=true;Class.forName(source.name(),true,loader);
125
+ stage="construct";
126
+ GhidraScript script=(GhidraScript)constructor.newInstance();
127
+ script.setSourceFile(new ResourceFile(file.toFile()));
128
+ String args=p.get("args").getAsString().trim();script.setScriptArgs(args.isEmpty()?new String[0]:args.split("\\s+"));
129
+ stage="execute";invoked=null; // Framework setup may fail before run(); never assert run() entry from an exception.
130
+ script.execute(state,monitor,scriptWriter);
131
+ out.put("success",true);
132
+ }
133
+ }catch(Throwable error){failure(out,stage,error,"Inline attempt failed");}
134
+ finally {
135
+ scriptWriter.flush();
136
+ System.setOut(originalOut);System.setErr(originalErr);
137
+ out.put("execution",execution(stage,mayRun,invoked));out.put("compilerOutput",build.value());out.put("console_output",console.text());out.put("outputTruncated",console.truncated);
138
+ Path receipt=root.resolve("receipt.json"),temporary=root.resolve("receipt.new");Files.writeString(temporary,JSON.toJson(out),StandardOpenOption.CREATE_NEW);
139
+ try(FileChannel channel=FileChannel.open(temporary,StandardOpenOption.WRITE)){channel.force(true);}
140
+ Files.move(temporary,receipt,StandardCopyOption.ATOMIC_MOVE);
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,14 @@
1
+ import ghidra.app.script.GhidraScript;
2
+ import insight.InsightInlineExecutor;
3
+ import java.nio.file.Path;
4
+ import java.nio.charset.StandardCharsets;
5
+ import java.util.Base64;
6
+
7
+ /** Only fixed trusted code is passed to the upstream script provider. User source is an input file. */
8
+ public class InsightInlineScript extends GhidraScript {
9
+ public void run() throws Exception {
10
+ Path request=Path.of(new String(Base64.getDecoder().decode(getScriptArgs()[0]),StandardCharsets.UTF_8));
11
+ InsightInlineExecutor.execute(request,getState(),monitor);
12
+ println("INSIGHT_INLINE_COMPLETE");
13
+ }
14
+ }
@@ -0,0 +1,92 @@
1
+ package com.eyeauras.insight;
2
+
3
+ import java.math.BigInteger;
4
+ import java.util.*;
5
+ import java.util.function.LongSupplier;
6
+
7
+ /** Current mapped PE32+/AMD64 runtime regions, never semantic function reconstruction. */
8
+ public final class InsightPeRanges {
9
+ public record Span(BigInteger begin,BigInteger endExclusive,boolean execute) {}
10
+ public interface Bytes { int read(BigInteger at,byte[] out)throws Exception; }
11
+ public record Range(BigInteger begin,BigInteger endExclusive,BigInteger recordAddress) {
12
+ public Map<String,Object> facts(){return Map.of("source","pe-exception-directory","begin",begin.toString(16),"endExclusive",endExclusive.toString(16),"tableRecordAddress",recordAddress.toString(16),"semanticMembership","not-established","instructionBoundary","not-checked","unwindInfo","not-parsed");}
13
+ }
14
+ public record Result(Map<String,Object> lookup,List<Range> ranges) {}
15
+ private static final BigInteger U64=BigInteger.ONE.shiftLeft(64);
16
+ private final List<BigInteger> addresses;
17
+ private final BigInteger base;private final List<Span> spans;private final Bytes memory;private final LongSupplier clock;private final int maximum;private final long start;
18
+ private int bytesRead=0,recordsValidated=0;private long recordCount=0;
19
+ private static final class Stop extends Exception {final String state,reason;Stop(String s,String r){state=s;reason=r;}}
20
+ public InsightPeRanges(BigInteger base,List<Span> spans,Bytes memory,int maximum,LongSupplier clock,List<BigInteger> addresses){
21
+ this(base,spans,memory,maximum,clock,addresses,clock.getAsLong());
22
+ }
23
+ public InsightPeRanges(BigInteger base,List<Span> spans,Bytes memory,int maximum,LongSupplier clock,List<BigInteger> addresses,long started){
24
+ if(base.signum()<0||base.compareTo(U64)>=0||maximum<1||maximum>16777216)throw new IllegalArgumentException("Invalid PE lookup bounds");
25
+ if(addresses.size()>20)throw new IllegalArgumentException("At most 20 addresses");this.addresses=List.copyOf(addresses);
26
+ start=started;this.base=base;this.spans=spans.stream().sorted(Comparator.comparing(Span::begin)).toList();this.memory=memory;this.maximum=maximum;this.clock=clock;
27
+ }
28
+ private void tick()throws Stop {if(clock.getAsLong()-start>=5_000_000_000L)throw new Stop("incomplete","timeout");}
29
+ private boolean mapped(BigInteger at,long length,boolean execute)throws Stop {
30
+ BigInteger end=at.add(BigInteger.valueOf(length));if(length<1||at.signum()<0||end.compareTo(U64)>0)return false;
31
+ BigInteger cursor=at;
32
+ for(Span s:spans){tick();if(s.endExclusive.compareTo(cursor)<=0)continue;if(s.begin.compareTo(cursor)>0)return false;if(execute&&!s.execute)return false;cursor=s.endExclusive.min(end);if(cursor.equals(end))return true;}
33
+ return false;
34
+ }
35
+ private byte[] read(BigInteger at,int length,String missing)throws Exception {
36
+ tick();if(length>maximum-bytesRead)throw new Stop("incomplete","byte_limit");
37
+ if(!mapped(at,length,false))throw new Stop("unavailable",missing);
38
+ byte[] out=new byte[length];int got;
39
+ try{got=memory.read(at,out);}catch(Exception e){throw new Stop("unavailable","memory_read_failed");}
40
+ // A failed/short read never establishes absence. Charge every reported physical byte.
41
+ if(got<0||got>length)throw new IllegalStateException("Invalid native byte count");bytesRead+=got;
42
+ if(got!=length)throw new Stop("unavailable","short_read");tick();return out;
43
+ }
44
+ private static int u16(byte[] a,int i){return (a[i]&255)|((a[i+1]&255)<<8);}
45
+ private static long u32(byte[] a,int i){return Integer.toUnsignedLong((a[i]&255)|((a[i+1]&255)<<8)|((a[i+2]&255)<<16)|((a[i+3]&255)<<24));}
46
+ private void valid(boolean condition,String reason)throws Stop {if(!condition)throw new Stop("malformed",reason);}
47
+ private BigInteger rva(long offset,long size)throws Stop {valid(offset>0&&size>0&&offset+size<=0x1_0000_0000L,"invalid_rva");BigInteger at=base.add(BigInteger.valueOf(offset));valid(at.add(BigInteger.valueOf(size)).compareTo(U64)<=0,"address_overflow");return at;}
48
+ public Result run()throws Exception {
49
+ List<Range> ranges=new ArrayList<>();String state="validated",reason="validated";
50
+ try{
51
+ byte[] dos=read(base,64,"headers_not_mapped");valid(u16(dos,0)==0x5a4d,"invalid_dos_signature");long pe=u32(dos,60);valid(pe>=64,"invalid_pe_offset");
52
+ byte[] header=read(base.add(BigInteger.valueOf(pe)),24,"headers_not_mapped");valid(u32(header,0)==0x4550,"invalid_pe_signature");
53
+ if(u16(header,4)!=0x8664)throw new Stop("unavailable","unsupported_machine");
54
+ int optionalSize=u16(header,20);valid(optionalSize>=112,"short_optional_header");
55
+ byte[] optional=read(base.add(BigInteger.valueOf(pe+24)),112,"headers_not_mapped");
56
+ if(u16(optional,0)!=0x20b)throw new Stop("unavailable","unsupported_optional_header");
57
+ long imageSize=u32(optional,56),headerSize=u32(optional,60),directories=u32(optional,108);
58
+ valid(imageSize>0&&headerSize>=pe+24+optionalSize&&headerSize<=imageSize,"invalid_image_headers");
59
+ valid(directories<=(optionalSize-112)/8,"short_optional_header");
60
+ valid(base.add(BigInteger.valueOf(imageSize)).compareTo(U64)<=0,"address_overflow");
61
+ if(!mapped(base,pe+24+optionalSize,false))throw new Stop("unavailable","headers_not_mapped");
62
+ if(directories<4){state="absent";reason="no_exception_directory";}
63
+ else{
64
+ byte[] directory=read(base.add(BigInteger.valueOf(pe+24+136)),8,"headers_not_mapped");
65
+ long table=u32(directory,0),size=u32(directory,4);
66
+ if(table==0&&size==0){state="absent";reason="no_exception_directory";}
67
+ else{
68
+ valid(table>0&&size>0&&size%12==0&&table%4==0&&table+size<=imageSize,"invalid_exception_directory");
69
+ recordCount=size/12;BigInteger tableAt=rva(table,size);long previousEnd=0;
70
+ for(long offset=0;offset<size;){
71
+ tick();int length=(int)Math.min(4092,size-offset);byte[] chunk=read(tableAt.add(BigInteger.valueOf(offset)),length,"table_not_mapped");
72
+ for(int i=0;i<length;i+=12){
73
+ tick();long begin=u32(chunk,i),end=u32(chunk,i+4),unwind=u32(chunk,i+8);
74
+ valid(begin>0&&begin<end&&end<=imageSize,"invalid_runtime_range");
75
+ valid(begin>=previousEnd,"unordered_or_overlapping_ranges");
76
+ valid(unwind>0&&unwind<imageSize&&unwind%4==0,"invalid_unwind_rva");
77
+ BigInteger lo=rva(begin,end-begin);
78
+ if(!mapped(lo,end-begin,true))throw new Stop("unavailable","runtime_range_not_initialized_executable");
79
+ if(!mapped(rva(unwind,1),1,false))throw new Stop("unavailable","unwind_not_mapped");
80
+ BigInteger hi=base.add(BigInteger.valueOf(end));
81
+ if(addresses.stream().anyMatch(at->at.compareTo(lo)>=0&&at.compareTo(hi)<0))ranges.add(new Range(lo,hi,tableAt.add(BigInteger.valueOf(offset+i))));previousEnd=end;recordsValidated++;
82
+ }
83
+ offset+=length;
84
+ }
85
+ }
86
+ }
87
+ tick();
88
+ }catch(Stop e){state=e.state;reason=e.reason;ranges.clear();}
89
+ boolean complete=state.equals("validated")||state.equals("absent");
90
+ return new Result(Map.of("state",state,"complete",complete,"reason",reason,"headersSource","current-mapped-memory","recordCount",recordCount,"recordsValidated",recordsValidated,"bytesRead",bytesRead,"maximumBytes",maximum,"deadlineMilliseconds",5000),List.copyOf(ranges));
91
+ }
92
+ }
@@ -111,6 +111,27 @@ public class InsightResearchQueries extends GhidraScript {
111
111
  candidates.add(map("address",currentProgram.getImageBase().toString(),"name","image_base","kind","image_base_candidate","confirmed",false,"provenance","program.image-base"));
112
112
  return map("entry_points",confirmed,"count",confirmed.size(),"candidates",candidates,"scan",scan(),"truncated",stopped,"semantics",map("kind","metadata","limitation","External-entry designation is Ghidra metadata, not proof of OS startup behavior; candidates are not confirmed entries."));
113
113
  }
114
+ Object navigation()throws Exception{
115
+ final int cap=128;int functions=0,oneByte=0,entryInstructions=0,instructions=0,references=0,data=0,strings=0;long bodyBytes=0;
116
+ var fs=currentProgram.getFunctionManager().getFunctions(true);
117
+ while(fs.hasNext()&&functions<cap){if(!visit())break;var f=fs.next();functions++;long size=f.getBody().getNumAddresses();bodyBytes+=size;if(size==1)oneByte++;if(currentProgram.getListing().getInstructionAt(f.getEntryPoint())!=null)entryInstructions++;}
118
+ boolean functionsComplete=!fs.hasNext();
119
+ var is=currentProgram.getListing().getInstructions(true);
120
+ while(is.hasNext()&&instructions<cap){if(!visit())break;is.next();instructions++;}boolean instructionsComplete=!is.hasNext();
121
+ var rs=currentProgram.getReferenceManager().getReferenceIterator(currentProgram.getMinAddress());
122
+ while(rs.hasNext()&&references<cap){if(!visit())break;rs.next();references++;}boolean referencesComplete=!rs.hasNext();
123
+ var ds=currentProgram.getListing().getDefinedData(true);
124
+ while(ds.hasNext()&&data<cap){if(!visit())break;var d=ds.next();data++;if(d.hasStringValue())strings++;}boolean dataComplete=!ds.hasNext();
125
+ boolean complete=functionsComplete&&instructionsComplete&&referencesComplete&&dataComplete;
126
+ String assessment=functions>0&&oneByte*5>=functions*4&&(functions-entryInstructions)*5>=functions*4?"sparse-metadata":functions==0&&instructions==0?"no-code-observed":"observed-metadata";
127
+ return map("functions",map("sampled",functions,"oneByteBodies",oneByte,"bodyBytes",bodyBytes,"entriesWithInstructions",entryInstructions,"complete",functionsComplete),
128
+ "instructions",map("sampled",instructions,"complete",instructionsComplete),"references",map("sampled",references,"complete",referencesComplete),
129
+ "definedData",map("sampled",data,"strings",strings,"complete",dataComplete),
130
+ "scan",map("visited",visited,"maximum",maxScan,"sampleLimitPerCategory",cap,"complete",complete,"reason",complete?"exhausted":stopped?"budget":"sample_limit"),
131
+ "assessment",assessment,"assessmentAlgorithm","one-byte-and-missing-entry-instruction-ratio-at-least-0.8-v1",
132
+ "recommendations",assessment.equals("sparse-metadata")||assessment.equals("no-code-observed")?List.of("Treat missing stored xrefs, strings and function containment as unknown. For known function entries try functions inspect --fields decompiled_code,callees; refresh stale/failed output with functions force-decompile --function <entry>.","For executable bytes without a function entry use functions decode. These reads do not repair Listing instructions or stored xrefs.","Find undefined literals with analysis search-byte-patterns and its next_address continuation; strings search visits defined data only.","Consider an explicit bounded analysis/disassembly Update only when needed; this diagnostic does not run analyzers."):List.of("Use observed metadata for navigation; verify individual bodies and references for the question under study."),
133
+ "coverage","At most 128 inputs per category, in address order, sharing max-scan and a five-second deadline. References are stored DB references; strings count only sampled defined data. Observations and heuristics do not prove analysis or semantic completeness.");
134
+ }
114
135
  Object strings(boolean search)throws Exception{
115
136
  List<Object> rows=new PagedRows();String needle=str(search?"search_term":"filter","");Pattern regex=search?safeRegex(needle):null;String encoding=str("encoding","");int min=number("min_length",search?4:0,0,65536);var iter=currentProgram.getListing().getDefinedData(true);
116
137
  while(iter.hasNext()){if(!visit())break;var d=iter.next();if(!d.hasStringValue())continue;if(d.getLength()>65536){stopped=true;break;}String value=Objects.toString(d.getValue(),"");if(value.length()>8192){stopped=true;break;}String enc=d.getDataType().getName().toLowerCase(Locale.ROOT).contains("unicode")?"unicode":"ascii";
@@ -203,7 +224,7 @@ public class InsightResearchQueries extends GhidraScript {
203
224
  if(consumed==input.length&&available<maximum&&reason.equals("byte_limit"))reason="executable_block_end";
204
225
  return map("address",start.toString(),"language",language,"compiler",currentProgram.getCompilerSpec().getCompilerSpecID().toString(),"input_bytes",HexFormat.of().formatHex(input),"input_sha256",HexFormat.of().formatHex(java.security.MessageDigest.getInstance("SHA-256").digest(input)),"maximum_bytes",maximum,"maximum_instructions",maxInstructions,"instructions",rows,"consumed_bytes",consumed,"stop_reason",reason,"truncated",List.of("byte_limit","instruction_limit","truncated_instruction","executable_block_end").contains(reason),"semantics","Linear pseudo instructions in the selected program context; direct targets are computed from bytes, not function membership or stored xrefs.");
205
226
  }
206
- Object execute(String operation)throws Exception{return switch(operation){case "/research_view"->map("modification",Long.toString(currentProgram.getModificationNumber()),"language",currentProgram.getLanguageID().toString(),"compiler",currentProgram.getCompilerSpec().getCompilerSpecID().toString());case "/decode_instructions"->decodeInstructions();case "/find_dead_code"->dead();case "/detect_crypto_constants"->bytes(true);case "/search_byte_patterns"->bytes(false);case "/get_valid_data_types"->types(true);case "/find_data_types"->types(false);case "/validate_data_type"->validateType();case "/validate_function_prototype"->validatePrototype();case "/get_entry_points"->entries();case "/list_globals"->globals();case "/list_strings"->strings(false);case "/search_strings"->strings(true);case "/search_instructions"->instructions();default->throw new IllegalArgumentException("Unknown native query");};}
227
+ Object execute(String operation)throws Exception{return switch(operation){case "/navigation_diagnostic"->navigation();case "/research_view"->map("modification",Long.toString(currentProgram.getModificationNumber()),"language",currentProgram.getLanguageID().toString(),"compiler",currentProgram.getCompilerSpec().getCompilerSpecID().toString());case "/decode_instructions"->decodeInstructions();case "/find_dead_code"->dead();case "/detect_crypto_constants"->bytes(true);case "/search_byte_patterns"->bytes(false);case "/get_valid_data_types"->types(true);case "/find_data_types"->types(false);case "/validate_data_type"->validateType();case "/validate_function_prototype"->validatePrototype();case "/get_entry_points"->entries();case "/list_globals"->globals();case "/list_strings"->strings(false);case "/search_strings"->strings(true);case "/search_instructions"->instructions();default->throw new IllegalArgumentException("Unknown native query");};}
207
228
  public void run()throws Exception{
208
229
  var request=JsonParser.parseString(new String(Base64.getDecoder().decode(getScriptArgs()[0]),StandardCharsets.UTF_8)).getAsJsonObject();p=request.getAsJsonObject("parameters");maxScan=number("max_scan",100000,1,200000);limit=number("limit",100,1,1000);offset=number("offset",0,0,2000000);deadline=System.nanoTime()+number("timeout_seconds",5,1,30)*1000000000L;
209
230
  boundedMonitor=TimeoutTaskMonitor.timeoutIn(number("timeout_seconds",5,1,30),TimeUnit.SECONDS,monitor);
@@ -0,0 +1,69 @@
1
+ package com.eyeauras.insight;
2
+
3
+ import com.xebyte.core.*;
4
+ import ghidra.program.model.listing.Program;
5
+ import ghidra.program.model.listing.Function;
6
+ import ghidra.program.model.address.Address;
7
+ import java.math.BigInteger;
8
+ import java.util.LinkedHashMap;
9
+ import java.util.Map;
10
+
11
+ /** Precompiled, authenticated bridge read. Does not open, analyze, transact or save programs. */
12
+ public final class InsightResearchView {
13
+ private final ProgramProvider programs;
14
+ private final ThreadingStrategy threading;
15
+ public InsightResearchView(ProgramProvider programs,ThreadingStrategy threading){this.programs=programs;this.threading=threading;}
16
+ private Program openProgram(String path){
17
+ if(programs==null||path==null||!path.startsWith("/")||path.length()>4096)throw new IllegalArgumentException("An explicit open program path is required");
18
+ Program selected=null;
19
+ for(Program p:programs.getAllOpenPrograms())if(p!=null&&!p.isClosed()&&p.getDomainFile()!=null&&path.equals(p.getDomainFile().getPathname())){
20
+ if(selected!=null&&selected!=p)throw new IllegalStateException("Program path is ambiguous");selected=p;
21
+ }
22
+ if(selected==null)throw new IllegalArgumentException("Program is not open");return selected;
23
+ }
24
+ @McpTool(path="/insight/research-view/v1",method="GET",description="Read modification/language/compiler of an already open program",access=ToolAccess.READ_ONLY)
25
+ public Response view(@Param(value="program",source=ParamSource.QUERY,allowEmpty=false) String path)throws Exception {
26
+ return threading.executeRead(()->{
27
+ Program selected=openProgram(path);
28
+ return Response.ok(Map.of("protocol","insight-research-view-v1","modification",Long.toString(selected.getModificationNumber()),"language",selected.getLanguageID().toString(),"compiler",selected.getCompilerSpec().getCompilerSpecID().toString()));
29
+ });
30
+ }
31
+ private static Map<String,Object> miss(String reason){return Map.of("status","not_found","reason",reason);}
32
+ private static Map<String,Object> resolved(Function function,Address at,String kind){
33
+ Map<String,Object> row=new LinkedHashMap<>();row.put("status","resolved");row.put("entryPoint",Long.toUnsignedString(function.getEntryPoint().getOffset(),16));
34
+ row.put("kind",kind);row.put("recordedBodyContainsAddress",function.getBody().contains(at));
35
+ if(!kind.equals("recorded-containing"))row.put("distanceBytes",at.subtract(function.getEntryPoint()));return row;
36
+ }
37
+ @McpTool(path="/insight/function-entries/v1",method="GET",description="Resolve up to 20 addresses using recorded bodies or a bounded preceding-entry candidate; no inferred membership",access=ToolAccess.READ_ONLY)
38
+ public Response entries(@Param(value="program",source=ParamSource.QUERY,allowEmpty=false) String path,
39
+ @Param(value="addresses",source=ParamSource.QUERY,allowEmpty=false) String addresses,
40
+ @Param(value="max_entry_distance",source=ParamSource.QUERY,allowEmpty=false) int maximum)throws Exception {
41
+ if(maximum<1||maximum>65536||addresses==null||addresses.length()>339)throw new IllegalArgumentException("Expected 1–20 addresses and a distance of 1–65536 bytes");
42
+ String[] words=addresses.split(",",-1);if(words.length<1||words.length>20)throw new IllegalArgumentException("Expected 1–20 addresses");
43
+ for(String word:words)if(!word.matches("[a-f0-9]{1,16}"))throw new IllegalArgumentException("Expected canonical default-space hexadecimal addresses");
44
+ return threading.executeRead(()->{
45
+ Program program=openProgram(path);long modification=program.getModificationNumber();var functions=program.getFunctionManager();
46
+ var space=program.getAddressFactory().getDefaultAddressSpace();var memory=program.getMemory();Map<String,Object> items=new LinkedHashMap<>();
47
+ for(String word:words){
48
+ if(items.containsKey(word)||!new BigInteger(word,16).toString(16).equals(word))throw new IllegalArgumentException("Expected distinct canonical addresses");
49
+ if(new BigInteger(word,16).bitLength()>space.getSize()){items.put(word,miss("address_out_of_range"));continue;}
50
+ Address at=space.getAddress(Long.parseUnsignedLong(word,16));
51
+ Function function=functions.getFunctionAt(at);
52
+ if(function!=null){items.put(word,resolved(function,at,"exact-entry"));continue;}
53
+ function=functions.getFunctionContaining(at);
54
+ if(function!=null){items.put(word,resolved(function,at,"recorded-containing"));continue;}
55
+ var block=memory.getBlock(at);
56
+ if(block==null||!block.isInitialized()||!block.isExecute()){items.put(word,miss("address_not_in_initialized_executable_block"));continue;}
57
+ // Address-seeded indexed iterator; consume at most one previous entry, never a full-table scan.
58
+ var previous=functions.getFunctions(at,false);
59
+ if(!previous.hasNext()){items.put(word,miss("no_preceding_entry"));continue;}
60
+ function=previous.next();Address entry=function.getEntryPoint();
61
+ if(!entry.getAddressSpace().equals(space)||entry.compareTo(at)>=0||!block.contains(entry)){items.put(word,miss("entry_outside_block"));continue;}
62
+ if(new BigInteger(word,16).subtract(new BigInteger(Long.toUnsignedString(entry.getOffset(),16),16)).compareTo(BigInteger.valueOf(maximum))>0){items.put(word,miss("entry_distance_exceeded"));continue;}
63
+ items.put(word,resolved(function,at,"preceding-entry-candidate"));
64
+ }
65
+ if(modification!=program.getModificationNumber())throw new IllegalStateException("Program changed during function-entry lookup");
66
+ return Response.ok(Map.of("protocol","insight-function-entries-v1","modification",Long.toString(modification),"items",items));
67
+ });
68
+ }
69
+ }