Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bindings/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# libhat-python

136 changes: 136 additions & 0 deletions bindings/python/src/libhat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import ctypes
from contextlib import nullcontext

from ._ffi import _library
from .address import Address
from .enums import Protection, ScanAlignment, ScanHint
from .error import LibhatError
from .module import Module, Section, Segment
from .signature import Signature

__all__ = [
# functions
'get_version',
'get_version_num',
'parse_signature',
'create_signature',
'find_pattern',
'find_pattern_mod',
'is_readable',
'is_writable',
'is_executable',
'get_process_module',
'get_module',
'module_at',

# types
'Signature',
'Module',
'Section',
'Segment',
'Protection',
'ScanAlignment',
'ScanHint',
'Address',
'LibhatError',
'MemoryBuffer',
]

MemoryBuffer = bytes | bytearray | memoryview | ctypes.Array


def get_version() -> str:
return _library.libhat_get_version().decode('utf-8')


def get_version_num() -> int:
return _library.libhat_get_version_num()


def parse_signature(signature_str: str) -> Signature:
handle = ctypes.c_void_p()
status = _library.libhat_parse_signature(signature_str.encode('utf-8'), ctypes.byref(handle))
if status != 0:
raise LibhatError(status)
return Signature(handle)


def create_signature(sig_bytes: bytes, sig_mask: bytes) -> Signature:
if len(sig_bytes) != len(sig_mask):
raise ValueError('Mismatch between len(sig_bytes) and len(sig_mask)')
handle = ctypes.c_void_p()
status = _library.libhat_create_signature(sig_bytes, sig_mask, len(sig_mask), ctypes.byref(handle))
if status != 0:
raise LibhatError(status)
return Signature(handle)


def find_pattern(sig: str | Signature, buf: MemoryBuffer, align: ScanAlignment = ScanAlignment.X1,
hints: ScanHint = ScanHint(0)) -> int | None:
data, size = _underlying_buffer(buf)

context = parse_signature(sig) if isinstance(sig, str) else nullcontext(sig)
with context as s:
result = ctypes.c_void_p()
status = _library.libhat_find_pattern(s.handle, data, size, ctypes.byref(result), align.value, hints.value)
if status != 0:
raise LibhatError(status)
return result.value - data.value if result else None


def find_pattern_mod(sig: str | Signature, mod: Module, section: str, align: ScanAlignment = ScanAlignment.X1,
hints: ScanHint = ScanHint(0)) -> Address | None:
context = parse_signature(sig) if isinstance(sig, str) else nullcontext(sig)
with context as s:
result = ctypes.c_void_p()
status = _library.libhat_find_pattern_mod(s.handle, mod.handle, section.encode('utf-8'), ctypes.byref(result),
align.value, hints.value)
if status != 0:
raise LibhatError(status)
return Address(result.value)


def is_readable(buf: MemoryBuffer) -> bool:
data, size = _underlying_buffer(buf)
return _library.libhat_is_readable(data, size)


def is_writable(buf: MemoryBuffer) -> bool:
data, size = _underlying_buffer(buf)
return _library.libhat_is_writable(data, size)


def is_executable(buf: MemoryBuffer) -> bool:
data, size = _underlying_buffer(buf)
return _library.libhat_is_executable(data, size)


def get_process_module() -> Module:
return Module(ctypes.cast(_library.libhat_get_process_module(), ctypes.c_void_p))


def get_module(name: str) -> Module | None:
handle = ctypes.cast(_library.libhat_get_module(name.encode('utf-8')), ctypes.c_void_p)
return Module(handle) if handle else None


def module_at(addr: Address) -> Module | None:
handle = ctypes.cast(_library.libhat_module_at(addr), ctypes.c_void_p)
return Module(handle) if handle else None


def _underlying_buffer(buf: MemoryBuffer) -> tuple[ctypes.c_void_p, int]:
if isinstance(buf, bytes):
return ctypes.cast(buf, ctypes.c_void_p), len(buf)
elif isinstance(buf, bytearray):
return ctypes.cast((ctypes.c_ubyte * len(buf)).from_buffer(buf), ctypes.c_void_p), len(buf)
elif isinstance(buf, memoryview):
if buf.readonly:
raise ValueError('memoryview must be writable')
buf = buf.cast('B')
return ctypes.cast((ctypes.c_ubyte * len(buf)).from_buffer(buf), ctypes.c_void_p), len(buf)
elif isinstance(buf, ctypes.Array):
size = ctypes.sizeof(buf)
return ctypes.cast((ctypes.c_ubyte * size).from_buffer(buf), ctypes.c_void_p), size
else:
raise ValueError(f'unexpected type for buf: {type(buf)}')
218 changes: 218 additions & 0 deletions bindings/python/src/libhat/_ffi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import ctypes
import ctypes.util
import sys
from pathlib import Path


def _load_library() -> ctypes.CDLL:
ext = '.so'
if sys.platform.startswith('darwin'):
ext = '.dylib'
elif sys.platform.startswith('win'):
ext = '.dll'

load_dir = Path(__file__).parent
local = load_dir / ('libhat_c' + ext)
if local.exists():
return ctypes.cdll.LoadLibrary(str(local.absolute()))

if found := ctypes.util.find_library('libhat'):
return ctypes.cdll.LoadLibrary(found)

raise RuntimeError('could not locate libhat_c shared library')


_library = _load_library()

# ------------------------------------------------------------------------
# C types
# ------------------------------------------------------------------------
c_uintptr = ctypes.c_uint64 if ctypes.sizeof(ctypes.c_void_p) == 8 else ctypes.c_uint32

# ------------------------------------------------------------------------
# Structures
# ------------------------------------------------------------------------
class Span(ctypes.Structure):
_fields_ = [
('data', ctypes.c_void_p),
('size', ctypes.c_size_t),
]

def view(self) -> memoryview:
return memoryview((ctypes.c_ubyte * self.size).from_address(self.data))


# ------------------------------------------------------------------------
# Callback types
# ------------------------------------------------------------------------
ForEachSectionCallback = ctypes.CFUNCTYPE(
ctypes.c_bool,
ctypes.c_char_p,
Span,
ctypes.c_int,
ctypes.c_void_p,
)

ForEachSegmentCallback = ctypes.CFUNCTYPE(
ctypes.c_bool,
Span,
ctypes.c_int,
ctypes.c_void_p,
)

# ------------------------------------------------------------------------
# API
# ------------------------------------------------------------------------

#
# const char* libhat_get_version();
#
_library.libhat_get_version.argtypes = []
_library.libhat_get_version.restype = ctypes.c_char_p

#
# int libhat_get_version_num();
#
_library.libhat_get_version_num.argtypes = []
_library.libhat_get_version_num.restype = ctypes.c_int

#
# const char* libhat_status_to_string(libhat_status status);
#
_library.libhat_status_to_string.argtypes = [ctypes.c_int]
_library.libhat_status_to_string.restype = ctypes.c_char_p

#
# libhat_status libhat_parse_signature(
# const char* signatureStr,
# const libhat_signature** signatureOut
# );
#
_library.libhat_parse_signature.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
_library.libhat_parse_signature.restype = ctypes.c_int

#
# libhat_status libhat_create_signature(
# const char* bytes,
# const char* mask,
# size_t size,
# const libhat_signature** signatureOut
# );
#
_library.libhat_create_signature.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_size_t,
ctypes.POINTER(ctypes.c_void_p)]
_library.libhat_create_signature.restype = ctypes.c_int

#
# libhat_status libhat_find_pattern(
# const libhat_signature* signature,
# const void* buffer,
# size_t size,
# const void** resultOut,
# libhat_alignment align,
# libhat_hint hints
# );
#
_library.libhat_find_pattern.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t,
ctypes.POINTER(ctypes.c_void_p), ctypes.c_int, ctypes.c_int]
_library.libhat_find_pattern.restype = ctypes.c_int

#
# libhat_status libhat_find_pattern_mod(
# const libhat_signature* signature,
# const libhat_module* module,
# const char* section,
# const void** resultOut,
# libhat_alignment align,
# libhat_hint hints
# );
#
_library.libhat_find_pattern_mod.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p,
ctypes.POINTER(ctypes.c_void_p), ctypes.c_int, ctypes.c_int]
_library.libhat_find_pattern_mod.restype = ctypes.c_int

#
# libhat_status libhat_module_address(const libhat_module* module, uintptr_t* out);
#
_library.libhat_module_address.argtypes = [ctypes.c_void_p, ctypes.POINTER(c_uintptr)]
_library.libhat_module_address.restype = ctypes.c_int

#
# libhat_status libhat_module_get_data(const libhat_module* module, libhat_span* out);
#
_library.libhat_module_get_data.argtypes = [ctypes.c_void_p, ctypes.POINTER(Span)]
_library.libhat_module_get_data.restype = ctypes.c_int

#
# libhat_status libhat_module_get_executable_data(const libhat_module* module, libhat_span* out);
#
_library.libhat_module_get_executable_data.argtypes = [ctypes.c_void_p, ctypes.POINTER(Span)]
_library.libhat_module_get_executable_data.restype = ctypes.c_int

#
# libhat_status libhat_module_get_section_data(const libhat_module* module, const char* name, libhat_span* out);
#
_library.libhat_module_get_section_data.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(Span)]
_library.libhat_module_get_section_data.restype = ctypes.c_int

#
# libhat_status libhat_module_for_each_section(
# const libhat_module* module,
# libhat_for_each_section_cb callback,
# void* user_data
# );
#
_library.libhat_module_for_each_section.argtypes = [ctypes.c_void_p, ForEachSectionCallback, ctypes.c_void_p]
_library.libhat_module_for_each_section.restype = ctypes.c_int

#
# libhat_status libhat_module_for_each_segment(
# const libhat_module* module,
# libhat_for_each_segment_cb callback,
# void* user_data
# );
#
_library.libhat_module_for_each_segment.argtypes = [ctypes.c_void_p, ForEachSegmentCallback, ctypes.c_void_p]
_library.libhat_module_for_each_segment.restype = ctypes.c_int

#
# bool libhat_is_readable(const void* data, size_t size);
#
_library.libhat_is_readable.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
_library.libhat_is_readable.restype = ctypes.c_bool

#
# bool libhat_is_writable(const void* data, size_t size);
#
_library.libhat_is_writable.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
_library.libhat_is_writable.restype = ctypes.c_bool

#
# bool libhat_is_executable(const void* data, size_t size);
#
_library.libhat_is_executable.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
_library.libhat_is_executable.restype = ctypes.c_bool

#
# const libhat_module* libhat_get_process_module();
#
_library.libhat_get_process_module.argtypes = []
_library.libhat_get_process_module.restype = ctypes.c_void_p

#
# const libhat_module* libhat_get_module(const char* name);
#
_library.libhat_get_module.argtypes = [ctypes.c_char_p]
_library.libhat_get_module.restype = ctypes.c_void_p

#
# const libhat_module* libhat_module_at(const void* address);
#
_library.libhat_module_at.argtypes = [ctypes.c_void_p]
_library.libhat_module_at.restype = ctypes.c_void_p

#
# void libhat_free(const void* object);
#
_library.libhat_free.argtypes = [ctypes.c_void_p]
_library.libhat_free.restype = None
2 changes: 2 additions & 0 deletions bindings/python/src/libhat/address.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Address(int):
"""A virtual memory address"""
19 changes: 19 additions & 0 deletions bindings/python/src/libhat/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from enum import Enum, Flag, auto


class Protection(Flag):
Read = auto()
Write = auto()
Execute = auto()


class ScanAlignment(Enum):
X1 = 1
X4 = 4
X16 = 16


class ScanHint(Flag):
X86_64 = auto()
PAIR0 = auto()
AARCH64 = auto()
6 changes: 6 additions & 0 deletions bindings/python/src/libhat/error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from ._ffi import _library

class LibhatError(Exception):
def __init__(self, status: int):
self.status = status
super().__init__(_library.libhat_status_to_string(status).decode('utf-8'))
Loading
Loading