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,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