@specy/x86 1.0.4 → 1.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specy/x86",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "An x86 interpreter for Javascript",
5
5
  "homepage": "https://github.com/Specy/x86-js#readme",
6
6
  "bugs": {
@@ -10,6 +10,9 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/Specy/x86-js.git"
12
12
  },
13
+ "files": [
14
+ "dist"
15
+ ],
13
16
  "license": "GPL-2.0",
14
17
  "author": "Specy",
15
18
  "type": "module",
@@ -1,13 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <profile version="1.0">
3
- <option name="myName" value="Project Default" />
4
- <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
5
- <option name="ignoredErrors">
6
- <list>
7
- <option value="N801" />
8
- <option value="N806" />
9
- </list>
10
- </option>
11
- </inspection_tool>
12
- </profile>
13
- </component>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/x86-js.iml" filepath="$PROJECT_DIR$/.idea/x86-js.iml" />
6
- </modules>
7
- </component>
8
- </project>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
- </component>
6
- </project>
package/.idea/x86-js.iml DELETED
@@ -1,12 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="WEB_MODULE" version="4">
3
- <component name="NewModuleRootManager">
4
- <content url="file://$MODULE_DIR$">
5
- <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
- <excludeFolder url="file://$MODULE_DIR$/temp" />
7
- <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
- </content>
9
- <orderEntry type="inheritedJdk" />
10
- <orderEntry type="sourceFolder" forTests="false" />
11
- </component>
12
- </module>
package/src/index.ts DELETED
@@ -1,443 +0,0 @@
1
- //@ts-ignore
2
- import {uc} from './x86/deps/unicorn-x86.min'
3
- //@ts-ignore
4
- import {ks} from './x86/deps/keystone-x86.min'
5
- //@ts-ignore
6
- import {cs} from './x86/deps/capstone-x86.min'
7
- import {X86InstructionCode, X86InstructionName, X86InstructionNames} from "./instructions";
8
- import {enumKeys} from "./utils";
9
-
10
- export {
11
- X86InstructionNames,
12
- X86InstructionCode,
13
- X86InstructionName,
14
- }
15
-
16
- /**
17
- * Enumeration of x86 condition flags
18
- */
19
- export enum X86ConditionFlags {
20
- C = 0, // Carry flag
21
- Z = 1, // Zero flag
22
- S = 2, // Sign flag
23
- O = 3, // Overflow flag
24
- P = 4, // Parity flag
25
- A = 5, // Auxiliary carry flag
26
- }
27
-
28
- /**
29
- * Enumeration of x86 registers and their corresponding values
30
- */
31
- export enum X86Register {
32
- EAX = 19,
33
- EBX = 21,
34
- ECX = 22,
35
- ESP = 30,
36
- EBP = 20,
37
- EDI = 23,
38
- ESI = 29,
39
- EDX = 24,
40
- }
41
-
42
-
43
-
44
- /** Array of all x86 register names */
45
- export const X86_REGISTERS = enumKeys(X86Register)
46
-
47
- /** Stack pointer register identifier */
48
- export const X86_STACK_POINTER_REGISTER = X86Register.ESP;
49
-
50
- /** Program counter register identifier (EIP) */
51
- export const X86_PROGRAM_COUNTER_REGISTER = 26 //X86_REG_EIP
52
-
53
- /** Flags register identifier (EFLAGS) */
54
- export const X86_FLAGS_REGISTER = 25; //X86_REG_EFLAGS
55
-
56
- /**
57
- * Converts a signed 32-bit integer to an unsigned 32-bit integer
58
- * @param {number} value - Signed 32-bit integer
59
- * @returns {number} Unsigned 32-bit integer
60
- */
61
- function signed32ToUnsigned32(value: number): number {
62
- if (value < 0) {
63
- return value + Math.pow(2, 32);
64
- }
65
- return value;
66
- }
67
-
68
- /**
69
- * Represents a single x86 instruction
70
- */
71
- export type X86Instruction = {
72
- /** Memory address of the instruction */
73
- address: number;
74
- /** Human-readable text representation of the instruction */
75
- text: string;
76
- /** Mnemonic name of the instruction */
77
- name: string;
78
- /** Instruction identifier */
79
- id: X86InstructionCode;
80
- /** Raw bytes of the instruction */
81
- bytes: number[];
82
- /** Line number in the source code */
83
- line: number;
84
- /** Size of the instruction in bytes */
85
- size: number;
86
- }
87
-
88
- //TODO
89
- type HistoryEntry = {}
90
-
91
- /**
92
- * Calculates the required page size for memory allocation
93
- * @param {number} size - Required memory size
94
- * @returns {number} Padded size aligned to page boundaries (4KB)
95
- */
96
- function calculatePageSize(size: number): number {
97
- const pageSize = 4096; // 4KB
98
- return Math.ceil(size / pageSize) * pageSize;
99
- }
100
-
101
- /**
102
- * Simplifies error objects to string messages
103
- * @param {any} e - Error object or string
104
- * @returns {string} Simplified error message
105
- */
106
- function simplifyError(e: any) {
107
- return typeof e === 'string' ? e : e.message
108
- }
109
-
110
- /**
111
- * X86 assembly interpreter using Unicorn, Keystone, and Capstone engines
112
- */
113
- export class X86Interpreter {
114
-
115
- /**
116
- * Creates a new X86Interpreter instance
117
- * @param {string} code - Assembly code to interpret
118
- * @param {any} assembler - Keystone assembler instance
119
- * @param {any} interpreter - Unicorn interpreter instance
120
- * @param {any} disambler - Capstone disassembler instance
121
- */
122
- constructor(
123
- private readonly code: string,
124
- private readonly assembler: any,
125
- private readonly interpreter: any,
126
- private readonly disambler: any,
127
- ) {
128
- }
129
-
130
- /** Starting address for the code segment */
131
- static START_ADDRESS = 0x1000;
132
- /** Starting address for the stack segment */
133
- static STACK_START_ADDRESS = 0xf0000;
134
- /** Size of the stack segment */
135
- static STACK_SIZE = 0xf0000;
136
- /** Buffer containing assembled code bytes */
137
- private codeBuffer: number[] | null = null;
138
- /** Array of disassembled instructions */
139
- private instructions: X86Instruction[] = []
140
- /** Address following the last instruction */
141
- private endAddress: number = 0;
142
-
143
- /**
144
- * Hook callback for the interpreter
145
- * @param {any} handle - Hook handle
146
- * @param {number} addr_lo - Lower address bound
147
- * @param {number} addr_hi - Higher address bound
148
- * @param {number} size - Size of the region
149
- * @param {any} user_data - User data
150
- */
151
- //@ts-ignore
152
- private hook = (handle, addr_lo, addr_hi, size, user_data) => {
153
-
154
- }
155
-
156
- /**
157
- * Gets the disassembled instructions from the code buffer
158
- * @returns {any[]} Array of disassembled instructions
159
- * @throws {Error} If disassembly fails
160
- */
161
- private getInstructions() {
162
- try {
163
- return this.disambler.disasm(this.codeBuffer, X86Interpreter.START_ADDRESS) as any[]
164
- } catch (e) {
165
- throw new Error(simplifyError(e));
166
- }
167
- }
168
-
169
- /**
170
- * Assembles the code and prepares for interpretation
171
- * @throws {Error} If assembly fails
172
- */
173
- assemble() {
174
- try {
175
- this.codeBuffer = this.assembler.asm(this.code) as number[]
176
- const instructions = this.getInstructions();
177
- this.instructions = instructions.map((ins, i) => {
178
- return {
179
- address: ins.address,
180
- bytes: ins.bytes,
181
- size: ins.size,
182
- id: ins.id,
183
- text: `${ins.mnemonic} ${ins.op_str}`,
184
- name: ins.mnemonic,
185
- line: i,
186
- }
187
- })
188
- this.endAddress = X86Interpreter.START_ADDRESS + this.codeBuffer.length;
189
- } catch (e) {
190
- throw new Error(simplifyError(e));
191
- }
192
- }
193
-
194
- /**
195
- * Initializes memory and registers for the interpreter
196
- * @throws {Error} If code buffer is not initialized
197
- */
198
- initialize() {
199
- if (!this.codeBuffer) {
200
- throw new Error('Code buffer is not initialized. Please call assemble() first.');
201
- }
202
- this.interpreter.mem_map(X86Interpreter.START_ADDRESS, calculatePageSize(this.codeBuffer.length), uc.PROT_ALL);
203
- this.interpreter.mem_write(X86Interpreter.START_ADDRESS, this.codeBuffer);
204
- this.interpreter.mem_map(X86Interpreter.STACK_START_ADDRESS, calculatePageSize(X86Interpreter.STACK_SIZE), uc.PROT_ALL);
205
- this.interpreter.reg_write_i32(X86Register.ESP, X86Interpreter.STACK_START_ADDRESS + X86Interpreter.STACK_SIZE);
206
- this.interpreter.reg_write_i32(X86Register.EBP, X86Interpreter.STACK_START_ADDRESS + X86Interpreter.STACK_SIZE);
207
- this.interpreter.reg_write_i32(X86_PROGRAM_COUNTER_REGISTER, X86Interpreter.START_ADDRESS);
208
- }
209
-
210
- /**
211
- * Simulates the execution of the code
212
- * @param {number} [limit] - Optional instruction execution limit
213
- * @throws {Error} If simulation fails
214
- */
215
- simulate(limit?: number) {
216
- try {
217
- //TODO add tracing
218
- const address = this.getProgramCounter();
219
- this.interpreter.emu_start(address, this.endAddress, 0, limit);
220
- } catch (e) {
221
- throw new Error(simplifyError(e));
222
- }
223
- }
224
-
225
- /**
226
- * Simulates execution with specified breakpoints
227
- * @param {number[]} breakpoints - Array of addresses to break at
228
- * @param {number} [limit] - Optional instruction execution limit
229
- * @throws {Error} If simulation fails
230
- */
231
- simulateWithBreakpoints(breakpoints: number[], limit?: number) {
232
- const breakpointsMap = new Map(breakpoints.map((bp) => [bp, true]));
233
- const handle = this.interpreter.hook_add(uc.HOOK_CODE, (_: unknown, address: number) => {
234
- if (breakpointsMap.has(address)) {
235
- this.interpreter.emu_stop();
236
- }
237
- });
238
- try {
239
- this.simulate(limit);
240
- } catch (e) {
241
- throw new Error(simplifyError(e));
242
- } finally {
243
- this.interpreter.hook_del(handle);
244
- }
245
- }
246
-
247
- /**
248
- * Executes a single instruction
249
- */
250
- step() {
251
- this.simulate(1);
252
- }
253
-
254
- /**
255
- * Gets the value of a register
256
- * @param {X86Register} register - Register to read
257
- * @returns {number} Unsigned 32-bit register value
258
- * @throws {Error} If reading the register fails
259
- */
260
- getRegisterValue(register: X86Register) {
261
- try {
262
- const signed = this.interpreter.reg_read_i32(register);
263
- return signed32ToUnsigned32(signed);
264
- } catch (e) {
265
- throw new Error(simplifyError(e));
266
- }
267
- }
268
-
269
- /**
270
- * Sets the value of a register
271
- * @param {X86Register} register - Register to write
272
- * @param {number} value - Value to set
273
- * @throws {Error} If writing to the register fails
274
- */
275
- setRegisterValue(register: X86Register, value: number) {
276
- try {
277
- this.interpreter.reg_write_i32(register, value);
278
- } catch (e) {
279
- throw new Error(simplifyError(e));
280
- }
281
- }
282
-
283
- /**
284
- * Gets the current stack pointer value
285
- * @returns {number} Stack pointer value
286
- */
287
- getStackPointer() {
288
- return this.getRegisterValue(X86_STACK_POINTER_REGISTER);
289
- }
290
-
291
- /**
292
- * Gets the current program counter value
293
- * @returns {number} Program counter value
294
- */
295
- getProgramCounter() {
296
- return this.getRegisterValue(X86_PROGRAM_COUNTER_REGISTER as X86Register);
297
- }
298
-
299
- /**
300
- * Gets the values of all registers
301
- * @returns {number[]} Array of register values
302
- */
303
- getRegistersValues(): number[] {
304
- return X86_REGISTERS.map((reg) => {
305
- return this.getRegisterValue(X86Register[reg as keyof typeof X86Register]);
306
- })
307
- }
308
-
309
- /**
310
- * Reads bytes from memory
311
- * @param {number} address - Memory address to read from
312
- * @param {number} length - Number of bytes to read
313
- * @returns {number[]} Array of byte values
314
- * @throws {Error} If reading memory fails
315
- */
316
- readMemoryBytes(address: number, length: number) {
317
- try {
318
- return [...this.interpreter.mem_read(address, length)];
319
- } catch (e) {
320
- throw new Error(simplifyError(e));
321
- }
322
- }
323
-
324
- /**
325
- * Writes bytes to memory
326
- * @param {number} address - Memory address to write to
327
- * @param {number[]} bytes - Array of byte values to write
328
- */
329
- setMemoryBytes(address: number, bytes: number[]) {
330
- try {
331
- this.interpreter.mem_write(address, bytes);
332
- } catch (e) {
333
- // Error handling is missing here
334
- }
335
- }
336
-
337
- /**
338
- * Gets the next instruction to be executed
339
- * @returns {X86Instruction | null} Next instruction or null if no more instructions
340
- */
341
- getNextStatement(): X86Instruction | null {
342
- const pc = this.getProgramCounter();
343
- const instruction = this.instructions.find((ins) => ins.address === pc);
344
- return instruction ?? null;
345
- }
346
-
347
- /**
348
- * Checks if the execution has terminated (no more instructions)
349
- * @returns {boolean} True if execution is terminated
350
- */
351
- isTerminated() {
352
- return this.getNextStatement() === null;
353
- }
354
-
355
- /**
356
- * Gets the current state of all condition flags
357
- * @returns {Record<X86ConditionFlags, number>} Object mapping flags to their values (0 or 1)
358
- */
359
- getConditionFlags(): Record<X86ConditionFlags, number> {
360
- const flags = this.interpreter.reg_read_i32(X86_FLAGS_REGISTER) as number;
361
- return {
362
- [X86ConditionFlags.C]: (flags >> 0) & 1,
363
- [X86ConditionFlags.P]: (flags >> 2) & 1,
364
- [X86ConditionFlags.A]: (flags >> 4) & 1,
365
- [X86ConditionFlags.Z]: (flags >> 6) & 1,
366
- [X86ConditionFlags.S]: (flags >> 7) & 1,
367
- [X86ConditionFlags.O]: (flags >> 11) & 1,
368
- }
369
- }
370
-
371
- /**
372
- * Gets the instruction at a specific address
373
- * @param {number} address - Memory address
374
- * @returns {X86Instruction | null} Instruction at the address or null if not found
375
- */
376
- getStatementAtAddress(address: number): X86Instruction | null {
377
- const instruction = this.instructions.find((ins) => ins.address === address);
378
- return instruction ?? null;
379
- }
380
-
381
- /**
382
- * Gets the source code as a string
383
- * @returns {string} Source code
384
- */
385
- getSource() {
386
- this.instructions.map((ins) => ins.text).join('/n')
387
- }
388
-
389
- /**
390
- * Gets the instruction at a specific source line
391
- * @param {number} lineIndex - Line index in the source code
392
- * @returns {X86Instruction | null} Instruction at the line or null if not found
393
- */
394
- getStatementAtSourceLine(lineIndex: number): X86Instruction | null {
395
- return this.instructions[lineIndex] ?? null;
396
- }
397
-
398
- /*
399
- getCallStack() {
400
- throw new Error('Method not implemented.');
401
- }
402
- */
403
-
404
- /**
405
- * Gets all disassembled instructions
406
- * @returns {X86Instruction[]} Array of all instructions
407
- */
408
- getAssembledStatements() {
409
- return this.instructions
410
- }
411
-
412
- /**
413
- * Creates a new X86Interpreter instance with the provided assembly code
414
- * @param {string} code - Assembly code to interpret
415
- * @returns {X86Interpreter} New interpreter instance
416
- * @throws {Error} If creating the interpreter fails
417
- */
418
- static create(code: string) {
419
- try {
420
- const keystone = new ks.Keystone(ks.ARCH_X86, ks.MODE_32);
421
- keystone.option(ks.OPT_SYNTAX, ks.OPT_SYNTAX_INTEL);
422
- const interpreter = new X86Interpreter(
423
- code,
424
- keystone,
425
- new uc.Unicorn(uc.ARCH_X86, uc.MODE_32),
426
- //@ts-ignore
427
- new cs.Capstone(cs.ARCH_X86, cs.MODE_32)
428
- )
429
- return interpreter;
430
- } catch (e) {
431
- throw new Error(simplifyError(e));
432
- }
433
- }
434
-
435
- /**
436
- * Releases resources used by the interpreter
437
- */
438
- dispose() {
439
- this.interpreter.close();
440
- this.assembler.close()
441
- this.disambler.close();
442
- }
443
- }
package/src/test.ts DELETED
@@ -1,17 +0,0 @@
1
- import {X86Interpreter, X86Register} from '../dist/index.js'
2
-
3
-
4
- const interpreter = X86Interpreter.create(`
5
- mov eax, 0
6
- mov edx, 1
7
- mov ecx, 30
8
- xadd ecx, edx
9
- `)
10
-
11
- interpreter.assemble()
12
- interpreter.initialize()
13
- interpreter.simulate()
14
-
15
- console.log(interpreter.getRegisterValue(X86Register.ECX))
16
-
17
-
package/src/utils.ts DELETED
@@ -1,14 +0,0 @@
1
-
2
-
3
-
4
- /**
5
- * Returns the keys of an enum
6
- * @template T - Enum type
7
- * @param {T} e - Enum object
8
- * @returns {string[]} Array of enum keys
9
- */
10
- export function enumKeys<T extends object>(e: T) {
11
- const keys = Object.keys(e)
12
- const isStringEnum = isNaN(Number(keys[0]))
13
- return isStringEnum ? keys : keys.slice(keys.length / 2)
14
- }