@cheatron/native-mock 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 +21 -0
- package/README.md +21 -0
- package/dist/constants.d.ts +175 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +173 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/kernel32.d.ts +22 -0
- package/dist/kernel32.d.ts.map +1 -0
- package/dist/kernel32.js +170 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +2 -0
- package/dist/os/handles.d.ts +17 -0
- package/dist/os/handles.d.ts.map +1 -0
- package/dist/os/handles.js +21 -0
- package/dist/os/kernel.d.ts +18 -0
- package/dist/os/kernel.d.ts.map +1 -0
- package/dist/os/kernel.js +50 -0
- package/dist/os/memory.d.ts +24 -0
- package/dist/os/memory.d.ts.map +1 -0
- package/dist/os/memory.js +147 -0
- package/dist/os/process.d.ts +17 -0
- package/dist/os/process.d.ts.map +1 -0
- package/dist/os/process.js +35 -0
- package/dist/os/thread.d.ts +40 -0
- package/dist/os/thread.d.ts.map +1 -0
- package/dist/os/thread.js +64 -0
- package/dist/process.d.ts +30 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +124 -0
- package/dist/thread.d.ts +28 -0
- package/dist/thread.d.ts.map +1 -0
- package/dist/thread.js +113 -0
- package/package.json +59 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SimulatedProcess } from './process';
|
|
2
|
+
import { HandleObject } from './handles';
|
|
3
|
+
import * as D from 'win32-def';
|
|
4
|
+
export declare class Kernel {
|
|
5
|
+
static instance: Kernel;
|
|
6
|
+
private processes;
|
|
7
|
+
private nextPid;
|
|
8
|
+
currentProcess: SimulatedProcess;
|
|
9
|
+
constructor();
|
|
10
|
+
private createSystemProcess;
|
|
11
|
+
createProcess(name: string): SimulatedProcess;
|
|
12
|
+
getProcess(pid: number): SimulatedProcess | undefined;
|
|
13
|
+
OpenProcess(dwDesiredAccess: number, bInheritHandle: boolean, dwProcessId: number): D.HANDLE;
|
|
14
|
+
CloseHandle(hObject: D.HANDLE): boolean;
|
|
15
|
+
getObjectFromHandle(handle: D.HANDLE): HandleObject | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare const kernel: Kernel;
|
|
18
|
+
//# sourceMappingURL=kernel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernel.d.ts","sourceRoot":"","sources":["../../src/os/kernel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,CAAC,MAAM,WAAW,CAAC;AAE/B,qBAAa,MAAM;IACjB,OAAc,QAAQ,EAAE,MAAM,CAAC;IAE/B,OAAO,CAAC,SAAS,CAA4C;IAC7D,OAAO,CAAC,OAAO,CAAa;IAGrB,cAAc,EAAE,gBAAgB,CAAC;;IAaxC,OAAO,CAAC,mBAAmB;IAKpB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB;IAQ7C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAMrD,WAAW,CAChB,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,OAAO,EACvB,WAAW,EAAE,MAAM,GAClB,CAAC,CAAC,MAAM;IAeJ,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,GAAG,OAAO;IAKvC,mBAAmB,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,YAAY,GAAG,SAAS;CAGvE;AAGD,eAAO,MAAM,MAAM,QAAe,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { SimulatedProcess } from './process';
|
|
2
|
+
export class Kernel {
|
|
3
|
+
static instance;
|
|
4
|
+
processes = new Map();
|
|
5
|
+
nextPid = 4; // System processes start low
|
|
6
|
+
// The process that is "executing" the API calls (e.g. the test runner or the cheat tool)
|
|
7
|
+
currentProcess;
|
|
8
|
+
constructor() {
|
|
9
|
+
Kernel.instance = this;
|
|
10
|
+
// Bootstrapping: Create a "System" process and "Current" process
|
|
11
|
+
this.createSystemProcess();
|
|
12
|
+
// The "Current Process" is the one running the tests/code.
|
|
13
|
+
// We simulate it as a process so it can have a Handle Table.
|
|
14
|
+
this.currentProcess = new SimulatedProcess(9999, 'CurrentTestRunner.exe');
|
|
15
|
+
this.processes.set(this.currentProcess.id, this.currentProcess);
|
|
16
|
+
}
|
|
17
|
+
createSystemProcess() {
|
|
18
|
+
const sys = new SimulatedProcess(4, 'System');
|
|
19
|
+
this.processes.set(4, sys);
|
|
20
|
+
}
|
|
21
|
+
createProcess(name) {
|
|
22
|
+
const pid = this.nextPid;
|
|
23
|
+
this.nextPid += 4;
|
|
24
|
+
const proc = new SimulatedProcess(pid, name);
|
|
25
|
+
this.processes.set(pid, proc);
|
|
26
|
+
return proc;
|
|
27
|
+
}
|
|
28
|
+
getProcess(pid) {
|
|
29
|
+
return this.processes.get(pid);
|
|
30
|
+
}
|
|
31
|
+
// --- Syscalls (Simulated) ---
|
|
32
|
+
OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId) {
|
|
33
|
+
const targetProc = this.processes.get(dwProcessId);
|
|
34
|
+
if (!targetProc) {
|
|
35
|
+
return 0n; // NULL
|
|
36
|
+
}
|
|
37
|
+
// Create a handle in the CURRENT process's handle table pointing to the TARGET process
|
|
38
|
+
const handle = this.currentProcess.handles.createHandle(targetProc, 'Process', dwDesiredAccess);
|
|
39
|
+
return handle;
|
|
40
|
+
}
|
|
41
|
+
CloseHandle(hObject) {
|
|
42
|
+
return this.currentProcess.handles.closeHandle(hObject);
|
|
43
|
+
}
|
|
44
|
+
// Helper to dereference a handle from the current process context
|
|
45
|
+
getObjectFromHandle(handle) {
|
|
46
|
+
return this.currentProcess.handles.getObject(handle);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Global kernel instance
|
|
50
|
+
export const kernel = new Kernel();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type MemoryBasicInformation } from '../constants';
|
|
2
|
+
export declare class MemoryManager {
|
|
3
|
+
private pages;
|
|
4
|
+
constructor();
|
|
5
|
+
/**
|
|
6
|
+
* Aligns an address down to the nearest page boundary.
|
|
7
|
+
*/
|
|
8
|
+
private alignDown;
|
|
9
|
+
/**
|
|
10
|
+
* Aligns an address up to the nearest page boundary.
|
|
11
|
+
*/
|
|
12
|
+
private alignUp;
|
|
13
|
+
/**
|
|
14
|
+
* Allocates memory.
|
|
15
|
+
* Simplification: Always allocates MEM_PRIVATE, MEM_COMMIT | MEM_RESERVE.
|
|
16
|
+
*/
|
|
17
|
+
allocate(address: number, // If 0, find free space
|
|
18
|
+
size: number, _allocationType?: number, protect?: number): number;
|
|
19
|
+
free(address: number, _size?: number, _freeType?: number): boolean;
|
|
20
|
+
read(address: number, size: number): Buffer;
|
|
21
|
+
write(address: number, data: Buffer): number;
|
|
22
|
+
query(address: number): MemoryBasicInformation;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/os/memory.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,sBAAsB,EAI5B,MAAM,cAAc,CAAC;AAYtB,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAA2B;;IAIxC;;OAEG;IACH,OAAO,CAAC,SAAS;IAIjB;;OAEG;IACH,OAAO,CAAC,OAAO;IAIf;;;OAGG;IACH,QAAQ,CACN,OAAO,EAAE,MAAM,EAAE,wBAAwB;IACzC,IAAI,EAAE,MAAM,EACZ,eAAe,GAAE,MAAiD,EAClE,OAAO,GAAE,MAAmC,GAC3C,MAAM;IA+CT,IAAI,CACF,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAU,EACjB,SAAS,GAAE,MAAe,GACzB,OAAO;IASV,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAiC3C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAwC5C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,sBAAsB;CA0B/C"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { MemoryProtection, MemoryState, MemoryType, } from '../constants';
|
|
2
|
+
const PAGE_SIZE = 4096;
|
|
3
|
+
export class MemoryManager {
|
|
4
|
+
pages = new Map();
|
|
5
|
+
constructor() { }
|
|
6
|
+
/**
|
|
7
|
+
* Aligns an address down to the nearest page boundary.
|
|
8
|
+
*/
|
|
9
|
+
alignDown(address) {
|
|
10
|
+
return Math.floor(address / PAGE_SIZE) * PAGE_SIZE;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Aligns an address up to the nearest page boundary.
|
|
14
|
+
*/
|
|
15
|
+
alignUp(address) {
|
|
16
|
+
return Math.ceil(address / PAGE_SIZE) * PAGE_SIZE;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Allocates memory.
|
|
20
|
+
* Simplification: Always allocates MEM_PRIVATE, MEM_COMMIT | MEM_RESERVE.
|
|
21
|
+
*/
|
|
22
|
+
allocate(address, // If 0, find free space
|
|
23
|
+
size, _allocationType = MemoryState.COMMIT | MemoryState.RESERVE, protect = MemoryProtection.READWRITE) {
|
|
24
|
+
const numPages = Math.ceil(size / PAGE_SIZE);
|
|
25
|
+
// Simple allocator: if address is 0, find a gap
|
|
26
|
+
// Start searching from 0x10000 (64KB) to avoid null pointer issues
|
|
27
|
+
let startAddress = address > 0 ? this.alignDown(address) : 0x10000;
|
|
28
|
+
if (address === 0) {
|
|
29
|
+
// Find a specialized gap
|
|
30
|
+
// This is a naive implementation; performance might key for huge allocations
|
|
31
|
+
while (true) {
|
|
32
|
+
let collision = false;
|
|
33
|
+
for (let i = 0; i < numPages; i++) {
|
|
34
|
+
if (this.pages.has(startAddress + i * PAGE_SIZE)) {
|
|
35
|
+
collision = true;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!collision)
|
|
40
|
+
break;
|
|
41
|
+
startAddress += PAGE_SIZE;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Verify requested range is free
|
|
46
|
+
for (let i = 0; i < numPages; i++) {
|
|
47
|
+
const _addr = startAddress + i * PAGE_SIZE;
|
|
48
|
+
// Should check for collisions or if re-allocation is allowed
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (let i = 0; i < numPages; i++) {
|
|
52
|
+
const pageAddr = startAddress + i * PAGE_SIZE;
|
|
53
|
+
const page = this.pages.get(pageAddr) || {
|
|
54
|
+
address: pageAddr,
|
|
55
|
+
data: Buffer.alloc(PAGE_SIZE),
|
|
56
|
+
state: MemoryState.FREE,
|
|
57
|
+
protect: MemoryProtection.NOACCESS,
|
|
58
|
+
type: MemoryType.PRIVATE,
|
|
59
|
+
};
|
|
60
|
+
page.state = MemoryState.COMMIT;
|
|
61
|
+
page.protect = protect;
|
|
62
|
+
this.pages.set(pageAddr, page);
|
|
63
|
+
}
|
|
64
|
+
return startAddress;
|
|
65
|
+
}
|
|
66
|
+
free(address, _size = 0, _freeType = 0x8000 /* MEM_RELEASE */) {
|
|
67
|
+
const startAddress = this.alignDown(address);
|
|
68
|
+
if (this.pages.has(startAddress)) {
|
|
69
|
+
this.pages.delete(startAddress);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
read(address, size) {
|
|
75
|
+
const result = Buffer.alloc(size);
|
|
76
|
+
let bytesRead = 0;
|
|
77
|
+
let currentAddr = address;
|
|
78
|
+
while (bytesRead < size) {
|
|
79
|
+
const pageAddr = this.alignDown(currentAddr);
|
|
80
|
+
const offsetInPage = currentAddr - pageAddr;
|
|
81
|
+
const bytesToRead = Math.min(PAGE_SIZE - offsetInPage, size - bytesRead);
|
|
82
|
+
const page = this.pages.get(pageAddr);
|
|
83
|
+
if (page &&
|
|
84
|
+
page.state & MemoryState.COMMIT &&
|
|
85
|
+
!(page.protect & MemoryProtection.NOACCESS)) {
|
|
86
|
+
page.data.copy(result, bytesRead, offsetInPage, offsetInPage + bytesToRead);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Unmapped or inaccessible
|
|
90
|
+
}
|
|
91
|
+
bytesRead += bytesToRead;
|
|
92
|
+
currentAddr += bytesToRead;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
write(address, data) {
|
|
97
|
+
let bytesWritten = 0;
|
|
98
|
+
let currentAddr = address;
|
|
99
|
+
const size = data.length;
|
|
100
|
+
while (bytesWritten < size) {
|
|
101
|
+
const pageAddr = this.alignDown(currentAddr);
|
|
102
|
+
const offsetInPage = currentAddr - pageAddr;
|
|
103
|
+
const bytesToWrite = Math.min(PAGE_SIZE - offsetInPage, size - bytesWritten);
|
|
104
|
+
const page = this.pages.get(pageAddr);
|
|
105
|
+
if (page && page.state & MemoryState.COMMIT) {
|
|
106
|
+
// Check write permissions
|
|
107
|
+
if (page.protect &
|
|
108
|
+
(MemoryProtection.READWRITE | MemoryProtection.EXECUTE_READWRITE)) {
|
|
109
|
+
data.copy(page.data, offsetInPage, bytesWritten, bytesWritten + bytesToWrite);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
bytesWritten += bytesToWrite;
|
|
119
|
+
currentAddr += bytesToWrite;
|
|
120
|
+
}
|
|
121
|
+
return bytesWritten;
|
|
122
|
+
}
|
|
123
|
+
query(address) {
|
|
124
|
+
const pageAddr = this.alignDown(address);
|
|
125
|
+
const page = this.pages.get(pageAddr);
|
|
126
|
+
if (!page) {
|
|
127
|
+
return {
|
|
128
|
+
BaseAddress: BigInt(pageAddr),
|
|
129
|
+
AllocationBase: BigInt(pageAddr),
|
|
130
|
+
AllocationProtect: MemoryProtection.NOACCESS,
|
|
131
|
+
RegionSize: BigInt(PAGE_SIZE),
|
|
132
|
+
State: MemoryState.FREE,
|
|
133
|
+
Protect: MemoryProtection.NOACCESS,
|
|
134
|
+
Type: MemoryType.PRIVATE,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
BaseAddress: BigInt(page.address),
|
|
139
|
+
AllocationBase: BigInt(page.address),
|
|
140
|
+
AllocationProtect: page.protect,
|
|
141
|
+
RegionSize: BigInt(PAGE_SIZE),
|
|
142
|
+
State: page.state,
|
|
143
|
+
Protect: page.protect,
|
|
144
|
+
Type: page.type,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MemoryManager } from './memory';
|
|
2
|
+
import { HandleTable } from './handles';
|
|
3
|
+
import { SimulatedThread } from './thread';
|
|
4
|
+
export declare class SimulatedProcess {
|
|
5
|
+
id: number;
|
|
6
|
+
name: string;
|
|
7
|
+
memory: MemoryManager;
|
|
8
|
+
handles: HandleTable;
|
|
9
|
+
threads: Map<number, SimulatedThread>;
|
|
10
|
+
exitCode: number | null;
|
|
11
|
+
private nextThreadId;
|
|
12
|
+
constructor(pid: number, name: string);
|
|
13
|
+
createThread(): SimulatedThread;
|
|
14
|
+
getThread(tid: number): SimulatedThread | undefined;
|
|
15
|
+
terminate(exitCode?: number): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/os/process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,qBAAa,gBAAgB;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEtC,OAAO,CAAC,YAAY,CAAgB;gBAExB,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAQrC,YAAY,IAAI,eAAe;IAO/B,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAInD,SAAS,CAAC,QAAQ,GAAE,MAAU;CAO/B"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { MemoryManager } from './memory';
|
|
2
|
+
import { HandleTable } from './handles';
|
|
3
|
+
import { SimulatedThread } from './thread';
|
|
4
|
+
export class SimulatedProcess {
|
|
5
|
+
id;
|
|
6
|
+
name;
|
|
7
|
+
memory;
|
|
8
|
+
handles;
|
|
9
|
+
threads;
|
|
10
|
+
exitCode = null;
|
|
11
|
+
nextThreadId = 1000;
|
|
12
|
+
constructor(pid, name) {
|
|
13
|
+
this.id = pid;
|
|
14
|
+
this.name = name;
|
|
15
|
+
this.memory = new MemoryManager();
|
|
16
|
+
this.handles = new HandleTable();
|
|
17
|
+
this.threads = new Map();
|
|
18
|
+
}
|
|
19
|
+
createThread() {
|
|
20
|
+
const tid = this.nextThreadId++;
|
|
21
|
+
const thread = new SimulatedThread(tid, this.id);
|
|
22
|
+
this.threads.set(tid, thread);
|
|
23
|
+
return thread;
|
|
24
|
+
}
|
|
25
|
+
getThread(tid) {
|
|
26
|
+
return this.threads.get(tid);
|
|
27
|
+
}
|
|
28
|
+
terminate(exitCode = 0) {
|
|
29
|
+
this.exitCode = exitCode;
|
|
30
|
+
// Cleanup threads
|
|
31
|
+
this.threads.clear();
|
|
32
|
+
// Cleanup handles?
|
|
33
|
+
// In a real OS, handles are closed.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare enum ThreadState {
|
|
2
|
+
INITIALIZED = 0,
|
|
3
|
+
READY = 1,
|
|
4
|
+
RUNNING = 2,
|
|
5
|
+
WAITING = 3,
|
|
6
|
+
TERMINATED = 4
|
|
7
|
+
}
|
|
8
|
+
export interface ThreadContext {
|
|
9
|
+
Rip: bigint;
|
|
10
|
+
Rax: bigint;
|
|
11
|
+
Rbx: bigint;
|
|
12
|
+
Rcx: bigint;
|
|
13
|
+
Rdx: bigint;
|
|
14
|
+
Rsi: bigint;
|
|
15
|
+
Rdi: bigint;
|
|
16
|
+
Rbp: bigint;
|
|
17
|
+
Rsp: bigint;
|
|
18
|
+
R8: bigint;
|
|
19
|
+
R9: bigint;
|
|
20
|
+
R10: bigint;
|
|
21
|
+
R11: bigint;
|
|
22
|
+
R12: bigint;
|
|
23
|
+
R13: bigint;
|
|
24
|
+
R14: bigint;
|
|
25
|
+
R15: bigint;
|
|
26
|
+
EFlags: number;
|
|
27
|
+
}
|
|
28
|
+
export declare class SimulatedThread {
|
|
29
|
+
id: number;
|
|
30
|
+
state: ThreadState;
|
|
31
|
+
suspendCount: number;
|
|
32
|
+
context: ThreadContext;
|
|
33
|
+
ownerProcessId: number;
|
|
34
|
+
constructor(tid: number, ownerPid: number);
|
|
35
|
+
suspend(): number;
|
|
36
|
+
resume(): number;
|
|
37
|
+
getContext(_flags: number): ThreadContext;
|
|
38
|
+
setContext(ctx: Partial<ThreadContext>): void;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=thread.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/os/thread.ts"],"names":[],"mappings":"AAAA,oBAAY,WAAW;IACrB,WAAW,IAAA;IACX,KAAK,IAAA;IACL,OAAO,IAAA;IACP,OAAO,IAAA;IACP,UAAU,IAAA;CACX;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,eAAe;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,WAAW,CAAC;IACnB,YAAY,EAAE,MAAM,CAAK;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;gBAElB,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IA4BzC,OAAO,IAAI,MAAM;IAQjB,MAAM,IAAI,MAAM;IAUhB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa;IAKzC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI;CAG9C"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export var ThreadState;
|
|
2
|
+
(function (ThreadState) {
|
|
3
|
+
ThreadState[ThreadState["INITIALIZED"] = 0] = "INITIALIZED";
|
|
4
|
+
ThreadState[ThreadState["READY"] = 1] = "READY";
|
|
5
|
+
ThreadState[ThreadState["RUNNING"] = 2] = "RUNNING";
|
|
6
|
+
ThreadState[ThreadState["WAITING"] = 3] = "WAITING";
|
|
7
|
+
ThreadState[ThreadState["TERMINATED"] = 4] = "TERMINATED";
|
|
8
|
+
})(ThreadState || (ThreadState = {}));
|
|
9
|
+
export class SimulatedThread {
|
|
10
|
+
id;
|
|
11
|
+
state;
|
|
12
|
+
suspendCount = 0;
|
|
13
|
+
context;
|
|
14
|
+
ownerProcessId;
|
|
15
|
+
constructor(tid, ownerPid) {
|
|
16
|
+
this.id = tid;
|
|
17
|
+
this.ownerProcessId = ownerPid;
|
|
18
|
+
this.state = ThreadState.INITIALIZED;
|
|
19
|
+
// Initialize context with some reasonable defaults
|
|
20
|
+
this.context = {
|
|
21
|
+
Rip: 0x7ff700001000n, // Dummy entry point
|
|
22
|
+
Rax: 0n,
|
|
23
|
+
Rbx: 0n,
|
|
24
|
+
Rcx: 0n,
|
|
25
|
+
Rdx: 0n,
|
|
26
|
+
Rsi: 0n,
|
|
27
|
+
Rdi: 0n,
|
|
28
|
+
Rbp: 0n,
|
|
29
|
+
Rsp: 0x000000e0000n, // Dummy stack
|
|
30
|
+
R8: 0n,
|
|
31
|
+
R9: 0n,
|
|
32
|
+
R10: 0n,
|
|
33
|
+
R11: 0n,
|
|
34
|
+
R12: 0n,
|
|
35
|
+
R13: 0n,
|
|
36
|
+
R14: 0n,
|
|
37
|
+
R15: 0n,
|
|
38
|
+
EFlags: 0x202, // IF bit set
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
suspend() {
|
|
42
|
+
this.suspendCount++;
|
|
43
|
+
if (this.state === ThreadState.RUNNING) {
|
|
44
|
+
this.state = ThreadState.WAITING; // Simplification
|
|
45
|
+
}
|
|
46
|
+
return this.suspendCount - 1; // Return previous count
|
|
47
|
+
}
|
|
48
|
+
resume() {
|
|
49
|
+
if (this.suspendCount > 0) {
|
|
50
|
+
this.suspendCount--;
|
|
51
|
+
if (this.suspendCount === 0) {
|
|
52
|
+
this.state = ThreadState.READY; // Ready to run
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return this.suspendCount;
|
|
56
|
+
}
|
|
57
|
+
getContext(_flags) {
|
|
58
|
+
// Should respect flags (CONTEXT_FULL, etc.), but for mock we return full.
|
|
59
|
+
return { ...this.context };
|
|
60
|
+
}
|
|
61
|
+
setContext(ctx) {
|
|
62
|
+
Object.assign(this.context, ctx);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as D from 'win32-def';
|
|
2
|
+
import { type MemoryBasicInformation } from './constants';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a remote process
|
|
5
|
+
*/
|
|
6
|
+
export declare class Process {
|
|
7
|
+
protected _handle: D.HANDLE | null;
|
|
8
|
+
protected _pid: number;
|
|
9
|
+
constructor(handle: D.HANDLE | null, autoClose?: boolean, pid?: number);
|
|
10
|
+
static open(pid: number, access?: number): Process;
|
|
11
|
+
static current(): CurrentProcess;
|
|
12
|
+
get handle(): D.HANDLE | null;
|
|
13
|
+
get pid(): number;
|
|
14
|
+
isValid(): boolean;
|
|
15
|
+
close(): void;
|
|
16
|
+
read(address: number | bigint, size: number): Buffer;
|
|
17
|
+
write(address: number | bigint, buffer: Buffer): void;
|
|
18
|
+
query(address: number | bigint): MemoryBasicInformation;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Represents the current process (singleton)
|
|
22
|
+
*/
|
|
23
|
+
export declare class CurrentProcess extends Process {
|
|
24
|
+
constructor();
|
|
25
|
+
close(): void;
|
|
26
|
+
query(address: number | bigint): MemoryBasicInformation;
|
|
27
|
+
}
|
|
28
|
+
export declare const currentProcess: CurrentProcess;
|
|
29
|
+
export declare const currentProcessId: number;
|
|
30
|
+
//# sourceMappingURL=process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../src/process.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,WAAW,CAAC;AAC/B,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,aAAa,CAAC;AAerB;;GAEG;AACH,qBAAa,OAAO;IAClB,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC;IACnC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC;gBAGrB,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,IAAI,EACvB,SAAS,GAAE,OAAc,EACzB,GAAG,GAAE,MAAU;IASjB,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAiC,GAAG,OAAO;IAU5E,MAAM,CAAC,OAAO,IAAI,cAAc;IAIhC,IAAI,MAAM,oBAET;IACD,IAAI,GAAG,WAEN;IAED,OAAO,IAAI,OAAO;IAIlB,KAAK;IAUL,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAyBpD,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAsBrD,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,sBAAsB;CAkBxD;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,OAAO;;IAMhC,KAAK;IAIL,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,sBAAsB;CAQjE;AAGD,eAAO,MAAM,cAAc,gBAAuB,CAAC;AACnD,eAAO,MAAM,gBAAgB,QAAqB,CAAC"}
|
package/dist/process.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Kernel32Impl as Kernel32 } from './kernel32';
|
|
2
|
+
import { ProcessAccess, MEMORY_BASIC_INFORMATION, MBI_SIZE, } from './constants';
|
|
3
|
+
import koffi from 'koffi';
|
|
4
|
+
import { log } from './logger';
|
|
5
|
+
/**
|
|
6
|
+
* Handle management registry for automatic cleanup
|
|
7
|
+
*/
|
|
8
|
+
const registry = new FinalizationRegistry((handle) => {
|
|
9
|
+
if (handle) {
|
|
10
|
+
log.trace('Process', 'Closing orphaned handle via GC');
|
|
11
|
+
Kernel32.CloseHandle(handle);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Represents a remote process
|
|
16
|
+
*/
|
|
17
|
+
export class Process {
|
|
18
|
+
_handle;
|
|
19
|
+
_pid;
|
|
20
|
+
constructor(handle, autoClose = true, pid = 0) {
|
|
21
|
+
this._handle = handle;
|
|
22
|
+
this._pid = pid;
|
|
23
|
+
if (autoClose && handle) {
|
|
24
|
+
registry.register(this, handle, this);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
static open(pid, access = ProcessAccess.ALL_ACCESS) {
|
|
28
|
+
log.debug('Process', `Opening process ${pid}`, { access });
|
|
29
|
+
const handle = Kernel32.OpenProcess(access, 0, pid);
|
|
30
|
+
if (!handle) {
|
|
31
|
+
log.error('Process', `Failed to open process ${pid}`);
|
|
32
|
+
throw new Error(`Failed to open process ${pid}`);
|
|
33
|
+
}
|
|
34
|
+
return new Process(handle, true, pid);
|
|
35
|
+
}
|
|
36
|
+
static current() {
|
|
37
|
+
return currentProcess;
|
|
38
|
+
}
|
|
39
|
+
get handle() {
|
|
40
|
+
return this._handle;
|
|
41
|
+
}
|
|
42
|
+
get pid() {
|
|
43
|
+
return this._pid;
|
|
44
|
+
}
|
|
45
|
+
isValid() {
|
|
46
|
+
return this._handle !== null && this._handle !== undefined;
|
|
47
|
+
}
|
|
48
|
+
close() {
|
|
49
|
+
if (this.isValid()) {
|
|
50
|
+
log.debug('Process', `Closing process handle ${this._pid}`);
|
|
51
|
+
const h = this._handle;
|
|
52
|
+
this._handle = null;
|
|
53
|
+
registry.unregister(this);
|
|
54
|
+
Kernel32.CloseHandle(h);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
read(address, size) {
|
|
58
|
+
if (!this.isValid())
|
|
59
|
+
throw new Error('Process handle is closed');
|
|
60
|
+
const buffer = Buffer.alloc(size);
|
|
61
|
+
const success = Kernel32.ReadProcessMemory(this._handle, address, buffer, size, null);
|
|
62
|
+
if (!success) {
|
|
63
|
+
const errCode = Kernel32.GetLastError
|
|
64
|
+
? Kernel32.GetLastError()
|
|
65
|
+
: 'unknown';
|
|
66
|
+
log.error('Process', `ReadProcessMemory failed at ${address}`, {
|
|
67
|
+
size,
|
|
68
|
+
errCode,
|
|
69
|
+
});
|
|
70
|
+
throw new Error('ReadProcessMemory failed');
|
|
71
|
+
}
|
|
72
|
+
return buffer;
|
|
73
|
+
}
|
|
74
|
+
write(address, buffer) {
|
|
75
|
+
if (!this.isValid())
|
|
76
|
+
throw new Error('Process handle is closed');
|
|
77
|
+
const success = Kernel32.WriteProcessMemory(this._handle, address, buffer, buffer.length, null);
|
|
78
|
+
if (!success) {
|
|
79
|
+
const errCode = Kernel32.GetLastError
|
|
80
|
+
? Kernel32.GetLastError()
|
|
81
|
+
: 'unknown';
|
|
82
|
+
log.error('Process', `WriteProcessMemory failed at ${address}`, {
|
|
83
|
+
size: buffer.length,
|
|
84
|
+
errCode,
|
|
85
|
+
});
|
|
86
|
+
throw new Error('WriteProcessMemory failed');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
query(address) {
|
|
90
|
+
if (!this.isValid())
|
|
91
|
+
throw new Error('Process handle is closed');
|
|
92
|
+
const buffer = Buffer.alloc(MBI_SIZE);
|
|
93
|
+
const result = Kernel32.VirtualQueryEx(this._handle, address, buffer, MBI_SIZE);
|
|
94
|
+
if (!result) {
|
|
95
|
+
log.error('Process', `VirtualQueryEx failed at ${address}`);
|
|
96
|
+
throw new Error('VirtualQueryEx failed');
|
|
97
|
+
}
|
|
98
|
+
const info = koffi.decode(buffer, MEMORY_BASIC_INFORMATION);
|
|
99
|
+
return info;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Represents the current process (singleton)
|
|
104
|
+
*/
|
|
105
|
+
export class CurrentProcess extends Process {
|
|
106
|
+
constructor() {
|
|
107
|
+
// Current process uses a pseudo-handle that doesn't need closing
|
|
108
|
+
super(Kernel32.GetCurrentProcess(), false, Kernel32.GetCurrentProcessId());
|
|
109
|
+
}
|
|
110
|
+
close() {
|
|
111
|
+
this._handle = null;
|
|
112
|
+
}
|
|
113
|
+
query(address) {
|
|
114
|
+
const buffer = Buffer.alloc(MBI_SIZE);
|
|
115
|
+
const result = Kernel32.VirtualQuery(address, buffer, MBI_SIZE);
|
|
116
|
+
if (!result)
|
|
117
|
+
throw new Error('VirtualQuery failed');
|
|
118
|
+
const info = koffi.decode(buffer, MEMORY_BASIC_INFORMATION);
|
|
119
|
+
return info;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Export a pre-initialized instance of the current process
|
|
123
|
+
export const currentProcess = new CurrentProcess();
|
|
124
|
+
export const currentProcessId = currentProcess.pid;
|
package/dist/thread.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as D from 'win32-def';
|
|
2
|
+
import { type ThreadContext } from './constants';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a thread handle
|
|
5
|
+
*/
|
|
6
|
+
export declare class Thread {
|
|
7
|
+
protected _handle: D.HANDLE | null;
|
|
8
|
+
constructor(handle: D.HANDLE | null, autoClose?: boolean);
|
|
9
|
+
static open(threadId: number, access?: number): Thread;
|
|
10
|
+
static current(): CurrentThread;
|
|
11
|
+
static currentId(): number;
|
|
12
|
+
isValid(): boolean;
|
|
13
|
+
close(): void;
|
|
14
|
+
suspend(): number;
|
|
15
|
+
resume(): number;
|
|
16
|
+
getContext(flags?: number): ThreadContext;
|
|
17
|
+
setContext(ctx: ThreadContext): void;
|
|
18
|
+
get handle(): D.HANDLE | null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Represents the current thread (singleton)
|
|
22
|
+
*/
|
|
23
|
+
export declare class CurrentThread extends Thread {
|
|
24
|
+
constructor();
|
|
25
|
+
close(): void;
|
|
26
|
+
}
|
|
27
|
+
export declare const currentThread: CurrentThread;
|
|
28
|
+
//# sourceMappingURL=thread.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../src/thread.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,WAAW,CAAC;AAC/B,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAerB;;GAEG;AACH,qBAAa,MAAM;IACjB,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC;gBAEvB,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,IAAI,EAAE,SAAS,GAAE,OAAc;IAO9D,MAAM,CAAC,IAAI,CACT,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAgC,GACvC,MAAM;IAUT,MAAM,CAAC,OAAO,IAAI,aAAa;IAI/B,MAAM,CAAC,SAAS,IAAI,MAAM;IAI1B,OAAO,IAAI,OAAO;IAIlB,KAAK;IAUL,OAAO,IAAI,MAAM;IAUjB,MAAM,IAAI,MAAM;IAUhB,UAAU,CAAC,KAAK,GAAE,MAA0B,GAAG,aAAa;IAgB5D,UAAU,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI;IAapC,IAAI,MAAM,oBAET;CACF;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,MAAM;;IAM9B,KAAK;CAGf;AAGD,eAAO,MAAM,aAAa,eAAsB,CAAC"}
|