windows-exe-decompiler-mcp-server 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CLAUDE_INSTALLATION.md +129 -0
  2. package/CODEX_INSTALLATION.md +25 -1
  3. package/COPILOT_INSTALLATION.md +29 -1
  4. package/LICENSE +21 -21
  5. package/README.md +232 -110
  6. package/dist/analysis-provenance.d.ts +50 -2
  7. package/dist/analysis-provenance.js +1 -0
  8. package/dist/analysis-task-runner.js +92 -0
  9. package/dist/config.d.ts +35 -16
  10. package/dist/config.js +37 -3
  11. package/dist/database.js +115 -115
  12. package/dist/decompiler-worker.d.ts +10 -5
  13. package/dist/decompiler-worker.js +240 -58
  14. package/dist/dynamic-trace.d.ts +6 -0
  15. package/dist/dynamic-trace.js +52 -1
  16. package/dist/ghidra-config.js +17 -1
  17. package/dist/index.js +38 -8
  18. package/dist/pe-runtime-functions.d.ts +90 -0
  19. package/dist/pe-runtime-functions.js +452 -0
  20. package/dist/prompts/module-reconstruction-review.d.ts +5 -0
  21. package/dist/prompts/module-reconstruction-review.js +65 -0
  22. package/dist/runtime-correlation.d.ts +5 -0
  23. package/dist/runtime-correlation.js +179 -2
  24. package/dist/sample-workspace.d.ts +28 -0
  25. package/dist/sample-workspace.js +186 -0
  26. package/dist/semantic-name-suggestion-artifacts.d.ts +56 -0
  27. package/dist/semantic-name-suggestion-artifacts.js +87 -0
  28. package/dist/server.js +1 -1
  29. package/dist/setup-guidance.d.ts +73 -0
  30. package/dist/setup-guidance.js +211 -0
  31. package/dist/tools/artifact-read.d.ts +10 -10
  32. package/dist/tools/artifacts-diff.d.ts +8 -8
  33. package/dist/tools/artifacts-list.d.ts +16 -16
  34. package/dist/tools/attack-map.d.ts +8 -8
  35. package/dist/tools/binary-role-profile.d.ts +1197 -0
  36. package/dist/tools/binary-role-profile.js +575 -0
  37. package/dist/tools/code-function-explain-apply.d.ts +12 -12
  38. package/dist/tools/code-function-explain-prepare.d.ts +12 -12
  39. package/dist/tools/code-function-explain-review.d.ts +58 -58
  40. package/dist/tools/code-function-rename-apply.d.ts +12 -12
  41. package/dist/tools/code-function-rename-prepare.d.ts +14 -14
  42. package/dist/tools/code-function-rename-review.d.ts +66 -66
  43. package/dist/tools/code-functions-define.d.ts +337 -0
  44. package/dist/tools/code-functions-define.js +337 -0
  45. package/dist/tools/code-functions-list.js +1 -1
  46. package/dist/tools/code-functions-rank.js +1 -1
  47. package/dist/tools/code-functions-reconstruct.d.ts +368 -58
  48. package/dist/tools/code-functions-reconstruct.js +130 -10
  49. package/dist/tools/code-functions-smart-recover.d.ts +352 -0
  50. package/dist/tools/code-functions-smart-recover.js +190 -0
  51. package/dist/tools/code-module-review-apply.d.ts +233 -0
  52. package/dist/tools/code-module-review-apply.js +209 -0
  53. package/dist/tools/code-module-review-prepare.d.ts +756 -0
  54. package/dist/tools/code-module-review-prepare.js +343 -0
  55. package/dist/tools/code-module-review.d.ts +474 -0
  56. package/dist/tools/code-module-review.js +566 -0
  57. package/dist/tools/code-reconstruct-export.d.ts +193 -21
  58. package/dist/tools/code-reconstruct-export.js +578 -21
  59. package/dist/tools/code-reconstruct-plan.d.ts +20 -20
  60. package/dist/tools/com-role-profile.d.ts +353 -0
  61. package/dist/tools/com-role-profile.js +186 -0
  62. package/dist/tools/dll-export-profile.d.ts +625 -0
  63. package/dist/tools/dll-export-profile.js +230 -0
  64. package/dist/tools/dotnet-metadata-extract.d.ts +122 -122
  65. package/dist/tools/dotnet-reconstruct-export.d.ts +49 -49
  66. package/dist/tools/dotnet-types-list.d.ts +66 -66
  67. package/dist/tools/dynamic-dependencies.d.ts +144 -8
  68. package/dist/tools/dynamic-dependencies.js +20 -1
  69. package/dist/tools/dynamic-memory-import.js +105 -2
  70. package/dist/tools/ghidra-analyze.d.ts +20 -0
  71. package/dist/tools/ghidra-analyze.js +10 -2
  72. package/dist/tools/ghidra-health.d.ts +177 -0
  73. package/dist/tools/ghidra-health.js +28 -0
  74. package/dist/tools/ioc-export.d.ts +8 -8
  75. package/dist/tools/packer-detect.d.ts +14 -14
  76. package/dist/tools/packer-detect.js +6 -9
  77. package/dist/tools/pe-exports-extract.d.ts +8 -8
  78. package/dist/tools/pe-exports-extract.js +6 -9
  79. package/dist/tools/pe-fingerprint.d.ts +36 -36
  80. package/dist/tools/pe-imports-extract.d.ts +8 -8
  81. package/dist/tools/pe-imports-extract.js +6 -9
  82. package/dist/tools/pe-pdata-extract.d.ts +448 -0
  83. package/dist/tools/pe-pdata-extract.js +219 -0
  84. package/dist/tools/pe-symbols-recover.d.ts +227 -0
  85. package/dist/tools/pe-symbols-recover.js +316 -0
  86. package/dist/tools/report-generate.d.ts +15 -1
  87. package/dist/tools/report-generate.js +139 -16
  88. package/dist/tools/report-summarize.d.ts +2242 -8
  89. package/dist/tools/report-summarize.js +246 -10
  90. package/dist/tools/runtime-detect.d.ts +8 -8
  91. package/dist/tools/runtime-detect.js +2 -12
  92. package/dist/tools/rust-binary-analyze.d.ts +2047 -0
  93. package/dist/tools/rust-binary-analyze.js +443 -0
  94. package/dist/tools/sample-profile-get.d.ts +135 -16
  95. package/dist/tools/sample-profile-get.js +35 -32
  96. package/dist/tools/sandbox-execute.d.ts +10 -10
  97. package/dist/tools/strings-extract.d.ts +8 -8
  98. package/dist/tools/strings-extract.js +2 -12
  99. package/dist/tools/strings-floss-decode.d.ts +8 -8
  100. package/dist/tools/system-health.d.ts +172 -36
  101. package/dist/tools/system-health.js +14 -0
  102. package/dist/tools/system-setup-guide.d.ts +179 -0
  103. package/dist/tools/system-setup-guide.js +62 -0
  104. package/dist/tools/tool-help.d.ts +16 -16
  105. package/dist/tools/tool-help.js +172 -0
  106. package/dist/tools/yara-scan.d.ts +8 -8
  107. package/dist/types.d.ts +2 -0
  108. package/dist/workflows/function-explanation-review.d.ts +3034 -76
  109. package/dist/workflows/function-explanation-review.js +172 -42
  110. package/dist/workflows/function-index-recover.d.ts +333 -0
  111. package/dist/workflows/function-index-recover.js +291 -0
  112. package/dist/workflows/module-reconstruction-review.d.ts +3537 -0
  113. package/dist/workflows/module-reconstruction-review.js +354 -0
  114. package/dist/workflows/reconstruct.d.ts +2182 -223
  115. package/dist/workflows/reconstruct.js +575 -32
  116. package/dist/workflows/semantic-name-review.d.ts +1436 -85
  117. package/dist/workflows/semantic-name-review.js +150 -58
  118. package/dist/workflows/triage.d.ts +8 -8
  119. package/ghidra_scripts/DecompileFunction.java +487 -487
  120. package/ghidra_scripts/ExtractCFG.java +256 -256
  121. package/ghidra_scripts/ExtractFunctions.java +442 -442
  122. package/ghidra_scripts/README.md +112 -112
  123. package/ghidra_scripts/SearchFunctionReferences.java +380 -380
  124. package/helpers/DotNetMetadataProbe/DotNetMetadataProbe.csproj +9 -9
  125. package/helpers/DotNetMetadataProbe/Program.cs +566 -566
  126. package/install-to-claude.ps1 +432 -0
  127. package/install-to-codex.ps1 +256 -178
  128. package/install-to-copilot.ps1 +389 -303
  129. package/package.json +4 -2
  130. package/requirements.txt +9 -9
  131. package/workers/requirements-dynamic.txt +11 -11
  132. package/workers/requirements.txt +8 -8
  133. package/workers/speakeasy_compat.py +175 -175
  134. package/workers/static_worker.py +3474 -3474
@@ -0,0 +1,452 @@
1
+ import fs from 'fs';
2
+ const IMAGE_FILE_MACHINE_NAMES = {
3
+ 0x014c: 'IMAGE_FILE_MACHINE_I386',
4
+ 0x0200: 'IMAGE_FILE_MACHINE_IA64',
5
+ 0x8664: 'IMAGE_FILE_MACHINE_AMD64',
6
+ 0x01c4: 'IMAGE_FILE_MACHINE_ARMNT',
7
+ 0xaa64: 'IMAGE_FILE_MACHINE_ARM64',
8
+ };
9
+ const UNWIND_FLAG_NAMES = [
10
+ [0x1, 'EHANDLER'],
11
+ [0x2, 'UHANDLER'],
12
+ [0x4, 'CHAININFO'],
13
+ ];
14
+ const X64_REGISTER_NAMES = [
15
+ null,
16
+ 'RCX',
17
+ 'RDX',
18
+ 'RBX',
19
+ 'RSP',
20
+ 'RBP',
21
+ 'RSI',
22
+ 'RDI',
23
+ 'R8',
24
+ 'R9',
25
+ 'R10',
26
+ 'R11',
27
+ 'R12',
28
+ 'R13',
29
+ 'R14',
30
+ 'R15',
31
+ ];
32
+ function readUInt64AsNumber(buffer, offset) {
33
+ const low = buffer.readUInt32LE(offset);
34
+ const high = buffer.readUInt32LE(offset + 4);
35
+ return high * 0x1_0000_0000 + low;
36
+ }
37
+ function formatHex(value) {
38
+ const width = value > 0xffffffff ? 16 : 8;
39
+ return `0x${value.toString(16).padStart(width, '0')}`;
40
+ }
41
+ function readNullTerminatedAscii(buffer, offset) {
42
+ let end = offset;
43
+ while (end < buffer.length && buffer[end] !== 0) {
44
+ end += 1;
45
+ }
46
+ return buffer.toString('ascii', offset, end);
47
+ }
48
+ function getSectionForRva(sections, rva) {
49
+ return sections.find((section) => {
50
+ const maxSize = Math.max(section.virtualSize, section.rawSize);
51
+ return rva >= section.virtualAddress && rva < section.virtualAddress + maxSize;
52
+ });
53
+ }
54
+ function rvaToOffset(buffer, sections, rva) {
55
+ if (rva < 0 || rva >= buffer.length) {
56
+ const section = getSectionForRva(sections, rva);
57
+ if (!section) {
58
+ return undefined;
59
+ }
60
+ const delta = rva - section.virtualAddress;
61
+ if (delta < 0 || delta >= section.rawSize) {
62
+ return undefined;
63
+ }
64
+ return section.rawPointer + delta;
65
+ }
66
+ const section = getSectionForRva(sections, rva);
67
+ if (!section) {
68
+ return rva;
69
+ }
70
+ const delta = rva - section.virtualAddress;
71
+ if (delta < 0 || delta >= section.rawSize) {
72
+ return undefined;
73
+ }
74
+ return section.rawPointer + delta;
75
+ }
76
+ function parseSectionHeaders(buffer, sectionTableOffset, numberOfSections) {
77
+ const sections = [];
78
+ for (let index = 0; index < numberOfSections; index += 1) {
79
+ const offset = sectionTableOffset + index * 40;
80
+ if (offset + 40 > buffer.length) {
81
+ break;
82
+ }
83
+ const rawName = buffer.toString('ascii', offset, offset + 8);
84
+ const name = rawName.replace(/\0+$/g, '');
85
+ const virtualSize = buffer.readUInt32LE(offset + 8);
86
+ const virtualAddress = buffer.readUInt32LE(offset + 12);
87
+ const rawSize = buffer.readUInt32LE(offset + 16);
88
+ const rawPointer = buffer.readUInt32LE(offset + 20);
89
+ const characteristics = buffer.readUInt32LE(offset + 36);
90
+ sections.push({
91
+ name,
92
+ virtualAddress,
93
+ virtualSize,
94
+ rawSize,
95
+ rawPointer,
96
+ characteristics,
97
+ executable: (characteristics & 0x20000000) !== 0,
98
+ });
99
+ }
100
+ return sections;
101
+ }
102
+ function parsePEContext(filePath) {
103
+ const buffer = fs.readFileSync(filePath);
104
+ if (buffer.length < 0x100) {
105
+ throw new Error('File is too small to be a valid PE image.');
106
+ }
107
+ if (buffer.toString('ascii', 0, 2) !== 'MZ') {
108
+ throw new Error('File does not start with an MZ DOS header.');
109
+ }
110
+ const peOffset = buffer.readUInt32LE(0x3c);
111
+ if (peOffset + 4 + 20 > buffer.length) {
112
+ throw new Error('PE header offset points outside the file.');
113
+ }
114
+ if (buffer.toString('ascii', peOffset, peOffset + 4) !== 'PE\u0000\u0000') {
115
+ throw new Error('PE signature was not found at e_lfanew.');
116
+ }
117
+ const fileHeaderOffset = peOffset + 4;
118
+ const machine = buffer.readUInt16LE(fileHeaderOffset);
119
+ const numberOfSections = buffer.readUInt16LE(fileHeaderOffset + 2);
120
+ const sizeOfOptionalHeader = buffer.readUInt16LE(fileHeaderOffset + 16);
121
+ const optionalHeaderOffset = fileHeaderOffset + 20;
122
+ const optionalMagic = buffer.readUInt16LE(optionalHeaderOffset);
123
+ const isPe32Plus = optionalMagic === 0x20b;
124
+ const isPe32 = optionalMagic === 0x10b;
125
+ if (!isPe32Plus && !isPe32) {
126
+ throw new Error(`Unsupported PE optional header magic: 0x${optionalMagic.toString(16)}`);
127
+ }
128
+ const entryPointRva = buffer.readUInt32LE(optionalHeaderOffset + 16);
129
+ const imageBase = isPe32Plus
130
+ ? readUInt64AsNumber(buffer, optionalHeaderOffset + 24)
131
+ : buffer.readUInt32LE(optionalHeaderOffset + 28);
132
+ const dataDirectoryStart = optionalHeaderOffset + (isPe32Plus ? 112 : 96);
133
+ const numberOfRvaAndSizes = buffer.readUInt32LE(optionalHeaderOffset + (isPe32Plus ? 108 : 92));
134
+ let exceptionDirectoryRva = 0;
135
+ let exceptionDirectorySize = 0;
136
+ if (numberOfRvaAndSizes > 3 && dataDirectoryStart + 32 <= buffer.length) {
137
+ exceptionDirectoryRva = buffer.readUInt32LE(dataDirectoryStart + 8 * 3);
138
+ exceptionDirectorySize = buffer.readUInt32LE(dataDirectoryStart + 8 * 3 + 4);
139
+ }
140
+ const sectionTableOffset = optionalHeaderOffset + sizeOfOptionalHeader;
141
+ const sections = parseSectionHeaders(buffer, sectionTableOffset, numberOfSections);
142
+ return {
143
+ buffer,
144
+ filePath,
145
+ machine,
146
+ machineName: IMAGE_FILE_MACHINE_NAMES[machine] || `UNKNOWN_0x${machine.toString(16)}`,
147
+ imageBase,
148
+ entryPointRva,
149
+ exceptionDirectoryRva,
150
+ exceptionDirectorySize,
151
+ sections,
152
+ };
153
+ }
154
+ function parseExportTable(context) {
155
+ const { buffer, sections } = context;
156
+ const peOffset = buffer.readUInt32LE(0x3c);
157
+ const fileHeaderOffset = peOffset + 4;
158
+ const optionalHeaderOffset = fileHeaderOffset + 20;
159
+ const optionalMagic = buffer.readUInt16LE(optionalHeaderOffset);
160
+ const isPe32Plus = optionalMagic === 0x20b;
161
+ const dataDirectoryStart = optionalHeaderOffset + (isPe32Plus ? 112 : 96);
162
+ const numberOfRvaAndSizes = buffer.readUInt32LE(optionalHeaderOffset + (isPe32Plus ? 108 : 92));
163
+ if (numberOfRvaAndSizes < 1) {
164
+ return [];
165
+ }
166
+ const exportRva = buffer.readUInt32LE(dataDirectoryStart);
167
+ const exportSize = buffer.readUInt32LE(dataDirectoryStart + 4);
168
+ if (!exportRva || !exportSize) {
169
+ return [];
170
+ }
171
+ const exportOffset = rvaToOffset(buffer, sections, exportRva);
172
+ if (exportOffset === undefined || exportOffset + 40 > buffer.length) {
173
+ return [];
174
+ }
175
+ const ordinalBase = buffer.readUInt32LE(exportOffset + 16);
176
+ const numberOfFunctions = buffer.readUInt32LE(exportOffset + 20);
177
+ const numberOfNames = buffer.readUInt32LE(exportOffset + 24);
178
+ const addressOfFunctionsRva = buffer.readUInt32LE(exportOffset + 28);
179
+ const addressOfNamesRva = buffer.readUInt32LE(exportOffset + 32);
180
+ const addressOfNameOrdinalsRva = buffer.readUInt32LE(exportOffset + 36);
181
+ const addressOfFunctionsOffset = rvaToOffset(buffer, sections, addressOfFunctionsRva);
182
+ const addressOfNamesOffset = rvaToOffset(buffer, sections, addressOfNamesRva);
183
+ const addressOfNameOrdinalsOffset = rvaToOffset(buffer, sections, addressOfNameOrdinalsRva);
184
+ if (addressOfFunctionsOffset === undefined ||
185
+ addressOfNamesOffset === undefined ||
186
+ addressOfNameOrdinalsOffset === undefined) {
187
+ return [];
188
+ }
189
+ const records = [];
190
+ for (let index = 0; index < numberOfNames; index += 1) {
191
+ const nameRva = buffer.readUInt32LE(addressOfNamesOffset + index * 4);
192
+ const nameOffset = rvaToOffset(buffer, sections, nameRva);
193
+ const ordinalIndex = buffer.readUInt16LE(addressOfNameOrdinalsOffset + index * 2);
194
+ if (nameOffset === undefined || ordinalIndex >= numberOfFunctions) {
195
+ continue;
196
+ }
197
+ const functionRva = buffer.readUInt32LE(addressOfFunctionsOffset + ordinalIndex * 4);
198
+ const name = readNullTerminatedAscii(buffer, nameOffset);
199
+ const isForwarder = functionRva >= exportRva && functionRva < exportRva + exportSize;
200
+ records.push({
201
+ rva: functionRva,
202
+ name,
203
+ ordinal: ordinalBase + ordinalIndex,
204
+ isForwarder,
205
+ });
206
+ }
207
+ return records;
208
+ }
209
+ function parseUnwindInfo(context, unwindInfoRva) {
210
+ const { buffer, sections } = context;
211
+ const offset = rvaToOffset(buffer, sections, unwindInfoRva);
212
+ if (offset === undefined || offset + 4 > buffer.length) {
213
+ return null;
214
+ }
215
+ const versionAndFlags = buffer.readUInt8(offset);
216
+ const version = versionAndFlags & 0x7;
217
+ const flags = versionAndFlags >> 3;
218
+ const prologSize = buffer.readUInt8(offset + 1);
219
+ const unwindCodeCount = buffer.readUInt8(offset + 2);
220
+ const frameRegisterAndOffset = buffer.readUInt8(offset + 3);
221
+ const frameRegisterId = frameRegisterAndOffset & 0x0f;
222
+ const frameOffset = frameRegisterAndOffset >> 4;
223
+ const flagNames = UNWIND_FLAG_NAMES
224
+ .filter(([flag]) => (flags & flag) !== 0)
225
+ .map(([, name]) => name);
226
+ let trailingOffset = offset + 4 + unwindCodeCount * 2;
227
+ if (trailingOffset % 4 !== 0) {
228
+ trailingOffset += 4 - (trailingOffset % 4);
229
+ }
230
+ const info = {
231
+ version,
232
+ flags,
233
+ flagNames,
234
+ prologSize,
235
+ unwindCodeCount,
236
+ frameRegister: X64_REGISTER_NAMES[frameRegisterId] || null,
237
+ frameRegisterId,
238
+ frameOffset,
239
+ };
240
+ if ((flags & 0x4) !== 0 && trailingOffset + 12 <= buffer.length) {
241
+ info.chainedRuntimeFunction = {
242
+ beginRva: buffer.readUInt32LE(trailingOffset),
243
+ endRva: buffer.readUInt32LE(trailingOffset + 4),
244
+ unwindInfoRva: buffer.readUInt32LE(trailingOffset + 8),
245
+ };
246
+ }
247
+ else if ((flags & 0x3) !== 0 && trailingOffset + 4 <= buffer.length) {
248
+ info.handlerRva = buffer.readUInt32LE(trailingOffset);
249
+ }
250
+ return info;
251
+ }
252
+ export function extractPdataFromPE(filePath) {
253
+ const context = parsePEContext(filePath);
254
+ const { buffer, sections, imageBase, entryPointRva } = context;
255
+ const warnings = [];
256
+ const exports = parseExportTable(context);
257
+ let exceptionDirectoryRva = context.exceptionDirectoryRva;
258
+ let exceptionDirectorySize = context.exceptionDirectorySize;
259
+ if (!exceptionDirectoryRva || !exceptionDirectorySize) {
260
+ const pdataSection = sections.find((section) => section.name.toLowerCase() === '.pdata');
261
+ if (pdataSection) {
262
+ exceptionDirectoryRva = pdataSection.virtualAddress;
263
+ exceptionDirectorySize = Math.min(pdataSection.virtualSize || pdataSection.rawSize, pdataSection.rawSize);
264
+ warnings.push('Exception directory metadata was empty; fell back to the .pdata section bounds.');
265
+ }
266
+ }
267
+ const entries = [];
268
+ if (!exceptionDirectoryRva || !exceptionDirectorySize) {
269
+ return {
270
+ machine: context.machine,
271
+ machineName: context.machineName,
272
+ imageBase,
273
+ entryPointRva,
274
+ exceptionDirectoryRva: 0,
275
+ exceptionDirectorySize: 0,
276
+ pdataPresent: false,
277
+ xdataPresent: sections.some((section) => section.name.toLowerCase() === '.xdata'),
278
+ count: 0,
279
+ sections,
280
+ exports,
281
+ entries,
282
+ warnings: ['PE image has no exception directory / .pdata section to parse.'],
283
+ };
284
+ }
285
+ const exceptionDirectoryOffset = rvaToOffset(buffer, sections, exceptionDirectoryRva);
286
+ if (exceptionDirectoryOffset === undefined) {
287
+ throw new Error('Exception directory RVA could not be mapped to a file offset.');
288
+ }
289
+ const availableBytes = Math.max(0, Math.min(exceptionDirectorySize, buffer.length - exceptionDirectoryOffset));
290
+ if (availableBytes % 12 !== 0) {
291
+ warnings.push(`Exception directory size (${availableBytes}) is not a multiple of 12 bytes.`);
292
+ }
293
+ for (let offset = 0; offset + 12 <= availableBytes; offset += 12) {
294
+ const beginRva = buffer.readUInt32LE(exceptionDirectoryOffset + offset);
295
+ const endRva = buffer.readUInt32LE(exceptionDirectoryOffset + offset + 4);
296
+ const unwindInfoRva = buffer.readUInt32LE(exceptionDirectoryOffset + offset + 8);
297
+ if (beginRva === 0 && endRva === 0 && unwindInfoRva === 0) {
298
+ continue;
299
+ }
300
+ if (!beginRva || !endRva || endRva <= beginRva) {
301
+ warnings.push(`Skipped malformed RUNTIME_FUNCTION entry at directory offset +0x${offset.toString(16)}.`);
302
+ continue;
303
+ }
304
+ const section = getSectionForRva(sections, beginRva);
305
+ const unwind = unwindInfoRva ? parseUnwindInfo(context, unwindInfoRva) : null;
306
+ let confidence = 0.68;
307
+ if (section?.executable) {
308
+ confidence += 0.12;
309
+ }
310
+ if (unwind) {
311
+ confidence += 0.12;
312
+ if (unwind.handlerRva || unwind.chainedRuntimeFunction) {
313
+ confidence += 0.05;
314
+ }
315
+ }
316
+ if (beginRva === entryPointRva) {
317
+ confidence += 0.03;
318
+ }
319
+ confidence = Math.min(confidence, 0.96);
320
+ entries.push({
321
+ beginRva,
322
+ endRva,
323
+ size: endRva - beginRva,
324
+ beginVa: imageBase + beginRva,
325
+ endVa: imageBase + endRva,
326
+ beginAddress: formatHex(imageBase + beginRva),
327
+ endAddress: formatHex(imageBase + endRva),
328
+ unwindInfoRva,
329
+ sectionName: section?.name || null,
330
+ executableSection: section?.executable || false,
331
+ confidence,
332
+ unwind,
333
+ });
334
+ }
335
+ entries.sort((left, right) => left.beginRva - right.beginRva);
336
+ return {
337
+ machine: context.machine,
338
+ machineName: context.machineName,
339
+ imageBase,
340
+ entryPointRva,
341
+ exceptionDirectoryRva,
342
+ exceptionDirectorySize,
343
+ pdataPresent: true,
344
+ xdataPresent: sections.some((section) => section.name.toLowerCase() === '.xdata'),
345
+ count: entries.length,
346
+ sections,
347
+ exports,
348
+ entries,
349
+ warnings,
350
+ };
351
+ }
352
+ export function smartRecoverFunctionsFromPE(filePath) {
353
+ const pdata = extractPdataFromPE(filePath);
354
+ const exportByRva = new Map();
355
+ for (const record of pdata.exports) {
356
+ if (!record.isForwarder && !exportByRva.has(record.rva)) {
357
+ exportByRva.set(record.rva, record);
358
+ }
359
+ }
360
+ const functions = pdata.entries.map((entry) => {
361
+ const exportMatch = exportByRva.get(entry.beginRva);
362
+ const isEntryPoint = entry.beginRva === pdata.entryPointRva;
363
+ let name = `sub_${entry.beginRva.toString(16).padStart(8, '0')}`;
364
+ let nameSource = 'synthetic_sub';
365
+ if (exportMatch?.name) {
366
+ name = exportMatch.name;
367
+ nameSource = 'export';
368
+ }
369
+ else if (isEntryPoint) {
370
+ name = 'entry_point';
371
+ nameSource = 'entry_point';
372
+ }
373
+ const evidence = [
374
+ 'Recovered from PE exception directory (.pdata) runtime function entry.',
375
+ `Size=${entry.size} bytes`,
376
+ ];
377
+ if (entry.sectionName) {
378
+ evidence.push(`Section=${entry.sectionName}`);
379
+ }
380
+ if (entry.unwind) {
381
+ evidence.push(`Unwind flags=${entry.unwind.flagNames.join('|') || 'none'}`);
382
+ }
383
+ if (exportMatch?.name) {
384
+ evidence.push(`Matched export ${exportMatch.name}`);
385
+ }
386
+ if (isEntryPoint) {
387
+ evidence.push('Matches PE entry point RVA');
388
+ }
389
+ return {
390
+ address: entry.beginAddress,
391
+ va: entry.beginVa,
392
+ rva: entry.beginRva,
393
+ size: entry.size,
394
+ name,
395
+ nameSource,
396
+ confidence: entry.confidence,
397
+ source: 'pdata_runtime_function',
398
+ sectionName: entry.sectionName,
399
+ executableSection: entry.executableSection,
400
+ isEntryPoint,
401
+ isExported: Boolean(exportMatch),
402
+ exportName: exportMatch?.name,
403
+ unwind: entry.unwind,
404
+ evidence,
405
+ };
406
+ });
407
+ const hasEntrypoint = functions.some((item) => item.isEntryPoint);
408
+ if (!hasEntrypoint && pdata.entryPointRva > 0) {
409
+ const section = getSectionForRva(pdata.sections, pdata.entryPointRva);
410
+ functions.unshift({
411
+ address: formatHex(pdata.imageBase + pdata.entryPointRva),
412
+ va: pdata.imageBase + pdata.entryPointRva,
413
+ rva: pdata.entryPointRva,
414
+ size: 0,
415
+ name: 'entry_point',
416
+ nameSource: 'entry_point',
417
+ confidence: 0.42,
418
+ source: 'entry_point_only',
419
+ sectionName: section?.name || null,
420
+ executableSection: section?.executable || false,
421
+ isEntryPoint: true,
422
+ isExported: false,
423
+ evidence: ['Recovered directly from PE AddressOfEntryPoint because no .pdata entry matched it.'],
424
+ unwind: null,
425
+ });
426
+ }
427
+ const deduped = new Map();
428
+ for (const item of functions) {
429
+ const existing = deduped.get(item.rva);
430
+ if (!existing || item.confidence > existing.confidence) {
431
+ deduped.set(item.rva, item);
432
+ }
433
+ }
434
+ return {
435
+ machine: pdata.machine,
436
+ machineName: pdata.machineName,
437
+ imageBase: pdata.imageBase,
438
+ entryPointRva: pdata.entryPointRva,
439
+ strategy: dedupeStrings([
440
+ pdata.count > 0 ? 'pdata_runtime_functions' : '',
441
+ pdata.exports.length > 0 ? 'export_surface' : '',
442
+ pdata.entryPointRva > 0 ? 'entry_point' : '',
443
+ ]),
444
+ count: deduped.size,
445
+ functions: Array.from(deduped.values()).sort((left, right) => left.rva - right.rva),
446
+ warnings: pdata.warnings,
447
+ };
448
+ }
449
+ function dedupeStrings(values) {
450
+ return Array.from(new Set(values.filter((item) => item.length > 0)));
451
+ }
452
+ //# sourceMappingURL=pe-runtime-functions.js.map
@@ -0,0 +1,5 @@
1
+ import type { PromptArgs, PromptDefinition, PromptResult } from '../types.js';
2
+ export declare const moduleReconstructionReviewPromptDefinition: PromptDefinition;
3
+ export declare function buildModuleReconstructionReviewPromptText(preparedBundleJson: string, analysisGoal?: string): string;
4
+ export declare function createModuleReconstructionReviewPromptHandler(): (args: PromptArgs) => Promise<PromptResult>;
5
+ //# sourceMappingURL=module-reconstruction-review.d.ts.map
@@ -0,0 +1,65 @@
1
+ export const moduleReconstructionReviewPromptDefinition = {
2
+ name: 'reverse.module_reconstruction_review',
3
+ title: 'Module Reconstruction Review',
4
+ description: 'Guide an external LLM to review reconstructed modules and return strict JSON summaries, role hints, and rewrite guidance grounded in MCP evidence.',
5
+ arguments: [
6
+ {
7
+ name: 'prepared_bundle_json',
8
+ description: 'JSON bundle produced by code.module.review.prepare',
9
+ required: true,
10
+ },
11
+ {
12
+ name: 'analysis_goal',
13
+ description: 'Optional analysis goal to bias the review toward tooling, malware, library, or plugin semantics',
14
+ required: false,
15
+ },
16
+ ],
17
+ };
18
+ export function buildModuleReconstructionReviewPromptText(preparedBundleJson, analysisGoal) {
19
+ const goal = analysisGoal?.trim() ||
20
+ 'Review the reconstructed modules, summarize their likely role, and propose evidence-grounded rewrite guidance.';
21
+ return [
22
+ goal,
23
+ '',
24
+ 'You are reviewing module-level reverse-engineering evidence from an MCP server.',
25
+ 'Use only the supplied evidence bundle. Do not invent exports, APIs, modules, or behaviors that do not appear in the evidence.',
26
+ '',
27
+ 'Review policy:',
28
+ '- Treat all confidence values as heuristic evidence strength, not calibrated probability.',
29
+ '- Prefer module-level roles such as "export dispatch", "COM activation", "DLL lifecycle", or "remote process operations".',
30
+ '- Preserve uncertainty explicitly when a module grouping may contain mixed responsibilities.',
31
+ '- Keep rewrite guidance implementation-oriented and evidence-grounded. Do not pretend you recovered the original source code.',
32
+ '',
33
+ 'Output contract:',
34
+ 'Return strict JSON only, with the shape {"reviews":[...]}',
35
+ 'Each review item must contain:',
36
+ '- module_name',
37
+ '- summary',
38
+ '- role_hint',
39
+ '- confidence',
40
+ '- assumptions',
41
+ '- evidence_used',
42
+ '- rewrite_guidance',
43
+ '- optional refined_name',
44
+ '- optional focus_areas',
45
+ '- optional priority_functions',
46
+ '',
47
+ 'Prepared bundle JSON:',
48
+ preparedBundleJson,
49
+ ].join('\n');
50
+ }
51
+ export function createModuleReconstructionReviewPromptHandler() {
52
+ return async (args) => ({
53
+ description: 'Prompt template for evidence-grounded module review, role refinement, and rewrite guidance.',
54
+ messages: [
55
+ {
56
+ role: 'user',
57
+ content: {
58
+ type: 'text',
59
+ text: buildModuleReconstructionReviewPromptText(args.prepared_bundle_json || '', args.analysis_goal),
60
+ },
61
+ },
62
+ ],
63
+ });
64
+ }
65
+ //# sourceMappingURL=module-reconstruction-review.js.map
@@ -20,6 +20,11 @@ export interface RuntimeCorrelation {
20
20
  artifact_count?: number;
21
21
  executed_artifact_count?: number;
22
22
  matched_memory_regions?: string[];
23
+ matched_protections?: string[];
24
+ matched_address_ranges?: string[];
25
+ matched_region_owners?: string[];
26
+ matched_observed_modules?: string[];
27
+ matched_segment_names?: string[];
23
28
  suggested_modules?: string[];
24
29
  matched_by?: string[];
25
30
  provenance_layers?: string[];