@remnux/mcp-server 0.1.54 → 0.1.56

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 (88) hide show
  1. package/README.md +16 -1
  2. package/data/osint-resources.json +546 -0
  3. package/dist/analysis/behavior-prerequisites.d.ts +65 -0
  4. package/dist/analysis/behavior-prerequisites.d.ts.map +1 -0
  5. package/dist/analysis/behavior-prerequisites.js +186 -0
  6. package/dist/analysis/behavior-prerequisites.js.map +1 -0
  7. package/dist/analysis/file-diff.d.ts +59 -0
  8. package/dist/analysis/file-diff.d.ts.map +1 -0
  9. package/dist/analysis/file-diff.js +55 -0
  10. package/dist/analysis/file-diff.js.map +1 -0
  11. package/dist/analysis/index.d.ts +1 -1
  12. package/dist/analysis/index.d.ts.map +1 -1
  13. package/dist/analysis/index.js +1 -1
  14. package/dist/analysis/index.js.map +1 -1
  15. package/dist/analysis/string-usage.d.ts +74 -0
  16. package/dist/analysis/string-usage.d.ts.map +1 -0
  17. package/dist/analysis/string-usage.js +124 -0
  18. package/dist/analysis/string-usage.js.map +1 -0
  19. package/dist/analysis/summarizer.d.ts +22 -0
  20. package/dist/analysis/summarizer.d.ts.map +1 -1
  21. package/dist/analysis/summarizer.js +41 -0
  22. package/dist/analysis/summarizer.js.map +1 -1
  23. package/dist/connectors/docker.d.ts +1 -0
  24. package/dist/connectors/docker.d.ts.map +1 -1
  25. package/dist/connectors/docker.js +39 -5
  26. package/dist/connectors/docker.js.map +1 -1
  27. package/dist/handlers/analyze-file.d.ts.map +1 -1
  28. package/dist/handlers/analyze-file.js +19 -2
  29. package/dist/handlers/analyze-file.js.map +1 -1
  30. package/dist/handlers/check-behavior-prerequisites.d.ts +16 -0
  31. package/dist/handlers/check-behavior-prerequisites.d.ts.map +1 -0
  32. package/dist/handlers/check-behavior-prerequisites.js +132 -0
  33. package/dist/handlers/check-behavior-prerequisites.js.map +1 -0
  34. package/dist/handlers/compare-files.d.ts +10 -0
  35. package/dist/handlers/compare-files.d.ts.map +1 -0
  36. package/dist/handlers/compare-files.js +147 -0
  37. package/dist/handlers/compare-files.js.map +1 -0
  38. package/dist/handlers/extract-iocs.d.ts.map +1 -1
  39. package/dist/handlers/extract-iocs.js +6 -0
  40. package/dist/handlers/extract-iocs.js.map +1 -1
  41. package/dist/handlers/get-tool-help.d.ts.map +1 -1
  42. package/dist/handlers/get-tool-help.js +11 -0
  43. package/dist/handlers/get-tool-help.js.map +1 -1
  44. package/dist/handlers/osint.d.ts +10 -0
  45. package/dist/handlers/osint.d.ts.map +1 -0
  46. package/dist/handlers/osint.js +72 -0
  47. package/dist/handlers/osint.js.map +1 -0
  48. package/dist/handlers/report.d.ts.map +1 -1
  49. package/dist/handlers/report.js +11 -0
  50. package/dist/handlers/report.js.map +1 -1
  51. package/dist/handlers/verify-string-usage.d.ts +10 -0
  52. package/dist/handlers/verify-string-usage.d.ts.map +1 -0
  53. package/dist/handlers/verify-string-usage.js +173 -0
  54. package/dist/handlers/verify-string-usage.js.map +1 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +55 -8
  57. package/dist/index.js.map +1 -1
  58. package/dist/osint/guidance.d.ts +125 -0
  59. package/dist/osint/guidance.d.ts.map +1 -0
  60. package/dist/osint/guidance.js +217 -0
  61. package/dist/osint/guidance.js.map +1 -0
  62. package/dist/osint/index.d.ts +61 -0
  63. package/dist/osint/index.d.ts.map +1 -0
  64. package/dist/osint/index.js +64 -0
  65. package/dist/osint/index.js.map +1 -0
  66. package/dist/parsers/capa.d.ts +8 -1
  67. package/dist/parsers/capa.d.ts.map +1 -1
  68. package/dist/parsers/capa.js +145 -0
  69. package/dist/parsers/capa.js.map +1 -1
  70. package/dist/parsers/imports.d.ts +36 -0
  71. package/dist/parsers/imports.d.ts.map +1 -0
  72. package/dist/parsers/imports.js +67 -0
  73. package/dist/parsers/imports.js.map +1 -0
  74. package/dist/parsers/r2.d.ts +73 -0
  75. package/dist/parsers/r2.d.ts.map +1 -0
  76. package/dist/parsers/r2.js +161 -0
  77. package/dist/parsers/r2.js.map +1 -0
  78. package/dist/parsers/types.d.ts +19 -0
  79. package/dist/parsers/types.d.ts.map +1 -1
  80. package/dist/report/triage-discipline.d.ts +28 -0
  81. package/dist/report/triage-discipline.d.ts.map +1 -0
  82. package/dist/report/triage-discipline.js +49 -0
  83. package/dist/report/triage-discipline.js.map +1 -0
  84. package/dist/schemas/tools.d.ts +56 -3
  85. package/dist/schemas/tools.d.ts.map +1 -1
  86. package/dist/schemas/tools.js +44 -2
  87. package/dist/schemas/tools.js.map +1 -1
  88. package/package.json +2 -1
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Static behavior-prerequisite analysis.
3
+ *
4
+ * Answers "can this binary even call the APIs that behavior X requires?" from
5
+ * the static import table. This is a deliberate GATE against the most common
6
+ * triage error — claiming a behavior on artifact evidence alone — by forcing an
7
+ * explicit "are the prerequisite APIs accessible?" check.
8
+ *
9
+ * Design constraints (from the project's design review):
10
+ * - It reports a STATIC-capability assessment, never a behavioral verdict. The
11
+ * field is `static_capability` (not `verdict`) and every value is suffixed
12
+ * with the basis (`_statically`) so it cannot be misread as "the sample does
13
+ * X". Static capability is necessary, not sufficient, for the behavior.
14
+ * - It never emits a clean "incapable" on a packed/obscured binary — the
15
+ * import table is unreliable there, so it returns `analysis_incomplete`
16
+ * ("unpack first") rather than a false negative.
17
+ * - When the required APIs are absent but GetProcAddress + a library loader are
18
+ * present, it returns `possibly_via_dynamic_resolution` — it cannot prove
19
+ * those resolvers are NOT used to reach the missing APIs without xref /
20
+ * dynamic analysis, so it does not claim `incapable_statically`.
21
+ * - The behavior→API table is generic malware-analysis knowledge, not tuned to
22
+ * any specific sample.
23
+ */
24
+ export type StaticCapability = "capable_statically" | "incapable_statically" | "possibly_via_dynamic_resolution" | "analysis_incomplete" | "not_applicable";
25
+ export interface BehaviorDef {
26
+ /** Human-readable description of the behavior. */
27
+ description: string;
28
+ /** Required API base names (no ANSI/Unicode suffix). */
29
+ apis: string[];
30
+ /** How many of `apis` must be present to call the binary statically capable. */
31
+ minMatch: number;
32
+ }
33
+ /**
34
+ * Behavior → prerequisite APIs. Base names only (apiPresent handles A/W). The
35
+ * minMatch threshold tolerates that a behavior rarely needs its entire API set
36
+ * and that import names vary, while still requiring a meaningful signature.
37
+ */
38
+ export declare const BEHAVIOR_PREREQUISITES: Record<string, BehaviorDef>;
39
+ export interface PrerequisiteResult {
40
+ behavior: string;
41
+ description: string;
42
+ required_apis: string[];
43
+ apis_present: string[];
44
+ apis_missing: string[];
45
+ dynamic_resolution_present: boolean;
46
+ static_capability: StaticCapability;
47
+ rationale: string;
48
+ recommended_followup: string;
49
+ }
50
+ /** Context derived from the binary (packer detection, whether imports parsed). */
51
+ export interface PrerequisiteContext {
52
+ /** A packer/protector was detected, or the import table is unreadable/empty. */
53
+ obscured: boolean;
54
+ /** readpe produced a usable import table. */
55
+ importsParsed: boolean;
56
+ /** Managed/.NET assembly — native imports don't reflect managed-code capability. */
57
+ managed?: boolean;
58
+ }
59
+ /** Detect the GetProcAddress + library-loader pair that enables runtime resolution. */
60
+ export declare function hasDynamicResolution(functionSet: Set<string>): boolean;
61
+ /**
62
+ * Classify one behavior against an import set + context. Pure and deterministic.
63
+ */
64
+ export declare function classifyPrerequisite(behaviorKey: string, def: BehaviorDef, functionSet: Set<string>, ctx: PrerequisiteContext): PrerequisiteResult;
65
+ //# sourceMappingURL=behavior-prerequisites.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behavior-prerequisites.d.ts","sourceRoot":"","sources":["../../src/analysis/behavior-prerequisites.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,MAAM,MAAM,gBAAgB,GACxB,oBAAoB,GACpB,sBAAsB,GACtB,iCAAiC,GACjC,qBAAqB,GACrB,gBAAgB,CAAC;AAErB,MAAM,WAAW,WAAW;IAC1B,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAkE9D,CAAC;AAMF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,0BAA0B,EAAE,OAAO,CAAC;IACpC,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IAClC,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAC;IAClB,6CAA6C;IAC7C,aAAa,EAAE,OAAO,CAAC;IACvB,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAiBD,uFAAuF;AACvF,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAKtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,GAAG,EAAE,mBAAmB,GACvB,kBAAkB,CA8DpB"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Static behavior-prerequisite analysis.
3
+ *
4
+ * Answers "can this binary even call the APIs that behavior X requires?" from
5
+ * the static import table. This is a deliberate GATE against the most common
6
+ * triage error — claiming a behavior on artifact evidence alone — by forcing an
7
+ * explicit "are the prerequisite APIs accessible?" check.
8
+ *
9
+ * Design constraints (from the project's design review):
10
+ * - It reports a STATIC-capability assessment, never a behavioral verdict. The
11
+ * field is `static_capability` (not `verdict`) and every value is suffixed
12
+ * with the basis (`_statically`) so it cannot be misread as "the sample does
13
+ * X". Static capability is necessary, not sufficient, for the behavior.
14
+ * - It never emits a clean "incapable" on a packed/obscured binary — the
15
+ * import table is unreliable there, so it returns `analysis_incomplete`
16
+ * ("unpack first") rather than a false negative.
17
+ * - When the required APIs are absent but GetProcAddress + a library loader are
18
+ * present, it returns `possibly_via_dynamic_resolution` — it cannot prove
19
+ * those resolvers are NOT used to reach the missing APIs without xref /
20
+ * dynamic analysis, so it does not claim `incapable_statically`.
21
+ * - The behavior→API table is generic malware-analysis knowledge, not tuned to
22
+ * any specific sample.
23
+ */
24
+ import { apiPresent } from "../parsers/imports.js";
25
+ /**
26
+ * Behavior → prerequisite APIs. Base names only (apiPresent handles A/W). The
27
+ * minMatch threshold tolerates that a behavior rarely needs its entire API set
28
+ * and that import names vary, while still requiring a meaningful signature.
29
+ */
30
+ export const BEHAVIOR_PREREQUISITES = {
31
+ clipboard_hijacking: {
32
+ description: "Read and/or replace clipboard contents (e.g. a crypto clipper).",
33
+ apis: ["OpenClipboard", "GetClipboardData", "SetClipboardData", "EmptyClipboard"],
34
+ minMatch: 2,
35
+ },
36
+ http_c2_wininet: {
37
+ description: "HTTP(S) command-and-control or download via the WinINet API.",
38
+ apis: ["InternetOpen", "InternetConnect", "InternetOpenUrl", "HttpOpenRequest", "HttpSendRequest", "HttpQueryInfo", "InternetReadFile"],
39
+ minMatch: 2,
40
+ },
41
+ winhttp_c2: {
42
+ description: "HTTP(S) command-and-control or download via the WinHTTP API.",
43
+ apis: ["WinHttpOpen", "WinHttpConnect", "WinHttpOpenRequest", "WinHttpSendRequest", "WinHttpReceiveResponse", "WinHttpReadData"],
44
+ minMatch: 2,
45
+ },
46
+ socket_c2: {
47
+ description: "Raw-socket network communication (Winsock).",
48
+ apis: ["WSAStartup", "socket", "connect", "send", "recv", "WSASend", "WSARecv"],
49
+ minMatch: 3,
50
+ },
51
+ process_injection_remote: {
52
+ description: "Inject code into another process (remote injection).",
53
+ apis: ["OpenProcess", "VirtualAllocEx", "WriteProcessMemory", "CreateRemoteThread", "NtCreateThreadEx", "QueueUserAPC", "NtMapViewOfSection", "NtWriteVirtualMemory"],
54
+ minMatch: 3,
55
+ },
56
+ process_injection_self: {
57
+ description: "Allocate, write, and execute code in the binary's own process.",
58
+ apis: ["VirtualAlloc", "VirtualProtect", "WriteProcessMemory", "NtProtectVirtualMemory"],
59
+ minMatch: 2,
60
+ },
61
+ registry_persistence_run: {
62
+ description: "Persist via a registry Run key (or similar).",
63
+ apis: ["RegOpenKeyEx", "RegCreateKeyEx", "RegSetValueEx"],
64
+ minMatch: 2,
65
+ },
66
+ lnk_persistence: {
67
+ description: "Create a shortcut (.lnk) for persistence via IShellLink (COM).",
68
+ apis: ["CoCreateInstance", "CoInitialize", "CoInitializeEx"],
69
+ minMatch: 1,
70
+ },
71
+ browser_credential_theft: {
72
+ description: "Decrypt DPAPI-protected secrets (browser logins, wallets).",
73
+ apis: ["CryptUnprotectData"],
74
+ minMatch: 1,
75
+ },
76
+ screen_capture: {
77
+ description: "Capture the screen via GDI.",
78
+ apis: ["GetDC", "GetDesktopWindow", "CreateCompatibleDC", "CreateCompatibleBitmap", "BitBlt", "GetDIBits"],
79
+ minMatch: 3,
80
+ },
81
+ keylog_polling: {
82
+ description: "Log keystrokes by polling key state.",
83
+ apis: ["GetAsyncKeyState", "GetKeyState", "GetKeyboardState"],
84
+ minMatch: 1,
85
+ },
86
+ keylog_hook: {
87
+ description: "Log keystrokes via a Windows hook.",
88
+ apis: ["SetWindowsHookEx"],
89
+ minMatch: 1,
90
+ },
91
+ network_share_enum: {
92
+ description: "Enumerate network shares / servers.",
93
+ apis: ["NetShareEnum", "NetServerEnum", "WNetEnumResource", "WNetOpenEnum"],
94
+ minMatch: 1,
95
+ },
96
+ };
97
+ /** Primitives that allow resolving APIs at runtime instead of importing them. */
98
+ const DYNAMIC_RESOLVERS = ["GetProcAddress"];
99
+ const LIBRARY_LOADERS = ["LoadLibrary", "LoadLibraryEx", "LdrLoadDll", "LdrGetProcedureAddress"];
100
+ function followupFor(verdict) {
101
+ switch (verdict) {
102
+ case "capable_statically":
103
+ return "Static capability only. Confirm the behavior at runtime (speakeasy emulation or sandbox detonation) and verify the imported APIs are called from reachable code.";
104
+ case "possibly_via_dynamic_resolution":
105
+ return "Cannot rule out runtime API resolution. Xref the GetProcAddress calls or detonate dynamically to see which APIs are actually resolved.";
106
+ case "incapable_statically":
107
+ return "No static support found. If you still suspect this behavior, the sample may resolve APIs in a way static imports miss — detonate dynamically to be sure (static analysis cannot prove a negative under obfuscation).";
108
+ case "analysis_incomplete":
109
+ return "Unpack the sample (e.g. 'upx -d', or dump it from a sandbox) and re-run this check on the unpacked binary; the visible import table is not the real one.";
110
+ case "not_applicable":
111
+ return "This check applies to Windows PE files. Use the analysis tools appropriate for this file type.";
112
+ }
113
+ }
114
+ /** Detect the GetProcAddress + library-loader pair that enables runtime resolution. */
115
+ export function hasDynamicResolution(functionSet) {
116
+ return (DYNAMIC_RESOLVERS.some((a) => apiPresent(a, functionSet)) &&
117
+ LIBRARY_LOADERS.some((a) => apiPresent(a, functionSet)));
118
+ }
119
+ /**
120
+ * Classify one behavior against an import set + context. Pure and deterministic.
121
+ */
122
+ export function classifyPrerequisite(behaviorKey, def, functionSet, ctx) {
123
+ const present = def.apis.filter((a) => apiPresent(a, functionSet));
124
+ const missing = def.apis.filter((a) => !apiPresent(a, functionSet));
125
+ const dynamicResolutionPresent = hasDynamicResolution(functionSet);
126
+ let static_capability;
127
+ let rationale;
128
+ let recommended_followup;
129
+ if (present.length >= def.minMatch) {
130
+ static_capability = "capable_statically";
131
+ rationale =
132
+ `${present.length} of ${def.apis.length} prerequisite APIs are statically imported ` +
133
+ `(${present.join(", ")}). The binary CAN make these calls; this is not evidence the behavior ` +
134
+ `executes at runtime.`;
135
+ recommended_followup = followupFor(static_capability);
136
+ }
137
+ else if (ctx.managed) {
138
+ // A .NET/managed binary calls the framework, not native APIs — the native
139
+ // import table can't establish capability, so don't emit a false negative.
140
+ static_capability = "analysis_incomplete";
141
+ rationale =
142
+ "This is a managed/.NET assembly; native API imports do not reflect managed-code capability (a .NET " +
143
+ "program calls the framework, e.g. System.Windows.Forms.Clipboard, rather than importing native APIs). " +
144
+ "A clean negative cannot be given from the native import table.";
145
+ recommended_followup =
146
+ "Analyze as .NET: decompile with ilspycmd or dnSpy and look for the relevant framework calls " +
147
+ "(e.g. System.Windows.Forms.Clipboard, System.Net.Http, Microsoft.Win32.Registry).";
148
+ }
149
+ else if (ctx.obscured || !ctx.importsParsed) {
150
+ static_capability = "analysis_incomplete";
151
+ rationale = ctx.importsParsed
152
+ ? "The binary appears packed/obscured, so the import table does not reflect its real API surface. A clean negative cannot be given until it is unpacked."
153
+ : "The PE import table could not be read, so static capability cannot be assessed.";
154
+ recommended_followup =
155
+ "Unpack the sample (e.g. 'upx -d', or dump it from a sandbox) and re-run this check on the unpacked " +
156
+ "binary; the visible import table is not the real one.";
157
+ }
158
+ else if (dynamicResolutionPresent) {
159
+ static_capability = "possibly_via_dynamic_resolution";
160
+ rationale =
161
+ "The prerequisite APIs are not statically imported, but GetProcAddress and a library loader are present, " +
162
+ "so the APIs could be resolved at runtime. Static imports alone cannot rule this out — proving which APIs " +
163
+ "are resolved requires cross-reference or dynamic analysis.";
164
+ recommended_followup = followupFor(static_capability);
165
+ }
166
+ else {
167
+ static_capability = "incapable_statically";
168
+ rationale =
169
+ "The prerequisite APIs are not imported and no runtime-resolution primitives (GetProcAddress + a library " +
170
+ "loader) are present, so the behavior is not statically supported. Note: static analysis cannot prove a " +
171
+ "negative under obfuscation.";
172
+ recommended_followup = followupFor(static_capability);
173
+ }
174
+ return {
175
+ behavior: behaviorKey,
176
+ description: def.description,
177
+ required_apis: def.apis,
178
+ apis_present: present,
179
+ apis_missing: missing,
180
+ dynamic_resolution_present: dynamicResolutionPresent,
181
+ static_capability,
182
+ rationale,
183
+ recommended_followup,
184
+ };
185
+ }
186
+ //# sourceMappingURL=behavior-prerequisites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behavior-prerequisites.js","sourceRoot":"","sources":["../../src/analysis/behavior-prerequisites.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Pure structured diff between two related samples (e.g. loader vs unpacked
3
+ * payload). Operates on already-extracted metadata so it is fully unit-testable
4
+ * with synthetic inputs; the handler does the tool runs and feeds this.
5
+ */
6
+ export interface FileMeta {
7
+ file: string;
8
+ file_type: string;
9
+ sha256: string;
10
+ size_bytes: number;
11
+ arch?: string;
12
+ entropy?: number;
13
+ compiler?: string;
14
+ packer?: string;
15
+ /** Imported function names. */
16
+ imports: string[];
17
+ import_count: number;
18
+ /** capa capability names (empty when capa was not run). */
19
+ capabilities: string[];
20
+ /** Whether capa was actually run on this file (depth gate). */
21
+ capa_run: boolean;
22
+ /** Section names. */
23
+ sections: string[];
24
+ }
25
+ export interface SetDiff {
26
+ added: string[];
27
+ removed: string[];
28
+ common_count: number;
29
+ }
30
+ export interface FileDiff {
31
+ file_a: {
32
+ file: string;
33
+ sha256: string;
34
+ };
35
+ file_b: {
36
+ file: string;
37
+ sha256: string;
38
+ };
39
+ size_delta: number;
40
+ entropy_delta?: number;
41
+ architecture?: {
42
+ a?: string;
43
+ b?: string;
44
+ };
45
+ compiler?: {
46
+ a?: string;
47
+ b?: string;
48
+ };
49
+ packer?: {
50
+ a?: string;
51
+ b?: string;
52
+ };
53
+ imports: SetDiff;
54
+ capabilities?: SetDiff;
55
+ sections: SetDiff;
56
+ notes: string[];
57
+ }
58
+ export declare function diffFileMeta(a: FileMeta, b: FileMeta): FileDiff;
59
+ //# sourceMappingURL=file-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-diff.d.ts","sourceRoot":"","sources":["../../src/analysis/file-diff.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAC;IAClB,qBAAqB;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,QAAQ,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,MAAM,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAoBD,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAiC/D"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Pure structured diff between two related samples (e.g. loader vs unpacked
3
+ * payload). Operates on already-extracted metadata so it is fully unit-testable
4
+ * with synthetic inputs; the handler does the tool runs and feeds this.
5
+ */
6
+ const CAP = 100;
7
+ /** Diff two string sets (case-insensitive de-dupe, original casing preserved). */
8
+ function setDiff(a, b) {
9
+ const aSet = new Set(a.map((x) => x.toLowerCase()));
10
+ const bSet = new Set(b.map((x) => x.toLowerCase()));
11
+ const added = [...new Set(b)].filter((x) => !aSet.has(x.toLowerCase()));
12
+ const removed = [...new Set(a)].filter((x) => !bSet.has(x.toLowerCase()));
13
+ let common = 0;
14
+ for (const x of aSet)
15
+ if (bSet.has(x))
16
+ common++;
17
+ const cap = (xs) => (xs.length > CAP ? [...xs.slice(0, CAP), `… and ${xs.length - CAP} more`] : xs);
18
+ return { added: cap(added), removed: cap(removed), common_count: common };
19
+ }
20
+ function changed(a, b) {
21
+ return a !== b ? { a, b } : undefined;
22
+ }
23
+ export function diffFileMeta(a, b) {
24
+ const notes = [];
25
+ const arch = changed(a.arch, b.arch);
26
+ if (arch)
27
+ notes.push(`Architecture changed ${a.arch ?? "?"} → ${b.arch ?? "?"} — a different build/stage.`);
28
+ const compiler = changed(a.compiler, b.compiler);
29
+ if (compiler)
30
+ notes.push(`Compiler/toolchain changed ${a.compiler ?? "?"} → ${b.compiler ?? "?"} (a toolchain mix is a strong multi-author/stage signal).`);
31
+ const packer = changed(a.packer, b.packer);
32
+ const imports = setDiff(a.imports, b.imports);
33
+ const sections = setDiff(a.sections, b.sections);
34
+ const bothCapa = a.capa_run && b.capa_run;
35
+ const capabilities = bothCapa ? setDiff(a.capabilities, b.capabilities) : undefined;
36
+ if (!bothCapa)
37
+ notes.push("Capability (capa) diff omitted — run with depth='standard' on both files for it.");
38
+ const entropy_delta = a.entropy !== undefined && b.entropy !== undefined ? Math.round((b.entropy - a.entropy) * 1000) / 1000 : undefined;
39
+ if (packer)
40
+ notes.push(`Packer differs (${a.packer ?? "none"} → ${b.packer ?? "none"}); compare on the unpacked stages for a meaningful import/capability diff.`);
41
+ return {
42
+ file_a: { file: a.file, sha256: a.sha256 },
43
+ file_b: { file: b.file, sha256: b.sha256 },
44
+ size_delta: b.size_bytes - a.size_bytes,
45
+ ...(entropy_delta !== undefined ? { entropy_delta } : {}),
46
+ ...(arch ? { architecture: arch } : {}),
47
+ ...(compiler ? { compiler } : {}),
48
+ ...(packer ? { packer } : {}),
49
+ imports,
50
+ ...(capabilities ? { capabilities } : {}),
51
+ sections,
52
+ notes,
53
+ };
54
+ }
55
+ //# sourceMappingURL=file-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-diff.js","sourceRoot":"","sources":["../../src/analysis/file-diff.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Analysis utilities for REMnux MCP server.
3
3
  */
4
- export { shouldSummarize, extractKeyLines, generateSummary, type ToolSummary, type AnalysisSummary, type ActionRequired, } from "./summarizer.js";
4
+ export { shouldSummarize, extractKeyLines, generateSummary, summarizeCapabilityEvidence, type ToolSummary, type AnalysisSummary, type ActionRequired, type CapabilityEvidence, } from "./summarizer.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,2BAA2B,EAC3B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,iBAAiB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Analysis utilities for REMnux MCP server.
3
3
  */
4
- export { shouldSummarize, extractKeyLines, generateSummary, } from "./summarizer.js";
4
+ export { shouldSummarize, extractKeyLines, generateSummary, summarizeCapabilityEvidence, } from "./summarizer.js";
5
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,GAIhB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,2BAA2B,GAK5B,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Pure classifier for verify_string_usage: given the strings/sections/xrefs
3
+ * radare2 reported for a queried value, decide whether the string is referenced
4
+ * by code — WITHOUT ever claiming it is "unused".
5
+ *
6
+ * The load-bearing rule (the "negative-only-when-complete" invariant): a
7
+ * `no_code_xrefs_detected` result is emitted ONLY when analysis was provably
8
+ * complete and well-formed. If the radare2 version/schema is unknown, the
9
+ * binary is packed/under-analyzed, output was truncated, or a non-empty xref
10
+ * blob failed to parse, the result is `unknown` — NEVER a negative. This mirrors
11
+ * the check_behavior_prerequisites packed-guard: it refuses to manufacture a
12
+ * confident "no xref" on exactly the obscured samples where static analysis is
13
+ * unreliable.
14
+ */
15
+ import type { R2String, R2Section, R2Xref } from "../parsers/r2.js";
16
+ export type XrefStatus = "referenced_from_code" | "no_code_xrefs_detected" | "data_only" | "unknown";
17
+ export interface MatchGroup {
18
+ value: string;
19
+ /** value === query (vs a substring hit). */
20
+ exact: boolean;
21
+ vaddrs: Array<{
22
+ vaddr: number;
23
+ section: string;
24
+ }>;
25
+ }
26
+ export interface MatchResult {
27
+ value: string;
28
+ exact: boolean;
29
+ found_at: Array<{
30
+ vaddr: string;
31
+ section: string;
32
+ }>;
33
+ xref_status: XrefStatus;
34
+ xref_count: number;
35
+ data_pointer_found?: boolean;
36
+ xref_sources?: Array<{
37
+ from: string;
38
+ fcn?: string;
39
+ opcode?: string;
40
+ }>;
41
+ note: string;
42
+ recommended_followup: string;
43
+ }
44
+ export interface ClassifyContext {
45
+ isCodeFile: boolean;
46
+ fileType: string;
47
+ /** `r2 -v` was parsed, so xref fields are read against a known schema. */
48
+ schemaKnown: boolean;
49
+ /** Analysis ran to completion, not truncated, no parse anomaly, entry resolved. */
50
+ analysisComplete: boolean;
51
+ /** The binary looks packed/obscured (import table / r2 coverage unreliable). */
52
+ obscured: boolean;
53
+ }
54
+ /** Group string hits by distinct value (exact matches first), capped. */
55
+ export declare function selectMatches(strings: R2String[], query: string, maxMatches?: number): {
56
+ groups: MatchGroup[];
57
+ truncated: boolean;
58
+ };
59
+ /**
60
+ * Whether a cross-reference originates from code. When section info is known,
61
+ * the source address must fall in an executable section (this filters `aar`
62
+ * heuristic refs whose source is in a data section). A containing function is
63
+ * surfaced as corroborating evidence; it's used as the signal only when section
64
+ * info is unavailable.
65
+ */
66
+ export declare function isFromCode(xref: R2Xref, sections: R2Section[]): boolean;
67
+ /**
68
+ * Classify one matched string value against the xrefs r2 reported for its
69
+ * address(es). `xrefs` is the parsed union across the group's vaddrs;
70
+ * `rawXrefParseAnomaly` is true when r2 emitted a non-empty xref blob that did
71
+ * not parse (version field drift — must degrade to `unknown`, never a negative).
72
+ */
73
+ export declare function classifyMatch(group: MatchGroup, xrefs: R2Xref[], sections: R2Section[], ctx: ClassifyContext, rawXrefParseAnomaly?: boolean): MatchResult;
74
+ //# sourceMappingURL=string-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string-usage.d.ts","sourceRoot":"","sources":["../../src/analysis/string-usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEpE,MAAM,MAAM,UAAU,GAClB,sBAAsB,GACtB,wBAAwB,GACxB,WAAW,GACX,SAAS,CAAC;AAEd,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,WAAW,EAAE,UAAU,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAC;IACrB,mFAAmF;IACnF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,EAAE,OAAO,CAAC;CACnB;AAID,yEAAyE;AACzE,wBAAgB,aAAa,CAC3B,OAAO,EAAE,QAAQ,EAAE,EACnB,KAAK,EAAE,MAAM,EACb,UAAU,SAAK,GACd;IAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAa9C;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAKvE;AAuCD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,SAAS,EAAE,EACrB,GAAG,EAAE,eAAe,EACpB,mBAAmB,UAAQ,GAC1B,WAAW,CAqDb"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Pure classifier for verify_string_usage: given the strings/sections/xrefs
3
+ * radare2 reported for a queried value, decide whether the string is referenced
4
+ * by code — WITHOUT ever claiming it is "unused".
5
+ *
6
+ * The load-bearing rule (the "negative-only-when-complete" invariant): a
7
+ * `no_code_xrefs_detected` result is emitted ONLY when analysis was provably
8
+ * complete and well-formed. If the radare2 version/schema is unknown, the
9
+ * binary is packed/under-analyzed, output was truncated, or a non-empty xref
10
+ * blob failed to parse, the result is `unknown` — NEVER a negative. This mirrors
11
+ * the check_behavior_prerequisites packed-guard: it refuses to manufacture a
12
+ * confident "no xref" on exactly the obscured samples where static analysis is
13
+ * unreliable.
14
+ */
15
+ const hex = (n) => "0x" + (n >>> 0 === n ? n.toString(16) : n.toString(16));
16
+ /** Group string hits by distinct value (exact matches first), capped. */
17
+ export function selectMatches(strings, query, maxMatches = 50) {
18
+ const byValue = new Map();
19
+ for (const s of strings) {
20
+ if (!s.value.includes(query))
21
+ continue;
22
+ let g = byValue.get(s.value);
23
+ if (!g) {
24
+ g = { value: s.value, exact: s.value === query, vaddrs: [] };
25
+ byValue.set(s.value, g);
26
+ }
27
+ g.vaddrs.push({ vaddr: s.vaddr, section: s.section });
28
+ }
29
+ const all = [...byValue.values()].sort((a, b) => (a.exact === b.exact ? 0 : a.exact ? -1 : 1));
30
+ return { groups: all.slice(0, maxMatches), truncated: all.length > maxMatches };
31
+ }
32
+ /**
33
+ * Whether a cross-reference originates from code. When section info is known,
34
+ * the source address must fall in an executable section (this filters `aar`
35
+ * heuristic refs whose source is in a data section). A containing function is
36
+ * surfaced as corroborating evidence; it's used as the signal only when section
37
+ * info is unavailable.
38
+ */
39
+ export function isFromCode(xref, sections) {
40
+ if (sections.length > 0) {
41
+ return sections.some((s) => s.exec && xref.from >= s.vaddr && xref.from < s.vaddrEnd);
42
+ }
43
+ return xref.fcnAddr !== undefined || !!xref.fcnName;
44
+ }
45
+ const NOTE_REFERENCED = "At least one instruction in an executable section references this string's address. The string is reachable " +
46
+ "from recovered code, consistent with operational use. This does not prove the referencing code executes in " +
47
+ "any given run; confirm runtime use dynamically if the distinction matters.";
48
+ const NOTE_NO_XREFS = "No direct code cross-reference to this string was found by static analysis (radare2 aa; aar). This is NOT " +
49
+ "evidence the string is unused. The reference may be computed at runtime (pointer arithmetic, indexing into a " +
50
+ "string table, stack/heap-built copies), resolved indirectly (pointer tables, relocations, vtables/RTTI), " +
51
+ "reachable only from code the analyzer did not recover (packed/encrypted/obfuscated regions, overlays, or " +
52
+ "functions analysis missed), or used by a separate component. Absence of a static xref means 'no DIRECT " +
53
+ "reference was resolvable here', not 'the string is dead or vestigial'. Do not dismiss it as a non-indicator " +
54
+ "on this basis alone; confirm with dynamic analysis or manual review.";
55
+ const NOTE_DATA_POINTER = " A reference from a non-executable (data) section was found (the address is stored in a pointer/data table); " +
56
+ "this is still not a code reference and does not by itself establish operational use.";
57
+ const NOTE_DATA_ONLY = (ft) => `This file is not an executable (detected type: ${ft}). It contains no code that could cross-reference the ` +
58
+ "string, so code-xref analysis does not apply. Evaluate the string as file content on its own merits.";
59
+ const NOTE_UNKNOWN = "Cross-reference analysis could not be completed reliably (radare2 unavailable, analysis timed out, the binary " +
60
+ "appears packed/obscured, output was truncated, or the version's output could not be parsed). The xref status " +
61
+ "is UNKNOWN — this is explicitly NOT 'no xref found' and must not be read as evidence the string is unused. " +
62
+ "Unpack first, re-run with a longer timeout (depth=deep), or analyze manually.";
63
+ const FOLLOWUP = {
64
+ referenced_from_code: "Confirm runtime use with emulation (speakeasy) or sandbox detonation; a static reference does not guarantee the path runs.",
65
+ no_code_xrefs_detected: "If this string matters to your assessment, confirm via dynamic analysis or manual review — do NOT record it as unused.",
66
+ data_only: "Use the analysis tools appropriate for this file type.",
67
+ unknown: "Unpack the sample, increase the timeout (depth=deep), or analyze manually, then re-check.",
68
+ };
69
+ /**
70
+ * Classify one matched string value against the xrefs r2 reported for its
71
+ * address(es). `xrefs` is the parsed union across the group's vaddrs;
72
+ * `rawXrefParseAnomaly` is true when r2 emitted a non-empty xref blob that did
73
+ * not parse (version field drift — must degrade to `unknown`, never a negative).
74
+ */
75
+ export function classifyMatch(group, xrefs, sections, ctx, rawXrefParseAnomaly = false) {
76
+ const found_at = group.vaddrs.map((v) => ({ vaddr: hex(v.vaddr), section: v.section }));
77
+ const base = { value: group.value, exact: group.exact, found_at };
78
+ if (!ctx.isCodeFile) {
79
+ return {
80
+ ...base,
81
+ xref_status: "data_only",
82
+ xref_count: 0,
83
+ note: NOTE_DATA_ONLY(ctx.fileType || "unknown"),
84
+ recommended_followup: FOLLOWUP.data_only,
85
+ };
86
+ }
87
+ const codeXrefs = xrefs.filter((x) => isFromCode(x, sections));
88
+ if (codeXrefs.length > 0) {
89
+ const xref_sources = codeXrefs.slice(0, 10).map((x) => ({
90
+ from: hex(x.from),
91
+ ...(x.fcnName ? { fcn: x.fcnName } : {}),
92
+ ...(x.opcode ? { opcode: x.opcode } : {}),
93
+ }));
94
+ return {
95
+ ...base,
96
+ xref_status: "referenced_from_code",
97
+ xref_count: codeXrefs.length,
98
+ xref_sources,
99
+ note: NOTE_REFERENCED,
100
+ recommended_followup: FOLLOWUP.referenced_from_code,
101
+ };
102
+ }
103
+ // No code xref. A negative is allowed ONLY when analysis was provably complete.
104
+ const degraded = !ctx.schemaKnown || !ctx.analysisComplete || ctx.obscured || rawXrefParseAnomaly;
105
+ if (degraded) {
106
+ return {
107
+ ...base,
108
+ xref_status: "unknown",
109
+ xref_count: 0,
110
+ note: NOTE_UNKNOWN,
111
+ recommended_followup: FOLLOWUP.unknown,
112
+ };
113
+ }
114
+ const dataPointerFound = xrefs.length > 0; // refs exist, but none from code
115
+ return {
116
+ ...base,
117
+ xref_status: "no_code_xrefs_detected",
118
+ xref_count: 0,
119
+ ...(dataPointerFound ? { data_pointer_found: true } : {}),
120
+ note: NOTE_NO_XREFS + (dataPointerFound ? NOTE_DATA_POINTER : ""),
121
+ recommended_followup: FOLLOWUP.no_code_xrefs_detected,
122
+ };
123
+ }
124
+ //# sourceMappingURL=string-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string-usage.js","sourceRoot":"","sources":["../../src/analysis/string-usage.ts"],"names":[],"mappings":"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"}