sekant-intercept-js 1.0.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/LICENSE +231 -0
- package/NOTICE +15 -0
- package/README.md +327 -0
- package/dist/sekant-intercept.browser.mjs +15 -0
- package/dist/sekant-intercept.node.mjs +15 -0
- package/package.json +69 -0
- package/src/ahocorasickEngine.mjs +229 -0
- package/src/elfModule.mjs +306 -0
- package/src/hashModule.mjs +284 -0
- package/src/index.js +9 -0
- package/src/interceptCustomModules.mjs +230 -0
- package/src/interceptScanner.mjs +865 -0
- package/src/mathModule.mjs +506 -0
- package/src/peModule.mjs +295 -0
- package/src/performanceInstrumentation.mjs +172 -0
- package/src/sharedUtils.mjs +127 -0
- package/src/stringModule.mjs +43 -0
- package/src/timeModule.mjs +19 -0
- package/src/yaraConditionParser.mjs +1083 -0
- package/src/yaraConditionsMatch.mjs +1373 -0
- package/src/yaraRuleCompiler.mjs +448 -0
- package/src/yaraStringMatch.mjs +449 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Rishi Kant (Sekant Security Inc.)
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { computeEntropy, digestHex, calculateMD5, readUint16, readUint32, readUint64, readString, toHex } from "./sharedUtils.mjs";
|
|
18
|
+
|
|
19
|
+
const ELF_MAGIC = [0x7f, 0x45, 0x4c, 0x46];
|
|
20
|
+
const DT_NEEDED = 1;
|
|
21
|
+
const DT_SONAME = 14;
|
|
22
|
+
const DT_RPATH = 15;
|
|
23
|
+
const DT_RUNPATH = 29;
|
|
24
|
+
|
|
25
|
+
/* -------------------------
|
|
26
|
+
ELF Header
|
|
27
|
+
------------------------- */
|
|
28
|
+
function parseElfHeader(buf) {
|
|
29
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
30
|
+
for (let i = 0; i < 4; i++) if (buf[i] !== ELF_MAGIC[i]) throw new Error("Not ELF");
|
|
31
|
+
|
|
32
|
+
const elfClass = buf[4];
|
|
33
|
+
const littleEndian = buf[5] === 1;
|
|
34
|
+
const header = { is64bit: elfClass === 2, littleEndian };
|
|
35
|
+
|
|
36
|
+
// Parse type and machine (standard ELF header fields)
|
|
37
|
+
header.type = readUint16(view, 0x10, littleEndian);
|
|
38
|
+
header.machine = readUint16(view, 0x12, littleEndian);
|
|
39
|
+
|
|
40
|
+
header.entry_point = header.is64bit ? readUint64(view, 0x18, littleEndian) : readUint32(view, 0x18, littleEndian);
|
|
41
|
+
header.phoff = header.is64bit ? readUint64(view, 0x20, littleEndian) : readUint32(view, 0x1C, littleEndian);
|
|
42
|
+
header.shoff = header.is64bit ? readUint64(view, 0x28, littleEndian) : readUint32(view, 0x20, littleEndian);
|
|
43
|
+
header.phentsize = header.is64bit ? readUint16(view, 0x36, littleEndian) : readUint16(view, 0x2A, littleEndian);
|
|
44
|
+
header.phnum = header.is64bit ? readUint16(view, 0x38, littleEndian) : readUint16(view, 0x2C, littleEndian);
|
|
45
|
+
header.shentsize = header.is64bit ? readUint16(view, 0x3A, littleEndian) : readUint16(view, 0x2E, littleEndian);
|
|
46
|
+
header.shnum = header.is64bit ? readUint16(view, 0x3C, littleEndian) : readUint16(view, 0x30, littleEndian);
|
|
47
|
+
header.shstrndx = header.is64bit ? readUint16(view, 0x3E, littleEndian) : readUint16(view, 0x32, littleEndian);
|
|
48
|
+
return header;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* -------------------------
|
|
52
|
+
Program headers
|
|
53
|
+
------------------------- */
|
|
54
|
+
function parseProgramHeaders(buf, header) {
|
|
55
|
+
const phs = [];
|
|
56
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
57
|
+
let offset = header.phoff;
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < header.phnum; i++) {
|
|
60
|
+
let p_type, p_offset, p_vaddr, p_filesz, p_memsz, p_flags, p_align;
|
|
61
|
+
if (header.is64bit) {
|
|
62
|
+
p_type = readUint32(view, offset, header.littleEndian);
|
|
63
|
+
p_flags = readUint32(view, offset + 4, header.littleEndian);
|
|
64
|
+
p_offset = readUint64(view, offset + 8, header.littleEndian);
|
|
65
|
+
p_vaddr = readUint64(view, offset + 16, header.littleEndian);
|
|
66
|
+
p_filesz = readUint64(view, offset + 32, header.littleEndian);
|
|
67
|
+
p_memsz = readUint64(view, offset + 40, header.littleEndian);
|
|
68
|
+
p_align = readUint64(view, offset + 48, header.littleEndian);
|
|
69
|
+
offset += 56;
|
|
70
|
+
} else {
|
|
71
|
+
p_type = readUint32(view, offset, header.littleEndian);
|
|
72
|
+
p_offset = readUint32(view, offset + 4, header.littleEndian);
|
|
73
|
+
p_vaddr = readUint32(view, offset + 8, header.littleEndian);
|
|
74
|
+
p_filesz = readUint32(view, offset + 16, header.littleEndian);
|
|
75
|
+
p_memsz = readUint32(view, offset + 20, header.littleEndian);
|
|
76
|
+
p_flags = readUint32(view, offset + 24, header.littleEndian);
|
|
77
|
+
p_align = readUint32(view, offset + 28, header.littleEndian);
|
|
78
|
+
offset += 32;
|
|
79
|
+
}
|
|
80
|
+
phs.push({ type: p_type, offset: p_offset, vaddr: p_vaddr, filesz: p_filesz, memsz: p_memsz, flags: p_flags, align: p_align });
|
|
81
|
+
}
|
|
82
|
+
return phs;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* -------------------------
|
|
86
|
+
Sections + hashes
|
|
87
|
+
------------------------- */
|
|
88
|
+
async function parseSections(buf, header) {
|
|
89
|
+
const shs = [];
|
|
90
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
91
|
+
let offset = header.shoff;
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < header.shnum; i++) {
|
|
94
|
+
const sh_offset = header.is64bit ? readUint64(view, offset + 24, header.littleEndian) : readUint32(view, offset + 16, header.littleEndian);
|
|
95
|
+
const sh_size = header.is64bit ? readUint64(view, offset + 32, header.littleEndian) : readUint32(view, offset + 20, header.littleEndian);
|
|
96
|
+
const sectionData = sh_offset + sh_size <= buf.length ? buf.slice(sh_offset, sh_offset + sh_size) : new Uint8Array();
|
|
97
|
+
|
|
98
|
+
// Compute hashes using shared utilities (browser-compatible)
|
|
99
|
+
const md5Hash = calculateMD5(sectionData);
|
|
100
|
+
const sha1Hash = await digestHex("SHA-1", sectionData);
|
|
101
|
+
const sha256Hash = await digestHex("SHA-256", sectionData);
|
|
102
|
+
|
|
103
|
+
shs.push({
|
|
104
|
+
offset: sh_offset,
|
|
105
|
+
size: sh_size,
|
|
106
|
+
entropy: computeEntropy(sectionData),
|
|
107
|
+
raw_data: sectionData,
|
|
108
|
+
md5: md5Hash,
|
|
109
|
+
sha1: sha1Hash,
|
|
110
|
+
sha256: sha256Hash,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
offset += header.is64bit ? 64 : 40;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return shs;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* -------------------------
|
|
120
|
+
Dynamic dependencies parsing
|
|
121
|
+
------------------------- */
|
|
122
|
+
function parseDynamicDependencies(buf, sections, header) {
|
|
123
|
+
const dynSection = sections.find(s => s.raw_data && s.raw_data.length > 0 && s.name === ".dynamic");
|
|
124
|
+
const dynstrSection = sections.find(s => s.raw_data && s.raw_data.length > 0 && s.name === ".dynstr");
|
|
125
|
+
if (!dynSection || !dynstrSection) return { needed_libraries: [], soname: null, rpath: null, runpath: null };
|
|
126
|
+
|
|
127
|
+
const dynBuf = dynSection.raw_data;
|
|
128
|
+
const strBuf = dynstrSection.raw_data;
|
|
129
|
+
const view = new DataView(dynBuf.buffer, dynBuf.byteOffset, dynBuf.byteLength);
|
|
130
|
+
const entrySize = header.is64bit ? 16 : 8;
|
|
131
|
+
|
|
132
|
+
const needed_libraries = [];
|
|
133
|
+
let soname = null;
|
|
134
|
+
let rpath = null;
|
|
135
|
+
let runpath = null;
|
|
136
|
+
|
|
137
|
+
for (let offset = 0; offset + entrySize <= dynBuf.length; offset += entrySize) {
|
|
138
|
+
const tag = header.is64bit ? readUint64(view, offset, header.littleEndian) : readUint32(view, offset, header.littleEndian);
|
|
139
|
+
const val = header.is64bit ? readUint64(view, offset + 8, header.littleEndian) : readUint32(view, offset + 4, header.littleEndian);
|
|
140
|
+
|
|
141
|
+
if (tag === DT_NEEDED) needed_libraries.push(readString(strBuf, val));
|
|
142
|
+
else if (tag === DT_SONAME) soname = readString(strBuf, val);
|
|
143
|
+
else if (tag === DT_RPATH) rpath = readString(strBuf, val);
|
|
144
|
+
else if (tag === DT_RUNPATH) runpath = readString(strBuf, val);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { needed_libraries, soname, rpath, runpath };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* -------------------------
|
|
151
|
+
Exported symbols
|
|
152
|
+
------------------------- */
|
|
153
|
+
function parseExports(buf, sections, header) {
|
|
154
|
+
const exportsArr = [];
|
|
155
|
+
const dynsym = sections.find(s => s.name === ".dynsym");
|
|
156
|
+
const dynstr = sections.find(s => s.name === ".dynstr");
|
|
157
|
+
if (!dynsym || !dynstr) return exportsArr;
|
|
158
|
+
|
|
159
|
+
const symBuf = dynsym.raw_data;
|
|
160
|
+
const strBuf = dynstr.raw_data;
|
|
161
|
+
const view = new DataView(symBuf.buffer, symBuf.byteOffset, symBuf.byteLength);
|
|
162
|
+
const entrySize = header.is64bit ? 24 : 16;
|
|
163
|
+
|
|
164
|
+
for (let offset = 0; offset + entrySize <= symBuf.length; offset += entrySize) {
|
|
165
|
+
const nameOffset = readUint32(view, offset, header.littleEndian);
|
|
166
|
+
if (nameOffset >= strBuf.length) continue;
|
|
167
|
+
const name = readString(strBuf, nameOffset);
|
|
168
|
+
if (name) exportsArr.push(name);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return exportsArr;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* -------------------------
|
|
175
|
+
Main YARA-Compatible ELF Parser
|
|
176
|
+
------------------------- */
|
|
177
|
+
export async function parseELFYaraFull(inputBytes) {
|
|
178
|
+
if (inputBytes instanceof ArrayBuffer) inputBytes = new Uint8Array(inputBytes);
|
|
179
|
+
const file_size = inputBytes.length;
|
|
180
|
+
|
|
181
|
+
let header;
|
|
182
|
+
try { header = parseElfHeader(inputBytes); }
|
|
183
|
+
catch (e) { return { error: "Invalid ELF file", message: e.message }; }
|
|
184
|
+
|
|
185
|
+
const program_headers = parseProgramHeaders(inputBytes, header);
|
|
186
|
+
const sections = await parseSections(inputBytes, header);
|
|
187
|
+
const { needed_libraries, soname, rpath, runpath } = parseDynamicDependencies(inputBytes, sections, header);
|
|
188
|
+
const exportsArr = parseExports(inputBytes, sections, header);
|
|
189
|
+
|
|
190
|
+
const vaddr = (program_headers?.map(ph => ph.vaddr) || [0])[0];
|
|
191
|
+
const offset = (program_headers?.map(ph => ph.offset) || [0])[0];
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
type: header.type,
|
|
195
|
+
machine: header.machine,
|
|
196
|
+
entry_point: header.entry_point - vaddr + offset,
|
|
197
|
+
is_64bit: header.is64bit,
|
|
198
|
+
endianness: header.littleEndian ? "little" : "big",
|
|
199
|
+
file_size,
|
|
200
|
+
program_headers,
|
|
201
|
+
sections,
|
|
202
|
+
imports: needed_libraries.map(lib => ({ dll: lib, functions: [] })),
|
|
203
|
+
exports: exportsArr,
|
|
204
|
+
needed_libraries,
|
|
205
|
+
soname,
|
|
206
|
+
rpath,
|
|
207
|
+
runpath,
|
|
208
|
+
hashes: {}, // optional whole-file hashes
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* -------------------------
|
|
213
|
+
Create YARA-Compatible ELF Module
|
|
214
|
+
------------------------- */
|
|
215
|
+
export function createELFModule(parsedELF) {
|
|
216
|
+
if (!parsedELF || parsedELF.error) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Create module with all ELF properties
|
|
221
|
+
const elfModule = {
|
|
222
|
+
// Basic properties
|
|
223
|
+
type: parsedELF.type || 0,
|
|
224
|
+
machine: parsedELF.machine || 0,
|
|
225
|
+
entry_point: parsedELF.entry_point || 0,
|
|
226
|
+
file_size: parsedELF.file_size || 0,
|
|
227
|
+
is_64bit: parsedELF.is_64bit || false,
|
|
228
|
+
endianness: parsedELF.endianness || "little",
|
|
229
|
+
|
|
230
|
+
// Sections
|
|
231
|
+
sections: parsedELF.sections || [],
|
|
232
|
+
number_of_sections: (parsedELF.sections || []).length,
|
|
233
|
+
|
|
234
|
+
// Program headers (also exposed as "segments" for Python YARA compatibility)
|
|
235
|
+
program_headers: parsedELF.program_headers || [],
|
|
236
|
+
segments: parsedELF.program_headers || [], // Alias for Python YARA compatibility
|
|
237
|
+
number_of_segments: (parsedELF.program_headers || []).length,
|
|
238
|
+
|
|
239
|
+
// Imports/Exports
|
|
240
|
+
imports: parsedELF.needed_libraries || [],
|
|
241
|
+
exports: parsedELF.exports || [],
|
|
242
|
+
needed_libraries: parsedELF.needed_libraries || [],
|
|
243
|
+
|
|
244
|
+
// Dynamic linking info
|
|
245
|
+
soname: parsedELF.soname || null,
|
|
246
|
+
rpath: parsedELF.rpath || null,
|
|
247
|
+
runpath: parsedELF.runpath || null,
|
|
248
|
+
|
|
249
|
+
// Helper methods
|
|
250
|
+
is_32bit() {
|
|
251
|
+
return !this.is_64bit;
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
is_little_endian() {
|
|
255
|
+
return this.endianness === "little";
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
is_big_endian() {
|
|
259
|
+
return this.endianness === "big";
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
// Check if imports a specific library
|
|
263
|
+
imports_library(libName) {
|
|
264
|
+
return this.needed_libraries.some(lib =>
|
|
265
|
+
lib.toLowerCase().includes(libName.toLowerCase())
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
// Check if exports a specific function
|
|
270
|
+
exports_function(funcName) {
|
|
271
|
+
return this.exports.some(exp => exp === funcName);
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
// Get section by name
|
|
275
|
+
get_section_by_name(name) {
|
|
276
|
+
return this.sections.find(s => s.name === name);
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// ELF constants (matching YARA standard)
|
|
280
|
+
// ELF types (e_type)
|
|
281
|
+
ET_NONE: 0,
|
|
282
|
+
ET_REL: 1,
|
|
283
|
+
ET_EXEC: 2,
|
|
284
|
+
ET_DYN: 3,
|
|
285
|
+
ET_CORE: 4,
|
|
286
|
+
|
|
287
|
+
// ELF machine types (e_machine)
|
|
288
|
+
EM_NONE: 0,
|
|
289
|
+
EM_386: 3, // Intel 80386
|
|
290
|
+
EM_X86_64: 62, // AMD x86-64
|
|
291
|
+
EM_ARM: 40, // ARM
|
|
292
|
+
EM_AARCH64: 183, // ARM 64-bit
|
|
293
|
+
|
|
294
|
+
// Legacy names for compatibility
|
|
295
|
+
ARCH_X86: 3,
|
|
296
|
+
ARCH_X86_64: 62,
|
|
297
|
+
ARCH_ARM: 40,
|
|
298
|
+
ARCH_AARCH64: 183,
|
|
299
|
+
|
|
300
|
+
TYPE_EXEC: 2,
|
|
301
|
+
TYPE_DYN: 3,
|
|
302
|
+
TYPE_CORE: 4,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
return elfModule;
|
|
306
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Rishi Kant (Sekant Security Inc.)
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* YARA Hash Module Implementation
|
|
19
|
+
*
|
|
20
|
+
* Browser-compatible implementation of YARA's hash module functions.
|
|
21
|
+
* Uses native Web Crypto API for SHA1 and SHA256, and spark-md5 for MD5.
|
|
22
|
+
*
|
|
23
|
+
* Supported functions:
|
|
24
|
+
* - md5(offset, size) : Calculate MD5 hash
|
|
25
|
+
* - md5(string) : Calculate MD5 hash of string
|
|
26
|
+
* - sha1(offset, size) : Calculate SHA1 hash
|
|
27
|
+
* - sha1(string) : Calculate SHA1 hash of string
|
|
28
|
+
* - sha256(offset, size) : Calculate SHA256 hash
|
|
29
|
+
* - sha256(string) : Calculate SHA256 hash of string
|
|
30
|
+
* - checksum32(offset, size) : Calculate 32-bit checksum
|
|
31
|
+
* - checksum32(string) : Calculate 32-bit checksum of string
|
|
32
|
+
* - crc32(offset, size) : Calculate CRC32
|
|
33
|
+
* - crc32(string) : Calculate CRC32 of string
|
|
34
|
+
*
|
|
35
|
+
* All hash functions return lowercase hexadecimal strings.
|
|
36
|
+
*
|
|
37
|
+
* @see https://yara.readthedocs.io/en/stable/modules/hash.html
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { digestHex, calculateMD5 } from './sharedUtils.mjs';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Convert string to Uint8Array
|
|
44
|
+
* @param {string} str
|
|
45
|
+
* @returns {Uint8Array}
|
|
46
|
+
*/
|
|
47
|
+
function stringToBytes(str) {
|
|
48
|
+
const encoder = new TextEncoder();
|
|
49
|
+
return encoder.encode(str);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Calculate 32-bit checksum (sum of all bytes modulo 2^32)
|
|
54
|
+
* @param {Uint8Array} data
|
|
55
|
+
* @returns {number}
|
|
56
|
+
*/
|
|
57
|
+
function calculateChecksum32(data) {
|
|
58
|
+
let sum = 0;
|
|
59
|
+
for (let i = 0; i < data.length; i++) {
|
|
60
|
+
sum = (sum + data[i]) >>> 0; // Keep as 32-bit unsigned
|
|
61
|
+
}
|
|
62
|
+
return sum & 0xFFFFFFFF;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* CRC32 lookup table
|
|
67
|
+
*/
|
|
68
|
+
const CRC32_TABLE = (() => {
|
|
69
|
+
const table = new Uint32Array(256);
|
|
70
|
+
for (let i = 0; i < 256; i++) {
|
|
71
|
+
let crc = i;
|
|
72
|
+
for (let j = 0; j < 8; j++) {
|
|
73
|
+
crc = (crc & 1) ? (0xEDB88320 ^ (crc >>> 1)) : (crc >>> 1);
|
|
74
|
+
}
|
|
75
|
+
table[i] = crc;
|
|
76
|
+
}
|
|
77
|
+
return table;
|
|
78
|
+
})();
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Calculate CRC32 checksum
|
|
82
|
+
* @param {Uint8Array} data
|
|
83
|
+
* @returns {number}
|
|
84
|
+
*/
|
|
85
|
+
function calculateCRC32(data) {
|
|
86
|
+
let crc = 0xFFFFFFFF;
|
|
87
|
+
for (let i = 0; i < data.length; i++) {
|
|
88
|
+
crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ data[i]) & 0xFF];
|
|
89
|
+
}
|
|
90
|
+
crc = (crc ^ 0xFFFFFFFF) >>> 0;
|
|
91
|
+
return crc;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create a hash module instance for a specific data buffer
|
|
96
|
+
* @param {Uint8Array} data - The data to compute hashes on
|
|
97
|
+
* @returns {Object} Hash module with all YARA hash functions
|
|
98
|
+
*/
|
|
99
|
+
export function createHashModule(data) {
|
|
100
|
+
if (!data || !(data instanceof Uint8Array)) {
|
|
101
|
+
throw new Error('Hash module requires a Uint8Array data buffer');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
/**
|
|
106
|
+
* Calculate MD5 hash
|
|
107
|
+
* Can be called with (offset, size) or (string)
|
|
108
|
+
*/
|
|
109
|
+
md5: async function(...args) {
|
|
110
|
+
if (args.length === 1 && typeof args[0] === 'string') {
|
|
111
|
+
// md5(string)
|
|
112
|
+
const bytes = stringToBytes(args[0]);
|
|
113
|
+
return calculateMD5(bytes);
|
|
114
|
+
} else if (args.length === 2) {
|
|
115
|
+
// md5(offset, size)
|
|
116
|
+
const offset = args[0];
|
|
117
|
+
const size = args[1];
|
|
118
|
+
if (offset < 0 || size < 0) {
|
|
119
|
+
throw new Error(`Invalid range: offset=${offset}, size=${size}, data.length=${data.length}`);
|
|
120
|
+
}
|
|
121
|
+
const slice = data.slice(offset, offset + size);
|
|
122
|
+
return calculateMD5(slice);
|
|
123
|
+
} else {
|
|
124
|
+
throw new Error('md5() requires either (offset, size) or (string)');
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Calculate SHA1 hash
|
|
130
|
+
* Can be called with (offset, size) or (string)
|
|
131
|
+
*/
|
|
132
|
+
sha1: async function(...args) {
|
|
133
|
+
if (args.length === 1 && typeof args[0] === 'string') {
|
|
134
|
+
// sha1(string)
|
|
135
|
+
const bytes = stringToBytes(args[0]);
|
|
136
|
+
return await digestHex('SHA-1', bytes);
|
|
137
|
+
} else if (args.length === 2) {
|
|
138
|
+
// sha1(offset, size)
|
|
139
|
+
const offset = args[0];
|
|
140
|
+
const size = args[1];
|
|
141
|
+
if (offset < 0 || size < 0 || offset + size > data.length) {
|
|
142
|
+
throw new Error(`Invalid range: offset=${offset}, size=${size}, data.length=${data.length}`);
|
|
143
|
+
}
|
|
144
|
+
const slice = data.slice(offset, offset + size);
|
|
145
|
+
return await digestHex('SHA-1', slice);
|
|
146
|
+
} else {
|
|
147
|
+
throw new Error('sha1() requires either (offset, size) or (string)');
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Calculate SHA256 hash
|
|
153
|
+
* Can be called with (offset, size) or (string)
|
|
154
|
+
*/
|
|
155
|
+
sha256: async function(...args) {
|
|
156
|
+
if (args.length === 1 && typeof args[0] === 'string') {
|
|
157
|
+
// sha256(string)
|
|
158
|
+
const bytes = stringToBytes(args[0]);
|
|
159
|
+
return await digestHex('SHA-256', bytes);
|
|
160
|
+
} else if (args.length === 2) {
|
|
161
|
+
// sha256(offset, size)
|
|
162
|
+
const offset = args[0];
|
|
163
|
+
const size = args[1];
|
|
164
|
+
if (offset < 0 || size < 0 || offset + size > data.length) {
|
|
165
|
+
throw new Error(`Invalid range: offset=${offset}, size=${size}, data.length=${data.length}`);
|
|
166
|
+
}
|
|
167
|
+
const slice = data.slice(offset, offset + size);
|
|
168
|
+
return await digestHex('SHA-256', slice);
|
|
169
|
+
} else {
|
|
170
|
+
throw new Error('sha256() requires either (offset, size) or (string)');
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Calculate 32-bit checksum
|
|
176
|
+
* Can be called with (offset, size) or (string)
|
|
177
|
+
*/
|
|
178
|
+
checksum32: function(...args) {
|
|
179
|
+
if (args.length === 1 && typeof args[0] === 'string') {
|
|
180
|
+
// checksum32(string)
|
|
181
|
+
const bytes = stringToBytes(args[0]);
|
|
182
|
+
return calculateChecksum32(bytes);
|
|
183
|
+
} else if (args.length === 2) {
|
|
184
|
+
// checksum32(offset, size)
|
|
185
|
+
const offset = args[0];
|
|
186
|
+
const size = args[1];
|
|
187
|
+
if (offset < 0 || size < 0) {
|
|
188
|
+
throw new Error(`Invalid range: offset=${offset}, size=${size}, data.length=${data.length}`);
|
|
189
|
+
}
|
|
190
|
+
const slice = data.slice(offset, offset + size);
|
|
191
|
+
if (slice.length === 0) return 0;
|
|
192
|
+
return calculateChecksum32(slice);
|
|
193
|
+
} else {
|
|
194
|
+
throw new Error('checksum32() requires either (offset, size) or (string)');
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Calculate CRC32 checksum
|
|
200
|
+
* Can be called with (offset, size) or (string)
|
|
201
|
+
*/
|
|
202
|
+
crc32: function(...args) {
|
|
203
|
+
if (args.length === 1 && typeof args[0] === 'string') {
|
|
204
|
+
// crc32(string)
|
|
205
|
+
const bytes = stringToBytes(args[0]);
|
|
206
|
+
return calculateCRC32(bytes);
|
|
207
|
+
} else if (args.length === 2) {
|
|
208
|
+
// crc32(offset, size)
|
|
209
|
+
const offset = args[0];
|
|
210
|
+
const size = args[1];
|
|
211
|
+
if (offset < 0 || size < 0) {
|
|
212
|
+
throw new Error(`Invalid range: offset=${offset}, size=${size}, data.length=${data.length}`);
|
|
213
|
+
}
|
|
214
|
+
const slice = data.slice(offset, offset + size);
|
|
215
|
+
if (slice.length === 0) return 0;
|
|
216
|
+
return calculateCRC32(slice);
|
|
217
|
+
} else {
|
|
218
|
+
throw new Error('crc32() requires either (offset, size) or (string)');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Standalone hash functions that don't require a data buffer
|
|
226
|
+
* (for string hashing only)
|
|
227
|
+
*/
|
|
228
|
+
export const hash = {
|
|
229
|
+
/**
|
|
230
|
+
* Calculate MD5 hash of a string
|
|
231
|
+
* @param {string} str
|
|
232
|
+
* @returns {Promise<string>} Hex string
|
|
233
|
+
*/
|
|
234
|
+
md5: async function(str) {
|
|
235
|
+
const bytes = stringToBytes(str);
|
|
236
|
+
return calculateMD5(bytes);
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Calculate SHA1 hash of a string
|
|
241
|
+
* @param {string} str
|
|
242
|
+
* @returns {Promise<string>} Hex string
|
|
243
|
+
*/
|
|
244
|
+
sha1: async function(str) {
|
|
245
|
+
const bytes = stringToBytes(str);
|
|
246
|
+
return await digestHex('SHA-1', bytes);
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Calculate SHA256 hash of a string
|
|
251
|
+
* @param {string} str
|
|
252
|
+
* @returns {Promise<string>} Hex string
|
|
253
|
+
*/
|
|
254
|
+
sha256: async function(str) {
|
|
255
|
+
const bytes = stringToBytes(str);
|
|
256
|
+
return await digestHex('SHA-256', bytes);
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Calculate 32-bit checksum of a string
|
|
261
|
+
* @param {string} str
|
|
262
|
+
* @returns {string} Hex string
|
|
263
|
+
*/
|
|
264
|
+
checksum32: function(str) {
|
|
265
|
+
const bytes = stringToBytes(str);
|
|
266
|
+
return calculateChecksum32(bytes);
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Calculate CRC32 of a string
|
|
271
|
+
* @param {string} str
|
|
272
|
+
* @returns {string} Hex string
|
|
273
|
+
*/
|
|
274
|
+
crc32: function(str) {
|
|
275
|
+
const bytes = stringToBytes(str);
|
|
276
|
+
return calculateCRC32(bytes);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Export for convenience
|
|
281
|
+
export default {
|
|
282
|
+
createHashModule,
|
|
283
|
+
hash
|
|
284
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { InterceptScanner, YaraScanner } from './interceptScanner.mjs';
|
|
2
|
+
|
|
3
|
+
export { parsePEYara, createPEModule } from "./peModule.mjs";
|
|
4
|
+
export { parseELFYaraFull, createELFModule } from "./elfModule.mjs";
|
|
5
|
+
export { createMathModule, math } from "./mathModule.mjs";
|
|
6
|
+
export { createHashModule, hash } from "./hashModule.mjs";
|
|
7
|
+
export { time } from "./timeModule.mjs";
|
|
8
|
+
export { string } from "./stringModule.mjs";
|
|
9
|
+
export { BaseCustomModule } from "./interceptCustomModules.mjs";
|