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,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pe.fingerprint tool implementation
|
|
3
|
+
* Extracts PE file fingerprint information
|
|
4
|
+
* Requirements: 2.1, 2.2, 2.3, 2.5
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
10
|
+
import { generateCacheKey } from '../cache-manager.js';
|
|
11
|
+
import { resolvePackagePath } from '../runtime-paths.js';
|
|
12
|
+
import { lookupCachedResult, formatCacheWarning } from './cache-observability.js';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Constants
|
|
15
|
+
// ============================================================================
|
|
16
|
+
const TOOL_NAME = 'pe.fingerprint';
|
|
17
|
+
const TOOL_VERSION = '1.0.0';
|
|
18
|
+
const CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Input/Output Schemas
|
|
21
|
+
// ============================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Input schema for pe.fingerprint tool
|
|
24
|
+
* Requirements: 2.1, 2.3
|
|
25
|
+
*/
|
|
26
|
+
export const PEFingerprintInputSchema = z.object({
|
|
27
|
+
sample_id: z.string().describe('Sample ID (format: sha256:<hex>)'),
|
|
28
|
+
fast: z.boolean().optional().default(false).describe('Fast mode (skip section entropy and signature info)'),
|
|
29
|
+
force_refresh: z
|
|
30
|
+
.boolean()
|
|
31
|
+
.optional()
|
|
32
|
+
.default(false)
|
|
33
|
+
.describe('Bypass cache lookup and recompute from source sample'),
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Output schema for pe.fingerprint tool
|
|
37
|
+
* Requirements: 2.1, 2.2, 2.3
|
|
38
|
+
*/
|
|
39
|
+
export const PEFingerprintOutputSchema = z.object({
|
|
40
|
+
ok: z.boolean(),
|
|
41
|
+
data: z.object({
|
|
42
|
+
machine: z.number(),
|
|
43
|
+
machine_name: z.string(),
|
|
44
|
+
subsystem: z.number(),
|
|
45
|
+
subsystem_name: z.string(),
|
|
46
|
+
timestamp: z.number(),
|
|
47
|
+
timestamp_iso: z.string().nullable(),
|
|
48
|
+
imphash: z.string().nullable(),
|
|
49
|
+
entry_point: z.number(),
|
|
50
|
+
image_base: z.number(),
|
|
51
|
+
sections: z.array(z.object({
|
|
52
|
+
name: z.string(),
|
|
53
|
+
virtual_address: z.number(),
|
|
54
|
+
virtual_size: z.number(),
|
|
55
|
+
raw_size: z.number(),
|
|
56
|
+
entropy: z.number(),
|
|
57
|
+
characteristics: z.number(),
|
|
58
|
+
})).optional(),
|
|
59
|
+
signature: z.object({
|
|
60
|
+
present: z.boolean(),
|
|
61
|
+
address: z.number().optional(),
|
|
62
|
+
size: z.number().optional(),
|
|
63
|
+
verified: z.boolean().optional(),
|
|
64
|
+
}).optional(),
|
|
65
|
+
_parser: z.string().optional(),
|
|
66
|
+
_pefile_error: z.string().optional(),
|
|
67
|
+
}).optional(),
|
|
68
|
+
warnings: z.array(z.string()).optional(),
|
|
69
|
+
errors: z.array(z.string()).optional(),
|
|
70
|
+
artifacts: z.array(z.any()).optional(),
|
|
71
|
+
metrics: z.object({
|
|
72
|
+
elapsed_ms: z.number(),
|
|
73
|
+
tool: z.string(),
|
|
74
|
+
}).optional(),
|
|
75
|
+
});
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Tool Definition
|
|
78
|
+
// ============================================================================
|
|
79
|
+
/**
|
|
80
|
+
* Tool definition for pe.fingerprint
|
|
81
|
+
*/
|
|
82
|
+
export const peFingerprintToolDefinition = {
|
|
83
|
+
name: TOOL_NAME,
|
|
84
|
+
description: '提取 PE 文件指纹信息(机器类型、子系统、时间戳、Imphash、节区熵值、签名)',
|
|
85
|
+
inputSchema: PEFingerprintInputSchema,
|
|
86
|
+
outputSchema: PEFingerprintOutputSchema,
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Spawn Python Static Worker and communicate via stdin/stdout JSON protocol
|
|
90
|
+
*
|
|
91
|
+
* Requirements: Worker communication
|
|
92
|
+
*
|
|
93
|
+
* @param request - Worker request object
|
|
94
|
+
* @returns Worker response object
|
|
95
|
+
*/
|
|
96
|
+
async function callStaticWorker(request) {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
// Get Python worker path
|
|
99
|
+
const workerPath = resolvePackagePath('workers', 'static_worker.py');
|
|
100
|
+
// Spawn Python process
|
|
101
|
+
const pythonCommand = process.platform === 'win32' ? 'python' : 'python3';
|
|
102
|
+
const pythonProcess = spawn(pythonCommand, [workerPath], {
|
|
103
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
104
|
+
});
|
|
105
|
+
let stdout = '';
|
|
106
|
+
let stderr = '';
|
|
107
|
+
// Collect stdout
|
|
108
|
+
pythonProcess.stdout.on('data', (data) => {
|
|
109
|
+
stdout += data.toString();
|
|
110
|
+
});
|
|
111
|
+
// Collect stderr
|
|
112
|
+
pythonProcess.stderr.on('data', (data) => {
|
|
113
|
+
stderr += data.toString();
|
|
114
|
+
});
|
|
115
|
+
// Handle process exit
|
|
116
|
+
pythonProcess.on('close', (code) => {
|
|
117
|
+
if (code !== 0) {
|
|
118
|
+
reject(new Error(`Python worker exited with code ${code}. stderr: ${stderr}`));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Parse response from stdout
|
|
122
|
+
try {
|
|
123
|
+
const lines = stdout.trim().split('\n');
|
|
124
|
+
const lastLine = lines[lines.length - 1];
|
|
125
|
+
const response = JSON.parse(lastLine);
|
|
126
|
+
resolve(response);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
reject(new Error(`Failed to parse worker response: ${error.message}. stdout: ${stdout}`));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
// Handle process error
|
|
133
|
+
pythonProcess.on('error', (error) => {
|
|
134
|
+
reject(new Error(`Failed to spawn Python worker: ${error.message}`));
|
|
135
|
+
});
|
|
136
|
+
// Send request to worker via stdin
|
|
137
|
+
try {
|
|
138
|
+
pythonProcess.stdin.write(JSON.stringify(request) + '\n');
|
|
139
|
+
pythonProcess.stdin.end();
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
reject(new Error(`Failed to write to worker stdin: ${error.message}`));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// Tool Handler
|
|
148
|
+
// ============================================================================
|
|
149
|
+
/**
|
|
150
|
+
* Create pe.fingerprint tool handler
|
|
151
|
+
* Requirements: 2.1, 2.2, 2.3, 2.5
|
|
152
|
+
*/
|
|
153
|
+
export function createPEFingerprintHandler(workspaceManager, database, cacheManager) {
|
|
154
|
+
return async (args) => {
|
|
155
|
+
const input = args;
|
|
156
|
+
const startTime = Date.now();
|
|
157
|
+
try {
|
|
158
|
+
// 1. Generate cache key
|
|
159
|
+
// Requirement: 2.5
|
|
160
|
+
const sample = database.findSample(input.sample_id);
|
|
161
|
+
if (!sample) {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
errors: [`Sample not found: ${input.sample_id}`],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const cacheKey = generateCacheKey({
|
|
168
|
+
sampleSha256: sample.sha256,
|
|
169
|
+
toolName: TOOL_NAME,
|
|
170
|
+
toolVersion: TOOL_VERSION,
|
|
171
|
+
args: { fast: input.fast },
|
|
172
|
+
});
|
|
173
|
+
// 2. Check cache
|
|
174
|
+
// Requirement: 2.5
|
|
175
|
+
if (!input.force_refresh) {
|
|
176
|
+
const cachedLookup = await lookupCachedResult(cacheManager, cacheKey);
|
|
177
|
+
if (cachedLookup) {
|
|
178
|
+
return {
|
|
179
|
+
ok: true,
|
|
180
|
+
data: cachedLookup.data,
|
|
181
|
+
warnings: ['Result from cache', formatCacheWarning(cachedLookup.metadata)],
|
|
182
|
+
metrics: {
|
|
183
|
+
elapsed_ms: Date.now() - startTime,
|
|
184
|
+
tool: TOOL_NAME,
|
|
185
|
+
cached: true,
|
|
186
|
+
cache_key: cachedLookup.metadata.key,
|
|
187
|
+
cache_tier: cachedLookup.metadata.tier,
|
|
188
|
+
cache_created_at: cachedLookup.metadata.createdAt,
|
|
189
|
+
cache_expires_at: cachedLookup.metadata.expiresAt,
|
|
190
|
+
cache_hit_at: cachedLookup.metadata.fetchedAt,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// 3. Get sample path from workspace
|
|
196
|
+
const workspace = await workspaceManager.getWorkspace(input.sample_id);
|
|
197
|
+
// Find the sample file in the original directory
|
|
198
|
+
const fs = await import('fs/promises');
|
|
199
|
+
const files = await fs.readdir(workspace.original);
|
|
200
|
+
if (files.length === 0) {
|
|
201
|
+
return {
|
|
202
|
+
ok: false,
|
|
203
|
+
errors: ['Sample file not found in workspace'],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const samplePath = path.join(workspace.original, files[0]);
|
|
207
|
+
// 4. Prepare worker request
|
|
208
|
+
const workerRequest = {
|
|
209
|
+
job_id: uuidv4(),
|
|
210
|
+
tool: TOOL_NAME,
|
|
211
|
+
sample: {
|
|
212
|
+
sample_id: input.sample_id,
|
|
213
|
+
path: samplePath,
|
|
214
|
+
},
|
|
215
|
+
args: {
|
|
216
|
+
fast: input.fast,
|
|
217
|
+
},
|
|
218
|
+
context: {
|
|
219
|
+
request_time_utc: new Date().toISOString(),
|
|
220
|
+
policy: {
|
|
221
|
+
allow_dynamic: false,
|
|
222
|
+
allow_network: false,
|
|
223
|
+
},
|
|
224
|
+
versions: {
|
|
225
|
+
tool_version: TOOL_VERSION,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
// 5. Call Static Worker
|
|
230
|
+
// Requirements: 2.1, 2.2, 2.3, 2.4
|
|
231
|
+
const workerResponse = await callStaticWorker(workerRequest);
|
|
232
|
+
if (!workerResponse.ok) {
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
errors: workerResponse.errors,
|
|
236
|
+
warnings: workerResponse.warnings,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// 6. Cache result
|
|
240
|
+
// Requirement: 2.5 (30-day TTL)
|
|
241
|
+
await cacheManager.setCachedResult(cacheKey, workerResponse.data, CACHE_TTL_MS);
|
|
242
|
+
// 7. Return result
|
|
243
|
+
return {
|
|
244
|
+
ok: true,
|
|
245
|
+
data: workerResponse.data,
|
|
246
|
+
warnings: input.force_refresh
|
|
247
|
+
? ['force_refresh=true; bypassed cache lookup', ...(workerResponse.warnings || [])]
|
|
248
|
+
: workerResponse.warnings,
|
|
249
|
+
errors: workerResponse.errors,
|
|
250
|
+
artifacts: workerResponse.artifacts,
|
|
251
|
+
metrics: {
|
|
252
|
+
...workerResponse.metrics,
|
|
253
|
+
elapsed_ms: Date.now() - startTime,
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
return {
|
|
259
|
+
ok: false,
|
|
260
|
+
errors: [error.message],
|
|
261
|
+
metrics: {
|
|
262
|
+
elapsed_ms: Date.now() - startTime,
|
|
263
|
+
tool: TOOL_NAME,
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=pe-fingerprint.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pe.imports.extract tool implementation
|
|
3
|
+
* Extracts PE file import table (DLLs and functions)
|
|
4
|
+
* Requirements: 3.1, 3.2
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import type { ToolDefinition, ToolArgs, WorkerResult } from '../types.js';
|
|
8
|
+
import type { WorkspaceManager } from '../workspace-manager.js';
|
|
9
|
+
import type { DatabaseManager } from '../database.js';
|
|
10
|
+
import type { CacheManager } from '../cache-manager.js';
|
|
11
|
+
/**
|
|
12
|
+
* Input schema for pe.imports.extract tool
|
|
13
|
+
* Requirements: 3.1, 3.2
|
|
14
|
+
*/
|
|
15
|
+
export declare const PEImportsExtractInputSchema: z.ZodObject<{
|
|
16
|
+
sample_id: z.ZodString;
|
|
17
|
+
group_by_dll: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
18
|
+
force_refresh: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
sample_id: string;
|
|
21
|
+
force_refresh: boolean;
|
|
22
|
+
group_by_dll: boolean;
|
|
23
|
+
}, {
|
|
24
|
+
sample_id: string;
|
|
25
|
+
force_refresh?: boolean | undefined;
|
|
26
|
+
group_by_dll?: boolean | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
export type PEImportsExtractInput = z.infer<typeof PEImportsExtractInputSchema>;
|
|
29
|
+
/**
|
|
30
|
+
* Output schema for pe.imports.extract tool
|
|
31
|
+
* Requirements: 3.1, 3.2
|
|
32
|
+
*/
|
|
33
|
+
export declare const PEImportsExtractOutputSchema: z.ZodObject<{
|
|
34
|
+
ok: z.ZodBoolean;
|
|
35
|
+
data: z.ZodOptional<z.ZodObject<{
|
|
36
|
+
imports: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
|
|
37
|
+
delay_imports: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
|
|
38
|
+
_parser: z.ZodOptional<z.ZodString>;
|
|
39
|
+
_pefile_error: z.ZodOptional<z.ZodString>;
|
|
40
|
+
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
_parser?: string | undefined;
|
|
42
|
+
_pefile_error?: string | undefined;
|
|
43
|
+
imports?: Record<string, string[]> | undefined;
|
|
44
|
+
delay_imports?: Record<string, string[]> | undefined;
|
|
45
|
+
}, {
|
|
46
|
+
_parser?: string | undefined;
|
|
47
|
+
_pefile_error?: string | undefined;
|
|
48
|
+
imports?: Record<string, string[]> | undefined;
|
|
49
|
+
delay_imports?: Record<string, string[]> | undefined;
|
|
50
|
+
}>>;
|
|
51
|
+
warnings: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
52
|
+
errors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
53
|
+
artifacts: z.ZodOptional<z.ZodArray<z.ZodAny, "many">>;
|
|
54
|
+
metrics: z.ZodOptional<z.ZodObject<{
|
|
55
|
+
elapsed_ms: z.ZodNumber;
|
|
56
|
+
tool: z.ZodString;
|
|
57
|
+
}, "strip", z.ZodTypeAny, {
|
|
58
|
+
elapsed_ms: number;
|
|
59
|
+
tool: string;
|
|
60
|
+
}, {
|
|
61
|
+
elapsed_ms: number;
|
|
62
|
+
tool: string;
|
|
63
|
+
}>>;
|
|
64
|
+
}, "strip", z.ZodTypeAny, {
|
|
65
|
+
ok: boolean;
|
|
66
|
+
metrics?: {
|
|
67
|
+
elapsed_ms: number;
|
|
68
|
+
tool: string;
|
|
69
|
+
} | undefined;
|
|
70
|
+
data?: {
|
|
71
|
+
_parser?: string | undefined;
|
|
72
|
+
_pefile_error?: string | undefined;
|
|
73
|
+
imports?: Record<string, string[]> | undefined;
|
|
74
|
+
delay_imports?: Record<string, string[]> | undefined;
|
|
75
|
+
} | undefined;
|
|
76
|
+
warnings?: string[] | undefined;
|
|
77
|
+
errors?: string[] | undefined;
|
|
78
|
+
artifacts?: any[] | undefined;
|
|
79
|
+
}, {
|
|
80
|
+
ok: boolean;
|
|
81
|
+
metrics?: {
|
|
82
|
+
elapsed_ms: number;
|
|
83
|
+
tool: string;
|
|
84
|
+
} | undefined;
|
|
85
|
+
data?: {
|
|
86
|
+
_parser?: string | undefined;
|
|
87
|
+
_pefile_error?: string | undefined;
|
|
88
|
+
imports?: Record<string, string[]> | undefined;
|
|
89
|
+
delay_imports?: Record<string, string[]> | undefined;
|
|
90
|
+
} | undefined;
|
|
91
|
+
warnings?: string[] | undefined;
|
|
92
|
+
errors?: string[] | undefined;
|
|
93
|
+
artifacts?: any[] | undefined;
|
|
94
|
+
}>;
|
|
95
|
+
export type PEImportsExtractOutput = z.infer<typeof PEImportsExtractOutputSchema>;
|
|
96
|
+
/**
|
|
97
|
+
* Tool definition for pe.imports.extract
|
|
98
|
+
*/
|
|
99
|
+
export declare const peImportsExtractToolDefinition: ToolDefinition;
|
|
100
|
+
/**
|
|
101
|
+
* Create pe.imports.extract tool handler
|
|
102
|
+
* Requirements: 3.1, 3.2
|
|
103
|
+
*/
|
|
104
|
+
export declare function createPEImportsExtractHandler(workspaceManager: WorkspaceManager, database: DatabaseManager, cacheManager: CacheManager): (args: ToolArgs) => Promise<WorkerResult>;
|
|
105
|
+
//# sourceMappingURL=pe-imports-extract.d.ts.map
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pe.imports.extract tool implementation
|
|
3
|
+
* Extracts PE file import table (DLLs and functions)
|
|
4
|
+
* Requirements: 3.1, 3.2
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
10
|
+
import { generateCacheKey } from '../cache-manager.js';
|
|
11
|
+
import { resolvePackagePath } from '../runtime-paths.js';
|
|
12
|
+
import { lookupCachedResult, formatCacheWarning } from './cache-observability.js';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Constants
|
|
15
|
+
// ============================================================================
|
|
16
|
+
const TOOL_NAME = 'pe.imports.extract';
|
|
17
|
+
const TOOL_VERSION = '1.0.0';
|
|
18
|
+
const CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Input/Output Schemas
|
|
21
|
+
// ============================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Input schema for pe.imports.extract tool
|
|
24
|
+
* Requirements: 3.1, 3.2
|
|
25
|
+
*/
|
|
26
|
+
export const PEImportsExtractInputSchema = z.object({
|
|
27
|
+
sample_id: z.string().describe('Sample ID (format: sha256:<hex>)'),
|
|
28
|
+
group_by_dll: z.boolean().optional().default(true).describe('Group functions by DLL name'),
|
|
29
|
+
force_refresh: z
|
|
30
|
+
.boolean()
|
|
31
|
+
.optional()
|
|
32
|
+
.default(false)
|
|
33
|
+
.describe('Bypass cache lookup and recompute from source sample'),
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Output schema for pe.imports.extract tool
|
|
37
|
+
* Requirements: 3.1, 3.2
|
|
38
|
+
*/
|
|
39
|
+
export const PEImportsExtractOutputSchema = z.object({
|
|
40
|
+
ok: z.boolean(),
|
|
41
|
+
data: z.object({
|
|
42
|
+
imports: z.record(z.string(), z.array(z.string())).optional(),
|
|
43
|
+
delay_imports: z.record(z.string(), z.array(z.string())).optional(),
|
|
44
|
+
_parser: z.string().optional(),
|
|
45
|
+
_pefile_error: z.string().optional(),
|
|
46
|
+
}).optional(),
|
|
47
|
+
warnings: z.array(z.string()).optional(),
|
|
48
|
+
errors: z.array(z.string()).optional(),
|
|
49
|
+
artifacts: z.array(z.any()).optional(),
|
|
50
|
+
metrics: z.object({
|
|
51
|
+
elapsed_ms: z.number(),
|
|
52
|
+
tool: z.string(),
|
|
53
|
+
}).optional(),
|
|
54
|
+
});
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Tool Definition
|
|
57
|
+
// ============================================================================
|
|
58
|
+
/**
|
|
59
|
+
* Tool definition for pe.imports.extract
|
|
60
|
+
*/
|
|
61
|
+
export const peImportsExtractToolDefinition = {
|
|
62
|
+
name: TOOL_NAME,
|
|
63
|
+
description: '提取 PE 文件的导入表(DLL 和函数),支持按 DLL 分组',
|
|
64
|
+
inputSchema: PEImportsExtractInputSchema,
|
|
65
|
+
outputSchema: PEImportsExtractOutputSchema,
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Spawn Python Static Worker and communicate via stdin/stdout JSON protocol
|
|
69
|
+
*
|
|
70
|
+
* Requirements: Worker communication
|
|
71
|
+
*
|
|
72
|
+
* @param request - Worker request object
|
|
73
|
+
* @returns Worker response object
|
|
74
|
+
*/
|
|
75
|
+
async function callStaticWorker(request) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
// Get Python worker path
|
|
78
|
+
const workerPath = resolvePackagePath('workers', 'static_worker.py');
|
|
79
|
+
// Spawn Python process
|
|
80
|
+
const pythonCommand = process.platform === 'win32' ? 'python' : 'python3';
|
|
81
|
+
const pythonProcess = spawn(pythonCommand, [workerPath], {
|
|
82
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
83
|
+
});
|
|
84
|
+
let stdout = '';
|
|
85
|
+
let stderr = '';
|
|
86
|
+
// Collect stdout
|
|
87
|
+
pythonProcess.stdout.on('data', (data) => {
|
|
88
|
+
stdout += data.toString();
|
|
89
|
+
});
|
|
90
|
+
// Collect stderr
|
|
91
|
+
pythonProcess.stderr.on('data', (data) => {
|
|
92
|
+
stderr += data.toString();
|
|
93
|
+
});
|
|
94
|
+
// Handle process exit
|
|
95
|
+
pythonProcess.on('close', (code) => {
|
|
96
|
+
if (code !== 0) {
|
|
97
|
+
reject(new Error(`Python worker exited with code ${code}. stderr: ${stderr}`));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Parse response from stdout
|
|
101
|
+
try {
|
|
102
|
+
const lines = stdout.trim().split('\n');
|
|
103
|
+
const lastLine = lines[lines.length - 1];
|
|
104
|
+
const response = JSON.parse(lastLine);
|
|
105
|
+
resolve(response);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
reject(new Error(`Failed to parse worker response: ${error.message}. stdout: ${stdout}`));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
// Handle process error
|
|
112
|
+
pythonProcess.on('error', (error) => {
|
|
113
|
+
reject(new Error(`Failed to spawn Python worker: ${error.message}`));
|
|
114
|
+
});
|
|
115
|
+
// Send request to worker via stdin
|
|
116
|
+
try {
|
|
117
|
+
pythonProcess.stdin.write(JSON.stringify(request) + '\n');
|
|
118
|
+
pythonProcess.stdin.end();
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
reject(new Error(`Failed to write to worker stdin: ${error.message}`));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Tool Handler
|
|
127
|
+
// ============================================================================
|
|
128
|
+
/**
|
|
129
|
+
* Create pe.imports.extract tool handler
|
|
130
|
+
* Requirements: 3.1, 3.2
|
|
131
|
+
*/
|
|
132
|
+
export function createPEImportsExtractHandler(workspaceManager, database, cacheManager) {
|
|
133
|
+
return async (args) => {
|
|
134
|
+
const input = args;
|
|
135
|
+
const startTime = Date.now();
|
|
136
|
+
try {
|
|
137
|
+
// 1. Generate cache key
|
|
138
|
+
const sample = database.findSample(input.sample_id);
|
|
139
|
+
if (!sample) {
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
errors: [`Sample not found: ${input.sample_id}`],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const cacheKey = generateCacheKey({
|
|
146
|
+
sampleSha256: sample.sha256,
|
|
147
|
+
toolName: TOOL_NAME,
|
|
148
|
+
toolVersion: TOOL_VERSION,
|
|
149
|
+
args: { group_by_dll: input.group_by_dll },
|
|
150
|
+
});
|
|
151
|
+
// 2. Check cache
|
|
152
|
+
if (!input.force_refresh) {
|
|
153
|
+
const cachedLookup = await lookupCachedResult(cacheManager, cacheKey);
|
|
154
|
+
if (cachedLookup) {
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
data: cachedLookup.data,
|
|
158
|
+
warnings: ['Result from cache', formatCacheWarning(cachedLookup.metadata)],
|
|
159
|
+
metrics: {
|
|
160
|
+
elapsed_ms: Date.now() - startTime,
|
|
161
|
+
tool: TOOL_NAME,
|
|
162
|
+
cached: true,
|
|
163
|
+
cache_key: cachedLookup.metadata.key,
|
|
164
|
+
cache_tier: cachedLookup.metadata.tier,
|
|
165
|
+
cache_created_at: cachedLookup.metadata.createdAt,
|
|
166
|
+
cache_expires_at: cachedLookup.metadata.expiresAt,
|
|
167
|
+
cache_hit_at: cachedLookup.metadata.fetchedAt,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// 3. Get sample path from workspace
|
|
173
|
+
const workspace = await workspaceManager.getWorkspace(input.sample_id);
|
|
174
|
+
// Find the sample file in the original directory
|
|
175
|
+
const fs = await import('fs/promises');
|
|
176
|
+
const files = await fs.readdir(workspace.original);
|
|
177
|
+
if (files.length === 0) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
errors: ['Sample file not found in workspace'],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const samplePath = path.join(workspace.original, files[0]);
|
|
184
|
+
// 4. Prepare worker request
|
|
185
|
+
const workerRequest = {
|
|
186
|
+
job_id: uuidv4(),
|
|
187
|
+
tool: TOOL_NAME,
|
|
188
|
+
sample: {
|
|
189
|
+
sample_id: input.sample_id,
|
|
190
|
+
path: samplePath,
|
|
191
|
+
},
|
|
192
|
+
args: {
|
|
193
|
+
group_by_dll: input.group_by_dll,
|
|
194
|
+
},
|
|
195
|
+
context: {
|
|
196
|
+
request_time_utc: new Date().toISOString(),
|
|
197
|
+
policy: {
|
|
198
|
+
allow_dynamic: false,
|
|
199
|
+
allow_network: false,
|
|
200
|
+
},
|
|
201
|
+
versions: {
|
|
202
|
+
tool_version: TOOL_VERSION,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
// 5. Call Static Worker
|
|
207
|
+
// Requirements: 3.1, 3.2
|
|
208
|
+
const workerResponse = await callStaticWorker(workerRequest);
|
|
209
|
+
if (!workerResponse.ok) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
errors: workerResponse.errors,
|
|
213
|
+
warnings: workerResponse.warnings,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
// 6. Cache result
|
|
217
|
+
await cacheManager.setCachedResult(cacheKey, workerResponse.data, CACHE_TTL_MS);
|
|
218
|
+
// 7. Return result
|
|
219
|
+
return {
|
|
220
|
+
ok: true,
|
|
221
|
+
data: workerResponse.data,
|
|
222
|
+
warnings: input.force_refresh
|
|
223
|
+
? ['force_refresh=true; bypassed cache lookup', ...(workerResponse.warnings || [])]
|
|
224
|
+
: workerResponse.warnings,
|
|
225
|
+
errors: workerResponse.errors,
|
|
226
|
+
artifacts: workerResponse.artifacts,
|
|
227
|
+
metrics: {
|
|
228
|
+
...workerResponse.metrics,
|
|
229
|
+
elapsed_ms: Date.now() - startTime,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
errors: [error.message],
|
|
237
|
+
metrics: {
|
|
238
|
+
elapsed_ms: Date.now() - startTime,
|
|
239
|
+
tool: TOOL_NAME,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=pe-imports-extract.js.map
|