@eyeauras/insight-cli 0.0.0-stage → 0.1.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 +54 -2
- package/SKILL.md +55 -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 +120 -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 +387 -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 +1068 -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 +148 -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 +171 -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/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 +44 -0
- package/dist/src/server.js +455 -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 +45 -4
- package/references/InsightRepositoryProbe.java +55 -0
- package/references/administration.md +33 -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/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/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 +47 -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,338 @@
|
|
|
1
|
+
package insight;
|
|
2
|
+
|
|
3
|
+
import com.google.gson.*;
|
|
4
|
+
import ghidra.app.decompiler.*;
|
|
5
|
+
import ghidra.app.plugin.assembler.Assemblers;
|
|
6
|
+
import ghidra.program.model.address.*;
|
|
7
|
+
import ghidra.program.model.lang.*;
|
|
8
|
+
import ghidra.program.model.listing.*;
|
|
9
|
+
import ghidra.program.model.mem.*;
|
|
10
|
+
import ghidra.program.model.pcode.*;
|
|
11
|
+
import ghidra.program.model.symbol.*;
|
|
12
|
+
import ghidra.program.util.*;
|
|
13
|
+
import ghidra.util.task.*;
|
|
14
|
+
import java.math.BigInteger;
|
|
15
|
+
import java.security.MessageDigest;
|
|
16
|
+
import java.util.*;
|
|
17
|
+
import java.util.concurrent.TimeUnit;
|
|
18
|
+
|
|
19
|
+
/** Selected typed SDK workflows. Every edit is verified again on an immutable saved Program. */
|
|
20
|
+
public class InsightSdkTools {
|
|
21
|
+
public static class Rejected extends Exception {
|
|
22
|
+
public Rejected(Exception cause) {super("SDK transaction rolled back",cause);}
|
|
23
|
+
}
|
|
24
|
+
public static String text(JsonObject p, String key) {
|
|
25
|
+
return p.has(key) && !p.get(key).isJsonNull() ? p.get(key).getAsString() : "";
|
|
26
|
+
}
|
|
27
|
+
static int integer(JsonObject p, String key, int fallback, int max) {
|
|
28
|
+
int n=p.has(key)?p.get(key).getAsInt():fallback;
|
|
29
|
+
if(n<1||n>max)throw new IllegalArgumentException("Invalid "+key+" bound");
|
|
30
|
+
return n;
|
|
31
|
+
}
|
|
32
|
+
static Address at(Program program, String value) {
|
|
33
|
+
Address address=program.getAddressFactory().getAddress(value);
|
|
34
|
+
if(address==null)throw new IllegalArgumentException("Address is not in this program's address spaces");
|
|
35
|
+
return address;
|
|
36
|
+
}
|
|
37
|
+
static byte[] hex(String value) {
|
|
38
|
+
if(value.isEmpty()||value.length()>8192||!value.matches("(?:[a-fA-F0-9]{2})+"))
|
|
39
|
+
throw new IllegalArgumentException("Expected 1–4096 complete hex bytes");
|
|
40
|
+
return HexFormat.of().parseHex(value);
|
|
41
|
+
}
|
|
42
|
+
static byte[] bytes(Program program, Address start, int size) throws Exception {
|
|
43
|
+
byte[] bytes=new byte[size];
|
|
44
|
+
if(program.getMemory().getBytes(start,bytes)!=size)throw new IllegalStateException("Incomplete memory range");
|
|
45
|
+
return bytes;
|
|
46
|
+
}
|
|
47
|
+
static String hash(Program program, Address start, long size) throws Exception {
|
|
48
|
+
if(size<1||size>16777216)throw new IllegalArgumentException("Memory verification bound is 16 MiB");
|
|
49
|
+
MessageDigest hash=MessageDigest.getInstance("SHA-256");
|
|
50
|
+
for(long offset=0;offset<size;){int count=(int)Math.min(65536,size-offset);hash.update(bytes(program,start.add(offset),count));offset+=count;}
|
|
51
|
+
return HexFormat.of().formatHex(hash.digest());
|
|
52
|
+
}
|
|
53
|
+
static MemoryBlock block(Program program, String name) {
|
|
54
|
+
// Ghidra's name lookup cache can retain a removed block until transaction completion.
|
|
55
|
+
MemoryBlock block=Arrays.stream(program.getMemory().getBlocks()).filter(value->value.getName().equals(name)).findFirst().orElse(null);
|
|
56
|
+
if(block==null)throw new IllegalArgumentException("Memory block does not exist");
|
|
57
|
+
return block;
|
|
58
|
+
}
|
|
59
|
+
static Map<String,Object> blockState(Program program, MemoryBlock b) throws Exception {
|
|
60
|
+
Map<String,Object> row=new LinkedHashMap<>();
|
|
61
|
+
row.put("name",b.getName());row.put("start",b.getStart().toString());row.put("end",b.getEnd().toString());
|
|
62
|
+
row.put("size",b.getSize());row.put("initialized",b.isInitialized());row.put("overlay",b.isOverlay());
|
|
63
|
+
row.put("read",b.isRead());row.put("write",b.isWrite());row.put("execute",b.isExecute());
|
|
64
|
+
row.put("sha256",b.isInitialized()?hash(program,b.getStart(),b.getSize()):null);return row;
|
|
65
|
+
}
|
|
66
|
+
static void require(boolean condition, String message) {
|
|
67
|
+
if(!condition)throw new IllegalStateException(message);
|
|
68
|
+
}
|
|
69
|
+
static Namespace namespace(Program program,String path) {
|
|
70
|
+
Namespace current=program.getGlobalNamespace();
|
|
71
|
+
if(path.isEmpty()||path.equals("Global"))return current;
|
|
72
|
+
for(String name:path.split("::",-1)){
|
|
73
|
+
if(name.isEmpty())throw new IllegalArgumentException("Invalid namespace path");
|
|
74
|
+
current=program.getSymbolTable().getNamespace(name,current);
|
|
75
|
+
if(current==null)throw new IllegalArgumentException("Namespace does not exist");
|
|
76
|
+
}
|
|
77
|
+
return current;
|
|
78
|
+
}
|
|
79
|
+
static Map<String,Object> namespaceState(Namespace value) {
|
|
80
|
+
return Map.of("path",value.getName(true),"name",value.getName(),"kind",value.getSymbol().getSymbolType().toString(),
|
|
81
|
+
"parent",value.getParentNamespace().isGlobal()?"":value.getParentNamespace().getName(true));
|
|
82
|
+
}
|
|
83
|
+
static ExternalLocation external(Program program,JsonObject p) {
|
|
84
|
+
ExternalLocation value=program.getExternalManager().getUniqueExternalLocation(text(p,"library"),text(p,"label"));
|
|
85
|
+
if(value==null)throw new IllegalArgumentException("External location is absent or ambiguous");return value;
|
|
86
|
+
}
|
|
87
|
+
static Map<String,Object> externalState(ExternalLocation value) {
|
|
88
|
+
Map<String,Object> row=new LinkedHashMap<>();row.put("library",value.getLibraryName());row.put("label",value.getLabel());
|
|
89
|
+
row.put("address",value.getAddress()==null?null:value.getAddress().toString());row.put("function",value.isFunction());
|
|
90
|
+
row.put("symbol_address",value.getExternalSpaceAddress().toString());row.put("source",value.getSource().toString());return row;
|
|
91
|
+
}
|
|
92
|
+
static byte[] assembly(Program program,JsonObject p) throws Exception {
|
|
93
|
+
JsonArray input=p.getAsJsonArray("instructions");
|
|
94
|
+
if(input==null||input.isEmpty()||input.size()>64)throw new IllegalArgumentException("Assembly requires 1–64 instructions");
|
|
95
|
+
var assembler=Assemblers.getAssembler(program);Address address=at(program,text(p,"address"));
|
|
96
|
+
var output=new java.io.ByteArrayOutputStream();
|
|
97
|
+
for(JsonElement row:input){String line=row.getAsString();if(line.length()>512||line.contains("\n"))throw new IllegalArgumentException("Instruction is not bounded single-line text");
|
|
98
|
+
byte[] value=assembler.assembleLine(address,line);output.write(value);address=address.add(value.length);}
|
|
99
|
+
if(output.size()>4096)throw new IllegalArgumentException("Assembly exceeds 4096 bytes");return output.toByteArray();
|
|
100
|
+
}
|
|
101
|
+
public static Object read(Program program,String operation,JsonObject p,TaskMonitor monitor) throws Exception {
|
|
102
|
+
switch(operation) {
|
|
103
|
+
case "block-state": return blockState(program,block(program,text(p,"name")));
|
|
104
|
+
case "assembly-preview": {
|
|
105
|
+
byte[] value=assembly(program,p);
|
|
106
|
+
return Map.of("address",at(program,text(p,"address")).toString(),"bytes_hex",HexFormat.of().formatHex(value),
|
|
107
|
+
"size",value.length,"language",program.getLanguageID().toString(),"context_aware",true);
|
|
108
|
+
}
|
|
109
|
+
case "context-read": {
|
|
110
|
+
Register register=program.getRegister(text(p,"register"));
|
|
111
|
+
if(register==null)throw new IllegalArgumentException("Unknown processor register");
|
|
112
|
+
Address start=at(program,text(p,"address")),end=p.has("end_address")?at(program,text(p,"end_address")):start;
|
|
113
|
+
if(!start.getAddressSpace().equals(end.getAddressSpace())||start.compareTo(end)>0)throw new IllegalArgumentException("Invalid context range");
|
|
114
|
+
List<Object> rows=new ArrayList<>();var ranges=program.getProgramContext().getRegisterValueAddressRanges(register,start,end);
|
|
115
|
+
int limit=integer(p,"limit",100,1000);
|
|
116
|
+
while(ranges.hasNext()&&rows.size()<limit){AddressRange range=ranges.next();var value=program.getProgramContext().getRegisterValue(register,range.getMinAddress());
|
|
117
|
+
var explicit=program.getProgramContext().getNonDefaultValue(register,range.getMinAddress());
|
|
118
|
+
rows.add(Map.of("start",range.getMinAddress().toString(),"end",range.getMaxAddress().toString(),"known",value.hasValue(),"explicit",explicit!=null&&explicit.hasAnyValue(),
|
|
119
|
+
"value","0x"+value.getUnsignedValueIgnoreMask().toString(16),"mask","0x"+value.getValueMask().toString(16)));}
|
|
120
|
+
return Map.of("register",register.getName(),"bits",register.getBitLength(),"ranges",rows,"complete",!ranges.hasNext());
|
|
121
|
+
}
|
|
122
|
+
case "namespace-list": {
|
|
123
|
+
Namespace parent=namespace(program,text(p,"parent"));var iter=program.getSymbolTable().getSymbols(parent);
|
|
124
|
+
int limit=integer(p,"limit",100,1000),visited=0,budget=integer(p,"max_scan",10000,200000);List<Object> rows=new ArrayList<>();
|
|
125
|
+
while(iter.hasNext()&&rows.size()<limit&&visited<budget){monitor.checkCancelled();Symbol symbol=iter.next();visited++;
|
|
126
|
+
if(symbol.getSymbolType()==SymbolType.NAMESPACE||symbol.getSymbolType()==SymbolType.CLASS)rows.add(namespaceState((Namespace)symbol.getObject()));}
|
|
127
|
+
return Map.of("items",rows,"visited",visited,"complete",!iter.hasNext());
|
|
128
|
+
}
|
|
129
|
+
case "external-list": {
|
|
130
|
+
var manager=program.getExternalManager();List<Object> rows=new ArrayList<>();int limit=integer(p,"limit",100,1000);
|
|
131
|
+
String library=text(p,"library");
|
|
132
|
+
if(library.isEmpty()){String[] names=manager.getExternalLibraryNames();for(int i=0;i<Math.min(names.length,limit);i++){
|
|
133
|
+
Map<String,Object> row=new LinkedHashMap<>();row.put("name",names[i]);row.put("path",manager.getExternalLibraryPath(names[i]));rows.add(row);}
|
|
134
|
+
return Map.of("items",rows,"complete",names.length<=limit);}
|
|
135
|
+
var iter=manager.getExternalLocations(library);while(iter.hasNext()&&rows.size()<limit){monitor.checkCancelled();rows.add(externalState(iter.next()));}
|
|
136
|
+
return Map.of("items",rows,"complete",!iter.hasNext());
|
|
137
|
+
}
|
|
138
|
+
case "decompiler-ir": return decompile(program,p,monitor);
|
|
139
|
+
case "type-layout": return InsightSdkTypes.layout(program.getDataTypeManager().getDataType(text(p,"path")));
|
|
140
|
+
default: throw new IllegalArgumentException("Unknown typed SDK read");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
public static Object mutate(Program program,String operation,JsonObject p,TaskMonitor monitor) throws Exception {
|
|
144
|
+
int transaction=program.startTransaction("insight "+operation);boolean committed=false,closed=false;
|
|
145
|
+
try {
|
|
146
|
+
var memory=program.getMemory();var symbols=program.getSymbolTable();var externals=program.getExternalManager();
|
|
147
|
+
switch(operation) {
|
|
148
|
+
case "type-structure": case "type-settings": case "type-archive-import":InsightSdkTypes.edit(program,operation,p,monitor);break;
|
|
149
|
+
case "patch-bytes": case "assemble": {
|
|
150
|
+
Address start=at(program,text(p,"address"));byte[] expected=hex(text(p,"expect_hex"));
|
|
151
|
+
byte[] desired=operation.equals("assemble")?assembly(program,p):hex(text(p,"bytes_hex"));
|
|
152
|
+
if(expected.length!=desired.length)throw new IllegalArgumentException("Expected and replacement lengths must match");
|
|
153
|
+
require(Arrays.equals(bytes(program,start,expected.length),expected),"Memory byte precondition changed");
|
|
154
|
+
program.getListing().clearCodeUnits(start,start.add(desired.length-1),false,monitor);memory.setBytes(start,desired);break;
|
|
155
|
+
}
|
|
156
|
+
case "split-block": {
|
|
157
|
+
MemoryBlock original=block(program,text(p,"name"));require(hash(program,original.getStart(),original.getSize()).equals(text(p,"expect_sha256")),"Block content precondition changed");
|
|
158
|
+
require(original.getStart().equals(at(program,text(p,"expect_start")))&&original.getSize()==p.get("expect_size").getAsLong(),"Block layout precondition changed");
|
|
159
|
+
Address split=at(program,text(p,"split_address"));memory.split(original,split);memory.getBlock(split).setName(text(p,"new_name"));break;
|
|
160
|
+
}
|
|
161
|
+
case "move-block": {
|
|
162
|
+
MemoryBlock original=block(program,text(p,"name"));require(hash(program,original.getStart(),original.getSize()).equals(text(p,"expect_sha256")),"Block content precondition changed");
|
|
163
|
+
require(original.getStart().equals(at(program,text(p,"expect_start")))&&original.getSize()==p.get("expect_size").getAsLong(),"Block layout precondition changed");
|
|
164
|
+
memory.moveBlock(original,at(program,text(p,"address")),monitor);break;
|
|
165
|
+
}
|
|
166
|
+
case "join-blocks": {
|
|
167
|
+
MemoryBlock first=block(program,text(p,"name")),second=block(program,text(p,"second_name"));
|
|
168
|
+
require(first.getSize()==p.get("expect_size").getAsLong()&&second.getSize()==p.get("second_size").getAsLong(),"Block sizes changed");
|
|
169
|
+
require(first.getEnd().next().equals(second.getStart()),"Blocks must be adjacent in the declared order");
|
|
170
|
+
require(hash(program,first.getStart(),first.getSize()).equals(text(p,"expect_sha256"))&&hash(program,second.getStart(),second.getSize()).equals(text(p,"second_sha256")),"Block contents changed");
|
|
171
|
+
memory.join(first,second);break;
|
|
172
|
+
}
|
|
173
|
+
case "context-set": {
|
|
174
|
+
Register register=program.getRegister(text(p,"register"));if(register==null)throw new IllegalArgumentException("Unknown processor register");
|
|
175
|
+
Address start=at(program,text(p,"address")),end=at(program,text(p,"end_address"));
|
|
176
|
+
program.getProgramContext().setValue(register,start,end,new BigInteger(text(p,"value").replaceFirst("^0x",""),16));break;
|
|
177
|
+
}
|
|
178
|
+
case "context-clear": {
|
|
179
|
+
Register register=program.getRegister(text(p,"register"));if(register==null)throw new IllegalArgumentException("Unknown processor register");
|
|
180
|
+
program.getProgramContext().remove(at(program,text(p,"address")),at(program,text(p,"end_address")),register);break;
|
|
181
|
+
}
|
|
182
|
+
case "namespace-create": {
|
|
183
|
+
Namespace parent=namespace(program,text(p,"parent"));
|
|
184
|
+
if(p.has("class")&&p.get("class").getAsBoolean())symbols.createClass(parent,text(p,"name"),SourceType.USER_DEFINED);
|
|
185
|
+
else symbols.createNameSpace(parent,text(p,"name"),SourceType.USER_DEFINED);break;
|
|
186
|
+
}
|
|
187
|
+
case "namespace-update": {
|
|
188
|
+
Namespace value=namespace(program,text(p,"path"));require(!value.isGlobal()&&!value.isExternal()&&!value.getSymbol().getSymbolType().equals(SymbolType.FUNCTION),"Only ordinary namespaces/classes can be edited");
|
|
189
|
+
value.getSymbol().setNameAndNamespace(text(p,"new_name"),namespace(program,text(p,"parent")),SourceType.USER_DEFINED);break;
|
|
190
|
+
}
|
|
191
|
+
case "namespace-delete": {
|
|
192
|
+
Namespace value=namespace(program,text(p,"path"));require(!value.isGlobal()&&!value.isExternal()&&!value.getSymbol().getSymbolType().equals(SymbolType.FUNCTION),"Only ordinary namespaces/classes can be deleted");
|
|
193
|
+
require(!symbols.getSymbols(value).hasNext(),"Namespace must be empty");require(value.getSymbol().delete(),"Namespace deletion was refused");break;
|
|
194
|
+
}
|
|
195
|
+
case "library-create": externals.addExternalLibraryName(text(p,"name"),SourceType.USER_DEFINED);break;
|
|
196
|
+
case "library-update": {
|
|
197
|
+
require(externals.contains(text(p,"name")),"External library does not exist");
|
|
198
|
+
if(p.has("new_name"))externals.updateExternalLibraryName(text(p,"name"),text(p,"new_name"),SourceType.USER_DEFINED);
|
|
199
|
+
if(p.has("path"))externals.setExternalPath(p.has("new_name")?text(p,"new_name"):text(p,"name"),text(p,"path"),true);break;
|
|
200
|
+
}
|
|
201
|
+
case "library-delete": require(externals.removeExternalLibrary(text(p,"name")),"Library is absent or still contains external locations");break;
|
|
202
|
+
case "external-create": {
|
|
203
|
+
require(externals.contains(text(p,"library")),"Create the explicit external library first");
|
|
204
|
+
require(externals.getExternalLocations(text(p,"library"),text(p,"label")).isEmpty(),"External label already exists");
|
|
205
|
+
Address address=p.has("address")?at(program,text(p,"address")):null;
|
|
206
|
+
if(p.has("function")&&p.get("function").getAsBoolean())externals.addExtFunction(text(p,"library"),text(p,"label"),address,SourceType.USER_DEFINED);
|
|
207
|
+
else externals.addExtLocation(text(p,"library"),text(p,"label"),address,SourceType.USER_DEFINED);break;
|
|
208
|
+
}
|
|
209
|
+
case "external-update": {
|
|
210
|
+
ExternalLocation value=external(program,p);value.setLocation(text(p,"new_label"),p.has("address")?at(program,text(p,"address")):value.getAddress(),SourceType.USER_DEFINED);break;
|
|
211
|
+
}
|
|
212
|
+
case "external-delete": {
|
|
213
|
+
ExternalLocation value=external(program,p);Address address=value.getExternalSpaceAddress();
|
|
214
|
+
require(!program.getReferenceManager().hasReferencesTo(address),"External location has retained references");
|
|
215
|
+
if(value.isFunction())program.getFunctionManager().removeFunction(address);
|
|
216
|
+
Symbol remaining=symbols.getPrimarySymbol(address);
|
|
217
|
+
if(remaining!=null)require(remaining.delete(),"External location deletion was refused");break;
|
|
218
|
+
}
|
|
219
|
+
default: throw new IllegalArgumentException("Unknown typed SDK edit");
|
|
220
|
+
}
|
|
221
|
+
Object verified=verify(program,operation,p,monitor);committed=true;return Map.of("success",true,"effect",verified);
|
|
222
|
+
} catch(Exception failure) {
|
|
223
|
+
// A rejection receipt is emitted only after native rollback returned successfully.
|
|
224
|
+
closed=true;program.endTransaction(transaction,false);throw new Rejected(failure);
|
|
225
|
+
} finally {if(!closed)program.endTransaction(transaction,committed);}
|
|
226
|
+
}
|
|
227
|
+
public static Object verify(Program program,String operation,JsonObject p,TaskMonitor monitor) throws Exception {
|
|
228
|
+
switch(operation) {
|
|
229
|
+
case "type-structure": case "type-settings": case "type-archive-import":return InsightSdkTypes.verify(program,operation,p,monitor);
|
|
230
|
+
case "patch-bytes": case "assemble": {
|
|
231
|
+
byte[] desired=operation.equals("assemble")?assembly(program,p):hex(text(p,"bytes_hex"));
|
|
232
|
+
Address address=at(program,text(p,"address"));require(Arrays.equals(bytes(program,address,desired.length),desired),"Saved replacement bytes differ");
|
|
233
|
+
return Map.of("verified",true,"address",address.toString(),"bytes_hex",HexFormat.of().formatHex(desired),"size",desired.length);
|
|
234
|
+
}
|
|
235
|
+
case "move-block": {
|
|
236
|
+
MemoryBlock value=block(program,text(p,"name"));require(value.getStart().equals(at(program,text(p,"address")))&&value.getSize()==p.get("expect_size").getAsLong(),"Saved moved layout differs");
|
|
237
|
+
require(hash(program,value.getStart(),value.getSize()).equals(text(p,"expect_sha256")),"Saved moved bytes differ");return blockState(program,value);
|
|
238
|
+
}
|
|
239
|
+
case "split-block": {
|
|
240
|
+
MemoryBlock first=block(program,text(p,"name")),second=block(program,text(p,"new_name"));
|
|
241
|
+
require(first.getStart().equals(at(program,text(p,"expect_start")))&&second.getStart().equals(at(program,text(p,"split_address")))&&first.getEnd().next().equals(second.getStart())&&first.getSize()+second.getSize()==p.get("expect_size").getAsLong(),"Saved split layout differs");
|
|
242
|
+
require(hash(program,first.getStart(),first.getSize()+second.getSize()).equals(text(p,"expect_sha256")),"Saved split bytes differ");
|
|
243
|
+
return Map.of("first",blockState(program,first),"second",blockState(program,second));
|
|
244
|
+
}
|
|
245
|
+
case "join-blocks": {
|
|
246
|
+
MemoryBlock value=block(program,text(p,"name"));long size=p.get("expect_size").getAsLong(),other=p.get("second_size").getAsLong();
|
|
247
|
+
require(value.getSize()==size+other&&Arrays.stream(program.getMemory().getBlocks()).noneMatch(item->item.getName().equals(text(p,"second_name"))),"Saved joined layout differs");
|
|
248
|
+
require(hash(program,value.getStart(),size).equals(text(p,"expect_sha256"))&&hash(program,value.getStart().add(size),other).equals(text(p,"second_sha256")),"Saved joined bytes differ");return blockState(program,value);
|
|
249
|
+
}
|
|
250
|
+
case "context-set": case "context-clear": {
|
|
251
|
+
Register register=program.getRegister(text(p,"register"));Address start=at(program,text(p,"address")),end=at(program,text(p,"end_address"));
|
|
252
|
+
var context=program.getProgramContext();BigInteger expected=operation.equals("context-clear")?null:new BigInteger(text(p,"value").replaceFirst("^0x",""),16);
|
|
253
|
+
Address cursor=start;
|
|
254
|
+
for(int visited=0;cursor.compareTo(end)<=0;visited++){
|
|
255
|
+
if(visited>=65536)throw new IllegalArgumentException("Context verification exceeds 65536 bytes");
|
|
256
|
+
if(expected==null){var explicit=context.getNonDefaultValue(register,cursor);require(explicit==null||!explicit.hasAnyValue(),"Saved explicit processor context remains");}
|
|
257
|
+
else require(Objects.equals(context.getValue(register,cursor,false),expected),"Saved processor context differs");cursor=cursor.next();
|
|
258
|
+
}
|
|
259
|
+
return Map.of("verified",true,"register",register.getName(),"start",start.toString(),"end",end.toString());
|
|
260
|
+
}
|
|
261
|
+
case "namespace-create": case "namespace-update": {
|
|
262
|
+
String name=operation.equals("namespace-create")?text(p,"name"):text(p,"new_name"),parent=text(p,"parent");
|
|
263
|
+
Namespace value=namespace(program,parent.isEmpty()?name:parent+"::"+name);
|
|
264
|
+
if(operation.equals("namespace-create"))require(value.getSymbol().getSymbolType()==(p.has("class")&&p.get("class").getAsBoolean()?SymbolType.CLASS:SymbolType.NAMESPACE),"Saved namespace kind differs");return namespaceState(value);
|
|
265
|
+
}
|
|
266
|
+
case "namespace-delete": {
|
|
267
|
+
String path=text(p,"path");int i=path.lastIndexOf("::");Namespace parent=namespace(program,i<0?"":path.substring(0,i));
|
|
268
|
+
require(program.getSymbolTable().getNamespace(i<0?path:path.substring(i+2),parent)==null,"Saved namespace still exists");return Map.of("deleted",true);
|
|
269
|
+
}
|
|
270
|
+
case "library-create": case "library-update": {
|
|
271
|
+
String name=p.has("new_name")?text(p,"new_name"):text(p,"name");var manager=program.getExternalManager();require(manager.contains(name),"Saved library is absent");
|
|
272
|
+
if(p.has("path"))require(Objects.equals(manager.getExternalLibraryPath(name),text(p,"path")),"Saved library path differs");
|
|
273
|
+
return Map.of("name",name,"path",Objects.toString(manager.getExternalLibraryPath(name),""));
|
|
274
|
+
}
|
|
275
|
+
case "library-delete": require(!program.getExternalManager().contains(text(p,"name")),"Saved library still exists");return Map.of("deleted",true);
|
|
276
|
+
case "external-create": case "external-update": {
|
|
277
|
+
String label=p.has("new_label")?text(p,"new_label"):text(p,"label");var value=program.getExternalManager().getUniqueExternalLocation(text(p,"library"),label);
|
|
278
|
+
require(value!=null,"Saved external location is absent or ambiguous");
|
|
279
|
+
if(p.has("address"))require(at(program,text(p,"address")).equals(value.getAddress()),"Saved external address differs");
|
|
280
|
+
if(p.has("function"))require(value.isFunction()==p.get("function").getAsBoolean(),"Saved external kind differs");return externalState(value);
|
|
281
|
+
}
|
|
282
|
+
case "external-delete": require(program.getExternalManager().getExternalLocations(text(p,"library"),text(p,"label")).isEmpty(),"Saved external location remains");return Map.of("deleted",true);
|
|
283
|
+
default:throw new IllegalArgumentException("Unknown saved SDK verification");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
static Map<String,Object> varnode(Varnode value) {
|
|
287
|
+
return Map.of("space",value.getAddress().getAddressSpace().getName(),"offset","0x"+Long.toUnsignedString(value.getOffset(),16),"size",value.getSize(),"unique_id",value instanceof VarnodeAST ast?ast.getUniqueId():-1);
|
|
288
|
+
}
|
|
289
|
+
static Object decompile(Program program,JsonObject p,TaskMonitor monitor) throws Exception {
|
|
290
|
+
Function function=program.getFunctionManager().getFunctionAt(at(program,text(p,"address")));
|
|
291
|
+
if(function==null)throw new IllegalArgumentException("No function starts at this address");
|
|
292
|
+
String style=p.has("simplification")?text(p,"simplification"):"decompile";
|
|
293
|
+
if(!List.of("decompile","normalize","firstpass","register","paramid").contains(style))throw new IllegalArgumentException("Unknown simplification style");
|
|
294
|
+
int limit=integer(p,"max_ops",1000,10000),seconds=integer(p,"timeout_seconds",30,120);
|
|
295
|
+
DecompInterface decompiler=new DecompInterface();
|
|
296
|
+
try {
|
|
297
|
+
DecompileOptions options=new DecompileOptions();options.grabFromProgram(program);options.setMaxPayloadMBytes(8);options.setMaxInstructions(200000);
|
|
298
|
+
if(p.has("options")){JsonObject values=p.getAsJsonObject("options");for(String key:values.keySet()){
|
|
299
|
+
switch(key){case "eliminate_unreachable":options.setEliminateUnreachable(values.get(key).getAsBoolean());break;
|
|
300
|
+
case "respect_read_only":options.setRespectReadOnly(values.get(key).getAsBoolean());break;
|
|
301
|
+
case "infer_constant_pointers":options.setInferConstantPointers(values.get(key).getAsBoolean());break;
|
|
302
|
+
case "split_structures":options.setSplitStructures(values.get(key).getAsBoolean());break;
|
|
303
|
+
case "split_arrays":options.setSplitArrays(values.get(key).getAsBoolean());break;
|
|
304
|
+
default:throw new IllegalArgumentException("Unknown decompiler option");}}}
|
|
305
|
+
decompiler.setOptions(options);decompiler.setSimplificationStyle(style);decompiler.toggleSyntaxTree(true);
|
|
306
|
+
decompiler.toggleCCode(p.has("c_code")&&p.get("c_code").getAsBoolean());
|
|
307
|
+
require(decompiler.openProgram(program),"Decompiler could not open the program");
|
|
308
|
+
DecompileResults result=decompiler.decompileFunction(function,seconds,monitor);
|
|
309
|
+
if(!result.decompileCompleted())return Map.of("completed",false,"timed_out",result.isTimedOut(),"diagnostic",result.getErrorMessage(),"operations",List.of());
|
|
310
|
+
HighFunction high=result.getHighFunction();List<Object> rows=new ArrayList<>();var iter=high.getPcodeOps();
|
|
311
|
+
while(iter.hasNext()&&rows.size()<limit){monitor.checkCancelled();PcodeOpAST op=iter.next();List<Object> inputs=new ArrayList<>();for(Varnode input:op.getInputs())inputs.add(varnode(input));
|
|
312
|
+
Map<String,Object> row=new LinkedHashMap<>();row.put("opcode",op.getMnemonic());row.put("address",op.getSeqnum().getTarget().toString());row.put("order",op.getSeqnum().getTime());
|
|
313
|
+
row.put("block",op.getParent().getIndex());row.put("inputs",inputs);row.put("output",op.getOutput()==null?null:varnode(op.getOutput()));rows.add(row);}
|
|
314
|
+
Map<String,Object> out=new LinkedHashMap<>();out.put("completed",true);out.put("operations",rows);out.put("ir_complete",!iter.hasNext());out.put("simplification",style);
|
|
315
|
+
out.put("language",program.getLanguageID().toString());out.put("address",function.getEntryPoint().toString());out.put("diagnostic",result.getErrorMessage());
|
|
316
|
+
if(p.has("c_code")&&p.get("c_code").getAsBoolean()){String c=result.getDecompiledFunction().getC();out.put("c_code",c.substring(0,Math.min(c.length(),131072)));out.put("c_complete",c.length()<=131072);}
|
|
317
|
+
return out;
|
|
318
|
+
} finally {decompiler.dispose();}
|
|
319
|
+
}
|
|
320
|
+
public static Object compare(Program first,Program second,JsonObject p,TaskMonitor monitor) throws Exception {
|
|
321
|
+
Map<String,Integer> categories=new LinkedHashMap<>();categories.put("bytes",ProgramDiffFilter.BYTE_DIFFS);categories.put("code_units",ProgramDiffFilter.CODE_UNIT_DIFFS);
|
|
322
|
+
categories.put("comments",ProgramDiffFilter.COMMENT_DIFFS);categories.put("symbols",ProgramDiffFilter.SYMBOL_DIFFS);categories.put("functions",ProgramDiffFilter.FUNCTION_DIFFS);
|
|
323
|
+
categories.put("references",ProgramDiffFilter.REFERENCE_DIFFS);categories.put("context",ProgramDiffFilter.PROGRAM_CONTEXT_DIFFS);categories.put("properties",ProgramDiffFilter.USER_DEFINED_DIFFS);
|
|
324
|
+
JsonArray requested=p.has("categories")?p.getAsJsonArray("categories"):new Gson().toJsonTree(List.of("bytes","comments","symbols","functions")).getAsJsonArray();
|
|
325
|
+
if(requested.isEmpty()||requested.size()>categories.size())throw new IllegalArgumentException("Select 1–8 diff categories");
|
|
326
|
+
int limit=integer(p,"max_ranges",100,1000),seconds=integer(p,"timeout_seconds",30,120);List<Object> rows=new ArrayList<>();boolean complete=true;
|
|
327
|
+
TimeoutTaskMonitor bounded=TimeoutTaskMonitor.timeoutIn(seconds,TimeUnit.SECONDS,monitor);
|
|
328
|
+
try {
|
|
329
|
+
ProgramDiff diff=new ProgramDiff(first,second);
|
|
330
|
+
for(JsonElement item:requested){String category=item.getAsString();Integer filter=categories.get(category);if(filter==null)throw new IllegalArgumentException("Unknown diff category");
|
|
331
|
+
AddressSetView differences=diff.getDifferences(new ProgramDiffFilter(filter),bounded);List<Object> ranges=new ArrayList<>();var iterator=differences.getAddressRanges();
|
|
332
|
+
while(iterator.hasNext()&&ranges.size()<limit){AddressRange range=iterator.next();ranges.add(Map.of("start",range.getMinAddress().toString(),"end",range.getMaxAddress().toString(),"length",Long.toUnsignedString(range.getLength())));}
|
|
333
|
+
boolean categoryComplete=!iterator.hasNext();complete&=categoryComplete;rows.add(Map.of("category",category,"ranges",ranges,"complete",categoryComplete));}
|
|
334
|
+
} finally {bounded.finished();}
|
|
335
|
+
return Map.of("algorithm","ghidra.ProgramDiff","categories",rows,"complete",complete,"read_only",true,
|
|
336
|
+
"memory_only_first",first.getMemory().subtract(second.getMemory()).toString(),"memory_only_second",second.getMemory().subtract(first.getMemory()).toString());
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
package insight;
|
|
2
|
+
|
|
3
|
+
import com.google.gson.*;
|
|
4
|
+
import com.xebyte.core.ServiceUtils;
|
|
5
|
+
import ghidra.docking.settings.*;
|
|
6
|
+
import ghidra.program.model.data.*;
|
|
7
|
+
import ghidra.program.model.listing.Program;
|
|
8
|
+
import ghidra.util.task.TaskMonitor;
|
|
9
|
+
import java.nio.file.*;
|
|
10
|
+
import java.util.*;
|
|
11
|
+
import java.math.BigInteger;
|
|
12
|
+
|
|
13
|
+
/** Native GDT/layout/settings workflows, including bitfields rather than C text substitutes. */
|
|
14
|
+
public class InsightSdkTypes {
|
|
15
|
+
static String text(JsonObject p,String key){return InsightSdkTools.text(p,key);}
|
|
16
|
+
static DataType type(Program program,String path){DataType value=program.getDataTypeManager().getDataType(path);
|
|
17
|
+
if(value==null)throw new IllegalArgumentException("Explicit type path is absent");return value;}
|
|
18
|
+
static DataType resolve(Program program,DataTypeManager manager,String name)throws Exception{
|
|
19
|
+
DataType source=program.getDataTypeManager().getDataType(name);
|
|
20
|
+
if(source==null)source=ServiceUtils.findDataTypeByNameInAllCategories(program.getDataTypeManager(),name);
|
|
21
|
+
DataType value=source==null?ServiceUtils.resolveDataType(manager,name):manager.resolve(source,DataTypeConflictHandler.DEFAULT_HANDLER);
|
|
22
|
+
if(value==null)throw new IllegalArgumentException("Field type could not be resolved");return value;
|
|
23
|
+
}
|
|
24
|
+
static List<Object> settings(DataType type){List<Object> rows=new ArrayList<>();Settings values=type.getDefaultSettings();
|
|
25
|
+
for(SettingsDefinition definition:type.getSettingsDefinitions()){
|
|
26
|
+
Map<String,Object> row=new LinkedHashMap<>();row.put("name",definition.getName());row.put("definition",definition.getClass().getSimpleName());row.put("explicit",definition.hasValue(values));
|
|
27
|
+
if(definition instanceof EnumSettingsDefinition enumeration){row.put("value",enumeration.getChoice(values));row.put("choices",enumeration.getDisplayChoices(values));}
|
|
28
|
+
else if(definition instanceof StringSettingsDefinition string)row.put("value",string.getValue(values));
|
|
29
|
+
else if(definition instanceof BooleanSettingsDefinition bool)row.put("value",bool.getValue(values));
|
|
30
|
+
else if(definition instanceof NumberSettingsDefinition number)row.put("value",Long.toString(number.getValue(values)));
|
|
31
|
+
else row.put("value",definition.getValueString(values));rows.add(row);}
|
|
32
|
+
return rows;
|
|
33
|
+
}
|
|
34
|
+
public static Object layout(DataType value){
|
|
35
|
+
if(value==null)throw new IllegalArgumentException("Explicit type path is absent");
|
|
36
|
+
Map<String,Object> row=new LinkedHashMap<>();row.put("path",value.getPathName());row.put("size",value.getLength());row.put("alignment",value.getAlignment());row.put("settings",settings(value));
|
|
37
|
+
var source=value.getSourceArchive();row.put("source_archive",source==null?null:Map.of("id",source.getSourceArchiveID().toString(),"name",source.getName(),"type",source.getArchiveType().toString()));
|
|
38
|
+
row.put("universal_id",value.getUniversalID()==null?null:value.getUniversalID().toString());
|
|
39
|
+
if(value instanceof Composite composite){
|
|
40
|
+
row.put("packing",composite.getPackingType().toString());row.put("packing_value",composite.getExplicitPackingValue());row.put("minimum_alignment",composite.getExplicitMinimumAlignment());
|
|
41
|
+
List<Object> fields=new ArrayList<>();int total=composite.getNumDefinedComponents();
|
|
42
|
+
DataTypeComponent[] components=total<=10000?composite.getDefinedComponents():new DataTypeComponent[0];
|
|
43
|
+
for(DataTypeComponent field:components){if(fields.size()>=256)break;
|
|
44
|
+
Map<String,Object> item=new LinkedHashMap<>();item.put("name",field.getFieldName());item.put("type",field.getDataType().getPathName());item.put("offset",field.getOffset());item.put("size",field.getLength());item.put("comment",field.getComment());
|
|
45
|
+
if(field.getDataType() instanceof BitFieldDataType bit){item.put("bit_size",bit.getDeclaredBitSize());item.put("bit_offset",bit.getBitOffset());item.put("base_type",bit.getBaseDataType().getPathName());}fields.add(item);}
|
|
46
|
+
row.put("fields",fields);row.put("fields_total",total);row.put("fields_complete",fields.size()==total);}
|
|
47
|
+
return row;
|
|
48
|
+
}
|
|
49
|
+
static Structure structure(Program program,DataTypeManager manager,JsonObject p)throws Exception{
|
|
50
|
+
String path=text(p,"path");int slash=path.lastIndexOf('/');
|
|
51
|
+
if(slash<0||slash==path.length()-1)throw new IllegalArgumentException("Structure requires an absolute type path");
|
|
52
|
+
Structure structure=new StructureDataType(new CategoryPath(slash==0?"/":path.substring(0,slash)),path.substring(slash+1),0,manager);
|
|
53
|
+
int packing=p.has("packing")?p.get("packing").getAsInt():0;
|
|
54
|
+
if(packing<0)structure.setPackingEnabled(false);else if(packing==0)structure.setToDefaultPacking();else structure.setExplicitPackingValue(packing);
|
|
55
|
+
int alignment=p.has("alignment")?p.get("alignment").getAsInt():0;
|
|
56
|
+
if(alignment<0)structure.setToMachineAligned();else if(alignment==0)structure.setToDefaultAligned();else structure.setExplicitMinimumAlignment(alignment);
|
|
57
|
+
JsonArray fields=p.getAsJsonArray("fields");if(fields==null||fields.isEmpty()||fields.size()>256)throw new IllegalArgumentException("Structure requires 1–256 fields");
|
|
58
|
+
Set<String> names=new HashSet<>();for(JsonElement element:fields){JsonObject field=element.getAsJsonObject();String name=text(field,"name");
|
|
59
|
+
if(name.isEmpty()||!names.add(name))throw new IllegalArgumentException("Field names must be nonempty and unique");
|
|
60
|
+
DataType type=resolve(program,manager,text(field,"type"));String comment=field.has("comment")?text(field,"comment"):null;
|
|
61
|
+
if(field.has("offset")){
|
|
62
|
+
if(structure.isPackingEnabled())throw new IllegalArgumentException("Explicit offsets require disabled packing");int offset=field.get("offset").getAsInt();
|
|
63
|
+
if(offset<0||offset>1048576)throw new IllegalArgumentException("Field offset exceeds layout bound");
|
|
64
|
+
if(field.has("bit_size")){int width=field.has("byte_width")?field.get("byte_width").getAsInt():type.getLength(),bit=field.has("bit_offset")?field.get("bit_offset").getAsInt():0;
|
|
65
|
+
structure.insertBitFieldAt(offset,width,bit,type,field.get("bit_size").getAsInt(),name,comment);}
|
|
66
|
+
else {if(offset<structure.getLength())throw new IllegalArgumentException("Explicit fields cannot overlap");structure.growStructure(offset-structure.getLength());structure.add(type,name,comment);}
|
|
67
|
+
}else if(field.has("bit_size"))structure.addBitField(type,field.get("bit_size").getAsInt(),name,comment);
|
|
68
|
+
else structure.add(type,name,comment);
|
|
69
|
+
}return structure;
|
|
70
|
+
}
|
|
71
|
+
public static void edit(Program program,String operation,JsonObject p,TaskMonitor monitor)throws Exception{
|
|
72
|
+
switch(operation){
|
|
73
|
+
case "type-structure": {
|
|
74
|
+
var manager=program.getDataTypeManager();DataType existing=manager.getDataType(text(p,"path"));
|
|
75
|
+
if(existing!=null&&(!p.has("replace_placeholder")||!p.get("replace_placeholder").getAsBoolean()||!(existing instanceof Structure s)||s.getNumDefinedComponents()!=0))throw new IllegalArgumentException("Existing type is not an explicitly replaceable empty structure");
|
|
76
|
+
Structure wanted=structure(program,manager,p);manager.addDataType(wanted,existing==null?DataTypeConflictHandler.DEFAULT_HANDLER:DataTypeConflictHandler.REPLACE_HANDLER);break;
|
|
77
|
+
}
|
|
78
|
+
case "type-settings": {
|
|
79
|
+
DataType type=type(program,text(p,"path"));JsonObject changes=p.getAsJsonObject("settings");if(changes==null||changes.size()>32)throw new IllegalArgumentException("Settings require at most 32 definition names");
|
|
80
|
+
Map<String,SettingsDefinition> definitions=new HashMap<>();for(var definition:type.getSettingsDefinitions())definitions.put(definition.getName(),definition);
|
|
81
|
+
for(String name:changes.keySet()){SettingsDefinition definition=definitions.get(name);if(definition==null)throw new IllegalArgumentException("Unknown native setting definition name");JsonElement value=changes.get(name);Settings settings=type.getDefaultSettings();
|
|
82
|
+
if(value.isJsonNull())definition.clear(settings);
|
|
83
|
+
else if(definition instanceof EnumSettingsDefinition enumeration){if(!value.isJsonPrimitive()||!value.getAsJsonPrimitive().isNumber())throw new IllegalArgumentException("Native enum setting requires an integer choice");int choice=value.getAsBigDecimal().intValueExact();if(choice<0||choice>=enumeration.getDisplayChoices(settings).length)throw new IllegalArgumentException("Setting choice is outside native enumeration");enumeration.setChoice(settings,choice);}
|
|
84
|
+
else if(definition instanceof StringSettingsDefinition string){if(!value.isJsonPrimitive()||!value.getAsJsonPrimitive().isString())throw new IllegalArgumentException("Native string setting requires text");string.setValue(settings,value.getAsString());}
|
|
85
|
+
else if(definition instanceof BooleanSettingsDefinition bool){if(!value.isJsonPrimitive()||!value.getAsJsonPrimitive().isBoolean())throw new IllegalArgumentException("Native boolean setting requires a Boolean");bool.setValue(settings,value.getAsBoolean());}
|
|
86
|
+
else if(definition instanceof NumberSettingsDefinition number){BigInteger n=new BigInteger(value.getAsString());if(n.signum()<0&&!number.allowNegativeValue()||n.compareTo(number.getMaxValue())>0||n.bitLength()>63)throw new IllegalArgumentException("Numeric setting exceeds native bounds");number.setValue(settings,n.longValueExact());}
|
|
87
|
+
else throw new IllegalArgumentException("Native setting has no supported typed setter");}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "type-archive-import": {
|
|
91
|
+
try(FileDataTypeManager archive=FileDataTypeManager.openFileArchive(Path.of(text(p,"input_path")).toFile(),false)){
|
|
92
|
+
JsonArray names=p.getAsJsonArray("types");if(names==null||names.isEmpty()||names.size()>256)throw new IllegalArgumentException("Choose 1–256 archive type paths");
|
|
93
|
+
var manager=program.getDataTypeManager();for(JsonElement name:names){DataType wanted=archive.getDataType(name.getAsString());if(wanted==null)throw new IllegalArgumentException("Archive type path is absent");
|
|
94
|
+
DataType existing=manager.getDataType(name.getAsString());if(existing!=null&&!existing.isEquivalent(wanted))throw new IllegalArgumentException("Existing type conflicts with the selected archive layout");
|
|
95
|
+
manager.resolve(wanted,DataTypeConflictHandler.DEFAULT_HANDLER);}
|
|
96
|
+
}break;
|
|
97
|
+
}
|
|
98
|
+
default:throw new IllegalArgumentException("Unknown type workflow");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
public static Object verify(Program program,String operation,JsonObject p,TaskMonitor monitor)throws Exception{
|
|
102
|
+
switch(operation){
|
|
103
|
+
case "type-structure": {
|
|
104
|
+
DataType actual=type(program,text(p,"path"));try(var expected=new StandAloneDataTypeManager("LayoutVerification",program.getDataTypeManager().getDataOrganization())){
|
|
105
|
+
int transaction=expected.startTransaction("Build expected layout");try{
|
|
106
|
+
Structure wanted=structure(program,expected,p);InsightSdkTools.require(actual instanceof Structure&&actual.isEquivalent(wanted),"Saved structure fields/packing/bitfields differ");
|
|
107
|
+
InsightSdkTools.require(actual.getLength()==wanted.getLength()&&actual.getAlignment()==wanted.getAlignment(),"Saved structure size/alignment differs");return layout(actual);
|
|
108
|
+
}finally{expected.endTransaction(transaction,false);}}
|
|
109
|
+
}
|
|
110
|
+
case "type-settings": {
|
|
111
|
+
DataType actual=type(program,text(p,"path"));JsonObject changes=p.getAsJsonObject("settings");Map<String,SettingsDefinition> definitions=new HashMap<>();for(var definition:actual.getSettingsDefinitions())definitions.put(definition.getName(),definition);
|
|
112
|
+
Settings settings=actual.getDefaultSettings();for(String name:changes.keySet()){SettingsDefinition definition=definitions.get(name);InsightSdkTools.require(definition!=null,"Saved setting definition disappeared");JsonElement expected=changes.get(name);
|
|
113
|
+
if(expected.isJsonNull())InsightSdkTools.require(!definition.hasValue(settings),"Saved explicit setting remains");
|
|
114
|
+
else if(definition instanceof EnumSettingsDefinition enumeration)InsightSdkTools.require(enumeration.getChoice(settings)==expected.getAsInt(),"Saved enum setting differs");
|
|
115
|
+
else if(definition instanceof BooleanSettingsDefinition bool)InsightSdkTools.require(bool.getValue(settings)==expected.getAsBoolean(),"Saved boolean setting differs");
|
|
116
|
+
else if(definition instanceof StringSettingsDefinition string)InsightSdkTools.require(Objects.equals(string.getValue(settings),expected.getAsString()),"Saved string setting differs");
|
|
117
|
+
else if(definition instanceof NumberSettingsDefinition number)InsightSdkTools.require(Long.toString(number.getValue(settings)).equals(expected.getAsString()),"Saved number setting differs");
|
|
118
|
+
else throw new IllegalArgumentException("Unsupported saved setting definition");}
|
|
119
|
+
return layout(actual);
|
|
120
|
+
}
|
|
121
|
+
case "type-archive-import": {
|
|
122
|
+
try(FileDataTypeManager archive=FileDataTypeManager.openFileArchive(Path.of(text(p,"input_path")).toFile(),false)){
|
|
123
|
+
List<Object> rows=new ArrayList<>();for(JsonElement name:p.getAsJsonArray("types")){DataType wanted=archive.getDataType(name.getAsString()),actual=type(program,name.getAsString());
|
|
124
|
+
InsightSdkTools.require(wanted!=null&&actual.isEquivalent(wanted),"Saved archive layout differs");
|
|
125
|
+
InsightSdkTools.require(actual.getSourceArchive()!=null&&actual.getSourceArchive().getSourceArchiveID().equals(archive.getUniversalID()),"Saved archive source identity differs");rows.add(layout(actual));}
|
|
126
|
+
return Map.of("archive_id",archive.getUniversalID().toString(),"types",rows);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
default:throw new IllegalArgumentException("Unknown saved type workflow");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import com.google.gson.*;
|
|
2
|
+
import com.xebyte.headless.GhidraServerManager;
|
|
3
|
+
import ghidra.GhidraApplicationLayout;
|
|
4
|
+
import ghidra.framework.Application;
|
|
5
|
+
import ghidra.framework.HeadlessGhidraApplicationConfiguration;
|
|
6
|
+
import ghidra.framework.client.RepositoryAdapter;
|
|
7
|
+
import ghidra.framework.remote.RepositoryItem;
|
|
8
|
+
import ghidra.framework.remote.User;
|
|
9
|
+
import java.nio.file.*;
|
|
10
|
+
import java.util.*;
|
|
11
|
+
|
|
12
|
+
/** Explicit repository administration with server-side authority and independent effect readback. */
|
|
13
|
+
class InsightServerAdministration {
|
|
14
|
+
static String string(JsonObject value,String name){return value.has(name)?value.get(name).getAsString():"";}
|
|
15
|
+
static String parent(String path){int i=path.lastIndexOf('/');return i>0?path.substring(0,i):"/";}
|
|
16
|
+
static String leaf(String path){return path.substring(path.lastIndexOf('/')+1);}
|
|
17
|
+
// RepositoryItem.getPathName() prepends another slash even when its parent is root.
|
|
18
|
+
static String canonicalPath(RepositoryItem item){return (item.getParentPath().equals("/")?"":item.getParentPath())+"/"+item.getName();}
|
|
19
|
+
static Map<String,Object> file(RepositoryItem item){return Map.of("name",item.getName(),"path",canonicalPath(item),"type",item.getContentType(),"version",item.getVersion());}
|
|
20
|
+
static void admin(RepositoryAdapter repo)throws Exception{if(!repo.getUser().isAdmin())throw new SecurityException("Repository administration requires actual admin permission");}
|
|
21
|
+
static List<RepositoryItem> files(RepositoryAdapter repo,String at)throws Exception{
|
|
22
|
+
List<RepositoryItem> out=new ArrayList<>();RepositoryItem exact=at.equals("/")?null:repo.getItem(parent(at),leaf(at));
|
|
23
|
+
if(exact!=null){out.add(exact);return out;}
|
|
24
|
+
var pending=new ArrayDeque<String>();pending.add(at);int folders=0;
|
|
25
|
+
while(!pending.isEmpty()){if(++folders>10000)throw new IllegalArgumentException("Repository walk exceeds 10000 folders");String folder=pending.remove();
|
|
26
|
+
var items=repo.getItemList(folder);if(items!=null)Collections.addAll(out,items);
|
|
27
|
+
if(out.size()>10000)throw new IllegalArgumentException("Repository walk exceeds 10000 files");
|
|
28
|
+
var children=repo.getSubfolderList(folder);if(children!=null)for(String child:children)pending.add((folder.equals("/")?"":folder)+"/"+child);}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
public static void main(String[] args){var output=System.out;System.setOut(System.err);
|
|
32
|
+
try{
|
|
33
|
+
if(args.length!=1)throw new IllegalArgumentException();String encoded=Files.readString(Path.of(args[0]));if(encoded.length()>65536)throw new IllegalArgumentException();
|
|
34
|
+
JsonObject request=JsonParser.parseString(encoded).getAsJsonObject();String operation=string(request,"operation"),name=string(request,"repository"),path=string(request,"path");
|
|
35
|
+
Application.initializeApplication(new GhidraApplicationLayout(),new HeadlessGhidraApplicationConfiguration());
|
|
36
|
+
var manager=new GhidraServerManager();manager.connect();var server=manager.server();if(server==null||!server.isConnected())throw new SecurityException("Server connection missing");
|
|
37
|
+
Map<String,Object> result=new LinkedHashMap<>();
|
|
38
|
+
try{
|
|
39
|
+
if(operation.equals("repositories")){var names=server.getRepositoryNames();result.put("repositories",Arrays.asList(names));result.put("count",names.length);}
|
|
40
|
+
else if(operation.equals("users")){var users=server.getAllUsers();result.put("users",Arrays.stream(users).map(user->Map.of("name",user)).toList());result.put("count",users.length);}
|
|
41
|
+
else if(operation.equals("create_repository")){
|
|
42
|
+
server.createRepository(name).connect();if(!Arrays.asList(server.getRepositoryNames()).contains(name))throw new IllegalStateException("Created repository missing");
|
|
43
|
+
result.put("state","created");result.put("repository",name);
|
|
44
|
+
}else{
|
|
45
|
+
var repo=server.getRepository(name);repo.connect();try{
|
|
46
|
+
result.put("repository",name);
|
|
47
|
+
if(operation.equals("files")){var folders=repo.getSubfolderList(path);var items=repo.getItemList(path);
|
|
48
|
+
result.put("path",path);result.put("folders",folders==null?List.of():Arrays.asList(folders));result.put("files",items==null?List.of():Arrays.stream(items).map(InsightServerAdministration::file).toList());}
|
|
49
|
+
else if(operation.equals("file")){var item=repo.getItem(parent(path),leaf(path));if(item==null)throw new IllegalArgumentException("Repository file missing");result.put("file",file(item));}
|
|
50
|
+
else if(operation.equals("permissions")){result.put("users",Arrays.stream(repo.getUserList()).map(user->Map.of("name",user.getName(),"access",user.getPermissionType())).toList());result.put("anonymous",repo.anonymousAccessAllowed());}
|
|
51
|
+
else if(operation.equals("set_permissions")){
|
|
52
|
+
admin(repo);String user=string(request,"user");int level=request.get("access").getAsInt();if(level<0||level>2)throw new IllegalArgumentException();
|
|
53
|
+
var before=new LinkedHashMap<String,Integer>();for(var item:repo.getUserList())before.put(item.getName(),item.getPermissionType());
|
|
54
|
+
boolean anonymous=repo.anonymousAccessAllowed();before.put(user,level);repo.setUserList(before.entrySet().stream().map(item->new User(item.getKey(),item.getValue())).toArray(User[]::new),anonymous);
|
|
55
|
+
var after=new LinkedHashMap<String,Integer>();for(var item:repo.getUserList())after.put(item.getName(),item.getPermissionType());
|
|
56
|
+
if(!before.equals(after)||repo.anonymousAccessAllowed()!=anonymous)throw new IllegalStateException("Repository ACL readback mismatch");
|
|
57
|
+
result.put("state","applied");result.put("users",after);result.put("anonymous",anonymous);
|
|
58
|
+
}else if(operation.equals("history")){var versions=repo.getVersions(parent(path),leaf(path));
|
|
59
|
+
result.put("path",path);result.put("versions",Arrays.stream(versions).map(version->Map.of("version",version.getVersion(),"user",version.getUser(),"time",version.getCreateTime(),"comment",Objects.toString(version.getComment(),""))).toList());}
|
|
60
|
+
else if(Set.of("checkouts","terminate_checkout","terminate_all_checkouts").contains(operation)){
|
|
61
|
+
boolean changing=!operation.equals("checkouts");if(changing)admin(repo);List<Object> rows=new ArrayList<>();int terminated=0;
|
|
62
|
+
var selected=files(repo,path);
|
|
63
|
+
if(changing)for(var item:selected){String itemPath=canonicalPath(item);
|
|
64
|
+
if(request.has("protected_repository")&&name.equals(string(request,"protected_repository"))&&itemPath.matches("/t_[a-f0-9-]+\\.bin"))throw new SecurityException("Facade publication checkout is protected");
|
|
65
|
+
if(repo.isCheckinActive(parent(itemPath),leaf(itemPath)))throw new IllegalStateException("File has an active check-in");}
|
|
66
|
+
for(var item:selected){String itemPath=canonicalPath(item);
|
|
67
|
+
var statuses=repo.getCheckouts(parent(itemPath),leaf(itemPath));if(statuses==null)continue;
|
|
68
|
+
for(var checkout:statuses){String id=Long.toString(checkout.getCheckoutId());
|
|
69
|
+
if(changing){if(operation.equals("terminate_checkout")&&request.has("checkout_id")&&!id.equals(string(request,"checkout_id")))continue;
|
|
70
|
+
repo.terminateCheckout(parent(itemPath),leaf(itemPath),checkout.getCheckoutId(),false);
|
|
71
|
+
if(repo.getCheckout(parent(itemPath),leaf(itemPath),checkout.getCheckoutId())!=null)throw new IllegalStateException("Checkout remains after termination");terminated++;}
|
|
72
|
+
else rows.add(Map.of("path",itemPath,"id",id,"user",checkout.getUser(),"version",checkout.getCheckoutVersion(),"type",checkout.getCheckoutType().toString()));}
|
|
73
|
+
}
|
|
74
|
+
result.put(changing?"terminated":"checkouts",changing?terminated:rows);result.put("path",path);
|
|
75
|
+
}else throw new IllegalArgumentException("Unsupported server administration operation");
|
|
76
|
+
}finally{repo.disconnect();}
|
|
77
|
+
}
|
|
78
|
+
}finally{server.disconnect();}
|
|
79
|
+
output.println(new GsonBuilder().serializeNulls().create().toJson(result));System.exit(0);
|
|
80
|
+
}catch(Throwable failure){failure.printStackTrace(System.err);output.println("{\"error\":\"server_administration_not_verified\"}");System.exit(1);}
|
|
81
|
+
}
|
|
82
|
+
}
|