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.
- package/CODEX_INSTALLATION.md +69 -0
- package/COPILOT_INSTALLATION.md +77 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/bin/windows-exe-decompiler-mcp-server.js +3 -0
- package/dist/analysis-provenance.d.ts +184 -0
- package/dist/analysis-provenance.js +74 -0
- package/dist/analysis-task-runner.d.ts +31 -0
- package/dist/analysis-task-runner.js +160 -0
- package/dist/artifact-inventory.d.ts +23 -0
- package/dist/artifact-inventory.js +175 -0
- package/dist/cache-manager.d.ts +128 -0
- package/dist/cache-manager.js +454 -0
- package/dist/confidence-semantics.d.ts +66 -0
- package/dist/confidence-semantics.js +122 -0
- package/dist/config.d.ts +335 -0
- package/dist/config.js +193 -0
- package/dist/database.d.ts +227 -0
- package/dist/database.js +601 -0
- package/dist/decompiler-worker.d.ts +441 -0
- package/dist/decompiler-worker.js +1962 -0
- package/dist/dynamic-trace.d.ts +95 -0
- package/dist/dynamic-trace.js +629 -0
- package/dist/env-validator.d.ts +15 -0
- package/dist/env-validator.js +249 -0
- package/dist/error-handler.d.ts +28 -0
- package/dist/error-handler.example.d.ts +22 -0
- package/dist/error-handler.example.js +141 -0
- package/dist/error-handler.js +139 -0
- package/dist/ghidra-analysis-status.d.ts +49 -0
- package/dist/ghidra-analysis-status.js +178 -0
- package/dist/ghidra-config.d.ts +134 -0
- package/dist/ghidra-config.js +464 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +200 -0
- package/dist/job-queue.d.ts +169 -0
- package/dist/job-queue.js +407 -0
- package/dist/logger.d.ts +106 -0
- package/dist/logger.js +176 -0
- package/dist/policy-guard.d.ts +115 -0
- package/dist/policy-guard.js +243 -0
- package/dist/process-output.d.ts +15 -0
- package/dist/process-output.js +90 -0
- package/dist/prompts/function-explanation-review.d.ts +5 -0
- package/dist/prompts/function-explanation-review.js +64 -0
- package/dist/prompts/semantic-name-review.d.ts +5 -0
- package/dist/prompts/semantic-name-review.js +63 -0
- package/dist/runtime-correlation.d.ts +34 -0
- package/dist/runtime-correlation.js +279 -0
- package/dist/runtime-paths.d.ts +3 -0
- package/dist/runtime-paths.js +11 -0
- package/dist/selection-diff.d.ts +667 -0
- package/dist/selection-diff.js +53 -0
- package/dist/semantic-name-suggestion-artifacts.d.ts +116 -0
- package/dist/semantic-name-suggestion-artifacts.js +314 -0
- package/dist/server.d.ts +129 -0
- package/dist/server.js +578 -0
- package/dist/tools/artifact-read.d.ts +235 -0
- package/dist/tools/artifact-read.js +317 -0
- package/dist/tools/artifacts-diff.d.ts +728 -0
- package/dist/tools/artifacts-diff.js +304 -0
- package/dist/tools/artifacts-list.d.ts +515 -0
- package/dist/tools/artifacts-list.js +389 -0
- package/dist/tools/attack-map.d.ts +290 -0
- package/dist/tools/attack-map.js +519 -0
- package/dist/tools/cache-observability.d.ts +4 -0
- package/dist/tools/cache-observability.js +36 -0
- package/dist/tools/code-function-cfg.d.ts +50 -0
- package/dist/tools/code-function-cfg.js +102 -0
- package/dist/tools/code-function-decompile.d.ts +55 -0
- package/dist/tools/code-function-decompile.js +103 -0
- package/dist/tools/code-function-disassemble.d.ts +43 -0
- package/dist/tools/code-function-disassemble.js +185 -0
- package/dist/tools/code-function-explain-apply.d.ts +255 -0
- package/dist/tools/code-function-explain-apply.js +225 -0
- package/dist/tools/code-function-explain-prepare.d.ts +535 -0
- package/dist/tools/code-function-explain-prepare.js +276 -0
- package/dist/tools/code-function-explain-review.d.ts +397 -0
- package/dist/tools/code-function-explain-review.js +589 -0
- package/dist/tools/code-function-rename-apply.d.ts +248 -0
- package/dist/tools/code-function-rename-apply.js +220 -0
- package/dist/tools/code-function-rename-prepare.d.ts +506 -0
- package/dist/tools/code-function-rename-prepare.js +279 -0
- package/dist/tools/code-function-rename-review.d.ts +574 -0
- package/dist/tools/code-function-rename-review.js +761 -0
- package/dist/tools/code-functions-list.d.ts +37 -0
- package/dist/tools/code-functions-list.js +91 -0
- package/dist/tools/code-functions-rank.d.ts +34 -0
- package/dist/tools/code-functions-rank.js +90 -0
- package/dist/tools/code-functions-reconstruct.d.ts +2725 -0
- package/dist/tools/code-functions-reconstruct.js +2807 -0
- package/dist/tools/code-functions-search.d.ts +39 -0
- package/dist/tools/code-functions-search.js +90 -0
- package/dist/tools/code-reconstruct-export.d.ts +1212 -0
- package/dist/tools/code-reconstruct-export.js +4002 -0
- package/dist/tools/code-reconstruct-plan.d.ts +274 -0
- package/dist/tools/code-reconstruct-plan.js +342 -0
- package/dist/tools/dotnet-metadata-extract.d.ts +541 -0
- package/dist/tools/dotnet-metadata-extract.js +355 -0
- package/dist/tools/dotnet-reconstruct-export.d.ts +567 -0
- package/dist/tools/dotnet-reconstruct-export.js +1151 -0
- package/dist/tools/dotnet-types-list.d.ts +325 -0
- package/dist/tools/dotnet-types-list.js +201 -0
- package/dist/tools/dynamic-dependencies.d.ts +115 -0
- package/dist/tools/dynamic-dependencies.js +213 -0
- package/dist/tools/dynamic-memory-import.d.ts +10 -0
- package/dist/tools/dynamic-memory-import.js +567 -0
- package/dist/tools/dynamic-trace-import.d.ts +10 -0
- package/dist/tools/dynamic-trace-import.js +235 -0
- package/dist/tools/entrypoint-fallback-disasm.d.ts +30 -0
- package/dist/tools/entrypoint-fallback-disasm.js +89 -0
- package/dist/tools/ghidra-analyze.d.ts +88 -0
- package/dist/tools/ghidra-analyze.js +208 -0
- package/dist/tools/ghidra-health.d.ts +37 -0
- package/dist/tools/ghidra-health.js +212 -0
- package/dist/tools/ioc-export.d.ts +209 -0
- package/dist/tools/ioc-export.js +542 -0
- package/dist/tools/packer-detect.d.ts +165 -0
- package/dist/tools/packer-detect.js +284 -0
- package/dist/tools/pe-exports-extract.d.ts +175 -0
- package/dist/tools/pe-exports-extract.js +253 -0
- package/dist/tools/pe-fingerprint.d.ts +234 -0
- package/dist/tools/pe-fingerprint.js +269 -0
- package/dist/tools/pe-imports-extract.d.ts +105 -0
- package/dist/tools/pe-imports-extract.js +245 -0
- package/dist/tools/report-generate.d.ts +157 -0
- package/dist/tools/report-generate.js +457 -0
- package/dist/tools/report-summarize.d.ts +2131 -0
- package/dist/tools/report-summarize.js +596 -0
- package/dist/tools/runtime-detect.d.ts +135 -0
- package/dist/tools/runtime-detect.js +247 -0
- package/dist/tools/sample-ingest.d.ts +94 -0
- package/dist/tools/sample-ingest.js +327 -0
- package/dist/tools/sample-profile-get.d.ts +183 -0
- package/dist/tools/sample-profile-get.js +121 -0
- package/dist/tools/sandbox-execute.d.ts +441 -0
- package/dist/tools/sandbox-execute.js +392 -0
- package/dist/tools/strings-extract.d.ts +375 -0
- package/dist/tools/strings-extract.js +314 -0
- package/dist/tools/strings-floss-decode.d.ts +143 -0
- package/dist/tools/strings-floss-decode.js +259 -0
- package/dist/tools/system-health.d.ts +434 -0
- package/dist/tools/system-health.js +446 -0
- package/dist/tools/task-cancel.d.ts +21 -0
- package/dist/tools/task-cancel.js +70 -0
- package/dist/tools/task-status.d.ts +27 -0
- package/dist/tools/task-status.js +106 -0
- package/dist/tools/task-sweep.d.ts +22 -0
- package/dist/tools/task-sweep.js +77 -0
- package/dist/tools/tool-help.d.ts +340 -0
- package/dist/tools/tool-help.js +261 -0
- package/dist/tools/yara-scan.d.ts +554 -0
- package/dist/tools/yara-scan.js +313 -0
- package/dist/types.d.ts +266 -0
- package/dist/types.js +41 -0
- package/dist/worker-pool.d.ts +204 -0
- package/dist/worker-pool.js +650 -0
- package/dist/workflows/deep-static.d.ts +104 -0
- package/dist/workflows/deep-static.js +276 -0
- package/dist/workflows/function-explanation-review.d.ts +655 -0
- package/dist/workflows/function-explanation-review.js +440 -0
- package/dist/workflows/reconstruct.d.ts +2053 -0
- package/dist/workflows/reconstruct.js +666 -0
- package/dist/workflows/semantic-name-review.d.ts +2418 -0
- package/dist/workflows/semantic-name-review.js +521 -0
- package/dist/workflows/triage.d.ts +659 -0
- package/dist/workflows/triage.js +1374 -0
- package/dist/workspace-manager.d.ts +150 -0
- package/dist/workspace-manager.js +411 -0
- package/ghidra_scripts/DecompileFunction.java +487 -0
- package/ghidra_scripts/DecompileFunction.py +150 -0
- package/ghidra_scripts/ExtractCFG.java +256 -0
- package/ghidra_scripts/ExtractCFG.py +233 -0
- package/ghidra_scripts/ExtractFunctions.java +442 -0
- package/ghidra_scripts/ExtractFunctions.py +101 -0
- package/ghidra_scripts/README.md +125 -0
- package/ghidra_scripts/SearchFunctionReferences.java +380 -0
- package/helpers/DotNetMetadataProbe/DotNetMetadataProbe.csproj +9 -0
- package/helpers/DotNetMetadataProbe/Program.cs +566 -0
- package/install-to-codex.ps1 +178 -0
- package/install-to-copilot.ps1 +303 -0
- package/package.json +101 -0
- package/requirements.txt +9 -0
- package/workers/requirements-dynamic.txt +11 -0
- package/workers/requirements.txt +8 -0
- package/workers/speakeasy_compat.py +175 -0
- package/workers/static_worker.py +5183 -0
- package/workers/yara_rules/default.yar +33 -0
- package/workers/yara_rules/malware_families.yar +93 -0
- 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/)
|