BX2trace 1.0.0__py3-none-any.whl

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.
bx2trace/__init__.py ADDED
@@ -0,0 +1,70 @@
1
+ # bx2trace - Windows Process Tracer & Live Memory Extractor
2
+ # =========================================================
3
+
4
+ # Core contracts & data types
5
+ from bx2trace.core.models import (
6
+ TraceEvent,
7
+ TraceMode,
8
+ Severity,
9
+ EngineConfig,
10
+ BaseDetector,
11
+ CURRENT_SCHEMA_VERSION,
12
+ )
13
+
14
+ # Execution engines
15
+ from bx2trace.core.debug_thread import DebugThread, TrackedProcess
16
+ from bx2trace.core.memory_scanner import MemoryScannerThread
17
+ from bx2trace.core.process_launcher import (
18
+ create_process_debug,
19
+ ArchitectureMismatchError,
20
+ )
21
+
22
+ # Raw memory access
23
+ from bx2trace.memory.reader import (
24
+ open_process_for_dump,
25
+ enumerate_regions,
26
+ read_region,
27
+ read_committed_regions,
28
+ )
29
+
30
+ # Extractors (platform-agnostic)
31
+ from bx2trace.extractors.strings import (
32
+ extract_strings,
33
+ extract_ascii_strings,
34
+ extract_utf16le_strings,
35
+ diff_new_strings,
36
+ URL_PATTERN,
37
+ IPV4_PATTERN,
38
+ )
39
+ from bx2trace.extractors.entropy import (
40
+ shannon_entropy,
41
+ entropy_by_page,
42
+ )
43
+
44
+ # Useful constants
45
+ from bx2trace.core.constants import (
46
+ PAGE_EXECUTE_READWRITE,
47
+ PAGE_EXECUTE_READ,
48
+ PAGE_READWRITE,
49
+ PAGE_READONLY,
50
+ MEM_COMMIT,
51
+ PROTECTIONS_OF_INTEREST,
52
+ )
53
+
54
+ __version__ = "0.1.0"
55
+ __author__ = "bx2trace contributors"
56
+
57
+ __all__ = [
58
+ "TraceEvent", "TraceMode", "Severity", "EngineConfig", "BaseDetector",
59
+ "CURRENT_SCHEMA_VERSION",
60
+ "DebugThread", "TrackedProcess", "MemoryScannerThread",
61
+ "create_process_debug", "ArchitectureMismatchError",
62
+ "open_process_for_dump", "enumerate_regions",
63
+ "read_region", "read_committed_regions",
64
+ "extract_strings", "extract_ascii_strings", "extract_utf16le_strings",
65
+ "diff_new_strings", "URL_PATTERN", "IPV4_PATTERN",
66
+ "shannon_entropy", "entropy_by_page",
67
+ "PAGE_EXECUTE_READWRITE", "PAGE_EXECUTE_READ",
68
+ "PAGE_READWRITE", "PAGE_READONLY",
69
+ "MEM_COMMIT", "PROTECTIONS_OF_INTEREST",
70
+ ]
File without changes
@@ -0,0 +1,192 @@
1
+ """
2
+ core/api_hooker.py
3
+ ==================
4
+ Management of API hooks using the INT3 (0xCC) software breakpoint method.
5
+ Provides resolution of API addresses and management of the original bytes.
6
+ """
7
+
8
+ import ctypes
9
+ from ctypes import wintypes
10
+ from typing import Dict, Optional, Tuple
11
+
12
+
13
+ class HookManager:
14
+ """
15
+ Manages API hooks for a debugged process.
16
+ Handles address resolution, INT3 placement, and original byte tracking.
17
+ """
18
+
19
+ def __init__(self):
20
+ self.hooks = {} # (pid, addr) -> byte # Address -> Original Byte
21
+ self.api_names: Dict[int, str] = {} # Address -> API Name
22
+ self.kernel32 = ctypes.windll.kernel32
23
+
24
+ def resolve_api(self, dll_name: str, function_name: str) -> Optional[int]:
25
+ """
26
+ Resolves the address of an API in the target process.
27
+ Since system DLLs are loaded at the same address across processes (ASLR),
28
+ we can resolve it locally.
29
+ """
30
+ try:
31
+ h_module = self.kernel32.GetModuleHandleW(dll_name)
32
+ if not h_module:
33
+ h_module = self.kernel32.LoadLibraryW(dll_name)
34
+
35
+ if not h_module:
36
+ return None
37
+
38
+ addr = self.kernel32.GetProcAddress(h_module, function_name.encode('ascii'))
39
+ if addr == 0:
40
+ return None
41
+
42
+ # Check for JMP/Forwarder (simple check)
43
+ first_byte = ctypes.c_ubyte()
44
+ ctypes.memmove(ctypes.byref(first_byte), addr, 1)
45
+ if first_byte.value == 0xE9: # JMP
46
+ # This is a jump, we might be hitting a hot-patch point or a wrapper
47
+ pass
48
+
49
+ return addr
50
+ except Exception:
51
+ return None
52
+
53
+ def apply_hook(self, process_handle: int, address: int, api_name: str, pid: int) -> bool:
54
+ """
55
+ Places an INT3 (0xCC) hook at the specified address.
56
+ Backups the original byte for restoration later.
57
+ """
58
+ if (pid, address) in self.hooks:
59
+ return True # Already hooked
60
+
61
+ try:
62
+ # Read original byte
63
+ old_byte = ctypes.c_ubyte()
64
+ bytes_read = ctypes.c_size_t()
65
+ if not self.kernel32.ReadProcessMemory(
66
+ process_handle,
67
+ ctypes.c_void_p(address),
68
+ ctypes.byref(old_byte),
69
+ 1,
70
+ ctypes.byref(bytes_read)
71
+ ) or bytes_read.value != 1:
72
+ return False
73
+
74
+ # Check if it's already 0xCC (maybe another debugger or already hooked)
75
+ if old_byte.value == 0xCC:
76
+ # Still store it so we can handle the breakpoint if it's hit
77
+ if (pid, address) not in self.hooks:
78
+ self.hooks[(pid, address)] = 0xCC # We don't know original, but we track it
79
+ self.api_names[address] = api_name
80
+ return True
81
+
82
+ # Ensure memory is writable (VirtualProtectEx)
83
+ old_protect = wintypes.DWORD()
84
+ PAGE_EXECUTE_READWRITE = 0x40
85
+ if not self.kernel32.VirtualProtectEx(
86
+ process_handle,
87
+ ctypes.c_void_p(address),
88
+ 1,
89
+ PAGE_EXECUTE_READWRITE,
90
+ ctypes.byref(old_protect)
91
+ ):
92
+ return False
93
+
94
+ # Write 0xCC (INT3)
95
+ int3 = ctypes.c_ubyte(0xCC)
96
+ bytes_written = ctypes.c_size_t()
97
+
98
+ success = self.kernel32.WriteProcessMemory(
99
+ process_handle,
100
+ ctypes.c_void_p(address),
101
+ ctypes.byref(int3),
102
+ 1,
103
+ ctypes.byref(bytes_written)
104
+ ) and bytes_written.value == 1
105
+
106
+ # Restore protection
107
+ temp_protect = wintypes.DWORD()
108
+ self.kernel32.VirtualProtectEx(
109
+ process_handle,
110
+ ctypes.c_void_p(address),
111
+ 1,
112
+ old_protect,
113
+ ctypes.byref(temp_protect)
114
+ )
115
+
116
+ if not success:
117
+ return False
118
+
119
+ self.hooks[(pid, address)] = old_byte.value
120
+ self.api_names[address] = api_name
121
+
122
+ # Flush instruction cache
123
+ self.kernel32.FlushInstructionCache(process_handle, ctypes.c_void_p(address), 1)
124
+
125
+ # Verify write
126
+ verify_byte = ctypes.c_ubyte()
127
+ self.kernel32.ReadProcessMemory(process_handle, ctypes.c_void_p(address), ctypes.byref(verify_byte), 1,
128
+ None)
129
+ if verify_byte.value != 0xCC:
130
+ # print(f"Verification failed at {hex(address)}: expected 0xCC, got {hex(verify_byte.value)}")
131
+ return False
132
+
133
+ return True
134
+ except Exception:
135
+ return False
136
+
137
+ def remove_hook(self, process_handle: int, address: int, pid: int) -> bool:
138
+ """
139
+ Restores the original byte at the specified address.
140
+ """
141
+ if (pid, address) not in self.hooks:
142
+ return False
143
+
144
+ try:
145
+ # Ensure memory is writable (VirtualProtectEx)
146
+ old_protect = wintypes.DWORD()
147
+ PAGE_EXECUTE_READWRITE = 0x40
148
+ if not self.kernel32.VirtualProtectEx(
149
+ process_handle,
150
+ ctypes.c_void_p(address),
151
+ 1,
152
+ PAGE_EXECUTE_READWRITE,
153
+ ctypes.byref(old_protect)
154
+ ):
155
+ return False
156
+
157
+ original_byte = ctypes.c_ubyte(self.hooks[(pid, address)])
158
+ bytes_written = ctypes.c_size_t()
159
+
160
+ success = self.kernel32.WriteProcessMemory(
161
+ process_handle,
162
+ ctypes.c_void_p(address),
163
+ ctypes.byref(original_byte),
164
+ 1,
165
+ ctypes.byref(bytes_written)
166
+ ) and bytes_written.value == 1
167
+
168
+ # Restore protection
169
+ temp_protect = wintypes.DWORD()
170
+ self.kernel32.VirtualProtectEx(
171
+ process_handle,
172
+ ctypes.c_void_p(address),
173
+ 1,
174
+ old_protect,
175
+ ctypes.byref(temp_protect)
176
+ )
177
+
178
+ if not success:
179
+ return False
180
+
181
+ self.kernel32.FlushInstructionCache(process_handle, ctypes.c_void_p(address), 1)
182
+ return True
183
+ except Exception:
184
+ return False
185
+
186
+ def is_our_hook(self, address: int, pid: int) -> bool:
187
+ """Checks if the breakpoint hit is one of our hooks."""
188
+ return (pid, address) in self.hooks
189
+
190
+ def get_api_name(self, address: int) -> str:
191
+ """Returns the name of the API at the hooked address."""
192
+ return self.api_names.get(address, "UnknownAPI")
@@ -0,0 +1,124 @@
1
+ """
2
+ core/constants.py
3
+ ==================
4
+ All raw Windows API constants in one place.
5
+ Rule: Any magic number in the code is written here only once, and the rest of the files import it by name.
6
+
7
+ Reference source for all these values: winbase.h / winnt.h (Official Microsoft Documentation).
8
+ Do not modify a value here unless verified against official documentation — any error here fails silently
9
+ in a completely different place from this file.
10
+ """
11
+
12
+ # ============================================================
13
+ # Process Creation Flags (Passed to CreateProcessW)
14
+ # ============================================================
15
+
16
+ # ⚠️ CRITICAL FIX: The following two values are easily inverted by mistake because they are numerically sequential.
17
+ DEBUG_PROCESS = 0x00000001
18
+ # ↑ Monitors the launched process + every child process it spawns (via CreateProcess internally)
19
+ # automatically, without any extra effort. This is the correct choice for our project.
20
+
21
+ DEBUG_ONLY_THIS_PROCESS = 0x00000002
22
+ # ↑ Monitors only the launched process. Any child process it spawns runs completely free
23
+ # without any monitoring or reporting. Only use this if you intentionally want to ignore children.
24
+
25
+ CREATE_NEW_CONSOLE = 0x00000010
26
+ CREATE_SUSPENDED = 0x00000004
27
+ # ↑ Useful in the future: starts the process "frozen" before executing any instruction, giving you a chance
28
+ # to set Hardware Breakpoints before anything runs at all.
29
+
30
+ # ============================================================
31
+ # ContinueDebugEvent — How we respond to each event
32
+ # ============================================================
33
+
34
+ DBG_CONTINUE = 0x00010002
35
+ # ↑ "Handle this exception as if it didn't happen" — we use this for our own events
36
+ # (like Single-Step from a Hardware Breakpoint we set).
37
+
38
+ DBG_EXCEPTION_NOT_HANDLED = 0x80010001
39
+ # ↑ "Pass this exception to the target program's own exception handler"
40
+ # This is the correct default for any exception we didn't intentionally cause — many Packers
41
+ # use intentional exceptions as part of their unpacking mechanism, and if you accidentally
42
+ # reply DBG_CONTINUE, the target program crashes because its internal handler
43
+ # never received the exception it was expecting.
44
+
45
+ INFINITE = 0xFFFFFFFF
46
+
47
+ # ============================================================
48
+ # Debug Event Type Codes (DEBUG_EVENT.dwDebugEventCode)
49
+ # ============================================================
50
+
51
+ EXCEPTION_DEBUG_EVENT = 1
52
+ CREATE_THREAD_DEBUG_EVENT = 2
53
+ CREATE_PROCESS_DEBUG_EVENT = 3
54
+ EXIT_THREAD_DEBUG_EVENT = 4
55
+ EXIT_PROCESS_DEBUG_EVENT = 5
56
+ LOAD_DLL_DEBUG_EVENT = 6
57
+ UNLOAD_DLL_DEBUG_EVENT = 7
58
+ OUTPUT_DEBUG_STRING_EVENT = 8
59
+ RIP_EVENT = 9
60
+
61
+ EVENT_CODE_NAMES = {
62
+ EXCEPTION_DEBUG_EVENT: "EXCEPTION",
63
+ CREATE_THREAD_DEBUG_EVENT: "CREATE_THREAD",
64
+ CREATE_PROCESS_DEBUG_EVENT: "CREATE_PROCESS",
65
+ EXIT_THREAD_DEBUG_EVENT: "EXIT_THREAD",
66
+ EXIT_PROCESS_DEBUG_EVENT: "EXIT_PROCESS",
67
+ LOAD_DLL_DEBUG_EVENT: "LOAD_DLL",
68
+ UNLOAD_DLL_DEBUG_EVENT: "UNLOAD_DLL",
69
+ OUTPUT_DEBUG_STRING_EVENT: "OUTPUT_DEBUG_STRING",
70
+ RIP_EVENT: "RIP",
71
+ }
72
+
73
+ # ============================================================
74
+ # Common Exception Codes (EXCEPTION_RECORD.ExceptionCode)
75
+ # ============================================================
76
+
77
+ EXCEPTION_BREAKPOINT = 0x80000003
78
+ EXCEPTION_SINGLE_STEP = 0x80000004
79
+ # ↑ We receive this code after any Hardware Breakpoint we set (DR0-DR3) —
80
+ # important for the future "function tracing" feature.
81
+ EXCEPTION_ACCESS_VIOLATION = 0xC0000005
82
+
83
+ # ============================================================
84
+ # CONTEXT Flags (x64)
85
+ # ============================================================
86
+ CONTEXT_AMD64 = 0x00100000
87
+ CONTEXT_CONTROL = CONTEXT_AMD64 | 0x00000001
88
+ CONTEXT_INTEGER = CONTEXT_AMD64 | 0x00000002
89
+ CONTEXT_SEGMENTS = CONTEXT_AMD64 | 0x00000004
90
+ CONTEXT_FLOATING_POINT = CONTEXT_AMD64 | 0x00000008
91
+ CONTEXT_DEBUG_REGISTERS = CONTEXT_AMD64 | 0x00000010
92
+ CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT
93
+ CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS
94
+
95
+ # ============================================================
96
+ # Memory Protections (VirtualQueryEx / VirtualProtect)
97
+ # ============================================================
98
+
99
+ PAGE_NOACCESS = 0x01
100
+ PAGE_READONLY = 0x02
101
+ PAGE_READWRITE = 0x04
102
+ PAGE_EXECUTE = 0x10
103
+ PAGE_EXECUTE_READ = 0x20
104
+ PAGE_EXECUTE_READWRITE = 0x40
105
+ # ↑ This specific combination (Executable + Writable at the same time) is the most important
106
+ # "Red Flag" for catching self-unpacking moments — very few legitimate programs
107
+ # need a page with this combination during normal execution.
108
+
109
+ MEM_COMMIT = 0x1000
110
+ MEM_FREE = 0x10000
111
+
112
+ PROTECTIONS_OF_INTEREST = {
113
+ PAGE_EXECUTE_READWRITE,
114
+ PAGE_READWRITE,
115
+ PAGE_EXECUTE_READ,
116
+ }
117
+
118
+ # ============================================================
119
+ # OpenProcess Access Rights (Used in MEMORY_DUMP mode only — already running process)
120
+ # ============================================================
121
+
122
+ PROCESS_QUERY_INFORMATION = 0x0400
123
+ PROCESS_VM_READ = 0x0010
124
+ PROCESS_DUMP_ACCESS = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ