@eyeauras/insight-cli 0.0.0-stage → 0.2.0
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.
- package/.cli-package-build.json +1 -0
- package/LICENSE +21 -0
- package/README.md +60 -2
- package/SKILL.md +58 -0
- package/dist/src/administration-results.d.ts +1 -0
- package/dist/src/administration-results.js +84 -0
- package/dist/src/administration.d.ts +14 -0
- package/dist/src/administration.js +134 -0
- package/dist/src/archive-contract.d.ts +6 -0
- package/dist/src/archive-contract.js +108 -0
- package/dist/src/archive.d.ts +12 -0
- package/dist/src/archive.js +116 -0
- package/dist/src/arguments.d.ts +14 -0
- package/dist/src/arguments.js +62 -0
- package/dist/src/artifacts.d.ts +17 -0
- package/dist/src/artifacts.js +115 -0
- package/dist/src/bin.d.ts +2 -0
- package/dist/src/bin.js +4 -0
- package/dist/src/cli.d.ts +10 -0
- package/dist/src/cli.js +419 -0
- package/dist/src/comment-effects.d.ts +3 -0
- package/dist/src/comment-effects.js +55 -0
- package/dist/src/contexts.d.ts +115 -0
- package/dist/src/contexts.js +1078 -0
- package/dist/src/coordinator.d.ts +41 -0
- package/dist/src/coordinator.js +160 -0
- package/dist/src/cross-results.d.ts +14 -0
- package/dist/src/cross-results.js +71 -0
- package/dist/src/cross.d.ts +3 -0
- package/dist/src/cross.js +43 -0
- package/dist/src/data-effects.d.ts +3 -0
- package/dist/src/data-effects.js +103 -0
- package/dist/src/debugger-results.d.ts +4 -0
- package/dist/src/debugger-results.js +151 -0
- package/dist/src/debugger-supervisor.d.ts +1 -0
- package/dist/src/debugger-supervisor.js +136 -0
- package/dist/src/debugger.d.ts +21 -0
- package/dist/src/debugger.js +267 -0
- package/dist/src/documentation-effects.d.ts +5 -0
- package/dist/src/documentation-effects.js +56 -0
- package/dist/src/extensions.d.ts +64 -0
- package/dist/src/extensions.js +289 -0
- package/dist/src/function-effects.d.ts +4 -0
- package/dist/src/function-effects.js +112 -0
- package/dist/src/ghidra.d.ts +50 -0
- package/dist/src/ghidra.js +468 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/initial-model.d.ts +6 -0
- package/dist/src/initial-model.js +85 -0
- package/dist/src/json.d.ts +2 -0
- package/dist/src/json.js +8 -0
- package/dist/src/lifecycle.d.ts +49 -0
- package/dist/src/lifecycle.js +154 -0
- package/dist/src/memory-inputs.d.ts +8 -0
- package/dist/src/memory-inputs.js +56 -0
- package/dist/src/merge.d.ts +29 -0
- package/dist/src/merge.js +66 -0
- package/dist/src/model.d.ts +74 -0
- package/dist/src/model.js +67 -0
- package/dist/src/mutation-effects.d.ts +4 -0
- package/dist/src/mutation-effects.js +321 -0
- package/dist/src/mutation-inputs.d.ts +2 -0
- package/dist/src/mutation-inputs.js +71 -0
- package/dist/src/mutations.d.ts +4 -0
- package/dist/src/mutations.js +53 -0
- package/dist/src/native-inputs.d.ts +28 -0
- package/dist/src/native-inputs.js +182 -0
- package/dist/src/native-queries.d.ts +2 -0
- package/dist/src/native-queries.js +29 -0
- package/dist/src/projects.d.ts +31 -0
- package/dist/src/projects.js +301 -0
- package/dist/src/prototype-cli.d.ts +8 -0
- package/dist/src/prototype-cli.js +155 -0
- package/dist/src/queries.d.ts +5 -0
- package/dist/src/queries.js +84 -0
- package/dist/src/query-policy.d.ts +6 -0
- package/dist/src/query-policy.js +72 -0
- package/dist/src/query-results.d.ts +2 -0
- package/dist/src/query-results.js +224 -0
- package/dist/src/research.d.ts +184 -0
- package/dist/src/research.js +808 -0
- package/dist/src/runtime-archives.d.ts +5 -0
- package/dist/src/runtime-archives.js +127 -0
- package/dist/src/runtime-catalog.d.ts +18 -0
- package/dist/src/runtime-catalog.js +27 -0
- package/dist/src/runtime-manager.d.ts +101 -0
- package/dist/src/runtime-manager.js +444 -0
- package/dist/src/runtime-selection.d.ts +13 -0
- package/dist/src/runtime-selection.js +25 -0
- package/dist/src/saved-materials.d.ts +12 -0
- package/dist/src/saved-materials.js +196 -0
- package/dist/src/scripts.d.ts +19 -0
- package/dist/src/scripts.js +79 -0
- package/dist/src/sdk-contracts.d.ts +13 -0
- package/dist/src/sdk-contracts.js +37 -0
- package/dist/src/sdk-files.d.ts +29 -0
- package/dist/src/sdk-files.js +249 -0
- package/dist/src/sdk.d.ts +4 -0
- package/dist/src/sdk.js +125 -0
- package/dist/src/server.d.ts +46 -0
- package/dist/src/server.js +460 -0
- package/dist/src/store.d.ts +33 -0
- package/dist/src/store.js +178 -0
- package/dist/src/sync-policy.d.ts +22 -0
- package/dist/src/sync-policy.js +123 -0
- package/dist/src/transport.d.ts +23 -0
- package/dist/src/transport.js +135 -0
- package/dist/src/ui-results.d.ts +2 -0
- package/dist/src/ui-results.js +80 -0
- package/dist/src/ui.d.ts +17 -0
- package/dist/src/ui.js +88 -0
- package/dist/src/utility.d.ts +14 -0
- package/dist/src/utility.js +43 -0
- package/dist/src/version-tracking.d.ts +57 -0
- package/dist/src/version-tracking.js +200 -0
- package/dist/src/workspace.d.ts +199 -0
- package/dist/src/workspace.js +564 -0
- package/package.json +47 -4
- package/references/InsightRepositoryProbe.java +55 -0
- package/references/administration.md +34 -0
- package/references/archive.md +32 -0
- package/references/artifacts.md +47 -0
- package/references/debugger.md +40 -0
- package/references/extensions-and-sync.md +124 -0
- package/references/managed-runtime.md +116 -0
- package/references/mutations.json +1424 -0
- package/references/native/InsightApplyDocumentation.java +48 -0
- package/references/native/InsightArchivePayloads.java +36 -0
- package/references/native/InsightBlockTransfer.java +57 -0
- package/references/native/InsightEmulation.java +93 -0
- package/references/native/InsightExtensions.java +36 -0
- package/references/native/InsightFunctionDatabases.java +98 -0
- package/references/native/InsightGuiMain.java +32 -0
- package/references/native/InsightHeadlessMain.java +10 -0
- package/references/native/InsightInitialImport.java +42 -0
- package/references/native/InsightInitialJournal.java +48 -0
- package/references/native/InsightInitialPublication.java +142 -0
- package/references/native/InsightKeytool.java +16 -0
- package/references/native/InsightNativeReceipt.java +31 -0
- package/references/native/InsightPlugins.java +34 -0
- package/references/native/InsightProjectFiles.java +92 -0
- package/references/native/InsightPublicationRecovery.java +63 -0
- package/references/native/InsightPythonWheels.java +31 -0
- package/references/native/InsightResearchQueries.java +188 -0
- package/references/native/InsightRuntimeDoctor.java +33 -0
- package/references/native/InsightSdkFiles.java +142 -0
- package/references/native/InsightSdkScript.java +31 -0
- package/references/native/InsightSdkTools.java +338 -0
- package/references/native/InsightSdkTypes.java +132 -0
- package/references/native/InsightServerAdministration.java +82 -0
- package/references/native/InsightServerInit.java +62 -0
- package/references/native/InsightServerMain.java +43 -0
- package/references/native/InsightServerProbe.java +29 -0
- package/references/native/InsightSnapshotFields.java +68 -0
- package/references/native/InsightSnapshotInspect.java +189 -0
- package/references/native/InsightSnapshotPublisher.java +123 -0
- package/references/native/InsightStartDebugger.java +28 -0
- package/references/native/InsightStopDebugger.java +20 -0
- package/references/native/InsightTraceInspect.java +24 -0
- package/references/native/InsightTraceState.java +130 -0
- package/references/native/InsightTraceTools.java +114 -0
- package/references/native/InsightVersionTracking.java +91 -0
- package/references/native-workflows.md +112 -0
- package/references/queries.json +1761 -0
- package/references/query-semantics.md +96 -0
- package/references/recovery.md +53 -0
- package/references/research.md +101 -0
- package/references/setup.md +49 -0
- package/runtime/GhidraMCP-7.0.0.jar +0 -0
- package/runtime/GhidraMCP-LICENSE +202 -0
- package/runtime/GhidraMCP-NOTICE +10 -0
- package/runtime/provenance.json +11 -0
- package/skills/insight-re/SKILL.md +108 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import ghidra.app.script.GhidraScript;
|
|
2
|
+
import ghidra.app.util.parser.FunctionSignatureParser;
|
|
3
|
+
import ghidra.program.model.address.*;
|
|
4
|
+
import ghidra.program.model.block.*;
|
|
5
|
+
import ghidra.program.model.data.*;
|
|
6
|
+
import ghidra.program.model.listing.*;
|
|
7
|
+
import ghidra.program.model.mem.*;
|
|
8
|
+
import ghidra.program.model.symbol.*;
|
|
9
|
+
import com.google.gson.*;
|
|
10
|
+
import java.nio.charset.StandardCharsets;
|
|
11
|
+
import java.util.*;
|
|
12
|
+
import java.util.regex.Pattern;
|
|
13
|
+
import ghidra.util.task.TimeoutTaskMonitor;
|
|
14
|
+
import java.util.concurrent.TimeUnit;
|
|
15
|
+
|
|
16
|
+
/** Facade-owned read operations. Budgets cover visited inputs, not just returned rows. */
|
|
17
|
+
public class InsightResearchQueries extends GhidraScript {
|
|
18
|
+
StandAloneDataTypeManager temporaryTypes; int temporaryTransaction;
|
|
19
|
+
TimeoutTaskMonitor boundedMonitor;
|
|
20
|
+
JsonObject p; int visited=0,maxScan,limit,offset; long deadline; boolean stopped=false;
|
|
21
|
+
String str(String key,String fallback){return !p.has(key)||p.get(key).isJsonNull()?fallback:p.get(key).getAsString();}
|
|
22
|
+
int number(String key,int fallback,int min,int max){int value=p.has(key)?p.get(key).getAsInt():fallback;if(value<min||value>max)throw new IllegalArgumentException(key+" outside supported range");return value;}
|
|
23
|
+
boolean bool(String key,boolean fallback){return p.has(key)?p.get(key).getAsBoolean():fallback;}
|
|
24
|
+
boolean visit()throws Exception{monitor.checkCancelled();if(visited>=maxScan||System.nanoTime()>deadline){stopped=true;return false;}visited++;return true;}
|
|
25
|
+
Map<String,Object> map(Object... values){Map<String,Object> out=new LinkedHashMap<>();for(int i=0;i<values.length;i+=2)out.put((String)values[i],values[i+1]);return out;}
|
|
26
|
+
Map<String,Object> scan(){return map("visited",visited,"maximum",maxScan,"complete",!stopped,"reason",stopped?"budget":"exhausted","timeout_seconds",number("timeout_seconds",5,1,30));}
|
|
27
|
+
class PagedRows extends ArrayList<Object> {
|
|
28
|
+
int total=0;
|
|
29
|
+
public boolean add(Object row){int index=total++;return index>=offset&&size()<limit?super.add(row):true;}
|
|
30
|
+
}
|
|
31
|
+
Map<String,Object> page(String key,List<?> rows){if(rows instanceof PagedRows paged)return map(key,rows,"count",rows.size(),"total",paged.total,"total_known",!stopped,"offset",offset,"limit",limit,"truncated",stopped||offset+rows.size()<paged.total,"scan",scan());int from=Math.min(offset,rows.size()),to=Math.min(from+limit,rows.size());return map(key,rows.subList(from,to),"count",to-from,"total",rows.size(),"total_known",!stopped,"offset",offset,"limit",limit,"truncated",stopped||to<rows.size(),"scan",scan());}
|
|
32
|
+
Address address(String value){Address at=currentProgram.getAddressFactory().getAddress(value);if(at==null)throw new IllegalArgumentException("Invalid or absent address space");return at;}
|
|
33
|
+
Function function(String value)throws Exception{
|
|
34
|
+
Function result=null;try{result=currentProgram.getFunctionManager().getFunctionAt(address(value));}catch(IllegalArgumentException ignored){}
|
|
35
|
+
if(result!=null)return result;
|
|
36
|
+
var iter=currentProgram.getFunctionManager().getFunctions(true);
|
|
37
|
+
while(iter.hasNext()){if(!visit())throw new IllegalArgumentException("Function-name lookup exceeded budget; use an entry address");var f=iter.next();if(f.getName().equals(value)||f.getName(true).equals(value)){if(result!=null)throw new IllegalArgumentException("Ambiguous function; use address");result=f;}}
|
|
38
|
+
if(result==null)throw new IllegalArgumentException("Function not found");return result;
|
|
39
|
+
}
|
|
40
|
+
DataTypeManager initTemporary(){temporaryTypes=new StandAloneDataTypeManager("insight-read-only-types",currentProgram.getDataTypeManager().getDataOrganization());temporaryTransaction=temporaryTypes.startTransaction("Temporary parsing");return temporaryTypes;}
|
|
41
|
+
FunctionSignatureParser parser(String declaration)throws Exception{
|
|
42
|
+
if(temporaryTypes==null){temporaryTypes=new StandAloneDataTypeManager("insight-read-only-types",currentProgram.getDataTypeManager().getDataOrganization());temporaryTransaction=temporaryTypes.startTransaction("Temporary parsing");}
|
|
43
|
+
var iter=currentProgram.getDataTypeManager().getAllDataTypes();while(iter.hasNext()){
|
|
44
|
+
if(!visit())throw new IllegalArgumentException("Type parsing input budget exceeded");var t=iter.next();
|
|
45
|
+
if(declaration.contains(t.getName())){temporaryTypes.resolve(t,DataTypeConflictHandler.DEFAULT_HANDLER);if(temporaryTypes.getDataTypeCount(true)>10000)throw new IllegalArgumentException("Temporary type dependency graph exceeds 10000 types");}
|
|
46
|
+
}
|
|
47
|
+
return new FunctionSignatureParser(temporaryTypes,null);
|
|
48
|
+
}
|
|
49
|
+
Map<String,Object> type(DataType type){return map("name",type.getName(),"path",type.getPathName(),"size",type.getLength(),"alignment",type.getAlignment(),"builtin",type instanceof BuiltInDataType,"category",type.getCategoryPath().getPath(),"kind",type instanceof Structure?"struct":type instanceof Union?"union":type instanceof ghidra.program.model.data.Enum?"enum":type instanceof Pointer?"pointer":type instanceof Array?"array":type instanceof TypeDef?"typedef":type instanceof FunctionDefinition?"function":"primitive","source",type.getDataTypeManager() instanceof BuiltInDataTypeManager?"builtin":type.getDataTypeManager()==temporaryTypes?"temporary-resolved":"program","source_archive",type.getSourceArchive()==null?null:type.getSourceArchive().getName());}
|
|
50
|
+
DataType resolve(String value)throws Exception{
|
|
51
|
+
var manager=currentProgram.getDataTypeManager();DataType result=null;
|
|
52
|
+
var iter=manager.getAllDataTypes();while(iter.hasNext()){if(!visit())throw new IllegalArgumentException("Type resolution exceeded budget; narrow its category path");var t=iter.next();if(t.getPathName().equals(value))return t;if(t.getName().equals(value)){if(result!=null)throw new IllegalArgumentException("Ambiguous type; use category path");result=t;}}
|
|
53
|
+
if(result!=null)return result;
|
|
54
|
+
return parser(value).parse(null,value+" __insight_type(void)").getReturnType();
|
|
55
|
+
}
|
|
56
|
+
Object validateType()throws Exception{
|
|
57
|
+
String name=str("type_name","");List<Object> diagnostics=new ArrayList<>(),warnings=new ArrayList<>();DataType resolved=null;
|
|
58
|
+
try{resolved=resolve(name);if(resolved.getLength()<=0)warnings.add("Type has no fixed positive length");String at=str("address","");if(!at.isEmpty()&&resolved.getAlignment()>1&&Long.remainderUnsigned(address(at).getUnsignedOffset(),resolved.getAlignment())!=0)warnings.add("Address is not aligned for this type");}
|
|
59
|
+
catch(Exception error){diagnostics.add(map("severity","error","code","unresolved_type","message",Objects.toString(error.getMessage(),"Type cannot be resolved")));}
|
|
60
|
+
return map("type_name",name,"valid",resolved!=null,"resolved",resolved==null?null:type(resolved),"warnings",warnings,"diagnostics",diagnostics,"semantics",map("kind","resolution","parser","Ghidra FunctionSignatureParser","persisted_changes",false));
|
|
61
|
+
}
|
|
62
|
+
Object validatePrototype()throws Exception{
|
|
63
|
+
var f=function(str("function",""));String declaration=str("prototype","");int paren=declaration.indexOf('(');
|
|
64
|
+
var inline=Pattern.compile("\\b(__cdecl|__stdcall|__thiscall|__fastcall|__vectorcall)\\b").matcher(paren<0?declaration:declaration.substring(0,paren));String embedded="";
|
|
65
|
+
if(inline.find()){embedded=inline.group(1);declaration=(declaration.substring(0,inline.start())+declaration.substring(inline.end())).replaceAll("\\s+"," ").trim();}
|
|
66
|
+
String convention=str("calling_convention","");if(convention.isEmpty())convention=embedded;
|
|
67
|
+
List<Object> diagnostics=new ArrayList<>();Map<String,Object> signature=null;
|
|
68
|
+
try{
|
|
69
|
+
if(!convention.isEmpty()&&!convention.equals("default")&¤tProgram.getCompilerSpec().getCallingConvention(convention)==null)throw new IllegalArgumentException("Calling convention is not supported by this compiler specification");
|
|
70
|
+
var parsed=parser(declaration).parse(f.getSignature(),declaration);
|
|
71
|
+
signature=map("name",parsed.getName(),"return_type",type(parsed.getReturnType()),"parameters",Arrays.stream(parsed.getArguments()).map(a->map("name",Objects.toString(a.getName(),""),"type",type(a.getDataType()))).toList(),"calling_convention",convention.equals("default")?currentProgram.getCompilerSpec().getDefaultCallingConvention().getName():convention.isEmpty()?Objects.toString(f.getCallingConventionName(),"default"):convention);
|
|
72
|
+
}catch(Exception error){diagnostics.add(map("severity","error","code","prototype_parse","message",Objects.toString(error.getMessage(),"Prototype cannot be parsed")));}
|
|
73
|
+
return map("valid",signature!=null,"signature",signature,"diagnostics",diagnostics,"parser","Ghidra FunctionSignatureParser","persisted_changes",false);
|
|
74
|
+
}
|
|
75
|
+
Object types(boolean validInventory)throws Exception{
|
|
76
|
+
List<Object> rows=new PagedRows();Set<String> categories=new LinkedHashSet<>(),paths=new HashSet<>();
|
|
77
|
+
String pattern=str("pattern","").toLowerCase(Locale.ROOT),category=str("category","").toLowerCase(Locale.ROOT),kind=str("kind","").trim().toLowerCase(Locale.ROOT);if(validInventory&&category.equals("all"))category="";
|
|
78
|
+
if(!validInventory&&bool("categories",false)){
|
|
79
|
+
List<Object> result=new PagedRows();Deque<Category> work=new ArrayDeque<>();work.add(currentProgram.getDataTypeManager().getRootCategory());
|
|
80
|
+
while(!work.isEmpty()){if(!visit())break;var current=work.removeFirst();String path=current.getCategoryPath().getPath();if(path.toLowerCase(Locale.ROOT).contains(pattern))result.add(path);work.addAll(Arrays.asList(current.getCategories()));}
|
|
81
|
+
return page("categories",result);
|
|
82
|
+
}
|
|
83
|
+
var iter=currentProgram.getDataTypeManager().getAllDataTypes();
|
|
84
|
+
while(iter.hasNext()){if(!visit())break;var t=iter.next();var row=type(t);String path=t.getPathName();if(!path.toLowerCase(Locale.ROOT).contains(pattern)||!t.getCategoryPath().getPath().toLowerCase(Locale.ROOT).contains(category)||!kind.isEmpty()&&!kind.equals(row.get("kind")))continue;paths.add(path);rows.add(row);categories.add(t.getCategoryPath().getPath());}
|
|
85
|
+
// Common builtin names are advertised only after actual resolution in this program.
|
|
86
|
+
if(validInventory&&!stopped&&category.isEmpty())for(String name:List.of("void","byte","char","short","int","long","longlong","float","double","pointer","bool","undefined1","undefined2","undefined4","undefined8","uchar","ushort","uint","ulong","ulonglong")){
|
|
87
|
+
if(!visit())break;try{var t=new FunctionSignatureParser(temporaryTypes==null?initTemporary():temporaryTypes,null).parse(null,name+" __insight_type(void)").getReturnType();if(paths.add(t.getPathName()))rows.add(type(t));}catch(Exception ignored){}
|
|
88
|
+
}
|
|
89
|
+
if(!validInventory&&bool("categories",false))return page("categories",new ArrayList<>(categories));
|
|
90
|
+
var out=page(validInventory?"types":"data_types",rows);out.put("scope","program-and-resolvable-builtins");if(validInventory){out.put("builtin_types",rows.stream().filter(r->Boolean.TRUE.equals(((Map<?,?>)r).get("builtin"))).map(r->((Map<?,?>)r).get("name")).toList());out.put("windows_types",List.of());out.put("suggestions_are_inventory",false);}return out;
|
|
91
|
+
}
|
|
92
|
+
boolean dataAddress(Address at,boolean all){var block=currentProgram.getMemory().getBlock(at);if(block==null)return false;var listing=currentProgram.getListing();if(listing.getInstructionAt(at)!=null||currentProgram.getFunctionManager().getFunctionContaining(at)!=null)return false;return all||!block.isExecute();}
|
|
93
|
+
Map<String,Object> global(Address at,Symbol symbol){var data=currentProgram.getListing().getDefinedDataAt(at);String name=symbol==null?"DAT_"+at.toString(false):symbol.getName();boolean named=symbol!=null&&!name.matches("(?i)(DAT_|PTR_|LAB_|FUN_|UNK_|undefined).*?");boolean typed=data!=null&&!data.getDataType().getName().startsWith("undefined");
|
|
94
|
+
return map("address",at.toString(),"name",name,"type",data==null?"undefined":data.getDataType().getName(),"type_path",data==null?null:data.getDataType().getPathName(),"size",data==null?1:data.getLength(),"xrefs",currentProgram.getReferenceManager().getReferenceCountTo(at),"named",named,"typed",typed,"symbol_source",symbol==null?"none":symbol.getSource().toString(),"namespace",symbol==null?"Global":symbol.getParentNamespace().getName(true));}
|
|
95
|
+
Object globals()throws Exception{
|
|
96
|
+
List<Object> rows=new PagedRows();Set<Address> seen=new HashSet<>();boolean all=bool("include_all_sections",false);String filter=str("filter","all"),typeFilter=str("type_filter","all"),needle=str("name_substring","").toLowerCase(Locale.ROOT);int minimum=number("min_xrefs",1,0,1000000);
|
|
97
|
+
if(!List.of("all","defined","undefined").contains(filter)||!List.of("all","defined","undefined").contains(typeFilter))throw new IllegalArgumentException("Global filter must be all, defined or undefined");
|
|
98
|
+
var symbols=currentProgram.getSymbolTable().getSymbols(currentProgram.getGlobalNamespace());
|
|
99
|
+
while(symbols.hasNext()){if(!visit())break;var symbol=symbols.next();if(symbol.getSymbolType()==SymbolType.FUNCTION||!dataAddress(symbol.getAddress(),all))continue;var row=global(symbol.getAddress(),symbol);if(globalMatch(row,filter,typeFilter,needle,minimum)){rows.add(row);seen.add(symbol.getAddress());}}
|
|
100
|
+
if(!stopped&&!filter.equals("defined")&&!typeFilter.equals("defined")){
|
|
101
|
+
var refs=currentProgram.getReferenceManager().getReferenceDestinationIterator(currentProgram.getMinAddress(),true);
|
|
102
|
+
while(refs.hasNext()){if(!visit())break;var at=refs.next();if(seen.contains(at)||currentProgram.getSymbolTable().getPrimarySymbol(at)!=null||!dataAddress(at,all))continue;var row=global(at,null);if(globalMatch(row,filter,typeFilter,needle,minimum)){rows.add(row);seen.add(at);}}
|
|
103
|
+
}
|
|
104
|
+
return page("globals",rows);
|
|
105
|
+
}
|
|
106
|
+
boolean globalMatch(Map<String,Object> row,String filter,String types,String needle,int minimum){boolean named=(boolean)row.get("named"),typed=(boolean)row.get("typed");return (!filter.equals("defined")||named)&&(!filter.equals("undefined")||!named)&&(!types.equals("defined")||typed)&&(!types.equals("undefined")||!typed)&&(named||(int)row.get("xrefs")>=minimum)&&(needle.isEmpty()||row.toString().toLowerCase(Locale.ROOT).contains(needle));}
|
|
107
|
+
Object entries()throws Exception{
|
|
108
|
+
List<Object> confirmed=new ArrayList<>(),candidates=new ArrayList<>();Set<Address> emitted=new HashSet<>();var it=currentProgram.getSymbolTable().getAllSymbols(true);
|
|
109
|
+
while(it.hasNext()){if(!visit())break;var s=it.next();if(s.isExternalEntryPoint()){if(confirmed.size()>=limit){stopped=true;break;}confirmed.add(map("address",s.getAddress().toString(),"name",s.getName(),"kind","external_entry","confirmed",true,"provenance","ghidra.symbol.external-entry-point"));emitted.add(s.getAddress());}
|
|
110
|
+
else if(candidates.size()<limit&&List.of("main","_main","start","_start","WinMain","_WinMain","DllMain","_DllMain","entry","_entry").contains(s.getName()))candidates.add(map("address",s.getAddress().toString(),"name",s.getName(),"kind","named_candidate","confirmed",false,"provenance","conventional-name"));}
|
|
111
|
+
candidates.add(map("address",currentProgram.getImageBase().toString(),"name","image_base","kind","image_base_candidate","confirmed",false,"provenance","program.image-base"));
|
|
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
|
+
}
|
|
114
|
+
Object strings(boolean search)throws Exception{
|
|
115
|
+
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
|
+
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";
|
|
117
|
+
if(value.length()<min||!encoding.isEmpty()&&!encoding.equalsIgnoreCase(enc)||search&&!regex.matcher(value).find()||!search&&!needle.isEmpty()&&!value.toLowerCase(Locale.ROOT).contains(needle.toLowerCase(Locale.ROOT)))continue;
|
|
118
|
+
rows.add(map("address",d.getAddress().toString(),"value",value.length()>8192?value.substring(0,8192):value,"value_truncated",value.length()>8192,"length",value.length(),"encoding",enc));}
|
|
119
|
+
return page(search?"matches":"strings",rows);
|
|
120
|
+
}
|
|
121
|
+
Pattern safeRegex(String expression){
|
|
122
|
+
// Preserve literal/escaped search and the common .* wildcard without nested repetition/backtracking.
|
|
123
|
+
if(expression.length()>512||expression.replaceAll("\\\\.", "").matches(".*[(){}|+?].*")||expression.matches(".*\\\\[1-9].*")||expression.replaceAll("\\\\.", "").replace(".*","").contains("*"))throw new IllegalArgumentException("Search regex supports literals, escapes, anchors, character classes and .*; nested/branching quantifiers are unsupported");
|
|
124
|
+
if(expression.split(Pattern.quote(".*"),-1).length>2)throw new IllegalArgumentException("At most one .* search wildcard is supported");
|
|
125
|
+
return Pattern.compile(expression,Pattern.CASE_INSENSITIVE);
|
|
126
|
+
}
|
|
127
|
+
Object instructions()throws Exception{
|
|
128
|
+
List<Object> rows=new PagedRows();String f=str("function","");var it=f.isEmpty()?currentProgram.getListing().getInstructions(true):currentProgram.getListing().getInstructions(function(f).getBody(),true);String mnemonic=str("mnemonic","").toLowerCase(Locale.ROOT),operand=str("operand_pattern","");Pattern pattern=operand.isEmpty()?null:safeRegex(operand);
|
|
129
|
+
while(it.hasNext()){if(!visit())break;var i=it.next();String display=i.toString();if(!mnemonic.isEmpty()&&!i.getMnemonicString().equalsIgnoreCase(mnemonic)||pattern!=null&&!pattern.matcher(display).find())continue;rows.add(map("address",i.getAddress().toString(),"mnemonic",i.getMnemonicString(),"instruction",display,"length",i.getLength()));}return page("matches",rows);
|
|
130
|
+
}
|
|
131
|
+
Object dead()throws Exception{
|
|
132
|
+
var f=function(str("function_name",""));var model=new BasicBlockModel(currentProgram);var iterator=model.getCodeBlocksContaining(f.getBody(),boundedMonitor);Map<Address,CodeBlock> blocks=new LinkedHashMap<>();
|
|
133
|
+
while(iterator.hasNext()){if(!visit())break;var block=iterator.next();blocks.put(block.getFirstStartAddress(),block);}
|
|
134
|
+
Set<Address> reachable=new HashSet<>();Deque<Address> work=new ArrayDeque<>();CodeBlock first=model.getFirstCodeBlockContaining(f.getEntryPoint(),boundedMonitor);if(first!=null)work.add(first.getFirstStartAddress());boolean unresolved=first==null||!blocks.containsKey(first.getFirstStartAddress());
|
|
135
|
+
while(!work.isEmpty()&&!stopped){var at=work.removeFirst();if(!reachable.add(at))continue;var block=blocks.get(at);if(block==null)continue;var refs=block.getDestinations(boundedMonitor);
|
|
136
|
+
while(refs.hasNext()){if(!visit())break;var ref=refs.next();if(ref.getFlowType().isCall())continue;var dest=ref.getDestinationAddress();if(ref.getFlowType().isComputed())unresolved=true;if(dest!=null&&f.getBody().contains(dest)){var target=model.getFirstCodeBlockContaining(dest,boundedMonitor);if(target!=null)work.add(target.getFirstStartAddress());else unresolved=true;}}
|
|
137
|
+
var last=currentProgram.getListing().getInstructionAt(block.getMaxAddress());if(last==null)last=currentProgram.getListing().getInstructionContaining(block.getMaxAddress());if(last!=null&&last.getFlowType().isComputed()&&last.getFlowType().isJump())unresolved=true;
|
|
138
|
+
}
|
|
139
|
+
List<Object> findings=new PagedRows();if(!stopped&&!unresolved)for(var entry:blocks.entrySet())if(!reachable.contains(entry.getKey()))findings.add(map("address",entry.getKey().toString(),"end",entry.getValue().getMaxAddress().toString(),"size",entry.getValue().getNumAddresses(),"kind","unreachable-in-static-cfg"));
|
|
140
|
+
var out=page("findings",findings);out.put("function_name",f.getName());out.put("address",f.getEntryPoint().toString());out.put("algorithm","static-basic-block-reachability-v1");out.put("complete",!stopped&&!unresolved);out.put("unresolved_flow",unresolved);out.put("blocks",blocks.size());out.put("reachable_blocks",reachable.size());out.put("limitations",List.of("Static CFG only; indirect unresolved flow suppresses unreachable conclusions.","Only instructions in the selected function body are classified; not a proof about runtime execution."));return out;
|
|
141
|
+
}
|
|
142
|
+
byte[] hex(String value){return java.util.HexFormat.of().parseHex(value.replace(" ",""));}
|
|
143
|
+
Object bytes(boolean crypto)throws Exception{
|
|
144
|
+
Map<String,byte[]> signatures=new LinkedHashMap<>();String database="insight-constant-tables-v1";
|
|
145
|
+
if(crypto){
|
|
146
|
+
signatures.put("AES-SBOX",hex("637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16".replace(" ","")));
|
|
147
|
+
signatures.put("SHA256-IV-BE",hex("6a09e667bb67ae853c6ef372a54ff53a510e527f9b05688c1f83d9ab5be0cd19"));
|
|
148
|
+
signatures.put("SHA1-IV-BE",hex("67452301efcdab8998badcfe10325476c3d2e1f0"));
|
|
149
|
+
signatures.put("MD5-IV-BE",hex("67452301efcdab8998badcfe10325476"));
|
|
150
|
+
signatures.put("ChaCha-sigma", "expand 32-byte k".getBytes(StandardCharsets.US_ASCII));
|
|
151
|
+
for(String name:List.copyOf(signatures.keySet()))if(name.endsWith("-BE")){byte[] b=signatures.get(name).clone();for(int i=0;i<b.length;i+=4)for(int j=0;j<2;j++){byte t=b[i+j];b[i+j]=b[i+3-j];b[i+3-j]=t;}signatures.put(name.replace("-BE","-LE"),b);}
|
|
152
|
+
}else {String pattern=str("pattern","").replaceAll("\\s","");if(pattern.isEmpty()||pattern.length()%2!=0||pattern.length()>1024)throw new IllegalArgumentException("Pattern must contain 1..512 hex byte pairs");byte[] values=new byte[pattern.length()/2],masks=new byte[values.length];String mask=str("mask","");for(int i=0;i<values.length;i++){String pair=pattern.substring(2*i,2*i+2);if(pair.equals("??")){masks[i]=0;values[i]=0;}else {values[i]=hex(pair)[0];masks[i]=(byte)255;}}if(!mask.isEmpty()){if(mask.length()!=values.length)throw new IllegalArgumentException("Mask length must equal pattern byte count");for(int i=0;i<masks.length;i++){if(mask.charAt(i)=='?')masks[i]=0;else if(mask.charAt(i)!='x'&&mask.charAt(i)!='X')throw new IllegalArgumentException("Mask uses x or ?");}}signatures.put("pattern",values);this.patternMasks=masks;}
|
|
153
|
+
int maxBytes=number("max_bytes",1048576,1,16777216),scanned=0;String from=str("scan_from","");Address start=from.isEmpty()?null:address(from),next=null;List<Object> matches=new PagedRows();boolean done=true;
|
|
154
|
+
var blocks=currentProgram.getMemory().getBlocks();Arrays.sort(blocks,Comparator.comparing(MemoryBlock::getStart));
|
|
155
|
+
int longest=signatures.values().stream().mapToInt(v->v.length).max().orElse(1);
|
|
156
|
+
outer:for(var block:blocks){
|
|
157
|
+
if(!block.isInitialized())continue;Address at=block.getStart();if(start!=null){if(block.getEnd().compareTo(start)<0)continue;if(at.compareTo(start)<0)at=start;}
|
|
158
|
+
if(scanned>=maxBytes||!visit()){next=at;done=false;break;}
|
|
159
|
+
long available=block.getEnd().subtract(at)+1;int count=(int)Math.min(available,maxBytes-scanned);byte[] data=new byte[count];int read=currentProgram.getMemory().getBytes(at,data);if(read!=count)throw new IllegalStateException("Incomplete initialized-memory read");scanned+=read;
|
|
160
|
+
// Stop before a truncated lookahead tail. The continuation resumes at its first unchecked start.
|
|
161
|
+
int positions=count==available?count:Math.max(0,count-longest+1);
|
|
162
|
+
for(int i=0;i<positions;i++){
|
|
163
|
+
if(i%256==0&&!visit()){next=at.add(i);done=false;break outer;}
|
|
164
|
+
List<String> names=new ArrayList<>();int length=0;
|
|
165
|
+
for(var signature:signatures.entrySet()){byte[] pattern=signature.getValue();if(i+pattern.length>data.length)continue;boolean equal=true;for(int j=0;j<pattern.length;j++){if(patternMasks!=null&&patternMasks[j]==0)continue;if(data[i+j]!=pattern[j]){equal=false;break;}}if(equal){names.add(signature.getKey());length=Math.max(length,pattern.length);}}
|
|
166
|
+
if(!names.isEmpty()){matches.add(map("address",at.add(i).toString(),"signature",names.getFirst(),"signatures",names,"length",length,"kind",crypto?"constant-table-match":"byte-pattern-match"));if(((PagedRows)matches).total>=offset+limit){next=at.add(i+1);done=false;break outer;}}
|
|
167
|
+
}
|
|
168
|
+
if(count<available){next=at.add(positions);done=false;break;}
|
|
169
|
+
}
|
|
170
|
+
stopped=!done;var out=page("matches",matches);out.put("complete",done);out.put("scanned_bytes",scanned);out.put("maximum_bytes",maxBytes);out.put("next_address",next==null?null:next.toString());out.put("algorithm",crypto?"exact-constant-table-match":"masked-byte-search");
|
|
171
|
+
if(crypto){out.put("database",map("version",database,"signatures",signatures.entrySet().stream().map(e->map("name",e.getKey(),"length",e.getValue().length)).toList()));out.put("limitations",List.of("An exact constant match is a candidate, not proof that cryptography executes.","Only the versioned signature catalog and the returned scan range were checked; increase the byte budget if a continuation cannot advance."));}return out;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
byte[] patternMasks;
|
|
175
|
+
Object execute(String operation)throws Exception{return switch(operation){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");};}
|
|
176
|
+
public void run()throws Exception{
|
|
177
|
+
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;
|
|
178
|
+
boundedMonitor=TimeoutTaskMonitor.timeoutIn(number("timeout_seconds",5,1,30),TimeUnit.SECONDS,monitor);
|
|
179
|
+
Map<String,Object> result;long modification=currentProgram.getModificationNumber();try{
|
|
180
|
+
Object value=execute(request.get("operation").getAsString());
|
|
181
|
+
if(currentProgram.getModificationNumber()!=modification)throw new IllegalStateException("Read-only query changed the program modification counter");
|
|
182
|
+
if(value instanceof Map<?,?>)((Map<String,Object>)value).put("read_only",map("modification_unchanged",true));result=map("value",value);
|
|
183
|
+
}catch(Exception error){result=map("error",map("code",error instanceof UnsupportedOperationException?"unsupported":error instanceof IllegalArgumentException?"invalid":"partial","message",Objects.toString(error.getMessage(),"Native query failed")));}
|
|
184
|
+
finally{boundedMonitor.finished();if(temporaryTypes!=null){temporaryTypes.endTransaction(temporaryTransaction,false);temporaryTypes.close();}}
|
|
185
|
+
|
|
186
|
+
println("INSIGHT_QUERY:"+new GsonBuilder().serializeNulls().create().toJson(result));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import ghidra.GhidraApplicationLayout;
|
|
2
|
+
import ghidra.framework.*;
|
|
3
|
+
import ghidra.program.database.ProgramDB;
|
|
4
|
+
import ghidra.program.model.lang.LanguageID;
|
|
5
|
+
import ghidra.program.util.DefaultLanguageService;
|
|
6
|
+
import ghidra.program.flatapi.FlatProgramAPI;
|
|
7
|
+
import ghidra.app.decompiler.DecompInterface;
|
|
8
|
+
import ghidra.util.task.TaskMonitor;
|
|
9
|
+
import java.io.ByteArrayInputStream;
|
|
10
|
+
|
|
11
|
+
/** Disposable native probe: decode and decompile a known function using the selected installation. */
|
|
12
|
+
class InsightRuntimeDoctor {
|
|
13
|
+
public static void main(String[] args) {
|
|
14
|
+
var out=System.out;System.setOut(System.err);
|
|
15
|
+
try {
|
|
16
|
+
Application.initializeApplication(new GhidraApplicationLayout(),new HeadlessGhidraApplicationConfiguration());
|
|
17
|
+
var language=DefaultLanguageService.getLanguageService().getLanguage(new LanguageID("x86:LE:32:default"));
|
|
18
|
+
var program=new ProgramDB("runtime-probe",language,language.getDefaultCompilerSpec(),new Object());
|
|
19
|
+
var consumer=new Object();program.addConsumer(consumer);
|
|
20
|
+
int tx=program.startTransaction("probe");
|
|
21
|
+
var start=program.getAddressFactory().getDefaultAddressSpace().getAddress(0x1000);
|
|
22
|
+
program.getMemory().createInitializedBlock(".text",start,new ByteArrayInputStream(new byte[]{(byte)0xb8,42,0,0,0,(byte)0xc3}),6,TaskMonitor.DUMMY,false);
|
|
23
|
+
var api=new FlatProgramAPI(program);api.disassemble(start);var function=api.createFunction(start,"runtime_probe");
|
|
24
|
+
program.endTransaction(tx,true);
|
|
25
|
+
var decompiler=new DecompInterface();
|
|
26
|
+
if(!decompiler.openProgram(program))throw new IllegalStateException("Decompiler did not open program");
|
|
27
|
+
var result=decompiler.decompileFunction(function,30,TaskMonitor.DUMMY);
|
|
28
|
+
if(!result.decompileCompleted()||result.getDecompiledFunction()==null||!result.getDecompiledFunction().getC().contains("0x2a"))throw new IllegalStateException("Native decompile did not produce the expected return value");
|
|
29
|
+
decompiler.dispose();program.release(consumer);
|
|
30
|
+
out.println("{\"state\":\"ready\",\"headless\":true,\"decompiler\":true,\"expectedReturn\":42}");System.exit(0);
|
|
31
|
+
} catch(Throwable failure){failure.printStackTrace(System.err);out.println("{\"state\":\"failed\"}");System.exit(1);}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import insight.InsightExtensions;
|
|
2
|
+
import com.google.gson.*;
|
|
3
|
+
import ghidra.GhidraApplicationLayout;
|
|
4
|
+
import ghidra.framework.*;
|
|
5
|
+
import ghidra.framework.data.OpenMode;
|
|
6
|
+
import ghidra.framework.store.db.PackedDatabase;
|
|
7
|
+
import ghidra.program.database.ProgramDB;
|
|
8
|
+
import ghidra.program.model.listing.Program;
|
|
9
|
+
import ghidra.program.model.address.*;
|
|
10
|
+
import ghidra.program.model.data.*;
|
|
11
|
+
import ghidra.program.model.lang.*;
|
|
12
|
+
import ghidra.program.util.DefaultLanguageService;
|
|
13
|
+
import ghidra.app.util.*;
|
|
14
|
+
import ghidra.app.util.bin.*;
|
|
15
|
+
import ghidra.app.util.exporter.*;
|
|
16
|
+
import ghidra.app.util.importer.*;
|
|
17
|
+
import ghidra.app.util.opinion.*;
|
|
18
|
+
import ghidra.app.util.importer.MessageLog;
|
|
19
|
+
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
|
20
|
+
import ghidra.util.classfinder.ClassSearcher;
|
|
21
|
+
import ghidra.util.task.*;
|
|
22
|
+
import java.nio.file.*;
|
|
23
|
+
import java.util.*;
|
|
24
|
+
import java.math.BigInteger;
|
|
25
|
+
|
|
26
|
+
/** File-oriented SDK work runs on immutable snapshots in a retained owned call directory. */
|
|
27
|
+
class InsightSdkFiles {
|
|
28
|
+
static String text(JsonObject p,String key){return p.has(key)&&!p.get(key).isJsonNull()?p.get(key).getAsString():"";}
|
|
29
|
+
static Object scalar(Object value){return value instanceof HexLong||value instanceof Long?value.toString():value==null?null:value instanceof Number||value instanceof Boolean||value instanceof String?value:value.toString();}
|
|
30
|
+
static List<Object> options(List<Option> values){List<Object> rows=new ArrayList<>();for(Option value:values){Map<String,Object> row=new LinkedHashMap<>();row.put("name",value.getName());row.put("type",value.getValueClass().getSimpleName());row.put("default",scalar(value.getValue()));rows.add(row);}return rows;}
|
|
31
|
+
static List<Option> configured(List<Option> values,JsonObject p,AddressFactory factory){
|
|
32
|
+
Set<String> known=new HashSet<>();for(Option option:values){known.add(option.getName());if(!p.has(option.getName()))continue;JsonElement input=p.get(option.getName());Class<?> type=option.getValueClass();Object value;
|
|
33
|
+
if(!input.isJsonPrimitive())throw new IllegalArgumentException("Native options require typed primitive values");
|
|
34
|
+
var primitive=input.getAsJsonPrimitive();
|
|
35
|
+
if(type==String.class){if(!primitive.isString())throw new IllegalArgumentException("Native text option requires a string");value=input.getAsString();}
|
|
36
|
+
else if(type==Boolean.class||type==boolean.class){if(!primitive.isBoolean())throw new IllegalArgumentException("Native Boolean option requires a Boolean");value=input.getAsBoolean();}
|
|
37
|
+
else if(type==Integer.class||type==int.class){if(!primitive.isNumber())throw new IllegalArgumentException("Native integer option requires a number");value=input.getAsBigDecimal().intValueExact();}
|
|
38
|
+
else if(type==Long.class||type==long.class){if(!primitive.isNumber())throw new IllegalArgumentException("Native long option requires a number");value=input.getAsBigDecimal().longValueExact();}
|
|
39
|
+
else if(type==HexLong.class){String encoded=input.getAsString();value=new HexLong(encoded.startsWith("0x")?Long.parseUnsignedLong(encoded.substring(2),16):Long.parseLong(encoded));}
|
|
40
|
+
else if(type==Address.class&&factory!=null){value=factory.getAddress(input.getAsString());if(value==null)throw new IllegalArgumentException("Invalid loader/exporter address option");}
|
|
41
|
+
else throw new IllegalArgumentException("Option value class is not supported by the typed file contract");option.setValue(value);}
|
|
42
|
+
if(!known.containsAll(p.keySet()))throw new IllegalArgumentException("Unknown loader/exporter option");return values;
|
|
43
|
+
}
|
|
44
|
+
static JsonObject optionValues(JsonObject p){return p.has("options")?p.getAsJsonObject("options"):new JsonObject();}
|
|
45
|
+
static Object loaders(JsonObject p) throws Exception {
|
|
46
|
+
try(ByteProvider provider=new RandomAccessByteProvider(Path.of(text(p,"input")).toFile())){
|
|
47
|
+
LoaderMap supported=LoaderService.getAllSupportedLoadSpecs(provider);List<Object> rows=new ArrayList<>();
|
|
48
|
+
for(var entry:supported.entrySet()){
|
|
49
|
+
Loader loader=entry.getKey();List<Object> specs=new ArrayList<>();
|
|
50
|
+
for(LoadSpec spec:entry.getValue()){Map<String,Object> row=new LinkedHashMap<>();var lcs=spec.getLanguageCompilerSpec();
|
|
51
|
+
row.put("language",lcs==null?null:lcs.languageID.toString());row.put("compiler_spec",lcs==null?null:lcs.compilerSpecID.toString());row.put("preferred",spec.isPreferred());row.put("complete",spec.isComplete());
|
|
52
|
+
row.put("options",options(loader.getDefaultOptions(provider,spec,null,false,false)));specs.add(row);}
|
|
53
|
+
rows.add(Map.of("id",loader.getClass().getName(),"name",loader.getName(),"load_specs",specs));}
|
|
54
|
+
return Map.of("supported",rows,"all_loader_names",LoaderService.getAllLoaderNames());
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
static Object importProgram(JsonObject p) throws Exception {
|
|
58
|
+
String selected=text(p,"loader");Object consumer=new Object();Loader loader=null;
|
|
59
|
+
if(selected.isEmpty()){
|
|
60
|
+
Language language=text(p,"language").isEmpty()?null:DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(text(p,"language")));
|
|
61
|
+
CompilerSpec compiler=language==null?null:text(p,"compiler_spec").isEmpty()?language.getDefaultCompilerSpec():language.getCompilerSpecByID(new CompilerSpecID(text(p,"compiler_spec")));
|
|
62
|
+
var loaded=language==null?AutoImporter.importByUsingBestGuess(Path.of(text(p,"input")).toFile(),null,"/",consumer,new MessageLog(),TaskMonitor.DUMMY):
|
|
63
|
+
AutoImporter.importByLookingForLcs(Path.of(text(p,"input")).toFile(),null,"/",language,compiler,consumer,new MessageLog(),TaskMonitor.DUMMY);
|
|
64
|
+
try{if(loaded==null||loaded.size()!=1)throw new IllegalArgumentException("Expected one imported program");return saveImport(loaded.getPrimaryDomainObject(),p,"automatic");}
|
|
65
|
+
finally{if(loaded!=null)loaded.release(consumer);}
|
|
66
|
+
}
|
|
67
|
+
for(Loader candidate:ClassSearcher.getInstances(Loader.class))if(candidate.getClass().getName().equals(selected)||candidate.getClass().getSimpleName().equals(selected)||candidate.getName().equals(selected)){
|
|
68
|
+
if(loader!=null)throw new IllegalArgumentException("Ambiguous installed loader");loader=candidate;}
|
|
69
|
+
if(loader==null)throw new IllegalArgumentException("Unknown installed loader");
|
|
70
|
+
Language language=text(p,"language").isEmpty()?null:DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(text(p,"language")));
|
|
71
|
+
CompilerSpec compiler=language==null?null:text(p,"compiler_spec").isEmpty()?language.getDefaultCompilerSpec():language.getCompilerSpecByID(new CompilerSpecID(text(p,"compiler_spec")));
|
|
72
|
+
try(ByteProvider provider=new RandomAccessByteProvider(Path.of(text(p,"input")).toFile())){
|
|
73
|
+
LoadSpec selectedSpec=null;for(LoadSpec spec:loader.findSupportedLoadSpecs(provider)){
|
|
74
|
+
if(language!=null){selectedSpec=new LoadSpec(loader,spec.getDesiredImageBase(),new LanguageCompilerSpecPair(language.getLanguageID(),compiler.getCompilerSpecID()),true);break;}
|
|
75
|
+
if(spec.isPreferred()&&spec.isComplete()){selectedSpec=spec;break;}}
|
|
76
|
+
if(selectedSpec==null)throw new IllegalArgumentException("Loader needs a complete language/compiler selection");
|
|
77
|
+
Language effectiveLanguage=language;if(effectiveLanguage==null&&selectedSpec.getLanguageCompilerSpec()!=null)effectiveLanguage=DefaultLanguageService.getLanguageService().getLanguage(selectedSpec.getLanguageCompilerSpec().languageID);
|
|
78
|
+
var options=configured(loader.getDefaultOptions(provider,selectedSpec,null,false,false),optionValues(p),effectiveLanguage==null?null:effectiveLanguage.getAddressFactory());
|
|
79
|
+
String error=loader.validateOptions(provider,selectedSpec,options,null);if(error!=null)throw new IllegalArgumentException("Native loader rejected options: "+error);
|
|
80
|
+
var settings=new Loader.ImporterSettings(provider,"OwnedInput",null,"/",false,selectedSpec,options,consumer,new MessageLog(),TaskMonitor.DUMMY);
|
|
81
|
+
var result=loader.load(settings);
|
|
82
|
+
if(result==null)throw new IllegalStateException("Native loader did not return a program");
|
|
83
|
+
try {
|
|
84
|
+
if(result.size()!=1||!(result.getPrimaryDomainObject() instanceof Program))throw new IllegalArgumentException("Loader must produce one program per explicit target");
|
|
85
|
+
return saveImport((Program)result.getPrimaryDomainObject(),p,loader.getClass().getName());
|
|
86
|
+
} finally {result.release(consumer);}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
static Object saveImport(Program program,JsonObject p,String loader)throws Exception{
|
|
90
|
+
boolean analyze=p.has("auto_analyze")&&p.get("auto_analyze").getAsBoolean();
|
|
91
|
+
if(analyze){ghidra.app.script.GhidraScriptUtil.acquireBundleHostReference();int tx=program.startTransaction("Initial analysis");boolean success=false;
|
|
92
|
+
try{AutoAnalysisManager analysis=AutoAnalysisManager.getAnalysisManager(program);analysis.initializeOptions();analysis.reAnalyzeAll(null);analysis.startAnalysis(TaskMonitor.DUMMY);success=true;}
|
|
93
|
+
finally{program.endTransaction(tx,success);ghidra.app.script.GhidraScriptUtil.releaseBundleHostReference();}}
|
|
94
|
+
if(p.has("image_base")){int tx=program.startTransaction("Initial image base");boolean success=false;
|
|
95
|
+
try{program.setImageBase(program.getAddressFactory().getDefaultAddressSpace().getAddress(new BigInteger(text(p,"image_base"),16).longValue()),true);success=true;}
|
|
96
|
+
finally{program.endTransaction(tx,success);}}
|
|
97
|
+
program.saveToPackedFile(Path.of(text(p,"output")).toFile(),TaskMonitor.DUMMY);
|
|
98
|
+
return Map.of("state","imported","loader",loader,"language",program.getLanguageID().toString(),"base_address",program.getImageBase().toString(),
|
|
99
|
+
"compiler",program.getCompilerSpec().getCompilerSpecID().toString(),"sha256",program.getExecutableSHA256(),"auto_analyzed",analyze);
|
|
100
|
+
}
|
|
101
|
+
static Object exporters(Program program) throws Exception {
|
|
102
|
+
List<Object> rows=new ArrayList<>();for(Exporter exporter:ClassSearcher.getInstances(Exporter.class)){
|
|
103
|
+
if(!exporter.canExportDomainObject(program))continue;
|
|
104
|
+
rows.add(Map.of("id",exporter.getClass().getName(),"name",exporter.getName(),"extension",Objects.toString(exporter.getDefaultFileExtension(),""),
|
|
105
|
+
"address_restriction",exporter.supportsAddressRestrictedExport(),"options",options(exporter.getOptions(()->program))));}
|
|
106
|
+
return Map.of("items",rows);
|
|
107
|
+
}
|
|
108
|
+
static Object export(Program program,JsonObject p) throws Exception {
|
|
109
|
+
Exporter exporter=null;for(Exporter candidate:ClassSearcher.getInstances(Exporter.class))if(candidate.canExportDomainObject(program)&&(candidate.getName().equals(text(p,"exporter"))||candidate.getClass().getName().equals(text(p,"exporter")))){if(exporter!=null)throw new IllegalArgumentException("Ambiguous exporter");exporter=candidate;}
|
|
110
|
+
if(exporter==null||!exporter.canExportDomainObject(program))throw new IllegalArgumentException("Exporter is not available for this program");
|
|
111
|
+
AddressSet selection=null;if(p.has("ranges")){if(!exporter.supportsAddressRestrictedExport())throw new IllegalArgumentException("Exporter cannot restrict address ranges");selection=new AddressSet();
|
|
112
|
+
for(JsonElement value:p.getAsJsonArray("ranges")){JsonObject range=value.getAsJsonObject();Address start=program.getAddressFactory().getAddress(text(range,"start")),end=program.getAddressFactory().getAddress(text(range,"end"));
|
|
113
|
+
if(start==null||end==null||!start.getAddressSpace().equals(end.getAddressSpace())||start.compareTo(end)>0)throw new IllegalArgumentException("Invalid export range");selection.add(start,end);}
|
|
114
|
+
if(selection.isEmpty()||selection.getNumAddresses()>16777216)throw new IllegalArgumentException("Export selection must be 1–16 MiB");}
|
|
115
|
+
exporter.setOptions(configured(exporter.getOptions(()->program),optionValues(p),program.getAddressFactory()));
|
|
116
|
+
long before=program.getModificationNumber();
|
|
117
|
+
if(!exporter.export(Path.of(text(p,"output")).toFile(),program,selection,TaskMonitor.DUMMY)||before!=program.getModificationNumber())throw new IllegalStateException("Native exporter did not complete a read-only export");
|
|
118
|
+
return Map.of("state","exported","exporter",exporter.getClass().getName(),"format",exporter.getName(),"source_sha256",program.getExecutableSHA256(),"selected",selection!=null);
|
|
119
|
+
}
|
|
120
|
+
static Object archive(Program program,JsonObject p) throws Exception {
|
|
121
|
+
JsonArray names=p.getAsJsonArray("types");if(names==null||names.isEmpty()||names.size()>256)throw new IllegalArgumentException("Choose 1–256 type paths");
|
|
122
|
+
try(FileDataTypeManager archive=FileDataTypeManager.createFileArchive(Path.of(text(p,"output")).toFile(),program.getLanguageID(),program.getCompilerSpec().getCompilerSpecID())){
|
|
123
|
+
int transaction=archive.startTransaction("Export selected types");try{for(JsonElement name:names){DataType type=program.getDataTypeManager().getDataType(name.getAsString());if(type==null)throw new IllegalArgumentException("Type path does not exist");archive.resolve(type,DataTypeConflictHandler.DEFAULT_HANDLER);}}finally{archive.endTransaction(transaction,true);}
|
|
124
|
+
archive.save();List<Object> types=new ArrayList<>();for(JsonElement name:names){DataType original=program.getDataTypeManager().getDataType(name.getAsString()),copied=archive.getDataType(name.getAsString());if(copied==null||!original.isEquivalent(copied))throw new IllegalStateException("Exported type layout differs");types.add(Map.of("path",copied.getPathName(),"size",copied.getLength()));}
|
|
125
|
+
return Map.of("state","exported","format","GDT","archive_id",archive.getUniversalID().toString(),"types",types);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
static Object run(JsonObject p) throws Exception {
|
|
129
|
+
String operation=text(p,"operation");if(operation.equals("extension-inventory"))return InsightExtensions.inventory(p);if(operation.equals("loaders"))return loaders(p);if(operation.equals("import"))return importProgram(p);
|
|
130
|
+
Object consumer=new Object();var db=PackedDatabase.getPackedDatabase(Path.of(text(p,"snapshot")).toFile(),true,TaskMonitor.DUMMY).open(TaskMonitor.DUMMY);
|
|
131
|
+
ProgramDB program=new ProgramDB(db,OpenMode.IMMUTABLE,TaskMonitor.DUMMY,consumer);
|
|
132
|
+
try {return switch(operation){case "exporters"->exporters(program);case "export"->export(program,p);case "archive-export"->archive(program,p);default->throw new IllegalArgumentException("Unknown SDK file operation");};}
|
|
133
|
+
finally{program.release(consumer);}
|
|
134
|
+
}
|
|
135
|
+
public static void main(String[] args) {
|
|
136
|
+
var output=System.out;System.setOut(System.err);
|
|
137
|
+
try{Application.initializeApplication(new GhidraApplicationLayout(),new HeadlessGhidraApplicationConfiguration());
|
|
138
|
+
JsonObject request=JsonParser.parseString(Files.readString(Path.of(args[0]))).getAsJsonObject();Object value=run(request);InsightNativeReceipt.write(request,value);
|
|
139
|
+
output.println(new GsonBuilder().serializeNulls().create().toJson(Map.of("value",value)));System.exit(0);
|
|
140
|
+
}catch(Throwable failure){failure.printStackTrace(System.err);output.println("{\"error\":\"native_sdk_file_failed\"}");System.exit(1);}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import com.google.gson.*;
|
|
2
|
+
import ghidra.app.script.GhidraScript;
|
|
3
|
+
import ghidra.program.model.listing.Program;
|
|
4
|
+
import insight.InsightSdkTools;
|
|
5
|
+
import java.nio.charset.StandardCharsets;
|
|
6
|
+
import java.util.*;
|
|
7
|
+
|
|
8
|
+
/** Fixed dispatch into compiled facade-owned SDK operations; no caller-supplied source. */
|
|
9
|
+
public class InsightSdkScript extends GhidraScript {
|
|
10
|
+
public void run() throws Exception {
|
|
11
|
+
Program peer=null;Object consumer=new Object();
|
|
12
|
+
try {
|
|
13
|
+
JsonObject request=JsonParser.parseString(new String(Base64.getDecoder().decode(getScriptArgs()[0]),StandardCharsets.UTF_8)).getAsJsonObject();
|
|
14
|
+
JsonObject p=request.getAsJsonObject("parameters");String operation=request.get("operation").getAsString().substring(5);
|
|
15
|
+
long before=currentProgram.getModificationNumber();Object value;
|
|
16
|
+
if(operation.equals("changes")){
|
|
17
|
+
var file=currentProgram.getDomainFile().getParent().getProjectData().getFile(p.get("peer_program").getAsString());
|
|
18
|
+
if(file==null)throw new IllegalArgumentException("Owned peer snapshot is absent");
|
|
19
|
+
peer=(Program)file.getReadOnlyDomainObject(consumer,-1,monitor);value=InsightSdkTools.compare(currentProgram,peer,p,monitor);
|
|
20
|
+
} else if(request.has("write")&&request.get("write").getAsBoolean())value=InsightSdkTools.mutate(currentProgram,operation,p,monitor);
|
|
21
|
+
else value=InsightSdkTools.read(currentProgram,operation,p,monitor);
|
|
22
|
+
if(!request.has("write")||!request.get("write").getAsBoolean()){
|
|
23
|
+
if(before!=currentProgram.getModificationNumber())throw new IllegalStateException("Read-only SDK operation modified the program");
|
|
24
|
+
}
|
|
25
|
+
println("INSIGHT_QUERY:"+new GsonBuilder().serializeNulls().create().toJson(Map.of("value",value)));
|
|
26
|
+
} catch(Throwable failure) {
|
|
27
|
+
failure.printStackTrace();String detail=failure.getStackTrace().length>0&&failure.getStackTrace()[0].getClassName().equals("insight.InsightSdkTools")?Objects.toString(failure.getMessage(),""):failure.getClass().getSimpleName();
|
|
28
|
+
println("INSIGHT_QUERY:"+new Gson().toJson(Map.of("error",Map.of("code",failure instanceof InsightSdkTools.Rejected?"rejected":failure instanceof IllegalArgumentException?"invalid":"partial","message","Native SDK operation could not establish its result: "+detail.substring(0,Math.min(detail.length(),300))))));
|
|
29
|
+
} finally {if(peer!=null)peer.release(consumer);}
|
|
30
|
+
}
|
|
31
|
+
}
|