windows-exe-decompiler-mcp-server 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.
Files changed (190) hide show
  1. package/CODEX_INSTALLATION.md +69 -0
  2. package/COPILOT_INSTALLATION.md +77 -0
  3. package/LICENSE +21 -0
  4. package/README.md +314 -0
  5. package/bin/windows-exe-decompiler-mcp-server.js +3 -0
  6. package/dist/analysis-provenance.d.ts +184 -0
  7. package/dist/analysis-provenance.js +74 -0
  8. package/dist/analysis-task-runner.d.ts +31 -0
  9. package/dist/analysis-task-runner.js +160 -0
  10. package/dist/artifact-inventory.d.ts +23 -0
  11. package/dist/artifact-inventory.js +175 -0
  12. package/dist/cache-manager.d.ts +128 -0
  13. package/dist/cache-manager.js +454 -0
  14. package/dist/confidence-semantics.d.ts +66 -0
  15. package/dist/confidence-semantics.js +122 -0
  16. package/dist/config.d.ts +335 -0
  17. package/dist/config.js +193 -0
  18. package/dist/database.d.ts +227 -0
  19. package/dist/database.js +601 -0
  20. package/dist/decompiler-worker.d.ts +441 -0
  21. package/dist/decompiler-worker.js +1962 -0
  22. package/dist/dynamic-trace.d.ts +95 -0
  23. package/dist/dynamic-trace.js +629 -0
  24. package/dist/env-validator.d.ts +15 -0
  25. package/dist/env-validator.js +249 -0
  26. package/dist/error-handler.d.ts +28 -0
  27. package/dist/error-handler.example.d.ts +22 -0
  28. package/dist/error-handler.example.js +141 -0
  29. package/dist/error-handler.js +139 -0
  30. package/dist/ghidra-analysis-status.d.ts +49 -0
  31. package/dist/ghidra-analysis-status.js +178 -0
  32. package/dist/ghidra-config.d.ts +134 -0
  33. package/dist/ghidra-config.js +464 -0
  34. package/dist/index.d.ts +9 -0
  35. package/dist/index.js +200 -0
  36. package/dist/job-queue.d.ts +169 -0
  37. package/dist/job-queue.js +407 -0
  38. package/dist/logger.d.ts +106 -0
  39. package/dist/logger.js +176 -0
  40. package/dist/policy-guard.d.ts +115 -0
  41. package/dist/policy-guard.js +243 -0
  42. package/dist/process-output.d.ts +15 -0
  43. package/dist/process-output.js +90 -0
  44. package/dist/prompts/function-explanation-review.d.ts +5 -0
  45. package/dist/prompts/function-explanation-review.js +64 -0
  46. package/dist/prompts/semantic-name-review.d.ts +5 -0
  47. package/dist/prompts/semantic-name-review.js +63 -0
  48. package/dist/runtime-correlation.d.ts +34 -0
  49. package/dist/runtime-correlation.js +279 -0
  50. package/dist/runtime-paths.d.ts +3 -0
  51. package/dist/runtime-paths.js +11 -0
  52. package/dist/selection-diff.d.ts +667 -0
  53. package/dist/selection-diff.js +53 -0
  54. package/dist/semantic-name-suggestion-artifacts.d.ts +116 -0
  55. package/dist/semantic-name-suggestion-artifacts.js +314 -0
  56. package/dist/server.d.ts +129 -0
  57. package/dist/server.js +578 -0
  58. package/dist/tools/artifact-read.d.ts +235 -0
  59. package/dist/tools/artifact-read.js +317 -0
  60. package/dist/tools/artifacts-diff.d.ts +728 -0
  61. package/dist/tools/artifacts-diff.js +304 -0
  62. package/dist/tools/artifacts-list.d.ts +515 -0
  63. package/dist/tools/artifacts-list.js +389 -0
  64. package/dist/tools/attack-map.d.ts +290 -0
  65. package/dist/tools/attack-map.js +519 -0
  66. package/dist/tools/cache-observability.d.ts +4 -0
  67. package/dist/tools/cache-observability.js +36 -0
  68. package/dist/tools/code-function-cfg.d.ts +50 -0
  69. package/dist/tools/code-function-cfg.js +102 -0
  70. package/dist/tools/code-function-decompile.d.ts +55 -0
  71. package/dist/tools/code-function-decompile.js +103 -0
  72. package/dist/tools/code-function-disassemble.d.ts +43 -0
  73. package/dist/tools/code-function-disassemble.js +185 -0
  74. package/dist/tools/code-function-explain-apply.d.ts +255 -0
  75. package/dist/tools/code-function-explain-apply.js +225 -0
  76. package/dist/tools/code-function-explain-prepare.d.ts +535 -0
  77. package/dist/tools/code-function-explain-prepare.js +276 -0
  78. package/dist/tools/code-function-explain-review.d.ts +397 -0
  79. package/dist/tools/code-function-explain-review.js +589 -0
  80. package/dist/tools/code-function-rename-apply.d.ts +248 -0
  81. package/dist/tools/code-function-rename-apply.js +220 -0
  82. package/dist/tools/code-function-rename-prepare.d.ts +506 -0
  83. package/dist/tools/code-function-rename-prepare.js +279 -0
  84. package/dist/tools/code-function-rename-review.d.ts +574 -0
  85. package/dist/tools/code-function-rename-review.js +761 -0
  86. package/dist/tools/code-functions-list.d.ts +37 -0
  87. package/dist/tools/code-functions-list.js +91 -0
  88. package/dist/tools/code-functions-rank.d.ts +34 -0
  89. package/dist/tools/code-functions-rank.js +90 -0
  90. package/dist/tools/code-functions-reconstruct.d.ts +2725 -0
  91. package/dist/tools/code-functions-reconstruct.js +2807 -0
  92. package/dist/tools/code-functions-search.d.ts +39 -0
  93. package/dist/tools/code-functions-search.js +90 -0
  94. package/dist/tools/code-reconstruct-export.d.ts +1212 -0
  95. package/dist/tools/code-reconstruct-export.js +4002 -0
  96. package/dist/tools/code-reconstruct-plan.d.ts +274 -0
  97. package/dist/tools/code-reconstruct-plan.js +342 -0
  98. package/dist/tools/dotnet-metadata-extract.d.ts +541 -0
  99. package/dist/tools/dotnet-metadata-extract.js +355 -0
  100. package/dist/tools/dotnet-reconstruct-export.d.ts +567 -0
  101. package/dist/tools/dotnet-reconstruct-export.js +1151 -0
  102. package/dist/tools/dotnet-types-list.d.ts +325 -0
  103. package/dist/tools/dotnet-types-list.js +201 -0
  104. package/dist/tools/dynamic-dependencies.d.ts +115 -0
  105. package/dist/tools/dynamic-dependencies.js +213 -0
  106. package/dist/tools/dynamic-memory-import.d.ts +10 -0
  107. package/dist/tools/dynamic-memory-import.js +567 -0
  108. package/dist/tools/dynamic-trace-import.d.ts +10 -0
  109. package/dist/tools/dynamic-trace-import.js +235 -0
  110. package/dist/tools/entrypoint-fallback-disasm.d.ts +30 -0
  111. package/dist/tools/entrypoint-fallback-disasm.js +89 -0
  112. package/dist/tools/ghidra-analyze.d.ts +88 -0
  113. package/dist/tools/ghidra-analyze.js +208 -0
  114. package/dist/tools/ghidra-health.d.ts +37 -0
  115. package/dist/tools/ghidra-health.js +212 -0
  116. package/dist/tools/ioc-export.d.ts +209 -0
  117. package/dist/tools/ioc-export.js +542 -0
  118. package/dist/tools/packer-detect.d.ts +165 -0
  119. package/dist/tools/packer-detect.js +284 -0
  120. package/dist/tools/pe-exports-extract.d.ts +175 -0
  121. package/dist/tools/pe-exports-extract.js +253 -0
  122. package/dist/tools/pe-fingerprint.d.ts +234 -0
  123. package/dist/tools/pe-fingerprint.js +269 -0
  124. package/dist/tools/pe-imports-extract.d.ts +105 -0
  125. package/dist/tools/pe-imports-extract.js +245 -0
  126. package/dist/tools/report-generate.d.ts +157 -0
  127. package/dist/tools/report-generate.js +457 -0
  128. package/dist/tools/report-summarize.d.ts +2131 -0
  129. package/dist/tools/report-summarize.js +596 -0
  130. package/dist/tools/runtime-detect.d.ts +135 -0
  131. package/dist/tools/runtime-detect.js +247 -0
  132. package/dist/tools/sample-ingest.d.ts +94 -0
  133. package/dist/tools/sample-ingest.js +327 -0
  134. package/dist/tools/sample-profile-get.d.ts +183 -0
  135. package/dist/tools/sample-profile-get.js +121 -0
  136. package/dist/tools/sandbox-execute.d.ts +441 -0
  137. package/dist/tools/sandbox-execute.js +392 -0
  138. package/dist/tools/strings-extract.d.ts +375 -0
  139. package/dist/tools/strings-extract.js +314 -0
  140. package/dist/tools/strings-floss-decode.d.ts +143 -0
  141. package/dist/tools/strings-floss-decode.js +259 -0
  142. package/dist/tools/system-health.d.ts +434 -0
  143. package/dist/tools/system-health.js +446 -0
  144. package/dist/tools/task-cancel.d.ts +21 -0
  145. package/dist/tools/task-cancel.js +70 -0
  146. package/dist/tools/task-status.d.ts +27 -0
  147. package/dist/tools/task-status.js +106 -0
  148. package/dist/tools/task-sweep.d.ts +22 -0
  149. package/dist/tools/task-sweep.js +77 -0
  150. package/dist/tools/tool-help.d.ts +340 -0
  151. package/dist/tools/tool-help.js +261 -0
  152. package/dist/tools/yara-scan.d.ts +554 -0
  153. package/dist/tools/yara-scan.js +313 -0
  154. package/dist/types.d.ts +266 -0
  155. package/dist/types.js +41 -0
  156. package/dist/worker-pool.d.ts +204 -0
  157. package/dist/worker-pool.js +650 -0
  158. package/dist/workflows/deep-static.d.ts +104 -0
  159. package/dist/workflows/deep-static.js +276 -0
  160. package/dist/workflows/function-explanation-review.d.ts +655 -0
  161. package/dist/workflows/function-explanation-review.js +440 -0
  162. package/dist/workflows/reconstruct.d.ts +2053 -0
  163. package/dist/workflows/reconstruct.js +666 -0
  164. package/dist/workflows/semantic-name-review.d.ts +2418 -0
  165. package/dist/workflows/semantic-name-review.js +521 -0
  166. package/dist/workflows/triage.d.ts +659 -0
  167. package/dist/workflows/triage.js +1374 -0
  168. package/dist/workspace-manager.d.ts +150 -0
  169. package/dist/workspace-manager.js +411 -0
  170. package/ghidra_scripts/DecompileFunction.java +487 -0
  171. package/ghidra_scripts/DecompileFunction.py +150 -0
  172. package/ghidra_scripts/ExtractCFG.java +256 -0
  173. package/ghidra_scripts/ExtractCFG.py +233 -0
  174. package/ghidra_scripts/ExtractFunctions.java +442 -0
  175. package/ghidra_scripts/ExtractFunctions.py +101 -0
  176. package/ghidra_scripts/README.md +125 -0
  177. package/ghidra_scripts/SearchFunctionReferences.java +380 -0
  178. package/helpers/DotNetMetadataProbe/DotNetMetadataProbe.csproj +9 -0
  179. package/helpers/DotNetMetadataProbe/Program.cs +566 -0
  180. package/install-to-codex.ps1 +178 -0
  181. package/install-to-copilot.ps1 +303 -0
  182. package/package.json +101 -0
  183. package/requirements.txt +9 -0
  184. package/workers/requirements-dynamic.txt +11 -0
  185. package/workers/requirements.txt +8 -0
  186. package/workers/speakeasy_compat.py +175 -0
  187. package/workers/static_worker.py +5183 -0
  188. package/workers/yara_rules/default.yar +33 -0
  189. package/workers/yara_rules/malware_families.yar +93 -0
  190. package/workers/yara_rules/packers.yar +80 -0
@@ -0,0 +1,442 @@
1
+ // ExtractFunctions.java - Ghidra script to extract function information without Python runtime dependency
2
+ // @category Analysis
3
+ // @description Extracts function information from analyzed binary and outputs JSON
4
+
5
+ import ghidra.app.script.GhidraScript;
6
+ import ghidra.program.model.address.Address;
7
+ import ghidra.program.model.address.AddressIterator;
8
+ import ghidra.program.model.listing.Function;
9
+ import ghidra.program.model.listing.FunctionIterator;
10
+ import ghidra.program.model.listing.FunctionManager;
11
+ import ghidra.program.model.listing.Instruction;
12
+ import ghidra.program.model.symbol.Reference;
13
+ import ghidra.program.model.symbol.SourceType;
14
+ import ghidra.program.model.symbol.Symbol;
15
+
16
+ import java.util.ArrayList;
17
+ import java.util.LinkedHashMap;
18
+ import java.util.LinkedHashSet;
19
+ import java.util.List;
20
+ import java.util.Map;
21
+ import java.util.Set;
22
+
23
+ public class ExtractFunctions extends GhidraScript {
24
+
25
+ private static class ResolvedTarget {
26
+ Function function;
27
+ String address;
28
+ String name;
29
+ String resolvedBy;
30
+ boolean exact;
31
+ }
32
+
33
+ private static class RelationshipAccumulator {
34
+ String address;
35
+ String name;
36
+ String resolvedBy;
37
+ boolean exact;
38
+ Set<String> relationTypes = new LinkedHashSet<>();
39
+ Set<String> referenceTypes = new LinkedHashSet<>();
40
+ Set<String> referenceAddresses = new LinkedHashSet<>();
41
+ Set<String> targetAddresses = new LinkedHashSet<>();
42
+ }
43
+
44
+ private static class RelationshipIndex {
45
+ Map<String, Map<String, RelationshipAccumulator>> callersByFunction = new LinkedHashMap<>();
46
+ Map<String, Map<String, RelationshipAccumulator>> calleesByFunction = new LinkedHashMap<>();
47
+ }
48
+
49
+ private String escapeJson(String value) {
50
+ if (value == null) {
51
+ return "";
52
+ }
53
+ StringBuilder out = new StringBuilder(value.length() + 16);
54
+ for (int i = 0; i < value.length(); i++) {
55
+ char c = value.charAt(i);
56
+ switch (c) {
57
+ case '"':
58
+ out.append("\\\"");
59
+ break;
60
+ case '\\':
61
+ out.append("\\\\");
62
+ break;
63
+ case '\b':
64
+ out.append("\\b");
65
+ break;
66
+ case '\f':
67
+ out.append("\\f");
68
+ break;
69
+ case '\n':
70
+ out.append("\\n");
71
+ break;
72
+ case '\r':
73
+ out.append("\\r");
74
+ break;
75
+ case '\t':
76
+ out.append("\\t");
77
+ break;
78
+ default:
79
+ if (c < 0x20) {
80
+ out.append(String.format("\\u%04x", (int) c));
81
+ } else {
82
+ out.append(c);
83
+ }
84
+ }
85
+ }
86
+ return out.toString();
87
+ }
88
+
89
+ private void appendNamedAddressList(StringBuilder sb, List<Map<String, String>> items) {
90
+ sb.append('[');
91
+ boolean first = true;
92
+ for (Map<String, String> item : items) {
93
+ if (!first) {
94
+ sb.append(',');
95
+ }
96
+ first = false;
97
+ sb.append("{\"address\":\"")
98
+ .append(escapeJson(item.get("address")))
99
+ .append("\",\"name\":\"")
100
+ .append(escapeJson(item.get("name")))
101
+ .append("\"}");
102
+ }
103
+ sb.append(']');
104
+ }
105
+
106
+ private void appendStringArray(StringBuilder sb, Set<String> values) {
107
+ sb.append('[');
108
+ boolean first = true;
109
+ for (String value : values) {
110
+ if (!first) {
111
+ sb.append(',');
112
+ }
113
+ first = false;
114
+ sb.append('"').append(escapeJson(value)).append('"');
115
+ }
116
+ sb.append(']');
117
+ }
118
+
119
+ private void appendRelationshipList(
120
+ StringBuilder sb,
121
+ Map<String, RelationshipAccumulator> relationships
122
+ ) {
123
+ sb.append('[');
124
+ boolean first = true;
125
+ for (RelationshipAccumulator relationship : relationships.values()) {
126
+ if (!first) {
127
+ sb.append(',');
128
+ }
129
+ first = false;
130
+ sb.append('{');
131
+ sb.append("\"address\":\"").append(escapeJson(relationship.address)).append("\",");
132
+ sb.append("\"name\":\"").append(escapeJson(relationship.name)).append("\",");
133
+ sb.append("\"relation_types\":");
134
+ appendStringArray(sb, relationship.relationTypes);
135
+ sb.append(",\"reference_types\":");
136
+ appendStringArray(sb, relationship.referenceTypes);
137
+ sb.append(",\"reference_addresses\":");
138
+ appendStringArray(sb, relationship.referenceAddresses);
139
+ sb.append(",\"target_addresses\":");
140
+ appendStringArray(sb, relationship.targetAddresses);
141
+ sb.append(",\"resolved_by\":\"").append(escapeJson(relationship.resolvedBy)).append("\",");
142
+ sb.append("\"is_exact\":").append(relationship.exact);
143
+ sb.append('}');
144
+ }
145
+ sb.append(']');
146
+ }
147
+
148
+ private String makeRelationKey(String address, String name) {
149
+ return address + "|" + name;
150
+ }
151
+
152
+ private Map<String, RelationshipAccumulator> ensureRelationshipBucket(
153
+ Map<String, Map<String, RelationshipAccumulator>> index,
154
+ String functionAddress
155
+ ) {
156
+ Map<String, RelationshipAccumulator> bucket = index.get(functionAddress);
157
+ if (bucket == null) {
158
+ bucket = new LinkedHashMap<>();
159
+ index.put(functionAddress, bucket);
160
+ }
161
+ return bucket;
162
+ }
163
+
164
+ private void addRelationship(
165
+ Map<String, RelationshipAccumulator> relationships,
166
+ String relationKey,
167
+ String address,
168
+ String name,
169
+ String relationType,
170
+ String referenceType,
171
+ String referenceAddress,
172
+ String targetAddress,
173
+ String resolvedBy,
174
+ boolean exact
175
+ ) {
176
+ RelationshipAccumulator relationship = relationships.get(relationKey);
177
+ if (relationship == null) {
178
+ relationship = new RelationshipAccumulator();
179
+ relationship.address = address;
180
+ relationship.name = name;
181
+ relationship.resolvedBy = resolvedBy;
182
+ relationship.exact = exact;
183
+ relationships.put(relationKey, relationship);
184
+ }
185
+
186
+ relationship.relationTypes.add(relationType);
187
+ relationship.referenceTypes.add(referenceType == null ? "unknown" : referenceType);
188
+ relationship.referenceAddresses.add(referenceAddress);
189
+ relationship.targetAddresses.add(targetAddress);
190
+
191
+ if (!relationship.exact && exact) {
192
+ relationship.exact = true;
193
+ }
194
+ if (!"function_at".equals(relationship.resolvedBy) && "function_at".equals(resolvedBy)) {
195
+ relationship.resolvedBy = resolvedBy;
196
+ } else if ("primary_symbol".equals(relationship.resolvedBy)
197
+ && !"primary_symbol".equals(resolvedBy)) {
198
+ relationship.resolvedBy = resolvedBy;
199
+ }
200
+ }
201
+
202
+ private List<Map<String, String>> toNamedAddressList(Map<String, RelationshipAccumulator> relationships) {
203
+ List<Map<String, String>> items = new ArrayList<>();
204
+ for (RelationshipAccumulator relationship : relationships.values()) {
205
+ Map<String, String> row = new LinkedHashMap<>();
206
+ row.put("address", relationship.address);
207
+ row.put("name", relationship.name);
208
+ items.add(row);
209
+ }
210
+ return items;
211
+ }
212
+
213
+ private ResolvedTarget resolveCallableTarget(FunctionManager manager, Address address) {
214
+ ResolvedTarget target = new ResolvedTarget();
215
+
216
+ Function function = manager.getFunctionAt(address);
217
+ if (function != null) {
218
+ target.function = function;
219
+ target.address = function.getEntryPoint().toString();
220
+ target.name = function.getName();
221
+ target.resolvedBy = "function_at";
222
+ target.exact = address.equals(function.getEntryPoint());
223
+ return target;
224
+ }
225
+
226
+ function = manager.getFunctionContaining(address);
227
+ if (function != null) {
228
+ target.function = function;
229
+ target.address = function.getEntryPoint().toString();
230
+ target.name = function.getName();
231
+ target.resolvedBy = "function_containing";
232
+ target.exact = address.equals(function.getEntryPoint());
233
+ return target;
234
+ }
235
+
236
+ Symbol symbol = currentProgram.getSymbolTable().getPrimarySymbol(address);
237
+ if (symbol != null) {
238
+ target.function = null;
239
+ target.address = symbol.getAddress().toString();
240
+ target.name = symbol.getName();
241
+ target.resolvedBy = "primary_symbol";
242
+ target.exact = address.equals(symbol.getAddress());
243
+ return target;
244
+ }
245
+
246
+ return null;
247
+ }
248
+
249
+ private boolean isTailJumpHint(Function sourceFunction, Address fromAddress) {
250
+ Instruction instruction = currentProgram.getListing().getInstructionContaining(fromAddress);
251
+ if (instruction == null || !instruction.getFlowType().isJump() || instruction.getFlowType().isConditional()) {
252
+ return false;
253
+ }
254
+
255
+ Instruction next = instruction.getNext();
256
+ return next == null || !sourceFunction.getBody().contains(next.getAddress());
257
+ }
258
+
259
+ private String classifyRelationType(Function sourceFunction, ResolvedTarget target, Reference ref) {
260
+ if (ref == null || ref.getReferenceType() == null || target == null) {
261
+ return null;
262
+ }
263
+
264
+ if (ref.getReferenceType().isCall()) {
265
+ return target.exact ? "direct_call" : "direct_call_body";
266
+ }
267
+
268
+ boolean sameFunction = target.function != null
269
+ && sourceFunction.getEntryPoint().equals(target.function.getEntryPoint());
270
+ if (sameFunction) {
271
+ return null;
272
+ }
273
+
274
+ if (ref.getReferenceType().isJump()) {
275
+ return isTailJumpHint(sourceFunction, ref.getFromAddress())
276
+ ? "tail_jump_hint"
277
+ : "body_reference_hint";
278
+ }
279
+
280
+ Instruction instruction = currentProgram.getListing().getInstructionContaining(ref.getFromAddress());
281
+ if (instruction != null) {
282
+ return "body_reference_hint";
283
+ }
284
+
285
+ return null;
286
+ }
287
+
288
+ private RelationshipIndex buildRelationshipIndex(FunctionManager manager, List<Function> functions) {
289
+ RelationshipIndex index = new RelationshipIndex();
290
+
291
+ for (Function function : functions) {
292
+ String address = function.getEntryPoint().toString();
293
+ index.callersByFunction.put(address, new LinkedHashMap<String, RelationshipAccumulator>());
294
+ index.calleesByFunction.put(address, new LinkedHashMap<String, RelationshipAccumulator>());
295
+ }
296
+
297
+ for (Function sourceFunction : functions) {
298
+ String sourceAddress = sourceFunction.getEntryPoint().toString();
299
+ Map<String, RelationshipAccumulator> calleeBucket =
300
+ ensureRelationshipBucket(index.calleesByFunction, sourceAddress);
301
+
302
+ AddressIterator addresses = sourceFunction.getBody().getAddresses(true);
303
+ while (addresses.hasNext()) {
304
+ Address fromAddress = addresses.next();
305
+ Reference[] refsFrom = currentProgram.getReferenceManager().getReferencesFrom(fromAddress);
306
+
307
+ for (Reference ref : refsFrom) {
308
+ ResolvedTarget target = resolveCallableTarget(manager, ref.getToAddress());
309
+ String relationType = classifyRelationType(sourceFunction, target, ref);
310
+ if (target == null || relationType == null) {
311
+ continue;
312
+ }
313
+
314
+ addRelationship(
315
+ calleeBucket,
316
+ makeRelationKey(target.address, target.name),
317
+ target.address,
318
+ target.name,
319
+ relationType,
320
+ ref.getReferenceType().getName(),
321
+ fromAddress.toString(),
322
+ ref.getToAddress().toString(),
323
+ target.resolvedBy,
324
+ target.exact
325
+ );
326
+
327
+ if (target.function == null) {
328
+ continue;
329
+ }
330
+
331
+ String targetFunctionAddress = target.function.getEntryPoint().toString();
332
+ Map<String, RelationshipAccumulator> callerBucket =
333
+ ensureRelationshipBucket(index.callersByFunction, targetFunctionAddress);
334
+
335
+ addRelationship(
336
+ callerBucket,
337
+ sourceAddress,
338
+ sourceAddress,
339
+ sourceFunction.getName(),
340
+ relationType,
341
+ ref.getReferenceType().getName(),
342
+ fromAddress.toString(),
343
+ ref.getToAddress().toString(),
344
+ target.resolvedBy,
345
+ target.exact
346
+ );
347
+ }
348
+ }
349
+ }
350
+
351
+ return index;
352
+ }
353
+
354
+ @Override
355
+ protected void run() throws Exception {
356
+ if (currentProgram == null) {
357
+ println("{\"error\":\"No program loaded\"}");
358
+ return;
359
+ }
360
+
361
+ FunctionManager functionManager = currentProgram.getFunctionManager();
362
+ FunctionIterator iterator = functionManager.getFunctions(true);
363
+ List<Function> functions = new ArrayList<>();
364
+ while (iterator.hasNext()) {
365
+ functions.add(iterator.next());
366
+ }
367
+ RelationshipIndex relationships = buildRelationshipIndex(functionManager, functions);
368
+
369
+ StringBuilder sb = new StringBuilder(1024 * 256);
370
+ sb.append('{');
371
+ sb.append("\"program_name\":\"").append(escapeJson(currentProgram.getName())).append("\",");
372
+ sb.append("\"program_path\":\"").append(escapeJson(currentProgram.getExecutablePath())).append("\",");
373
+ sb.append("\"functions\":[");
374
+
375
+ int functionCount = 0;
376
+ boolean firstFunction = true;
377
+
378
+ for (Function function : functions) {
379
+
380
+ try {
381
+ String functionAddress = function.getEntryPoint().toString();
382
+ Map<String, RelationshipAccumulator> callerRelationships =
383
+ ensureRelationshipBucket(relationships.callersByFunction, functionAddress);
384
+ Map<String, RelationshipAccumulator> calleeRelationships =
385
+ ensureRelationshipBucket(relationships.calleesByFunction, functionAddress);
386
+ List<Map<String, String>> callers = toNamedAddressList(callerRelationships);
387
+ List<Map<String, String>> callees = toNamedAddressList(calleeRelationships);
388
+ boolean isEntryPoint =
389
+ currentProgram.getSymbolTable().isExternalEntryPoint(function.getEntryPoint());
390
+ boolean isExported = function.isExternal()
391
+ || function.getSymbol().getSource() == SourceType.IMPORTED;
392
+
393
+ if (!firstFunction) {
394
+ sb.append(',');
395
+ }
396
+ firstFunction = false;
397
+
398
+ sb.append('{');
399
+ sb.append("\"address\":\"").append(escapeJson(function.getEntryPoint().toString())).append("\",");
400
+ sb.append("\"name\":\"").append(escapeJson(function.getName())).append("\",");
401
+ sb.append("\"size\":").append(function.getBody().getNumAddresses()).append(',');
402
+ sb.append("\"is_thunk\":").append(function.isThunk()).append(',');
403
+ sb.append("\"is_external\":").append(function.isExternal()).append(',');
404
+ sb.append("\"calling_convention\":\"")
405
+ .append(escapeJson(function.getCallingConventionName() == null
406
+ ? "unknown"
407
+ : function.getCallingConventionName()))
408
+ .append("\",");
409
+ sb.append("\"signature\":\"")
410
+ .append(escapeJson(function.getSignature().getPrototypeString()))
411
+ .append("\",");
412
+ sb.append("\"callers\":");
413
+ appendNamedAddressList(sb, callers);
414
+ sb.append(',');
415
+ sb.append("\"caller_count\":").append(callers.size()).append(',');
416
+ sb.append("\"caller_relationships\":");
417
+ appendRelationshipList(sb, callerRelationships);
418
+ sb.append(',');
419
+ sb.append("\"callees\":");
420
+ appendNamedAddressList(sb, callees);
421
+ sb.append(',');
422
+ sb.append("\"callee_count\":").append(callees.size()).append(',');
423
+ sb.append("\"callee_relationships\":");
424
+ appendRelationshipList(sb, calleeRelationships);
425
+ sb.append(',');
426
+ sb.append("\"is_entry_point\":").append(isEntryPoint).append(',');
427
+ sb.append("\"is_exported\":").append(isExported);
428
+ sb.append('}');
429
+
430
+ functionCount += 1;
431
+ } catch (Exception e) {
432
+ printerr("Error processing function " + function.getName() + ": " + e.getMessage());
433
+ }
434
+ }
435
+
436
+ sb.append("],");
437
+ sb.append("\"function_count\":").append(functionCount);
438
+ sb.append('}');
439
+
440
+ println(sb.toString());
441
+ }
442
+ }
@@ -0,0 +1,101 @@
1
+ # ExtractFunctions.py - Ghidra script to extract function information
2
+ # @category Analysis
3
+ # @description Extracts function information from analyzed binary and outputs as JSON
4
+
5
+ import json
6
+ import sys
7
+ from ghidra.program.model.listing import Function
8
+ from ghidra.program.model.symbol import SourceType
9
+
10
+ def extract_functions():
11
+ """
12
+ Extract all functions from the current program and output as JSON
13
+ """
14
+ functions_data = []
15
+
16
+ # Get the current program
17
+ program = getCurrentProgram()
18
+ if program is None:
19
+ print(json.dumps({"error": "No program loaded"}))
20
+ return
21
+
22
+ # Get function manager
23
+ function_manager = program.getFunctionManager()
24
+
25
+ # Iterate through all functions
26
+ for function in function_manager.getFunctions(True):
27
+ try:
28
+ # Extract basic function information
29
+ function_info = {
30
+ "address": function.getEntryPoint().toString(),
31
+ "name": function.getName(),
32
+ "size": function.getBody().getNumAddresses(),
33
+ "is_thunk": function.isThunk(),
34
+ "is_external": function.isExternal(),
35
+ "calling_convention": function.getCallingConventionName() if function.getCallingConventionName() else "unknown",
36
+ "signature": function.getSignature().getPrototypeString(),
37
+ }
38
+
39
+ # Get caller and callee information
40
+ callers = []
41
+ for caller_ref in function.getSymbol().getReferences():
42
+ if caller_ref.getReferenceType().isCall():
43
+ from_addr = caller_ref.getFromAddress()
44
+ caller_func = function_manager.getFunctionContaining(from_addr)
45
+ if caller_func:
46
+ callers.append({
47
+ "address": caller_func.getEntryPoint().toString(),
48
+ "name": caller_func.getName()
49
+ })
50
+
51
+ function_info["callers"] = callers
52
+ function_info["caller_count"] = len(callers)
53
+
54
+ # Get called functions
55
+ callees = []
56
+ for ref in function.getBody().getAddresses(True):
57
+ refs_from = program.getReferenceManager().getReferencesFrom(ref)
58
+ for ref_from in refs_from:
59
+ if ref_from.getReferenceType().isCall():
60
+ to_addr = ref_from.getToAddress()
61
+ callee_func = function_manager.getFunctionAt(to_addr)
62
+ if callee_func:
63
+ callees.append({
64
+ "address": callee_func.getEntryPoint().toString(),
65
+ "name": callee_func.getName()
66
+ })
67
+
68
+ function_info["callees"] = callees
69
+ function_info["callee_count"] = len(callees)
70
+
71
+ # Check if function is entry point
72
+ function_info["is_entry_point"] = (
73
+ function.getEntryPoint() == program.getImageBase().add(program.getMinAddress().getOffset())
74
+ )
75
+
76
+ # Check if function is exported
77
+ function_info["is_exported"] = (
78
+ function.getSymbol().getSource() == SourceType.IMPORTED or
79
+ function.isExternal()
80
+ )
81
+
82
+ functions_data.append(function_info)
83
+
84
+ except Exception as e:
85
+ # Log error but continue processing other functions
86
+ print("Error processing function {}: {}".format(function.getName(), str(e)), file=sys.stderr)
87
+ continue
88
+
89
+ # Output results as JSON
90
+ result = {
91
+ "program_name": program.getName(),
92
+ "program_path": program.getExecutablePath(),
93
+ "function_count": len(functions_data),
94
+ "functions": functions_data
95
+ }
96
+
97
+ print(json.dumps(result, indent=2))
98
+
99
+ # Main execution
100
+ if __name__ == "__main__":
101
+ extract_functions()
@@ -0,0 +1,125 @@
1
+ # Ghidra Scripts
2
+
3
+ This directory contains custom Ghidra scripts used by the Windows EXE Decompiler MCP Server for automated binary analysis.
4
+
5
+ ## Scripts
6
+
7
+ ### ExtractFunctions.py
8
+
9
+ **Purpose**: Extracts function information from analyzed binaries and outputs as JSON.
10
+
11
+ **Category**: Analysis
12
+
13
+ **Description**: This script is executed as a post-analysis script by Ghidra Headless to extract comprehensive function information including:
14
+ - Function addresses and names
15
+ - Function sizes
16
+ - Calling conventions and signatures
17
+ - Caller and callee relationships
18
+ - Entry point and export status
19
+ - Thunk and external function detection
20
+
21
+ **Output Format**: JSON with the following structure:
22
+
23
+ ```json
24
+ {
25
+ "program_name": "sample.exe",
26
+ "program_path": "/path/to/sample.exe",
27
+ "function_count": 150,
28
+ "functions": [
29
+ {
30
+ "address": "0x00401000",
31
+ "name": "main",
32
+ "size": 256,
33
+ "is_thunk": false,
34
+ "is_external": false,
35
+ "calling_convention": "__cdecl",
36
+ "signature": "int main(int argc, char** argv)",
37
+ "callers": [
38
+ {"address": "0x00401500", "name": "_start"}
39
+ ],
40
+ "caller_count": 1,
41
+ "callees": [
42
+ {"address": "0x00402000", "name": "printf"}
43
+ ],
44
+ "callee_count": 1,
45
+ "is_entry_point": true,
46
+ "is_exported": false
47
+ }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ **Usage**: This script is automatically invoked by the Decompiler Worker when running Ghidra Headless analysis:
53
+
54
+ ```bash
55
+ analyzeHeadless <project_path> <project_name> \
56
+ -import <sample_path> \
57
+ -scriptPath <scripts_dir> \
58
+ -postScript ExtractFunctions.py
59
+ ```
60
+
61
+ **Requirements**:
62
+ - Ghidra 10.0 or higher
63
+ - Python 2.7 (Jython, included with Ghidra)
64
+
65
+ ### ExtractFunctions.java
66
+
67
+ **Purpose**: Java fallback for function extraction when Python runtime/PyGhidra is unavailable.
68
+
69
+ **Category**: Analysis
70
+
71
+ **Description**: Produces the same JSON schema as `ExtractFunctions.py` and is used automatically by
72
+ the decompiler worker if Python post-scripts fail with PyGhidra availability errors.
73
+
74
+ ## Adding Custom Scripts
75
+
76
+ To add custom Ghidra scripts:
77
+
78
+ 1. Create a new `.py` file in this directory
79
+ 2. Add the required Ghidra script metadata comments:
80
+ ```python
81
+ # @category <Category>
82
+ # @description <Description>
83
+ ```
84
+ 3. Import required Ghidra modules
85
+ 4. Implement your analysis logic
86
+ 5. Output results (typically as JSON to stdout)
87
+
88
+ ## Configuration
89
+
90
+ The scripts directory path is configured in the Ghidra configuration module (`src/ghidra-config.ts`). By default, it uses `./ghidra_scripts` relative to the project root.
91
+
92
+ You can override this by:
93
+ - Setting the `GHIDRA_SCRIPTS_DIR` environment variable
94
+ - Modifying the configuration in `src/config.ts`
95
+
96
+ ## Troubleshooting
97
+
98
+ ### Script Not Found
99
+
100
+ If Ghidra reports that a script cannot be found:
101
+ 1. Verify the script exists in this directory
102
+ 2. Check that the `-scriptPath` parameter points to this directory
103
+ 3. Ensure the script has the correct file extension (`.py`)
104
+
105
+ ### Script Execution Errors
106
+
107
+ If a script fails during execution:
108
+ 1. Check the Ghidra Headless output for error messages
109
+ 2. Verify the script syntax is correct
110
+ 3. Ensure all required Ghidra modules are imported
111
+ 4. Test the script manually in Ghidra GUI's Script Manager
112
+
113
+ ### JSON Output Issues
114
+
115
+ If JSON output is malformed:
116
+ 1. Ensure all string values are properly escaped
117
+ 2. Check for print statements that might interfere with JSON output
118
+ 3. Verify the script uses `json.dumps()` for serialization
119
+ 4. Redirect error messages to stderr instead of stdout
120
+
121
+ ## References
122
+
123
+ - [Ghidra Scripting Documentation](https://ghidra.re/ghidra_docs/api/ghidra/app/script/GhidraScript.html)
124
+ - [Ghidra Headless Analyzer](https://ghidra.re/ghidra_docs/analyzeHeadlessREADME.html)
125
+ - [Ghidra Python API](https://ghidra.re/ghidra_docs/api/)