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,4002 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* code.reconstruct.export tool implementation
|
|
3
|
+
* Module-level regrouping + source-like project skeleton export.
|
|
4
|
+
*/
|
|
5
|
+
import { createHash, randomUUID } from 'crypto';
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { generateCacheKey } from '../cache-manager.js';
|
|
11
|
+
import { lookupCachedResult, formatCacheWarning } from './cache-observability.js';
|
|
12
|
+
import { createCodeFunctionsReconstructHandler } from './code-functions-reconstruct.js';
|
|
13
|
+
import { createPEImportsExtractHandler } from './pe-imports-extract.js';
|
|
14
|
+
import { createPEExportsExtractHandler } from './pe-exports-extract.js';
|
|
15
|
+
import { createPackerDetectHandler } from './packer-detect.js';
|
|
16
|
+
import { createStringsExtractHandler } from './strings-extract.js';
|
|
17
|
+
import { DecompilerWorker, } from '../decompiler-worker.js';
|
|
18
|
+
import { findBestGhidraAnalysis } from '../ghidra-analysis-status.js';
|
|
19
|
+
import { ghidraConfig } from '../ghidra-config.js';
|
|
20
|
+
import { loadDynamicTraceEvidence } from '../dynamic-trace.js';
|
|
21
|
+
import { getPackageRoot } from '../runtime-paths.js';
|
|
22
|
+
import { correlateFunctionWithRuntimeEvidence, modulesSuggestedByRuntimeStages, } from '../runtime-correlation.js';
|
|
23
|
+
import { findSemanticFunctionExplanation, loadSemanticFunctionExplanationIndex, loadSemanticNameSuggestionIndex, SEMANTIC_FUNCTION_EXPLANATIONS_ARTIFACT_TYPE, SEMANTIC_NAME_SUGGESTIONS_ARTIFACT_TYPE, } from '../semantic-name-suggestion-artifacts.js';
|
|
24
|
+
import { AnalysisProvenanceSchema, buildRuntimeArtifactProvenance, buildSemanticArtifactProvenance, } from '../analysis-provenance.js';
|
|
25
|
+
const TOOL_NAME = 'code.reconstruct.export';
|
|
26
|
+
const TOOL_VERSION = '0.2.13';
|
|
27
|
+
const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
28
|
+
export const CodeReconstructExportInputSchema = z.object({
|
|
29
|
+
sample_id: z.string().describe('Sample ID (format: sha256:<hex>)'),
|
|
30
|
+
topk: z
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(40)
|
|
35
|
+
.default(12)
|
|
36
|
+
.describe('How many high-value functions to include for module regrouping'),
|
|
37
|
+
module_limit: z
|
|
38
|
+
.number()
|
|
39
|
+
.int()
|
|
40
|
+
.min(1)
|
|
41
|
+
.max(12)
|
|
42
|
+
.default(6)
|
|
43
|
+
.describe('Maximum module count in exported skeleton'),
|
|
44
|
+
min_module_size: z
|
|
45
|
+
.number()
|
|
46
|
+
.int()
|
|
47
|
+
.min(1)
|
|
48
|
+
.max(20)
|
|
49
|
+
.default(2)
|
|
50
|
+
.describe('Modules with fewer functions than this threshold are merged into core'),
|
|
51
|
+
include_imports: z
|
|
52
|
+
.boolean()
|
|
53
|
+
.default(true)
|
|
54
|
+
.describe('Use import features for module hints'),
|
|
55
|
+
include_strings: z
|
|
56
|
+
.boolean()
|
|
57
|
+
.default(true)
|
|
58
|
+
.describe('Use high-value string clusters for module hints'),
|
|
59
|
+
export_name: z
|
|
60
|
+
.string()
|
|
61
|
+
.min(1)
|
|
62
|
+
.max(64)
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('Optional export folder name; default auto-generated'),
|
|
65
|
+
validate_build: z
|
|
66
|
+
.boolean()
|
|
67
|
+
.default(true)
|
|
68
|
+
.describe('Compile the exported C skeleton with clang when available'),
|
|
69
|
+
run_harness: z
|
|
70
|
+
.boolean()
|
|
71
|
+
.default(true)
|
|
72
|
+
.describe('Execute reconstruct_harness after a successful build validation'),
|
|
73
|
+
compiler_path: z
|
|
74
|
+
.string()
|
|
75
|
+
.min(1)
|
|
76
|
+
.max(260)
|
|
77
|
+
.optional()
|
|
78
|
+
.describe('Optional explicit clang compiler path'),
|
|
79
|
+
build_timeout_ms: z
|
|
80
|
+
.number()
|
|
81
|
+
.int()
|
|
82
|
+
.min(5000)
|
|
83
|
+
.max(300000)
|
|
84
|
+
.default(60000)
|
|
85
|
+
.describe('Timeout for clang build validation in milliseconds'),
|
|
86
|
+
run_timeout_ms: z
|
|
87
|
+
.number()
|
|
88
|
+
.int()
|
|
89
|
+
.min(5000)
|
|
90
|
+
.max(300000)
|
|
91
|
+
.default(30000)
|
|
92
|
+
.describe('Timeout for reconstruct_harness execution in milliseconds'),
|
|
93
|
+
evidence_scope: z
|
|
94
|
+
.enum(['all', 'latest', 'session'])
|
|
95
|
+
.default('all')
|
|
96
|
+
.describe('Runtime evidence scope: all artifacts, only the latest artifact window, or a specific session selector'),
|
|
97
|
+
evidence_session_tag: z
|
|
98
|
+
.string()
|
|
99
|
+
.optional()
|
|
100
|
+
.describe('Optional runtime evidence session selector used when evidence_scope=session or to narrow all/latest results'),
|
|
101
|
+
semantic_scope: z
|
|
102
|
+
.enum(['all', 'latest', 'session'])
|
|
103
|
+
.default('all')
|
|
104
|
+
.describe('Semantic review artifact scope: all artifacts, only the latest semantic artifact window, or a specific semantic review session'),
|
|
105
|
+
semantic_session_tag: z
|
|
106
|
+
.string()
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('Optional semantic review session selector used when semantic_scope=session or to narrow all/latest results'),
|
|
109
|
+
reuse_cached: z
|
|
110
|
+
.boolean()
|
|
111
|
+
.default(true)
|
|
112
|
+
.describe('When true, reuse cached export result for identical inputs'),
|
|
113
|
+
})
|
|
114
|
+
.refine((value) => value.evidence_scope !== 'session' || Boolean(value.evidence_session_tag?.trim()), {
|
|
115
|
+
message: 'evidence_session_tag is required when evidence_scope=session',
|
|
116
|
+
path: ['evidence_session_tag'],
|
|
117
|
+
})
|
|
118
|
+
.refine((value) => value.semantic_scope !== 'session' || Boolean(value.semantic_session_tag?.trim()), {
|
|
119
|
+
message: 'semantic_session_tag is required when semantic_scope=session',
|
|
120
|
+
path: ['semantic_session_tag'],
|
|
121
|
+
});
|
|
122
|
+
const ModuleFunctionSchema = z.object({
|
|
123
|
+
function: z.string(),
|
|
124
|
+
address: z.string(),
|
|
125
|
+
confidence: z.number().min(0).max(1),
|
|
126
|
+
gaps: z.array(z.string()),
|
|
127
|
+
suggested_name: z.string().nullable().optional(),
|
|
128
|
+
suggested_role: z.string().nullable().optional(),
|
|
129
|
+
rename_confidence: z.number().min(0).max(1).nullable().optional(),
|
|
130
|
+
rule_based_name: z.string().nullable().optional(),
|
|
131
|
+
llm_suggested_name: z.string().nullable().optional(),
|
|
132
|
+
validated_name: z.string().nullable().optional(),
|
|
133
|
+
name_resolution_source: z.enum(['rule', 'llm', 'hybrid', 'unresolved']).nullable().optional(),
|
|
134
|
+
explanation_summary: z.string().nullable().optional(),
|
|
135
|
+
explanation_behavior: z.string().nullable().optional(),
|
|
136
|
+
explanation_confidence: z.number().min(0).max(1).nullable().optional(),
|
|
137
|
+
});
|
|
138
|
+
const ModuleSchema = z.object({
|
|
139
|
+
name: z.string(),
|
|
140
|
+
confidence: z.number().min(0).max(1),
|
|
141
|
+
function_count: z.number().int().nonnegative(),
|
|
142
|
+
import_hints: z.array(z.string()),
|
|
143
|
+
string_hints: z.array(z.string()),
|
|
144
|
+
runtime_apis: z.array(z.string()),
|
|
145
|
+
runtime_stages: z.array(z.string()),
|
|
146
|
+
interface_path: z.string(),
|
|
147
|
+
pseudocode_path: z.string(),
|
|
148
|
+
rewrite_path: z.string(),
|
|
149
|
+
functions: z.array(ModuleFunctionSchema),
|
|
150
|
+
});
|
|
151
|
+
const CliProfileSchema = z.object({
|
|
152
|
+
tool_name: z.string(),
|
|
153
|
+
help_banner: z.string(),
|
|
154
|
+
command_count: z.number().int().nonnegative(),
|
|
155
|
+
commands: z.array(z.object({
|
|
156
|
+
verb: z.string(),
|
|
157
|
+
summary: z.string(),
|
|
158
|
+
})),
|
|
159
|
+
});
|
|
160
|
+
const BinaryProfileSchema = z.object({
|
|
161
|
+
binary_role: z.string(),
|
|
162
|
+
original_filename: z.string().nullable(),
|
|
163
|
+
export_count: z.number().int().nonnegative(),
|
|
164
|
+
forwarder_count: z.number().int().nonnegative(),
|
|
165
|
+
notable_exports: z.array(z.string()),
|
|
166
|
+
packed: z.boolean(),
|
|
167
|
+
packing_confidence: z.number().min(0).max(1),
|
|
168
|
+
analysis_priorities: z.array(z.string()),
|
|
169
|
+
cli_profile: CliProfileSchema.nullable().optional(),
|
|
170
|
+
});
|
|
171
|
+
const NativeBuildValidationSchema = z.object({
|
|
172
|
+
attempted: z.boolean(),
|
|
173
|
+
status: z.enum(['passed', 'failed', 'skipped', 'unavailable']),
|
|
174
|
+
compiler: z.string().nullable(),
|
|
175
|
+
compiler_path: z.string().nullable(),
|
|
176
|
+
command: z.string().nullable(),
|
|
177
|
+
exit_code: z.number().int().nullable(),
|
|
178
|
+
timed_out: z.boolean(),
|
|
179
|
+
error: z.string().nullable(),
|
|
180
|
+
log_path: z.string().nullable(),
|
|
181
|
+
executable_path: z.string().nullable(),
|
|
182
|
+
});
|
|
183
|
+
const HarnessValidationSchema = z.object({
|
|
184
|
+
attempted: z.boolean(),
|
|
185
|
+
status: z.enum(['passed', 'failed', 'skipped', 'unavailable']),
|
|
186
|
+
command: z.string().nullable(),
|
|
187
|
+
exit_code: z.number().int().nullable(),
|
|
188
|
+
timed_out: z.boolean(),
|
|
189
|
+
error: z.string().nullable(),
|
|
190
|
+
log_path: z.string().nullable(),
|
|
191
|
+
matched_entries: z.number().int().nonnegative(),
|
|
192
|
+
mismatched_entries: z.number().int().nonnegative(),
|
|
193
|
+
});
|
|
194
|
+
export const CodeReconstructExportOutputSchema = z.object({
|
|
195
|
+
ok: z.boolean(),
|
|
196
|
+
data: z
|
|
197
|
+
.object({
|
|
198
|
+
sample_id: z.string(),
|
|
199
|
+
export_root: z.string(),
|
|
200
|
+
manifest_path: z.string(),
|
|
201
|
+
gaps_path: z.string(),
|
|
202
|
+
notes_path: z.string(),
|
|
203
|
+
cli_model_path: z.string(),
|
|
204
|
+
support_header_path: z.string(),
|
|
205
|
+
harness_path: z.string(),
|
|
206
|
+
build_manifest_path: z.string(),
|
|
207
|
+
build_validation: NativeBuildValidationSchema,
|
|
208
|
+
harness_validation: HarnessValidationSchema,
|
|
209
|
+
module_count: z.number().int().nonnegative(),
|
|
210
|
+
unresolved_count: z.number().int().nonnegative(),
|
|
211
|
+
binary_profile: BinaryProfileSchema,
|
|
212
|
+
runtime_evidence: z
|
|
213
|
+
.object({
|
|
214
|
+
executed: z.boolean(),
|
|
215
|
+
api_count: z.number().int().nonnegative(),
|
|
216
|
+
stage_count: z.number().int().nonnegative(),
|
|
217
|
+
observed_apis: z.array(z.string()),
|
|
218
|
+
region_types: z.array(z.string()).optional(),
|
|
219
|
+
observed_modules: z.array(z.string()).optional(),
|
|
220
|
+
observed_strings: z.array(z.string()).optional(),
|
|
221
|
+
stages: z.array(z.string()),
|
|
222
|
+
summary: z.string(),
|
|
223
|
+
})
|
|
224
|
+
.nullable(),
|
|
225
|
+
provenance: AnalysisProvenanceSchema,
|
|
226
|
+
modules: z.array(ModuleSchema),
|
|
227
|
+
})
|
|
228
|
+
.optional(),
|
|
229
|
+
warnings: z.array(z.string()).optional(),
|
|
230
|
+
errors: z.array(z.string()).optional(),
|
|
231
|
+
artifacts: z.array(z.any()).optional(),
|
|
232
|
+
metrics: z
|
|
233
|
+
.object({
|
|
234
|
+
elapsed_ms: z.number(),
|
|
235
|
+
tool: z.string(),
|
|
236
|
+
cached: z.boolean().optional(),
|
|
237
|
+
cache_key: z.string().optional(),
|
|
238
|
+
cache_tier: z.string().optional(),
|
|
239
|
+
cache_created_at: z.string().optional(),
|
|
240
|
+
cache_expires_at: z.string().optional(),
|
|
241
|
+
cache_hit_at: z.string().optional(),
|
|
242
|
+
})
|
|
243
|
+
.optional(),
|
|
244
|
+
});
|
|
245
|
+
export const codeReconstructExportToolDefinition = {
|
|
246
|
+
name: TOOL_NAME,
|
|
247
|
+
description: 'Regroup recovered functions into source-like modules and export project skeleton with manifest and gaps.md.',
|
|
248
|
+
inputSchema: CodeReconstructExportInputSchema,
|
|
249
|
+
outputSchema: CodeReconstructExportOutputSchema,
|
|
250
|
+
};
|
|
251
|
+
const BEHAVIOR_TO_MODULE = {
|
|
252
|
+
process_injection: 'process_ops',
|
|
253
|
+
process_spawn: 'process_ops',
|
|
254
|
+
networking: 'network_ops',
|
|
255
|
+
file_io: 'file_ops',
|
|
256
|
+
registry: 'registry_ops',
|
|
257
|
+
crypto: 'crypto_ops',
|
|
258
|
+
anti_debug: 'anti_analysis',
|
|
259
|
+
service_control: 'service_ops',
|
|
260
|
+
};
|
|
261
|
+
const IMPORT_TO_MODULE_HINT = [
|
|
262
|
+
{ module: 'network_ops', matcher: /^(ws2_32|wininet|winhttp|dnsapi)\.dll$/i },
|
|
263
|
+
{ module: 'registry_ops', matcher: /^advapi32\.dll$/i },
|
|
264
|
+
{ module: 'gui_ops', matcher: /^user32\.dll$/i },
|
|
265
|
+
{ module: 'file_ops', matcher: /^(kernel32|ntdll)\.dll$/i },
|
|
266
|
+
{ module: 'crypto_ops', matcher: /^(crypt32|bcrypt)\.dll$/i },
|
|
267
|
+
];
|
|
268
|
+
const API_TO_MODULE_HINT = [
|
|
269
|
+
{
|
|
270
|
+
module: 'process_ops',
|
|
271
|
+
matcher: /\b(OpenProcess|CreateProcess|CreateRemoteThread|WriteProcessMemory|ReadProcessMemory|VirtualAllocEx|SetThreadContext|ResumeThread|NtWriteVirtualMemory|NtQueryInformationProcess)\b/i,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
module: 'network_ops',
|
|
275
|
+
matcher: /\b(socket|connect|send|recv|WSAStartup|InternetOpen|InternetConnect|HttpOpenRequest|HttpSendRequest|WinHttp|URLDownloadToFile)\b/i,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
module: 'file_ops',
|
|
279
|
+
matcher: /\b(CreateFile\w*|WriteFile\w*|ReadFile\w*|DeleteFile\w*|MoveFile\w*|CopyFile\w*|GetTempPath\w*|FindFirstFile\w*|FindNextFile\w*)\b/i,
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
module: 'registry_ops',
|
|
283
|
+
matcher: /\b(Reg(Open|Create|Set|Query|Delete)Key\w*|RegSetValue\w*|RegQueryValue\w*)\b/i,
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
module: 'crypto_ops',
|
|
287
|
+
matcher: /\b(CryptAcquire|CryptEncrypt|CryptDecrypt|BCrypt|RtlEncrypt|RtlDecrypt)\b/i,
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
module: 'service_ops',
|
|
291
|
+
matcher: /\b(OpenSCManager|CreateService|StartService|ControlService|DeleteService)\b/i,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
module: 'anti_analysis',
|
|
295
|
+
matcher: /\b(IsDebuggerPresent|CheckRemoteDebuggerPresent|NtQuerySystemInformation|NtQueryInformationProcess|OutputDebugString)\b/i,
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
module: 'packer_analysis',
|
|
299
|
+
matcher: /\b(section|entropy|packer|unpack|signature|overlay|pe header|goblin|iced-x86|disasm|recon)\b/i,
|
|
300
|
+
},
|
|
301
|
+
];
|
|
302
|
+
function clamp(value, min, max) {
|
|
303
|
+
return Math.max(min, Math.min(max, value));
|
|
304
|
+
}
|
|
305
|
+
function toPosixRelative(fromRoot, absoluteFile) {
|
|
306
|
+
return path.relative(fromRoot, absoluteFile).split(path.sep).join('/');
|
|
307
|
+
}
|
|
308
|
+
function normalizeError(error) {
|
|
309
|
+
if (error instanceof Error) {
|
|
310
|
+
return error.message;
|
|
311
|
+
}
|
|
312
|
+
return String(error);
|
|
313
|
+
}
|
|
314
|
+
function normalizeReadableHint(value, maxLength = 96) {
|
|
315
|
+
return value.replace(/\s+/g, ' ').trim().slice(0, maxLength);
|
|
316
|
+
}
|
|
317
|
+
function normalizeExplanationText(value, maxLength = 220) {
|
|
318
|
+
if (!value) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
322
|
+
return normalized.length > 0 ? normalized.slice(0, maxLength) : null;
|
|
323
|
+
}
|
|
324
|
+
function escapeCString(value) {
|
|
325
|
+
return value
|
|
326
|
+
.replace(/\\/g, '\\\\')
|
|
327
|
+
.replace(/"/g, '\\"')
|
|
328
|
+
.replace(/\r/g, '\\r')
|
|
329
|
+
.replace(/\n/g, '\\n')
|
|
330
|
+
.replace(/\t/g, '\\t');
|
|
331
|
+
}
|
|
332
|
+
function isReadableTextCandidate(value) {
|
|
333
|
+
const normalized = normalizeReadableHint(value, 160);
|
|
334
|
+
if (normalized.length < 4) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
const printable = (normalized.match(/[ -~]/g) || []).length;
|
|
338
|
+
const ratio = printable / normalized.length;
|
|
339
|
+
return ratio >= 0.8 && /[A-Za-z]/.test(normalized);
|
|
340
|
+
}
|
|
341
|
+
function sanitizeModuleName(name) {
|
|
342
|
+
const cleaned = name.toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '');
|
|
343
|
+
return cleaned.length > 0 ? cleaned : 'core';
|
|
344
|
+
}
|
|
345
|
+
function sanitizeSymbolForHeader(symbol) {
|
|
346
|
+
const cleaned = symbol.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
347
|
+
if (cleaned.length === 0) {
|
|
348
|
+
return 'func_unknown';
|
|
349
|
+
}
|
|
350
|
+
if (/^[0-9]/.test(cleaned)) {
|
|
351
|
+
return `func_${cleaned}`;
|
|
352
|
+
}
|
|
353
|
+
return cleaned;
|
|
354
|
+
}
|
|
355
|
+
async function attachFunctionExplanations(workspaceManager, database, sampleId, functions, options) {
|
|
356
|
+
const explanationIndex = await loadSemanticFunctionExplanationIndex(workspaceManager, database, sampleId, {
|
|
357
|
+
scope: options?.scope,
|
|
358
|
+
sessionTag: options?.sessionTag,
|
|
359
|
+
});
|
|
360
|
+
for (const func of functions) {
|
|
361
|
+
const explanation = findSemanticFunctionExplanation(explanationIndex, func.address, func.function);
|
|
362
|
+
if (!explanation) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
func.explanation_resolution = {
|
|
366
|
+
summary: explanation.summary,
|
|
367
|
+
behavior: explanation.behavior,
|
|
368
|
+
confidence: explanation.confidence,
|
|
369
|
+
assumptions: explanation.assumptions,
|
|
370
|
+
evidence_used: explanation.evidence_used,
|
|
371
|
+
rewrite_guidance: explanation.rewrite_guidance,
|
|
372
|
+
source: 'llm',
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
return explanationIndex;
|
|
376
|
+
}
|
|
377
|
+
function addHint(hints, module, value) {
|
|
378
|
+
const normalized = normalizeReadableHint(value);
|
|
379
|
+
if (!isReadableTextCandidate(normalized)) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const current = hints.get(module) || [];
|
|
383
|
+
if (!current.includes(normalized)) {
|
|
384
|
+
current.push(normalized);
|
|
385
|
+
hints.set(module, current);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function inferModulesFromText(text, categories = []) {
|
|
389
|
+
const modules = new Set();
|
|
390
|
+
const lowered = text.toLowerCase();
|
|
391
|
+
if (categories.includes('network') || categories.includes('url')) {
|
|
392
|
+
modules.add('network_ops');
|
|
393
|
+
}
|
|
394
|
+
if (categories.includes('registry')) {
|
|
395
|
+
modules.add('registry_ops');
|
|
396
|
+
}
|
|
397
|
+
if (categories.includes('file_path')) {
|
|
398
|
+
modules.add('file_ops');
|
|
399
|
+
}
|
|
400
|
+
if (categories.includes('command')) {
|
|
401
|
+
modules.add('process_ops');
|
|
402
|
+
}
|
|
403
|
+
if (categories.includes('suspicious_api')) {
|
|
404
|
+
modules.add('process_ops');
|
|
405
|
+
}
|
|
406
|
+
for (const mapper of API_TO_MODULE_HINT) {
|
|
407
|
+
if (mapper.matcher.test(text)) {
|
|
408
|
+
modules.add(mapper.module);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (/\b(akasha|auto recon|recon|telemetry|enumerate|analysis)\b/i.test(text)) {
|
|
412
|
+
modules.add('packer_analysis');
|
|
413
|
+
}
|
|
414
|
+
if (/\b(pack(er)? detection|protection|entropy|section|signature)\b/i.test(lowered)) {
|
|
415
|
+
modules.add('packer_analysis');
|
|
416
|
+
}
|
|
417
|
+
return Array.from(modules);
|
|
418
|
+
}
|
|
419
|
+
function detectModuleByNameOrReasons(func) {
|
|
420
|
+
const xrefApis = (func.xref_signals || []).map((item) => item.api).join(' ');
|
|
421
|
+
const callContext = [
|
|
422
|
+
...(func.call_context?.callers || []),
|
|
423
|
+
...(func.call_context?.callees || []),
|
|
424
|
+
].join(' ');
|
|
425
|
+
const corpus = [
|
|
426
|
+
func.function,
|
|
427
|
+
(func.rank_reasons || []).join(' '),
|
|
428
|
+
func.semantic_summary || '',
|
|
429
|
+
func.source_like_snippet,
|
|
430
|
+
xrefApis,
|
|
431
|
+
callContext,
|
|
432
|
+
].join(' ');
|
|
433
|
+
const lowered = corpus.toLowerCase();
|
|
434
|
+
if (/socket|http|internet|dns|connect|send|recv/.test(lowered)) {
|
|
435
|
+
return 'network_ops';
|
|
436
|
+
}
|
|
437
|
+
if (/process|createprocess|inject|thread|virtualalloc/.test(lowered)) {
|
|
438
|
+
return 'process_ops';
|
|
439
|
+
}
|
|
440
|
+
if (/reg(set|open|create)|registry/.test(lowered)) {
|
|
441
|
+
return 'registry_ops';
|
|
442
|
+
}
|
|
443
|
+
if (/file|writefile|readfile|deletefile/.test(lowered)) {
|
|
444
|
+
return 'file_ops';
|
|
445
|
+
}
|
|
446
|
+
if (/crypt|bcrypt|hash|encrypt|decrypt/.test(lowered)) {
|
|
447
|
+
return 'crypto_ops';
|
|
448
|
+
}
|
|
449
|
+
if (/service|scm|startservice/.test(lowered)) {
|
|
450
|
+
return 'service_ops';
|
|
451
|
+
}
|
|
452
|
+
if (/debug|antidebug|ntqueryinformationprocess/.test(lowered)) {
|
|
453
|
+
return 'anti_analysis';
|
|
454
|
+
}
|
|
455
|
+
if (/packer|entropy|section|signature|goblin|iced-x86|recon|analysis/.test(lowered)) {
|
|
456
|
+
return 'packer_analysis';
|
|
457
|
+
}
|
|
458
|
+
return 'core';
|
|
459
|
+
}
|
|
460
|
+
function computeImportModuleHints(importsData) {
|
|
461
|
+
const hints = new Map();
|
|
462
|
+
if (!importsData?.imports) {
|
|
463
|
+
return hints;
|
|
464
|
+
}
|
|
465
|
+
for (const [dllName, apiNames] of Object.entries(importsData.imports)) {
|
|
466
|
+
for (const mapper of IMPORT_TO_MODULE_HINT) {
|
|
467
|
+
if (mapper.matcher.test(dllName)) {
|
|
468
|
+
addHint(hints, mapper.module, dllName);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
for (const apiName of apiNames || []) {
|
|
472
|
+
for (const mapper of API_TO_MODULE_HINT) {
|
|
473
|
+
if (mapper.matcher.test(apiName)) {
|
|
474
|
+
addHint(hints, mapper.module, `${dllName}!${apiName}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return hints;
|
|
480
|
+
}
|
|
481
|
+
function computeStringModuleHints(stringsData) {
|
|
482
|
+
const hints = new Map();
|
|
483
|
+
const topHighValue = stringsData?.summary?.top_high_value || [];
|
|
484
|
+
const contextWindows = stringsData?.summary?.context_windows || [];
|
|
485
|
+
for (const item of topHighValue) {
|
|
486
|
+
for (const module of inferModulesFromText(item.string, item.categories || [])) {
|
|
487
|
+
addHint(hints, module, item.string);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
for (const window of contextWindows) {
|
|
491
|
+
const windowStrings = window.strings || [];
|
|
492
|
+
const mergedText = windowStrings.map((item) => item.string).join(' ');
|
|
493
|
+
const categories = dedupe([
|
|
494
|
+
...(window.categories || []),
|
|
495
|
+
...windowStrings.flatMap((item) => item.categories || []),
|
|
496
|
+
]);
|
|
497
|
+
for (const module of inferModulesFromText(mergedText, categories)) {
|
|
498
|
+
for (const hint of windowStrings.slice(0, 3).map((item) => item.string)) {
|
|
499
|
+
addHint(hints, module, hint);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return hints;
|
|
504
|
+
}
|
|
505
|
+
function dedupe(values) {
|
|
506
|
+
return Array.from(new Set(values.filter((value) => value.length > 0)));
|
|
507
|
+
}
|
|
508
|
+
function scoreStringQuery(text, categories) {
|
|
509
|
+
let score = 0;
|
|
510
|
+
if (categories.includes('command')) {
|
|
511
|
+
score += 3;
|
|
512
|
+
}
|
|
513
|
+
if (categories.includes('suspicious_api')) {
|
|
514
|
+
score += 3;
|
|
515
|
+
}
|
|
516
|
+
if (categories.includes('network') || categories.includes('url')) {
|
|
517
|
+
score += 2;
|
|
518
|
+
}
|
|
519
|
+
if (/\b(pack(er)? detection|protection|entropy|akasha|auto recon|recon|WriteProcessMemory|SetThreadContext)\b/i.test(text)) {
|
|
520
|
+
score += 5;
|
|
521
|
+
}
|
|
522
|
+
score += Math.min(Math.floor(text.length / 16), 3);
|
|
523
|
+
return score;
|
|
524
|
+
}
|
|
525
|
+
function buildStringSearchQueries(stringsData) {
|
|
526
|
+
if (!stringsData?.summary) {
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
const candidates = [];
|
|
530
|
+
const pushCandidate = (query, categories = []) => {
|
|
531
|
+
const normalized = normalizeReadableHint(query);
|
|
532
|
+
if (normalized.length < 8 || normalized.length > 96) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (!isReadableTextCandidate(normalized)) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const modules = inferModulesFromText(normalized, categories);
|
|
539
|
+
if (modules.length === 0) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
candidates.push({
|
|
543
|
+
query: normalized,
|
|
544
|
+
modules,
|
|
545
|
+
score: scoreStringQuery(normalized, categories),
|
|
546
|
+
});
|
|
547
|
+
};
|
|
548
|
+
for (const item of stringsData.summary.top_high_value || []) {
|
|
549
|
+
pushCandidate(item.string, item.categories || []);
|
|
550
|
+
}
|
|
551
|
+
for (const window of stringsData.summary.context_windows || []) {
|
|
552
|
+
for (const item of (window.strings || []).slice(0, 4)) {
|
|
553
|
+
pushCandidate(item.string, dedupe([...(item.categories || []), ...(window.categories || [])]));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
const seen = new Set();
|
|
557
|
+
return candidates
|
|
558
|
+
.sort((a, b) => b.score - a.score || a.query.localeCompare(b.query))
|
|
559
|
+
.filter((item) => {
|
|
560
|
+
const key = item.query.toLowerCase();
|
|
561
|
+
if (seen.has(key)) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
seen.add(key);
|
|
565
|
+
return true;
|
|
566
|
+
})
|
|
567
|
+
.slice(0, 6)
|
|
568
|
+
.map((item) => ({
|
|
569
|
+
query: item.query,
|
|
570
|
+
modules: item.modules,
|
|
571
|
+
}));
|
|
572
|
+
}
|
|
573
|
+
async function buildFunctionStringSearchHints(sampleId, stringsData, searchFunctions, warnings, enabled) {
|
|
574
|
+
const hints = new Map();
|
|
575
|
+
if (!enabled || !searchFunctions) {
|
|
576
|
+
return hints;
|
|
577
|
+
}
|
|
578
|
+
for (const query of buildStringSearchQueries(stringsData)) {
|
|
579
|
+
try {
|
|
580
|
+
const result = await searchFunctions(sampleId, {
|
|
581
|
+
stringQuery: query.query,
|
|
582
|
+
limit: 6,
|
|
583
|
+
timeout: 45000,
|
|
584
|
+
});
|
|
585
|
+
for (const match of result.matches || []) {
|
|
586
|
+
const entry = hints.get(match.address) || {
|
|
587
|
+
modules: new Set(),
|
|
588
|
+
strings: new Set(),
|
|
589
|
+
};
|
|
590
|
+
for (const module of query.modules) {
|
|
591
|
+
entry.modules.add(module);
|
|
592
|
+
}
|
|
593
|
+
const linkedValue = normalizeReadableHint(match.string_matches?.find((item) => item.value)?.value || query.query);
|
|
594
|
+
if (isReadableTextCandidate(linkedValue)) {
|
|
595
|
+
entry.strings.add(linkedValue);
|
|
596
|
+
}
|
|
597
|
+
hints.set(match.address, entry);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
warnings.push(`string reverse lookup failed for "${query.query}": ${normalizeError(error)}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return hints;
|
|
605
|
+
}
|
|
606
|
+
function enrichFunctionsWithRuntimeContext(functions, dynamicEvidence) {
|
|
607
|
+
if (!dynamicEvidence) {
|
|
608
|
+
return functions;
|
|
609
|
+
}
|
|
610
|
+
return functions.map((func) => {
|
|
611
|
+
const runtimeContext = correlateFunctionWithRuntimeEvidence({
|
|
612
|
+
functionName: func.function,
|
|
613
|
+
moduleName: detectModuleByNameOrReasons(func),
|
|
614
|
+
behaviorTags: func.behavior_tags || [],
|
|
615
|
+
xrefApis: (func.xref_signals || []).map((item) => item.api),
|
|
616
|
+
rankReasons: func.rank_reasons || [],
|
|
617
|
+
semanticSummary: `${func.semantic_summary || ''}\n${func.source_like_snippet}`,
|
|
618
|
+
stringHints: (func.source_like_snippet || '')
|
|
619
|
+
.split(/\r?\n/)
|
|
620
|
+
.filter((line) => line.startsWith('// strings:'))
|
|
621
|
+
.map((line) => line.replace(/^\/\/ strings:/, '').trim()),
|
|
622
|
+
callTargets: [
|
|
623
|
+
...(func.call_context?.callers || []),
|
|
624
|
+
...(func.call_context?.callees || []),
|
|
625
|
+
],
|
|
626
|
+
}, dynamicEvidence);
|
|
627
|
+
if (!runtimeContext) {
|
|
628
|
+
return func;
|
|
629
|
+
}
|
|
630
|
+
if (!func.runtime_context) {
|
|
631
|
+
return { ...func, runtime_context: runtimeContext };
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
...func,
|
|
635
|
+
runtime_context: {
|
|
636
|
+
corroborated_apis: dedupe([
|
|
637
|
+
...(func.runtime_context.corroborated_apis || []),
|
|
638
|
+
...(runtimeContext.corroborated_apis || []),
|
|
639
|
+
]).slice(0, 8),
|
|
640
|
+
corroborated_stages: dedupe([
|
|
641
|
+
...(func.runtime_context.corroborated_stages || []),
|
|
642
|
+
...(runtimeContext.corroborated_stages || []),
|
|
643
|
+
]).slice(0, 6),
|
|
644
|
+
notes: dedupe([...(func.runtime_context.notes || []), ...(runtimeContext.notes || [])]).slice(0, 6),
|
|
645
|
+
confidence: Math.max(Number(func.runtime_context.confidence || 0), Number(runtimeContext.confidence || 0)),
|
|
646
|
+
executed: func.runtime_context.executed ?? runtimeContext.executed,
|
|
647
|
+
evidence_sources: dedupe([
|
|
648
|
+
...(func.runtime_context.evidence_sources || []),
|
|
649
|
+
...(runtimeContext.evidence_sources || []),
|
|
650
|
+
]).slice(0, 6),
|
|
651
|
+
source_names: dedupe([
|
|
652
|
+
...(func.runtime_context.source_names || []),
|
|
653
|
+
...(runtimeContext.source_names || []),
|
|
654
|
+
]).slice(0, 6),
|
|
655
|
+
artifact_count: Math.max(Number(func.runtime_context.artifact_count || 0), Number(runtimeContext.artifact_count || 0)),
|
|
656
|
+
executed_artifact_count: Math.max(Number(func.runtime_context.executed_artifact_count || 0), Number(runtimeContext.executed_artifact_count || 0)),
|
|
657
|
+
matched_memory_regions: dedupe([
|
|
658
|
+
...(func.runtime_context.matched_memory_regions || []),
|
|
659
|
+
...(runtimeContext.matched_memory_regions || []),
|
|
660
|
+
]).slice(0, 6),
|
|
661
|
+
suggested_modules: dedupe([
|
|
662
|
+
...(func.runtime_context.suggested_modules || []),
|
|
663
|
+
...(runtimeContext.suggested_modules || []),
|
|
664
|
+
]).slice(0, 6),
|
|
665
|
+
matched_by: dedupe([
|
|
666
|
+
...(func.runtime_context.matched_by || []),
|
|
667
|
+
...(runtimeContext.matched_by || []),
|
|
668
|
+
]).slice(0, 6),
|
|
669
|
+
},
|
|
670
|
+
};
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
function chooseModuleForFunctionWithScoring(func, functionStringHints) {
|
|
674
|
+
const scores = new Map();
|
|
675
|
+
const importHints = new Map();
|
|
676
|
+
const stringHints = new Map();
|
|
677
|
+
const addScore = (module, score, hint, hintTarget) => {
|
|
678
|
+
scores.set(module, (scores.get(module) || 0) + score);
|
|
679
|
+
if (hint && hintTarget) {
|
|
680
|
+
const current = hintTarget.get(module) || new Set();
|
|
681
|
+
current.add(hint);
|
|
682
|
+
hintTarget.set(module, current);
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
for (const tag of func.behavior_tags || []) {
|
|
686
|
+
const mapped = BEHAVIOR_TO_MODULE[tag];
|
|
687
|
+
if (mapped) {
|
|
688
|
+
addScore(mapped, 7, tag, stringHints);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const textCorpus = [
|
|
692
|
+
func.function,
|
|
693
|
+
func.semantic_summary || '',
|
|
694
|
+
func.source_like_snippet,
|
|
695
|
+
...(func.rank_reasons || []),
|
|
696
|
+
...((func.call_context?.callers || []).slice(0, 3)),
|
|
697
|
+
...((func.call_context?.callees || []).slice(0, 5)),
|
|
698
|
+
].join(' ');
|
|
699
|
+
for (const module of inferModulesFromText(textCorpus)) {
|
|
700
|
+
addScore(module, 3);
|
|
701
|
+
}
|
|
702
|
+
for (const signal of func.xref_signals || []) {
|
|
703
|
+
for (const mapper of API_TO_MODULE_HINT) {
|
|
704
|
+
if (mapper.matcher.test(signal.api)) {
|
|
705
|
+
addScore(mapper.module, 5 * Math.max(signal.confidence, 0.4), signal.api, importHints);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
for (const reason of func.rank_reasons || []) {
|
|
710
|
+
const apiMatch = /^calls_sensitive_api:(.+)$/i.exec(reason);
|
|
711
|
+
if (!apiMatch) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
for (const mapper of API_TO_MODULE_HINT) {
|
|
715
|
+
if (mapper.matcher.test(apiMatch[1])) {
|
|
716
|
+
addScore(mapper.module, 4, apiMatch[1], importHints);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const linkedStrings = functionStringHints.get(func.address);
|
|
721
|
+
if (linkedStrings) {
|
|
722
|
+
for (const module of linkedStrings.modules) {
|
|
723
|
+
addScore(module, 8);
|
|
724
|
+
}
|
|
725
|
+
for (const module of linkedStrings.modules) {
|
|
726
|
+
for (const hint of linkedStrings.strings) {
|
|
727
|
+
addScore(module, 0, hint, stringHints);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const runtimeContext = func.runtime_context;
|
|
732
|
+
if (runtimeContext) {
|
|
733
|
+
for (const api of runtimeContext.corroborated_apis || []) {
|
|
734
|
+
for (const mapper of API_TO_MODULE_HINT) {
|
|
735
|
+
if (mapper.matcher.test(api)) {
|
|
736
|
+
addScore(mapper.module, 6, api, importHints);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
for (const module of modulesSuggestedByRuntimeStages(runtimeContext.corroborated_stages || [])) {
|
|
741
|
+
addScore(module, 5);
|
|
742
|
+
}
|
|
743
|
+
for (const module of runtimeContext.suggested_modules || []) {
|
|
744
|
+
addScore(module, 6, `runtime:${module}`, stringHints);
|
|
745
|
+
}
|
|
746
|
+
for (const region of runtimeContext.matched_memory_regions || []) {
|
|
747
|
+
for (const mapper of API_TO_MODULE_HINT) {
|
|
748
|
+
if (mapper.matcher.test(region)) {
|
|
749
|
+
addScore(mapper.module, 3, region, stringHints);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (/process|thread|dispatch|resolution|command/i.test(region)) {
|
|
753
|
+
addScore('process_ops', 3, region, stringHints);
|
|
754
|
+
}
|
|
755
|
+
if (/registry|key/i.test(region)) {
|
|
756
|
+
addScore('registry_ops', 3, region, stringHints);
|
|
757
|
+
}
|
|
758
|
+
if (/analysis|integrity|environment/i.test(region)) {
|
|
759
|
+
addScore('anti_analysis', 3, region, stringHints);
|
|
760
|
+
}
|
|
761
|
+
if (/packer|entropy|section|layout/i.test(region)) {
|
|
762
|
+
addScore('packer_analysis', 3, region, stringHints);
|
|
763
|
+
}
|
|
764
|
+
if (/network|socket|http|pipe|ipc/i.test(region)) {
|
|
765
|
+
addScore('network_ops', 3, region, stringHints);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const top = Array.from(scores.entries()).sort((a, b) => b[1] - a[1])[0];
|
|
770
|
+
const moduleName = top && top[1] >= 3 ? sanitizeModuleName(top[0]) : sanitizeModuleName(detectModuleByNameOrReasons(func));
|
|
771
|
+
return {
|
|
772
|
+
moduleName,
|
|
773
|
+
importHints: Array.from(importHints.get(moduleName) || []).slice(0, 8),
|
|
774
|
+
stringHints: Array.from(stringHints.get(moduleName) || []).slice(0, 8),
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
function regroupModules(functions, moduleLimit, minModuleSize, importsData, stringsData, functionStringHints) {
|
|
778
|
+
const moduleMap = new Map();
|
|
779
|
+
const importHints = computeImportModuleHints(importsData);
|
|
780
|
+
const stringHints = computeStringModuleHints(stringsData);
|
|
781
|
+
for (const func of functions) {
|
|
782
|
+
const decision = chooseModuleForFunctionWithScoring(func, functionStringHints || new Map());
|
|
783
|
+
const moduleName = sanitizeModuleName(decision.moduleName);
|
|
784
|
+
if (!moduleMap.has(moduleName)) {
|
|
785
|
+
moduleMap.set(moduleName, {
|
|
786
|
+
name: moduleName,
|
|
787
|
+
functions: [],
|
|
788
|
+
importHints: new Set(importHints.get(moduleName) || []),
|
|
789
|
+
stringHints: new Set((stringHints.get(moduleName) || []).slice(0, 8)),
|
|
790
|
+
runtimeApis: new Set(),
|
|
791
|
+
runtimeStages: new Set(),
|
|
792
|
+
runtimeNotes: new Set(),
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
const bucket = moduleMap.get(moduleName);
|
|
796
|
+
bucket.functions.push(func);
|
|
797
|
+
for (const hint of decision.importHints) {
|
|
798
|
+
bucket.importHints.add(hint);
|
|
799
|
+
}
|
|
800
|
+
for (const hint of decision.stringHints) {
|
|
801
|
+
bucket.stringHints.add(hint);
|
|
802
|
+
}
|
|
803
|
+
for (const api of func.runtime_context?.corroborated_apis || []) {
|
|
804
|
+
bucket.runtimeApis.add(api);
|
|
805
|
+
}
|
|
806
|
+
for (const stage of func.runtime_context?.corroborated_stages || []) {
|
|
807
|
+
bucket.runtimeStages.add(stage);
|
|
808
|
+
}
|
|
809
|
+
for (const note of func.runtime_context?.notes || []) {
|
|
810
|
+
bucket.runtimeNotes.add(note);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
let modules = Array.from(moduleMap.values()).sort((a, b) => b.functions.length - a.functions.length);
|
|
814
|
+
if (modules.length > moduleLimit) {
|
|
815
|
+
const kept = modules.slice(0, moduleLimit - 1);
|
|
816
|
+
const overflow = modules.slice(moduleLimit - 1);
|
|
817
|
+
const mergedCore = {
|
|
818
|
+
name: 'core',
|
|
819
|
+
functions: overflow.flatMap((module) => module.functions),
|
|
820
|
+
importHints: new Set(overflow.flatMap((module) => Array.from(module.importHints))),
|
|
821
|
+
stringHints: new Set(overflow.flatMap((module) => Array.from(module.stringHints))),
|
|
822
|
+
runtimeApis: new Set(overflow.flatMap((module) => Array.from(module.runtimeApis))),
|
|
823
|
+
runtimeStages: new Set(overflow.flatMap((module) => Array.from(module.runtimeStages))),
|
|
824
|
+
runtimeNotes: new Set(overflow.flatMap((module) => Array.from(module.runtimeNotes))),
|
|
825
|
+
};
|
|
826
|
+
modules = [...kept, mergedCore];
|
|
827
|
+
}
|
|
828
|
+
const small = modules.filter((module) => module.functions.length < minModuleSize);
|
|
829
|
+
if (small.length > 0 && modules.length > 1) {
|
|
830
|
+
const large = modules.filter((module) => module.functions.length >= minModuleSize);
|
|
831
|
+
const core = large.find((module) => module.name === 'core') || {
|
|
832
|
+
name: 'core',
|
|
833
|
+
functions: [],
|
|
834
|
+
importHints: new Set(),
|
|
835
|
+
stringHints: new Set(),
|
|
836
|
+
runtimeApis: new Set(),
|
|
837
|
+
runtimeStages: new Set(),
|
|
838
|
+
runtimeNotes: new Set(),
|
|
839
|
+
};
|
|
840
|
+
for (const module of small) {
|
|
841
|
+
core.functions.push(...module.functions);
|
|
842
|
+
for (const hint of module.importHints) {
|
|
843
|
+
core.importHints.add(hint);
|
|
844
|
+
}
|
|
845
|
+
for (const hint of module.stringHints) {
|
|
846
|
+
core.stringHints.add(hint);
|
|
847
|
+
}
|
|
848
|
+
for (const api of module.runtimeApis) {
|
|
849
|
+
core.runtimeApis.add(api);
|
|
850
|
+
}
|
|
851
|
+
for (const stage of module.runtimeStages) {
|
|
852
|
+
core.runtimeStages.add(stage);
|
|
853
|
+
}
|
|
854
|
+
for (const note of module.runtimeNotes) {
|
|
855
|
+
core.runtimeNotes.add(note);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
modules = large.filter((module) => module.functions.length >= minModuleSize);
|
|
859
|
+
if (!modules.find((module) => module.name === 'core')) {
|
|
860
|
+
modules.push(core);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return modules.sort((a, b) => b.functions.length - a.functions.length);
|
|
864
|
+
}
|
|
865
|
+
const RESERVED_C_WRAPPER_NAMES = new Set([
|
|
866
|
+
'main',
|
|
867
|
+
'memcpy',
|
|
868
|
+
'memcmp',
|
|
869
|
+
'memset',
|
|
870
|
+
'strlen',
|
|
871
|
+
'strcmp',
|
|
872
|
+
'strncmp',
|
|
873
|
+
'strcpy',
|
|
874
|
+
'strncpy',
|
|
875
|
+
'strcat',
|
|
876
|
+
'strncat',
|
|
877
|
+
'malloc',
|
|
878
|
+
'calloc',
|
|
879
|
+
'realloc',
|
|
880
|
+
'free',
|
|
881
|
+
]);
|
|
882
|
+
function deriveRewriteEntryNames(func, module) {
|
|
883
|
+
const originalBaseName = sanitizeSymbolForHeader(func.function);
|
|
884
|
+
const originalName = RESERVED_C_WRAPPER_NAMES.has(originalBaseName.toLowerCase())
|
|
885
|
+
? `${sanitizeModuleName(module.name)}_${originalBaseName}_wrapper`
|
|
886
|
+
: originalBaseName;
|
|
887
|
+
const semanticAlias = sanitizeSymbolForHeader(buildSemanticAlias(func, module));
|
|
888
|
+
return {
|
|
889
|
+
originalName,
|
|
890
|
+
semanticAlias,
|
|
891
|
+
implementationName: semanticAlias === originalName ? `${semanticAlias}_impl` : semanticAlias,
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
function trimCommandToken(value) {
|
|
895
|
+
const cleaned = value.trim().replace(/^[`"'[\](){}]+|[`"'[\](){}]+$/g, '');
|
|
896
|
+
return cleaned.replace(/[,:;]+$/g, '');
|
|
897
|
+
}
|
|
898
|
+
function looksLikeCommandToken(value) {
|
|
899
|
+
return (/^--?[a-z0-9][a-z0-9_-]*$/i.test(value) ||
|
|
900
|
+
/^\/[a-z0-9][a-z0-9_-]*$/i.test(value) ||
|
|
901
|
+
/^(scan|inject|dump|recon|detect|list|enum|query|read|write|copy|delete|spawn|resume|suspend|probe|unpack|help|version)$/i.test(value));
|
|
902
|
+
}
|
|
903
|
+
function isCliNoiseCandidate(value) {
|
|
904
|
+
const lowered = value.toLowerCase();
|
|
905
|
+
if (lowered.includes('internal error: entered unreachable code') ||
|
|
906
|
+
lowered.includes('stack backtrace') ||
|
|
907
|
+
lowered.includes('tokio 1.x context was found') ||
|
|
908
|
+
lowered.includes('.cargo\\registry\\src\\') ||
|
|
909
|
+
lowered.includes('/rustc/')) {
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
const slashCount = (value.match(/[\\/]/g) || []).length;
|
|
913
|
+
return slashCount >= 6 && !/\b(akasha|packer|protector|scan|detect|inject|dump|cmd\.exe|writeprocessmemory)\b/i.test(value);
|
|
914
|
+
}
|
|
915
|
+
function normalizeCliFragment(value, maxLength = 160) {
|
|
916
|
+
return value
|
|
917
|
+
.replace(/\0+/g, ' ')
|
|
918
|
+
.replace(/\s+/g, ' ')
|
|
919
|
+
.replace(/\s+\|\s+/g, ' | ')
|
|
920
|
+
.trim()
|
|
921
|
+
.slice(0, maxLength);
|
|
922
|
+
}
|
|
923
|
+
function stripFeatureNoise(value) {
|
|
924
|
+
return value
|
|
925
|
+
.replace(/internal error: entered unreachable code/gi, ' ')
|
|
926
|
+
.replace(/[A-Z]:\\Users\\[^\\\s]+\\\.cargo\\registry\\src\\[^\s|]+/gi, ' ')
|
|
927
|
+
.replace(/\/rustc\/\S+/gi, ' ')
|
|
928
|
+
.replace(/stack backtrace:?/gi, ' ');
|
|
929
|
+
}
|
|
930
|
+
function expandCliFragments(value) {
|
|
931
|
+
const coarseParts = value
|
|
932
|
+
.split(/\r?\n|\|/g)
|
|
933
|
+
.map((item) => stripFeatureNoise(item))
|
|
934
|
+
.map((item) => normalizeCliFragment(item))
|
|
935
|
+
.filter(Boolean);
|
|
936
|
+
const fragments = [];
|
|
937
|
+
for (const part of coarseParts) {
|
|
938
|
+
const optionExpanded = part
|
|
939
|
+
.replace(/([,;])\s*(--?[a-z][a-z0-9_-]*|\/[a-z][a-z0-9_-]*)/gi, '$1\n$2')
|
|
940
|
+
.split(/\n+/g)
|
|
941
|
+
.map((item) => normalizeCliFragment(item))
|
|
942
|
+
.filter(Boolean);
|
|
943
|
+
fragments.push(...optionExpanded);
|
|
944
|
+
}
|
|
945
|
+
return dedupe(fragments).filter((value) => isReadableTextCandidate(value) && !isCliNoiseCandidate(value));
|
|
946
|
+
}
|
|
947
|
+
function scoreCliBannerCandidate(value, module) {
|
|
948
|
+
const normalized = normalizeCliFragment(value, 160);
|
|
949
|
+
let score = 0;
|
|
950
|
+
if (/\b(usage|help|packer(?:\/protector)? detection|protector detection)\b/i.test(normalized)) {
|
|
951
|
+
score += 8;
|
|
952
|
+
}
|
|
953
|
+
if (/\b(akasha|auto recon)\b/i.test(normalized)) {
|
|
954
|
+
score += 4;
|
|
955
|
+
}
|
|
956
|
+
if (/\b(scan|inject|dump|recon|detect|list|enum|query|spawn|resume|suspend|probe|unpack)\b/i.test(normalized)) {
|
|
957
|
+
score += 4;
|
|
958
|
+
}
|
|
959
|
+
if (normalized.length >= 12 && normalized.length <= 96) {
|
|
960
|
+
score += 2;
|
|
961
|
+
}
|
|
962
|
+
else if (normalized.length > 132) {
|
|
963
|
+
score -= 1;
|
|
964
|
+
}
|
|
965
|
+
if (/^[@A-Za-z0-9]/.test(normalized)) {
|
|
966
|
+
score += 1;
|
|
967
|
+
}
|
|
968
|
+
if (/^@/.test(normalized)) {
|
|
969
|
+
score += 2;
|
|
970
|
+
}
|
|
971
|
+
if (isCliNoiseCandidate(normalized)) {
|
|
972
|
+
score -= 12;
|
|
973
|
+
}
|
|
974
|
+
if (module) {
|
|
975
|
+
const lowered = normalized.toLowerCase();
|
|
976
|
+
if (module.name === 'packer_analysis') {
|
|
977
|
+
if (/\b(packer|protector|section|entropy|entry point|upx|vmprotect|themida|aspack)\b/i.test(lowered)) {
|
|
978
|
+
score += 6;
|
|
979
|
+
}
|
|
980
|
+
if (/\b(writeprocessmemory|openprocess|createprocess|cmd\.exe)\b/i.test(lowered)) {
|
|
981
|
+
score -= 4;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (module.name === 'process_ops') {
|
|
985
|
+
if (/\b(writeprocessmemory|readprocessmemory|openprocess|createprocess|setthreadcontext|resumethread|cmd\.exe)\b/i.test(lowered)) {
|
|
986
|
+
score += 6;
|
|
987
|
+
}
|
|
988
|
+
if (/\b(packer|protector|entry point|entropy)\b/i.test(lowered)) {
|
|
989
|
+
score -= 3;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return score;
|
|
994
|
+
}
|
|
995
|
+
function scoreCliCommandForModule(command, module) {
|
|
996
|
+
const verb = command.verb.toLowerCase();
|
|
997
|
+
let score = 0;
|
|
998
|
+
if (module.name === 'packer_analysis') {
|
|
999
|
+
if (verb === 'scan' || verb === 'detect' || verb === 'probe' || verb === 'unpack') {
|
|
1000
|
+
score += 10;
|
|
1001
|
+
}
|
|
1002
|
+
if (verb === 'inject' || verb === 'spawn') {
|
|
1003
|
+
score -= 3;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
if (module.name === 'process_ops') {
|
|
1007
|
+
if (verb === 'inject' || verb === 'spawn' || verb === 'resume' || verb === 'suspend' || verb === 'query') {
|
|
1008
|
+
score += 10;
|
|
1009
|
+
}
|
|
1010
|
+
if (verb === 'scan' || verb === 'detect') {
|
|
1011
|
+
score -= 2;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (module.name === 'file_ops') {
|
|
1015
|
+
if (verb === 'dump' || verb === 'read' || verb === 'write' || verb === 'copy' || verb === 'delete') {
|
|
1016
|
+
score += 8;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (module.name === 'registry_ops') {
|
|
1020
|
+
if (verb === 'query' || verb === 'read' || verb === 'write') {
|
|
1021
|
+
score += 8;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return score;
|
|
1025
|
+
}
|
|
1026
|
+
function collectModuleFeatureSnapshot(module) {
|
|
1027
|
+
return module.functions.reduce((acc, func) => {
|
|
1028
|
+
const current = collectRewriteFeatures(func, module);
|
|
1029
|
+
return {
|
|
1030
|
+
hasDynamicResolver: acc.hasDynamicResolver || current.hasDynamicResolver,
|
|
1031
|
+
hasProcessInjection: acc.hasProcessInjection || current.hasProcessInjection,
|
|
1032
|
+
hasProcessSpawn: acc.hasProcessSpawn || current.hasProcessSpawn,
|
|
1033
|
+
hasFileApiTable: acc.hasFileApiTable || current.hasFileApiTable,
|
|
1034
|
+
hasRegistryApiTable: acc.hasRegistryApiTable || current.hasRegistryApiTable,
|
|
1035
|
+
hasNtQueryInformationProcess: acc.hasNtQueryInformationProcess || current.hasNtQueryInformationProcess,
|
|
1036
|
+
hasNtQuerySystemInformation: acc.hasNtQuerySystemInformation || current.hasNtQuerySystemInformation,
|
|
1037
|
+
hasCodeIntegrity: acc.hasCodeIntegrity || current.hasCodeIntegrity,
|
|
1038
|
+
hasPackerScan: acc.hasPackerScan || current.hasPackerScan,
|
|
1039
|
+
hasTailJumpHints: acc.hasTailJumpHints || current.hasTailJumpHints,
|
|
1040
|
+
hasBodyReferenceHints: acc.hasBodyReferenceHints || current.hasBodyReferenceHints,
|
|
1041
|
+
};
|
|
1042
|
+
}, {
|
|
1043
|
+
hasDynamicResolver: false,
|
|
1044
|
+
hasProcessInjection: false,
|
|
1045
|
+
hasProcessSpawn: false,
|
|
1046
|
+
hasFileApiTable: false,
|
|
1047
|
+
hasRegistryApiTable: false,
|
|
1048
|
+
hasNtQueryInformationProcess: false,
|
|
1049
|
+
hasNtQuerySystemInformation: false,
|
|
1050
|
+
hasCodeIntegrity: false,
|
|
1051
|
+
hasPackerScan: false,
|
|
1052
|
+
hasTailJumpHints: false,
|
|
1053
|
+
hasBodyReferenceHints: false,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
function humanizeModuleName(value) {
|
|
1057
|
+
return value
|
|
1058
|
+
.split(/[_\s]+/g)
|
|
1059
|
+
.filter(Boolean)
|
|
1060
|
+
.map((item) => item.charAt(0).toUpperCase() + item.slice(1))
|
|
1061
|
+
.join(' ');
|
|
1062
|
+
}
|
|
1063
|
+
function deriveSemanticCliDefaults(module) {
|
|
1064
|
+
const features = collectModuleFeatureSnapshot(module);
|
|
1065
|
+
if (module.name === 'packer_analysis' || features.hasPackerScan) {
|
|
1066
|
+
return {
|
|
1067
|
+
toolName: 'Packer/Protector Detection',
|
|
1068
|
+
helpBanner: 'Detect packers, protectors, and suspicious PE layout signals.',
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
if (module.name === 'process_ops' || features.hasProcessInjection || features.hasProcessSpawn) {
|
|
1072
|
+
return {
|
|
1073
|
+
toolName: 'Remote Process Operation Dispatcher',
|
|
1074
|
+
helpBanner: 'Prepare remote-process access, dynamic API resolution, and execution-transfer operations.',
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
if (module.name === 'anti_analysis' ||
|
|
1078
|
+
features.hasNtQueryInformationProcess ||
|
|
1079
|
+
features.hasNtQuerySystemInformation ||
|
|
1080
|
+
features.hasCodeIntegrity) {
|
|
1081
|
+
return {
|
|
1082
|
+
toolName: 'Environment And Code Integrity Probe',
|
|
1083
|
+
helpBanner: 'Probe code-integrity state and environment-sensitive process signals.',
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
if (module.name === 'registry_ops' || features.hasRegistryApiTable) {
|
|
1087
|
+
return {
|
|
1088
|
+
toolName: 'Registry Capability Dispatcher',
|
|
1089
|
+
helpBanner: 'Resolve and exercise registry inspection and configuration paths.',
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
if (module.name === 'file_ops' || features.hasFileApiTable) {
|
|
1093
|
+
return {
|
|
1094
|
+
toolName: 'File And Artifact Capability Dispatcher',
|
|
1095
|
+
helpBanner: 'Stage file, buffer, and artifact materialization capabilities.',
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
toolName: humanizeModuleName(sanitizeModuleName(module.name)),
|
|
1100
|
+
helpBanner: 'Recovered command model from string and runtime evidence.',
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function shouldPreferSemanticBanner(candidate, module) {
|
|
1104
|
+
const normalized = normalizeCliFragment(candidate, 160);
|
|
1105
|
+
if (normalized.length < 10 || isCliNoiseCandidate(normalized)) {
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1108
|
+
if (/\b(assertion failed|panic|entered unreachable code|stack backtrace)\b/i.test(normalized) ||
|
|
1109
|
+
/^[A-Za-z]:\\/.test(normalized) ||
|
|
1110
|
+
/^exe\\cmd\.exe/i.test(normalized)) {
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1113
|
+
if (module.name === 'file_ops' && /\b(actual_state|tokio|shutdown)\b/i.test(normalized)) {
|
|
1114
|
+
return true;
|
|
1115
|
+
}
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
function shouldPreferSemanticToolName(candidate, module) {
|
|
1119
|
+
const normalized = candidate.trim().toLowerCase();
|
|
1120
|
+
if (normalized.length === 0 || normalized === sanitizeModuleName(module.name).toLowerCase()) {
|
|
1121
|
+
return true;
|
|
1122
|
+
}
|
|
1123
|
+
if (module.name === 'process_ops') {
|
|
1124
|
+
return /\b(packer|protector)\b/i.test(normalized);
|
|
1125
|
+
}
|
|
1126
|
+
if (module.name === 'file_ops') {
|
|
1127
|
+
return /\b(packer|protector|process|tokio|assertion)\b/i.test(normalized);
|
|
1128
|
+
}
|
|
1129
|
+
if (module.name === 'anti_analysis') {
|
|
1130
|
+
return /\b(packer|protector|file)\b/i.test(normalized);
|
|
1131
|
+
}
|
|
1132
|
+
if (module.name === 'packer_analysis') {
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
function collectDisplayStringHints(module, limit = 8) {
|
|
1138
|
+
const curated = collectModuleStringHints(module).filter((value) => {
|
|
1139
|
+
if (/\b(assertion failed|panic|tokio|snapshot\.is_join_interested|curr\.is_join_interested|sharded_size\.is_power_of_two|actual_state ==|internal exception)\b/i.test(value)) {
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
if (/^[0-9a-f]{6,}$/i.test(value) || /^00[0-9a-f]{4,}$/i.test(value)) {
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
if (/ret' or hex|base address for encoding/i.test(value)) {
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
1148
|
+
if (module.name === 'process_ops' && /^exe\\cmd\.exe/i.test(value)) {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
return true;
|
|
1152
|
+
});
|
|
1153
|
+
if (curated.length > 0) {
|
|
1154
|
+
return curated.slice(0, limit);
|
|
1155
|
+
}
|
|
1156
|
+
const semanticDefaults = deriveSemanticCliDefaults(module);
|
|
1157
|
+
return [semanticDefaults.helpBanner].filter(Boolean).slice(0, limit);
|
|
1158
|
+
}
|
|
1159
|
+
function describeModuleRole(module) {
|
|
1160
|
+
const defaults = deriveSemanticCliDefaults(module);
|
|
1161
|
+
return defaults.helpBanner;
|
|
1162
|
+
}
|
|
1163
|
+
function collectModuleCliModel(module) {
|
|
1164
|
+
const rawCorpus = dedupe([
|
|
1165
|
+
...Array.from(module.stringHints),
|
|
1166
|
+
...Array.from(module.runtimeNotes),
|
|
1167
|
+
...module.functions.flatMap((func) => {
|
|
1168
|
+
const lines = (func.source_like_snippet || '').split(/\r?\n/);
|
|
1169
|
+
return lines
|
|
1170
|
+
.filter((line) => line.startsWith('// strings:') || line.startsWith('// summary='))
|
|
1171
|
+
.map((line) => line.replace(/^\/\/ (strings|summary)=/, '').trim());
|
|
1172
|
+
}),
|
|
1173
|
+
]);
|
|
1174
|
+
const corpus = dedupe(rawCorpus.flatMap((value) => expandCliFragments(value)));
|
|
1175
|
+
if (corpus.length === 0) {
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
const semanticDefaults = deriveSemanticCliDefaults(module);
|
|
1179
|
+
const bannerCandidate = corpus
|
|
1180
|
+
.slice()
|
|
1181
|
+
.sort((left, right) => scoreCliBannerCandidate(right, module) - scoreCliBannerCandidate(left, module))[0] ||
|
|
1182
|
+
corpus.find((value) => value.length >= 24) ||
|
|
1183
|
+
corpus[0];
|
|
1184
|
+
const toolNameMatch = /\b(akasha(?:\s+auto\s+recon)?|auto recon|packer(?:\/protector)? detection|protector detection)\b/i.exec(rawCorpus.join(' ')) || /\b(akasha(?:\s+auto\s+recon)?|auto recon|packer(?:\/protector)? detection|protector detection)\b/i.exec(corpus.join(' '));
|
|
1185
|
+
const rawToolName = toolNameMatch ? toolNameMatch[1] : sanitizeModuleName(module.name);
|
|
1186
|
+
const commands = [];
|
|
1187
|
+
const seen = new Set();
|
|
1188
|
+
const pushCommand = (verb, summary) => {
|
|
1189
|
+
const normalizedVerb = trimCommandToken(verb);
|
|
1190
|
+
const normalizedSummary = normalizeReadableHint(summary, 120);
|
|
1191
|
+
if (normalizedVerb.length < 2 || normalizedSummary.length < 4) {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
const key = normalizedVerb.toLowerCase();
|
|
1195
|
+
if (seen.has(key)) {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
seen.add(key);
|
|
1199
|
+
commands.push({
|
|
1200
|
+
verb: normalizedVerb,
|
|
1201
|
+
summary: normalizedSummary,
|
|
1202
|
+
});
|
|
1203
|
+
};
|
|
1204
|
+
for (const item of corpus) {
|
|
1205
|
+
const usageMatch = /\busage:\s+(?:[^\s]+\s+)?([a-z][a-z0-9_-]{1,31})\b/i.exec(item);
|
|
1206
|
+
if (usageMatch) {
|
|
1207
|
+
pushCommand(usageMatch[1], item);
|
|
1208
|
+
}
|
|
1209
|
+
for (const token of item.match(/(?:^|\s)(--?[a-z0-9][a-z0-9_-]*|\/[a-z0-9][a-z0-9_-]*)\b/gi) || []) {
|
|
1210
|
+
pushCommand(token.trim(), item);
|
|
1211
|
+
}
|
|
1212
|
+
const parts = item.split(/\s+/).map((value) => trimCommandToken(value));
|
|
1213
|
+
const firstToken = parts[0] || '';
|
|
1214
|
+
if (looksLikeCommandToken(firstToken)) {
|
|
1215
|
+
pushCommand(firstToken, item);
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
const embedded = item.match(/\b(scan|inject|dump|recon|detect|list|enum|query|spawn|resume|suspend|probe|unpack)\b/i);
|
|
1219
|
+
if (embedded) {
|
|
1220
|
+
pushCommand(embedded[1], item);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
synthesizeModuleCliCommands(module, bannerCandidate, pushCommand);
|
|
1224
|
+
commands.sort((left, right) => scoreCliCommandForModule(right, module) - scoreCliCommandForModule(left, module));
|
|
1225
|
+
const finalToolName = shouldPreferSemanticToolName(rawToolName, module)
|
|
1226
|
+
? semanticDefaults.toolName
|
|
1227
|
+
: rawToolName;
|
|
1228
|
+
const finalHelpBanner = shouldPreferSemanticBanner(bannerCandidate, module)
|
|
1229
|
+
? semanticDefaults.helpBanner
|
|
1230
|
+
: bannerCandidate;
|
|
1231
|
+
return {
|
|
1232
|
+
toolName: normalizeReadableHint(finalToolName, 48),
|
|
1233
|
+
helpBanner: normalizeReadableHint(finalHelpBanner, 160),
|
|
1234
|
+
commands: commands.slice(0, 6),
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function buildExportCliModels(modules) {
|
|
1238
|
+
return modules
|
|
1239
|
+
.map((module) => {
|
|
1240
|
+
const cliModel = collectModuleCliModel(module);
|
|
1241
|
+
if (!cliModel) {
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
return {
|
|
1245
|
+
module: module.name,
|
|
1246
|
+
tool_name: cliModel.toolName,
|
|
1247
|
+
help_banner: cliModel.helpBanner,
|
|
1248
|
+
commands: cliModel.commands.slice(0, 8),
|
|
1249
|
+
};
|
|
1250
|
+
})
|
|
1251
|
+
.filter((item) => Boolean(item));
|
|
1252
|
+
}
|
|
1253
|
+
function buildReconstructCliProfile(modules) {
|
|
1254
|
+
const seen = new Set();
|
|
1255
|
+
const combinedCommands = [];
|
|
1256
|
+
let toolName = '';
|
|
1257
|
+
let helpBanner = '';
|
|
1258
|
+
let bestToolScore = Number.NEGATIVE_INFINITY;
|
|
1259
|
+
let bestBannerScore = Number.NEGATIVE_INFINITY;
|
|
1260
|
+
for (const module of modules) {
|
|
1261
|
+
const cliModel = collectModuleCliModel(module);
|
|
1262
|
+
if (!cliModel) {
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
const hasDescriptiveToolName = cliModel.toolName.toLowerCase() !== sanitizeModuleName(module.name).toLowerCase();
|
|
1266
|
+
const selectionScore = scoreCliBannerCandidate(cliModel.helpBanner, module) +
|
|
1267
|
+
cliModel.commands.length * 6 +
|
|
1268
|
+
(hasDescriptiveToolName ? 4 : -4);
|
|
1269
|
+
if (toolName.length === 0 || selectionScore > bestToolScore) {
|
|
1270
|
+
toolName = cliModel.toolName;
|
|
1271
|
+
bestToolScore = selectionScore;
|
|
1272
|
+
}
|
|
1273
|
+
const bannerScore = scoreCliBannerCandidate(cliModel.helpBanner, module);
|
|
1274
|
+
if (helpBanner.length === 0 || bannerScore > bestBannerScore) {
|
|
1275
|
+
helpBanner = cliModel.helpBanner;
|
|
1276
|
+
bestBannerScore = bannerScore;
|
|
1277
|
+
}
|
|
1278
|
+
for (const command of cliModel.commands) {
|
|
1279
|
+
const key = command.verb.toLowerCase();
|
|
1280
|
+
if (seen.has(key)) {
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
seen.add(key);
|
|
1284
|
+
combinedCommands.push(command);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (helpBanner.length === 0 && combinedCommands.length === 0) {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
return {
|
|
1291
|
+
tool_name: toolName || 'recovered_tool',
|
|
1292
|
+
help_banner: helpBanner || 'Recovered command model from string and runtime evidence.',
|
|
1293
|
+
command_count: combinedCommands.length,
|
|
1294
|
+
commands: combinedCommands.slice(0, 8),
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
function deriveHarnessSeedText(func, module) {
|
|
1298
|
+
const cliModel = collectModuleCliModel(module);
|
|
1299
|
+
const firstCommand = cliModel?.commands[0];
|
|
1300
|
+
if (firstCommand) {
|
|
1301
|
+
return normalizeReadableHint(`${cliModel?.toolName || sanitizeModuleName(module.name)} ${firstCommand.verb}`, 120);
|
|
1302
|
+
}
|
|
1303
|
+
const stringHint = Array.from(module.stringHints)
|
|
1304
|
+
.flatMap((value) => expandCliFragments(value))
|
|
1305
|
+
.find((value) => !isCliNoiseCandidate(value));
|
|
1306
|
+
if (stringHint) {
|
|
1307
|
+
return normalizeReadableHint(stringHint, 120);
|
|
1308
|
+
}
|
|
1309
|
+
const runtimeApi = (func.runtime_context?.corroborated_apis || [])[0];
|
|
1310
|
+
if (runtimeApi) {
|
|
1311
|
+
return normalizeReadableHint(runtimeApi, 64);
|
|
1312
|
+
}
|
|
1313
|
+
return normalizeReadableHint(func.semantic_summary || func.function, 120);
|
|
1314
|
+
}
|
|
1315
|
+
function buildRecoveredContractHints(func, module) {
|
|
1316
|
+
const features = collectRewriteFeatures(func, module);
|
|
1317
|
+
const cliModel = collectModuleCliModel(module);
|
|
1318
|
+
const recoveredVerbs = (cliModel?.commands || [])
|
|
1319
|
+
.map((item) => item.verb)
|
|
1320
|
+
.filter(Boolean)
|
|
1321
|
+
.slice(0, 3);
|
|
1322
|
+
const hints = [
|
|
1323
|
+
'runtime_ctx stores recovered mutable state and the last observed semantic detail.',
|
|
1324
|
+
'outputs captures the stage and status exposed by the reconstructed skeleton.',
|
|
1325
|
+
];
|
|
1326
|
+
if (features.hasProcessInjection || features.hasProcessSpawn) {
|
|
1327
|
+
hints.push('inputs.string_args[0] seeds remote_request.target_selector and target routing hints.');
|
|
1328
|
+
hints.push('inputs.string_args[1] seeds remote_request.launch_command_line when a recovered spawn path is present.');
|
|
1329
|
+
hints.push('inputs.pointer_args[0] is treated as remote_request.payload_view for execution-transfer scaffolding.');
|
|
1330
|
+
hints.push('inputs.handle_args[0..1] seed remote_request.process_handle and remote_request.thread_handle placeholders.');
|
|
1331
|
+
}
|
|
1332
|
+
else if (features.hasFileApiTable) {
|
|
1333
|
+
hints.push('inputs.string_args[0] is treated as a path or file-operation hint.');
|
|
1334
|
+
}
|
|
1335
|
+
else if (features.hasRegistryApiTable) {
|
|
1336
|
+
hints.push('inputs.string_args[1] is treated as a registry key or value hint.');
|
|
1337
|
+
}
|
|
1338
|
+
else if (features.hasPackerScan) {
|
|
1339
|
+
hints.push('inputs.pointer_args[0] is treated as a PE image or buffer view placeholder.');
|
|
1340
|
+
hints.push('outputs.scalar_result carries the recovered packer heuristic score.');
|
|
1341
|
+
}
|
|
1342
|
+
else if (recoveredVerbs.length > 0) {
|
|
1343
|
+
hints.push(`inputs.string_args[0] is treated as a recovered command verb or CLI token (${recoveredVerbs.join(', ')}).`);
|
|
1344
|
+
}
|
|
1345
|
+
else if (cliModel) {
|
|
1346
|
+
hints.push('inputs.string_args[0] is treated as a command verb or CLI token recovered from help text.');
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
hints.push('inputs.scalar_args[0] is treated as a generic mode or flag bitfield until stronger typing exists.');
|
|
1350
|
+
}
|
|
1351
|
+
return hints.slice(0, 4);
|
|
1352
|
+
}
|
|
1353
|
+
function deriveHarnessExpectedStage(func, module) {
|
|
1354
|
+
const features = collectRewriteFeatures(func, module);
|
|
1355
|
+
if (features.hasProcessInjection || features.hasProcessSpawn) {
|
|
1356
|
+
return 'AK_STAGE_PREPARE_REMOTE_PROCESS_ACCESS';
|
|
1357
|
+
}
|
|
1358
|
+
if (features.hasNtQueryInformationProcess ||
|
|
1359
|
+
features.hasNtQuerySystemInformation ||
|
|
1360
|
+
features.hasCodeIntegrity) {
|
|
1361
|
+
return 'AK_STAGE_ANTI_ANALYSIS_CHECKS';
|
|
1362
|
+
}
|
|
1363
|
+
if (features.hasRegistryApiTable) {
|
|
1364
|
+
return 'AK_STAGE_REGISTRY_OPERATIONS';
|
|
1365
|
+
}
|
|
1366
|
+
if (features.hasFileApiTable) {
|
|
1367
|
+
return 'AK_STAGE_FILE_OPERATIONS';
|
|
1368
|
+
}
|
|
1369
|
+
if (features.hasPackerScan) {
|
|
1370
|
+
return 'AK_STAGE_SCAN_PE_LAYOUT';
|
|
1371
|
+
}
|
|
1372
|
+
if (collectModuleCliModel(module)) {
|
|
1373
|
+
return 'AK_STAGE_COMMAND_MODEL_READY';
|
|
1374
|
+
}
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
function buildSupportHeaderContent(modules) {
|
|
1378
|
+
const moduleNames = modules.map((module) => module.name).join(', ') || 'none';
|
|
1379
|
+
const lines = [];
|
|
1380
|
+
lines.push('/* shared support types for semantic reconstruction skeleton */');
|
|
1381
|
+
lines.push('#pragma once');
|
|
1382
|
+
lines.push('');
|
|
1383
|
+
lines.push('#include <stddef.h>');
|
|
1384
|
+
lines.push('#include <stdint.h>');
|
|
1385
|
+
lines.push('');
|
|
1386
|
+
lines.push(`/* modules: ${moduleNames} */`);
|
|
1387
|
+
lines.push('enum {');
|
|
1388
|
+
lines.push(' AK_STATUS_OK = 0,');
|
|
1389
|
+
lines.push(' AK_STATUS_RESOLVE_FAILED = -1,');
|
|
1390
|
+
lines.push(' AK_STATUS_QUERY_FAILED = -2,');
|
|
1391
|
+
lines.push(' AK_STATUS_UNSUPPORTED = -3,');
|
|
1392
|
+
lines.push('};');
|
|
1393
|
+
lines.push('');
|
|
1394
|
+
lines.push('typedef struct AkCommandSpec {');
|
|
1395
|
+
lines.push(' const char *verb;');
|
|
1396
|
+
lines.push(' const char *summary;');
|
|
1397
|
+
lines.push('} AkCommandSpec;');
|
|
1398
|
+
lines.push('');
|
|
1399
|
+
lines.push('typedef struct AkCliModel {');
|
|
1400
|
+
lines.push(' const char *tool_name;');
|
|
1401
|
+
lines.push(' const char *help_banner;');
|
|
1402
|
+
lines.push(' AkCommandSpec commands[8];');
|
|
1403
|
+
lines.push(' int command_count;');
|
|
1404
|
+
lines.push('} AkCliModel;');
|
|
1405
|
+
lines.push('');
|
|
1406
|
+
lines.push('typedef struct AkResolvedApiTable {');
|
|
1407
|
+
lines.push(' int ready;');
|
|
1408
|
+
lines.push(' const char *role;');
|
|
1409
|
+
lines.push(' const char *apis[8];');
|
|
1410
|
+
lines.push(' int api_count;');
|
|
1411
|
+
lines.push('} AkResolvedApiTable;');
|
|
1412
|
+
lines.push('');
|
|
1413
|
+
lines.push('typedef struct AkProcessProbeResult {');
|
|
1414
|
+
lines.push(' int status;');
|
|
1415
|
+
lines.push(' int remote_process_checked;');
|
|
1416
|
+
lines.push(' int code_integrity_checked;');
|
|
1417
|
+
lines.push(' const char *last_observation;');
|
|
1418
|
+
lines.push('} AkProcessProbeResult;');
|
|
1419
|
+
lines.push('');
|
|
1420
|
+
lines.push('typedef struct AkPackerHeuristics {');
|
|
1421
|
+
lines.push(' int score;');
|
|
1422
|
+
lines.push(' const char *matched_signatures[8];');
|
|
1423
|
+
lines.push(' int matched_count;');
|
|
1424
|
+
lines.push(' const char *entrypoint_signal;');
|
|
1425
|
+
lines.push('} AkPackerHeuristics;');
|
|
1426
|
+
lines.push('');
|
|
1427
|
+
lines.push('typedef struct AkSemanticInputs {');
|
|
1428
|
+
lines.push(' uint64_t scalar_args[8];');
|
|
1429
|
+
lines.push(' const char *string_args[4];');
|
|
1430
|
+
lines.push(' void *pointer_args[8];');
|
|
1431
|
+
lines.push(' uintptr_t handle_args[4];');
|
|
1432
|
+
lines.push('} AkSemanticInputs;');
|
|
1433
|
+
lines.push('');
|
|
1434
|
+
lines.push('/* Semantic input helpers keep rewrite code readable while preserving a compact ABI. */');
|
|
1435
|
+
lines.push('#define AK_INPUT_PRIMARY_TEXT(inputs) ((inputs) != 0 ? (inputs)->string_args[0] : 0)');
|
|
1436
|
+
lines.push('#define AK_INPUT_SECONDARY_TEXT(inputs) ((inputs) != 0 ? (inputs)->string_args[1] : 0)');
|
|
1437
|
+
lines.push('#define AK_INPUT_PRIMARY_POINTER(inputs) ((inputs) != 0 ? (inputs)->pointer_args[0] : 0)');
|
|
1438
|
+
lines.push('#define AK_INPUT_PRIMARY_HANDLE(inputs) ((inputs) != 0 ? (inputs)->handle_args[0] : 0)');
|
|
1439
|
+
lines.push('#define AK_INPUT_SECONDARY_HANDLE(inputs) ((inputs) != 0 ? (inputs)->handle_args[1] : 0)');
|
|
1440
|
+
lines.push('#define AK_INPUT_PRIMARY_MODE(inputs) ((inputs) != 0 ? (inputs)->scalar_args[0] : 0)');
|
|
1441
|
+
lines.push('');
|
|
1442
|
+
lines.push('/* Stage labels are centralized so the rewrite reads like a named state machine. */');
|
|
1443
|
+
lines.push('#define AK_STAGE_COMMAND_MODEL_READY "command_model_ready"');
|
|
1444
|
+
lines.push('#define AK_STAGE_PREPARE_REMOTE_PROCESS_ACCESS "prepare_remote_process_access"');
|
|
1445
|
+
lines.push('#define AK_STAGE_ANTI_ANALYSIS_CHECKS "anti_analysis_checks"');
|
|
1446
|
+
lines.push('#define AK_STAGE_REGISTRY_OPERATIONS "registry_operations"');
|
|
1447
|
+
lines.push('#define AK_STAGE_FILE_OPERATIONS "file_operations"');
|
|
1448
|
+
lines.push('#define AK_STAGE_SCAN_PE_LAYOUT "scan_pe_layout"');
|
|
1449
|
+
lines.push('');
|
|
1450
|
+
lines.push('typedef struct AkSemanticOutputs {');
|
|
1451
|
+
lines.push(' int status_code;');
|
|
1452
|
+
lines.push(' uint64_t scalar_result;');
|
|
1453
|
+
lines.push(' const char *status_detail;');
|
|
1454
|
+
lines.push(' const char *observed_stage;');
|
|
1455
|
+
lines.push('} AkSemanticOutputs;');
|
|
1456
|
+
lines.push('');
|
|
1457
|
+
lines.push('typedef struct AkRemoteProcessRequest {');
|
|
1458
|
+
lines.push(' const char *target_selector;');
|
|
1459
|
+
lines.push(' const char *launch_command_line;');
|
|
1460
|
+
lines.push(' void *payload_view;');
|
|
1461
|
+
lines.push(' uintptr_t process_handle;');
|
|
1462
|
+
lines.push(' uintptr_t thread_handle;');
|
|
1463
|
+
lines.push(' uint64_t mode_flags;');
|
|
1464
|
+
lines.push('} AkRemoteProcessRequest;');
|
|
1465
|
+
lines.push('');
|
|
1466
|
+
lines.push('typedef struct AkExecutionTransferResult {');
|
|
1467
|
+
lines.push(' int status_code;');
|
|
1468
|
+
lines.push(' const char *stage_name;');
|
|
1469
|
+
lines.push(' const char *detail;');
|
|
1470
|
+
lines.push(' const char *transfer_mode;');
|
|
1471
|
+
lines.push(' uint64_t observed_value;');
|
|
1472
|
+
lines.push('} AkExecutionTransferResult;');
|
|
1473
|
+
lines.push('');
|
|
1474
|
+
lines.push('typedef struct AkProcessOperationSession {');
|
|
1475
|
+
lines.push(' AkRemoteProcessRequest remote_request;');
|
|
1476
|
+
lines.push(' AkExecutionTransferResult transfer_result;');
|
|
1477
|
+
lines.push('} AkProcessOperationSession;');
|
|
1478
|
+
lines.push('');
|
|
1479
|
+
lines.push('typedef struct AkCapabilityDispatchRequest {');
|
|
1480
|
+
lines.push(' const char *primary_hint;');
|
|
1481
|
+
lines.push(' const char *secondary_hint;');
|
|
1482
|
+
lines.push(' uint64_t mode_flags;');
|
|
1483
|
+
lines.push('} AkCapabilityDispatchRequest;');
|
|
1484
|
+
lines.push('');
|
|
1485
|
+
lines.push('typedef struct AkCapabilityDispatchResult {');
|
|
1486
|
+
lines.push(' int status_code;');
|
|
1487
|
+
lines.push(' const char *stage_name;');
|
|
1488
|
+
lines.push(' const char *detail;');
|
|
1489
|
+
lines.push(' uint64_t observed_value;');
|
|
1490
|
+
lines.push('} AkCapabilityDispatchResult;');
|
|
1491
|
+
lines.push('');
|
|
1492
|
+
lines.push('typedef struct AkCapabilityDispatchPlan {');
|
|
1493
|
+
lines.push(' AkCapabilityDispatchRequest request;');
|
|
1494
|
+
lines.push(' AkCapabilityDispatchResult result;');
|
|
1495
|
+
lines.push('} AkCapabilityDispatchPlan;');
|
|
1496
|
+
lines.push('');
|
|
1497
|
+
lines.push('typedef struct AkPackerScanRequest {');
|
|
1498
|
+
lines.push(' const char *command_hint;');
|
|
1499
|
+
lines.push(' void *image_view;');
|
|
1500
|
+
lines.push(' uint64_t mode_flags;');
|
|
1501
|
+
lines.push('} AkPackerScanRequest;');
|
|
1502
|
+
lines.push('');
|
|
1503
|
+
lines.push('typedef struct AkPackerScanResult {');
|
|
1504
|
+
lines.push(' int status_code;');
|
|
1505
|
+
lines.push(' const char *stage_name;');
|
|
1506
|
+
lines.push(' const char *detail;');
|
|
1507
|
+
lines.push(' uint64_t heuristic_score;');
|
|
1508
|
+
lines.push('} AkPackerScanResult;');
|
|
1509
|
+
lines.push('');
|
|
1510
|
+
lines.push('typedef struct AkPackerScanSession {');
|
|
1511
|
+
lines.push(' AkPackerScanRequest request;');
|
|
1512
|
+
lines.push(' AkPackerScanResult result;');
|
|
1513
|
+
lines.push('} AkPackerScanSession;');
|
|
1514
|
+
lines.push('');
|
|
1515
|
+
lines.push('static inline AkRemoteProcessRequest ak_build_remote_process_request(const AkSemanticInputs *inputs)');
|
|
1516
|
+
lines.push('{');
|
|
1517
|
+
lines.push(' AkRemoteProcessRequest request = {0};');
|
|
1518
|
+
lines.push(' request.target_selector = AK_INPUT_PRIMARY_TEXT(inputs);');
|
|
1519
|
+
lines.push(' request.launch_command_line = AK_INPUT_SECONDARY_TEXT(inputs) != 0 ? AK_INPUT_SECONDARY_TEXT(inputs) : AK_INPUT_PRIMARY_TEXT(inputs);');
|
|
1520
|
+
lines.push(' request.payload_view = AK_INPUT_PRIMARY_POINTER(inputs);');
|
|
1521
|
+
lines.push(' request.process_handle = AK_INPUT_PRIMARY_HANDLE(inputs);');
|
|
1522
|
+
lines.push(' request.thread_handle = AK_INPUT_SECONDARY_HANDLE(inputs);');
|
|
1523
|
+
lines.push(' request.mode_flags = AK_INPUT_PRIMARY_MODE(inputs);');
|
|
1524
|
+
lines.push(' return request;');
|
|
1525
|
+
lines.push('}');
|
|
1526
|
+
lines.push('');
|
|
1527
|
+
lines.push('static inline AkExecutionTransferResult ak_init_execution_transfer_result(void)');
|
|
1528
|
+
lines.push('{');
|
|
1529
|
+
lines.push(' AkExecutionTransferResult result = { AK_STATUS_UNSUPPORTED, 0, 0, 0, 0 };');
|
|
1530
|
+
lines.push(' return result;');
|
|
1531
|
+
lines.push('}');
|
|
1532
|
+
lines.push('');
|
|
1533
|
+
lines.push('static inline AkProcessOperationSession ak_start_process_session(const AkSemanticInputs *inputs)');
|
|
1534
|
+
lines.push('{');
|
|
1535
|
+
lines.push(' AkProcessOperationSession session = {0};');
|
|
1536
|
+
lines.push(' session.remote_request = ak_build_remote_process_request(inputs);');
|
|
1537
|
+
lines.push(' session.transfer_result = ak_init_execution_transfer_result();');
|
|
1538
|
+
lines.push(' return session;');
|
|
1539
|
+
lines.push('}');
|
|
1540
|
+
lines.push('');
|
|
1541
|
+
lines.push('static inline void ak_publish_process_result(AkSemanticOutputs *outputs, const AkExecutionTransferResult *result)');
|
|
1542
|
+
lines.push('{');
|
|
1543
|
+
lines.push(' if (outputs == 0 || result == 0) {');
|
|
1544
|
+
lines.push(' return;');
|
|
1545
|
+
lines.push(' }');
|
|
1546
|
+
lines.push(' outputs->status_code = result->status_code;');
|
|
1547
|
+
lines.push(' outputs->scalar_result = result->observed_value;');
|
|
1548
|
+
lines.push(' outputs->status_detail = result->detail;');
|
|
1549
|
+
lines.push(' outputs->observed_stage = result->stage_name;');
|
|
1550
|
+
lines.push('}');
|
|
1551
|
+
lines.push('');
|
|
1552
|
+
lines.push('static inline AkCapabilityDispatchRequest ak_build_capability_request(const AkSemanticInputs *inputs)');
|
|
1553
|
+
lines.push('{');
|
|
1554
|
+
lines.push(' AkCapabilityDispatchRequest request = {0};');
|
|
1555
|
+
lines.push(' request.primary_hint = AK_INPUT_PRIMARY_TEXT(inputs);');
|
|
1556
|
+
lines.push(' request.secondary_hint = AK_INPUT_SECONDARY_TEXT(inputs);');
|
|
1557
|
+
lines.push(' request.mode_flags = AK_INPUT_PRIMARY_MODE(inputs);');
|
|
1558
|
+
lines.push(' return request;');
|
|
1559
|
+
lines.push('}');
|
|
1560
|
+
lines.push('');
|
|
1561
|
+
lines.push('static inline AkCapabilityDispatchResult ak_init_capability_result(void)');
|
|
1562
|
+
lines.push('{');
|
|
1563
|
+
lines.push(' AkCapabilityDispatchResult result = { AK_STATUS_UNSUPPORTED, 0, 0, 0 };');
|
|
1564
|
+
lines.push(' return result;');
|
|
1565
|
+
lines.push('}');
|
|
1566
|
+
lines.push('');
|
|
1567
|
+
lines.push('static inline AkCapabilityDispatchPlan ak_start_capability_plan(const AkSemanticInputs *inputs)');
|
|
1568
|
+
lines.push('{');
|
|
1569
|
+
lines.push(' AkCapabilityDispatchPlan plan = {0};');
|
|
1570
|
+
lines.push(' plan.request = ak_build_capability_request(inputs);');
|
|
1571
|
+
lines.push(' plan.result = ak_init_capability_result();');
|
|
1572
|
+
lines.push(' return plan;');
|
|
1573
|
+
lines.push('}');
|
|
1574
|
+
lines.push('');
|
|
1575
|
+
lines.push('static inline void ak_publish_capability_result(AkSemanticOutputs *outputs, const AkCapabilityDispatchResult *result)');
|
|
1576
|
+
lines.push('{');
|
|
1577
|
+
lines.push(' if (outputs == 0 || result == 0) {');
|
|
1578
|
+
lines.push(' return;');
|
|
1579
|
+
lines.push(' }');
|
|
1580
|
+
lines.push(' outputs->status_code = result->status_code;');
|
|
1581
|
+
lines.push(' outputs->scalar_result = result->observed_value;');
|
|
1582
|
+
lines.push(' outputs->status_detail = result->detail;');
|
|
1583
|
+
lines.push(' outputs->observed_stage = result->stage_name;');
|
|
1584
|
+
lines.push('}');
|
|
1585
|
+
lines.push('');
|
|
1586
|
+
lines.push('static inline AkPackerScanRequest ak_build_packer_request(const AkSemanticInputs *inputs)');
|
|
1587
|
+
lines.push('{');
|
|
1588
|
+
lines.push(' AkPackerScanRequest request = {0};');
|
|
1589
|
+
lines.push(' request.command_hint = AK_INPUT_PRIMARY_TEXT(inputs);');
|
|
1590
|
+
lines.push(' request.image_view = AK_INPUT_PRIMARY_POINTER(inputs);');
|
|
1591
|
+
lines.push(' request.mode_flags = AK_INPUT_PRIMARY_MODE(inputs);');
|
|
1592
|
+
lines.push(' return request;');
|
|
1593
|
+
lines.push('}');
|
|
1594
|
+
lines.push('');
|
|
1595
|
+
lines.push('static inline AkPackerScanResult ak_init_packer_result(void)');
|
|
1596
|
+
lines.push('{');
|
|
1597
|
+
lines.push(' AkPackerScanResult result = { AK_STATUS_UNSUPPORTED, 0, 0, 0 };');
|
|
1598
|
+
lines.push(' return result;');
|
|
1599
|
+
lines.push('}');
|
|
1600
|
+
lines.push('');
|
|
1601
|
+
lines.push('static inline AkPackerScanSession ak_start_packer_session(const AkSemanticInputs *inputs)');
|
|
1602
|
+
lines.push('{');
|
|
1603
|
+
lines.push(' AkPackerScanSession session = {0};');
|
|
1604
|
+
lines.push(' session.request = ak_build_packer_request(inputs);');
|
|
1605
|
+
lines.push(' session.result = ak_init_packer_result();');
|
|
1606
|
+
lines.push(' return session;');
|
|
1607
|
+
lines.push('}');
|
|
1608
|
+
lines.push('');
|
|
1609
|
+
lines.push('static inline void ak_publish_packer_result(AkSemanticOutputs *outputs, const AkPackerScanResult *result)');
|
|
1610
|
+
lines.push('{');
|
|
1611
|
+
lines.push(' if (outputs == 0 || result == 0) {');
|
|
1612
|
+
lines.push(' return;');
|
|
1613
|
+
lines.push(' }');
|
|
1614
|
+
lines.push(' outputs->status_code = result->status_code;');
|
|
1615
|
+
lines.push(' outputs->scalar_result = result->heuristic_score;');
|
|
1616
|
+
lines.push(' outputs->status_detail = result->detail;');
|
|
1617
|
+
lines.push(' outputs->observed_stage = result->stage_name;');
|
|
1618
|
+
lines.push('}');
|
|
1619
|
+
lines.push('');
|
|
1620
|
+
lines.push('typedef struct AkRuntimeContext {');
|
|
1621
|
+
lines.push(' AkResolvedApiTable dynamic_apis;');
|
|
1622
|
+
lines.push(' AkResolvedApiTable file_apis;');
|
|
1623
|
+
lines.push(' AkResolvedApiTable registry_apis;');
|
|
1624
|
+
lines.push(' AkProcessProbeResult process_probe;');
|
|
1625
|
+
lines.push(' AkPackerHeuristics packer_heuristics;');
|
|
1626
|
+
lines.push(' AkCliModel cli;');
|
|
1627
|
+
lines.push(' int last_status;');
|
|
1628
|
+
lines.push(' const char *last_status_detail;');
|
|
1629
|
+
lines.push('} AkRuntimeContext;');
|
|
1630
|
+
lines.push('');
|
|
1631
|
+
return lines.join('\n') + '\n';
|
|
1632
|
+
}
|
|
1633
|
+
function buildInterfaceContent(module) {
|
|
1634
|
+
const lines = [];
|
|
1635
|
+
lines.push(`/* module: ${module.name} */`);
|
|
1636
|
+
if (module.runtimeApis.size > 0 || module.runtimeStages.size > 0) {
|
|
1637
|
+
lines.push(`/* runtime: apis=${Array.from(module.runtimeApis).slice(0, 6).join(', ') || 'none'} | stages=${Array.from(module.runtimeStages).slice(0, 4).join(', ') || 'none'} */`);
|
|
1638
|
+
}
|
|
1639
|
+
lines.push('#pragma once');
|
|
1640
|
+
lines.push('');
|
|
1641
|
+
lines.push('#include "reconstruct_support.h"');
|
|
1642
|
+
lines.push('');
|
|
1643
|
+
for (const func of module.functions) {
|
|
1644
|
+
const names = deriveRewriteEntryNames(func, module);
|
|
1645
|
+
lines.push(`int ${names.originalName}(void); /* ${func.address} confidence=${func.confidence.toFixed(2)} */`);
|
|
1646
|
+
lines.push(`int ${names.implementationName}(AkRuntimeContext *runtime_ctx, const AkSemanticInputs *inputs, AkSemanticOutputs *outputs); /* semantic_alias=${names.semanticAlias} */`);
|
|
1647
|
+
lines.push(`/* contract: ${buildRecoveredContractHints(func, module).map((item) => normalizeReadableHint(item, 96)).join(' | ')} */`);
|
|
1648
|
+
lines.push('');
|
|
1649
|
+
}
|
|
1650
|
+
return lines.join('\n') + '\n';
|
|
1651
|
+
}
|
|
1652
|
+
function buildPseudocodeContent(module) {
|
|
1653
|
+
const lines = [];
|
|
1654
|
+
lines.push(`/* module: ${module.name} */`);
|
|
1655
|
+
if (module.runtimeApis.size > 0 || module.runtimeStages.size > 0) {
|
|
1656
|
+
lines.push(`/* runtime: apis=${Array.from(module.runtimeApis).slice(0, 6).join(', ') || 'none'} | stages=${Array.from(module.runtimeStages).slice(0, 4).join(', ') || 'none'} */`);
|
|
1657
|
+
}
|
|
1658
|
+
lines.push('');
|
|
1659
|
+
for (const func of module.functions) {
|
|
1660
|
+
lines.push(func.source_like_snippet);
|
|
1661
|
+
lines.push('');
|
|
1662
|
+
}
|
|
1663
|
+
return lines.join('\n').trimEnd() + '\n';
|
|
1664
|
+
}
|
|
1665
|
+
function summarizeXrefSignals(func) {
|
|
1666
|
+
const signals = (func.xref_signals || [])
|
|
1667
|
+
.map((item) => `${item.api}:${item.provenance}`)
|
|
1668
|
+
.slice(0, 6);
|
|
1669
|
+
return signals.length > 0 ? signals.join(', ') : 'none';
|
|
1670
|
+
}
|
|
1671
|
+
function summarizeRelationshipEntries(entries) {
|
|
1672
|
+
const labels = (entries || [])
|
|
1673
|
+
.slice(0, 4)
|
|
1674
|
+
.map((entry) => {
|
|
1675
|
+
const details = [
|
|
1676
|
+
...(entry.relation_types || []),
|
|
1677
|
+
...(entry.reference_types || []),
|
|
1678
|
+
entry.resolved_by ? `resolved_by=${entry.resolved_by}` : '',
|
|
1679
|
+
entry.is_exact === false ? 'heuristic' : '',
|
|
1680
|
+
].filter((item) => item.length > 0);
|
|
1681
|
+
return details.length > 0 ? `${entry.target} [${details.join('; ')}]` : entry.target;
|
|
1682
|
+
});
|
|
1683
|
+
return labels.length > 0 ? labels.join(', ') : 'none';
|
|
1684
|
+
}
|
|
1685
|
+
function summarizeRewriteParameterRoles(func) {
|
|
1686
|
+
const roles = func.parameter_roles || func.semantic_evidence?.parameter_roles || [];
|
|
1687
|
+
if (roles.length === 0) {
|
|
1688
|
+
return 'none';
|
|
1689
|
+
}
|
|
1690
|
+
return roles
|
|
1691
|
+
.slice(0, 6)
|
|
1692
|
+
.map((item) => `${item.slot}=>${item.role}<${item.inferred_type}>`)
|
|
1693
|
+
.join('; ');
|
|
1694
|
+
}
|
|
1695
|
+
function summarizeRewriteStateRoles(func) {
|
|
1696
|
+
const roles = func.state_roles || func.semantic_evidence?.state_roles || [];
|
|
1697
|
+
if (roles.length === 0) {
|
|
1698
|
+
return 'none';
|
|
1699
|
+
}
|
|
1700
|
+
return roles
|
|
1701
|
+
.slice(0, 6)
|
|
1702
|
+
.map((item) => `${item.state_key}=>${item.role}`)
|
|
1703
|
+
.join('; ');
|
|
1704
|
+
}
|
|
1705
|
+
function summarizeRewriteStructInference(func) {
|
|
1706
|
+
const structs = func.struct_inference || func.semantic_evidence?.struct_inference || [];
|
|
1707
|
+
if (structs.length === 0) {
|
|
1708
|
+
return 'none';
|
|
1709
|
+
}
|
|
1710
|
+
return structs
|
|
1711
|
+
.slice(0, 4)
|
|
1712
|
+
.map((item) => `${item.semantic_name}${item.rewrite_type_name ? `=>${item.rewrite_type_name}` : ''}`)
|
|
1713
|
+
.join('; ');
|
|
1714
|
+
}
|
|
1715
|
+
function collectRewriteFeatures(func, module) {
|
|
1716
|
+
const functionCorpus = [
|
|
1717
|
+
func.function,
|
|
1718
|
+
func.semantic_summary || '',
|
|
1719
|
+
func.source_like_snippet,
|
|
1720
|
+
(func.behavior_tags || []).join(' '),
|
|
1721
|
+
(func.rank_reasons || []).join(' '),
|
|
1722
|
+
(func.xref_signals || []).map((item) => `${item.api} ${item.provenance}`).join(' '),
|
|
1723
|
+
(func.call_context?.callers || []).join(' '),
|
|
1724
|
+
(func.call_context?.callees || []).join(' '),
|
|
1725
|
+
(func.call_relationships?.callers || [])
|
|
1726
|
+
.flatMap((item) => [item.target, ...(item.relation_types || []), ...(item.reference_types || [])])
|
|
1727
|
+
.join(' '),
|
|
1728
|
+
(func.call_relationships?.callees || [])
|
|
1729
|
+
.flatMap((item) => [item.target, ...(item.relation_types || []), ...(item.reference_types || [])])
|
|
1730
|
+
.join(' '),
|
|
1731
|
+
]
|
|
1732
|
+
.join('\n')
|
|
1733
|
+
.replace(/\s+/g, ' ')
|
|
1734
|
+
.replace(/registry\\src/gi, ' ')
|
|
1735
|
+
.replace(/\.cargo\\registry/gi, ' ')
|
|
1736
|
+
.replace(/stack backtrace:?/gi, ' ')
|
|
1737
|
+
.replace(/internal error: entered unreachable code/gi, ' ')
|
|
1738
|
+
.toLowerCase();
|
|
1739
|
+
const moduleCorpus = [
|
|
1740
|
+
module.name,
|
|
1741
|
+
Array.from(module.stringHints)
|
|
1742
|
+
.map((value) => stripFeatureNoise(value))
|
|
1743
|
+
.filter((value) => !isCliNoiseCandidate(value))
|
|
1744
|
+
.join(' '),
|
|
1745
|
+
Array.from(module.importHints).join(' '),
|
|
1746
|
+
]
|
|
1747
|
+
.join('\n')
|
|
1748
|
+
.replace(/\s+/g, ' ')
|
|
1749
|
+
.replace(/registry\\src/gi, ' ')
|
|
1750
|
+
.replace(/\.cargo\\registry/gi, ' ')
|
|
1751
|
+
.toLowerCase();
|
|
1752
|
+
const packerPattern = /\b(packer|protector|upx|vmprotect|themida|aspack|entry point in non-first section|goblin|iced-x86)\b/i;
|
|
1753
|
+
const hasDynamicResolver = /\b(getprocaddress|loadlibrary|loadlibraryex)\b/i.test(functionCorpus);
|
|
1754
|
+
const hasProcessInjection = /\b(writeprocessmemory|setthreadcontext|resumethread|createremotethread|virtualallocex)\b/i.test(functionCorpus) || (func.behavior_tags || []).includes('process_injection');
|
|
1755
|
+
const hasProcessSpawn = /\b(createprocessw|createprocessa|cmd\.exe|shellexecute|winexec)\b/i.test(functionCorpus) ||
|
|
1756
|
+
(func.behavior_tags || []).includes('process_spawn');
|
|
1757
|
+
const hasFileApiTable = /\b(createfile\w*|readfile\w*|writefile\w*|deletefile\w*|copyfile\w*|movefile\w*|findfirstfile\w*|findnextfile\w*|gettemppath\w*)\b/i.test(functionCorpus) ||
|
|
1758
|
+
((module.name === 'process_ops' || module.name === 'file_ops') &&
|
|
1759
|
+
/\b(createfile\w*|readfile\w*|writefile\w*|deletefile\w*|copyfile\w*|movefile\w*|findfirstfile\w*|findnextfile\w*|gettemppath\w*)\b/i.test(moduleCorpus));
|
|
1760
|
+
const hasRegistryApiTable = /\b(reg(open|set|create|query|delete)key\w*|reg(set|query)value\w*|registry)\b/i.test(functionCorpus) ||
|
|
1761
|
+
((module.name === 'process_ops' || module.name === 'registry_ops') &&
|
|
1762
|
+
/\b(reg(open|set|create|query|delete)key\w*|reg(set|query)value\w*|registry)\b/i.test(moduleCorpus));
|
|
1763
|
+
const hasNtQueryInformationProcess = /\bntqueryinformationprocess\b/i.test(functionCorpus);
|
|
1764
|
+
const hasNtQuerySystemInformation = /\bntquerysysteminformation\b/i.test(functionCorpus);
|
|
1765
|
+
const hasCodeIntegrity = /\b(code integrity|codeintegrity|test signing|kernel_code_integrity_status_raw)\b/i.test(functionCorpus);
|
|
1766
|
+
const allowModulePackerBias = module.name === 'packer_analysis' &&
|
|
1767
|
+
!hasProcessInjection &&
|
|
1768
|
+
!hasProcessSpawn &&
|
|
1769
|
+
!hasNtQueryInformationProcess &&
|
|
1770
|
+
!hasNtQuerySystemInformation &&
|
|
1771
|
+
!hasCodeIntegrity;
|
|
1772
|
+
return {
|
|
1773
|
+
hasDynamicResolver,
|
|
1774
|
+
hasProcessInjection,
|
|
1775
|
+
hasProcessSpawn,
|
|
1776
|
+
hasFileApiTable,
|
|
1777
|
+
hasRegistryApiTable,
|
|
1778
|
+
hasNtQueryInformationProcess,
|
|
1779
|
+
hasNtQuerySystemInformation,
|
|
1780
|
+
hasCodeIntegrity,
|
|
1781
|
+
hasPackerScan: packerPattern.test(functionCorpus) || (allowModulePackerBias && packerPattern.test(moduleCorpus)),
|
|
1782
|
+
hasTailJumpHints: (func.call_relationships?.callers || []).some((item) => (item.relation_types || []).some((relation) => relation.toLowerCase() === 'tail_jump_hint')) ||
|
|
1783
|
+
(func.call_relationships?.callees || []).some((item) => (item.relation_types || []).some((relation) => relation.toLowerCase() === 'tail_jump_hint')),
|
|
1784
|
+
hasBodyReferenceHints: (func.call_relationships?.callers || []).some((item) => (item.relation_types || []).some((relation) => relation.toLowerCase() === 'body_reference_hint')) ||
|
|
1785
|
+
(func.call_relationships?.callees || []).some((item) => (item.relation_types || []).some((relation) => relation.toLowerCase() === 'body_reference_hint')),
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
function synthesizeModuleCliCommands(module, bannerCandidate, pushCommand) {
|
|
1789
|
+
const moduleFeatures = module.functions.map((func) => collectRewriteFeatures(func, module));
|
|
1790
|
+
const hasProcessInjection = moduleFeatures.some((item) => item.hasProcessInjection);
|
|
1791
|
+
const hasProcessSpawn = moduleFeatures.some((item) => item.hasProcessSpawn);
|
|
1792
|
+
const hasFileApiTable = moduleFeatures.some((item) => item.hasFileApiTable);
|
|
1793
|
+
const hasRegistryApiTable = moduleFeatures.some((item) => item.hasRegistryApiTable);
|
|
1794
|
+
const hasPackerScan = moduleFeatures.some((item) => item.hasPackerScan);
|
|
1795
|
+
const helpSummary = normalizeReadableHint(bannerCandidate, 120);
|
|
1796
|
+
if (hasPackerScan) {
|
|
1797
|
+
pushCommand('scan', helpSummary || 'Recovered PE layout and packer/protector scan pipeline.');
|
|
1798
|
+
pushCommand('detect', 'Recovered packer/protector detection flow driven by PE layout and signature hints.');
|
|
1799
|
+
}
|
|
1800
|
+
if (hasProcessInjection) {
|
|
1801
|
+
pushCommand('inject', 'Recovered remote-process memory and thread-context operation pipeline.');
|
|
1802
|
+
}
|
|
1803
|
+
if (hasProcessSpawn) {
|
|
1804
|
+
pushCommand('spawn', 'Recovered process creation and launch orchestration flow.');
|
|
1805
|
+
}
|
|
1806
|
+
if (hasFileApiTable) {
|
|
1807
|
+
pushCommand('dump', 'Recovered file capability table suggests dump or file materialization support.');
|
|
1808
|
+
}
|
|
1809
|
+
if (hasRegistryApiTable) {
|
|
1810
|
+
pushCommand('query', 'Recovered registry capability table suggests registry inspection or configuration lookup.');
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
function getValidatedSemanticName(func) {
|
|
1814
|
+
const validatedName = func.name_resolution?.validated_name;
|
|
1815
|
+
if (typeof validatedName === 'string' && validatedName.trim().length > 0) {
|
|
1816
|
+
return validatedName;
|
|
1817
|
+
}
|
|
1818
|
+
const suggestedName = func.suggested_name;
|
|
1819
|
+
if (typeof suggestedName === 'string' && suggestedName.trim().length > 0) {
|
|
1820
|
+
return suggestedName;
|
|
1821
|
+
}
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1824
|
+
function buildSemanticAlias(func, module) {
|
|
1825
|
+
const features = collectRewriteFeatures(func, module);
|
|
1826
|
+
const suffix = func.address.replace(/^0x/i, '').toLowerCase();
|
|
1827
|
+
const suggestedName = sanitizeSymbolForHeader(getValidatedSemanticName(func) || '');
|
|
1828
|
+
if (suggestedName && Number(func.rename_confidence || 0) >= 0.45) {
|
|
1829
|
+
return `${suggestedName}_${suffix}`;
|
|
1830
|
+
}
|
|
1831
|
+
if (features.hasProcessInjection && (features.hasFileApiTable || features.hasRegistryApiTable)) {
|
|
1832
|
+
return `build_capability_dispatch_tables_${suffix}`;
|
|
1833
|
+
}
|
|
1834
|
+
if (features.hasProcessInjection || features.hasProcessSpawn) {
|
|
1835
|
+
return `dispatch_process_operation_${suffix}`;
|
|
1836
|
+
}
|
|
1837
|
+
if (features.hasFileApiTable || features.hasRegistryApiTable) {
|
|
1838
|
+
return `prepare_capability_tables_${suffix}`;
|
|
1839
|
+
}
|
|
1840
|
+
if (features.hasNtQueryInformationProcess && features.hasCodeIntegrity) {
|
|
1841
|
+
return `probe_process_and_code_integrity_${suffix}`;
|
|
1842
|
+
}
|
|
1843
|
+
if (features.hasNtQueryInformationProcess) {
|
|
1844
|
+
return `query_remote_process_snapshot_${suffix}`;
|
|
1845
|
+
}
|
|
1846
|
+
if (features.hasNtQuerySystemInformation || features.hasCodeIntegrity) {
|
|
1847
|
+
return `query_code_integrity_state_${suffix}`;
|
|
1848
|
+
}
|
|
1849
|
+
if (features.hasPackerScan) {
|
|
1850
|
+
return `scan_packer_signatures_${suffix}`;
|
|
1851
|
+
}
|
|
1852
|
+
if (features.hasTailJumpHints) {
|
|
1853
|
+
return `tailcall_dispatch_thunk_${suffix}`;
|
|
1854
|
+
}
|
|
1855
|
+
const summary = `${module.name} ${func.semantic_summary || ''}`.toLowerCase();
|
|
1856
|
+
if (summary.includes('dispatcher')) {
|
|
1857
|
+
return `dispatch_${sanitizeModuleName(module.name)}_${suffix}`;
|
|
1858
|
+
}
|
|
1859
|
+
return `${sanitizeModuleName(module.name)}_${sanitizeSymbolForHeader(func.function)}_${suffix}`;
|
|
1860
|
+
}
|
|
1861
|
+
function extractApiHint(value) {
|
|
1862
|
+
const apiName = value.includes('!') ? value.split('!').pop() || value : value;
|
|
1863
|
+
return normalizeReadableHint(apiName, 96);
|
|
1864
|
+
}
|
|
1865
|
+
function collectModuleApiHints(module) {
|
|
1866
|
+
return dedupe([
|
|
1867
|
+
...Array.from(module.importHints).map((value) => extractApiHint(value)),
|
|
1868
|
+
...Array.from(module.runtimeApis).map((value) => normalizeReadableHint(value, 96)),
|
|
1869
|
+
...module.functions.flatMap((func) => (func.xref_signals || []).map((item) => normalizeReadableHint(item.api, 96))),
|
|
1870
|
+
].filter((value) => isReadableTextCandidate(value)));
|
|
1871
|
+
}
|
|
1872
|
+
function collectModuleStringHints(module) {
|
|
1873
|
+
return dedupe(Array.from(module.stringHints)
|
|
1874
|
+
.map((value) => normalizeReadableHint(stripFeatureNoise(value), 96))
|
|
1875
|
+
.filter((value) => isReadableTextCandidate(value) && !isCliNoiseCandidate(value)));
|
|
1876
|
+
}
|
|
1877
|
+
function selectMatchingHints(values, matcher, limit = 8) {
|
|
1878
|
+
return values.filter((value) => matcher.test(value)).slice(0, limit);
|
|
1879
|
+
}
|
|
1880
|
+
function buildCStringTable(name, values) {
|
|
1881
|
+
const hints = dedupe(values.map((value) => normalizeReadableHint(value, 96))).slice(0, 8);
|
|
1882
|
+
const lines = [];
|
|
1883
|
+
lines.push(`static const int ${name}_COUNT = ${hints.length};`);
|
|
1884
|
+
lines.push(`static const char *${name}[] = {`);
|
|
1885
|
+
if (hints.length === 0) {
|
|
1886
|
+
lines.push(' 0,');
|
|
1887
|
+
}
|
|
1888
|
+
else {
|
|
1889
|
+
for (const hint of hints) {
|
|
1890
|
+
lines.push(` "${escapeCString(hint)}",`);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
lines.push('};');
|
|
1894
|
+
lines.push('');
|
|
1895
|
+
return lines;
|
|
1896
|
+
}
|
|
1897
|
+
function buildModuleRewritePrelude(module) {
|
|
1898
|
+
const moduleFeatures = module.functions.map((func) => collectRewriteFeatures(func, module));
|
|
1899
|
+
const needsResolvedApiTable = moduleFeatures.some((item) => item.hasDynamicResolver || item.hasFileApiTable || item.hasRegistryApiTable);
|
|
1900
|
+
const needsProcessProbe = moduleFeatures.some((item) => item.hasProcessInjection ||
|
|
1901
|
+
item.hasProcessSpawn ||
|
|
1902
|
+
item.hasNtQueryInformationProcess ||
|
|
1903
|
+
item.hasNtQuerySystemInformation ||
|
|
1904
|
+
item.hasCodeIntegrity);
|
|
1905
|
+
const needsPackerHeuristics = moduleFeatures.some((item) => item.hasPackerScan);
|
|
1906
|
+
const apiHints = collectModuleApiHints(module);
|
|
1907
|
+
const stringHints = collectModuleStringHints(module);
|
|
1908
|
+
const combinedHints = dedupe([...apiHints, ...stringHints]);
|
|
1909
|
+
const dynamicApiHints = selectMatchingHints(combinedHints, /\b(GetProcAddress|LoadLibrary\w*|GetModuleHandle\w*)\b/i);
|
|
1910
|
+
const fileApiHints = selectMatchingHints(combinedHints, /\b(CreateFile\w*|ReadFile\w*|WriteFile\w*|DeleteFile\w*|MoveFile\w*|CopyFile\w*|FindFirstFile\w*|FindNextFile\w*|GetTempPath\w*)\b/i);
|
|
1911
|
+
const registryApiHints = selectMatchingHints(combinedHints, /\b(Reg(Open|Create|Set|Query|Delete)Key\w*|RegSetValue\w*|RegQueryValue\w*)\b/i);
|
|
1912
|
+
const remoteProcessHints = selectMatchingHints(combinedHints, /\b(OpenProcess|ReadProcessMemory|WriteProcessMemory|SetThreadContext|ResumeThread|CreateProcess\w*|CreateRemoteThread|VirtualAllocEx|NtQueryInformationProcess|process_injection|process_spawn|cmd\.exe)\b/i);
|
|
1913
|
+
const codeIntegrityHints = selectMatchingHints(combinedHints, /\b(NtQuerySystemInformation|Kernel_Code_Integrity_Status_Raw|CODEINTEGRITY|test signing|code integrity)\b/i);
|
|
1914
|
+
const packerSignatureHints = selectMatchingHints(combinedHints, /\b(UPX|VMProtect|Themida|ASPack|Packer|Protector|goblin|iced-x86)\b/i);
|
|
1915
|
+
const packerEntrypointHints = selectMatchingHints(combinedHints, /\b(Entry point|section|entropy|overlay)\b/i);
|
|
1916
|
+
const cliModel = collectModuleCliModel(module);
|
|
1917
|
+
const lines = [];
|
|
1918
|
+
if (needsResolvedApiTable || needsProcessProbe || needsPackerHeuristics || cliModel) {
|
|
1919
|
+
lines.push('/* Recovered module hints used to keep this rewrite self-contained and readable. */');
|
|
1920
|
+
if (needsResolvedApiTable || needsProcessProbe) {
|
|
1921
|
+
lines.push(...buildCStringTable('AK_DYNAMIC_API_HINTS', dynamicApiHints));
|
|
1922
|
+
lines.push(...buildCStringTable('AK_FILE_API_HINTS', fileApiHints));
|
|
1923
|
+
lines.push(...buildCStringTable('AK_REGISTRY_API_HINTS', registryApiHints));
|
|
1924
|
+
lines.push(...buildCStringTable('AK_REMOTE_PROCESS_HINTS', remoteProcessHints));
|
|
1925
|
+
lines.push(...buildCStringTable('AK_CODE_INTEGRITY_HINTS', codeIntegrityHints));
|
|
1926
|
+
}
|
|
1927
|
+
if (needsPackerHeuristics) {
|
|
1928
|
+
lines.push(...buildCStringTable('AK_PACKER_SIGNATURE_HINTS', packerSignatureHints));
|
|
1929
|
+
lines.push(...buildCStringTable('AK_PACKER_ENTRY_HINTS', packerEntrypointHints));
|
|
1930
|
+
}
|
|
1931
|
+
if (cliModel) {
|
|
1932
|
+
lines.push(`static const char *AK_TOOL_NAME = "${escapeCString(cliModel.toolName)}";`);
|
|
1933
|
+
lines.push(`static const char *AK_HELP_BANNER = "${escapeCString(cliModel.helpBanner)}";`);
|
|
1934
|
+
lines.push('');
|
|
1935
|
+
for (const [index, command] of cliModel.commands.entries()) {
|
|
1936
|
+
lines.push(`static const AkCommandSpec AK_COMMAND_${index} = { "${escapeCString(command.verb)}", "${escapeCString(command.summary)}" };`);
|
|
1937
|
+
}
|
|
1938
|
+
lines.push(`static const int AK_COMMAND_COUNT = ${cliModel.commands.length};`);
|
|
1939
|
+
lines.push('');
|
|
1940
|
+
}
|
|
1941
|
+
lines.push('static int ak_copy_hint_table(const char *const *source, int source_count, const char **destination, int destination_capacity)');
|
|
1942
|
+
lines.push('{');
|
|
1943
|
+
lines.push(' int copied = 0;');
|
|
1944
|
+
lines.push(' int index = 0;');
|
|
1945
|
+
lines.push('');
|
|
1946
|
+
lines.push(' if (destination == 0 || destination_capacity <= 0) {');
|
|
1947
|
+
lines.push(' return 0;');
|
|
1948
|
+
lines.push(' }');
|
|
1949
|
+
lines.push('');
|
|
1950
|
+
lines.push(' for (index = 0; index < source_count && copied < destination_capacity; ++index) {');
|
|
1951
|
+
lines.push(" if (source[index] == 0 || source[index][0] == '\\0') {");
|
|
1952
|
+
lines.push(' continue;');
|
|
1953
|
+
lines.push(' }');
|
|
1954
|
+
lines.push(' destination[copied++] = source[index];');
|
|
1955
|
+
lines.push(' }');
|
|
1956
|
+
lines.push('');
|
|
1957
|
+
lines.push(' return copied;');
|
|
1958
|
+
lines.push('}');
|
|
1959
|
+
lines.push('');
|
|
1960
|
+
lines.push('static const char *ak_first_hint(const char *const *source, int source_count, const char *fallback_hint)');
|
|
1961
|
+
lines.push('{');
|
|
1962
|
+
lines.push(' int index = 0;');
|
|
1963
|
+
lines.push('');
|
|
1964
|
+
lines.push(' for (index = 0; index < source_count; ++index) {');
|
|
1965
|
+
lines.push(" if (source[index] != 0 && source[index][0] != '\\0') {");
|
|
1966
|
+
lines.push(' return source[index];');
|
|
1967
|
+
lines.push(' }');
|
|
1968
|
+
lines.push(' }');
|
|
1969
|
+
lines.push('');
|
|
1970
|
+
lines.push(' return fallback_hint;');
|
|
1971
|
+
lines.push('}');
|
|
1972
|
+
lines.push('');
|
|
1973
|
+
}
|
|
1974
|
+
if (cliModel) {
|
|
1975
|
+
lines.push('static int ak_prepare_cli_model(AkCliModel *model)');
|
|
1976
|
+
lines.push('{');
|
|
1977
|
+
lines.push(' if (model == 0) {');
|
|
1978
|
+
lines.push(' return 0;');
|
|
1979
|
+
lines.push(' }');
|
|
1980
|
+
lines.push('');
|
|
1981
|
+
lines.push(' model->tool_name = AK_TOOL_NAME;');
|
|
1982
|
+
lines.push(' model->help_banner = AK_HELP_BANNER;');
|
|
1983
|
+
lines.push(' model->command_count = 0;');
|
|
1984
|
+
for (const [index] of cliModel.commands.entries()) {
|
|
1985
|
+
lines.push(` if (model->command_count < 8) {`);
|
|
1986
|
+
lines.push(` model->commands[model->command_count++] = AK_COMMAND_${index};`);
|
|
1987
|
+
lines.push(' }');
|
|
1988
|
+
}
|
|
1989
|
+
lines.push('');
|
|
1990
|
+
lines.push(' return 1;');
|
|
1991
|
+
lines.push('}');
|
|
1992
|
+
lines.push('');
|
|
1993
|
+
}
|
|
1994
|
+
if (needsResolvedApiTable) {
|
|
1995
|
+
lines.push('static int resolve_dynamic_api_table(AkResolvedApiTable *table)');
|
|
1996
|
+
lines.push('{');
|
|
1997
|
+
lines.push(' if (table == 0) {');
|
|
1998
|
+
lines.push(' return 0;');
|
|
1999
|
+
lines.push(' }');
|
|
2000
|
+
lines.push('');
|
|
2001
|
+
lines.push(' table->ready = 1;');
|
|
2002
|
+
lines.push(' table->role = "dynamic_loader";');
|
|
2003
|
+
lines.push(' table->api_count = ak_copy_hint_table(AK_DYNAMIC_API_HINTS, AK_DYNAMIC_API_HINTS_COUNT, table->apis, 8);');
|
|
2004
|
+
lines.push(' if (table->api_count == 0) {');
|
|
2005
|
+
lines.push(' table->apis[0] = "GetProcAddress";');
|
|
2006
|
+
lines.push(' table->api_count = 1;');
|
|
2007
|
+
lines.push(' }');
|
|
2008
|
+
lines.push('');
|
|
2009
|
+
lines.push(' return 1;');
|
|
2010
|
+
lines.push('}');
|
|
2011
|
+
lines.push('');
|
|
2012
|
+
lines.push('static int resolve_file_api_table(AkResolvedApiTable *table)');
|
|
2013
|
+
lines.push('{');
|
|
2014
|
+
lines.push(' if (table == 0) {');
|
|
2015
|
+
lines.push(' return 0;');
|
|
2016
|
+
lines.push(' }');
|
|
2017
|
+
lines.push('');
|
|
2018
|
+
lines.push(' table->ready = 1;');
|
|
2019
|
+
lines.push(' table->role = "file_capabilities";');
|
|
2020
|
+
lines.push(' table->api_count = ak_copy_hint_table(AK_FILE_API_HINTS, AK_FILE_API_HINTS_COUNT, table->apis, 8);');
|
|
2021
|
+
lines.push(' if (table->api_count == 0) {');
|
|
2022
|
+
lines.push(' table->apis[0] = "CreateFileW";');
|
|
2023
|
+
lines.push(' table->api_count = 1;');
|
|
2024
|
+
lines.push(' }');
|
|
2025
|
+
lines.push('');
|
|
2026
|
+
lines.push(' return 1;');
|
|
2027
|
+
lines.push('}');
|
|
2028
|
+
lines.push('');
|
|
2029
|
+
lines.push('static int resolve_registry_api_table(AkResolvedApiTable *table)');
|
|
2030
|
+
lines.push('{');
|
|
2031
|
+
lines.push(' if (table == 0) {');
|
|
2032
|
+
lines.push(' return 0;');
|
|
2033
|
+
lines.push(' }');
|
|
2034
|
+
lines.push('');
|
|
2035
|
+
lines.push(' table->ready = 1;');
|
|
2036
|
+
lines.push(' table->role = "registry_capabilities";');
|
|
2037
|
+
lines.push(' table->api_count = ak_copy_hint_table(AK_REGISTRY_API_HINTS, AK_REGISTRY_API_HINTS_COUNT, table->apis, 8);');
|
|
2038
|
+
lines.push(' if (table->api_count == 0) {');
|
|
2039
|
+
lines.push(' table->apis[0] = "RegOpenKeyExW";');
|
|
2040
|
+
lines.push(' table->api_count = 1;');
|
|
2041
|
+
lines.push(' }');
|
|
2042
|
+
lines.push('');
|
|
2043
|
+
lines.push(' return 1;');
|
|
2044
|
+
lines.push('}');
|
|
2045
|
+
lines.push('');
|
|
2046
|
+
lines.push('static int ak_prepare_runtime_capabilities(AkRuntimeContext *runtime_ctx, int needs_dynamic_loader, int needs_file_capabilities, int needs_registry_capabilities)');
|
|
2047
|
+
lines.push('{');
|
|
2048
|
+
lines.push(' if (runtime_ctx == 0) {');
|
|
2049
|
+
lines.push(' return 0;');
|
|
2050
|
+
lines.push(' }');
|
|
2051
|
+
lines.push(' if (needs_dynamic_loader && !resolve_dynamic_api_table(&runtime_ctx->dynamic_apis)) {');
|
|
2052
|
+
lines.push(' return 0;');
|
|
2053
|
+
lines.push(' }');
|
|
2054
|
+
lines.push(' if (needs_file_capabilities && !resolve_file_api_table(&runtime_ctx->file_apis)) {');
|
|
2055
|
+
lines.push(' return 0;');
|
|
2056
|
+
lines.push(' }');
|
|
2057
|
+
lines.push(' if (needs_registry_capabilities && !resolve_registry_api_table(&runtime_ctx->registry_apis)) {');
|
|
2058
|
+
lines.push(' return 0;');
|
|
2059
|
+
lines.push(' }');
|
|
2060
|
+
lines.push(' return 1;');
|
|
2061
|
+
lines.push('}');
|
|
2062
|
+
lines.push('');
|
|
2063
|
+
lines.push('static void ak_finalize_capability_plan(AkRuntimeContext *runtime_ctx, AkSemanticOutputs *outputs, AkCapabilityDispatchPlan *plan, int recovered_status, const char *stage_name)');
|
|
2064
|
+
lines.push('{');
|
|
2065
|
+
lines.push(' if (runtime_ctx == 0 || plan == 0) {');
|
|
2066
|
+
lines.push(' return;');
|
|
2067
|
+
lines.push(' }');
|
|
2068
|
+
lines.push(' runtime_ctx->last_status = recovered_status;');
|
|
2069
|
+
lines.push(' plan->result.status_code = recovered_status;');
|
|
2070
|
+
lines.push(' plan->result.stage_name = stage_name;');
|
|
2071
|
+
lines.push(' plan->result.detail = runtime_ctx->last_status_detail;');
|
|
2072
|
+
lines.push(' plan->result.observed_value = (uint64_t)recovered_status;');
|
|
2073
|
+
lines.push(' ak_publish_capability_result(outputs, &plan->result);');
|
|
2074
|
+
lines.push('}');
|
|
2075
|
+
lines.push('');
|
|
2076
|
+
lines.push('static const char *ak_select_capability_observation(const AkResolvedApiTable *file_apis, const AkResolvedApiTable *registry_apis)');
|
|
2077
|
+
lines.push('{');
|
|
2078
|
+
lines.push(' if (registry_apis != 0 && registry_apis->ready && registry_apis->api_count > 0) {');
|
|
2079
|
+
lines.push(' return registry_apis->apis[0];');
|
|
2080
|
+
lines.push(' }');
|
|
2081
|
+
lines.push(' if (file_apis != 0 && file_apis->ready && file_apis->api_count > 0) {');
|
|
2082
|
+
lines.push(' return file_apis->apis[0];');
|
|
2083
|
+
lines.push(' }');
|
|
2084
|
+
lines.push(' return 0;');
|
|
2085
|
+
lines.push('}');
|
|
2086
|
+
lines.push('');
|
|
2087
|
+
lines.push('static int finalize_capability_dispatch(const AkResolvedApiTable *file_apis, const AkResolvedApiTable *registry_apis)');
|
|
2088
|
+
lines.push('{');
|
|
2089
|
+
lines.push(' return ak_select_capability_observation(file_apis, registry_apis) != 0 ? AK_STATUS_OK : AK_STATUS_UNSUPPORTED;');
|
|
2090
|
+
lines.push('}');
|
|
2091
|
+
lines.push('');
|
|
2092
|
+
}
|
|
2093
|
+
if (needsProcessProbe) {
|
|
2094
|
+
lines.push('static int query_remote_process_snapshot(AkProcessProbeResult *probe)');
|
|
2095
|
+
lines.push('{');
|
|
2096
|
+
lines.push(' if (probe == 0) {');
|
|
2097
|
+
lines.push(' return 0;');
|
|
2098
|
+
lines.push(' }');
|
|
2099
|
+
lines.push('');
|
|
2100
|
+
lines.push(' probe->remote_process_checked = 1;');
|
|
2101
|
+
lines.push(' probe->status = AK_STATUS_OK;');
|
|
2102
|
+
lines.push(' probe->last_observation = ak_first_hint(AK_REMOTE_PROCESS_HINTS, AK_REMOTE_PROCESS_HINTS_COUNT, "remote process capability observed");');
|
|
2103
|
+
lines.push(' return 1;');
|
|
2104
|
+
lines.push('}');
|
|
2105
|
+
lines.push('');
|
|
2106
|
+
lines.push('static int query_code_integrity_state(AkProcessProbeResult *probe)');
|
|
2107
|
+
lines.push('{');
|
|
2108
|
+
lines.push(' if (probe == 0) {');
|
|
2109
|
+
lines.push(' return 0;');
|
|
2110
|
+
lines.push(' }');
|
|
2111
|
+
lines.push('');
|
|
2112
|
+
lines.push(' probe->code_integrity_checked = 1;');
|
|
2113
|
+
lines.push(' probe->status = AK_STATUS_OK;');
|
|
2114
|
+
lines.push(' probe->last_observation = ak_first_hint(AK_CODE_INTEGRITY_HINTS, AK_CODE_INTEGRITY_HINTS_COUNT, "code integrity state queried");');
|
|
2115
|
+
lines.push(' return 1;');
|
|
2116
|
+
lines.push('}');
|
|
2117
|
+
lines.push('');
|
|
2118
|
+
lines.push('static int dispatch_process_operation(AkProcessProbeResult *probe, const AkResolvedApiTable *file_apis, const AkResolvedApiTable *registry_apis)');
|
|
2119
|
+
lines.push('{');
|
|
2120
|
+
lines.push(' const char *observation = 0;');
|
|
2121
|
+
lines.push('');
|
|
2122
|
+
lines.push(' if (probe == 0) {');
|
|
2123
|
+
lines.push(' return AK_STATUS_QUERY_FAILED;');
|
|
2124
|
+
lines.push(' }');
|
|
2125
|
+
lines.push(' if (file_apis != 0 && file_apis->ready && file_apis->api_count > 0) {');
|
|
2126
|
+
lines.push(' observation = file_apis->apis[0];');
|
|
2127
|
+
lines.push(' }');
|
|
2128
|
+
lines.push(' if (observation == 0 && registry_apis != 0 && registry_apis->ready && registry_apis->api_count > 0) {');
|
|
2129
|
+
lines.push(' observation = registry_apis->apis[0];');
|
|
2130
|
+
lines.push(' }');
|
|
2131
|
+
lines.push(' if (observation == 0) {');
|
|
2132
|
+
lines.push(' observation = ak_first_hint(AK_REMOTE_PROCESS_HINTS, AK_REMOTE_PROCESS_HINTS_COUNT, "process capability dispatch");');
|
|
2133
|
+
lines.push(' }');
|
|
2134
|
+
lines.push('');
|
|
2135
|
+
lines.push(' probe->status = AK_STATUS_OK;');
|
|
2136
|
+
lines.push(' probe->last_observation = observation;');
|
|
2137
|
+
lines.push(' return probe->status;');
|
|
2138
|
+
lines.push('}');
|
|
2139
|
+
lines.push('');
|
|
2140
|
+
lines.push('static int finalize_process_probe(const AkProcessProbeResult *probe)');
|
|
2141
|
+
lines.push('{');
|
|
2142
|
+
lines.push(' if (probe == 0) {');
|
|
2143
|
+
lines.push(' return AK_STATUS_QUERY_FAILED;');
|
|
2144
|
+
lines.push(' }');
|
|
2145
|
+
lines.push(' if (probe->status != 0) {');
|
|
2146
|
+
lines.push(' return probe->status;');
|
|
2147
|
+
lines.push(' }');
|
|
2148
|
+
lines.push(' if (probe->remote_process_checked || probe->code_integrity_checked) {');
|
|
2149
|
+
lines.push(' return AK_STATUS_OK;');
|
|
2150
|
+
lines.push(' }');
|
|
2151
|
+
lines.push(' return AK_STATUS_UNSUPPORTED;');
|
|
2152
|
+
lines.push('}');
|
|
2153
|
+
lines.push('');
|
|
2154
|
+
lines.push('static int ak_collect_process_context(AkRuntimeContext *runtime_ctx, int needs_remote_probe, int needs_code_integrity)');
|
|
2155
|
+
lines.push('{');
|
|
2156
|
+
lines.push(' if (runtime_ctx == 0) {');
|
|
2157
|
+
lines.push(' return 0;');
|
|
2158
|
+
lines.push(' }');
|
|
2159
|
+
lines.push(' if (needs_remote_probe && !query_remote_process_snapshot(&runtime_ctx->process_probe)) {');
|
|
2160
|
+
lines.push(' return 0;');
|
|
2161
|
+
lines.push(' }');
|
|
2162
|
+
lines.push(' if (needs_code_integrity && !query_code_integrity_state(&runtime_ctx->process_probe)) {');
|
|
2163
|
+
lines.push(' return 0;');
|
|
2164
|
+
lines.push(' }');
|
|
2165
|
+
lines.push(' return 1;');
|
|
2166
|
+
lines.push('}');
|
|
2167
|
+
lines.push('');
|
|
2168
|
+
lines.push('static void ak_finalize_process_session(AkRuntimeContext *runtime_ctx, AkSemanticOutputs *outputs, AkProcessOperationSession *session, int recovered_status, const char *stage_name, const char *transfer_mode)');
|
|
2169
|
+
lines.push('{');
|
|
2170
|
+
lines.push(' if (runtime_ctx == 0 || session == 0) {');
|
|
2171
|
+
lines.push(' return;');
|
|
2172
|
+
lines.push(' }');
|
|
2173
|
+
lines.push(' runtime_ctx->last_status = recovered_status;');
|
|
2174
|
+
lines.push(' if (runtime_ctx->process_probe.last_observation != 0) {');
|
|
2175
|
+
lines.push(' runtime_ctx->last_status_detail = runtime_ctx->process_probe.last_observation;');
|
|
2176
|
+
lines.push(' }');
|
|
2177
|
+
lines.push(' if (runtime_ctx->last_status_detail == 0 && transfer_mode != 0) {');
|
|
2178
|
+
lines.push(' runtime_ctx->last_status_detail = transfer_mode;');
|
|
2179
|
+
lines.push(' }');
|
|
2180
|
+
lines.push(' session->transfer_result.status_code = recovered_status;');
|
|
2181
|
+
lines.push(' session->transfer_result.stage_name = stage_name;');
|
|
2182
|
+
lines.push(' session->transfer_result.detail = runtime_ctx->last_status_detail;');
|
|
2183
|
+
lines.push(' session->transfer_result.transfer_mode = transfer_mode;');
|
|
2184
|
+
lines.push(' session->transfer_result.observed_value = (uint64_t)recovered_status;');
|
|
2185
|
+
lines.push(' ak_publish_process_result(outputs, &session->transfer_result);');
|
|
2186
|
+
lines.push('}');
|
|
2187
|
+
lines.push('');
|
|
2188
|
+
}
|
|
2189
|
+
if (needsPackerHeuristics) {
|
|
2190
|
+
lines.push('static int scan_packer_signatures(AkPackerHeuristics *heuristics)');
|
|
2191
|
+
lines.push('{');
|
|
2192
|
+
lines.push(' if (heuristics == 0) {');
|
|
2193
|
+
lines.push(' return 0;');
|
|
2194
|
+
lines.push(' }');
|
|
2195
|
+
lines.push('');
|
|
2196
|
+
lines.push(' heuristics->matched_count = ak_copy_hint_table(AK_PACKER_SIGNATURE_HINTS, AK_PACKER_SIGNATURE_HINTS_COUNT, heuristics->matched_signatures, 8);');
|
|
2197
|
+
lines.push(' heuristics->entrypoint_signal = ak_first_hint(AK_PACKER_ENTRY_HINTS, AK_PACKER_ENTRY_HINTS_COUNT, "packer-like section layout");');
|
|
2198
|
+
lines.push(' if (heuristics->matched_count == 0) {');
|
|
2199
|
+
lines.push(' heuristics->matched_signatures[0] = "generic_packer_signature";');
|
|
2200
|
+
lines.push(' heuristics->matched_count = 1;');
|
|
2201
|
+
lines.push(' }');
|
|
2202
|
+
lines.push(' heuristics->score = heuristics->matched_count * 10;');
|
|
2203
|
+
lines.push(' return 1;');
|
|
2204
|
+
lines.push('}');
|
|
2205
|
+
lines.push('');
|
|
2206
|
+
lines.push('static int finalize_packer_assessment(const AkPackerHeuristics *heuristics)');
|
|
2207
|
+
lines.push('{');
|
|
2208
|
+
lines.push(' if (heuristics == 0) {');
|
|
2209
|
+
lines.push(' return AK_STATUS_UNSUPPORTED;');
|
|
2210
|
+
lines.push(' }');
|
|
2211
|
+
lines.push(' return heuristics->score > 0 ? AK_STATUS_OK : AK_STATUS_UNSUPPORTED;');
|
|
2212
|
+
lines.push('}');
|
|
2213
|
+
lines.push('');
|
|
2214
|
+
lines.push('static void ak_finalize_packer_session(AkRuntimeContext *runtime_ctx, AkSemanticOutputs *outputs, AkPackerScanSession *session, int recovered_status)');
|
|
2215
|
+
lines.push('{');
|
|
2216
|
+
lines.push(' if (runtime_ctx == 0 || session == 0) {');
|
|
2217
|
+
lines.push(' return;');
|
|
2218
|
+
lines.push(' }');
|
|
2219
|
+
lines.push(' runtime_ctx->last_status = recovered_status;');
|
|
2220
|
+
lines.push(' runtime_ctx->last_status_detail = runtime_ctx->packer_heuristics.entrypoint_signal;');
|
|
2221
|
+
lines.push(' session->result.status_code = recovered_status;');
|
|
2222
|
+
lines.push(' session->result.stage_name = AK_STAGE_SCAN_PE_LAYOUT;');
|
|
2223
|
+
lines.push(' session->result.detail = runtime_ctx->packer_heuristics.entrypoint_signal;');
|
|
2224
|
+
lines.push(' session->result.heuristic_score = (uint64_t)runtime_ctx->packer_heuristics.score;');
|
|
2225
|
+
lines.push(' ak_publish_packer_result(outputs, &session->result);');
|
|
2226
|
+
lines.push('}');
|
|
2227
|
+
lines.push('');
|
|
2228
|
+
}
|
|
2229
|
+
return lines;
|
|
2230
|
+
}
|
|
2231
|
+
function buildSemanticRewriteBody(func, module) {
|
|
2232
|
+
const features = collectRewriteFeatures(func, module);
|
|
2233
|
+
const cliModel = collectModuleCliModel(module);
|
|
2234
|
+
const usesProcessRequestView = features.hasProcessInjection ||
|
|
2235
|
+
features.hasProcessSpawn ||
|
|
2236
|
+
features.hasNtQueryInformationProcess ||
|
|
2237
|
+
features.hasNtQuerySystemInformation ||
|
|
2238
|
+
features.hasCodeIntegrity;
|
|
2239
|
+
const lines = [];
|
|
2240
|
+
lines.push(` /* semantic_alias: ${buildSemanticAlias(func, module)} */`);
|
|
2241
|
+
lines.push(` /* semantic_parameters: ${buildRecoveredContractHints(func, module).join(' | ')} */`);
|
|
2242
|
+
lines.push(' if (runtime_ctx == 0) {');
|
|
2243
|
+
lines.push(' return AK_STATUS_QUERY_FAILED;');
|
|
2244
|
+
lines.push(' }');
|
|
2245
|
+
lines.push(' runtime_ctx->last_status = AK_STATUS_UNSUPPORTED;');
|
|
2246
|
+
lines.push(' runtime_ctx->last_status_detail = 0;');
|
|
2247
|
+
lines.push(' if (outputs != 0) {');
|
|
2248
|
+
lines.push(' outputs->status_code = AK_STATUS_UNSUPPORTED;');
|
|
2249
|
+
lines.push(' outputs->scalar_result = 0;');
|
|
2250
|
+
lines.push(' outputs->status_detail = 0;');
|
|
2251
|
+
lines.push(' outputs->observed_stage = 0;');
|
|
2252
|
+
lines.push(' }');
|
|
2253
|
+
lines.push('');
|
|
2254
|
+
if (cliModel) {
|
|
2255
|
+
lines.push(' if (!ak_prepare_cli_model(&runtime_ctx->cli)) {');
|
|
2256
|
+
lines.push(' return AK_STATUS_QUERY_FAILED;');
|
|
2257
|
+
lines.push(' }');
|
|
2258
|
+
lines.push(' runtime_ctx->last_status_detail = runtime_ctx->cli.help_banner;');
|
|
2259
|
+
lines.push(' if (outputs != 0 && outputs->observed_stage == 0) {');
|
|
2260
|
+
lines.push(' outputs->observed_stage = AK_STAGE_COMMAND_MODEL_READY;');
|
|
2261
|
+
lines.push(' }');
|
|
2262
|
+
lines.push('');
|
|
2263
|
+
}
|
|
2264
|
+
if (features.hasProcessInjection ||
|
|
2265
|
+
features.hasProcessSpawn ||
|
|
2266
|
+
features.hasFileApiTable ||
|
|
2267
|
+
features.hasRegistryApiTable ||
|
|
2268
|
+
features.hasNtQueryInformationProcess ||
|
|
2269
|
+
features.hasNtQuerySystemInformation ||
|
|
2270
|
+
features.hasCodeIntegrity) {
|
|
2271
|
+
if (usesProcessRequestView) {
|
|
2272
|
+
lines.push(' AkProcessOperationSession process_session = ak_start_process_session(inputs);');
|
|
2273
|
+
lines.push('');
|
|
2274
|
+
lines.push(' if (process_session.remote_request.target_selector != 0) {');
|
|
2275
|
+
lines.push(' runtime_ctx->last_status_detail = process_session.remote_request.target_selector;');
|
|
2276
|
+
lines.push(' }');
|
|
2277
|
+
lines.push(' if (process_session.remote_request.launch_command_line != 0 && runtime_ctx->last_status_detail == 0) {');
|
|
2278
|
+
lines.push(' runtime_ctx->last_status_detail = process_session.remote_request.launch_command_line;');
|
|
2279
|
+
lines.push(' }');
|
|
2280
|
+
lines.push(' if (process_session.remote_request.payload_view != 0 && runtime_ctx->last_status_detail == 0) {');
|
|
2281
|
+
lines.push(' runtime_ctx->last_status_detail = "payload_view_available";');
|
|
2282
|
+
lines.push(' }');
|
|
2283
|
+
}
|
|
2284
|
+
else {
|
|
2285
|
+
lines.push(' AkCapabilityDispatchPlan capability_plan = ak_start_capability_plan(inputs);');
|
|
2286
|
+
lines.push('');
|
|
2287
|
+
lines.push(' if (capability_plan.request.primary_hint != 0) {');
|
|
2288
|
+
lines.push(' runtime_ctx->last_status_detail = capability_plan.request.primary_hint;');
|
|
2289
|
+
lines.push(' }');
|
|
2290
|
+
}
|
|
2291
|
+
lines.push('');
|
|
2292
|
+
if (features.hasDynamicResolver) {
|
|
2293
|
+
lines.push(' /* Resolve loader pointers before the capability dispatch touches higher-risk APIs. */');
|
|
2294
|
+
lines.push('');
|
|
2295
|
+
}
|
|
2296
|
+
if (features.hasDynamicResolver || features.hasFileApiTable || features.hasRegistryApiTable) {
|
|
2297
|
+
lines.push(` if (!ak_prepare_runtime_capabilities(runtime_ctx, ${features.hasDynamicResolver ? 1 : 0}, ${features.hasFileApiTable ? 1 : 0}, ${features.hasRegistryApiTable ? 1 : 0})) {`);
|
|
2298
|
+
lines.push(' return AK_STATUS_RESOLVE_FAILED;');
|
|
2299
|
+
lines.push(' }');
|
|
2300
|
+
lines.push('');
|
|
2301
|
+
}
|
|
2302
|
+
if (features.hasNtQueryInformationProcess ||
|
|
2303
|
+
features.hasNtQuerySystemInformation ||
|
|
2304
|
+
features.hasCodeIntegrity) {
|
|
2305
|
+
lines.push(` if (!ak_collect_process_context(runtime_ctx, ${features.hasNtQueryInformationProcess ? 1 : 0}, ${features.hasNtQuerySystemInformation || features.hasCodeIntegrity ? 1 : 0})) {`);
|
|
2306
|
+
lines.push(' return AK_STATUS_QUERY_FAILED;');
|
|
2307
|
+
lines.push(' }');
|
|
2308
|
+
lines.push('');
|
|
2309
|
+
}
|
|
2310
|
+
if (features.hasProcessInjection || features.hasProcessSpawn) {
|
|
2311
|
+
lines.push(' recovered_status = dispatch_process_operation(');
|
|
2312
|
+
lines.push(' &runtime_ctx->process_probe,');
|
|
2313
|
+
lines.push(` ${features.hasDynamicResolver || features.hasFileApiTable ? '&runtime_ctx->file_apis' : '0'},`);
|
|
2314
|
+
lines.push(` ${features.hasRegistryApiTable ? '&runtime_ctx->registry_apis' : '0'}`);
|
|
2315
|
+
lines.push(' );');
|
|
2316
|
+
}
|
|
2317
|
+
else if (usesProcessRequestView) {
|
|
2318
|
+
lines.push(' recovered_status = finalize_process_probe(&runtime_ctx->process_probe);');
|
|
2319
|
+
}
|
|
2320
|
+
else {
|
|
2321
|
+
lines.push(` recovered_status = finalize_capability_dispatch(${features.hasFileApiTable ? '&runtime_ctx->file_apis' : '0'}, ${features.hasRegistryApiTable ? '&runtime_ctx->registry_apis' : '0'});`);
|
|
2322
|
+
lines.push(` const char *capability_observation = ak_select_capability_observation(${features.hasFileApiTable ? '&runtime_ctx->file_apis' : '0'}, ${features.hasRegistryApiTable ? '&runtime_ctx->registry_apis' : '0'});`);
|
|
2323
|
+
lines.push(' if (capability_observation != 0) {');
|
|
2324
|
+
lines.push(' runtime_ctx->last_status_detail = capability_observation;');
|
|
2325
|
+
lines.push(' }');
|
|
2326
|
+
}
|
|
2327
|
+
if (usesProcessRequestView) {
|
|
2328
|
+
if (features.hasProcessInjection || features.hasProcessSpawn) {
|
|
2329
|
+
lines.push(` ak_finalize_process_session(runtime_ctx, outputs, &process_session, recovered_status, AK_STAGE_PREPARE_REMOTE_PROCESS_ACCESS, "${features.hasProcessInjection ? 'remote_memory_transfer' : 'process_spawn_transfer'}");`);
|
|
2330
|
+
}
|
|
2331
|
+
else {
|
|
2332
|
+
lines.push(' ak_finalize_process_session(runtime_ctx, outputs, &process_session, recovered_status, AK_STAGE_ANTI_ANALYSIS_CHECKS, "process_probe");');
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
else {
|
|
2336
|
+
lines.push(' if (runtime_ctx->process_probe.last_observation != 0) {');
|
|
2337
|
+
lines.push(' runtime_ctx->last_status_detail = runtime_ctx->process_probe.last_observation;');
|
|
2338
|
+
lines.push(' }');
|
|
2339
|
+
if (features.hasRegistryApiTable) {
|
|
2340
|
+
lines.push(' ak_finalize_capability_plan(runtime_ctx, outputs, &capability_plan, recovered_status, AK_STAGE_REGISTRY_OPERATIONS);');
|
|
2341
|
+
}
|
|
2342
|
+
else {
|
|
2343
|
+
lines.push(' ak_finalize_capability_plan(runtime_ctx, outputs, &capability_plan, recovered_status, AK_STAGE_FILE_OPERATIONS);');
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
return lines;
|
|
2347
|
+
}
|
|
2348
|
+
if (features.hasPackerScan) {
|
|
2349
|
+
lines.push(' AkPackerScanSession packer_session = ak_start_packer_session(inputs);');
|
|
2350
|
+
lines.push('');
|
|
2351
|
+
lines.push(' if (packer_session.request.command_hint != 0) {');
|
|
2352
|
+
lines.push(' runtime_ctx->last_status_detail = packer_session.request.command_hint;');
|
|
2353
|
+
lines.push(' }');
|
|
2354
|
+
lines.push('');
|
|
2355
|
+
lines.push(' if (!scan_packer_signatures(&runtime_ctx->packer_heuristics)) {');
|
|
2356
|
+
lines.push(' return AK_STATUS_UNSUPPORTED;');
|
|
2357
|
+
lines.push(' }');
|
|
2358
|
+
lines.push('');
|
|
2359
|
+
lines.push(' recovered_status = finalize_packer_assessment(&runtime_ctx->packer_heuristics);');
|
|
2360
|
+
lines.push(' ak_finalize_packer_session(runtime_ctx, outputs, &packer_session, recovered_status);');
|
|
2361
|
+
return lines;
|
|
2362
|
+
}
|
|
2363
|
+
lines.push(' recovered_status = AK_STATUS_UNSUPPORTED;');
|
|
2364
|
+
lines.push(' runtime_ctx->last_status = recovered_status;');
|
|
2365
|
+
lines.push(' if (outputs != 0) {');
|
|
2366
|
+
lines.push(' outputs->status_code = recovered_status;');
|
|
2367
|
+
lines.push(' outputs->scalar_result = 0;');
|
|
2368
|
+
lines.push(' outputs->status_detail = runtime_ctx->last_status_detail;');
|
|
2369
|
+
lines.push(' }');
|
|
2370
|
+
return lines;
|
|
2371
|
+
}
|
|
2372
|
+
function buildRewriteSteps(func) {
|
|
2373
|
+
const tags = new Set((func.behavior_tags || []).map((item) => item.toLowerCase()));
|
|
2374
|
+
const relationshipTypes = new Set([
|
|
2375
|
+
...(func.call_relationships?.callers || []).flatMap((item) => item.relation_types || []),
|
|
2376
|
+
...(func.call_relationships?.callees || []).flatMap((item) => item.relation_types || []),
|
|
2377
|
+
].map((item) => item.toLowerCase()));
|
|
2378
|
+
let steps;
|
|
2379
|
+
if (tags.has('process_injection')) {
|
|
2380
|
+
steps = [
|
|
2381
|
+
'Locate or receive the remote process / thread objects required for execution takeover.',
|
|
2382
|
+
'Prepare the remote payload buffer or thread context update before writing to the target.',
|
|
2383
|
+
'Transfer bytes or register state into the remote process and validate the transition status.',
|
|
2384
|
+
];
|
|
2385
|
+
}
|
|
2386
|
+
else if (tags.has('process_spawn')) {
|
|
2387
|
+
steps = [
|
|
2388
|
+
'Construct the child-process launch context and execution parameters.',
|
|
2389
|
+
'Spawn or resume the target process while preserving handles or inherited state.',
|
|
2390
|
+
'Validate process creation results and bubble failure information to the caller.',
|
|
2391
|
+
];
|
|
2392
|
+
}
|
|
2393
|
+
else if (tags.has('networking')) {
|
|
2394
|
+
steps = [
|
|
2395
|
+
'Initialize the network client or session state used by this routine.',
|
|
2396
|
+
'Exchange request / response data with a remote endpoint or local relay.',
|
|
2397
|
+
'Parse the returned data and hand control back to the surrounding dispatcher.',
|
|
2398
|
+
];
|
|
2399
|
+
}
|
|
2400
|
+
else if (tags.has('file_io')) {
|
|
2401
|
+
steps = [
|
|
2402
|
+
'Resolve the path or file handle needed for this operation.',
|
|
2403
|
+
'Read, write, or enumerate on-disk data while checking for short or failed operations.',
|
|
2404
|
+
'Return status information that influences later persistence or collection logic.',
|
|
2405
|
+
];
|
|
2406
|
+
}
|
|
2407
|
+
else if (tags.has('anti_debug')) {
|
|
2408
|
+
steps = [
|
|
2409
|
+
'Probe the host for analysis or debugging indicators.',
|
|
2410
|
+
'Update an internal decision bitfield or branch guard based on the probe result.',
|
|
2411
|
+
'Short-circuit or harden later execution stages when the environment looks suspicious.',
|
|
2412
|
+
];
|
|
2413
|
+
}
|
|
2414
|
+
else if (tags.has('crypto')) {
|
|
2415
|
+
steps = [
|
|
2416
|
+
'Prepare cryptographic state, keys, or buffers.',
|
|
2417
|
+
'Transform input data through an encode/decode or hash routine.',
|
|
2418
|
+
'Propagate the transformed material to downstream storage or transport logic.',
|
|
2419
|
+
];
|
|
2420
|
+
}
|
|
2421
|
+
else if (tags.has('registry')) {
|
|
2422
|
+
steps = [
|
|
2423
|
+
'Resolve the registry hive / key path relevant to this routine.',
|
|
2424
|
+
'Create, query, or update registry values used as configuration or persistence state.',
|
|
2425
|
+
'Return a success code that influences later bootstrap or cleanup behavior.',
|
|
2426
|
+
];
|
|
2427
|
+
}
|
|
2428
|
+
else if ((func.semantic_summary || '').toLowerCase().includes('entropy')) {
|
|
2429
|
+
steps = [
|
|
2430
|
+
'Inspect PE sections or buffers for compression / packing characteristics.',
|
|
2431
|
+
'Compare observed structure against known thresholds or signatures.',
|
|
2432
|
+
'Return the classification result to a higher-level analysis dispatcher.',
|
|
2433
|
+
];
|
|
2434
|
+
}
|
|
2435
|
+
else {
|
|
2436
|
+
steps = [
|
|
2437
|
+
'Recover local state and identify the primary control inputs for this routine.',
|
|
2438
|
+
'Trace the major side effects that feed other modules or exported entrypoints.',
|
|
2439
|
+
'Confirm final branching and status propagation against decompiler / disassembly evidence.',
|
|
2440
|
+
];
|
|
2441
|
+
}
|
|
2442
|
+
if (relationshipTypes.has('tail_jump_hint') || relationshipTypes.has('body_reference_hint')) {
|
|
2443
|
+
steps.push('Validate recovered thunk or body-reference edges before finalizing names, parameters, and module boundaries.');
|
|
2444
|
+
}
|
|
2445
|
+
return steps;
|
|
2446
|
+
}
|
|
2447
|
+
function buildAnnotatedRewriteContent(module) {
|
|
2448
|
+
const lines = [];
|
|
2449
|
+
const displayStringHints = collectDisplayStringHints(module);
|
|
2450
|
+
lines.push(`/* module: ${module.name} | annotated rewrite */`);
|
|
2451
|
+
lines.push(`#include "${sanitizeModuleName(module.name)}.interface.h"`);
|
|
2452
|
+
lines.push('');
|
|
2453
|
+
lines.push('/*');
|
|
2454
|
+
lines.push(` * Analyst summary:`);
|
|
2455
|
+
lines.push(` * - function_count: ${module.functions.length}`);
|
|
2456
|
+
lines.push(` * - recovered_role: ${describeModuleRole(module)}`);
|
|
2457
|
+
lines.push(` * - import_hints: ${Array.from(module.importHints).slice(0, 8).join(', ') || 'none'}`);
|
|
2458
|
+
lines.push(` * - string_hints: ${displayStringHints.join(' | ') || 'none'}`);
|
|
2459
|
+
lines.push(` * - runtime_apis: ${Array.from(module.runtimeApis).slice(0, 8).join(', ') || 'none'}`);
|
|
2460
|
+
lines.push(` * - runtime_stages: ${Array.from(module.runtimeStages).slice(0, 6).join(', ') || 'none'}`);
|
|
2461
|
+
lines.push(` * - runtime_notes: ${Array.from(module.runtimeNotes).slice(0, 4).join(' | ') || 'none'}`);
|
|
2462
|
+
lines.push(' * - This file is a human-readable rewrite scaffold, not original source.');
|
|
2463
|
+
lines.push(' */');
|
|
2464
|
+
lines.push('');
|
|
2465
|
+
lines.push(...buildModuleRewritePrelude(module));
|
|
2466
|
+
for (const func of module.functions) {
|
|
2467
|
+
const names = deriveRewriteEntryNames(func, module);
|
|
2468
|
+
const validatedSemanticName = getValidatedSemanticName(func);
|
|
2469
|
+
lines.push(`int ${names.implementationName}(AkRuntimeContext *runtime_ctx, const AkSemanticInputs *inputs, AkSemanticOutputs *outputs)`);
|
|
2470
|
+
lines.push('{');
|
|
2471
|
+
lines.push(' int recovered_status = AK_STATUS_UNSUPPORTED;');
|
|
2472
|
+
lines.push(` /* original_symbol: ${func.function} @ ${func.address} */`);
|
|
2473
|
+
if (validatedSemanticName) {
|
|
2474
|
+
lines.push(` /* suggested_name: ${validatedSemanticName} confidence=${Number(func.rename_confidence || 0).toFixed(2)} role=${func.suggested_role || 'unknown'} evidence=${(func.rename_evidence || []).join(', ') || 'none'} */`);
|
|
2475
|
+
}
|
|
2476
|
+
if (func.name_resolution) {
|
|
2477
|
+
lines.push(` /* name_resolution: source=${func.name_resolution.resolution_source || 'unknown'} rule=${func.name_resolution.rule_based_name || 'none'} llm=${func.name_resolution.llm_suggested_name || 'none'} validated=${func.name_resolution.validated_name || 'none'} unresolved=${func.name_resolution.unresolved_semantic_name ? 'yes' : 'no'} */`);
|
|
2478
|
+
if (func.name_resolution.llm_why) {
|
|
2479
|
+
lines.push(` /* llm_why: ${func.name_resolution.llm_why} */`);
|
|
2480
|
+
}
|
|
2481
|
+
if ((func.name_resolution.required_assumptions || []).length > 0) {
|
|
2482
|
+
lines.push(` /* llm_assumptions: ${(func.name_resolution.required_assumptions || []).join(' || ')} */`);
|
|
2483
|
+
}
|
|
2484
|
+
if ((func.name_resolution.evidence_used || []).length > 0) {
|
|
2485
|
+
lines.push(` /* llm_evidence: ${(func.name_resolution.evidence_used || []).join(' || ')} */`);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
if (func.explanation_resolution) {
|
|
2489
|
+
lines.push(` /* explanation: behavior=${func.explanation_resolution.behavior || 'unknown'} confidence=${Number(func.explanation_resolution.confidence || 0).toFixed(2)} source=${func.explanation_resolution.source || 'unknown'} summary=${normalizeExplanationText(func.explanation_resolution.summary) || 'none'} */`);
|
|
2490
|
+
if ((func.explanation_resolution.assumptions || []).length > 0) {
|
|
2491
|
+
lines.push(` /* explanation_assumptions: ${(func.explanation_resolution.assumptions || []).join(' || ')} */`);
|
|
2492
|
+
}
|
|
2493
|
+
if ((func.explanation_resolution.evidence_used || []).length > 0) {
|
|
2494
|
+
lines.push(` /* explanation_evidence: ${(func.explanation_resolution.evidence_used || []).join(' || ')} */`);
|
|
2495
|
+
}
|
|
2496
|
+
if ((func.explanation_resolution.rewrite_guidance || []).length > 0) {
|
|
2497
|
+
lines.push(` /* rewrite_guidance: ${(func.explanation_resolution.rewrite_guidance || []).join(' || ')} */`);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
lines.push(` /* inferred_role: ${func.semantic_summary || 'semantic role still being refined'} */`);
|
|
2501
|
+
lines.push(` /* evidence: confidence=${func.confidence.toFixed(2)} tags=${(func.behavior_tags || []).join(', ') || 'none'} xrefs=${summarizeXrefSignals(func)} */`);
|
|
2502
|
+
lines.push(` /* call_context: callers=${func.call_context?.callers?.slice(0, 4).join(', ') || 'none'} callees=${func.call_context?.callees?.slice(0, 6).join(', ') || 'none'} */`);
|
|
2503
|
+
lines.push(` /* relation_hints: callers=${summarizeRelationshipEntries(func.call_relationships?.callers)} callees=${summarizeRelationshipEntries(func.call_relationships?.callees)} */`);
|
|
2504
|
+
lines.push(` /* parameter_roles: ${summarizeRewriteParameterRoles(func)} */`);
|
|
2505
|
+
lines.push(` /* state_roles: ${summarizeRewriteStateRoles(func)} */`);
|
|
2506
|
+
lines.push(` /* struct_inference: ${summarizeRewriteStructInference(func)} */`);
|
|
2507
|
+
if ((func.runtime_context?.corroborated_apis || []).length > 0 ||
|
|
2508
|
+
(func.runtime_context?.corroborated_stages || []).length > 0 ||
|
|
2509
|
+
(func.runtime_context?.matched_memory_regions || []).length > 0) {
|
|
2510
|
+
lines.push(` /* runtime_context: apis=${(func.runtime_context?.corroborated_apis || []).join(', ') || 'none'} stages=${(func.runtime_context?.corroborated_stages || []).join(', ') || 'none'} regions=${(func.runtime_context?.matched_memory_regions || []).join(', ') || 'none'} modules=${(func.runtime_context?.suggested_modules || []).join(', ') || 'none'} confidence=${Number(func.runtime_context?.confidence || 0).toFixed(2)} executed=${func.runtime_context?.executed ? 'yes' : 'no'} sources=${(func.runtime_context?.evidence_sources || []).join(', ') || 'unknown'} names=${(func.runtime_context?.source_names || []).join(', ') || 'unknown'} matched_by=${(func.runtime_context?.matched_by || []).join(', ') || 'unknown'} artifacts=${func.runtime_context?.executed_artifact_count || 0}/${func.runtime_context?.artifact_count || 0} */`);
|
|
2511
|
+
}
|
|
2512
|
+
if ((func.runtime_context?.notes || []).length > 0) {
|
|
2513
|
+
lines.push(` /* runtime_notes: ${(func.runtime_context?.notes || []).join(' || ')} */`);
|
|
2514
|
+
}
|
|
2515
|
+
lines.push(` /* gaps: ${(func.gaps || []).join(', ') || 'none'} rank_reasons=${(func.rank_reasons || []).join(', ') || 'none'} */`);
|
|
2516
|
+
for (const [index, step] of buildRewriteSteps(func).entries()) {
|
|
2517
|
+
lines.push(` /* step_${index + 1}: ${step} */`);
|
|
2518
|
+
}
|
|
2519
|
+
lines.push(' /* TODO: Confirm parameter contract, buffer ownership, and error propagation. */');
|
|
2520
|
+
lines.push('');
|
|
2521
|
+
lines.push(...buildSemanticRewriteBody(func, module));
|
|
2522
|
+
lines.push('');
|
|
2523
|
+
lines.push(' return recovered_status;');
|
|
2524
|
+
lines.push('}');
|
|
2525
|
+
lines.push('');
|
|
2526
|
+
lines.push(`int ${names.originalName}(void)`);
|
|
2527
|
+
lines.push('{');
|
|
2528
|
+
lines.push(' AkRuntimeContext runtime_ctx = {0};');
|
|
2529
|
+
lines.push(' AkSemanticInputs inputs = {0};');
|
|
2530
|
+
lines.push(' AkSemanticOutputs outputs = {0};');
|
|
2531
|
+
lines.push(` return ${names.implementationName}(&runtime_ctx, &inputs, &outputs);`);
|
|
2532
|
+
lines.push('}');
|
|
2533
|
+
lines.push('');
|
|
2534
|
+
}
|
|
2535
|
+
return lines.join('\n').trimEnd() + '\n';
|
|
2536
|
+
}
|
|
2537
|
+
function buildHarnessContent(modules) {
|
|
2538
|
+
const lines = [];
|
|
2539
|
+
lines.push('/* semantic reconstruction harness */');
|
|
2540
|
+
lines.push('#include <stdio.h>');
|
|
2541
|
+
lines.push('#include <string.h>');
|
|
2542
|
+
lines.push('#include "reconstruct_support.h"');
|
|
2543
|
+
for (const module of modules) {
|
|
2544
|
+
lines.push(`#include "${sanitizeModuleName(module.name)}.interface.h"`);
|
|
2545
|
+
}
|
|
2546
|
+
lines.push('');
|
|
2547
|
+
lines.push('typedef struct AkHarnessEntry {');
|
|
2548
|
+
lines.push(' const char *module_name;');
|
|
2549
|
+
lines.push(' const char *original_symbol;');
|
|
2550
|
+
lines.push(' const char *seed_text;');
|
|
2551
|
+
lines.push(' const char *expected_stage;');
|
|
2552
|
+
lines.push(' int (*semantic_entry)(AkRuntimeContext *runtime_ctx, const AkSemanticInputs *inputs, AkSemanticOutputs *outputs);');
|
|
2553
|
+
lines.push(' int (*wrapper_entry)(void);');
|
|
2554
|
+
lines.push('} AkHarnessEntry;');
|
|
2555
|
+
lines.push('');
|
|
2556
|
+
lines.push('static int ak_stage_matches(const char *expected_stage, const char *observed_stage)');
|
|
2557
|
+
lines.push('{');
|
|
2558
|
+
lines.push(' if (expected_stage == 0 || expected_stage[0] == \'\\0\') {');
|
|
2559
|
+
lines.push(' return 1;');
|
|
2560
|
+
lines.push(' }');
|
|
2561
|
+
lines.push(' if (observed_stage == 0) {');
|
|
2562
|
+
lines.push(' return 0;');
|
|
2563
|
+
lines.push(' }');
|
|
2564
|
+
lines.push(' return strcmp(expected_stage, observed_stage) == 0;');
|
|
2565
|
+
lines.push('}');
|
|
2566
|
+
lines.push('');
|
|
2567
|
+
lines.push('static const AkHarnessEntry AK_HARNESS_ENTRIES[] = {');
|
|
2568
|
+
for (const module of modules) {
|
|
2569
|
+
for (const func of module.functions) {
|
|
2570
|
+
const names = deriveRewriteEntryNames(func, module);
|
|
2571
|
+
const expectedStage = deriveHarnessExpectedStage(func, module);
|
|
2572
|
+
lines.push(` { "${escapeCString(module.name)}", "${escapeCString(func.function)}", "${escapeCString(deriveHarnessSeedText(func, module))}", ${expectedStage || '0'}, ${names.implementationName}, ${names.originalName} },`);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
lines.push('};');
|
|
2576
|
+
lines.push('');
|
|
2577
|
+
lines.push('int main(void)');
|
|
2578
|
+
lines.push('{');
|
|
2579
|
+
lines.push(' size_t index = 0;');
|
|
2580
|
+
lines.push(' size_t mismatch_count = 0;');
|
|
2581
|
+
lines.push(' for (index = 0; index < (sizeof(AK_HARNESS_ENTRIES) / sizeof(AK_HARNESS_ENTRIES[0])); ++index) {');
|
|
2582
|
+
lines.push(' AkRuntimeContext runtime_ctx = {0};');
|
|
2583
|
+
lines.push(' AkSemanticInputs inputs = {0};');
|
|
2584
|
+
lines.push(' AkSemanticOutputs outputs = {0};');
|
|
2585
|
+
lines.push(' inputs.string_args[0] = AK_HARNESS_ENTRIES[index].seed_text;');
|
|
2586
|
+
lines.push(' inputs.string_args[1] = AK_HARNESS_ENTRIES[index].seed_text;');
|
|
2587
|
+
lines.push(' inputs.scalar_args[0] = (uint64_t)index;');
|
|
2588
|
+
lines.push(' inputs.pointer_args[0] = (void *)(uintptr_t)(0x10000000u + ((unsigned int)index * 0x1000u));');
|
|
2589
|
+
lines.push(' inputs.handle_args[0] = (uintptr_t)(0x1000u + (unsigned int)index);');
|
|
2590
|
+
lines.push(' inputs.handle_args[1] = (uintptr_t)(0x2000u + (unsigned int)index);');
|
|
2591
|
+
lines.push(' int status = AK_HARNESS_ENTRIES[index].semantic_entry(&runtime_ctx, &inputs, &outputs);');
|
|
2592
|
+
lines.push(' int stage_match = ak_stage_matches(AK_HARNESS_ENTRIES[index].expected_stage, outputs.observed_stage);');
|
|
2593
|
+
lines.push(' if (!stage_match) {');
|
|
2594
|
+
lines.push(' ++mismatch_count;');
|
|
2595
|
+
lines.push(' }');
|
|
2596
|
+
lines.push(' printf("[%s] %s => status=%d stage=%s expected=%s match=%s detail=%s\\n", AK_HARNESS_ENTRIES[index].module_name, AK_HARNESS_ENTRIES[index].original_symbol, status, outputs.observed_stage ? outputs.observed_stage : "none", AK_HARNESS_ENTRIES[index].expected_stage ? AK_HARNESS_ENTRIES[index].expected_stage : "none", stage_match ? "ok" : "mismatch", outputs.status_detail ? outputs.status_detail : "none");');
|
|
2597
|
+
lines.push(' }');
|
|
2598
|
+
lines.push(' return mismatch_count == 0 ? 0 : 1;');
|
|
2599
|
+
lines.push('}');
|
|
2600
|
+
lines.push('');
|
|
2601
|
+
return lines.join('\n');
|
|
2602
|
+
}
|
|
2603
|
+
function buildCMakeContent(modules) {
|
|
2604
|
+
const lines = [];
|
|
2605
|
+
lines.push('cmake_minimum_required(VERSION 3.20)');
|
|
2606
|
+
lines.push('project(reconstruct_skeleton C)');
|
|
2607
|
+
lines.push('');
|
|
2608
|
+
lines.push('set(CMAKE_C_STANDARD 99)');
|
|
2609
|
+
lines.push('set(CMAKE_C_STANDARD_REQUIRED ON)');
|
|
2610
|
+
lines.push('');
|
|
2611
|
+
lines.push('add_executable(reconstruct_harness');
|
|
2612
|
+
lines.push(' src/reconstruct_harness.c');
|
|
2613
|
+
for (const module of modules) {
|
|
2614
|
+
lines.push(` src/${sanitizeModuleName(module.name)}.rewrite.c`);
|
|
2615
|
+
}
|
|
2616
|
+
lines.push(')');
|
|
2617
|
+
lines.push('');
|
|
2618
|
+
lines.push('target_include_directories(reconstruct_harness PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)');
|
|
2619
|
+
lines.push('');
|
|
2620
|
+
return lines.join('\n');
|
|
2621
|
+
}
|
|
2622
|
+
async function sha256File(filePath) {
|
|
2623
|
+
const content = await fs.readFile(filePath);
|
|
2624
|
+
return createHash('sha256').update(content).digest('hex');
|
|
2625
|
+
}
|
|
2626
|
+
function scoreFunctionForDedup(func) {
|
|
2627
|
+
return (func.confidence * 100 +
|
|
2628
|
+
(func.semantic_summary ? Math.min(func.semantic_summary.length, 120) / 10 : 0) +
|
|
2629
|
+
(func.source_like_snippet ? Math.min(func.source_like_snippet.length, 400) / 100 : 0) +
|
|
2630
|
+
(func.xref_signals?.length || 0) * 4 +
|
|
2631
|
+
(func.runtime_context?.corroborated_apis?.length || 0) * 3 +
|
|
2632
|
+
(func.runtime_context?.matched_memory_regions?.length || 0) * 2 -
|
|
2633
|
+
(func.gaps?.length || 0) * 3);
|
|
2634
|
+
}
|
|
2635
|
+
function dedupeReconstructedFunctions(functions) {
|
|
2636
|
+
const orderedKeys = [];
|
|
2637
|
+
const byKey = new Map();
|
|
2638
|
+
for (const func of functions) {
|
|
2639
|
+
const key = (func.address || func.function).toLowerCase();
|
|
2640
|
+
const existing = byKey.get(key);
|
|
2641
|
+
if (!existing) {
|
|
2642
|
+
orderedKeys.push(key);
|
|
2643
|
+
byKey.set(key, func);
|
|
2644
|
+
continue;
|
|
2645
|
+
}
|
|
2646
|
+
if (scoreFunctionForDedup(func) > scoreFunctionForDedup(existing)) {
|
|
2647
|
+
byKey.set(key, func);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
return orderedKeys.map((key) => byKey.get(key)).filter(Boolean);
|
|
2651
|
+
}
|
|
2652
|
+
function ensureNameResolution(func) {
|
|
2653
|
+
const existing = func.name_resolution;
|
|
2654
|
+
const validatedName = existing?.validated_name || func.suggested_name || null;
|
|
2655
|
+
const ruleBasedName = existing?.rule_based_name || func.suggested_name || null;
|
|
2656
|
+
const resolutionSource = existing?.resolution_source || (validatedName ? 'rule' : 'unresolved');
|
|
2657
|
+
return {
|
|
2658
|
+
...func,
|
|
2659
|
+
name_resolution: {
|
|
2660
|
+
rule_based_name: ruleBasedName,
|
|
2661
|
+
llm_suggested_name: existing?.llm_suggested_name || null,
|
|
2662
|
+
llm_confidence: typeof existing?.llm_confidence === 'number' ? existing.llm_confidence : null,
|
|
2663
|
+
llm_why: existing?.llm_why || null,
|
|
2664
|
+
required_assumptions: existing?.required_assumptions || [],
|
|
2665
|
+
evidence_used: existing?.evidence_used || func.rename_evidence || [],
|
|
2666
|
+
validated_name: validatedName,
|
|
2667
|
+
resolution_source: resolutionSource,
|
|
2668
|
+
unresolved_semantic_name: typeof existing?.unresolved_semantic_name === 'boolean'
|
|
2669
|
+
? existing.unresolved_semantic_name
|
|
2670
|
+
: !validatedName,
|
|
2671
|
+
},
|
|
2672
|
+
};
|
|
2673
|
+
}
|
|
2674
|
+
async function pathExists(filePath) {
|
|
2675
|
+
if (!filePath) {
|
|
2676
|
+
return false;
|
|
2677
|
+
}
|
|
2678
|
+
try {
|
|
2679
|
+
await fs.access(filePath);
|
|
2680
|
+
return true;
|
|
2681
|
+
}
|
|
2682
|
+
catch {
|
|
2683
|
+
return false;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
function quoteCommand(command, args) {
|
|
2687
|
+
return [command, ...args]
|
|
2688
|
+
.map((value) => {
|
|
2689
|
+
if (value.length === 0) {
|
|
2690
|
+
return '""';
|
|
2691
|
+
}
|
|
2692
|
+
return /[\s"]/u.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
|
|
2693
|
+
})
|
|
2694
|
+
.join(' ');
|
|
2695
|
+
}
|
|
2696
|
+
async function runCommandWithTimeout(command, args, cwd, timeoutMs) {
|
|
2697
|
+
return new Promise((resolve) => {
|
|
2698
|
+
const commandDisplay = quoteCommand(command, args);
|
|
2699
|
+
const effectiveTimeoutMs = Math.max(5000, timeoutMs);
|
|
2700
|
+
const child = spawn(command, args, {
|
|
2701
|
+
cwd,
|
|
2702
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
2703
|
+
windowsHide: true,
|
|
2704
|
+
});
|
|
2705
|
+
let stdout = '';
|
|
2706
|
+
let stderr = '';
|
|
2707
|
+
let settled = false;
|
|
2708
|
+
let timedOut = false;
|
|
2709
|
+
const finish = (result) => {
|
|
2710
|
+
if (settled) {
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
settled = true;
|
|
2714
|
+
clearTimeout(timer);
|
|
2715
|
+
resolve(result);
|
|
2716
|
+
};
|
|
2717
|
+
const timer = setTimeout(() => {
|
|
2718
|
+
timedOut = true;
|
|
2719
|
+
child.kill();
|
|
2720
|
+
finish({
|
|
2721
|
+
command: commandDisplay,
|
|
2722
|
+
exitCode: null,
|
|
2723
|
+
timedOut: true,
|
|
2724
|
+
stdout,
|
|
2725
|
+
stderr,
|
|
2726
|
+
error: `command timed out after ${effectiveTimeoutMs}ms`,
|
|
2727
|
+
});
|
|
2728
|
+
}, effectiveTimeoutMs);
|
|
2729
|
+
child.stdout.on('data', (chunk) => {
|
|
2730
|
+
stdout += chunk.toString();
|
|
2731
|
+
});
|
|
2732
|
+
child.stderr.on('data', (chunk) => {
|
|
2733
|
+
stderr += chunk.toString();
|
|
2734
|
+
});
|
|
2735
|
+
child.on('error', (error) => {
|
|
2736
|
+
finish({
|
|
2737
|
+
command: commandDisplay,
|
|
2738
|
+
exitCode: null,
|
|
2739
|
+
timedOut: false,
|
|
2740
|
+
stdout,
|
|
2741
|
+
stderr,
|
|
2742
|
+
error: error.message,
|
|
2743
|
+
});
|
|
2744
|
+
});
|
|
2745
|
+
child.on('close', (code) => {
|
|
2746
|
+
if (timedOut) {
|
|
2747
|
+
return;
|
|
2748
|
+
}
|
|
2749
|
+
finish({
|
|
2750
|
+
command: commandDisplay,
|
|
2751
|
+
exitCode: code ?? null,
|
|
2752
|
+
timedOut: false,
|
|
2753
|
+
stdout,
|
|
2754
|
+
stderr,
|
|
2755
|
+
error: code === 0 ? null : `command failed with exit code ${code ?? 'unknown'}`,
|
|
2756
|
+
});
|
|
2757
|
+
});
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
async function resolveCommandFromPath(commandNames) {
|
|
2761
|
+
const locator = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
2762
|
+
for (const commandName of commandNames) {
|
|
2763
|
+
const result = await runCommandWithTimeout(locator, [commandName], getPackageRoot(), 5000);
|
|
2764
|
+
if (result.exitCode !== 0) {
|
|
2765
|
+
continue;
|
|
2766
|
+
}
|
|
2767
|
+
const firstLine = result.stdout
|
|
2768
|
+
.split(/\r?\n/)
|
|
2769
|
+
.map((line) => line.trim())
|
|
2770
|
+
.find((line) => line.length > 0);
|
|
2771
|
+
if (firstLine && (await pathExists(firstLine))) {
|
|
2772
|
+
return firstLine;
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
return null;
|
|
2776
|
+
}
|
|
2777
|
+
async function collectWindowsClangCandidates(root) {
|
|
2778
|
+
const candidates = [];
|
|
2779
|
+
try {
|
|
2780
|
+
const level1 = await fs.readdir(root, { withFileTypes: true });
|
|
2781
|
+
for (const entry of level1) {
|
|
2782
|
+
if (!entry.isDirectory() || !entry.name.toLowerCase().includes('clang+llvm')) {
|
|
2783
|
+
continue;
|
|
2784
|
+
}
|
|
2785
|
+
const level1Path = path.join(root, entry.name);
|
|
2786
|
+
candidates.push(path.join(level1Path, 'bin', 'clang.exe'));
|
|
2787
|
+
try {
|
|
2788
|
+
const level2 = await fs.readdir(level1Path, { withFileTypes: true });
|
|
2789
|
+
for (const nested of level2) {
|
|
2790
|
+
if (!nested.isDirectory() || !nested.name.toLowerCase().includes('clang+llvm')) {
|
|
2791
|
+
continue;
|
|
2792
|
+
}
|
|
2793
|
+
const level2Path = path.join(level1Path, nested.name);
|
|
2794
|
+
candidates.push(path.join(level2Path, 'bin', 'clang.exe'));
|
|
2795
|
+
try {
|
|
2796
|
+
const level3 = await fs.readdir(level2Path, { withFileTypes: true });
|
|
2797
|
+
for (const deeper of level3) {
|
|
2798
|
+
if (!deeper.isDirectory() || !deeper.name.toLowerCase().includes('clang+llvm')) {
|
|
2799
|
+
continue;
|
|
2800
|
+
}
|
|
2801
|
+
candidates.push(path.join(level2Path, deeper.name, 'bin', 'clang.exe'));
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
catch {
|
|
2805
|
+
// best-effort candidate discovery
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
catch {
|
|
2810
|
+
// best-effort candidate discovery
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
catch {
|
|
2815
|
+
// root does not exist or is unreadable
|
|
2816
|
+
}
|
|
2817
|
+
return candidates;
|
|
2818
|
+
}
|
|
2819
|
+
async function resolveClangCompilerPath(explicitCompilerPath) {
|
|
2820
|
+
const candidates = [];
|
|
2821
|
+
if (explicitCompilerPath) {
|
|
2822
|
+
candidates.push(explicitCompilerPath);
|
|
2823
|
+
}
|
|
2824
|
+
if (process.env.CLANG_PATH) {
|
|
2825
|
+
candidates.push(process.env.CLANG_PATH);
|
|
2826
|
+
}
|
|
2827
|
+
const pathLookup = await resolveCommandFromPath(['clang.exe', 'clang']);
|
|
2828
|
+
if (pathLookup) {
|
|
2829
|
+
candidates.push(pathLookup);
|
|
2830
|
+
}
|
|
2831
|
+
const pathEntries = (process.env.PATH || '')
|
|
2832
|
+
.split(path.delimiter)
|
|
2833
|
+
.map((entry) => entry.trim())
|
|
2834
|
+
.filter((entry) => entry.length > 0);
|
|
2835
|
+
for (const entry of pathEntries) {
|
|
2836
|
+
candidates.push(path.join(entry, process.platform === 'win32' ? 'clang.exe' : 'clang'));
|
|
2837
|
+
}
|
|
2838
|
+
candidates.push('C:\\Program Files\\LLVM\\bin\\clang.exe', 'E:\\clang+llvm-18.1.8-x86_64-pc-windows-msvc\\clang+llvm-18.1.8-x86_64-pc-windows-msvc\\bin\\clang.exe', 'E:\\clang+llvm-18.1.8-x86_64-pc-windows-msvc.tar\\clang+llvm-18.1.8-x86_64-pc-windows-msvc\\clang+llvm-18.1.8-x86_64-pc-windows-msvc\\bin\\clang.exe');
|
|
2839
|
+
if (process.platform === 'win32') {
|
|
2840
|
+
candidates.push(...(await collectWindowsClangCandidates('E:\\')));
|
|
2841
|
+
candidates.push(...(await collectWindowsClangCandidates('C:\\')));
|
|
2842
|
+
}
|
|
2843
|
+
for (const candidate of dedupe(candidates)) {
|
|
2844
|
+
if (await pathExists(candidate)) {
|
|
2845
|
+
return candidate;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
return null;
|
|
2849
|
+
}
|
|
2850
|
+
function buildNativeValidationLog(validation) {
|
|
2851
|
+
const lines = [];
|
|
2852
|
+
lines.push('# BUILD_VALIDATION.log');
|
|
2853
|
+
lines.push('');
|
|
2854
|
+
lines.push(`status: ${validation.status}`);
|
|
2855
|
+
lines.push(`attempted: ${validation.attempted}`);
|
|
2856
|
+
lines.push(`compiler: ${validation.compiler || 'none'}`);
|
|
2857
|
+
lines.push(`compiler_path: ${validation.compiler_path || 'none'}`);
|
|
2858
|
+
lines.push(`command: ${validation.command || 'n/a'}`);
|
|
2859
|
+
lines.push(`exit_code: ${validation.exit_code === null ? 'n/a' : validation.exit_code}`);
|
|
2860
|
+
lines.push(`timed_out: ${validation.timed_out}`);
|
|
2861
|
+
lines.push(`error: ${validation.error || 'none'}`);
|
|
2862
|
+
lines.push(`executable_path: ${validation.executable_path || 'none'}`);
|
|
2863
|
+
lines.push('');
|
|
2864
|
+
lines.push('## stdout');
|
|
2865
|
+
lines.push('```text');
|
|
2866
|
+
lines.push(validation.stdout || '');
|
|
2867
|
+
lines.push('```');
|
|
2868
|
+
lines.push('');
|
|
2869
|
+
lines.push('## stderr');
|
|
2870
|
+
lines.push('```text');
|
|
2871
|
+
lines.push(validation.stderr || '');
|
|
2872
|
+
lines.push('```');
|
|
2873
|
+
lines.push('');
|
|
2874
|
+
return lines.join('\n');
|
|
2875
|
+
}
|
|
2876
|
+
function buildHarnessValidationLog(validation) {
|
|
2877
|
+
const lines = [];
|
|
2878
|
+
lines.push('# HARNESS_VALIDATION.log');
|
|
2879
|
+
lines.push('');
|
|
2880
|
+
lines.push(`status: ${validation.status}`);
|
|
2881
|
+
lines.push(`attempted: ${validation.attempted}`);
|
|
2882
|
+
lines.push(`command: ${validation.command || 'n/a'}`);
|
|
2883
|
+
lines.push(`exit_code: ${validation.exit_code === null ? 'n/a' : validation.exit_code}`);
|
|
2884
|
+
lines.push(`timed_out: ${validation.timed_out}`);
|
|
2885
|
+
lines.push(`error: ${validation.error || 'none'}`);
|
|
2886
|
+
lines.push(`matched_entries: ${validation.matched_entries}`);
|
|
2887
|
+
lines.push(`mismatched_entries: ${validation.mismatched_entries}`);
|
|
2888
|
+
lines.push('');
|
|
2889
|
+
lines.push('## stdout');
|
|
2890
|
+
lines.push('```text');
|
|
2891
|
+
lines.push(validation.stdout || '');
|
|
2892
|
+
lines.push('```');
|
|
2893
|
+
lines.push('');
|
|
2894
|
+
lines.push('## stderr');
|
|
2895
|
+
lines.push('```text');
|
|
2896
|
+
lines.push(validation.stderr || '');
|
|
2897
|
+
lines.push('```');
|
|
2898
|
+
lines.push('');
|
|
2899
|
+
return lines.join('\n');
|
|
2900
|
+
}
|
|
2901
|
+
async function runNativeBuildValidation(args) {
|
|
2902
|
+
const compilerPath = await resolveClangCompilerPath(args.compilerPath);
|
|
2903
|
+
if (!compilerPath) {
|
|
2904
|
+
return {
|
|
2905
|
+
attempted: true,
|
|
2906
|
+
status: 'unavailable',
|
|
2907
|
+
compiler: 'clang',
|
|
2908
|
+
compiler_path: null,
|
|
2909
|
+
command: null,
|
|
2910
|
+
exit_code: null,
|
|
2911
|
+
timed_out: false,
|
|
2912
|
+
error: 'clang compiler is not available in PATH or known install locations',
|
|
2913
|
+
stdout: '',
|
|
2914
|
+
stderr: '',
|
|
2915
|
+
log_path: null,
|
|
2916
|
+
executable_path: null,
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
const harnessSource = path.join(args.srcRoot, 'reconstruct_harness.c');
|
|
2920
|
+
const executablePath = path.join(args.exportRoot, 'reconstruct_harness.exe');
|
|
2921
|
+
const buildArgs = [
|
|
2922
|
+
'-std=c99',
|
|
2923
|
+
'-Wall',
|
|
2924
|
+
'-Wextra',
|
|
2925
|
+
'-I',
|
|
2926
|
+
args.srcRoot,
|
|
2927
|
+
'-o',
|
|
2928
|
+
executablePath,
|
|
2929
|
+
harnessSource,
|
|
2930
|
+
...args.moduleRewriteFiles,
|
|
2931
|
+
];
|
|
2932
|
+
const result = await runCommandWithTimeout(compilerPath, buildArgs, args.exportRoot, args.timeoutMs);
|
|
2933
|
+
return {
|
|
2934
|
+
attempted: true,
|
|
2935
|
+
status: result.error && result.exitCode === null && /enoent/i.test(result.error)
|
|
2936
|
+
? 'unavailable'
|
|
2937
|
+
: result.exitCode === 0
|
|
2938
|
+
? 'passed'
|
|
2939
|
+
: 'failed',
|
|
2940
|
+
compiler: 'clang',
|
|
2941
|
+
compiler_path: compilerPath,
|
|
2942
|
+
command: result.command,
|
|
2943
|
+
exit_code: result.exitCode,
|
|
2944
|
+
timed_out: result.timedOut,
|
|
2945
|
+
error: result.exitCode === 0 && !result.timedOut
|
|
2946
|
+
? null
|
|
2947
|
+
: result.error || `clang build failed with exit code ${result.exitCode ?? 'unknown'}`,
|
|
2948
|
+
stdout: result.stdout,
|
|
2949
|
+
stderr: result.stderr,
|
|
2950
|
+
log_path: null,
|
|
2951
|
+
executable_path: result.exitCode === 0 ? executablePath : null,
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
async function runHarnessValidation(args) {
|
|
2955
|
+
if (!(await pathExists(args.executablePath))) {
|
|
2956
|
+
return {
|
|
2957
|
+
attempted: true,
|
|
2958
|
+
status: 'unavailable',
|
|
2959
|
+
command: quoteCommand(args.executablePath, []),
|
|
2960
|
+
exit_code: null,
|
|
2961
|
+
timed_out: false,
|
|
2962
|
+
error: 'reconstruct_harness executable is not present',
|
|
2963
|
+
stdout: '',
|
|
2964
|
+
stderr: '',
|
|
2965
|
+
log_path: null,
|
|
2966
|
+
matched_entries: 0,
|
|
2967
|
+
mismatched_entries: 0,
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
const result = await runCommandWithTimeout(args.executablePath, [], args.cwd, args.timeoutMs);
|
|
2971
|
+
const combinedOutput = `${result.stdout}\n${result.stderr}`;
|
|
2972
|
+
const matchedEntries = (combinedOutput.match(/match=ok/g) || []).length;
|
|
2973
|
+
const mismatchedEntries = (combinedOutput.match(/match=mismatch/g) || []).length;
|
|
2974
|
+
return {
|
|
2975
|
+
attempted: true,
|
|
2976
|
+
status: result.exitCode === 0 && mismatchedEntries === 0 ? 'passed' : 'failed',
|
|
2977
|
+
command: result.command,
|
|
2978
|
+
exit_code: result.exitCode,
|
|
2979
|
+
timed_out: result.timedOut,
|
|
2980
|
+
error: result.exitCode === 0 && mismatchedEntries === 0
|
|
2981
|
+
? null
|
|
2982
|
+
: result.error || `reconstruct_harness failed with exit code ${result.exitCode ?? 'unknown'}`,
|
|
2983
|
+
stdout: result.stdout,
|
|
2984
|
+
stderr: result.stderr,
|
|
2985
|
+
log_path: null,
|
|
2986
|
+
matched_entries: matchedEntries,
|
|
2987
|
+
mismatched_entries: mismatchedEntries,
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
function averageConfidence(functions) {
|
|
2991
|
+
if (functions.length === 0) {
|
|
2992
|
+
return 0;
|
|
2993
|
+
}
|
|
2994
|
+
const sum = functions.reduce((acc, item) => acc + item.confidence, 0);
|
|
2995
|
+
return clamp(sum / functions.length, 0, 1);
|
|
2996
|
+
}
|
|
2997
|
+
function buildGapsMarkdown(modules, warnings, functionSet) {
|
|
2998
|
+
const lines = [];
|
|
2999
|
+
lines.push('# gaps.md');
|
|
3000
|
+
lines.push('');
|
|
3001
|
+
lines.push('## Global Warnings');
|
|
3002
|
+
if (warnings.length === 0) {
|
|
3003
|
+
lines.push('- None');
|
|
3004
|
+
}
|
|
3005
|
+
else {
|
|
3006
|
+
for (const warning of warnings) {
|
|
3007
|
+
lines.push(`- ${warning}`);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
lines.push('');
|
|
3011
|
+
lines.push('## Low Confidence Modules');
|
|
3012
|
+
const lowConfidenceModules = modules
|
|
3013
|
+
.map((module) => ({
|
|
3014
|
+
name: module.name,
|
|
3015
|
+
confidence: averageConfidence(module.functions),
|
|
3016
|
+
count: module.functions.length,
|
|
3017
|
+
}))
|
|
3018
|
+
.filter((item) => item.confidence < 0.55);
|
|
3019
|
+
if (lowConfidenceModules.length === 0) {
|
|
3020
|
+
lines.push('- None');
|
|
3021
|
+
}
|
|
3022
|
+
else {
|
|
3023
|
+
for (const item of lowConfidenceModules) {
|
|
3024
|
+
lines.push(`- ${item.name}: confidence=${item.confidence.toFixed(2)}, functions=${item.count}`);
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
lines.push('');
|
|
3028
|
+
lines.push('## Function Gaps');
|
|
3029
|
+
const unresolved = functionSet.filter((func) => func.gaps.length > 0);
|
|
3030
|
+
if (unresolved.length === 0) {
|
|
3031
|
+
lines.push('- None');
|
|
3032
|
+
}
|
|
3033
|
+
else {
|
|
3034
|
+
for (const func of unresolved) {
|
|
3035
|
+
lines.push(`- ${func.function} (${func.address}): ${func.gaps.join(', ')}`);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
lines.push('');
|
|
3039
|
+
lines.push('## Notes');
|
|
3040
|
+
lines.push('- Source-like export is semantic reconstruction, not original source recovery.');
|
|
3041
|
+
lines.push('- Validate low-confidence blocks with disassembly and runtime context.');
|
|
3042
|
+
lines.push('');
|
|
3043
|
+
return lines.join('\n');
|
|
3044
|
+
}
|
|
3045
|
+
function inferBinaryRole(originalFilename, sampleFileType, exportCount) {
|
|
3046
|
+
const loweredName = (originalFilename || '').toLowerCase();
|
|
3047
|
+
const loweredType = (sampleFileType || '').toLowerCase();
|
|
3048
|
+
if (loweredName.endsWith('.sys') || loweredType.includes('driver')) {
|
|
3049
|
+
return 'driver';
|
|
3050
|
+
}
|
|
3051
|
+
if (loweredName.endsWith('.dll') ||
|
|
3052
|
+
loweredName.endsWith('.ocx') ||
|
|
3053
|
+
loweredName.endsWith('.cpl') ||
|
|
3054
|
+
loweredType.includes('dll')) {
|
|
3055
|
+
return 'dll';
|
|
3056
|
+
}
|
|
3057
|
+
if (loweredName.endsWith('.exe') || loweredType.includes('exe') || loweredType.includes('pe32')) {
|
|
3058
|
+
return exportCount > 0 ? 'executable_with_exports' : 'executable';
|
|
3059
|
+
}
|
|
3060
|
+
if (exportCount > 0) {
|
|
3061
|
+
return 'library_like_pe';
|
|
3062
|
+
}
|
|
3063
|
+
return 'pe_image';
|
|
3064
|
+
}
|
|
3065
|
+
function buildBinaryProfile(sampleFileType, originalFilename, exportsData, packerData, modules, cliProfile) {
|
|
3066
|
+
const derivedCliProfile = cliProfile ||
|
|
3067
|
+
buildReconstructCliProfile(modules.map((module) => ({
|
|
3068
|
+
name: module.name,
|
|
3069
|
+
functions: [],
|
|
3070
|
+
importHints: new Set(module.import_hints || []),
|
|
3071
|
+
stringHints: new Set(module.string_hints || []),
|
|
3072
|
+
runtimeApis: new Set(module.runtime_apis || []),
|
|
3073
|
+
runtimeStages: new Set(module.runtime_stages || []),
|
|
3074
|
+
runtimeNotes: new Set(),
|
|
3075
|
+
})));
|
|
3076
|
+
const exportEntries = exportsData?.exports || [];
|
|
3077
|
+
const exportCount = typeof exportsData?.total_exports === 'number'
|
|
3078
|
+
? exportsData.total_exports
|
|
3079
|
+
: exportEntries.length;
|
|
3080
|
+
const forwarderCount = typeof exportsData?.total_forwarders === 'number'
|
|
3081
|
+
? exportsData.total_forwarders
|
|
3082
|
+
: (exportsData?.forwarders || []).length;
|
|
3083
|
+
const notableExports = exportEntries
|
|
3084
|
+
.map((item) => item.name || `ordinal_${item.ordinal}`)
|
|
3085
|
+
.filter((item, index, all) => all.indexOf(item) === index)
|
|
3086
|
+
.slice(0, 8);
|
|
3087
|
+
const binaryRole = inferBinaryRole(originalFilename, sampleFileType, exportCount);
|
|
3088
|
+
const packed = packerData?.packed === true;
|
|
3089
|
+
const packingConfidence = clamp(packerData?.confidence ?? 0, 0, 1);
|
|
3090
|
+
const priorities = [];
|
|
3091
|
+
if (packed || packingConfidence >= 0.45) {
|
|
3092
|
+
priorities.push('unpack_or_deobfuscate_before_deep_semantics');
|
|
3093
|
+
}
|
|
3094
|
+
if (exportCount > 0) {
|
|
3095
|
+
priorities.push('trace_export_surface_first');
|
|
3096
|
+
}
|
|
3097
|
+
if (forwarderCount > 0) {
|
|
3098
|
+
priorities.push('inspect_forwarded_exports');
|
|
3099
|
+
}
|
|
3100
|
+
if (modules.some((module) => module.name === 'process_ops')) {
|
|
3101
|
+
priorities.push('review_process_manipulation_paths');
|
|
3102
|
+
}
|
|
3103
|
+
if (modules.some((module) => module.name === 'network_ops')) {
|
|
3104
|
+
priorities.push('review_network_reachability');
|
|
3105
|
+
}
|
|
3106
|
+
if (modules.some((module) => module.name === 'packer_analysis')) {
|
|
3107
|
+
priorities.push('review_packer_or_format_analysis_logic');
|
|
3108
|
+
}
|
|
3109
|
+
if (derivedCliProfile && (derivedCliProfile.command_count > 0 || derivedCliProfile.help_banner.length > 0)) {
|
|
3110
|
+
priorities.push('recover_cli_and_command_model');
|
|
3111
|
+
}
|
|
3112
|
+
return {
|
|
3113
|
+
binary_role: binaryRole,
|
|
3114
|
+
original_filename: originalFilename,
|
|
3115
|
+
export_count: exportCount,
|
|
3116
|
+
forwarder_count: forwarderCount,
|
|
3117
|
+
notable_exports: notableExports,
|
|
3118
|
+
packed,
|
|
3119
|
+
packing_confidence: packingConfidence,
|
|
3120
|
+
analysis_priorities: priorities.slice(0, 6),
|
|
3121
|
+
cli_profile: derivedCliProfile,
|
|
3122
|
+
};
|
|
3123
|
+
}
|
|
3124
|
+
function buildReverseNotesMarkdown(profile, modules, warnings, runtimeEvidence, cliModels, buildValidation, harnessValidation) {
|
|
3125
|
+
const lines = [];
|
|
3126
|
+
lines.push('# reverse_notes.md');
|
|
3127
|
+
lines.push('');
|
|
3128
|
+
lines.push('## Binary Profile');
|
|
3129
|
+
lines.push(`- binary_role: ${profile.binary_role}`);
|
|
3130
|
+
lines.push(`- original_filename: ${profile.original_filename || 'unknown'}`);
|
|
3131
|
+
lines.push(`- packed: ${profile.packed} (confidence=${profile.packing_confidence.toFixed(2)})`);
|
|
3132
|
+
lines.push(`- export_count: ${profile.export_count}`);
|
|
3133
|
+
lines.push(`- forwarder_count: ${profile.forwarder_count}`);
|
|
3134
|
+
lines.push(`- notable_exports: ${profile.notable_exports.length > 0 ? profile.notable_exports.join(', ') : 'none'}`);
|
|
3135
|
+
if (profile.cli_profile) {
|
|
3136
|
+
lines.push('## Primary CLI Model');
|
|
3137
|
+
lines.push(`- tool_name: ${profile.cli_profile.tool_name}`);
|
|
3138
|
+
lines.push(`- help_banner: ${profile.cli_profile.help_banner}`);
|
|
3139
|
+
lines.push(`- command_count: ${profile.cli_profile.command_count}`);
|
|
3140
|
+
lines.push(`- commands: ${profile.cli_profile.commands.map((item) => `${item.verb} => ${item.summary}`).join(' | ') || 'none'}`);
|
|
3141
|
+
lines.push('');
|
|
3142
|
+
}
|
|
3143
|
+
lines.push('## Analysis Priorities');
|
|
3144
|
+
if (profile.analysis_priorities.length === 0) {
|
|
3145
|
+
lines.push('- Start from the highest-confidence reconstructed modules and function summaries.');
|
|
3146
|
+
}
|
|
3147
|
+
else {
|
|
3148
|
+
for (const item of profile.analysis_priorities) {
|
|
3149
|
+
lines.push(`- ${item}`);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
lines.push('');
|
|
3153
|
+
lines.push('## Module Guide');
|
|
3154
|
+
for (const module of modules) {
|
|
3155
|
+
const moduleBucket = {
|
|
3156
|
+
name: module.name,
|
|
3157
|
+
functions: [],
|
|
3158
|
+
importHints: new Set(module.import_hints || []),
|
|
3159
|
+
stringHints: new Set(module.string_hints || []),
|
|
3160
|
+
runtimeApis: new Set(module.runtime_apis || []),
|
|
3161
|
+
runtimeStages: new Set(module.runtime_stages || []),
|
|
3162
|
+
runtimeNotes: new Set(),
|
|
3163
|
+
};
|
|
3164
|
+
const displayHints = collectDisplayStringHints(moduleBucket).slice(0, 3);
|
|
3165
|
+
const semanticRole = describeModuleRole(moduleBucket);
|
|
3166
|
+
lines.push(`- ${module.name}: role=${semanticRole} confidence=${module.confidence.toFixed(2)}, functions=${module.function_count}, strings=${displayHints.join(' | ') || 'none'}, runtime=${module.runtime_stages.slice(0, 2).join(', ') || 'none'}`);
|
|
3167
|
+
}
|
|
3168
|
+
lines.push('');
|
|
3169
|
+
if (runtimeEvidence) {
|
|
3170
|
+
lines.push('## Runtime Evidence');
|
|
3171
|
+
lines.push(`- executed: ${runtimeEvidence.executed}`);
|
|
3172
|
+
lines.push(`- api_count: ${runtimeEvidence.api_count}`);
|
|
3173
|
+
lines.push(`- stage_count: ${runtimeEvidence.stage_count}`);
|
|
3174
|
+
lines.push(`- observed_apis: ${runtimeEvidence.observed_apis.slice(0, 8).join(', ') || 'none'}`);
|
|
3175
|
+
lines.push(`- region_types: ${(runtimeEvidence.region_types || []).slice(0, 8).join(', ') || 'none'}`);
|
|
3176
|
+
lines.push(`- observed_modules: ${(runtimeEvidence.observed_modules || []).slice(0, 6).join(', ') || 'none'}`);
|
|
3177
|
+
lines.push(`- stages: ${runtimeEvidence.stages.slice(0, 6).join(', ') || 'none'}`);
|
|
3178
|
+
lines.push(`- summary: ${runtimeEvidence.summary}`);
|
|
3179
|
+
lines.push('');
|
|
3180
|
+
}
|
|
3181
|
+
lines.push('## Native Validation');
|
|
3182
|
+
lines.push(`- build: status=${buildValidation.status}, compiler=${buildValidation.compiler || 'none'}, exit_code=${buildValidation.exit_code === null ? 'n/a' : buildValidation.exit_code}, executable=${buildValidation.executable_path || 'none'}`);
|
|
3183
|
+
lines.push(`- harness: status=${harnessValidation.status}, exit_code=${harnessValidation.exit_code === null ? 'n/a' : harnessValidation.exit_code}, matched=${harnessValidation.matched_entries}, mismatched=${harnessValidation.mismatched_entries}`);
|
|
3184
|
+
if (buildValidation.error) {
|
|
3185
|
+
lines.push(`- build_error: ${buildValidation.error}`);
|
|
3186
|
+
}
|
|
3187
|
+
if (harnessValidation.error) {
|
|
3188
|
+
lines.push(`- harness_error: ${harnessValidation.error}`);
|
|
3189
|
+
}
|
|
3190
|
+
lines.push('');
|
|
3191
|
+
if (cliModels.length > 0) {
|
|
3192
|
+
lines.push('## Module CLI Models');
|
|
3193
|
+
for (const cliModel of cliModels) {
|
|
3194
|
+
lines.push(`- ${cliModel.module}: tool=${cliModel.tool_name}, help=${cliModel.help_banner || 'none'}, commands=${cliModel.commands.map((item) => item.verb).join(', ') || 'none'}`);
|
|
3195
|
+
}
|
|
3196
|
+
lines.push('');
|
|
3197
|
+
}
|
|
3198
|
+
lines.push('## Reverse-Engineering Notes');
|
|
3199
|
+
if (profile.binary_role.includes('dll') || profile.export_count > 0) {
|
|
3200
|
+
lines.push('- Treat the export surface as an entry map and trace each exported routine into internal dispatchers.');
|
|
3201
|
+
}
|
|
3202
|
+
if (profile.packed || profile.packing_confidence >= 0.45) {
|
|
3203
|
+
lines.push('- Packer or obfuscation indicators are present; unpacking may be required before claiming source-equivalent recovery.');
|
|
3204
|
+
}
|
|
3205
|
+
lines.push('- Generated pseudocode is reconstructed and commented for analyst use; it is not original author source.');
|
|
3206
|
+
lines.push('- Export also includes a shared support header, a semantic harness, and a CMake skeleton for compile-oriented review.');
|
|
3207
|
+
lines.push('');
|
|
3208
|
+
if (warnings.length > 0) {
|
|
3209
|
+
lines.push('## Recent Warnings');
|
|
3210
|
+
for (const warning of warnings.slice(0, 10)) {
|
|
3211
|
+
lines.push(`- ${warning}`);
|
|
3212
|
+
}
|
|
3213
|
+
lines.push('');
|
|
3214
|
+
}
|
|
3215
|
+
return lines.join('\n');
|
|
3216
|
+
}
|
|
3217
|
+
export function createCodeReconstructExportHandler(workspaceManager, database, cacheManager, dependencies) {
|
|
3218
|
+
const decompilerWorker = new DecompilerWorker(database, workspaceManager);
|
|
3219
|
+
const reconstructFunctionsHandler = dependencies?.reconstructFunctionsHandler ||
|
|
3220
|
+
createCodeFunctionsReconstructHandler(workspaceManager, database, cacheManager);
|
|
3221
|
+
const importsExtractHandler = dependencies?.importsExtractHandler ||
|
|
3222
|
+
createPEImportsExtractHandler(workspaceManager, database, cacheManager);
|
|
3223
|
+
const exportsExtractHandler = dependencies?.exportsExtractHandler ||
|
|
3224
|
+
createPEExportsExtractHandler(workspaceManager, database, cacheManager);
|
|
3225
|
+
const packerDetectHandler = dependencies?.packerDetectHandler ||
|
|
3226
|
+
createPackerDetectHandler(workspaceManager, database, cacheManager);
|
|
3227
|
+
const stringsExtractHandler = dependencies?.stringsExtractHandler ||
|
|
3228
|
+
createStringsExtractHandler(workspaceManager, database, cacheManager);
|
|
3229
|
+
const runNativeBuild = dependencies?.nativeBuildValidator || runNativeBuildValidation;
|
|
3230
|
+
const runHarness = dependencies?.harnessValidator || runHarnessValidation;
|
|
3231
|
+
const searchFunctions = dependencies?.searchFunctions ||
|
|
3232
|
+
((sampleId, options) => decompilerWorker.searchFunctions(sampleId, options));
|
|
3233
|
+
const runtimeEvidenceLoader = dependencies?.runtimeEvidenceLoader ||
|
|
3234
|
+
((sampleId, options) => loadDynamicTraceEvidence(workspaceManager, database, sampleId, {
|
|
3235
|
+
evidenceScope: options?.evidenceScope,
|
|
3236
|
+
sessionTag: options?.sessionTag,
|
|
3237
|
+
}));
|
|
3238
|
+
return async (args) => {
|
|
3239
|
+
const input = CodeReconstructExportInputSchema.parse(args);
|
|
3240
|
+
const startTime = Date.now();
|
|
3241
|
+
try {
|
|
3242
|
+
const sample = database.findSample(input.sample_id);
|
|
3243
|
+
if (!sample) {
|
|
3244
|
+
return {
|
|
3245
|
+
ok: false,
|
|
3246
|
+
errors: [`Sample not found: ${input.sample_id}`],
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
const completedGhidraAnalysis = findBestGhidraAnalysis(database.findAnalysesBySample(input.sample_id), 'function_index');
|
|
3250
|
+
const decompileReadyAnalysis = findBestGhidraAnalysis(database.findAnalysesBySample(input.sample_id), 'decompile');
|
|
3251
|
+
const analysisMarker = decompileReadyAnalysis?.finished_at ||
|
|
3252
|
+
completedGhidraAnalysis?.finished_at ||
|
|
3253
|
+
decompileReadyAnalysis?.id ||
|
|
3254
|
+
completedGhidraAnalysis?.id ||
|
|
3255
|
+
'none';
|
|
3256
|
+
const runtimeArtifacts = [
|
|
3257
|
+
...database.findArtifactsByType(input.sample_id, 'dynamic_trace_json'),
|
|
3258
|
+
...database.findArtifactsByType(input.sample_id, 'sandbox_trace_json'),
|
|
3259
|
+
];
|
|
3260
|
+
const runtimeMarker = runtimeArtifacts.length > 0
|
|
3261
|
+
? runtimeArtifacts.map((item) => `${item.type}:${item.sha256}`).sort().join('|')
|
|
3262
|
+
: 'none';
|
|
3263
|
+
const semanticNameArtifacts = database.findArtifactsByType(input.sample_id, SEMANTIC_NAME_SUGGESTIONS_ARTIFACT_TYPE);
|
|
3264
|
+
const semanticNameMarker = semanticNameArtifacts.length > 0
|
|
3265
|
+
? semanticNameArtifacts.map((item) => `${item.id}:${item.sha256}`).sort().join('|')
|
|
3266
|
+
: 'none';
|
|
3267
|
+
const semanticExplanationArtifacts = database.findArtifactsByType(input.sample_id, SEMANTIC_FUNCTION_EXPLANATIONS_ARTIFACT_TYPE);
|
|
3268
|
+
const semanticExplanationMarker = semanticExplanationArtifacts.length > 0
|
|
3269
|
+
? semanticExplanationArtifacts.map((item) => `${item.id}:${item.sha256}`).sort().join('|')
|
|
3270
|
+
: 'none';
|
|
3271
|
+
const cacheKey = generateCacheKey({
|
|
3272
|
+
sampleSha256: sample.sha256,
|
|
3273
|
+
toolName: TOOL_NAME,
|
|
3274
|
+
toolVersion: TOOL_VERSION,
|
|
3275
|
+
args: {
|
|
3276
|
+
topk: input.topk,
|
|
3277
|
+
module_limit: input.module_limit,
|
|
3278
|
+
min_module_size: input.min_module_size,
|
|
3279
|
+
include_imports: input.include_imports,
|
|
3280
|
+
include_strings: input.include_strings,
|
|
3281
|
+
export_name: input.export_name || null,
|
|
3282
|
+
validate_build: input.validate_build,
|
|
3283
|
+
run_harness: input.run_harness,
|
|
3284
|
+
compiler_path: input.compiler_path || null,
|
|
3285
|
+
build_timeout_ms: input.build_timeout_ms,
|
|
3286
|
+
run_timeout_ms: input.run_timeout_ms,
|
|
3287
|
+
evidence_scope: input.evidence_scope,
|
|
3288
|
+
evidence_session_tag: input.evidence_session_tag || null,
|
|
3289
|
+
semantic_scope: input.semantic_scope,
|
|
3290
|
+
semantic_session_tag: input.semantic_session_tag || null,
|
|
3291
|
+
analysis_marker: analysisMarker,
|
|
3292
|
+
runtime_marker: runtimeMarker,
|
|
3293
|
+
semantic_name_marker: semanticNameMarker,
|
|
3294
|
+
semantic_explanation_marker: semanticExplanationMarker,
|
|
3295
|
+
ghidra_valid: ghidraConfig.isValid,
|
|
3296
|
+
ghidra_install_dir: ghidraConfig.installDir || 'none',
|
|
3297
|
+
ghidra_version: ghidraConfig.version || 'unknown',
|
|
3298
|
+
},
|
|
3299
|
+
});
|
|
3300
|
+
if (input.reuse_cached) {
|
|
3301
|
+
const cachedLookup = await lookupCachedResult(cacheManager, cacheKey);
|
|
3302
|
+
if (cachedLookup) {
|
|
3303
|
+
return {
|
|
3304
|
+
ok: true,
|
|
3305
|
+
data: cachedLookup.data,
|
|
3306
|
+
warnings: ['Result from cache', formatCacheWarning(cachedLookup.metadata)],
|
|
3307
|
+
metrics: {
|
|
3308
|
+
elapsed_ms: Date.now() - startTime,
|
|
3309
|
+
tool: TOOL_NAME,
|
|
3310
|
+
cached: true,
|
|
3311
|
+
cache_key: cachedLookup.metadata.key,
|
|
3312
|
+
cache_tier: cachedLookup.metadata.tier,
|
|
3313
|
+
cache_created_at: cachedLookup.metadata.createdAt,
|
|
3314
|
+
cache_expires_at: cachedLookup.metadata.expiresAt,
|
|
3315
|
+
cache_hit_at: cachedLookup.metadata.fetchedAt,
|
|
3316
|
+
},
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
const warnings = [];
|
|
3321
|
+
const dynamicEvidence = await runtimeEvidenceLoader(input.sample_id, {
|
|
3322
|
+
evidenceScope: input.evidence_scope,
|
|
3323
|
+
sessionTag: input.evidence_session_tag,
|
|
3324
|
+
});
|
|
3325
|
+
const semanticNameIndex = await loadSemanticNameSuggestionIndex(workspaceManager, database, input.sample_id, {
|
|
3326
|
+
scope: input.semantic_scope,
|
|
3327
|
+
sessionTag: input.semantic_session_tag,
|
|
3328
|
+
});
|
|
3329
|
+
const reconstructResult = await reconstructFunctionsHandler({
|
|
3330
|
+
sample_id: input.sample_id,
|
|
3331
|
+
topk: input.topk,
|
|
3332
|
+
include_xrefs: true,
|
|
3333
|
+
evidence_scope: input.evidence_scope,
|
|
3334
|
+
evidence_session_tag: input.evidence_session_tag,
|
|
3335
|
+
semantic_scope: input.semantic_scope,
|
|
3336
|
+
semantic_session_tag: input.semantic_session_tag,
|
|
3337
|
+
});
|
|
3338
|
+
if (!reconstructResult.ok || !reconstructResult.data) {
|
|
3339
|
+
return {
|
|
3340
|
+
ok: false,
|
|
3341
|
+
errors: reconstructResult.errors || ['Failed to reconstruct functions'],
|
|
3342
|
+
warnings: reconstructResult.warnings,
|
|
3343
|
+
metrics: {
|
|
3344
|
+
elapsed_ms: Date.now() - startTime,
|
|
3345
|
+
tool: TOOL_NAME,
|
|
3346
|
+
},
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
if (reconstructResult.warnings && reconstructResult.warnings.length > 0) {
|
|
3350
|
+
warnings.push(...reconstructResult.warnings.map((item) => `reconstruct: ${item}`));
|
|
3351
|
+
}
|
|
3352
|
+
const reconstructedData = reconstructResult.data;
|
|
3353
|
+
const reconstructedFunctions = dedupeReconstructedFunctions(enrichFunctionsWithRuntimeContext(reconstructedData.functions || [], dynamicEvidence).map((func) => ensureNameResolution(func)));
|
|
3354
|
+
if (reconstructedFunctions.length === 0) {
|
|
3355
|
+
return {
|
|
3356
|
+
ok: false,
|
|
3357
|
+
errors: ['No reconstructed functions available. Run ghidra.analyze and retry.'],
|
|
3358
|
+
metrics: {
|
|
3359
|
+
elapsed_ms: Date.now() - startTime,
|
|
3360
|
+
tool: TOOL_NAME,
|
|
3361
|
+
},
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
let importsData;
|
|
3365
|
+
if (input.include_imports) {
|
|
3366
|
+
const importsResult = await importsExtractHandler({
|
|
3367
|
+
sample_id: input.sample_id,
|
|
3368
|
+
group_by_dll: true,
|
|
3369
|
+
});
|
|
3370
|
+
if (importsResult.ok && importsResult.data) {
|
|
3371
|
+
importsData = importsResult.data;
|
|
3372
|
+
}
|
|
3373
|
+
else {
|
|
3374
|
+
warnings.push(`imports unavailable: ${(importsResult.errors || ['unknown error']).join('; ')}`);
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
let exportsData;
|
|
3378
|
+
const exportsResult = await exportsExtractHandler({
|
|
3379
|
+
sample_id: input.sample_id,
|
|
3380
|
+
});
|
|
3381
|
+
if (exportsResult.ok && exportsResult.data) {
|
|
3382
|
+
exportsData = exportsResult.data;
|
|
3383
|
+
}
|
|
3384
|
+
else {
|
|
3385
|
+
warnings.push(`exports unavailable: ${(exportsResult.errors || ['unknown error']).join('; ')}`);
|
|
3386
|
+
}
|
|
3387
|
+
let packerData;
|
|
3388
|
+
const packerResult = await packerDetectHandler({
|
|
3389
|
+
sample_id: input.sample_id,
|
|
3390
|
+
});
|
|
3391
|
+
if (packerResult.ok && packerResult.data) {
|
|
3392
|
+
packerData = packerResult.data;
|
|
3393
|
+
}
|
|
3394
|
+
else {
|
|
3395
|
+
warnings.push(`packer unavailable: ${(packerResult.errors || ['unknown error']).join('; ')}`);
|
|
3396
|
+
}
|
|
3397
|
+
let stringsData;
|
|
3398
|
+
if (input.include_strings) {
|
|
3399
|
+
const stringsResult = await stringsExtractHandler({
|
|
3400
|
+
sample_id: input.sample_id,
|
|
3401
|
+
min_len: 6,
|
|
3402
|
+
max_strings: 350,
|
|
3403
|
+
context_window_bytes: 2048,
|
|
3404
|
+
max_context_windows: 20,
|
|
3405
|
+
category_filter: 'all',
|
|
3406
|
+
});
|
|
3407
|
+
if (stringsResult.ok && stringsResult.data) {
|
|
3408
|
+
stringsData = stringsResult.data;
|
|
3409
|
+
}
|
|
3410
|
+
else {
|
|
3411
|
+
warnings.push(`strings unavailable: ${(stringsResult.errors || ['unknown error']).join('; ')}`);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
const functionStringHints = await buildFunctionStringSearchHints(input.sample_id, stringsData, searchFunctions, warnings, Boolean(decompileReadyAnalysis));
|
|
3415
|
+
const mergedFunctions = [...reconstructedFunctions];
|
|
3416
|
+
const knownAddresses = new Set(mergedFunctions.map((item) => item.address));
|
|
3417
|
+
const supplementalAddresses = Array.from(functionStringHints.keys())
|
|
3418
|
+
.filter((address) => !knownAddresses.has(address))
|
|
3419
|
+
.slice(0, 4);
|
|
3420
|
+
for (const address of supplementalAddresses) {
|
|
3421
|
+
const supplemental = await reconstructFunctionsHandler({
|
|
3422
|
+
sample_id: input.sample_id,
|
|
3423
|
+
address,
|
|
3424
|
+
include_xrefs: true,
|
|
3425
|
+
evidence_scope: input.evidence_scope,
|
|
3426
|
+
evidence_session_tag: input.evidence_session_tag,
|
|
3427
|
+
semantic_scope: input.semantic_scope,
|
|
3428
|
+
semantic_session_tag: input.semantic_session_tag,
|
|
3429
|
+
});
|
|
3430
|
+
if (!supplemental.ok || !supplemental.data) {
|
|
3431
|
+
warnings.push(`supplemental reconstruct unavailable for ${address}: ${(supplemental.errors || ['unknown error']).join('; ')}`);
|
|
3432
|
+
continue;
|
|
3433
|
+
}
|
|
3434
|
+
if (supplemental.warnings && supplemental.warnings.length > 0) {
|
|
3435
|
+
warnings.push(...supplemental.warnings.map((item) => `supplemental: ${item}`));
|
|
3436
|
+
}
|
|
3437
|
+
for (const func of supplemental.data.functions || []) {
|
|
3438
|
+
if (knownAddresses.has(func.address)) {
|
|
3439
|
+
continue;
|
|
3440
|
+
}
|
|
3441
|
+
const [enriched] = enrichFunctionsWithRuntimeContext([func], dynamicEvidence).map((item) => ensureNameResolution(item));
|
|
3442
|
+
mergedFunctions.push(enriched);
|
|
3443
|
+
knownAddresses.add(func.address);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
const normalizedFunctions = dedupeReconstructedFunctions(mergedFunctions);
|
|
3447
|
+
const explanationIndex = await attachFunctionExplanations(workspaceManager, database, input.sample_id, normalizedFunctions, {
|
|
3448
|
+
scope: input.semantic_scope,
|
|
3449
|
+
sessionTag: input.semantic_session_tag,
|
|
3450
|
+
});
|
|
3451
|
+
const provenance = {
|
|
3452
|
+
runtime: buildRuntimeArtifactProvenance(dynamicEvidence, input.evidence_scope, input.evidence_session_tag),
|
|
3453
|
+
semantic_names: buildSemanticArtifactProvenance('semantic naming artifacts', semanticNameIndex, input.semantic_scope, input.semantic_session_tag),
|
|
3454
|
+
semantic_explanations: buildSemanticArtifactProvenance('semantic explanation artifacts', explanationIndex, input.semantic_scope, input.semantic_session_tag),
|
|
3455
|
+
};
|
|
3456
|
+
const modules = regroupModules(normalizedFunctions, input.module_limit, input.min_module_size, importsData, stringsData, functionStringHints);
|
|
3457
|
+
const workspace = await workspaceManager.getWorkspace(input.sample_id);
|
|
3458
|
+
const originalEntries = await fs.readdir(workspace.original);
|
|
3459
|
+
const originalFilename = originalEntries.length > 0 ? originalEntries[0] : null;
|
|
3460
|
+
const exportFolderName = input.export_name ||
|
|
3461
|
+
`export_${new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').replace('Z', '')}`;
|
|
3462
|
+
const exportRoot = path.join(workspace.reports, 'reconstruct', exportFolderName);
|
|
3463
|
+
const srcRoot = path.join(exportRoot, 'src');
|
|
3464
|
+
const supportHeaderPath = path.join(srcRoot, 'reconstruct_support.h');
|
|
3465
|
+
const harnessPath = path.join(srcRoot, 'reconstruct_harness.c');
|
|
3466
|
+
const buildManifestPath = path.join(exportRoot, 'CMakeLists.txt');
|
|
3467
|
+
const cliModelPath = path.join(exportRoot, 'cli_model.json');
|
|
3468
|
+
await fs.mkdir(srcRoot, { recursive: true });
|
|
3469
|
+
await fs.writeFile(supportHeaderPath, buildSupportHeaderContent(modules), 'utf-8');
|
|
3470
|
+
const outputModules = [];
|
|
3471
|
+
const artifacts = [];
|
|
3472
|
+
const moduleRewriteFiles = [];
|
|
3473
|
+
for (const module of modules) {
|
|
3474
|
+
const safeName = sanitizeModuleName(module.name);
|
|
3475
|
+
const interfaceFile = path.join(srcRoot, `${safeName}.interface.h`);
|
|
3476
|
+
const pseudoFile = path.join(srcRoot, `${safeName}.pseudo.c`);
|
|
3477
|
+
const rewriteFile = path.join(srcRoot, `${safeName}.rewrite.c`);
|
|
3478
|
+
await fs.writeFile(interfaceFile, buildInterfaceContent(module), 'utf-8');
|
|
3479
|
+
await fs.writeFile(pseudoFile, buildPseudocodeContent(module), 'utf-8');
|
|
3480
|
+
await fs.writeFile(rewriteFile, buildAnnotatedRewriteContent(module), 'utf-8');
|
|
3481
|
+
moduleRewriteFiles.push(rewriteFile);
|
|
3482
|
+
outputModules.push({
|
|
3483
|
+
name: safeName,
|
|
3484
|
+
confidence: averageConfidence(module.functions),
|
|
3485
|
+
function_count: module.functions.length,
|
|
3486
|
+
import_hints: Array.from(module.importHints).slice(0, 10),
|
|
3487
|
+
string_hints: Array.from(module.stringHints).slice(0, 10),
|
|
3488
|
+
runtime_apis: Array.from(module.runtimeApis).slice(0, 10),
|
|
3489
|
+
runtime_stages: Array.from(module.runtimeStages).slice(0, 10),
|
|
3490
|
+
interface_path: toPosixRelative(workspace.root, interfaceFile),
|
|
3491
|
+
pseudocode_path: toPosixRelative(workspace.root, pseudoFile),
|
|
3492
|
+
rewrite_path: toPosixRelative(workspace.root, rewriteFile),
|
|
3493
|
+
functions: module.functions.map((func) => ({
|
|
3494
|
+
function: func.function,
|
|
3495
|
+
address: func.address,
|
|
3496
|
+
confidence: func.confidence,
|
|
3497
|
+
gaps: func.gaps,
|
|
3498
|
+
suggested_name: func.suggested_name || null,
|
|
3499
|
+
suggested_role: func.suggested_role || null,
|
|
3500
|
+
rename_confidence: typeof func.rename_confidence === 'number' ? func.rename_confidence : null,
|
|
3501
|
+
rule_based_name: func.name_resolution?.rule_based_name || null,
|
|
3502
|
+
llm_suggested_name: func.name_resolution?.llm_suggested_name || null,
|
|
3503
|
+
validated_name: func.name_resolution?.validated_name || func.suggested_name || null,
|
|
3504
|
+
name_resolution_source: func.name_resolution?.resolution_source || null,
|
|
3505
|
+
explanation_summary: func.explanation_resolution?.summary || null,
|
|
3506
|
+
explanation_behavior: func.explanation_resolution?.behavior || null,
|
|
3507
|
+
explanation_confidence: typeof func.explanation_resolution?.confidence === 'number'
|
|
3508
|
+
? func.explanation_resolution.confidence
|
|
3509
|
+
: null,
|
|
3510
|
+
})),
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
const cliModels = buildExportCliModels(modules);
|
|
3514
|
+
const cliProfile = buildReconstructCliProfile(modules);
|
|
3515
|
+
await fs.writeFile(harnessPath, buildHarnessContent(modules), 'utf-8');
|
|
3516
|
+
await fs.writeFile(buildManifestPath, buildCMakeContent(modules), 'utf-8');
|
|
3517
|
+
await fs.writeFile(cliModelPath, JSON.stringify(cliModels, null, 2), 'utf-8');
|
|
3518
|
+
let buildValidation = {
|
|
3519
|
+
attempted: false,
|
|
3520
|
+
status: 'skipped',
|
|
3521
|
+
compiler: null,
|
|
3522
|
+
compiler_path: null,
|
|
3523
|
+
command: null,
|
|
3524
|
+
exit_code: null,
|
|
3525
|
+
timed_out: false,
|
|
3526
|
+
error: null,
|
|
3527
|
+
stdout: '',
|
|
3528
|
+
stderr: '',
|
|
3529
|
+
log_path: null,
|
|
3530
|
+
executable_path: null,
|
|
3531
|
+
};
|
|
3532
|
+
let harnessValidation = {
|
|
3533
|
+
attempted: false,
|
|
3534
|
+
status: 'skipped',
|
|
3535
|
+
command: null,
|
|
3536
|
+
exit_code: null,
|
|
3537
|
+
timed_out: false,
|
|
3538
|
+
error: null,
|
|
3539
|
+
stdout: '',
|
|
3540
|
+
stderr: '',
|
|
3541
|
+
log_path: null,
|
|
3542
|
+
matched_entries: 0,
|
|
3543
|
+
mismatched_entries: 0,
|
|
3544
|
+
};
|
|
3545
|
+
if (input.validate_build) {
|
|
3546
|
+
buildValidation = await runNativeBuild({
|
|
3547
|
+
exportRoot,
|
|
3548
|
+
srcRoot,
|
|
3549
|
+
moduleRewriteFiles,
|
|
3550
|
+
compilerPath: input.compiler_path || null,
|
|
3551
|
+
timeoutMs: input.build_timeout_ms,
|
|
3552
|
+
});
|
|
3553
|
+
if (buildValidation.status === 'passed' && input.run_harness && buildValidation.executable_path) {
|
|
3554
|
+
harnessValidation = await runHarness({
|
|
3555
|
+
executablePath: buildValidation.executable_path,
|
|
3556
|
+
cwd: exportRoot,
|
|
3557
|
+
timeoutMs: input.run_timeout_ms,
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
else if (input.run_harness) {
|
|
3561
|
+
harnessValidation = {
|
|
3562
|
+
attempted: false,
|
|
3563
|
+
status: buildValidation.status === 'unavailable' ? 'unavailable' : 'skipped',
|
|
3564
|
+
command: null,
|
|
3565
|
+
exit_code: null,
|
|
3566
|
+
timed_out: false,
|
|
3567
|
+
error: buildValidation.status === 'passed'
|
|
3568
|
+
? 'reconstruct_harness executable was not produced'
|
|
3569
|
+
: 'Build validation must pass before harness execution.',
|
|
3570
|
+
stdout: '',
|
|
3571
|
+
stderr: '',
|
|
3572
|
+
log_path: null,
|
|
3573
|
+
matched_entries: 0,
|
|
3574
|
+
mismatched_entries: 0,
|
|
3575
|
+
};
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
else if (input.run_harness) {
|
|
3579
|
+
harnessValidation = {
|
|
3580
|
+
attempted: false,
|
|
3581
|
+
status: 'skipped',
|
|
3582
|
+
command: null,
|
|
3583
|
+
exit_code: null,
|
|
3584
|
+
timed_out: false,
|
|
3585
|
+
error: 'Build validation is disabled; harness execution was skipped.',
|
|
3586
|
+
stdout: '',
|
|
3587
|
+
stderr: '',
|
|
3588
|
+
log_path: null,
|
|
3589
|
+
matched_entries: 0,
|
|
3590
|
+
mismatched_entries: 0,
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
if (input.validate_build && buildValidation.status !== 'passed') {
|
|
3594
|
+
warnings.push(`build_validation: ${buildValidation.error || buildValidation.status}`);
|
|
3595
|
+
}
|
|
3596
|
+
if (input.run_harness && harnessValidation.status === 'failed') {
|
|
3597
|
+
warnings.push(`harness_validation: ${harnessValidation.error || 'reconstruct_harness reported mismatches'}`);
|
|
3598
|
+
}
|
|
3599
|
+
const buildLogPath = path.join(exportRoot, 'BUILD_VALIDATION.log');
|
|
3600
|
+
const harnessLogPath = path.join(exportRoot, 'HARNESS_VALIDATION.log');
|
|
3601
|
+
buildValidation = {
|
|
3602
|
+
...buildValidation,
|
|
3603
|
+
log_path: buildLogPath,
|
|
3604
|
+
};
|
|
3605
|
+
harnessValidation = {
|
|
3606
|
+
...harnessValidation,
|
|
3607
|
+
log_path: harnessLogPath,
|
|
3608
|
+
};
|
|
3609
|
+
await fs.writeFile(buildLogPath, buildNativeValidationLog(buildValidation), 'utf-8');
|
|
3610
|
+
await fs.writeFile(harnessLogPath, buildHarnessValidationLog(harnessValidation), 'utf-8');
|
|
3611
|
+
const gapsContent = buildGapsMarkdown(modules, warnings, normalizedFunctions);
|
|
3612
|
+
const gapsPath = path.join(exportRoot, 'gaps.md');
|
|
3613
|
+
await fs.writeFile(gapsPath, gapsContent, 'utf-8');
|
|
3614
|
+
const binaryProfile = buildBinaryProfile(sample.file_type, originalFilename, exportsData, packerData, outputModules, cliProfile);
|
|
3615
|
+
const notesContent = buildReverseNotesMarkdown(binaryProfile, outputModules, warnings, dynamicEvidence, cliModels, buildValidation, harnessValidation);
|
|
3616
|
+
const notesPath = path.join(exportRoot, 'reverse_notes.md');
|
|
3617
|
+
await fs.writeFile(notesPath, notesContent, 'utf-8');
|
|
3618
|
+
const executableAbsolute = buildValidation.executable_path && path.isAbsolute(buildValidation.executable_path)
|
|
3619
|
+
? buildValidation.executable_path
|
|
3620
|
+
: buildValidation.executable_path
|
|
3621
|
+
? path.join(exportRoot, buildValidation.executable_path)
|
|
3622
|
+
: null;
|
|
3623
|
+
const buildLogRelative = toPosixRelative(workspace.root, buildLogPath);
|
|
3624
|
+
const harnessLogRelative = toPosixRelative(workspace.root, harnessLogPath);
|
|
3625
|
+
const executableRelative = executableAbsolute && (await pathExists(executableAbsolute))
|
|
3626
|
+
? toPosixRelative(workspace.root, executableAbsolute)
|
|
3627
|
+
: null;
|
|
3628
|
+
const buildValidationOutput = {
|
|
3629
|
+
...buildValidation,
|
|
3630
|
+
log_path: buildLogRelative,
|
|
3631
|
+
executable_path: executableRelative,
|
|
3632
|
+
};
|
|
3633
|
+
const harnessValidationOutput = {
|
|
3634
|
+
...harnessValidation,
|
|
3635
|
+
log_path: harnessLogRelative,
|
|
3636
|
+
};
|
|
3637
|
+
const manifest = {
|
|
3638
|
+
sample_id: input.sample_id,
|
|
3639
|
+
tool_version: TOOL_VERSION,
|
|
3640
|
+
exported_at: new Date().toISOString(),
|
|
3641
|
+
module_count: outputModules.length,
|
|
3642
|
+
topk: input.topk,
|
|
3643
|
+
module_limit: input.module_limit,
|
|
3644
|
+
min_module_size: input.min_module_size,
|
|
3645
|
+
include_imports: input.include_imports,
|
|
3646
|
+
include_strings: input.include_strings,
|
|
3647
|
+
validate_build: input.validate_build,
|
|
3648
|
+
run_harness: input.run_harness,
|
|
3649
|
+
warnings,
|
|
3650
|
+
binary_profile: binaryProfile,
|
|
3651
|
+
provenance,
|
|
3652
|
+
build_validation: buildValidationOutput,
|
|
3653
|
+
harness_validation: harnessValidationOutput,
|
|
3654
|
+
runtime_evidence: dynamicEvidence
|
|
3655
|
+
? {
|
|
3656
|
+
executed: dynamicEvidence.executed,
|
|
3657
|
+
api_count: dynamicEvidence.api_count,
|
|
3658
|
+
stage_count: dynamicEvidence.stage_count,
|
|
3659
|
+
observed_apis: dynamicEvidence.observed_apis.slice(0, 12),
|
|
3660
|
+
region_types: (dynamicEvidence.region_types || []).slice(0, 12),
|
|
3661
|
+
observed_modules: (dynamicEvidence.observed_modules || []).slice(0, 8),
|
|
3662
|
+
observed_strings: (dynamicEvidence.observed_strings || []).slice(0, 8),
|
|
3663
|
+
stages: dynamicEvidence.stages.slice(0, 12),
|
|
3664
|
+
summary: dynamicEvidence.summary,
|
|
3665
|
+
}
|
|
3666
|
+
: null,
|
|
3667
|
+
cli_models: cliModels,
|
|
3668
|
+
modules: outputModules.map((module) => ({
|
|
3669
|
+
name: module.name,
|
|
3670
|
+
confidence: module.confidence,
|
|
3671
|
+
function_count: module.function_count,
|
|
3672
|
+
interface_path: module.interface_path,
|
|
3673
|
+
pseudocode_path: module.pseudocode_path,
|
|
3674
|
+
rewrite_path: module.rewrite_path,
|
|
3675
|
+
import_hints: module.import_hints,
|
|
3676
|
+
string_hints: module.string_hints,
|
|
3677
|
+
runtime_apis: module.runtime_apis,
|
|
3678
|
+
runtime_stages: module.runtime_stages,
|
|
3679
|
+
})),
|
|
3680
|
+
files: {
|
|
3681
|
+
support_header: toPosixRelative(workspace.root, supportHeaderPath),
|
|
3682
|
+
harness: toPosixRelative(workspace.root, harnessPath),
|
|
3683
|
+
build_manifest: toPosixRelative(workspace.root, buildManifestPath),
|
|
3684
|
+
build_validation_log: buildLogRelative,
|
|
3685
|
+
harness_validation_log: harnessLogRelative,
|
|
3686
|
+
harness_binary: executableRelative,
|
|
3687
|
+
cli_model: toPosixRelative(workspace.root, cliModelPath),
|
|
3688
|
+
gaps: toPosixRelative(workspace.root, gapsPath),
|
|
3689
|
+
notes: toPosixRelative(workspace.root, notesPath),
|
|
3690
|
+
},
|
|
3691
|
+
};
|
|
3692
|
+
const manifestPath = path.join(exportRoot, 'manifest.json');
|
|
3693
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
3694
|
+
const manifestRelative = toPosixRelative(workspace.root, manifestPath);
|
|
3695
|
+
const supportHeaderRelative = toPosixRelative(workspace.root, supportHeaderPath);
|
|
3696
|
+
const harnessRelative = toPosixRelative(workspace.root, harnessPath);
|
|
3697
|
+
const buildManifestRelative = toPosixRelative(workspace.root, buildManifestPath);
|
|
3698
|
+
const cliModelRelative = toPosixRelative(workspace.root, cliModelPath);
|
|
3699
|
+
const gapsRelative = toPosixRelative(workspace.root, gapsPath);
|
|
3700
|
+
const notesRelative = toPosixRelative(workspace.root, notesPath);
|
|
3701
|
+
const executableRelativePath = buildValidationOutput.executable_path;
|
|
3702
|
+
const manifestSha = await sha256File(manifestPath);
|
|
3703
|
+
const supportHeaderSha = await sha256File(supportHeaderPath);
|
|
3704
|
+
const harnessSha = await sha256File(harnessPath);
|
|
3705
|
+
const buildManifestSha = await sha256File(buildManifestPath);
|
|
3706
|
+
const buildLogSha = await sha256File(buildLogPath);
|
|
3707
|
+
const harnessLogSha = await sha256File(harnessLogPath);
|
|
3708
|
+
const cliModelSha = await sha256File(cliModelPath);
|
|
3709
|
+
const gapsSha = await sha256File(gapsPath);
|
|
3710
|
+
const notesSha = await sha256File(notesPath);
|
|
3711
|
+
const manifestArtifactId = randomUUID();
|
|
3712
|
+
database.insertArtifact({
|
|
3713
|
+
id: manifestArtifactId,
|
|
3714
|
+
sample_id: input.sample_id,
|
|
3715
|
+
type: 'reconstruct_manifest',
|
|
3716
|
+
path: manifestRelative,
|
|
3717
|
+
sha256: manifestSha,
|
|
3718
|
+
mime: 'application/json',
|
|
3719
|
+
created_at: new Date().toISOString(),
|
|
3720
|
+
});
|
|
3721
|
+
artifacts.push({
|
|
3722
|
+
id: manifestArtifactId,
|
|
3723
|
+
type: 'reconstruct_manifest',
|
|
3724
|
+
path: manifestRelative,
|
|
3725
|
+
sha256: manifestSha,
|
|
3726
|
+
mime: 'application/json',
|
|
3727
|
+
});
|
|
3728
|
+
const supportHeaderArtifactId = randomUUID();
|
|
3729
|
+
database.insertArtifact({
|
|
3730
|
+
id: supportHeaderArtifactId,
|
|
3731
|
+
sample_id: input.sample_id,
|
|
3732
|
+
type: 'reconstruct_support_header',
|
|
3733
|
+
path: supportHeaderRelative,
|
|
3734
|
+
sha256: supportHeaderSha,
|
|
3735
|
+
mime: 'text/x-c',
|
|
3736
|
+
created_at: new Date().toISOString(),
|
|
3737
|
+
});
|
|
3738
|
+
artifacts.push({
|
|
3739
|
+
id: supportHeaderArtifactId,
|
|
3740
|
+
type: 'reconstruct_support_header',
|
|
3741
|
+
path: supportHeaderRelative,
|
|
3742
|
+
sha256: supportHeaderSha,
|
|
3743
|
+
mime: 'text/x-c',
|
|
3744
|
+
});
|
|
3745
|
+
const harnessArtifactId = randomUUID();
|
|
3746
|
+
database.insertArtifact({
|
|
3747
|
+
id: harnessArtifactId,
|
|
3748
|
+
sample_id: input.sample_id,
|
|
3749
|
+
type: 'reconstruct_harness',
|
|
3750
|
+
path: harnessRelative,
|
|
3751
|
+
sha256: harnessSha,
|
|
3752
|
+
mime: 'text/x-c',
|
|
3753
|
+
created_at: new Date().toISOString(),
|
|
3754
|
+
});
|
|
3755
|
+
artifacts.push({
|
|
3756
|
+
id: harnessArtifactId,
|
|
3757
|
+
type: 'reconstruct_harness',
|
|
3758
|
+
path: harnessRelative,
|
|
3759
|
+
sha256: harnessSha,
|
|
3760
|
+
mime: 'text/x-c',
|
|
3761
|
+
});
|
|
3762
|
+
const buildManifestArtifactId = randomUUID();
|
|
3763
|
+
database.insertArtifact({
|
|
3764
|
+
id: buildManifestArtifactId,
|
|
3765
|
+
sample_id: input.sample_id,
|
|
3766
|
+
type: 'reconstruct_build_manifest',
|
|
3767
|
+
path: buildManifestRelative,
|
|
3768
|
+
sha256: buildManifestSha,
|
|
3769
|
+
mime: 'text/plain',
|
|
3770
|
+
created_at: new Date().toISOString(),
|
|
3771
|
+
});
|
|
3772
|
+
artifacts.push({
|
|
3773
|
+
id: buildManifestArtifactId,
|
|
3774
|
+
type: 'reconstruct_build_manifest',
|
|
3775
|
+
path: buildManifestRelative,
|
|
3776
|
+
sha256: buildManifestSha,
|
|
3777
|
+
mime: 'text/plain',
|
|
3778
|
+
});
|
|
3779
|
+
const buildLogArtifactId = randomUUID();
|
|
3780
|
+
database.insertArtifact({
|
|
3781
|
+
id: buildLogArtifactId,
|
|
3782
|
+
sample_id: input.sample_id,
|
|
3783
|
+
type: 'reconstruct_build_log',
|
|
3784
|
+
path: buildLogRelative,
|
|
3785
|
+
sha256: buildLogSha,
|
|
3786
|
+
mime: 'text/markdown',
|
|
3787
|
+
created_at: new Date().toISOString(),
|
|
3788
|
+
});
|
|
3789
|
+
artifacts.push({
|
|
3790
|
+
id: buildLogArtifactId,
|
|
3791
|
+
type: 'reconstruct_build_log',
|
|
3792
|
+
path: buildLogRelative,
|
|
3793
|
+
sha256: buildLogSha,
|
|
3794
|
+
mime: 'text/markdown',
|
|
3795
|
+
});
|
|
3796
|
+
const harnessLogArtifactId = randomUUID();
|
|
3797
|
+
database.insertArtifact({
|
|
3798
|
+
id: harnessLogArtifactId,
|
|
3799
|
+
sample_id: input.sample_id,
|
|
3800
|
+
type: 'reconstruct_run_log',
|
|
3801
|
+
path: harnessLogRelative,
|
|
3802
|
+
sha256: harnessLogSha,
|
|
3803
|
+
mime: 'text/markdown',
|
|
3804
|
+
created_at: new Date().toISOString(),
|
|
3805
|
+
});
|
|
3806
|
+
artifacts.push({
|
|
3807
|
+
id: harnessLogArtifactId,
|
|
3808
|
+
type: 'reconstruct_run_log',
|
|
3809
|
+
path: harnessLogRelative,
|
|
3810
|
+
sha256: harnessLogSha,
|
|
3811
|
+
mime: 'text/markdown',
|
|
3812
|
+
});
|
|
3813
|
+
const cliModelArtifactId = randomUUID();
|
|
3814
|
+
database.insertArtifact({
|
|
3815
|
+
id: cliModelArtifactId,
|
|
3816
|
+
sample_id: input.sample_id,
|
|
3817
|
+
type: 'reconstruct_cli_model',
|
|
3818
|
+
path: cliModelRelative,
|
|
3819
|
+
sha256: cliModelSha,
|
|
3820
|
+
mime: 'application/json',
|
|
3821
|
+
created_at: new Date().toISOString(),
|
|
3822
|
+
});
|
|
3823
|
+
artifacts.push({
|
|
3824
|
+
id: cliModelArtifactId,
|
|
3825
|
+
type: 'reconstruct_cli_model',
|
|
3826
|
+
path: cliModelRelative,
|
|
3827
|
+
sha256: cliModelSha,
|
|
3828
|
+
mime: 'application/json',
|
|
3829
|
+
});
|
|
3830
|
+
const gapsArtifactId = randomUUID();
|
|
3831
|
+
database.insertArtifact({
|
|
3832
|
+
id: gapsArtifactId,
|
|
3833
|
+
sample_id: input.sample_id,
|
|
3834
|
+
type: 'reconstruct_gaps',
|
|
3835
|
+
path: gapsRelative,
|
|
3836
|
+
sha256: gapsSha,
|
|
3837
|
+
mime: 'text/markdown',
|
|
3838
|
+
created_at: new Date().toISOString(),
|
|
3839
|
+
});
|
|
3840
|
+
artifacts.push({
|
|
3841
|
+
id: gapsArtifactId,
|
|
3842
|
+
type: 'reconstruct_gaps',
|
|
3843
|
+
path: gapsRelative,
|
|
3844
|
+
sha256: gapsSha,
|
|
3845
|
+
mime: 'text/markdown',
|
|
3846
|
+
});
|
|
3847
|
+
const notesArtifactId = randomUUID();
|
|
3848
|
+
database.insertArtifact({
|
|
3849
|
+
id: notesArtifactId,
|
|
3850
|
+
sample_id: input.sample_id,
|
|
3851
|
+
type: 'reconstruct_notes',
|
|
3852
|
+
path: notesRelative,
|
|
3853
|
+
sha256: notesSha,
|
|
3854
|
+
mime: 'text/markdown',
|
|
3855
|
+
created_at: new Date().toISOString(),
|
|
3856
|
+
});
|
|
3857
|
+
artifacts.push({
|
|
3858
|
+
id: notesArtifactId,
|
|
3859
|
+
type: 'reconstruct_notes',
|
|
3860
|
+
path: notesRelative,
|
|
3861
|
+
sha256: notesSha,
|
|
3862
|
+
mime: 'text/markdown',
|
|
3863
|
+
});
|
|
3864
|
+
if (executableAbsolute && executableRelativePath && (await pathExists(executableAbsolute))) {
|
|
3865
|
+
const executableSha = await sha256File(executableAbsolute);
|
|
3866
|
+
const executableArtifactId = randomUUID();
|
|
3867
|
+
database.insertArtifact({
|
|
3868
|
+
id: executableArtifactId,
|
|
3869
|
+
sample_id: input.sample_id,
|
|
3870
|
+
type: 'reconstruct_harness_binary',
|
|
3871
|
+
path: executableRelativePath,
|
|
3872
|
+
sha256: executableSha,
|
|
3873
|
+
mime: 'application/vnd.microsoft.portable-executable',
|
|
3874
|
+
created_at: new Date().toISOString(),
|
|
3875
|
+
});
|
|
3876
|
+
artifacts.push({
|
|
3877
|
+
id: executableArtifactId,
|
|
3878
|
+
type: 'reconstruct_harness_binary',
|
|
3879
|
+
path: executableRelativePath,
|
|
3880
|
+
sha256: executableSha,
|
|
3881
|
+
mime: 'application/vnd.microsoft.portable-executable',
|
|
3882
|
+
});
|
|
3883
|
+
}
|
|
3884
|
+
for (const module of outputModules) {
|
|
3885
|
+
const interfaceRelative = module.interface_path;
|
|
3886
|
+
const pseudocodeRelative = module.pseudocode_path;
|
|
3887
|
+
const rewriteRelative = module.rewrite_path;
|
|
3888
|
+
const interfaceAbs = workspaceManager.normalizePath(workspace.root, interfaceRelative);
|
|
3889
|
+
const pseudocodeAbs = workspaceManager.normalizePath(workspace.root, pseudocodeRelative);
|
|
3890
|
+
const rewriteAbs = workspaceManager.normalizePath(workspace.root, rewriteRelative);
|
|
3891
|
+
const interfaceSha = await sha256File(interfaceAbs);
|
|
3892
|
+
const pseudocodeSha = await sha256File(pseudocodeAbs);
|
|
3893
|
+
const rewriteSha = await sha256File(rewriteAbs);
|
|
3894
|
+
const interfaceArtifactId = randomUUID();
|
|
3895
|
+
database.insertArtifact({
|
|
3896
|
+
id: interfaceArtifactId,
|
|
3897
|
+
sample_id: input.sample_id,
|
|
3898
|
+
type: 'report',
|
|
3899
|
+
path: interfaceRelative,
|
|
3900
|
+
sha256: interfaceSha,
|
|
3901
|
+
mime: 'text/plain',
|
|
3902
|
+
created_at: new Date().toISOString(),
|
|
3903
|
+
});
|
|
3904
|
+
artifacts.push({
|
|
3905
|
+
id: interfaceArtifactId,
|
|
3906
|
+
type: 'report',
|
|
3907
|
+
path: interfaceRelative,
|
|
3908
|
+
sha256: interfaceSha,
|
|
3909
|
+
mime: 'text/plain',
|
|
3910
|
+
});
|
|
3911
|
+
const pseudocodeArtifactId = randomUUID();
|
|
3912
|
+
database.insertArtifact({
|
|
3913
|
+
id: pseudocodeArtifactId,
|
|
3914
|
+
sample_id: input.sample_id,
|
|
3915
|
+
type: 'ghidra_pseudocode',
|
|
3916
|
+
path: pseudocodeRelative,
|
|
3917
|
+
sha256: pseudocodeSha,
|
|
3918
|
+
mime: 'text/x-c',
|
|
3919
|
+
created_at: new Date().toISOString(),
|
|
3920
|
+
});
|
|
3921
|
+
artifacts.push({
|
|
3922
|
+
id: pseudocodeArtifactId,
|
|
3923
|
+
type: 'ghidra_pseudocode',
|
|
3924
|
+
path: pseudocodeRelative,
|
|
3925
|
+
sha256: pseudocodeSha,
|
|
3926
|
+
mime: 'text/x-c',
|
|
3927
|
+
});
|
|
3928
|
+
const rewriteArtifactId = randomUUID();
|
|
3929
|
+
database.insertArtifact({
|
|
3930
|
+
id: rewriteArtifactId,
|
|
3931
|
+
sample_id: input.sample_id,
|
|
3932
|
+
type: 'reconstruct_rewrite',
|
|
3933
|
+
path: rewriteRelative,
|
|
3934
|
+
sha256: rewriteSha,
|
|
3935
|
+
mime: 'text/x-c',
|
|
3936
|
+
created_at: new Date().toISOString(),
|
|
3937
|
+
});
|
|
3938
|
+
artifacts.push({
|
|
3939
|
+
id: rewriteArtifactId,
|
|
3940
|
+
type: 'reconstruct_rewrite',
|
|
3941
|
+
path: rewriteRelative,
|
|
3942
|
+
sha256: rewriteSha,
|
|
3943
|
+
mime: 'text/x-c',
|
|
3944
|
+
});
|
|
3945
|
+
}
|
|
3946
|
+
const unresolvedCount = normalizedFunctions.filter((func) => func.gaps.length > 0).length;
|
|
3947
|
+
const outputData = {
|
|
3948
|
+
sample_id: input.sample_id,
|
|
3949
|
+
export_root: toPosixRelative(workspace.root, exportRoot),
|
|
3950
|
+
manifest_path: manifestRelative,
|
|
3951
|
+
gaps_path: gapsRelative,
|
|
3952
|
+
notes_path: notesRelative,
|
|
3953
|
+
cli_model_path: cliModelRelative,
|
|
3954
|
+
support_header_path: supportHeaderRelative,
|
|
3955
|
+
harness_path: harnessRelative,
|
|
3956
|
+
build_manifest_path: buildManifestRelative,
|
|
3957
|
+
build_validation: buildValidationOutput,
|
|
3958
|
+
harness_validation: harnessValidationOutput,
|
|
3959
|
+
module_count: outputModules.length,
|
|
3960
|
+
unresolved_count: unresolvedCount,
|
|
3961
|
+
binary_profile: binaryProfile,
|
|
3962
|
+
runtime_evidence: dynamicEvidence
|
|
3963
|
+
? {
|
|
3964
|
+
executed: dynamicEvidence.executed,
|
|
3965
|
+
api_count: dynamicEvidence.api_count,
|
|
3966
|
+
stage_count: dynamicEvidence.stage_count,
|
|
3967
|
+
observed_apis: dynamicEvidence.observed_apis.slice(0, 12),
|
|
3968
|
+
region_types: (dynamicEvidence.region_types || []).slice(0, 12),
|
|
3969
|
+
observed_modules: (dynamicEvidence.observed_modules || []).slice(0, 8),
|
|
3970
|
+
observed_strings: (dynamicEvidence.observed_strings || []).slice(0, 8),
|
|
3971
|
+
stages: dynamicEvidence.stages.slice(0, 12),
|
|
3972
|
+
summary: dynamicEvidence.summary,
|
|
3973
|
+
}
|
|
3974
|
+
: null,
|
|
3975
|
+
provenance,
|
|
3976
|
+
modules: outputModules,
|
|
3977
|
+
};
|
|
3978
|
+
await cacheManager.setCachedResult(cacheKey, outputData, CACHE_TTL_MS, sample.sha256);
|
|
3979
|
+
return {
|
|
3980
|
+
ok: true,
|
|
3981
|
+
data: outputData,
|
|
3982
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
3983
|
+
artifacts,
|
|
3984
|
+
metrics: {
|
|
3985
|
+
elapsed_ms: Date.now() - startTime,
|
|
3986
|
+
tool: TOOL_NAME,
|
|
3987
|
+
},
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
catch (error) {
|
|
3991
|
+
return {
|
|
3992
|
+
ok: false,
|
|
3993
|
+
errors: [normalizeError(error)],
|
|
3994
|
+
metrics: {
|
|
3995
|
+
elapsed_ms: Date.now() - startTime,
|
|
3996
|
+
tool: TOOL_NAME,
|
|
3997
|
+
},
|
|
3998
|
+
};
|
|
3999
|
+
}
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
4002
|
+
//# sourceMappingURL=code-reconstruct-export.js.map
|