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,519 @@
1
+ /**
2
+ * attack.map tool
3
+ * Map static/simulated indicators to MITRE ATT&CK techniques with evidence links.
4
+ */
5
+ import { z } from 'zod';
6
+ import { createTriageWorkflowHandler } from '../workflows/triage.js';
7
+ import { createPackerDetectHandler } from './packer-detect.js';
8
+ const TOOL_NAME = 'attack.map';
9
+ export const AttackMapInputSchema = z.object({
10
+ sample_id: z.string().describe('Sample ID (format: sha256:<hex>)'),
11
+ include_low_confidence: z
12
+ .boolean()
13
+ .optional()
14
+ .default(false)
15
+ .describe('Include low-confidence ATT&CK mappings'),
16
+ max_techniques: z.number().int().min(1).max(200).optional().default(50),
17
+ force_refresh: z
18
+ .boolean()
19
+ .optional()
20
+ .default(false)
21
+ .describe('Bypass cache in upstream analysis tools'),
22
+ });
23
+ const AttackTechniqueSchema = z.object({
24
+ technique_id: z.string(),
25
+ name: z.string(),
26
+ tactics: z.array(z.string()),
27
+ confidence: z.number(),
28
+ confidence_level: z.enum(['low', 'medium', 'high']),
29
+ evidence: z.array(z.string()),
30
+ sources: z.array(z.string()),
31
+ evidence_weights: z.object({
32
+ import: z.number(),
33
+ string: z.number(),
34
+ runtime: z.number(),
35
+ }),
36
+ counter_evidence: z.array(z.string()).optional(),
37
+ });
38
+ const CapabilityClusterSchema = z.object({
39
+ capability: z.string(),
40
+ confidence: z.number(),
41
+ indicators: z.array(z.string()),
42
+ });
43
+ export const AttackMapOutputSchema = z.object({
44
+ ok: z.boolean(),
45
+ data: z
46
+ .object({
47
+ sample_id: z.string(),
48
+ techniques: z.array(AttackTechniqueSchema),
49
+ capability_clusters: z.array(CapabilityClusterSchema),
50
+ tactic_summary: z.record(z.number()),
51
+ inference: z.object({
52
+ classification: z.enum(['benign', 'suspicious', 'malicious', 'unknown']),
53
+ summary: z.string(),
54
+ }),
55
+ })
56
+ .optional(),
57
+ warnings: z.array(z.string()).optional(),
58
+ errors: z.array(z.string()).optional(),
59
+ metrics: z
60
+ .object({
61
+ elapsed_ms: z.number(),
62
+ tool: z.string(),
63
+ })
64
+ .optional(),
65
+ });
66
+ export const attackMapToolDefinition = {
67
+ name: TOOL_NAME,
68
+ description: 'Generate MITRE ATT&CK technique mapping from triage indicators with evidence-linked confidence scoring.',
69
+ inputSchema: AttackMapInputSchema,
70
+ outputSchema: AttackMapOutputSchema,
71
+ };
72
+ function toStringArray(value) {
73
+ if (!Array.isArray(value)) {
74
+ return [];
75
+ }
76
+ return value.filter((item) => typeof item === 'string');
77
+ }
78
+ function normalizeImportApi(importRef) {
79
+ const last = importRef.split('!').pop() || importRef;
80
+ return last.toLowerCase();
81
+ }
82
+ function buildCapabilityClusters(indicators) {
83
+ const clusters = [];
84
+ const normalizedApis = indicators.suspiciousImports.map(normalizeImportApi);
85
+ const injectionHits = normalizedApis.filter((api) => ['writeprocessmemory', 'createremotethread', 'virtualallocex', 'setwindowshookex'].some((needle) => api.includes(needle)));
86
+ if (injectionHits.length > 0) {
87
+ clusters.push({
88
+ capability: 'process_injection',
89
+ confidence: 0.85,
90
+ indicators: injectionHits.slice(0, 8),
91
+ });
92
+ }
93
+ const commandHits = [
94
+ ...normalizedApis.filter((api) => ['createprocess', 'winexec', 'shellexecute'].some((needle) => api.includes(needle))),
95
+ ...indicators.commands.map((cmd) => cmd.toLowerCase()),
96
+ ];
97
+ if (commandHits.length > 0) {
98
+ clusters.push({
99
+ capability: 'command_execution',
100
+ confidence: 0.72,
101
+ indicators: Array.from(new Set(commandHits)).slice(0, 8),
102
+ });
103
+ }
104
+ const networkHits = [
105
+ ...normalizedApis.filter((api) => ['internetopen', 'internetconnect', 'httpsendrequest', 'urldownloadtofile'].some((needle) => api.includes(needle))),
106
+ ...indicators.urls,
107
+ ...indicators.ips,
108
+ ];
109
+ if (networkHits.length > 0) {
110
+ clusters.push({
111
+ capability: 'network_communication',
112
+ confidence: 0.74,
113
+ indicators: Array.from(new Set(networkHits)).slice(0, 10),
114
+ });
115
+ }
116
+ const registryHits = [
117
+ ...normalizedApis.filter((api) => ['regsetvalue', 'regcreatekey'].some((needle) => api.includes(needle))),
118
+ ...indicators.registryKeys,
119
+ ];
120
+ if (registryHits.length > 0) {
121
+ clusters.push({
122
+ capability: 'registry_modification',
123
+ confidence: 0.68,
124
+ indicators: Array.from(new Set(registryHits)).slice(0, 8),
125
+ });
126
+ }
127
+ if (indicators.packed || indicators.packerConfidence >= 0.58) {
128
+ clusters.push({
129
+ capability: 'defense_evasion_or_packing',
130
+ confidence: Math.max(0.62, Math.min(indicators.packerConfidence, 0.92)),
131
+ indicators: ['packed_or_obfuscated_binary'],
132
+ });
133
+ }
134
+ return clusters;
135
+ }
136
+ function normalizeWeights(weights) {
137
+ const safe = {
138
+ import: Math.max(0, weights.import),
139
+ string: Math.max(0, weights.string),
140
+ runtime: Math.max(0, weights.runtime),
141
+ };
142
+ const sum = safe.import + safe.string + safe.runtime;
143
+ if (sum <= 0) {
144
+ return { import: 0.34, string: 0.33, runtime: 0.33 };
145
+ }
146
+ return {
147
+ import: Number((safe.import / sum).toFixed(2)),
148
+ string: Number((safe.string / sum).toFixed(2)),
149
+ runtime: Number((safe.runtime / sum).toFixed(2)),
150
+ };
151
+ }
152
+ function estimateEvidenceWeights(technique, indicators) {
153
+ const sourceSet = new Set(technique.sources.map((item) => item.toLowerCase()));
154
+ let importWeight = sourceSet.has('imports') ? 0.7 : 0.15;
155
+ let stringWeight = sourceSet.has('strings') ? 0.7 : 0.2;
156
+ let runtimeWeight = sourceSet.has('runtime') ? 0.7 : 0.15;
157
+ if (technique.sources.some((item) => item.toLowerCase().includes('packer'))) {
158
+ runtimeWeight = Math.max(runtimeWeight, 0.4);
159
+ }
160
+ if ((indicators.runtimeHints || []).length > 0) {
161
+ runtimeWeight += 0.2;
162
+ }
163
+ const hasImportEvidence = technique.evidence.some((item) => item.includes('!'));
164
+ if (!hasImportEvidence) {
165
+ importWeight *= 0.6;
166
+ }
167
+ if (indicators.suspiciousStrings.length === 0) {
168
+ stringWeight *= 0.6;
169
+ }
170
+ return normalizeWeights({
171
+ import: importWeight,
172
+ string: stringWeight,
173
+ runtime: runtimeWeight,
174
+ });
175
+ }
176
+ function upsertTechnique(map, technique) {
177
+ const existing = map.get(technique.technique_id);
178
+ if (!existing) {
179
+ const level = technique.confidence >= 0.8 ? 'high' : technique.confidence >= 0.55 ? 'medium' : 'low';
180
+ map.set(technique.technique_id, {
181
+ ...technique,
182
+ confidence: Number(technique.confidence.toFixed(2)),
183
+ confidence_level: level,
184
+ evidence: Array.from(new Set(technique.evidence)),
185
+ sources: Array.from(new Set(technique.sources)),
186
+ evidence_weights: normalizeWeights(technique.evidence_weights || { import: 0.34, string: 0.33, runtime: 0.33 }),
187
+ counter_evidence: technique.counter_evidence?.length
188
+ ? Array.from(new Set(technique.counter_evidence))
189
+ : undefined,
190
+ });
191
+ return;
192
+ }
193
+ const mergedConfidence = Math.max(existing.confidence, technique.confidence);
194
+ const mergedEvidence = Array.from(new Set([...existing.evidence, ...technique.evidence]));
195
+ const mergedSources = Array.from(new Set([...existing.sources, ...technique.sources]));
196
+ const level = mergedConfidence >= 0.8 ? 'high' : mergedConfidence >= 0.55 ? 'medium' : 'low';
197
+ map.set(technique.technique_id, {
198
+ ...existing,
199
+ confidence: Number(mergedConfidence.toFixed(2)),
200
+ confidence_level: level,
201
+ evidence: mergedEvidence,
202
+ sources: mergedSources,
203
+ evidence_weights: normalizeWeights({
204
+ import: Math.max(existing.evidence_weights.import, technique.evidence_weights?.import || 0),
205
+ string: Math.max(existing.evidence_weights.string, technique.evidence_weights?.string || 0),
206
+ runtime: Math.max(existing.evidence_weights.runtime, technique.evidence_weights?.runtime || 0),
207
+ }),
208
+ counter_evidence: Array.from(new Set([...(existing.counter_evidence || []), ...(technique.counter_evidence || [])])),
209
+ });
210
+ }
211
+ export function mapIndicatorsToAttack(indicators, options) {
212
+ const techniqueMap = new Map();
213
+ const normalizedApis = indicators.suspiciousImports.map(normalizeImportApi);
214
+ const joinedStrings = indicators.suspiciousStrings.map((item) => item.toLowerCase()).join(' || ');
215
+ const joinedCommands = indicators.commands.map((item) => item.toLowerCase()).join(' || ');
216
+ const dualUseIntent = indicators.intentLabel === 'dual_use_tool';
217
+ const operatorUtilityIntent = indicators.intentLabel === 'operator_utility';
218
+ const injectionEvidence = indicators.suspiciousImports.filter((entry) => ['writeprocessmemory', 'createremotethread', 'virtualallocex', 'setwindowshookex'].some((needle) => normalizeImportApi(entry).includes(needle)));
219
+ if (injectionEvidence.length > 0) {
220
+ upsertTechnique(techniqueMap, {
221
+ technique_id: 'T1055',
222
+ name: 'Process Injection',
223
+ tactics: ['Defense Evasion', 'Privilege Escalation'],
224
+ confidence: 0.86,
225
+ evidence: injectionEvidence.slice(0, 8),
226
+ sources: ['imports'],
227
+ });
228
+ }
229
+ if (normalizedApis.some((api) => ['createprocess', 'winexec', 'shellexecute'].some((needle) => api.includes(needle))) || /cmd\.exe|wscript\.exe|cscript\.exe|mshta\.exe/.test(joinedCommands)) {
230
+ upsertTechnique(techniqueMap, {
231
+ technique_id: 'T1059.003',
232
+ name: 'Windows Command Shell',
233
+ tactics: ['Execution'],
234
+ confidence: 0.72,
235
+ evidence: indicators.commands.slice(0, 6),
236
+ sources: ['imports', 'strings'],
237
+ });
238
+ }
239
+ if (/powershell\.exe/.test(joinedCommands) || /powershell/.test(joinedStrings)) {
240
+ upsertTechnique(techniqueMap, {
241
+ technique_id: 'T1059.001',
242
+ name: 'PowerShell',
243
+ tactics: ['Execution'],
244
+ confidence: 0.76,
245
+ evidence: indicators.commands.slice(0, 6),
246
+ sources: ['strings'],
247
+ });
248
+ }
249
+ const networkEvidence = [
250
+ ...indicators.urls.slice(0, 6),
251
+ ...indicators.ips.slice(0, 6),
252
+ ...indicators.suspiciousImports
253
+ .filter((entry) => ['internetopen', 'internetconnect', 'httpsendrequest'].some((needle) => normalizeImportApi(entry).includes(needle)))
254
+ .slice(0, 6),
255
+ ];
256
+ if (networkEvidence.length > 0) {
257
+ upsertTechnique(techniqueMap, {
258
+ technique_id: 'T1071.001',
259
+ name: 'Application Layer Protocol: Web Protocols',
260
+ tactics: ['Command and Control'],
261
+ confidence: 0.74,
262
+ evidence: networkEvidence,
263
+ sources: ['imports', 'strings'],
264
+ });
265
+ }
266
+ if (indicators.urls.length > 0 &&
267
+ normalizedApis.some((api) => api.includes('urldownloadtofile'))) {
268
+ upsertTechnique(techniqueMap, {
269
+ technique_id: 'T1105',
270
+ name: 'Ingress Tool Transfer',
271
+ tactics: ['Command and Control'],
272
+ confidence: 0.71,
273
+ evidence: [
274
+ ...indicators.urls.slice(0, 4),
275
+ ...indicators.suspiciousImports
276
+ .filter((entry) => normalizeImportApi(entry).includes('urldownloadtofile'))
277
+ .slice(0, 4),
278
+ ],
279
+ sources: ['imports', 'strings'],
280
+ });
281
+ }
282
+ if (indicators.registryKeys.length > 0 ||
283
+ normalizedApis.some((api) => api.includes('regsetvalue') || api.includes('regcreatekey'))) {
284
+ upsertTechnique(techniqueMap, {
285
+ technique_id: 'T1112',
286
+ name: 'Modify Registry',
287
+ tactics: ['Defense Evasion', 'Persistence'],
288
+ confidence: 0.69,
289
+ evidence: [
290
+ ...indicators.registryKeys.slice(0, 6),
291
+ ...indicators.suspiciousImports
292
+ .filter((entry) => {
293
+ const api = normalizeImportApi(entry);
294
+ return api.includes('regsetvalue') || api.includes('regcreatekey');
295
+ })
296
+ .slice(0, 4),
297
+ ],
298
+ sources: ['imports', 'strings'],
299
+ });
300
+ }
301
+ if (indicators.packed || indicators.packerConfidence >= 0.58) {
302
+ upsertTechnique(techniqueMap, {
303
+ technique_id: 'T1027',
304
+ name: 'Obfuscated/Compressed Files and Information',
305
+ tactics: ['Defense Evasion'],
306
+ confidence: Math.max(0.62, Math.min(indicators.packerConfidence, 0.9)),
307
+ evidence: ['packer.detect signaled packing/obfuscation traits'],
308
+ sources: ['packer.detect'],
309
+ });
310
+ }
311
+ const highConfidenceRansomSignals = indicators.yaraMatches.filter((rule) => rule.toLowerCase().includes('ransomware') || rule.toLowerCase().includes('encrypt'));
312
+ const lowConfidenceRansomSignals = indicators.yaraLowConfidence.filter((rule) => rule.toLowerCase().includes('ransomware') || rule.toLowerCase().includes('encrypt'));
313
+ if (highConfidenceRansomSignals.length > 0 || lowConfidenceRansomSignals.length > 0) {
314
+ const onlyWeakRansomSignals = highConfidenceRansomSignals.length === 0 && lowConfidenceRansomSignals.length > 0;
315
+ const counterEvidence = [];
316
+ let ransomwareConfidence = highConfidenceRansomSignals.length > 0 ? 0.78 : 0.34;
317
+ if (onlyWeakRansomSignals) {
318
+ counterEvidence.push('Only low-confidence/string-heavy ransomware YARA hints were present without stronger corroboration.');
319
+ }
320
+ if (dualUseIntent) {
321
+ ransomwareConfidence = Math.max(0.1, ransomwareConfidence - 0.16);
322
+ counterEvidence.push('Triage intent assessment suggests a dual-use operator tool, which weakens impact-only ransomware mapping.');
323
+ }
324
+ else if (operatorUtilityIntent) {
325
+ ransomwareConfidence = Math.max(0.1, ransomwareConfidence - 0.1);
326
+ counterEvidence.push('Operator-facing CLI/help surface reduces confidence that encryption-related strings imply destructive impact.');
327
+ }
328
+ upsertTechnique(techniqueMap, {
329
+ technique_id: 'T1486',
330
+ name: 'Data Encrypted for Impact',
331
+ tactics: ['Impact'],
332
+ confidence: ransomwareConfidence,
333
+ evidence: [...highConfidenceRansomSignals, ...lowConfidenceRansomSignals].slice(0, 6),
334
+ sources: ['yara'],
335
+ counter_evidence: counterEvidence.length > 0 ? counterEvidence : undefined,
336
+ });
337
+ }
338
+ let techniques = Array.from(techniqueMap.values()).sort((a, b) => b.confidence - a.confidence);
339
+ const networkApiPresent = indicators.suspiciousImports.some((entry) => ['internetopen', 'internetconnect', 'httpsendrequest', 'urldownloadtofile'].some((needle) => normalizeImportApi(entry).includes(needle)));
340
+ const noNetworkEvidence = indicators.urls.length === 0 && indicators.ips.length === 0 && !networkApiPresent;
341
+ const noCommandEvidence = indicators.commands.length === 0 &&
342
+ !indicators.suspiciousImports.some((entry) => ['createprocess', 'winexec', 'shellexecute'].some((needle) => normalizeImportApi(entry).includes(needle)));
343
+ const staticOnlyAssessment = true;
344
+ techniques = techniques.map((technique) => {
345
+ const counterEvidence = [...(technique.counter_evidence || [])];
346
+ let adjustedConfidence = technique.confidence;
347
+ if (dualUseIntent && ['T1055', 'T1059.003', 'T1059.001'].includes(technique.technique_id)) {
348
+ adjustedConfidence = Math.max(0.1, adjustedConfidence - 0.18);
349
+ counterEvidence.push('Triage intent assessment suggests a dual-use operator tool; downgraded static-only execution/injection mapping.');
350
+ }
351
+ else if (operatorUtilityIntent &&
352
+ ['T1055', 'T1059.003', 'T1059.001'].includes(technique.technique_id)) {
353
+ adjustedConfidence = Math.max(0.1, adjustedConfidence - 0.1);
354
+ counterEvidence.push('Operator-facing CLI/help surface reduces confidence that this static capability is malware-exclusive.');
355
+ }
356
+ if (noNetworkEvidence &&
357
+ (technique.technique_id === 'T1071.001' || technique.technique_id === 'T1105')) {
358
+ adjustedConfidence = Math.max(0.1, adjustedConfidence - 0.22);
359
+ counterEvidence.push('Missing network import/URL/IP evidence for C2 behavior.');
360
+ }
361
+ if (noCommandEvidence &&
362
+ (technique.technique_id === 'T1059.003' || technique.technique_id === 'T1059.001')) {
363
+ adjustedConfidence = Math.max(0.1, adjustedConfidence - 0.16);
364
+ counterEvidence.push('Missing explicit command execution evidence.');
365
+ }
366
+ if (staticOnlyAssessment) {
367
+ adjustedConfidence = Math.max(0.1, adjustedConfidence - 0.05);
368
+ counterEvidence.push('No dynamic execution trace; static-only inference.');
369
+ }
370
+ const confidence_level = adjustedConfidence >= 0.8 ? 'high' : adjustedConfidence >= 0.55 ? 'medium' : 'low';
371
+ return {
372
+ ...technique,
373
+ confidence: Number(adjustedConfidence.toFixed(2)),
374
+ confidence_level,
375
+ evidence_weights: estimateEvidenceWeights(technique, indicators),
376
+ counter_evidence: Array.from(new Set(counterEvidence)).filter((item) => item.length > 0),
377
+ };
378
+ });
379
+ if (!options.includeLowConfidence) {
380
+ techniques = techniques.filter((tech) => tech.confidence_level !== 'low');
381
+ }
382
+ techniques = techniques.slice(0, options.maxTechniques);
383
+ const capabilityClusters = buildCapabilityClusters(indicators);
384
+ return { techniques, capabilityClusters };
385
+ }
386
+ function getClassification(threatLevel, techniqueCount, highConfidenceCount, intentLabel) {
387
+ if (intentLabel === 'dual_use_tool' &&
388
+ highConfidenceCount < 2 &&
389
+ (threatLevel === 'malicious' || techniqueCount > 0)) {
390
+ return 'suspicious';
391
+ }
392
+ if (threatLevel === 'malicious' || highConfidenceCount >= 2) {
393
+ return 'malicious';
394
+ }
395
+ if (threatLevel === 'suspicious' || techniqueCount > 0) {
396
+ return 'suspicious';
397
+ }
398
+ if (threatLevel === 'clean') {
399
+ return 'benign';
400
+ }
401
+ return 'unknown';
402
+ }
403
+ export function createAttackMapHandler(workspaceManager, database, cacheManager) {
404
+ const triageHandler = createTriageWorkflowHandler(workspaceManager, database, cacheManager);
405
+ const packerHandler = createPackerDetectHandler(workspaceManager, database, cacheManager);
406
+ return async (args) => {
407
+ const startTime = Date.now();
408
+ try {
409
+ const input = AttackMapInputSchema.parse(args);
410
+ const sample = database.findSample(input.sample_id);
411
+ if (!sample) {
412
+ return {
413
+ ok: false,
414
+ errors: [`Sample not found: ${input.sample_id}`],
415
+ metrics: {
416
+ elapsed_ms: Date.now() - startTime,
417
+ tool: TOOL_NAME,
418
+ },
419
+ };
420
+ }
421
+ const triageResult = await triageHandler({
422
+ sample_id: input.sample_id,
423
+ force_refresh: input.force_refresh,
424
+ });
425
+ if (!triageResult.ok || !triageResult.data) {
426
+ return {
427
+ ok: false,
428
+ errors: triageResult.errors || ['workflow.triage failed'],
429
+ warnings: triageResult.warnings,
430
+ metrics: {
431
+ elapsed_ms: Date.now() - startTime,
432
+ tool: TOOL_NAME,
433
+ },
434
+ };
435
+ }
436
+ const triageData = triageResult.data;
437
+ const iocs = (triageData.iocs || {});
438
+ const highValue = (iocs.high_value_iocs || {});
439
+ const runtimeRaw = (triageData.raw_results || {}).runtime;
440
+ const packerResult = await packerHandler({
441
+ sample_id: input.sample_id,
442
+ force_refresh: input.force_refresh,
443
+ });
444
+ const packerData = (packerResult.data || {});
445
+ const indicators = {
446
+ suspiciousImports: toStringArray(iocs.suspicious_imports),
447
+ suspiciousStrings: toStringArray(iocs.suspicious_strings),
448
+ commands: toStringArray(highValue.commands),
449
+ urls: toStringArray(iocs.urls),
450
+ ips: toStringArray(iocs.ip_addresses),
451
+ registryKeys: toStringArray(iocs.registry_keys),
452
+ yaraMatches: toStringArray(iocs.yara_matches),
453
+ yaraLowConfidence: toStringArray(iocs.yara_low_confidence),
454
+ packed: Boolean(packerData.packed),
455
+ packerConfidence: typeof packerData.confidence === 'number' ? Number(packerData.confidence) : 0,
456
+ runtimeHints: Array.isArray(runtimeRaw?.suspected)
457
+ ? runtimeRaw.suspected
458
+ .map((item) => String(item.runtime || '').trim())
459
+ .filter((item) => item.length > 0)
460
+ : [],
461
+ intentLabel: triageData.inference?.intent_assessment?.label || 'unknown',
462
+ intentConfidence: typeof triageData.inference?.intent_assessment?.confidence === 'number'
463
+ ? triageData.inference.intent_assessment.confidence
464
+ : 0,
465
+ };
466
+ const mapping = mapIndicatorsToAttack(indicators, {
467
+ includeLowConfidence: input.include_low_confidence,
468
+ maxTechniques: input.max_techniques,
469
+ });
470
+ const tacticSummary = {};
471
+ for (const technique of mapping.techniques) {
472
+ for (const tactic of technique.tactics) {
473
+ tacticSummary[tactic] = (tacticSummary[tactic] || 0) + 1;
474
+ }
475
+ }
476
+ const highConfidenceCount = mapping.techniques.filter((technique) => technique.confidence_level === 'high').length;
477
+ const counterEvidenceCount = mapping.techniques.reduce((count, technique) => count + (technique.counter_evidence?.length || 0), 0);
478
+ const classification = getClassification(triageData.threat_level, mapping.techniques.length, highConfidenceCount, indicators.intentLabel);
479
+ const intentSummary = indicators.intentLabel && indicators.intentLabel !== 'unknown'
480
+ ? ` Triage intent=${indicators.intentLabel}(${Number(indicators.intentConfidence || 0).toFixed(2)}).`
481
+ : '';
482
+ return {
483
+ ok: true,
484
+ data: {
485
+ sample_id: input.sample_id,
486
+ techniques: mapping.techniques,
487
+ capability_clusters: mapping.capabilityClusters,
488
+ tactic_summary: tacticSummary,
489
+ inference: {
490
+ classification,
491
+ summary: mapping.techniques.length > 0
492
+ ? `Mapped ${mapping.techniques.length} ATT&CK technique(s) from correlated indicators; ` +
493
+ `applied ${counterEvidenceCount} counter-evidence factor(s).${intentSummary}`
494
+ : 'No strong ATT&CK technique mapping from current evidence.',
495
+ },
496
+ },
497
+ warnings: [
498
+ ...(triageResult.warnings || []),
499
+ ...(packerResult.warnings || []),
500
+ ],
501
+ metrics: {
502
+ elapsed_ms: Date.now() - startTime,
503
+ tool: TOOL_NAME,
504
+ },
505
+ };
506
+ }
507
+ catch (error) {
508
+ return {
509
+ ok: false,
510
+ errors: [error.message],
511
+ metrics: {
512
+ elapsed_ms: Date.now() - startTime,
513
+ tool: TOOL_NAME,
514
+ },
515
+ };
516
+ }
517
+ };
518
+ }
519
+ //# sourceMappingURL=attack-map.js.map
@@ -0,0 +1,4 @@
1
+ import type { CacheManager, CacheHitLookup, CacheHitMetadata } from '../cache-manager.js';
2
+ export declare function lookupCachedResult(cacheManager: CacheManager, cacheKey: string): Promise<CacheHitLookup | null>;
3
+ export declare function formatCacheWarning(metadata: CacheHitMetadata): string;
4
+ //# sourceMappingURL=cache-observability.d.ts.map
@@ -0,0 +1,36 @@
1
+ export async function lookupCachedResult(cacheManager, cacheKey) {
2
+ const manager = cacheManager;
3
+ if (typeof manager.getCachedResultWithMetadata === 'function') {
4
+ return manager.getCachedResultWithMetadata(cacheKey);
5
+ }
6
+ const data = await cacheManager.getCachedResult(cacheKey);
7
+ if (data === null) {
8
+ return null;
9
+ }
10
+ return {
11
+ data,
12
+ metadata: {
13
+ key: cacheKey,
14
+ tier: 'unknown',
15
+ fetchedAt: new Date().toISOString(),
16
+ },
17
+ };
18
+ }
19
+ export function formatCacheWarning(metadata) {
20
+ const parts = [
21
+ `tier=${metadata.tier}`,
22
+ `key=${metadata.key}`,
23
+ `hit_at=${metadata.fetchedAt}`,
24
+ ];
25
+ if (metadata.createdAt) {
26
+ parts.push(`created_at=${metadata.createdAt}`);
27
+ }
28
+ if (metadata.expiresAt) {
29
+ parts.push(`expires_at=${metadata.expiresAt}`);
30
+ }
31
+ if (metadata.sampleSha256) {
32
+ parts.push(`sample_sha256=${metadata.sampleSha256}`);
33
+ }
34
+ return `Cache details: ${parts.join(', ')}`;
35
+ }
36
+ //# sourceMappingURL=cache-observability.js.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * code.function.cfg MCP Tool
3
+ *
4
+ * Requirements: 11.1, 11.2, 11.3, 11.4
5
+ *
6
+ * Extracts control flow graph for a function
7
+ */
8
+ import { z } from 'zod';
9
+ import type { ToolDefinition, ToolHandler } from '../types.js';
10
+ import type { DatabaseManager } from '../database.js';
11
+ import type { WorkspaceManager } from '../workspace-manager.js';
12
+ /**
13
+ * Input schema for code.function.cfg tool
14
+ */
15
+ export declare const codeFunctionCFGInputSchema: z.ZodEffects<z.ZodObject<{
16
+ sample_id: z.ZodString;
17
+ address: z.ZodOptional<z.ZodString>;
18
+ symbol: z.ZodOptional<z.ZodString>;
19
+ timeout: z.ZodOptional<z.ZodNumber>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ sample_id: string;
22
+ symbol?: string | undefined;
23
+ timeout?: number | undefined;
24
+ address?: string | undefined;
25
+ }, {
26
+ sample_id: string;
27
+ symbol?: string | undefined;
28
+ timeout?: number | undefined;
29
+ address?: string | undefined;
30
+ }>, {
31
+ sample_id: string;
32
+ symbol?: string | undefined;
33
+ timeout?: number | undefined;
34
+ address?: string | undefined;
35
+ }, {
36
+ sample_id: string;
37
+ symbol?: string | undefined;
38
+ timeout?: number | undefined;
39
+ address?: string | undefined;
40
+ }>;
41
+ export type CodeFunctionCFGInput = z.infer<typeof codeFunctionCFGInputSchema>;
42
+ /**
43
+ * Tool definition for code.function.cfg
44
+ */
45
+ export declare const codeFunctionCFGToolDefinition: ToolDefinition;
46
+ /**
47
+ * Create handler for code.function.cfg tool
48
+ */
49
+ export declare function createCodeFunctionCFGHandler(workspaceManager: WorkspaceManager, database: DatabaseManager): ToolHandler;
50
+ //# sourceMappingURL=code-function-cfg.d.ts.map