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,1151 @@
1
+ /**
2
+ * dotnet.reconstruct.export tool implementation
3
+ * .NET-oriented source skeleton export with confidence annotations and fallback guidance.
4
+ */
5
+ import { createHash, randomUUID } from 'crypto';
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+ import { spawn } from 'child_process';
9
+ import { z } from 'zod';
10
+ import { generateCacheKey } from '../cache-manager.js';
11
+ import { lookupCachedResult, formatCacheWarning } from './cache-observability.js';
12
+ import { createRuntimeDetectHandler } from './runtime-detect.js';
13
+ import { createPackerDetectHandler } from './packer-detect.js';
14
+ import { createCodeReconstructExportHandler } from './code-reconstruct-export.js';
15
+ import { createDotNetMetadataExtractHandler, } from './dotnet-metadata-extract.js';
16
+ import { findBestGhidraAnalysis } from '../ghidra-analysis-status.js';
17
+ const TOOL_NAME = 'dotnet.reconstruct.export';
18
+ const TOOL_VERSION = '0.2.0';
19
+ const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
20
+ export const DotNetReconstructExportInputSchema = z.object({
21
+ sample_id: z.string().describe('Sample ID (format: sha256:<hex>)'),
22
+ topk: z
23
+ .number()
24
+ .int()
25
+ .min(1)
26
+ .max(40)
27
+ .default(16)
28
+ .describe('Top-K high-value functions used for reconstruction'),
29
+ project_name: z
30
+ .string()
31
+ .min(1)
32
+ .max(64)
33
+ .default('RecoveredDotNet')
34
+ .describe('Exported C# project name'),
35
+ namespace: z
36
+ .string()
37
+ .min(1)
38
+ .max(128)
39
+ .default('Recovered')
40
+ .describe('Root C# namespace for reconstructed classes'),
41
+ include_metadata_types: z
42
+ .boolean()
43
+ .default(true)
44
+ .describe('Generate CLR metadata-driven type skeletons alongside module skeletons'),
45
+ max_managed_types: z
46
+ .number()
47
+ .int()
48
+ .min(1)
49
+ .max(200)
50
+ .default(64)
51
+ .describe('Maximum number of managed metadata types emitted as C# skeletons'),
52
+ export_name: z
53
+ .string()
54
+ .min(1)
55
+ .max(64)
56
+ .optional()
57
+ .describe('Optional folder name for export'),
58
+ include_obfuscation_fallback: z
59
+ .boolean()
60
+ .default(true)
61
+ .describe('Generate IL fallback notes when packed/obfuscated signals exist'),
62
+ validate_build: z
63
+ .boolean()
64
+ .default(true)
65
+ .describe('Run dotnet build validation for exported project skeleton'),
66
+ build_timeout_ms: z
67
+ .number()
68
+ .int()
69
+ .min(5000)
70
+ .max(180000)
71
+ .default(45000)
72
+ .describe('Timeout for dotnet build validation in milliseconds'),
73
+ evidence_scope: z
74
+ .enum(['all', 'latest', 'session'])
75
+ .default('all')
76
+ .describe('Runtime evidence scope forwarded to native reconstruction fallback: all artifacts, latest artifact window, or a specific session selector'),
77
+ evidence_session_tag: z
78
+ .string()
79
+ .optional()
80
+ .describe('Optional runtime evidence session selector used when evidence_scope=session or to narrow all/latest results'),
81
+ reuse_cached: z
82
+ .boolean()
83
+ .default(true)
84
+ .describe('Reuse cached result for identical inputs'),
85
+ }).refine((value) => value.evidence_scope !== 'session' || Boolean(value.evidence_session_tag?.trim()), {
86
+ message: 'evidence_session_tag is required when evidence_scope=session',
87
+ path: ['evidence_session_tag'],
88
+ });
89
+ const DotNetMethodSchema = z.object({
90
+ name: z.string(),
91
+ address: z.string().nullable(),
92
+ token: z.string().nullable(),
93
+ confidence: z.number().min(0).max(1),
94
+ gaps: z.array(z.string()),
95
+ });
96
+ const DotNetClassSchema = z.object({
97
+ class_name: z.string(),
98
+ source: z.enum(['module', 'metadata']),
99
+ module: z.string(),
100
+ namespace: z.string(),
101
+ full_name: z.string().nullable(),
102
+ kind: z.string(),
103
+ confidence: z.number().min(0).max(1),
104
+ file_path: z.string(),
105
+ methods: z.array(DotNetMethodSchema),
106
+ });
107
+ const ManagedProfileSchema = z.object({
108
+ assembly_name: z.string().nullable(),
109
+ assembly_version: z.string().nullable(),
110
+ module_name: z.string().nullable(),
111
+ metadata_version: z.string().nullable(),
112
+ is_library: z.boolean(),
113
+ entry_point_token: z.string().nullable(),
114
+ type_count: z.number().int().nonnegative(),
115
+ method_count: z.number().int().nonnegative(),
116
+ namespace_count: z.number().int().nonnegative(),
117
+ assembly_reference_count: z.number().int().nonnegative(),
118
+ resource_count: z.number().int().nonnegative(),
119
+ dominant_namespaces: z.array(z.string()),
120
+ notable_types: z.array(z.string()),
121
+ assembly_references: z.array(z.string()),
122
+ resources: z.array(z.string()),
123
+ analysis_priorities: z.array(z.string()),
124
+ });
125
+ const BuildValidationSchema = z.object({
126
+ attempted: z.boolean(),
127
+ status: z.enum(['passed', 'failed', 'skipped', 'unavailable']),
128
+ command: z.string().nullable(),
129
+ dotnet_cli_available: z.boolean(),
130
+ exit_code: z.number().int().nullable(),
131
+ timed_out: z.boolean(),
132
+ error: z.string().nullable(),
133
+ log_path: z.string().nullable(),
134
+ });
135
+ export const DotNetReconstructExportOutputSchema = z.object({
136
+ ok: z.boolean(),
137
+ data: z
138
+ .object({
139
+ sample_id: z.string(),
140
+ is_dotnet: z.boolean(),
141
+ dotnet_version: z.string().nullable(),
142
+ target_framework: z.string().nullable(),
143
+ packed: z.boolean(),
144
+ packing_confidence: z.number().min(0).max(1),
145
+ export_root: z.string(),
146
+ csproj_path: z.string(),
147
+ readme_path: z.string(),
148
+ metadata_path: z.string().nullable(),
149
+ reverse_notes_path: z.string().nullable(),
150
+ fallback_notes_path: z.string().nullable(),
151
+ degraded_mode: z.boolean(),
152
+ degradation_reasons: z.array(z.string()),
153
+ build_validation: BuildValidationSchema,
154
+ managed_profile: ManagedProfileSchema.nullable(),
155
+ classes: z.array(DotNetClassSchema),
156
+ })
157
+ .optional(),
158
+ warnings: z.array(z.string()).optional(),
159
+ errors: z.array(z.string()).optional(),
160
+ artifacts: z.array(z.any()).optional(),
161
+ metrics: z
162
+ .object({
163
+ elapsed_ms: z.number(),
164
+ tool: z.string(),
165
+ cached: z.boolean().optional(),
166
+ cache_key: z.string().optional(),
167
+ cache_tier: z.string().optional(),
168
+ cache_created_at: z.string().optional(),
169
+ cache_expires_at: z.string().optional(),
170
+ cache_hit_at: z.string().optional(),
171
+ })
172
+ .optional(),
173
+ });
174
+ export const dotNetReconstructExportToolDefinition = {
175
+ name: TOOL_NAME,
176
+ description: 'Export a maintainable C# reconstruction skeleton for .NET samples with confidence annotations and IL fallback guidance.',
177
+ inputSchema: DotNetReconstructExportInputSchema,
178
+ outputSchema: DotNetReconstructExportOutputSchema,
179
+ };
180
+ function clamp(value, min, max) {
181
+ return Math.max(min, Math.min(max, value));
182
+ }
183
+ function normalizeError(error) {
184
+ if (error instanceof Error) {
185
+ return error.message;
186
+ }
187
+ return String(error);
188
+ }
189
+ function toPosixRelative(root, filePath) {
190
+ return path.relative(root, filePath).split(path.sep).join('/');
191
+ }
192
+ function sanitizeIdentifier(value) {
193
+ const cleaned = value.replace(/[^a-zA-Z0-9_]/g, '_');
194
+ if (cleaned.length === 0) {
195
+ return 'RecoveredItem';
196
+ }
197
+ if (/^[0-9]/.test(cleaned)) {
198
+ return `Recovered_${cleaned}`;
199
+ }
200
+ return cleaned;
201
+ }
202
+ function toPascalCase(value) {
203
+ const parts = value
204
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
205
+ .trim()
206
+ .split(/\s+/)
207
+ .filter((item) => item.length > 0);
208
+ if (parts.length === 0) {
209
+ return 'RecoveredModule';
210
+ }
211
+ return parts
212
+ .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1).toLowerCase()}`)
213
+ .join('');
214
+ }
215
+ function mapTargetFramework(raw) {
216
+ if (!raw) {
217
+ return 'net6.0';
218
+ }
219
+ const lowered = raw.toLowerCase();
220
+ if (lowered.includes('netframework') || lowered.includes('.net framework')) {
221
+ return 'net48';
222
+ }
223
+ const match = lowered.match(/net\s*([0-9]+)(?:\.([0-9]+))?/);
224
+ if (match) {
225
+ const major = match[1];
226
+ const minor = match[2] || '0';
227
+ if (parseInt(major, 10) >= 5) {
228
+ return `net${major}.${minor}`;
229
+ }
230
+ }
231
+ return 'net6.0';
232
+ }
233
+ function buildCsproj(projectName, targetFramework) {
234
+ return [
235
+ '<Project Sdk="Microsoft.NET.Sdk">',
236
+ ' <PropertyGroup>',
237
+ ` <AssemblyName>${projectName}</AssemblyName>`,
238
+ ` <RootNamespace>${projectName}</RootNamespace>`,
239
+ ' <OutputType>Library</OutputType>',
240
+ ` <TargetFramework>${targetFramework}</TargetFramework>`,
241
+ ' <Nullable>enable</Nullable>',
242
+ ' <ImplicitUsings>enable</ImplicitUsings>',
243
+ ' <LangVersion>latest</LangVersion>',
244
+ ' </PropertyGroup>',
245
+ '</Project>',
246
+ '',
247
+ ].join('\n');
248
+ }
249
+ function sanitizeNamespaceSegment(value) {
250
+ return sanitizeIdentifier(value).replace(/_+/g, '_');
251
+ }
252
+ function buildNamespace(root, suffix) {
253
+ const parts = [root, suffix]
254
+ .filter((item) => Boolean(item && item.trim()))
255
+ .flatMap((item) => item.split('.'))
256
+ .map((item) => sanitizeNamespaceSegment(item))
257
+ .filter((item) => item.length > 0);
258
+ return parts.length > 0 ? parts.join('.') : 'Recovered';
259
+ }
260
+ function buildModuleClassContent(namespaceRoot, className, moduleName, moduleConfidence, methods) {
261
+ const lines = [];
262
+ lines.push(`namespace ${namespaceRoot};`);
263
+ lines.push('');
264
+ lines.push(`// Module: ${moduleName} | confidence=${moduleConfidence.toFixed(2)}`);
265
+ lines.push(`public static partial class ${className}`);
266
+ lines.push('{');
267
+ if (methods.length === 0) {
268
+ lines.push(' // No methods recovered for this module.');
269
+ }
270
+ else {
271
+ for (const method of methods) {
272
+ const methodName = sanitizeIdentifier(`m_${method.function}`);
273
+ lines.push(` // address=${method.address} confidence=${method.confidence.toFixed(2)} gaps=${method.gaps.join(',') || 'none'}`);
274
+ lines.push(` public static void ${methodName}()`);
275
+ lines.push(' {');
276
+ lines.push(' // TODO: Recover exact managed semantics by comparing IL with native reconstruction.');
277
+ lines.push(' }');
278
+ lines.push('');
279
+ }
280
+ if (lines[lines.length - 1] === '') {
281
+ lines.pop();
282
+ }
283
+ }
284
+ lines.push('}');
285
+ lines.push('');
286
+ return lines.join('\n');
287
+ }
288
+ function inferTypeModuleHints(typeInfo, modules) {
289
+ const lowered = `${typeInfo.full_name} ${typeInfo.name}`.toLowerCase();
290
+ const hints = [];
291
+ const keywordMap = {
292
+ process_ops: ['process', 'thread', 'inject', 'remote', 'context'],
293
+ network_ops: ['net', 'http', 'socket', 'dns', 'web', 'client', 'server'],
294
+ file_ops: ['file', 'path', 'stream', 'io', 'archive', 'disk'],
295
+ registry_ops: ['registry', 'reg'],
296
+ crypto_ops: ['crypto', 'hash', 'cipher', 'aes', 'rsa', 'encrypt', 'decrypt'],
297
+ anti_analysis: ['debug', 'analysis', 'sandbox', 'vm', 'anti'],
298
+ packer_analysis: ['packer', 'entropy', 'section', 'image', 'header'],
299
+ };
300
+ for (const [moduleName, tokens] of Object.entries(keywordMap)) {
301
+ if (!modules.some((module) => module.name === moduleName)) {
302
+ continue;
303
+ }
304
+ if (tokens.some((token) => lowered.includes(token))) {
305
+ hints.push(moduleName);
306
+ }
307
+ }
308
+ return hints.slice(0, 3);
309
+ }
310
+ function pickMetadataTypeConfidence(typeInfo) {
311
+ let confidence = 0.48;
312
+ confidence += Math.min(0.18, typeInfo.method_count * 0.015);
313
+ confidence += Math.min(0.08, typeInfo.field_count * 0.01);
314
+ if (typeInfo.visibility === 'public') {
315
+ confidence += 0.07;
316
+ }
317
+ if (typeInfo.kind === 'interface' || typeInfo.kind === 'delegate') {
318
+ confidence += 0.03;
319
+ }
320
+ return clamp(confidence, 0.35, 0.82);
321
+ }
322
+ function buildManagedMethodName(method, usedNames) {
323
+ const baseName = method.name === '.ctor'
324
+ ? 'Ctor'
325
+ : method.name === '.cctor'
326
+ ? 'TypeInitializer'
327
+ : sanitizeIdentifier(method.name);
328
+ let candidate = baseName;
329
+ const tokenSuffix = method.token.replace(/^0x/i, '');
330
+ if (candidate.length === 0) {
331
+ candidate = `Method_${tokenSuffix}`;
332
+ }
333
+ while (usedNames.has(candidate)) {
334
+ candidate = `${baseName}_${tokenSuffix}`;
335
+ }
336
+ usedNames.add(candidate);
337
+ return candidate;
338
+ }
339
+ function buildMetadataClassContent(namespaceRoot, className, typeInfo, relatedModules) {
340
+ const lines = [];
341
+ const usedMethodNames = new Set();
342
+ lines.push(`namespace ${namespaceRoot};`);
343
+ lines.push('');
344
+ lines.push('/// <summary>');
345
+ lines.push(`/// Metadata-driven reconstruction for ${typeInfo.full_name}.`);
346
+ lines.push(`/// token=${typeInfo.token} kind=${typeInfo.kind} visibility=${typeInfo.visibility}`);
347
+ lines.push(`/// base_type=${typeInfo.base_type || 'unknown'} methods=${typeInfo.method_count} fields=${typeInfo.field_count}`);
348
+ if (relatedModules.length > 0) {
349
+ lines.push(`/// related_modules=${relatedModules.join(', ')}`);
350
+ }
351
+ lines.push('/// </summary>');
352
+ lines.push(`public partial class ${className}`);
353
+ lines.push('{');
354
+ if (typeInfo.methods.length === 0) {
355
+ lines.push(' // No CLR methods were emitted for this type.');
356
+ }
357
+ else {
358
+ for (const method of typeInfo.methods) {
359
+ const methodName = buildManagedMethodName(method, usedMethodNames);
360
+ lines.push(' /// <summary>');
361
+ lines.push(` /// token=${method.token} rva=${method.rva > 0 ? `0x${method.rva.toString(16)}` : 'n/a'} flags=${method.attributes.join(',') || 'none'}`);
362
+ lines.push(' /// </summary>');
363
+ lines.push(` public void ${methodName}()`);
364
+ lines.push(' {');
365
+ lines.push(` // TODO: Recover IL body for ${typeInfo.full_name}.${method.name}.`);
366
+ if (relatedModules.length > 0) {
367
+ lines.push(` // Likely related reconstructed modules: ${relatedModules.join(', ')}.`);
368
+ }
369
+ lines.push(' }');
370
+ lines.push('');
371
+ }
372
+ if (lines[lines.length - 1] === '') {
373
+ lines.pop();
374
+ }
375
+ }
376
+ lines.push('}');
377
+ lines.push('');
378
+ return lines.join('\n');
379
+ }
380
+ function buildManagedProfile(metadata, packed, packingConfidence) {
381
+ if (!metadata) {
382
+ return null;
383
+ }
384
+ const assemblyRefs = metadata.assembly_references.map((item) => item.name).filter(Boolean);
385
+ const resources = metadata.resources.map((item) => item.name).filter(Boolean);
386
+ const dominantNamespaces = metadata.namespaces.slice(0, 6).map((item) => item.name);
387
+ const notableTypes = metadata.types.slice(0, 8).map((item) => item.full_name);
388
+ const priorities = [];
389
+ const refsLower = assemblyRefs.map((item) => item.toLowerCase());
390
+ if (metadata.is_library || !metadata.entry_point_token) {
391
+ priorities.push('inspect_public_surface_and_host_integration');
392
+ }
393
+ else {
394
+ priorities.push('trace_entrypoint_and_dispatch_chain');
395
+ }
396
+ if (packed || packingConfidence >= 0.45) {
397
+ priorities.push('deobfuscate_before_claiming_source_equivalence');
398
+ }
399
+ if (refsLower.some((item) => item.includes('system.net'))) {
400
+ priorities.push('review_managed_network_paths');
401
+ }
402
+ if (refsLower.some((item) => item.includes('system.management'))) {
403
+ priorities.push('review_wmi_and_inventory_collection_paths');
404
+ }
405
+ if (refsLower.some((item) => item.includes('system.reflection'))) {
406
+ priorities.push('review_dynamic_loading_and_plugin_paths');
407
+ }
408
+ if (refsLower.some((item) => item.includes('system.security') || item.includes('crypt'))) {
409
+ priorities.push('review_crypto_and_secret_handling');
410
+ }
411
+ if (resources.length > 0) {
412
+ priorities.push('inspect_embedded_resources_and_config_payloads');
413
+ }
414
+ return {
415
+ assembly_name: metadata.assembly_name,
416
+ assembly_version: metadata.assembly_version,
417
+ module_name: metadata.module_name,
418
+ metadata_version: metadata.metadata_version,
419
+ is_library: metadata.is_library,
420
+ entry_point_token: metadata.entry_point_token,
421
+ type_count: metadata.summary.type_count,
422
+ method_count: metadata.summary.method_count,
423
+ namespace_count: metadata.summary.namespace_count,
424
+ assembly_reference_count: metadata.summary.assembly_reference_count,
425
+ resource_count: metadata.summary.resource_count,
426
+ dominant_namespaces: dominantNamespaces,
427
+ notable_types: notableTypes,
428
+ assembly_references: assemblyRefs.slice(0, 16),
429
+ resources: resources.slice(0, 16),
430
+ analysis_priorities: priorities.slice(0, 8),
431
+ };
432
+ }
433
+ function buildDotNetReverseNotes(input, runtime, managedProfile, modules, packed, packingConfidence, degradationReasons, warnings) {
434
+ const lines = [];
435
+ lines.push('# REVERSE_NOTES.md');
436
+ lines.push('');
437
+ lines.push('## Runtime');
438
+ lines.push(`- sample_id: ${input.sample_id}`);
439
+ lines.push(`- dotnet_version: ${runtime.dotnet_version || 'unknown'}`);
440
+ lines.push(`- target_framework: ${runtime.target_framework || 'unknown'}`);
441
+ lines.push(`- packed: ${packed} (confidence=${packingConfidence.toFixed(2)})`);
442
+ lines.push('');
443
+ lines.push('## Managed Profile');
444
+ if (!managedProfile) {
445
+ lines.push('- CLR metadata profile unavailable; rely on module skeletons and fallback notes.');
446
+ }
447
+ else {
448
+ lines.push(`- assembly_name: ${managedProfile.assembly_name || 'unknown'}`);
449
+ lines.push(`- assembly_version: ${managedProfile.assembly_version || 'unknown'}`);
450
+ lines.push(`- module_name: ${managedProfile.module_name || 'unknown'}`);
451
+ lines.push(`- is_library: ${managedProfile.is_library}`);
452
+ lines.push(`- entry_point_token: ${managedProfile.entry_point_token || 'none'}`);
453
+ lines.push(`- counts: types=${managedProfile.type_count}, methods=${managedProfile.method_count}, namespaces=${managedProfile.namespace_count}, refs=${managedProfile.assembly_reference_count}, resources=${managedProfile.resource_count}`);
454
+ lines.push(`- dominant_namespaces: ${managedProfile.dominant_namespaces.length > 0 ? managedProfile.dominant_namespaces.join(', ') : 'none'}`);
455
+ lines.push(`- notable_types: ${managedProfile.notable_types.length > 0 ? managedProfile.notable_types.join(', ') : 'none'}`);
456
+ lines.push(`- assembly_references: ${managedProfile.assembly_references.length > 0 ? managedProfile.assembly_references.join(', ') : 'none'}`);
457
+ if (managedProfile.resources.length > 0) {
458
+ lines.push(`- resources: ${managedProfile.resources.join(', ')}`);
459
+ }
460
+ }
461
+ lines.push('');
462
+ lines.push('## Module Skeleton Guide');
463
+ if (modules.length === 0) {
464
+ lines.push('- No native reconstruction modules were available.');
465
+ }
466
+ else {
467
+ for (const module of modules.slice(0, 10)) {
468
+ lines.push(`- ${module.name}: confidence=${module.confidence.toFixed(2)} functions=${module.functions.length}`);
469
+ }
470
+ }
471
+ lines.push('');
472
+ lines.push('## Analyst Priorities');
473
+ if (!managedProfile || managedProfile.analysis_priorities.length === 0) {
474
+ lines.push('- Compare exported type skeletons with IL and module skeletons before renaming methods.');
475
+ }
476
+ else {
477
+ for (const item of managedProfile.analysis_priorities) {
478
+ lines.push(`- ${item}`);
479
+ }
480
+ }
481
+ lines.push('');
482
+ if (degradationReasons.length > 0) {
483
+ lines.push('## Degradation Reasons');
484
+ for (const reason of degradationReasons) {
485
+ lines.push(`- ${reason}`);
486
+ }
487
+ lines.push('');
488
+ }
489
+ if (warnings.length > 0) {
490
+ lines.push('## Warnings');
491
+ for (const warning of warnings.slice(0, 12)) {
492
+ lines.push(`- ${warning}`);
493
+ }
494
+ lines.push('');
495
+ }
496
+ return lines.join('\n');
497
+ }
498
+ function buildReadme(input, runtime, packed, packingConfidence, classesCount, degradedMode, degradationReasons, managedProfile) {
499
+ return [
500
+ `# ${input.project_name}`,
501
+ '',
502
+ '## Reconstruction Summary',
503
+ `- Sample: ${input.sample_id}`,
504
+ `- Runtime: .NET (${runtime.dotnet_version || 'unknown'})`,
505
+ `- Target framework hint: ${runtime.target_framework || 'unknown'}`,
506
+ `- Classes generated: ${classesCount}`,
507
+ `- Metadata-driven types enabled: ${input.include_metadata_types}`,
508
+ `- Packed/obfuscated signal: ${packed} (confidence=${packingConfidence.toFixed(2)})`,
509
+ `- Degraded mode: ${degradedMode}`,
510
+ `- Degradation reasons: ${degradationReasons.length > 0 ? degradationReasons.join('; ') : 'none'}`,
511
+ `- Assembly name: ${managedProfile?.assembly_name || 'unknown'}`,
512
+ `- Managed type count: ${managedProfile?.type_count ?? 0}`,
513
+ `- Assembly references: ${managedProfile?.assembly_references.slice(0, 6).join(', ') || 'none'}`,
514
+ '',
515
+ '## Notes',
516
+ '- This project is a source-like reconstruction, not the original source code.',
517
+ '- Module classes preserve recovered behavioral groupings from native reconstruction.',
518
+ '- Metadata classes preserve namespace/type/method structure from CLR metadata.',
519
+ '- Method bodies are placeholders and require IL/decompiler evidence review.',
520
+ '- Preserve confidence annotations and gap notes during manual refinement.',
521
+ '',
522
+ ].join('\n');
523
+ }
524
+ function collectLowConfidenceMethods(modules, confidenceThreshold = 0.55) {
525
+ const lowConfidence = [];
526
+ for (const module of modules) {
527
+ for (const method of module.functions || []) {
528
+ const hasHeavyGaps = Array.isArray(method.gaps) && method.gaps.length >= 2;
529
+ if (method.confidence < confidenceThreshold || hasHeavyGaps) {
530
+ lowConfidence.push({
531
+ module: module.name,
532
+ method: method.function,
533
+ address: method.address,
534
+ confidence: method.confidence,
535
+ gaps: method.gaps || [],
536
+ });
537
+ }
538
+ }
539
+ }
540
+ return lowConfidence;
541
+ }
542
+ function buildFallbackNotes(runtime, packed, degradationReasons, lowConfidenceMethods) {
543
+ const lines = [];
544
+ lines.push('# IL_FALLBACK_NOTES.md');
545
+ lines.push('');
546
+ lines.push('## Why fallback is needed');
547
+ if (degradationReasons.length === 0) {
548
+ lines.push('- Fallback was requested, but no explicit degradation reason was detected.');
549
+ }
550
+ else if (packed) {
551
+ lines.push('- Packer/obfuscation signals detected; high-fidelity C# requires IL-level recovery.');
552
+ lines.push(`- Reasons: ${degradationReasons.join('; ')}`);
553
+ }
554
+ else {
555
+ lines.push(`- Reasons: ${degradationReasons.join('; ')}`);
556
+ }
557
+ lines.push('');
558
+ lines.push('## Suggested workflow');
559
+ lines.push('1. Use ILSpy/dnlib to export IL and metadata (types, methods, resources).');
560
+ lines.push('2. Compare IL control flow with recovered placeholders in this project.');
561
+ lines.push('3. Restore method signatures and attributes before rewriting method bodies.');
562
+ lines.push('4. Keep unresolved APIs/types in TODO comments with confidence notes.');
563
+ lines.push('');
564
+ lines.push('## Runtime hints');
565
+ lines.push(`- dotnet_version: ${runtime.dotnet_version || 'unknown'}`);
566
+ lines.push(`- target_framework: ${runtime.target_framework || 'unknown'}`);
567
+ lines.push('');
568
+ lines.push('## Priority methods for IL-first recovery');
569
+ if (lowConfidenceMethods.length === 0) {
570
+ lines.push('- No low-confidence methods were auto-detected.');
571
+ }
572
+ else {
573
+ for (const method of lowConfidenceMethods.slice(0, 24)) {
574
+ lines.push(`- ${method.module}::${method.method} @ ${method.address} confidence=${method.confidence.toFixed(2)} gaps=${method.gaps.join(',') || 'none'}`);
575
+ }
576
+ if (lowConfidenceMethods.length > 24) {
577
+ lines.push(`- ... ${lowConfidenceMethods.length - 24} more low-confidence methods omitted`);
578
+ }
579
+ }
580
+ lines.push('');
581
+ return lines.join('\n');
582
+ }
583
+ function buildValidationLog(validation) {
584
+ const lines = [];
585
+ lines.push('# BUILD_VALIDATION.log');
586
+ lines.push('');
587
+ lines.push(`status: ${validation.status}`);
588
+ lines.push(`attempted: ${validation.attempted}`);
589
+ lines.push(`dotnet_cli_available: ${validation.dotnet_cli_available}`);
590
+ lines.push(`command: ${validation.command || 'n/a'}`);
591
+ lines.push(`exit_code: ${validation.exit_code === null ? 'n/a' : validation.exit_code}`);
592
+ lines.push(`timed_out: ${validation.timed_out}`);
593
+ lines.push(`error: ${validation.error || 'none'}`);
594
+ lines.push('');
595
+ lines.push('## stdout');
596
+ lines.push('```text');
597
+ lines.push(validation.stdout || '');
598
+ lines.push('```');
599
+ lines.push('');
600
+ lines.push('## stderr');
601
+ lines.push('```text');
602
+ lines.push(validation.stderr || '');
603
+ lines.push('```');
604
+ lines.push('');
605
+ return lines.join('\n');
606
+ }
607
+ async function runDotNetBuildValidation(csprojPath, cwd, timeoutMs) {
608
+ return new Promise((resolve) => {
609
+ const args = ['build', csprojPath, '-nologo', '-v', 'minimal'];
610
+ const command = 'dotnet';
611
+ const commandDisplay = `${command} ${args.map((arg) => `"${arg}"`).join(' ')}`;
612
+ const effectiveTimeoutMs = Math.max(5000, timeoutMs);
613
+ const child = spawn(command, args, {
614
+ cwd,
615
+ stdio: ['ignore', 'pipe', 'pipe'],
616
+ windowsHide: true,
617
+ });
618
+ let stdout = '';
619
+ let stderr = '';
620
+ let settled = false;
621
+ let timedOut = false;
622
+ const finish = (result) => {
623
+ if (settled) {
624
+ return;
625
+ }
626
+ settled = true;
627
+ clearTimeout(timer);
628
+ resolve(result);
629
+ };
630
+ const timer = setTimeout(() => {
631
+ timedOut = true;
632
+ child.kill();
633
+ finish({
634
+ attempted: true,
635
+ status: 'failed',
636
+ command: commandDisplay,
637
+ dotnet_cli_available: true,
638
+ exit_code: null,
639
+ timed_out: true,
640
+ stdout,
641
+ stderr,
642
+ error: `dotnet build timed out after ${effectiveTimeoutMs}ms`,
643
+ });
644
+ }, effectiveTimeoutMs);
645
+ child.stdout.on('data', (chunk) => {
646
+ stdout += chunk.toString();
647
+ });
648
+ child.stderr.on('data', (chunk) => {
649
+ stderr += chunk.toString();
650
+ });
651
+ child.on('error', (error) => {
652
+ const unavailable = error.code === 'ENOENT';
653
+ finish({
654
+ attempted: true,
655
+ status: unavailable ? 'unavailable' : 'failed',
656
+ command: commandDisplay,
657
+ dotnet_cli_available: !unavailable,
658
+ exit_code: null,
659
+ timed_out: false,
660
+ stdout,
661
+ stderr,
662
+ error: unavailable ? 'dotnet CLI is not available in PATH' : error.message,
663
+ });
664
+ });
665
+ child.on('close', (code) => {
666
+ if (timedOut) {
667
+ return;
668
+ }
669
+ finish({
670
+ attempted: true,
671
+ status: code === 0 ? 'passed' : 'failed',
672
+ command: commandDisplay,
673
+ dotnet_cli_available: true,
674
+ exit_code: code ?? null,
675
+ timed_out: false,
676
+ stdout,
677
+ stderr,
678
+ error: code === 0 ? null : `dotnet build failed with exit code ${code ?? 'unknown'}`,
679
+ });
680
+ });
681
+ });
682
+ }
683
+ async function sha256File(filePath) {
684
+ const content = await fs.readFile(filePath);
685
+ return createHash('sha256').update(content).digest('hex');
686
+ }
687
+ export function createDotNetReconstructExportHandler(workspaceManager, database, cacheManager, dependencies) {
688
+ const runtimeDetectHandler = dependencies?.runtimeDetectHandler ||
689
+ createRuntimeDetectHandler(workspaceManager, database, cacheManager);
690
+ const packerDetectHandler = dependencies?.packerDetectHandler ||
691
+ createPackerDetectHandler(workspaceManager, database, cacheManager);
692
+ const reconstructExportHandler = dependencies?.reconstructExportHandler ||
693
+ createCodeReconstructExportHandler(workspaceManager, database, cacheManager);
694
+ const dotNetMetadataHandler = dependencies?.dotNetMetadataHandler ||
695
+ createDotNetMetadataExtractHandler(workspaceManager, database, cacheManager);
696
+ const runBuildValidation = dependencies?.buildValidator || runDotNetBuildValidation;
697
+ return async (args) => {
698
+ const input = DotNetReconstructExportInputSchema.parse(args);
699
+ const startTime = Date.now();
700
+ try {
701
+ const sample = database.findSample(input.sample_id);
702
+ if (!sample) {
703
+ return {
704
+ ok: false,
705
+ errors: [`Sample not found: ${input.sample_id}`],
706
+ };
707
+ }
708
+ const runtimeResult = await runtimeDetectHandler({ sample_id: input.sample_id });
709
+ if (!runtimeResult.ok || !runtimeResult.data) {
710
+ return {
711
+ ok: false,
712
+ errors: runtimeResult.errors || ['runtime.detect failed'],
713
+ warnings: runtimeResult.warnings,
714
+ metrics: {
715
+ elapsed_ms: Date.now() - startTime,
716
+ tool: TOOL_NAME,
717
+ },
718
+ };
719
+ }
720
+ const runtime = runtimeResult.data;
721
+ if (!runtime.is_dotnet) {
722
+ const suspected = (runtime.suspected || [])
723
+ .map((item) => `${item.runtime}(${item.confidence.toFixed(2)})`)
724
+ .join(', ');
725
+ return {
726
+ ok: false,
727
+ errors: ['Target sample is not recognized as .NET runtime.'],
728
+ warnings: suspected.length > 0
729
+ ? [`runtime.detect suspected: ${suspected}`]
730
+ : ['runtime.detect found no .NET signal'],
731
+ metrics: {
732
+ elapsed_ms: Date.now() - startTime,
733
+ tool: TOOL_NAME,
734
+ },
735
+ };
736
+ }
737
+ const completedGhidraAnalysis = findBestGhidraAnalysis(database.findAnalysesBySample(input.sample_id), 'function_index');
738
+ const analysisMarker = completedGhidraAnalysis?.finished_at || completedGhidraAnalysis?.id || 'none';
739
+ const cacheKey = generateCacheKey({
740
+ sampleSha256: sample.sha256,
741
+ toolName: TOOL_NAME,
742
+ toolVersion: TOOL_VERSION,
743
+ args: {
744
+ topk: input.topk,
745
+ project_name: input.project_name,
746
+ namespace: input.namespace,
747
+ include_metadata_types: input.include_metadata_types,
748
+ max_managed_types: input.max_managed_types,
749
+ export_name: input.export_name || null,
750
+ include_obfuscation_fallback: input.include_obfuscation_fallback,
751
+ validate_build: input.validate_build,
752
+ build_timeout_ms: input.build_timeout_ms,
753
+ evidence_scope: input.evidence_scope,
754
+ evidence_session_tag: input.evidence_session_tag || null,
755
+ dotnet_version: runtime.dotnet_version || null,
756
+ target_framework: runtime.target_framework || null,
757
+ analysis_marker: analysisMarker,
758
+ },
759
+ });
760
+ if (input.reuse_cached) {
761
+ const cachedLookup = await lookupCachedResult(cacheManager, cacheKey);
762
+ if (cachedLookup) {
763
+ return {
764
+ ok: true,
765
+ data: cachedLookup.data,
766
+ warnings: ['Result from cache', formatCacheWarning(cachedLookup.metadata)],
767
+ metrics: {
768
+ elapsed_ms: Date.now() - startTime,
769
+ tool: TOOL_NAME,
770
+ cached: true,
771
+ cache_key: cachedLookup.metadata.key,
772
+ cache_tier: cachedLookup.metadata.tier,
773
+ cache_created_at: cachedLookup.metadata.createdAt,
774
+ cache_expires_at: cachedLookup.metadata.expiresAt,
775
+ cache_hit_at: cachedLookup.metadata.fetchedAt,
776
+ },
777
+ };
778
+ }
779
+ }
780
+ const warnings = [];
781
+ const packerResult = await packerDetectHandler({
782
+ sample_id: input.sample_id,
783
+ engines: ['yara', 'entropy', 'entrypoint'],
784
+ });
785
+ const packerData = (packerResult.ok ? packerResult.data : undefined);
786
+ const packed = packerData?.packed === true;
787
+ const packingConfidence = clamp(packerData?.confidence ?? 0, 0, 1);
788
+ if (!packerResult.ok) {
789
+ warnings.push(`packer.detect unavailable: ${(packerResult.errors || ['unknown']).join('; ')}`);
790
+ }
791
+ const baseExportName = input.export_name ||
792
+ `dotnet_${new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').replace('Z', '')}`;
793
+ const nativeExportName = `${baseExportName}_native`;
794
+ const reconstructExportResult = await reconstructExportHandler({
795
+ sample_id: input.sample_id,
796
+ topk: input.topk,
797
+ module_limit: 8,
798
+ min_module_size: 1,
799
+ include_imports: true,
800
+ include_strings: true,
801
+ export_name: nativeExportName,
802
+ evidence_scope: input.evidence_scope,
803
+ evidence_session_tag: input.evidence_session_tag,
804
+ reuse_cached: true,
805
+ });
806
+ if (reconstructExportResult.warnings && reconstructExportResult.warnings.length > 0) {
807
+ warnings.push(...reconstructExportResult.warnings.map((item) => `reconstruct: ${item}`));
808
+ }
809
+ let modules = [];
810
+ const degradationReasons = [];
811
+ if (reconstructExportResult.ok && reconstructExportResult.data) {
812
+ const reconstructedData = reconstructExportResult.data;
813
+ modules = reconstructedData.modules || [];
814
+ }
815
+ else {
816
+ warnings.push(`module reconstruction unavailable: ${(reconstructExportResult.errors || ['code.reconstruct.export failed']).join('; ')}`);
817
+ degradationReasons.push('module reconstruction unavailable');
818
+ }
819
+ let managedMetadata = null;
820
+ const metadataResult = await dotNetMetadataHandler({
821
+ sample_id: input.sample_id,
822
+ include_types: input.include_metadata_types,
823
+ include_methods: true,
824
+ max_types: input.max_managed_types,
825
+ max_methods_per_type: 24,
826
+ });
827
+ if (metadataResult.ok && metadataResult.data) {
828
+ managedMetadata = metadataResult.data;
829
+ if (metadataResult.warnings && metadataResult.warnings.length > 0) {
830
+ warnings.push(...metadataResult.warnings.map((item) => `metadata: ${item}`));
831
+ }
832
+ }
833
+ else {
834
+ warnings.push(`managed metadata unavailable: ${(metadataResult.errors || ['dotnet.metadata.extract failed']).join('; ')}`);
835
+ degradationReasons.push('managed metadata unavailable');
836
+ }
837
+ if (modules.length === 0 && !managedMetadata) {
838
+ return {
839
+ ok: false,
840
+ errors: ['Neither native reconstruction modules nor CLR metadata were available for export.'],
841
+ warnings,
842
+ metrics: {
843
+ elapsed_ms: Date.now() - startTime,
844
+ tool: TOOL_NAME,
845
+ },
846
+ };
847
+ }
848
+ const workspace = await workspaceManager.getWorkspace(input.sample_id);
849
+ const dotnetExportRoot = path.join(workspace.reports, 'dotnet_reconstruct', baseExportName);
850
+ const srcRoot = path.join(dotnetExportRoot, 'src');
851
+ const moduleSrcRoot = path.join(srcRoot, 'modules');
852
+ const typeSrcRoot = path.join(srcRoot, 'types');
853
+ await fs.mkdir(srcRoot, { recursive: true });
854
+ await fs.mkdir(moduleSrcRoot, { recursive: true });
855
+ await fs.mkdir(typeSrcRoot, { recursive: true });
856
+ const framework = mapTargetFramework(runtime.target_framework);
857
+ const csprojPath = path.join(dotnetExportRoot, `${sanitizeIdentifier(input.project_name)}.csproj`);
858
+ await fs.writeFile(csprojPath, buildCsproj(input.project_name, framework), 'utf-8');
859
+ const classOutputs = [];
860
+ for (const module of modules) {
861
+ const className = `${toPascalCase(module.name)}Module`;
862
+ const classFile = path.join(moduleSrcRoot, `${className}.cs`);
863
+ const classNamespace = buildNamespace(input.namespace, 'Modules');
864
+ await fs.writeFile(classFile, buildModuleClassContent(classNamespace, className, module.name, module.confidence, module.functions), 'utf-8');
865
+ classOutputs.push({
866
+ class_name: className,
867
+ source: 'module',
868
+ module: module.name,
869
+ namespace: classNamespace,
870
+ full_name: `${classNamespace}.${className}`,
871
+ kind: 'class',
872
+ confidence: module.confidence,
873
+ file_path: toPosixRelative(workspace.root, classFile),
874
+ methods: module.functions.map((method) => ({
875
+ name: sanitizeIdentifier(`m_${method.function}`),
876
+ address: method.address,
877
+ token: null,
878
+ confidence: method.confidence,
879
+ gaps: method.gaps,
880
+ })),
881
+ });
882
+ }
883
+ const usedMetadataFiles = new Set();
884
+ if (input.include_metadata_types && managedMetadata?.types?.length) {
885
+ for (const typeInfo of managedMetadata.types) {
886
+ const typeNamespaceSuffix = typeInfo.namespace && typeInfo.namespace.trim().length > 0
887
+ ? `Types.${typeInfo.namespace}`
888
+ : 'Types.Global';
889
+ const classNamespace = buildNamespace(input.namespace, typeNamespaceSuffix);
890
+ const relativeTypeDir = path.join(typeSrcRoot, ...(typeInfo.namespace && typeInfo.namespace.trim().length > 0
891
+ ? typeInfo.namespace.split('.').map((part) => sanitizeNamespaceSegment(part))
892
+ : ['Global']));
893
+ await fs.mkdir(relativeTypeDir, { recursive: true });
894
+ const classBaseName = sanitizeIdentifier(typeInfo.name);
895
+ let className = classBaseName;
896
+ let classFile = path.join(relativeTypeDir, `${className}.cs`);
897
+ while (usedMetadataFiles.has(classFile)) {
898
+ className = `${classBaseName}_${typeInfo.token.replace(/^0x/i, '')}`;
899
+ classFile = path.join(relativeTypeDir, `${className}.cs`);
900
+ }
901
+ usedMetadataFiles.add(classFile);
902
+ const relatedModules = inferTypeModuleHints(typeInfo, modules);
903
+ await fs.writeFile(classFile, buildMetadataClassContent(classNamespace, className, typeInfo, relatedModules), 'utf-8');
904
+ classOutputs.push({
905
+ class_name: className,
906
+ source: 'metadata',
907
+ module: typeInfo.namespace || '<global>',
908
+ namespace: classNamespace,
909
+ full_name: typeInfo.full_name,
910
+ kind: typeInfo.kind,
911
+ confidence: pickMetadataTypeConfidence(typeInfo),
912
+ file_path: toPosixRelative(workspace.root, classFile),
913
+ methods: typeInfo.methods.map((method) => ({
914
+ name: sanitizeIdentifier(method.name === '.ctor' ? 'Ctor' : method.name),
915
+ address: method.rva > 0 ? `0x${method.rva.toString(16)}` : null,
916
+ token: method.token,
917
+ confidence: pickMetadataTypeConfidence(typeInfo),
918
+ gaps: ['metadata_only_rewrite'],
919
+ })),
920
+ });
921
+ }
922
+ }
923
+ const lowConfidenceMethods = collectLowConfidenceMethods(modules);
924
+ if (packed || packingConfidence >= 0.6) {
925
+ degradationReasons.push(`packer/obfuscation signal=${packed} confidence=${packingConfidence.toFixed(2)}`);
926
+ }
927
+ if (lowConfidenceMethods.length > 0) {
928
+ degradationReasons.push(`low-confidence methods detected: ${lowConfidenceMethods.length}`);
929
+ }
930
+ if (managedMetadata && input.include_metadata_types && managedMetadata.types.length === 0) {
931
+ degradationReasons.push('managed metadata contained no analyst-facing types after filtering');
932
+ }
933
+ const managedProfile = buildManagedProfile(managedMetadata, packed, packingConfidence);
934
+ let buildValidation = {
935
+ attempted: false,
936
+ status: 'skipped',
937
+ command: null,
938
+ dotnet_cli_available: false,
939
+ exit_code: null,
940
+ timed_out: false,
941
+ stdout: '',
942
+ stderr: '',
943
+ error: null,
944
+ };
945
+ let buildLogPath = null;
946
+ if (input.validate_build) {
947
+ buildValidation = await runBuildValidation(csprojPath, dotnetExportRoot, input.build_timeout_ms);
948
+ buildLogPath = path.join(dotnetExportRoot, 'BUILD_VALIDATION.log');
949
+ await fs.writeFile(buildLogPath, buildValidationLog(buildValidation), 'utf-8');
950
+ if (buildValidation.status === 'failed') {
951
+ warnings.push('dotnet build validation failed; review BUILD_VALIDATION.log before using exported project.');
952
+ degradationReasons.push('dotnet build validation failed');
953
+ }
954
+ else if (buildValidation.status === 'unavailable') {
955
+ warnings.push('dotnet CLI unavailable; skipped compile validation (export still generated).');
956
+ }
957
+ }
958
+ const degradedMode = degradationReasons.length > 0;
959
+ const readmePath = path.join(dotnetExportRoot, 'README.md');
960
+ await fs.writeFile(readmePath, buildReadme(input, runtime, packed, packingConfidence, classOutputs.length, degradedMode, degradationReasons, managedProfile), 'utf-8');
961
+ const reverseNotesPath = path.join(dotnetExportRoot, 'REVERSE_NOTES.md');
962
+ await fs.writeFile(reverseNotesPath, buildDotNetReverseNotes(input, runtime, managedProfile, modules, packed, packingConfidence, degradationReasons, warnings), 'utf-8');
963
+ let metadataPath = null;
964
+ if (managedMetadata) {
965
+ metadataPath = path.join(dotnetExportRoot, 'MANAGED_METADATA.json');
966
+ await fs.writeFile(metadataPath, JSON.stringify(managedMetadata, null, 2), 'utf-8');
967
+ }
968
+ let fallbackPath = null;
969
+ if (input.include_obfuscation_fallback && degradedMode) {
970
+ fallbackPath = path.join(dotnetExportRoot, 'IL_FALLBACK_NOTES.md');
971
+ await fs.writeFile(fallbackPath, buildFallbackNotes(runtime, packed, degradationReasons, lowConfidenceMethods), 'utf-8');
972
+ warnings.push('Degraded reconstruction detected; generated IL fallback notes with priority methods.');
973
+ }
974
+ const artifacts = [];
975
+ const csprojSha = await sha256File(csprojPath);
976
+ const csprojArtifactId = randomUUID();
977
+ const csprojRelative = toPosixRelative(workspace.root, csprojPath);
978
+ database.insertArtifact({
979
+ id: csprojArtifactId,
980
+ sample_id: input.sample_id,
981
+ type: 'dotnet_csproj',
982
+ path: csprojRelative,
983
+ sha256: csprojSha,
984
+ mime: 'text/xml',
985
+ created_at: new Date().toISOString(),
986
+ });
987
+ artifacts.push({
988
+ id: csprojArtifactId,
989
+ type: 'dotnet_csproj',
990
+ path: csprojRelative,
991
+ sha256: csprojSha,
992
+ mime: 'text/xml',
993
+ });
994
+ const readmeSha = await sha256File(readmePath);
995
+ const readmeArtifactId = randomUUID();
996
+ const readmeRelative = toPosixRelative(workspace.root, readmePath);
997
+ database.insertArtifact({
998
+ id: readmeArtifactId,
999
+ sample_id: input.sample_id,
1000
+ type: 'dotnet_readme',
1001
+ path: readmeRelative,
1002
+ sha256: readmeSha,
1003
+ mime: 'text/markdown',
1004
+ created_at: new Date().toISOString(),
1005
+ });
1006
+ artifacts.push({
1007
+ id: readmeArtifactId,
1008
+ type: 'dotnet_readme',
1009
+ path: readmeRelative,
1010
+ sha256: readmeSha,
1011
+ mime: 'text/markdown',
1012
+ });
1013
+ const reverseNotesSha = await sha256File(reverseNotesPath);
1014
+ const reverseNotesArtifactId = randomUUID();
1015
+ const reverseNotesRelative = toPosixRelative(workspace.root, reverseNotesPath);
1016
+ database.insertArtifact({
1017
+ id: reverseNotesArtifactId,
1018
+ sample_id: input.sample_id,
1019
+ type: 'dotnet_reverse_notes',
1020
+ path: reverseNotesRelative,
1021
+ sha256: reverseNotesSha,
1022
+ mime: 'text/markdown',
1023
+ created_at: new Date().toISOString(),
1024
+ });
1025
+ artifacts.push({
1026
+ id: reverseNotesArtifactId,
1027
+ type: 'dotnet_reverse_notes',
1028
+ path: reverseNotesRelative,
1029
+ sha256: reverseNotesSha,
1030
+ mime: 'text/markdown',
1031
+ });
1032
+ let metadataRelative = null;
1033
+ if (metadataPath) {
1034
+ const metadataSha = await sha256File(metadataPath);
1035
+ const metadataArtifactId = randomUUID();
1036
+ metadataRelative = toPosixRelative(workspace.root, metadataPath);
1037
+ database.insertArtifact({
1038
+ id: metadataArtifactId,
1039
+ sample_id: input.sample_id,
1040
+ type: 'dotnet_metadata',
1041
+ path: metadataRelative,
1042
+ sha256: metadataSha,
1043
+ mime: 'application/json',
1044
+ created_at: new Date().toISOString(),
1045
+ });
1046
+ artifacts.push({
1047
+ id: metadataArtifactId,
1048
+ type: 'dotnet_metadata',
1049
+ path: metadataRelative,
1050
+ sha256: metadataSha,
1051
+ mime: 'application/json',
1052
+ });
1053
+ }
1054
+ let buildLogRelative = null;
1055
+ if (buildLogPath) {
1056
+ const buildLogSha = await sha256File(buildLogPath);
1057
+ const buildLogArtifactId = randomUUID();
1058
+ buildLogRelative = toPosixRelative(workspace.root, buildLogPath);
1059
+ database.insertArtifact({
1060
+ id: buildLogArtifactId,
1061
+ sample_id: input.sample_id,
1062
+ type: 'dotnet_build_log',
1063
+ path: buildLogRelative,
1064
+ sha256: buildLogSha,
1065
+ mime: 'text/plain',
1066
+ created_at: new Date().toISOString(),
1067
+ });
1068
+ artifacts.push({
1069
+ id: buildLogArtifactId,
1070
+ type: 'dotnet_build_log',
1071
+ path: buildLogRelative,
1072
+ sha256: buildLogSha,
1073
+ mime: 'text/plain',
1074
+ });
1075
+ }
1076
+ if (fallbackPath) {
1077
+ const fallbackSha = await sha256File(fallbackPath);
1078
+ const fallbackArtifactId = randomUUID();
1079
+ const fallbackRelative = toPosixRelative(workspace.root, fallbackPath);
1080
+ database.insertArtifact({
1081
+ id: fallbackArtifactId,
1082
+ sample_id: input.sample_id,
1083
+ type: 'dotnet_il_fallback',
1084
+ path: fallbackRelative,
1085
+ sha256: fallbackSha,
1086
+ mime: 'text/markdown',
1087
+ created_at: new Date().toISOString(),
1088
+ });
1089
+ artifacts.push({
1090
+ id: fallbackArtifactId,
1091
+ type: 'dotnet_il_fallback',
1092
+ path: fallbackRelative,
1093
+ sha256: fallbackSha,
1094
+ mime: 'text/markdown',
1095
+ });
1096
+ }
1097
+ const outputData = {
1098
+ sample_id: input.sample_id,
1099
+ is_dotnet: true,
1100
+ dotnet_version: runtime.dotnet_version || null,
1101
+ target_framework: runtime.target_framework || null,
1102
+ packed,
1103
+ packing_confidence: packingConfidence,
1104
+ export_root: toPosixRelative(workspace.root, dotnetExportRoot),
1105
+ csproj_path: csprojRelative,
1106
+ readme_path: readmeRelative,
1107
+ metadata_path: metadataRelative,
1108
+ reverse_notes_path: reverseNotesRelative,
1109
+ degraded_mode: degradedMode,
1110
+ degradation_reasons: degradationReasons,
1111
+ build_validation: {
1112
+ attempted: buildValidation.attempted,
1113
+ status: buildValidation.status,
1114
+ command: buildValidation.command,
1115
+ dotnet_cli_available: buildValidation.dotnet_cli_available,
1116
+ exit_code: buildValidation.exit_code,
1117
+ timed_out: buildValidation.timed_out,
1118
+ error: buildValidation.error,
1119
+ log_path: buildLogRelative,
1120
+ },
1121
+ managed_profile: managedProfile,
1122
+ fallback_notes_path: fallbackPath
1123
+ ? toPosixRelative(workspace.root, fallbackPath)
1124
+ : null,
1125
+ classes: classOutputs,
1126
+ };
1127
+ await cacheManager.setCachedResult(cacheKey, outputData, CACHE_TTL_MS, sample.sha256);
1128
+ return {
1129
+ ok: true,
1130
+ data: outputData,
1131
+ warnings: warnings.length > 0 ? warnings : undefined,
1132
+ artifacts,
1133
+ metrics: {
1134
+ elapsed_ms: Date.now() - startTime,
1135
+ tool: TOOL_NAME,
1136
+ },
1137
+ };
1138
+ }
1139
+ catch (error) {
1140
+ return {
1141
+ ok: false,
1142
+ errors: [normalizeError(error)],
1143
+ metrics: {
1144
+ elapsed_ms: Date.now() - startTime,
1145
+ tool: TOOL_NAME,
1146
+ },
1147
+ };
1148
+ }
1149
+ };
1150
+ }
1151
+ //# sourceMappingURL=dotnet-reconstruct-export.js.map