Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit 4608d7a

Browse files
Add files via upload
1 parent 916eeb1 commit 4608d7a

File tree

11 files changed

+826
-0
lines changed

11 files changed

+826
-0
lines changed

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
cmake_minimum_required(VERSION 4.0)
2+
project(example/pyCTools C)
3+
4+
set(CMAKE_C_STANDARD 11)
5+
6+
add_library(PyCTools SHARED
7+
src/hRng.c
8+
src/processInspect.c)

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Project Overview
2+
3+
This project provides a cross-language toolkit for Windows process inspection and hardware random number generation, with both Python and C components. It includes:
4+
5+
- **Python library (`pyCTools`)**: Easy-to-use wrappers for native DLLs to access process metrics and hardware RNG.
6+
- **C source files**: Implement the DLLs for process inspection and hardware RNG.
7+
- **Example Python scripts**: Demonstrate usage of the library.
8+
- **PowerShell build helper**: Automates DLL compilation for x86/x64.
9+
10+
## Directory Structure
11+
12+
```
13+
example/
14+
pyCTools/
15+
hwrng.py # Python wrapper for hardware RNG DLL
16+
processInspect.py # Python wrapper for process inspection DLL
17+
hwrng_example.py # Example: hardware RNG usage
18+
process_inspect_example.py # Example: process metrics usage
19+
src/
20+
hRng.c # C source for hardware RNG DLL
21+
processInspect.c # C source for process inspection DLL
22+
tool/
23+
compilerHelper.ps1 # PowerShell script to build DLLs for x86/x64
24+
dist/
25+
x64/ # Compiled DLLs for 64-bit
26+
x86/ # Compiled DLLs for 32-bit
27+
```
28+
29+
## Building the DLLs
30+
31+
1. Open PowerShell and run `tool/compilerHelper.ps1`.
32+
2. Select which `.c` files to compile.
33+
3. The script will build both x86 and x64 DLLs and place them in `dist/x86` and `dist/x64`.
34+
35+
## Using the Python Library
36+
37+
- Place the `dist/` folder as a sibling to your Python scripts or as described in the wrappers.
38+
- Import and use `pyCTools.hwrng` or `pyCTools.processInspect` as shown in the examples.
39+
40+
## Example Usage
41+
42+
**Hardware RNG:**
43+
```python
44+
from pyCTools.hwrng import get_hardware_random_bytes
45+
rb = get_hardware_random_bytes(16)
46+
print(rb.hex())
47+
```
48+
49+
**Process Inspection:**
50+
```python
51+
from pyCTools.processInspect import ProcessMetrics
52+
metrics = ProcessMetrics()
53+
pid = 1234 # Target PID
54+
flags = ProcessMetrics.METRIC_WORKING_SET | ProcessMetrics.METRIC_CPU_USAGE
55+
snapshot = metrics.get_snapshot(pid, flags)
56+
print(snapshot)
57+
```
58+
59+
## DLL Discovery
60+
61+
The Python wrappers automatically search for the correct DLL in:
62+
- `./dist/{arch}/<dll>`
63+
- `../dist/{arch}/<dll>`
64+
- `../../dist/{arch}/<dll>`
65+
66+
where `{arch}` is `x64` or `x86` depending on your Python interpreter.
67+
68+
## License
69+
70+
MIT or specify your license here.
71+

example/hwrng_example.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Optional CLI usage
2+
from pyCTools.hwrng import get_hardware_random_bytes
3+
4+
if __name__ == '__main__':
5+
try:
6+
rb = get_hardware_random_bytes(256)
7+
print("Random bytes:", rb.hex())
8+
except Exception as e:
9+
print("Error:", e)

example/process_inspect_example.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import os
2+
3+
from pyCTools.processInspect import ProcessMetrics
4+
5+
metrics = ProcessMetrics()
6+
pid = os.getpid() # Replace with your actual target PID
7+
flags = (
8+
ProcessMetrics.METRIC_WORKING_SET |
9+
ProcessMetrics.METRIC_PRIVATE_BYTES |
10+
ProcessMetrics.METRIC_CPU_USAGE |
11+
ProcessMetrics.METRIC_IO |
12+
ProcessMetrics.METRIC_THREADS |
13+
ProcessMetrics.METRIC_HANDLES
14+
)
15+
16+
# Start session
17+
if metrics.start_session(pid, flags):
18+
# Simulate some work to collect metrics
19+
# Time await without work
20+
import time
21+
time.sleep(5)
22+
23+
for i in range(3):
24+
# Simulate resource intensive work
25+
_ = [x ** 2 for x in range(10**6)]
26+
27+
# End session and get metrics delta
28+
result = metrics.end_session(pid, flags)
29+
print("Delta Metrics:", result)
30+
else:
31+
print("Failed to start metrics session.")
32+
33+
# Or get a snapshot instead
34+
try:
35+
# Some values will be unreliable if it's a snapshot, example CPU usage
36+
snapshot = metrics.get_snapshot(pid, flags)
37+
print("Instant Snapshot:", snapshot)
38+
except RuntimeError as e:
39+
print("Error:", str(e))
3.2 KB
Binary file not shown.
7.25 KB
Binary file not shown.

example/pyCTools/hwrng.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
hwrng_example.py
3+
4+
Hardware Random Number Generator (RDRAND) interface for Python using ctypes and a custom DLL.
5+
"""
6+
7+
import ctypes
8+
import os
9+
import platform
10+
11+
__all__ = ["get_hardware_random_bytes", "HardwareRNGError"]
12+
13+
14+
class HardwareRNGError(RuntimeError):
15+
"""Raised when hardware RNG fails or DLL cannot be loaded."""
16+
pass
17+
18+
19+
def _load_rng_function():
20+
arch = 'x64' if platform.architecture()[0] == '64bit' else 'x86'
21+
dll_name = f'hRng_{arch}.dll'
22+
base_dir = os.path.dirname(__file__)
23+
possible_dist_paths = [
24+
os.path.join(base_dir, 'dist', arch, dll_name),
25+
os.path.join(base_dir, '..', 'dist', arch, dll_name),
26+
os.path.join(base_dir, '..', '..', 'dist', arch, dll_name),
27+
]
28+
29+
dll_path = None
30+
for path in possible_dist_paths:
31+
abs_path = os.path.abspath(path)
32+
if os.path.exists(abs_path):
33+
dll_path = abs_path
34+
break
35+
36+
if dll_path is None:
37+
dll_path = os.path.abspath(possible_dist_paths[0]) # fallback for error message
38+
39+
dll = ctypes.CDLL(dll_path)
40+
41+
# Setup and return the function
42+
func = dll.read_hwrng
43+
func.argtypes = [ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int]
44+
func.restype = ctypes.c_int
45+
return func
46+
47+
48+
# Load the function only once
49+
_read_hwrng = _load_rng_function()
50+
51+
52+
def get_hardware_random_bytes(size: int) -> bytes:
53+
"""
54+
Retrieve cryptographically secure random bytes from the hardware RNG via a custom DLL.
55+
56+
Args:
57+
size (int): The number of random bytes to generate. Must be a positive integer.
58+
59+
Returns:
60+
bytes: A bytes object containing `size` random bytes from the hardware RNG.
61+
62+
Raises:
63+
ValueError: If `size` is not a positive integer.
64+
HardwareRNGError: If the hardware RNG fails or is not supported on this system.
65+
66+
Example:
67+
>>> random_bytes = get_hardware_random_bytes(16)
68+
>>> len(random_bytes)
69+
16
70+
"""
71+
if size <= 0:
72+
raise ValueError("Size must be a positive integer")
73+
74+
buffer = (ctypes.c_ubyte * size)()
75+
success = _read_hwrng(buffer, size)
76+
77+
if not success:
78+
raise HardwareRNGError("Hardware RNG failed or RDRAND not supported.")
79+
80+
return bytes(buffer)

example/pyCTools/processInspect.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import ctypes
2+
import os
3+
import json
4+
import platform
5+
from ctypes import c_char_p, c_size_t, c_ulong, create_string_buffer
6+
7+
8+
class ProcessMetrics:
9+
"""
10+
Wrapper class for interfacing with the native `processInspect` DLL that collects
11+
various system and process metrics on Windows. Supports querying working set,
12+
private bytes, pagefile usage, handle count, thread count, CPU usage, IO stats,
13+
and network stats.
14+
15+
The correct DLL is selected automatically based on the platform architecture (x64 or x86),
16+
searching multiple possible relative paths.
17+
18+
Methods allow starting a metric collection session, ending it (to retrieve final metrics),
19+
and getting an instant snapshot of metrics. All metric results are returned as parsed JSON dicts.
20+
21+
Attributes:
22+
METRIC_WORKING_SET (int): Flag for working set memory usage.
23+
METRIC_PRIVATE_BYTES (int): Flag for private bytes memory usage.
24+
METRIC_PAGEFILE (int): Flag for pagefile usage.
25+
METRIC_HANDLES (int): Flag for handle count.
26+
METRIC_THREADS (int): Flag for thread count.
27+
METRIC_CPU_USAGE (int): Flag for CPU usage percentage.
28+
METRIC_IO (int): Flag for I/O statistics.
29+
METRIC_NET (int): Flag for network statistics.
30+
31+
Raises:
32+
RuntimeError: If the DLL is not found or if metric collection fails.
33+
"""
34+
35+
METRIC_WORKING_SET = 0x01
36+
METRIC_PRIVATE_BYTES = 0x02
37+
METRIC_PAGEFILE = 0x04
38+
METRIC_HANDLES = 0x08
39+
METRIC_THREADS = 0x10
40+
METRIC_CPU_USAGE = 0x20
41+
METRIC_IO = 0x40
42+
METRIC_NET = 0x80
43+
44+
def __init__(self):
45+
"""
46+
Initialize ProcessMetrics instance by loading the appropriate DLL
47+
for the current platform architecture.
48+
49+
Searches for DLL in relative paths:
50+
- ./dist/{arch}/processInspect_{arch}.dll
51+
- ../dist/{arch}/processInspect_{arch}.dll
52+
- ../../dist/{arch}/processInspect_{arch}.dll
53+
54+
Raises:
55+
FileNotFoundError: If the DLL cannot be found in any of the expected locations.
56+
"""
57+
arch = 'x64' if platform.architecture()[0] == '64bit' else 'x86'
58+
dll_name = f'processInspect_{arch}.dll'
59+
base_dir = os.path.dirname(__file__)
60+
possible_dist_paths = [
61+
os.path.join(base_dir, 'dist', arch, dll_name),
62+
os.path.join(base_dir, '..', 'dist', arch, dll_name),
63+
os.path.join(base_dir, '..', '..', 'dist', arch, dll_name),
64+
]
65+
66+
dll_path = None
67+
for path in possible_dist_paths:
68+
abs_path = os.path.abspath(path)
69+
if os.path.exists(abs_path):
70+
dll_path = abs_path
71+
break
72+
73+
if dll_path is None:
74+
# Could not find DLL, raise an informative error
75+
raise FileNotFoundError(
76+
f"Could not find {dll_name} DLL in any of the expected locations:\n" +
77+
"\n".join(os.path.abspath(p) for p in possible_dist_paths)
78+
)
79+
80+
# Load the DLL using ctypes
81+
self._dll = ctypes.CDLL(dll_path)
82+
83+
# Define argument and return types of DLL functions for type safety
84+
self._dll.start_metrics_collection.argtypes = [c_ulong, c_ulong]
85+
self._dll.start_metrics_collection.restype = ctypes.c_int
86+
87+
self._dll.end_metrics_collection.argtypes = [c_ulong, c_ulong, c_char_p, c_size_t]
88+
self._dll.end_metrics_collection.restype = ctypes.c_int
89+
90+
self._dll.get_metrics_json.argtypes = [c_ulong, c_ulong, c_char_p, c_size_t]
91+
self._dll.get_metrics_json.restype = ctypes.c_int
92+
93+
@staticmethod
94+
def _json_call(func, pid: int, metrics: int) -> dict:
95+
"""
96+
Internal helper method to call a DLL function that returns JSON data
97+
in a buffer, parse it, and return as a Python dictionary.
98+
99+
Args:
100+
func (callable): DLL function to call, which fills a buffer with JSON.
101+
pid (int): Process ID to query.
102+
metrics (int): Bitmask of metrics flags to request.
103+
104+
Returns:
105+
dict: Parsed JSON metrics.
106+
107+
Raises:
108+
RuntimeError: If the DLL function call returns failure.
109+
"""
110+
buf = create_string_buffer(4096) # buffer size fixed to 4 KB
111+
success = func(pid, metrics, buf, ctypes.sizeof(buf))
112+
if not success:
113+
raise RuntimeError(f"Metric collection failed for PID {pid}")
114+
return json.loads(buf.value.decode('utf-8'))
115+
116+
def start_session(self, pid: int, metrics: int) -> bool:
117+
"""
118+
Start a metrics collection session for a specific process ID.
119+
120+
Args:
121+
pid (int): Process ID to start metrics collection for.
122+
metrics (int): Bitmask of metrics to collect (use class flags).
123+
124+
Returns:
125+
bool: True if session started successfully, False otherwise.
126+
"""
127+
return bool(self._dll.start_metrics_collection(pid, metrics))
128+
129+
def end_session(self, pid: int, metrics: int) -> dict:
130+
"""
131+
End a previously started metrics collection session and retrieve results.
132+
133+
Args:
134+
pid (int): Process ID of the session.
135+
metrics (int): Bitmask of metrics to retrieve.
136+
137+
Returns:
138+
dict: Metrics collected during the session.
139+
"""
140+
return self._json_call(self._dll.end_metrics_collection, pid, metrics)
141+
142+
def get_snapshot(self, pid: int, metrics: int) -> dict:
143+
"""
144+
Retrieve an instant snapshot of metrics for a process without starting a session.
145+
146+
Args:
147+
pid (int): Process ID to query.
148+
metrics (int): Bitmask of metrics to retrieve.
149+
150+
Returns:
151+
dict: Current metrics snapshot.
152+
"""
153+
return self._json_call(self._dll.get_metrics_json, pid, metrics)

0 commit comments

Comments
 (0)