diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 3598378..ea5d884 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -4,13 +4,12 @@ jobs: black: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - uses: psf/black@stable isort: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.8 - - uses: jamescurtin/isort-action@master + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 + - run: pip install isort + - run: isort --check-only --diff . diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 3022fd0..9a747f5 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -4,12 +4,13 @@ jobs: pyflakes: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: lgeiger/pyflakes-action@master + - uses: actions/checkout@v7 + - run: pip install pyflakes + - run: pyflakes $(git ls-files '*.py' | grep -v '^frida/frida_bindgen/assets/') mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - uses: jpetrucciani/mypy-check@master with: - mypy_flags: '--exclude examples --exclude setup' + mypy_flags: '--exclude examples --exclude setup --exclude frida/frida_bindgen/assets' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..e4cc68b --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,19 @@ +name: tests +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + - uses: actions/setup-python@v6 + with: + python-version: '3.x' + - name: Install build dependencies + run: pip install setuptools + - name: Build the extension + run: make + - name: Run the test suite + run: python3 -m unittest tests.test_bindgen -v diff --git a/.gitignore b/.gitignore index 40d32fa..97dc772 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.pyc *.user +*.abi3.so /*.egg-info/ +/.cache/ /build/ /dist/ diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..596dc60 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include frida/extension.version +include frida/py.typed +recursive-include frida/frida_bindgen *.py *.c *.h +recursive-include frida/frida_bindgen_core *.py diff --git a/README.md b/README.md index d891382..761c506 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # frida-python -Python [bindings](https://github.com/frida/frida-python) for [Frida](https://frida.re) but with devkit. +Python [bindings](https://github.com/frida/frida-python) for [Frida](https://frida.re), built against a Frida devkit. + +The extension and its Python facade are generated at install time from the devkit's `frida-core.gir`, then linked against its `libfrida-core` library. No Frida source checkout or Frida source build is needed. # Some tips during development @@ -12,7 +14,7 @@ pip install --force-reinstall frida--cp37-abi3-linux_aarch64.whl ``` > [!NOTE] -> Note that you use devkit of same version which you're installing for. +> Use the devkit for the same version that you are installing. The generator also needs the system `GLib-2.0.gir`, `GObject-2.0.gir`, and `Gio-2.0.gir` files. On Termux these are normally in `$PREFIX/share/gir-1.0`. Set `FRIDA_GIR_DIR` if they are elsewhere. ## Example: diff --git a/frida/__init__.py b/frida/__init__.py deleted file mode 100644 index 46cf817..0000000 --- a/frida/__init__.py +++ /dev/null @@ -1,189 +0,0 @@ -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union - -try: - from . import _frida -except Exception as ex: - print("") - print("***") - if str(ex).startswith("No module named "): - print("Frida native extension not found") - print("Please check your PYTHONPATH.") - else: - print(f"Failed to load the Frida native extension: {ex}") - print("Please ensure that the extension was compiled correctly") - print("***") - print("") - raise ex -from . import core - -__version__: str = _frida.__version__ - -get_device_manager = core.get_device_manager -Relay = _frida.Relay -PortalService = core.PortalService -EndpointParameters = core.EndpointParameters -Compiler = core.Compiler -PackageManager = core.PackageManager -FileMonitor = _frida.FileMonitor -Cancellable = core.Cancellable - -ServerNotRunningError = _frida.ServerNotRunningError -ExecutableNotFoundError = _frida.ExecutableNotFoundError -ExecutableNotSupportedError = _frida.ExecutableNotSupportedError -ProcessNotFoundError = _frida.ProcessNotFoundError -ProcessNotRespondingError = _frida.ProcessNotRespondingError -InvalidArgumentError = _frida.InvalidArgumentError -InvalidOperationError = _frida.InvalidOperationError -PermissionDeniedError = _frida.PermissionDeniedError -AddressInUseError = _frida.AddressInUseError -TimedOutError = _frida.TimedOutError -NotSupportedError = _frida.NotSupportedError -ProtocolError = _frida.ProtocolError -TransportError = _frida.TransportError -OperationCancelledError = _frida.OperationCancelledError - - -def query_system_parameters() -> Dict[str, Any]: - """ - Returns a dictionary of information about the host system - """ - - return get_local_device().query_system_parameters() - - -def spawn( - program: Union[str, List[Union[str, bytes]], Tuple[Union[str, bytes]]], - argv: Union[None, List[Union[str, bytes]], Tuple[Union[str, bytes]]] = None, - envp: Optional[Dict[str, str]] = None, - env: Optional[Dict[str, str]] = None, - cwd: Optional[str] = None, - stdio: Optional[str] = None, - **kwargs: Any, -) -> int: - """ - Spawn a process into an attachable state - """ - - return get_local_device().spawn(program=program, argv=argv, envp=envp, env=env, cwd=cwd, stdio=stdio, **kwargs) - - -def resume(target: core.ProcessTarget) -> None: - """ - Resume a process from the attachable state - :param target: the PID or name of the process - """ - - get_local_device().resume(target) - - -def kill(target: core.ProcessTarget) -> None: - """ - Kill a process - :param target: the PID or name of the process - """ - - get_local_device().kill(target) - - -def attach( - target: core.ProcessTarget, - realm: Optional[str] = None, - persist_timeout: Optional[int] = None, - exceptor: Optional[str] = None, - unwind_broker: Optional[bool] = None, - exit_monitor: Optional[bool] = None, - thread_suspend_monitor: Optional[bool] = None, - linker_notifier_offsets: Optional[Sequence[int]] = None, -) -> core.Session: - """ - Attach to a process - :param target: the PID or name of the process - """ - - return get_local_device().attach( - target, - realm=realm, - persist_timeout=persist_timeout, - exceptor=exceptor, - unwind_broker=unwind_broker, - exit_monitor=exit_monitor, - thread_suspend_monitor=thread_suspend_monitor, - linker_notifier_offsets=linker_notifier_offsets, - ) - - -def inject_library_file(target: core.ProcessTarget, path: str, entrypoint: str, data: str) -> int: - """ - Inject a library file to a process. - :param target: the PID or name of the process - """ - - return get_local_device().inject_library_file(target, path, entrypoint, data) - - -def inject_library_blob(target: core.ProcessTarget, blob: bytes, entrypoint: str, data: str) -> int: - """ - Inject a library blob to a process - :param target: the PID or name of the process - """ - - return get_local_device().inject_library_blob(target, blob, entrypoint, data) - - -def get_local_device() -> core.Device: - """ - Get the local device - """ - - return get_device_manager().get_local_device() - - -def get_remote_device() -> core.Device: - """ - Get the first remote device in the devices list - """ - - return get_device_manager().get_remote_device() - - -def get_usb_device(timeout: int = 0) -> core.Device: - """ - Get the first device connected over USB in the devices list - """ - - return get_device_manager().get_usb_device(timeout) - - -def get_device(id: Optional[str], timeout: int = 0) -> core.Device: - """ - Get a device by its id - """ - - return get_device_manager().get_device(id, timeout) - - -def get_device_matching(predicate: Callable[[core.Device], bool], timeout: int = 0) -> core.Device: - """ - Get device matching predicate. - :param predicate: a function to filter the devices - :param timeout: operation timeout in seconds - """ - - return get_device_manager().get_device_matching(predicate, timeout) - - -def enumerate_devices() -> List[core.Device]: - """ - Enumerate all the devices from the device manager - """ - - return get_device_manager().enumerate_devices() - - -@core.cancellable -def shutdown() -> None: - """ - Shutdown the main device manager - """ - - get_device_manager()._impl.close() diff --git a/frida/_frida/__init__.pyi b/frida/_frida/__init__.pyi deleted file mode 100644 index 618e2e1..0000000 --- a/frida/_frida/__init__.pyi +++ /dev/null @@ -1,905 +0,0 @@ -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union - -# Exceptions -class AddressInUseError(Exception): ... -class ExecutableNotFoundError(Exception): ... -class ExecutableNotSupportedError(Exception): ... -class ServerNotRunningError(Exception): ... -class TimedOutError(Exception): ... -class TransportError(Exception): ... -class ProcessNotFoundError(Exception): ... -class ProcessNotRespondingError(Exception): ... -class ProtocolError(Exception): ... -class InvalidArgumentError(Exception): ... -class InvalidOperationError(Exception): ... -class NotSupportedError(Exception): ... -class OperationCancelledError(Exception): ... -class PermissionDeniedError(Exception): ... - -class Object: - def __init__(self, *args: Any, **kwargs: Any) -> None: ... - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler. - """ - ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler. - """ - ... - -class Application(Object): - @property - def identifier(self) -> str: - """ - Application identifier. - """ - ... - - @property - def name(self) -> str: - """ - Human-readable application name. - """ - ... - - @property - def parameters(self) -> Dict[str, Any]: - """ - Parameters. - """ - ... - - @property - def pid(self) -> int: - """ - Process ID, or 0 if not running. - """ - ... - -class Bus(Object): - def attach(self) -> None: - """ - Attach to the bus. - """ - ... - - def post(self, message: str, data: Optional[Union[bytes, str]]) -> None: - """ - Post a JSON-encoded message to the bus. - """ - ... - -class Cancellable(Object): - def cancel(self) -> None: - """ - Set cancellable to cancelled. - """ - ... - - def connect(self, callback: Callable[..., Any]) -> int: - """ - Register notification callback. - """ - ... - - def disconnect(self, handler_id: int) -> None: - """ - Unregister notification callback. - """ - ... - - @classmethod - def get_current(cls) -> "Cancellable": - """ - Get the top cancellable from the stack. - """ - ... - - def get_fd(self) -> int: - """ - Get file descriptor for integrating with an event loop. - """ - ... - - def is_cancelled(self) -> bool: - """ - Query whether cancellable has been cancelled. - """ - ... - - def pop_current(self) -> None: - """ - Pop cancellable off the cancellable stack. - """ - ... - - def push_current(self) -> None: - """ - Push cancellable onto the cancellable stack. - """ - ... - - def raise_if_cancelled(self) -> None: - """ - Raise an exception if cancelled. - """ - ... - - def release_fd(self) -> None: - """ - Release a resource previously allocated by get_fd(). - """ - ... - -class Child(Object): - @property - def argv(self) -> List[str]: - """ - Argument vector. - """ - ... - - @property - def envp(self) -> Dict[str, str]: - """ - Environment vector. - """ - ... - - @property - def identifier(self) -> str: - """ - Application identifier. - """ - ... - - @property - def origin(self) -> str: - """ - Origin. - """ - ... - - @property - def parent_pid(self) -> int: - """ - Parent Process ID. - """ - ... - - @property - def path(self) -> str: - """ - Path of executable. - """ - ... - - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - -class Crash(Object): - @property - def parameters(self) -> Dict[str, Any]: - """ - Parameters. - """ - ... - - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - - @property - def process_name(self) -> str: - """ - Process name. - """ - ... - - @property - def report(self) -> str: - """ - Human-readable crash report. - """ - ... - - @property - def summary(self) -> str: - """ - Human-readable crash summary. - """ - ... - -class Device(Object): - @property - def id(self) -> Optional[str]: - """ - Device ID. - """ - ... - - @property - def name(self) -> Optional[str]: - """ - Human-readable device name. - """ - ... - - @property - def icon(self) -> Optional[Any]: - """ - Icon. - """ - ... - - @property - def type(self) -> Optional[str]: - """ - Device type. One of: local, remote, usb. - """ - ... - - @property - def bus(self) -> Optional[Bus]: - """ - Message bus. - """ - ... - - def attach( - self, - pid: int, - realm: Optional[str] = None, - persist_timeout: Optional[int] = None, - exceptor: Optional[str] = None, - unwind_broker: Optional[bool] = None, - exit_monitor: Optional[bool] = None, - thread_suspend_monitor: Optional[bool] = None, - linker_notifier_offsets: Optional[Sequence[int]] = None, - ) -> "Session": - """ - Attach to a PID. - """ - ... - - def disable_spawn_gating(self) -> None: - """ - Disable spawn gating. - """ - ... - - def enable_spawn_gating(self) -> None: - """ - Enable spawn gating. - """ - ... - - def enumerate_applications( - self, identifiers: Optional[Sequence[str]] = None, scope: Optional[str] = None - ) -> List[Application]: - """ - Enumerate applications. - """ - ... - - def enumerate_pending_children(self) -> List[Child]: - """ - Enumerate pending children. - """ - ... - - def enumerate_pending_spawn(self) -> List["Spawn"]: - """ - Enumerate pending spawn. - """ - ... - - def enumerate_processes(self, pids: Optional[Sequence[int]] = None, scope: Optional[str] = None) -> List[Process]: - """ - Enumerate processes. - """ - ... - - def get_frontmost_application(self, scope: Optional[str] = None) -> Optional[Application]: - """ - Get details about the frontmost application. - """ - ... - - def inject_library_blob(self, pid: int, blob_buffer: bytes, entrypoint: str, data: str) -> int: - """ - Inject a library blob to a PID. - """ - ... - - def inject_library_file(self, pid: int, path: str, entrypoint: str, data: str) -> int: - """ - Inject a library file to a PID. - """ - ... - - def input(self, pid: int, data: bytes) -> None: - """ - Input data on stdin of a spawned process. - """ - ... - - def is_lost(self) -> bool: - """ - Query whether the device has been lost. - """ - ... - - def kill(self, pid: int) -> None: - """ - Kill a PID. - """ - ... - - def open_channel(self, address: str) -> "IOStream": - """ - Open a device-specific communication channel. - """ - ... - - def open_service(self, address: str) -> Service: - """ - Open a device-specific service. - """ - ... - - def unpair(self) -> None: - """ - Unpair device. - """ - ... - - def override_option(self, name: str, value: Any) -> None: - """ - Override a backend-specific option. - """ - ... - - def query_system_parameters(self) -> Dict[str, Any]: - """ - Returns a dictionary of information about the host system. - """ - ... - - def resume(self, pid: int) -> None: - """ - Resume a process from the attachable state. - """ - ... - - def spawn( - self, - program: str, - argv: Union[None, List[Union[str, bytes]], Tuple[Union[str, bytes]]] = None, - envp: Optional[Dict[str, str]] = None, - env: Optional[Dict[str, str]] = None, - cwd: Optional[str] = None, - stdio: Optional[str] = None, - **kwargs: Any, - ) -> int: - """ - Spawn a process into an attachable state. - """ - ... - -class DeviceManager(Object): - def add_remote_device( - self, - address: str, - certificate: Optional[str] = None, - origin: Optional[str] = None, - token: Optional[str] = None, - keepalive_interval: Optional[int] = None, - ) -> Device: - """ - Add a remote device. - """ - ... - - def close(self) -> None: - """ - Close the device manager. - """ - ... - - def enumerate_devices(self) -> List[Device]: - """ - Enumerate devices. - """ - ... - - def get_device_matching(self, predicate: Callable[[Device], bool], timeout: int) -> Device: - """ - Get device matching predicate. - """ - ... - - def remove_remote_device(self, address: str) -> None: - """ - Remove a remote device. - """ - ... - -class EndpointParameters(Object): ... - -class FileMonitor(Object): - def disable(self) -> None: - """ - Disable the file monitor. - """ - ... - - def enable(self) -> None: - """ - Enable the file monitor. - """ - ... - -class IOStream(Object): - def close(self) -> None: - """ - Close the stream. - """ - ... - - def is_closed(self) -> bool: - """ - Query whether the stream is closed. - """ - ... - - def read(self, size: int) -> bytes: - """ - Read up to the specified number of bytes from the stream. - """ - ... - - def read_all(self, size: int) -> bytes: - """ - Read exactly the specified number of bytes from the stream. - """ - ... - - def write(self, data: bytes) -> int: - """ - Write as much as possible of the provided data to the stream. - """ - ... - - def write_all(self, data: bytes) -> None: - """ - Write all of the provided data to the stream. - """ - ... - -class PortalMembership(Object): - def terminate(self) -> None: - """ - Terminate the membership. - """ - ... - -class PortalService(Object): - @property - def device(self) -> Device: - """ - Device for in-process control. - """ - ... - - def broadcast(self, message: str, data: Optional[Union[str, bytes]] = None) -> None: - """ - Broadcast a message to all control channels. - """ - ... - - def enumerate_tags(self, connection_id: int) -> List[str]: - """ - Enumerate tags of a specific connection. - """ - ... - - def kick(self, connection_id: int) -> None: - """ - Kick out a specific connection. - """ - ... - - def narrowcast(self, tag: str, message: str, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a message to control channels with a specific tag. - """ - ... - - def post(self, connection_id: int, message: str, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a message to a specific control channel. - """ - ... - - def start(self) -> None: - """ - Start listening for incoming connections. - """ - ... - - def stop(self) -> None: - """ - Stop listening for incoming connections, and kick any connected clients. - """ - ... - - def tag(self, connection_id: int, tag: str) -> None: - """ - Tag a specific control channel. - """ - ... - - def untag(self, connection_id: int, tag: str) -> None: - """ - Untag a specific control channel. - """ - ... - -class Process(Object): - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - - @property - def name(self) -> str: - """ - Human-readable process name. - """ - ... - - @property - def parameters(self) -> Dict[str, Any]: - """ - Parameters. - """ - ... - -class Relay(Object): - def __init__(self, address: str, username: str, password: str, kind: str) -> None: ... - @property - def address(self) -> str: - """ - Network address or address:port of the TURN server. - """ - ... - - @property - def kind(self) -> str: - """ - Relay kind. One of: turn-udp, turn-tcp, turn-tls. - """ - ... - - @property - def password(self) -> str: - """ - The TURN password to use for the allocate request. - """ - ... - - @property - def username(self) -> str: - """ - The TURN username to use for the allocate request. - """ - ... - -class Script(Object): - def eternalize(self) -> None: - """ - Eternalize the script. - """ - ... - - def is_destroyed(self) -> bool: - """ - Query whether the script has been destroyed. - """ - ... - - def load(self) -> None: - """ - Load the script. - """ - ... - - def interrupt(self) -> None: - """ - Interrupt any JavaScript currently executing in the script. - """ - ... - - def post(self, message: str, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a JSON-encoded message to the script. - """ - ... - - def unload(self) -> None: - """ - Unload the script. - """ - ... - - def terminate(self) -> None: - """ - Interrupt execution and unload the script. - """ - ... - - def enable_debugger(self, port: Optional[int]) -> None: - """ - Enable the Node.js compatible script debugger - """ - ... - - def disable_debugger(self) -> None: - """ - Disable the Node.js compatible script debugger - """ - ... - -class Service(Object): - def activate(self) -> None: - """ - Activate the service. - """ - ... - - def cancel(self) -> None: - """ - Cancel the service. - """ - ... - - def request(self, parameters: Any) -> Any: - """ - Perform a request. - """ - ... - -class Session(Object): - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - - def compile_script(self, source: str, name: Optional[str] = None, runtime: Optional[str] = None) -> bytes: - """ - Compile script source code to bytecode. - """ - ... - - def create_script(self, source: str, name: Optional[str] = None, runtime: Optional[str] = None) -> Script: - """ - Create a new script. - """ - ... - - def create_script_from_bytes( - self, data: bytes, name: Optional[str] = None, runtime: Optional[str] = None - ) -> Script: - """ - Create a new script from bytecode. - """ - ... - - def snapshot_script(self, embed_script: str, warmup_script: Optional[str], runtime: Optional[str] = None) -> bytes: - """ - Evaluate script and snapshot the resulting VM state - """ - ... - - def detach(self) -> None: - """ - Detach session from the process. - """ - ... - - def disable_child_gating(self) -> None: - """ - Disable child gating. - """ - ... - - def enable_child_gating(self) -> None: - """ - Enable child gating. - """ - ... - - def is_detached(self) -> bool: - """ - Query whether the session is detached. - """ - ... - - def join_portal( - self, address: str, certificate: Optional[str] = None, token: Optional[str] = None, acl: Optional[Any] = None - ) -> PortalMembership: - """ - Join a portal. - """ - ... - - def resume(self) -> None: - """ - Resume session after network error. - """ - ... - - def setup_peer_connection( - self, stun_server: Optional[str] = None, relays: Optional[Sequence[Relay]] = None - ) -> None: - """ - Set up a peer connection with the target process. - """ - ... - -class Spawn(Object): - @property - def identifier(self) -> str: - """ - Application identifier. - """ - ... - - @property - def pid(self) -> int: - """ - Process ID. - """ - ... - -class Compiler(Object): - def build( - self, - entrypoint: str, - project_root: Optional[str] = None, - output_format: Optional[str] = None, - bundle_format: Optional[str] = None, - type_check: Optional[str] = None, - source_maps: Optional[str] = None, - compression: Optional[str] = None, - platform: Optional[str] = None, - externals: Optional[Sequence[str]] = None, - ) -> str: - """ - Build an agent. - """ - ... - - def watch( - self, - entrypoint: str, - project_root: Optional[str] = None, - output_format: Optional[str] = None, - bundle_format: Optional[str] = None, - type_check: Optional[str] = None, - source_maps: Optional[str] = None, - compression: Optional[str] = None, - platform: Optional[str] = None, - externals: Optional[Sequence[str]] = None, - ) -> None: - """ - Continuously build an agent. - """ - ... - -class PackageManager(Object): - @property - def registry(self) -> str: - """ - The registry being used. - """ - ... - - @registry.setter - def registry(self, value: str) -> None: - """ - Change the registry to use. - """ - ... - - def search( - self, - query: str, - offset: Optional[int] = None, - limit: Optional[int] = None, - ) -> PackageSearchResult: - """ - Search for packages to install. - """ - ... - - def install( - self, - project_root: Optional[str] = None, - role: Optional[str] = None, - specs: Optional[Sequence[str]] = None, - omits: Optional[Sequence[str]] = None, - ) -> PackageInstallResult: - """ - Install one or more packages. - """ - ... - -class Package(Object): - @property - def name(self) -> str: - """ - Package name. - """ - ... - - @property - def version(self) -> str: - """ - Package version. - """ - ... - - @property - def description(self) -> Optional[str]: - """ - Package description. - """ - ... - - @property - def url(self) -> Optional[str]: - """ - Package URL. - """ - ... - -class PackageSearchResult(Object): - @property - def packages(self) -> List[Package]: - """ - Batch of matching packages. - """ - ... - - @property - def total(self) -> int: - """ - Total matching packages. - """ - ... - -class PackageInstallResult(Object): - @property - def packages(self) -> List[Package]: - """ - The toplevel packages that are installed. - """ - ... - -__version__: str diff --git a/frida/_frida/extension.c b/frida/_frida/extension.c deleted file mode 100644 index 58ca6b0..0000000 --- a/frida/_frida/extension.c +++ /dev/null @@ -1,6298 +0,0 @@ -/* - * Copyright (C) 2013-2026 Ole André Vadla Ravnås - * Copyright (C) 2024 Håvard Sørbø - * - * Licence: wxWindows Library Licence, Version 3.1 - */ - -#include -#include - -#ifdef _MSC_VER -# pragma warning (push) -# pragma warning (disable: 4115) -# pragma warning (disable: 4211) -#endif -#ifdef _POSIX_C_SOURCE -# undef _POSIX_C_SOURCE -#endif - -#define PY_SSIZE_T_CLEAN - -/* - * Don't propagate _DEBUG state to pyconfig as it incorrectly attempts to load - * debug libraries that don't normally ship with Python (e.g. 2.x). Debuggers - * wishing to spelunk the Python core can override this workaround by defining - * _FRIDA_ENABLE_PYDEBUG. - */ -#if defined (_DEBUG) && !defined (_FRIDA_ENABLE_PYDEBUG) -# undef _DEBUG -# include -# define _DEBUG -#else -# include -#endif - -#include -#include -#include -#ifdef _MSC_VER -# pragma warning (pop) -#endif -#ifdef __APPLE__ -# include -# if TARGET_OS_OSX -# include -# endif -#endif - -#define PYFRIDA_TYPE(name) \ - (&_PYFRIDA_TYPE_VAR (name, type)) -#define PYFRIDA_TYPE_OBJECT(name) \ - PYFRIDA_TYPE (name)->object -#define _PYFRIDA_TYPE_VAR(name, var) \ - G_PASTE (G_PASTE (G_PASTE (Py, name), _), var) -#define PYFRIDA_DEFINE_BASETYPE(pyname, cname, init_func, destroy_func, ...) \ - _PYFRIDA_DEFINE_TYPE_SLOTS (cname, __VA_ARGS__); \ - _PYFRIDA_DEFINE_TYPE_SPEC (cname, pyname, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE); \ - static PyGObjectType _PYFRIDA_TYPE_VAR (cname, type) = \ - { \ - .parent = NULL, \ - .object = NULL, \ - .init_from_handle = (PyGObjectInitFromHandleFunc) init_func, \ - .destroy = destroy_func, \ - } -#define PYFRIDA_DEFINE_TYPE(pyname, cname, parent_cname, init_func, destroy_func, ...) \ - _PYFRIDA_DEFINE_TYPE_SLOTS (cname, __VA_ARGS__); \ - _PYFRIDA_DEFINE_TYPE_SPEC (cname, pyname, Py_TPFLAGS_DEFAULT); \ - static PyGObjectType _PYFRIDA_TYPE_VAR (cname, type) = \ - { \ - .parent = PYFRIDA_TYPE (parent_cname), \ - .object = NULL, \ - .init_from_handle = (PyGObjectInitFromHandleFunc) init_func, \ - .destroy = destroy_func, \ - } -#define PYFRIDA_REGISTER_TYPE(cname, gtype) \ - G_BEGIN_DECLS \ - { \ - PyGObjectType * t = PYFRIDA_TYPE (cname); \ - t->object = PyType_FromSpecWithBases (&_PYFRIDA_TYPE_VAR (cname, spec), \ - (t->parent != NULL) ? PyTuple_Pack (1, t->parent->object) : NULL); \ - PyGObject_register_type (gtype, t); \ - Py_IncRef (t->object); \ - PyModule_AddObject (module, G_STRINGIFY (cname), t->object); \ - } \ - G_END_DECLS -#define _PYFRIDA_DEFINE_TYPE_SPEC(cname, pyname, type_flags) \ - static PyType_Spec _PYFRIDA_TYPE_VAR (cname, spec) = \ - { \ - .name = pyname, \ - .basicsize = sizeof (G_PASTE (Py, cname)), \ - .itemsize = 0, \ - .flags = type_flags, \ - .slots = _PYFRIDA_TYPE_VAR (cname, slots), \ - } -#define _PYFRIDA_DEFINE_TYPE_SLOTS(cname, ...) \ - static PyType_Slot _PYFRIDA_TYPE_VAR (cname, slots)[] = \ - { \ - __VA_ARGS__ \ - { 0 }, \ - } - -#define PY_GOBJECT(o) ((PyGObject *) (o)) -#define PY_GOBJECT_HANDLE(o) (PY_GOBJECT (o)->handle) -#define PY_GOBJECT_SIGNAL_CLOSURE(o) ((PyGObjectSignalClosure *) (o)) - -#define PyFrida_RETURN_NONE \ - G_STMT_START \ - { \ - Py_IncRef (Py_None); \ - return Py_None; \ - } \ - G_STMT_END - -static struct PyModuleDef PyFrida_moduledef = { PyModuleDef_HEAD_INIT, "_frida", "Frida", -1, NULL, }; - -static volatile gint toplevel_objects_alive = 0; - -static PyObject * inspect_getargspec; -static PyObject * inspect_ismethod; - -static PyObject * datetime_constructor; - -static initproc PyGObject_tp_init; -static destructor PyGObject_tp_dealloc; -static GHashTable * pygobject_type_spec_by_type; -static GHashTable * frida_exception_by_error_code; -static PyObject * cancelled_exception; - -typedef struct _PyGObject PyGObject; -typedef struct _PyGObjectType PyGObjectType; -typedef struct _PyGObjectSignalClosure PyGObjectSignalClosure; -typedef struct _PyDeviceManager PyDeviceManager; -typedef struct _PyDevice PyDevice; -typedef struct _PyApplication PyApplication; -typedef struct _PyProcess PyProcess; -typedef struct _PySpawn PySpawn; -typedef struct _PyChild PyChild; -typedef struct _PyCrash PyCrash; -typedef struct _PyBus PyBus; -typedef struct _PyService PyService; -typedef struct _PySession PySession; -typedef struct _PyScript PyScript; -typedef struct _PyRelay PyRelay; -typedef struct _PyPortalMembership PyPortalMembership; -typedef struct _PyPortalService PyPortalService; -typedef struct _PyEndpointParameters PyEndpointParameters; -typedef struct _PyCompiler PyCompiler; -typedef struct _PyPackageManager PyPackageManager; -typedef struct _PyPackage PyPackage; -typedef struct _PyPackageSearchResult PyPackageSearchResult; -typedef struct _PyPackageInstallResult PyPackageInstallResult; -typedef struct _PyFileMonitor PyFileMonitor; -typedef struct _PyIOStream PyIOStream; -typedef struct _PyCancellable PyCancellable; - -#define FRIDA_TYPE_PYTHON_AUTHENTICATION_SERVICE (frida_python_authentication_service_get_type ()) -G_DECLARE_FINAL_TYPE (FridaPythonAuthenticationService, frida_python_authentication_service, FRIDA, PYTHON_AUTHENTICATION_SERVICE, GObject) - -typedef void (* PyGObjectInitFromHandleFunc) (PyObject * self, gpointer handle); - -struct _PyGObject -{ - PyObject_HEAD - - gpointer handle; - const PyGObjectType * type; - - GSList * signal_closures; -}; - -struct _PyGObjectType -{ - PyGObjectType * parent; - PyObject * object; - PyGObjectInitFromHandleFunc init_from_handle; - GDestroyNotify destroy; -}; - -struct _PyGObjectSignalClosure -{ - GClosure parent; - guint signal_id; - guint max_arg_count; -}; - -struct _PyDeviceManager -{ - PyGObject parent; -}; - -struct _PyDevice -{ - PyGObject parent; - PyObject * id; - PyObject * name; - PyObject * icon; - PyObject * type; - PyObject * bus; -}; - -struct _PyApplication -{ - PyGObject parent; - PyObject * identifier; - PyObject * name; - guint pid; - PyObject * parameters; -}; - -struct _PyProcess -{ - PyGObject parent; - guint pid; - PyObject * name; - PyObject * parameters; -}; - -struct _PySpawn -{ - PyGObject parent; - guint pid; - PyObject * identifier; -}; - -struct _PyChild -{ - PyGObject parent; - guint pid; - guint parent_pid; - PyObject * origin; - PyObject * identifier; - PyObject * path; - PyObject * argv; - PyObject * envp; -}; - -struct _PyCrash -{ - PyGObject parent; - guint pid; - PyObject * process_name; - PyObject * summary; - PyObject * report; - PyObject * parameters; -}; - -struct _PyBus -{ - PyGObject parent; -}; - -struct _PyService -{ - PyGObject parent; -}; - -struct _PySession -{ - PyGObject parent; - guint pid; -}; - -struct _PyScript -{ - PyGObject parent; -}; - -struct _PyRelay -{ - PyGObject parent; - PyObject * address; - PyObject * username; - PyObject * password; - PyObject * kind; -}; - -struct _PyPortalMembership -{ - PyGObject parent; -}; - -struct _PyPortalService -{ - PyGObject parent; - PyObject * device; -}; - -struct _PyEndpointParameters -{ - PyGObject parent; -}; - -struct _FridaPythonAuthenticationService -{ - GObject parent; - PyObject * callback; - GThreadPool * pool; -}; - -struct _PyCompiler -{ - PyGObject parent; -}; - -struct _PyPackageManager -{ - PyGObject parent; -}; - -struct _PyPackage -{ - PyGObject parent; - PyObject * name; - PyObject * version; - PyObject * description; - PyObject * url; -}; - -struct _PyPackageSearchResult -{ - PyGObject parent; - PyObject * packages; - guint total; -}; - -struct _PyPackageInstallResult -{ - PyGObject parent; - PyObject * packages; -}; - -struct _PyFileMonitor -{ - PyGObject parent; -}; - -struct _PyIOStream -{ - PyGObject parent; - GInputStream * input; - GOutputStream * output; -}; - -struct _PyCancellable -{ - PyGObject parent; -}; - -static PyObject * PyGObject_new_take_handle (gpointer handle, const PyGObjectType * type); -static PyObject * PyGObject_try_get_from_handle (gpointer handle); -static int PyGObject_init (PyGObject * self); -static void PyGObject_dealloc (PyGObject * self); -static void PyGObject_take_handle (PyGObject * self, gpointer handle, const PyGObjectType * type); -static gpointer PyGObject_steal_handle (PyGObject * self); -static PyObject * PyGObject_on (PyGObject * self, PyObject * args); -static PyObject * PyGObject_off (PyGObject * self, PyObject * args); -static gint PyGObject_compare_signal_closure_callback (PyGObjectSignalClosure * closure, PyObject * callback); -static gboolean PyGObject_parse_signal_method_args (PyObject * args, GType instance_type, guint * signal_id, PyObject ** callback); -static const gchar * PyGObject_class_name_from_c (const gchar * cname); -static GClosure * PyGObject_make_closure_for_signal (guint signal_id, PyObject * callback, guint max_arg_count); -static void PyGObjectSignalClosure_finalize (PyObject * callback); -static void PyGObjectSignalClosure_marshal (GClosure * closure, GValue * return_gvalue, guint n_param_values, const GValue * param_values, - gpointer invocation_hint, gpointer marshal_data); -static PyObject * PyGObjectSignalClosure_marshal_params (const GValue * params, guint params_length); -static PyObject * PyGObject_marshal_value (const GValue * value); -static PyObject * PyGObject_marshal_string (const gchar * str); -static gboolean PyGObject_unmarshal_string (PyObject * value, gchar ** str); -static PyObject * PyGObject_marshal_datetime (const gchar * iso8601_text); -static PyObject * PyGObject_marshal_strv (gchar * const * strv, gint length); -static gboolean PyGObject_unmarshal_strv (PyObject * value, gchar *** strv, gint * length); -static PyObject * PyGObject_marshal_envp (gchar * const * envp, gint length); -static gboolean PyGObject_unmarshal_envp (PyObject * value, gchar *** envp, gint * length); -static PyObject * PyGObject_marshal_enum (gint value, GType type); -static gboolean PyGObject_unmarshal_enum (const gchar * str, GType type, gpointer value); -static PyObject * PyGObject_marshal_bytes (GBytes * bytes); -static PyObject * PyGObject_marshal_bytes_non_nullable (GBytes * bytes); -static PyObject * PyGObject_marshal_variant (GVariant * variant); -static PyObject * PyGObject_marshal_variant_byte_array (GVariant * variant); -static PyObject * PyGObject_marshal_variant_dict (GVariant * variant); -static PyObject * PyGObject_marshal_variant_array (GVariant * variant); -static PyObject * PyGObject_marshal_variant_tuple (GVariant * variant); -static gboolean PyGObject_unmarshal_variant (PyObject * value, GVariant ** variant); -static gboolean PyGObject_unmarshal_variant_from_mapping (PyObject * mapping, GVariant ** variant); -static gboolean PyGObject_unmarshal_variant_from_sequence (PyObject * sequence, GVariant ** variant); -static PyObject * PyGObject_marshal_parameters_dict (GHashTable * dict); -static PyObject * PyGObject_marshal_socket_address (GSocketAddress * address); -static gboolean PyGObject_unmarshal_certificate (const gchar * str, GTlsCertificate ** certificate); -static PyObject * PyGObject_marshal_object (gpointer handle, GType type); - -static int PyDeviceManager_init (PyDeviceManager * self, PyObject * args, PyObject * kwds); -static void PyDeviceManager_dealloc (PyDeviceManager * self); -static PyObject * PyDeviceManager_close (PyDeviceManager * self); -static PyObject * PyDeviceManager_get_device_matching (PyDeviceManager * self, PyObject * args); -static gboolean PyDeviceManager_is_matching_device (FridaDevice * device, PyObject * predicate); -static PyObject * PyDeviceManager_enumerate_devices (PyDeviceManager * self); -static PyObject * PyDeviceManager_add_remote_device (PyDeviceManager * self, PyObject * args, PyObject * kw); -static PyObject * PyDeviceManager_remove_remote_device (PyDeviceManager * self, PyObject * args, PyObject * kw); -static FridaRemoteDeviceOptions * PyDeviceManager_parse_remote_device_options (const gchar * certificate_value, const gchar * origin, - const gchar * token, gint keepalive_interval); - -static PyObject * PyDevice_new_take_handle (FridaDevice * handle); -static int PyDevice_init (PyDevice * self, PyObject * args, PyObject * kw); -static void PyDevice_init_from_handle (PyDevice * self, FridaDevice * handle); -static void PyDevice_dealloc (PyDevice * self); -static PyObject * PyDevice_repr (PyDevice * self); -static PyObject * PyDevice_is_lost (PyDevice * self); -static PyObject * PyDevice_override_option (PyDevice * self, PyObject * args, PyObject * kw); -static PyObject * PyDevice_query_system_parameters (PyDevice * self); -static PyObject * PyDevice_get_frontmost_application (PyDevice * self, PyObject * args, PyObject * kw); -static PyObject * PyDevice_enumerate_applications (PyDevice * self, PyObject * args, PyObject * kw); -static FridaApplicationQueryOptions * PyDevice_parse_application_query_options (PyObject * identifiers_value, const gchar * scope_value); -static PyObject * PyDevice_enumerate_processes (PyDevice * self, PyObject * args, PyObject * kw); -static FridaProcessQueryOptions * PyDevice_parse_process_query_options (PyObject * pids_value, const gchar * scope_value); -static PyObject * PyDevice_enable_spawn_gating (PyDevice * self); -static PyObject * PyDevice_disable_spawn_gating (PyDevice * self); -static PyObject * PyDevice_enumerate_pending_spawn (PyDevice * self); -static PyObject * PyDevice_enumerate_pending_children (PyDevice * self); -static PyObject * PyDevice_spawn (PyDevice * self, PyObject * args, PyObject * kw); -static PyObject * PyDevice_input (PyDevice * self, PyObject * args); -static PyObject * PyDevice_resume (PyDevice * self, PyObject * args); -static PyObject * PyDevice_kill (PyDevice * self, PyObject * args); -static PyObject * PyDevice_attach (PyDevice * self, PyObject * args, PyObject * kw); -static FridaSessionOptions * PyDevice_parse_session_options (const gchar * realm_value, guint persist_timeout, const gchar * exceptor_value, gint unwind_broker, gint exit_monitor, gint thread_suspend_monitor, PyObject * linker_notifier_offsets_value); -static PyObject * PyDevice_inject_library_file (PyDevice * self, PyObject * args); -static PyObject * PyDevice_inject_library_blob (PyDevice * self, PyObject * args); -static PyObject * PyDevice_open_channel (PyDevice * self, PyObject * args); -static PyObject * PyDevice_open_service (PyDevice * self, PyObject * args); -static PyObject * PyDevice_unpair (PyDevice * self); - -static PyObject * PyApplication_new_take_handle (FridaApplication * handle); -static int PyApplication_init (PyApplication * self, PyObject * args, PyObject * kw); -static void PyApplication_init_from_handle (PyApplication * self, FridaApplication * handle); -static void PyApplication_dealloc (PyApplication * self); -static PyObject * PyApplication_repr (PyApplication * self); -static PyObject * PyApplication_marshal_parameters_dict (GHashTable * dict); - -static PyObject * PyProcess_new_take_handle (FridaProcess * handle); -static int PyProcess_init (PyProcess * self, PyObject * args, PyObject * kw); -static void PyProcess_init_from_handle (PyProcess * self, FridaProcess * handle); -static void PyProcess_dealloc (PyProcess * self); -static PyObject * PyProcess_repr (PyProcess * self); -static PyObject * PyProcess_marshal_parameters_dict (GHashTable * dict); - -static PyObject * PySpawn_new_take_handle (FridaSpawn * handle); -static int PySpawn_init (PySpawn * self, PyObject * args, PyObject * kw); -static void PySpawn_init_from_handle (PySpawn * self, FridaSpawn * handle); -static void PySpawn_dealloc (PySpawn * self); -static PyObject * PySpawn_repr (PySpawn * self); - -static PyObject * PyChild_new_take_handle (FridaChild * handle); -static int PyChild_init (PyChild * self, PyObject * args, PyObject * kw); -static void PyChild_init_from_handle (PyChild * self, FridaChild * handle); -static void PyChild_dealloc (PyChild * self); -static PyObject * PyChild_repr (PyChild * self); - -static int PyCrash_init (PyCrash * self, PyObject * args, PyObject * kw); -static void PyCrash_init_from_handle (PyCrash * self, FridaCrash * handle); -static void PyCrash_dealloc (PyCrash * self); -static PyObject * PyCrash_repr (PyCrash * self); - -static PyObject * PyBus_new_take_handle (FridaBus * handle); -static PyObject * PyBus_attach (PySession * self); -static PyObject * PyBus_post (PyScript * self, PyObject * args, PyObject * kw); - -static PyObject * PyService_new_take_handle (FridaService * handle); -static PyObject * PyService_activate (PyService * self); -static PyObject * PyService_cancel (PyService * self); -static PyObject * PyService_request (PyService * self, PyObject * args); - -static PyObject * PySession_new_take_handle (FridaSession * handle); -static int PySession_init (PySession * self, PyObject * args, PyObject * kw); -static void PySession_init_from_handle (PySession * self, FridaSession * handle); -static PyObject * PySession_repr (PySession * self); -static PyObject * PySession_is_detached (PySession * self); -static PyObject * PySession_detach (PySession * self); -static PyObject * PySession_resume (PySession * self); -static PyObject * PySession_enable_child_gating (PySession * self); -static PyObject * PySession_disable_child_gating (PySession * self); -static PyObject * PySession_create_script (PySession * self, PyObject * args, PyObject * kw); -static PyObject * PySession_create_script_from_bytes (PySession * self, PyObject * args, PyObject * kw); -static PyObject * PySession_compile_script (PySession * self, PyObject * args, PyObject * kw); -static PyObject * PySession_snapshot_script (PySession * self, PyObject * args, PyObject * kw); -static FridaScriptOptions * PySession_parse_script_options (const gchar * name, gconstpointer snapshot_data, gsize snapshot_size, - const gchar * runtime_value); -static PyObject * PySession_snapshot_script (PySession * self, PyObject * args, PyObject * kw); -static FridaSnapshotOptions * PySession_parse_snapshot_options (const gchar * warmup_script, const gchar * runtime_value); -static PyObject * PySession_setup_peer_connection (PySession * self, PyObject * args, PyObject * kw); -static FridaPeerOptions * PySession_parse_peer_options (const gchar * stun_server, PyObject * relays); -static PyObject * PySession_join_portal (PySession * self, PyObject * args, PyObject * kw); -static FridaPortalOptions * PySession_parse_portal_options (const gchar * certificate_value, const gchar * token, PyObject * acl_value); - -static PyObject * PyScript_new_take_handle (FridaScript * handle); -static PyObject * PyScript_is_destroyed (PyScript * self); -static PyObject * PyScript_load (PyScript * self); -static PyObject * PyScript_interrupt (PyScript * self); -static PyObject * PyScript_unload (PyScript * self); -static PyObject * PyScript_terminate (PyScript * self); -static PyObject * PyScript_eternalize (PyScript * self); -static PyObject * PyScript_post (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyScript_enable_debugger (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyScript_disable_debugger (PyScript * self); - -static int PyRelay_init (PyRelay * self, PyObject * args, PyObject * kw); -static void PyRelay_init_from_handle (PyRelay * self, FridaRelay * handle); -static void PyRelay_dealloc (PyRelay * self); -static PyObject * PyRelay_repr (PyRelay * self); - -static PyObject * PyPortalMembership_new_take_handle (FridaPortalMembership * handle); -static PyObject * PyPortalMembership_terminate (PyPortalMembership * self); - -static int PyPortalService_init (PyPortalService * self, PyObject * args, PyObject * kw); -static void PyPortalService_init_from_handle (PyPortalService * self, FridaPortalService * handle); -static void PyPortalService_dealloc (PyPortalService * self); -static PyObject * PyPortalService_start (PyPortalService * self); -static PyObject * PyPortalService_stop (PyPortalService * self); -static PyObject * PyPortalService_kick (PyScript * self, PyObject * args); -static PyObject * PyPortalService_post (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyPortalService_narrowcast (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyPortalService_broadcast (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyPortalService_enumerate_tags (PyScript * self, PyObject * args); -static PyObject * PyPortalService_tag (PyScript * self, PyObject * args, PyObject * kw); -static PyObject * PyPortalService_untag (PyScript * self, PyObject * args, PyObject * kw); - -static int PyEndpointParameters_init (PyEndpointParameters * self, PyObject * args, PyObject * kw); - -static FridaPythonAuthenticationService * frida_python_authentication_service_new (PyObject * callback); -static void frida_python_authentication_service_iface_init (gpointer g_iface, gpointer iface_data); -static void frida_python_authentication_service_dispose (GObject * object); -static void frida_python_authentication_service_authenticate (FridaAuthenticationService * service, const gchar * token, - GCancellable * cancellable, GAsyncReadyCallback callback, gpointer user_data); -static gchar * frida_python_authentication_service_authenticate_finish (FridaAuthenticationService * service, GAsyncResult * result, - GError ** error); -static void frida_python_authentication_service_do_authenticate (GTask * task, FridaPythonAuthenticationService * self); - -static int PyCompiler_init (PyCompiler * self, PyObject * args, PyObject * kw); -static void PyCompiler_dealloc (PyCompiler * self); -static PyObject * PyCompiler_build (PyCompiler * self, PyObject * args, PyObject * kw); -static PyObject * PyCompiler_watch (PyCompiler * self, PyObject * args, PyObject * kw); -static gboolean PyCompiler_set_options (FridaCompilerOptions * options, const gchar * project_root_value, const gchar * output_format_value, - const gchar * bundle_format_value, const gchar * type_check_value, const gchar * source_maps_value, const gchar * compression_value, - const gchar * platform_value, PyObject * externals_value); - -static int PyPackageManager_init (PyPackageManager * self, PyObject * args, PyObject * kw); -static void PyPackageManager_dealloc (PyPackageManager * self); -static PyObject * PyPackageManager_repr (PyPackageManager * self); -static PyObject * PyPackageManager_get_registry (PyPackageManager * self, void * closure); -static int PyPackageManager_set_registry (PyPackageManager * self, PyObject * val, void * closure); -static PyObject * PyPackageManager_search (PyPackageManager * self, PyObject * args, PyObject * kw); -static PyObject * PyPackageManager_install (PyPackageManager * self, PyObject * args, PyObject * kw); -static FridaPackageInstallOptions * PyPackageManager_parse_install_options (const gchar * project_root, const char * role_value, - PyObject * specs_value, PyObject * omits_value); - -static PyObject * PyPackage_new_take_handle (FridaPackage * handle); -static int PyPackage_init (PyPackage * self, PyObject * args, PyObject * kw); -static void PyPackage_init_from_handle (PyPackage * self, FridaPackage * handle); -static void PyPackage_dealloc (PyPackage * self); -static PyObject * PyPackage_repr (PyPackage * self); - -static PyObject * PyPackageSearchResult_new_take_handle (FridaPackageSearchResult * handle); -static int PyPackageSearchResult_init (PyPackageSearchResult * self, PyObject * args, PyObject * kw); -static void PyPackageSearchResult_init_from_handle (PyPackageSearchResult * self, FridaPackageSearchResult * handle); -static void PyPackageSearchResult_dealloc (PyPackageSearchResult * self); -static PyObject * PyPackageSearchResult_repr (PyPackageSearchResult * self); - -static PyObject * PyPackageInstallResult_new_take_handle (FridaPackageInstallResult * handle); -static int PyPackageInstallResult_init (PyPackageInstallResult * self, PyObject * args, PyObject * kw); -static void PyPackageInstallResult_init_from_handle (PyPackageInstallResult * self, FridaPackageInstallResult * handle); -static void PyPackageInstallResult_dealloc (PyPackageInstallResult * self); -static PyObject * PyPackageInstallResult_repr (PyPackageInstallResult * self); - -static int PyFileMonitor_init (PyFileMonitor * self, PyObject * args, PyObject * kw); -static void PyFileMonitor_dealloc (PyFileMonitor * self); -static PyObject * PyFileMonitor_enable (PyFileMonitor * self); -static PyObject * PyFileMonitor_disable (PyFileMonitor * self); - -static PyObject * PyIOStream_new_take_handle (GIOStream * handle); -static int PyIOStream_init (PyIOStream * self, PyObject * args, PyObject * kw); -static void PyIOStream_init_from_handle (PyIOStream * self, GIOStream * handle); -static PyObject * PyIOStream_repr (PyIOStream * self); -static PyObject * PyIOStream_is_closed (PyIOStream * self); -static PyObject * PyIOStream_close (PyIOStream * self); -static PyObject * PyIOStream_read (PyIOStream * self, PyObject * args); -static PyObject * PyIOStream_read_all (PyIOStream * self, PyObject * args); -static PyObject * PyIOStream_write (PyIOStream * self, PyObject * args); -static PyObject * PyIOStream_write_all (PyIOStream * self, PyObject * args); - -static int PyCancellable_init (PyCancellable * self, PyObject * args, PyObject * kw); -static PyObject * PyCancellable_repr (PyCancellable * self); -static PyObject * PyCancellable_is_cancelled (PyCancellable * self); -static PyObject * PyCancellable_raise_if_cancelled (PyCancellable * self); -static PyObject * PyCancellable_get_fd (PyCancellable * self); -static PyObject * PyCancellable_release_fd (PyCancellable * self); -static PyObject * PyCancellable_get_current (PyCancellable * self); -static PyObject * PyCancellable_push_current (PyCancellable * self); -static PyObject * PyCancellable_pop_current (PyCancellable * self); -static PyObject * PyCancellable_connect (PyCancellable * self, PyObject * args); -static PyObject * PyCancellable_disconnect (PyCancellable * self, PyObject * args); -static void PyCancellable_on_cancelled (GCancellable * cancellable, PyObject * callback); -static void PyCancellable_destroy_callback (PyObject * callback); -static PyObject * PyCancellable_cancel (PyCancellable * self); - -static PyObject * PyFrida_raise (GError * error); -static gchar * PyFrida_repr (PyObject * obj); -static guint PyFrida_get_max_argument_count (PyObject * callable); - -static PyMethodDef PyGObject_methods[] = -{ - { "on", (PyCFunction) PyGObject_on, METH_VARARGS, "Add a signal handler." }, - { "off", (PyCFunction) PyGObject_off, METH_VARARGS, "Remove a signal handler." }, - { NULL } -}; - -static PyMethodDef PyDeviceManager_methods[] = -{ - { "close", (PyCFunction) PyDeviceManager_close, METH_NOARGS, "Close the device manager." }, - { "get_device_matching", (PyCFunction) PyDeviceManager_get_device_matching, METH_VARARGS, "Get device matching predicate." }, - { "enumerate_devices", (PyCFunction) PyDeviceManager_enumerate_devices, METH_NOARGS, "Enumerate devices." }, - { "add_remote_device", (PyCFunction) PyDeviceManager_add_remote_device, METH_VARARGS | METH_KEYWORDS, "Add a remote device." }, - { "remove_remote_device", (PyCFunction) PyDeviceManager_remove_remote_device, METH_VARARGS | METH_KEYWORDS, "Remove a remote device." }, - { NULL } -}; - -static PyMethodDef PyDevice_methods[] = -{ - { "is_lost", (PyCFunction) PyDevice_is_lost, METH_NOARGS, "Query whether the device has been lost." }, - { "override_option", (PyCFunction) PyDevice_override_option, METH_VARARGS | METH_KEYWORDS, "Override a backend-specific option." }, - { "query_system_parameters", (PyCFunction) PyDevice_query_system_parameters, METH_NOARGS, "Returns a dictionary of information about the host system." }, - { "get_frontmost_application", (PyCFunction) PyDevice_get_frontmost_application, METH_VARARGS | METH_KEYWORDS, "Get details about the frontmost application." }, - { "enumerate_applications", (PyCFunction) PyDevice_enumerate_applications, METH_VARARGS | METH_KEYWORDS, "Enumerate applications." }, - { "enumerate_processes", (PyCFunction) PyDevice_enumerate_processes, METH_VARARGS | METH_KEYWORDS, "Enumerate processes." }, - { "enable_spawn_gating", (PyCFunction) PyDevice_enable_spawn_gating, METH_NOARGS, "Enable spawn gating." }, - { "disable_spawn_gating", (PyCFunction) PyDevice_disable_spawn_gating, METH_NOARGS, "Disable spawn gating." }, - { "enumerate_pending_spawn", (PyCFunction) PyDevice_enumerate_pending_spawn, METH_NOARGS, "Enumerate pending spawn." }, - { "enumerate_pending_children", (PyCFunction) PyDevice_enumerate_pending_children, METH_NOARGS, "Enumerate pending children." }, - { "spawn", (PyCFunction) PyDevice_spawn, METH_VARARGS | METH_KEYWORDS, "Spawn a process into an attachable state." }, - { "input", (PyCFunction) PyDevice_input, METH_VARARGS, "Input data on stdin of a spawned process." }, - { "resume", (PyCFunction) PyDevice_resume, METH_VARARGS, "Resume a process from the attachable state." }, - { "kill", (PyCFunction) PyDevice_kill, METH_VARARGS, "Kill a PID." }, - { "attach", (PyCFunction) PyDevice_attach, METH_VARARGS | METH_KEYWORDS, "Attach to a PID." }, - { "inject_library_file", (PyCFunction) PyDevice_inject_library_file, METH_VARARGS, "Inject a library file to a PID." }, - { "inject_library_blob", (PyCFunction) PyDevice_inject_library_blob, METH_VARARGS, "Inject a library blob to a PID." }, - { "open_channel", (PyCFunction) PyDevice_open_channel, METH_VARARGS, "Open a device-specific communication channel." }, - { "open_service", (PyCFunction) PyDevice_open_service, METH_VARARGS, "Open a device-specific service." }, - { "unpair", (PyCFunction) PyDevice_unpair, METH_NOARGS, "Unpair device." }, - { NULL } -}; - -static PyMemberDef PyDevice_members[] = -{ - { "id", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, id), READONLY, "Device ID." }, - { "name", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, name), READONLY, "Human-readable device name." }, - { "icon", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, icon), READONLY, "Icon." }, - { "type", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, type), READONLY, "Device type. One of: local, remote, usb." }, - { "bus", T_OBJECT_EX, G_STRUCT_OFFSET (PyDevice, bus), READONLY, "Message bus." }, - { NULL } -}; - -static PyMemberDef PyApplication_members[] = -{ - { "identifier", T_OBJECT_EX, G_STRUCT_OFFSET (PyApplication, identifier), READONLY, "Application identifier." }, - { "name", T_OBJECT_EX, G_STRUCT_OFFSET (PyApplication, name), READONLY, "Human-readable application name." }, - { "pid", T_UINT, G_STRUCT_OFFSET (PyApplication, pid), READONLY, "Process ID, or 0 if not running." }, - { "parameters", T_OBJECT_EX, G_STRUCT_OFFSET (PyApplication, parameters), READONLY, "Parameters." }, - { NULL } -}; - -static PyMemberDef PyProcess_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PyProcess, pid), READONLY, "Process ID." }, - { "name", T_OBJECT_EX, G_STRUCT_OFFSET (PyProcess, name), READONLY, "Human-readable process name." }, - { "parameters", T_OBJECT_EX, G_STRUCT_OFFSET (PyProcess, parameters), READONLY, "Parameters." }, - { NULL } -}; - -static PyMemberDef PySpawn_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PySpawn, pid), READONLY, "Process ID." }, - { "identifier", T_OBJECT_EX, G_STRUCT_OFFSET (PySpawn, identifier), READONLY, "Application identifier." }, - { NULL } -}; - -static PyMemberDef PyChild_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PyChild, pid), READONLY, "Process ID." }, - { "parent_pid", T_UINT, G_STRUCT_OFFSET (PyChild, parent_pid), READONLY, "Parent Process ID." }, - { "origin", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, origin), READONLY, "Origin." }, - { "identifier", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, identifier), READONLY, "Application identifier." }, - { "path", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, path), READONLY, "Path of executable." }, - { "argv", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, argv), READONLY, "Argument vector." }, - { "envp", T_OBJECT_EX, G_STRUCT_OFFSET (PyChild, envp), READONLY, "Environment vector." }, - { NULL } -}; - -static PyMemberDef PyCrash_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PyCrash, pid), READONLY, "Process ID." }, - { "process_name", T_OBJECT_EX, G_STRUCT_OFFSET (PyCrash, process_name), READONLY, "Process name." }, - { "summary", T_OBJECT_EX, G_STRUCT_OFFSET (PyCrash, summary), READONLY, "Human-readable crash summary." }, - { "report", T_OBJECT_EX, G_STRUCT_OFFSET (PyCrash, report), READONLY, "Human-readable crash report." }, - { "parameters", T_OBJECT_EX, G_STRUCT_OFFSET (PyCrash, parameters), READONLY, "Parameters." }, - { NULL } -}; - -static PyMethodDef PyBus_methods[] = -{ - { "attach", (PyCFunction) PyBus_attach, METH_NOARGS, "Attach to the bus." }, - { "post", (PyCFunction) PyBus_post, METH_VARARGS | METH_KEYWORDS, "Post a JSON-encoded message to the bus." }, - { NULL } -}; - -static PyMethodDef PyService_methods[] = -{ - { "activate", (PyCFunction) PyService_activate, METH_NOARGS, "Activate the service." }, - { "cancel", (PyCFunction) PyService_cancel, METH_NOARGS, "Cancel the service." }, - { "request", (PyCFunction) PyService_request, METH_VARARGS, "Perform a request." }, - { NULL } -}; - -static PyMethodDef PySession_methods[] = -{ - { "is_detached", (PyCFunction) PySession_is_detached, METH_NOARGS, "Query whether the session is detached." }, - { "detach", (PyCFunction) PySession_detach, METH_NOARGS, "Detach session from the process." }, - { "resume", (PyCFunction) PySession_resume, METH_NOARGS, "Resume session after network error." }, - { "enable_child_gating", (PyCFunction) PySession_enable_child_gating, METH_NOARGS, "Enable child gating." }, - { "disable_child_gating", (PyCFunction) PySession_disable_child_gating, METH_NOARGS, "Disable child gating." }, - { "create_script", (PyCFunction) PySession_create_script, METH_VARARGS | METH_KEYWORDS, "Create a new script." }, - { "create_script_from_bytes", (PyCFunction) PySession_create_script_from_bytes, METH_VARARGS | METH_KEYWORDS, "Create a new script from bytecode." }, - { "compile_script", (PyCFunction) PySession_compile_script, METH_VARARGS | METH_KEYWORDS, "Compile script source code to bytecode." }, - { "snapshot_script", (PyCFunction) PySession_snapshot_script, METH_VARARGS | METH_KEYWORDS, "Evaluate script and snapshot the resulting VM state." }, - { "setup_peer_connection", (PyCFunction) PySession_setup_peer_connection, METH_VARARGS | METH_KEYWORDS, "Set up a peer connection with the target process." }, - { "join_portal", (PyCFunction) PySession_join_portal, METH_VARARGS | METH_KEYWORDS, "Join a portal." }, - { NULL } -}; - -static PyMemberDef PySession_members[] = -{ - { "pid", T_UINT, G_STRUCT_OFFSET (PySession, pid), READONLY, "Process ID." }, - { NULL } -}; - -static PyMethodDef PyScript_methods[] = -{ - { "is_destroyed", (PyCFunction) PyScript_is_destroyed, METH_NOARGS, "Query whether the script has been destroyed." }, - { "load", (PyCFunction) PyScript_load, METH_NOARGS, "Load the script." }, - { "interrupt", (PyCFunction) PyScript_interrupt, METH_NOARGS, "Interrupt any JavaScript currently executing in the script." }, - { "unload", (PyCFunction) PyScript_unload, METH_NOARGS, "Unload the script." }, - { "terminate", (PyCFunction) PyScript_terminate, METH_NOARGS, "Interrupt execution and unload the script." }, - { "eternalize", (PyCFunction) PyScript_eternalize, METH_NOARGS, "Eternalize the script." }, - { "post", (PyCFunction) PyScript_post, METH_VARARGS | METH_KEYWORDS, "Post a JSON-encoded message to the script." }, - { "enable_debugger", (PyCFunction) PyScript_enable_debugger, METH_VARARGS | METH_KEYWORDS, "Enable the Node.js compatible script debugger." }, - { "disable_debugger", (PyCFunction) PyScript_disable_debugger, METH_NOARGS, "Disable the Node.js compatible script debugger." }, - { NULL } -}; - -static PyMemberDef PyRelay_members[] = -{ - { "address", T_OBJECT_EX, G_STRUCT_OFFSET (PyRelay, address), READONLY, "Network address or address:port of the TURN server." }, - { "username", T_OBJECT_EX, G_STRUCT_OFFSET (PyRelay, username), READONLY, "The TURN username to use for the allocate request." }, - { "password", T_OBJECT_EX, G_STRUCT_OFFSET (PyRelay, password), READONLY, "The TURN password to use for the allocate request." }, - { "kind", T_OBJECT_EX, G_STRUCT_OFFSET (PyRelay, kind), READONLY, "Relay kind. One of: turn-udp, turn-tcp, turn-tls." }, - { NULL } -}; - -static PyMethodDef PyPortalMembership_methods[] = -{ - { "terminate", (PyCFunction) PyPortalMembership_terminate, METH_NOARGS, "Terminate the membership." }, - { NULL } -}; - -static PyMethodDef PyPortalService_methods[] = -{ - { "start", (PyCFunction) PyPortalService_start, METH_NOARGS, "Start listening for incoming connections." }, - { "stop", (PyCFunction) PyPortalService_stop, METH_NOARGS, "Stop listening for incoming connections, and kick any connected clients." }, - { "kick", (PyCFunction) PyPortalService_kick, METH_VARARGS, "Kick out a specific connection." }, - { "post", (PyCFunction) PyPortalService_post, METH_VARARGS | METH_KEYWORDS, "Post a message to a specific control channel." }, - { "narrowcast", (PyCFunction) PyPortalService_narrowcast, METH_VARARGS | METH_KEYWORDS, "Post a message to control channels with a specific tag." }, - { "broadcast", (PyCFunction) PyPortalService_broadcast, METH_VARARGS | METH_KEYWORDS, "Broadcast a message to all control channels." }, - { "enumerate_tags", (PyCFunction) PyPortalService_enumerate_tags, METH_VARARGS, "Enumerate tags of a specific connection." }, - { "tag", (PyCFunction) PyPortalService_tag, METH_VARARGS | METH_KEYWORDS, "Tag a specific control channel." }, - { "untag", (PyCFunction) PyPortalService_untag, METH_VARARGS | METH_KEYWORDS, "Untag a specific control channel." }, - { NULL } -}; - -static PyMemberDef PyPortalService_members[] = -{ - { "device", T_OBJECT_EX, G_STRUCT_OFFSET (PyPortalService, device), READONLY, "Device for in-process control." }, - { NULL } -}; - -static PyMethodDef PyCompiler_methods[] = -{ - { "build", (PyCFunction) PyCompiler_build, METH_VARARGS | METH_KEYWORDS, "Build an agent." }, - { "watch", (PyCFunction) PyCompiler_watch, METH_VARARGS | METH_KEYWORDS, "Continuously build an agent." }, - { NULL } -}; - -static PyGetSetDef PyPackageManager_getset[] = -{ - { "registry", (getter) PyPackageManager_get_registry, (setter) PyPackageManager_set_registry, "The registry to use.", NULL }, - { NULL } -}; - -static PyMethodDef PyPackageManager_methods[] = -{ - { "search", (PyCFunction) PyPackageManager_search, METH_VARARGS | METH_KEYWORDS, "Search for packages to install." }, - { "install", (PyCFunction) PyPackageManager_install, METH_VARARGS | METH_KEYWORDS, "Install one or more packages." }, - { NULL } -}; - -static PyMemberDef PyPackage_members[] = -{ - { "name", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackage, name), READONLY, "Package name." }, - { "version", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackage, version), READONLY, "Package version." }, - { "description", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackage, description), READONLY, "Package description." }, - { "url", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackage, url), READONLY, "Package URL." }, - { NULL } -}; - -static PyMemberDef PyPackageSearchResult_members[] = -{ - { "packages", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackageSearchResult, packages), READONLY, "Batch of matching packages." }, - { "total", T_UINT, G_STRUCT_OFFSET (PyPackageSearchResult, total), READONLY, "Total matching packages." }, - { NULL } -}; - -static PyMemberDef PyPackageInstallResult_members[] = -{ - { "packages", T_OBJECT_EX, G_STRUCT_OFFSET (PyPackageInstallResult, packages), READONLY, "The toplevel packages that are installed." }, - { NULL } -}; - -static PyMethodDef PyFileMonitor_methods[] = -{ - { "enable", (PyCFunction) PyFileMonitor_enable, METH_NOARGS, "Enable the file monitor." }, - { "disable", (PyCFunction) PyFileMonitor_disable, METH_NOARGS, "Disable the file monitor." }, - { NULL } -}; - -static PyMethodDef PyIOStream_methods[] = -{ - { "is_closed", (PyCFunction) PyIOStream_is_closed, METH_NOARGS, "Query whether the stream is closed." }, - { "close", (PyCFunction) PyIOStream_close, METH_NOARGS, "Close the stream." }, - { "read", (PyCFunction) PyIOStream_read, METH_VARARGS, "Read up to the specified number of bytes from the stream." }, - { "read_all", (PyCFunction) PyIOStream_read_all, METH_VARARGS, "Read exactly the specified number of bytes from the stream." }, - { "write", (PyCFunction) PyIOStream_write, METH_VARARGS, "Write as much as possible of the provided data to the stream." }, - { "write_all", (PyCFunction) PyIOStream_write_all, METH_VARARGS, "Write all of the provided data to the stream." }, - { NULL } -}; - -static PyMethodDef PyCancellable_methods[] = -{ - { "is_cancelled", (PyCFunction) PyCancellable_is_cancelled, METH_NOARGS, "Query whether cancellable has been cancelled." }, - { "raise_if_cancelled", (PyCFunction) PyCancellable_raise_if_cancelled, METH_NOARGS, "Raise an exception if cancelled." }, - { "get_fd", (PyCFunction) PyCancellable_get_fd, METH_NOARGS, "Get file descriptor for integrating with an event loop." }, - { "release_fd", (PyCFunction) PyCancellable_release_fd, METH_NOARGS, "Release a resource previously allocated by get_fd()." }, - { "get_current", (PyCFunction) PyCancellable_get_current, METH_CLASS | METH_NOARGS, "Get the top cancellable from the stack." }, - { "push_current", (PyCFunction) PyCancellable_push_current, METH_NOARGS, "Push cancellable onto the cancellable stack." }, - { "pop_current", (PyCFunction) PyCancellable_pop_current, METH_NOARGS, "Pop cancellable off the cancellable stack." }, - { "connect", (PyCFunction) PyCancellable_connect, METH_VARARGS, "Register notification callback." }, - { "disconnect", (PyCFunction) PyCancellable_disconnect, METH_VARARGS, "Unregister notification callback." }, - { "cancel", (PyCFunction) PyCancellable_cancel, METH_NOARGS, "Set cancellable to cancelled." }, - { NULL } -}; - -PYFRIDA_DEFINE_BASETYPE ("_frida.Object", GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida Object" }, - { Py_tp_init, PyGObject_init }, - { Py_tp_dealloc, PyGObject_dealloc }, - { Py_tp_methods, PyGObject_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.DeviceManager", DeviceManager, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Device Manager" }, - { Py_tp_init, PyDeviceManager_init }, - { Py_tp_dealloc, PyDeviceManager_dealloc }, - { Py_tp_methods, PyDeviceManager_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Device", Device, GObject, PyDevice_init_from_handle, frida_unref, - { Py_tp_doc, "Frida Device" }, - { Py_tp_init, PyDevice_init }, - { Py_tp_dealloc, PyDevice_dealloc }, - { Py_tp_repr, PyDevice_repr }, - { Py_tp_methods, PyDevice_methods }, - { Py_tp_members, PyDevice_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Application", Application, GObject, PyApplication_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Application" }, - { Py_tp_init, PyApplication_init }, - { Py_tp_dealloc, PyApplication_dealloc }, - { Py_tp_repr, PyApplication_repr }, - { Py_tp_members, PyApplication_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Process", Process, GObject, PyProcess_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Process" }, - { Py_tp_init, PyProcess_init }, - { Py_tp_dealloc, PyProcess_dealloc }, - { Py_tp_repr, PyProcess_repr }, - { Py_tp_members, PyProcess_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Spawn", Spawn, GObject, PySpawn_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Spawn" }, - { Py_tp_init, PySpawn_init }, - { Py_tp_dealloc, PySpawn_dealloc }, - { Py_tp_repr, PySpawn_repr }, - { Py_tp_members, PySpawn_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Child", Child, GObject, PyChild_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Child" }, - { Py_tp_init, PyChild_init }, - { Py_tp_dealloc, PyChild_dealloc }, - { Py_tp_repr, PyChild_repr }, - { Py_tp_members, PyChild_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Crash", Crash, GObject, PyCrash_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Crash Details" }, - { Py_tp_init, PyCrash_init }, - { Py_tp_dealloc, PyCrash_dealloc }, - { Py_tp_repr, PyCrash_repr }, - { Py_tp_members, PyCrash_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Bus", Bus, GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida Message Bus" }, - { Py_tp_methods, PyBus_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Service", Service, GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida Service" }, - { Py_tp_methods, PyService_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Session", Session, GObject, PySession_init_from_handle, frida_unref, - { Py_tp_doc, "Frida Session" }, - { Py_tp_init, PySession_init }, - { Py_tp_repr, PySession_repr }, - { Py_tp_methods, PySession_methods }, - { Py_tp_members, PySession_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Script", Script, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Script" }, - { Py_tp_methods, PyScript_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Relay", Relay, GObject, PyRelay_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Relay" }, - { Py_tp_init, PyRelay_init }, - { Py_tp_dealloc, PyRelay_dealloc }, - { Py_tp_repr, PyRelay_repr }, - { Py_tp_members, PyRelay_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PortalMembership", PortalMembership, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Portal Membership" }, - { Py_tp_methods, PyPortalMembership_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PortalService", PortalService, GObject, PyPortalService_init_from_handle, frida_unref, - { Py_tp_doc, "Frida Portal Service" }, - { Py_tp_init, PyPortalService_init }, - { Py_tp_dealloc, PyPortalService_dealloc }, - { Py_tp_methods, PyPortalService_methods }, - { Py_tp_members, PyPortalService_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.EndpointParameters", EndpointParameters, GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida EndpointParameters" }, - { Py_tp_init, PyEndpointParameters_init }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Compiler", Compiler, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Compiler" }, - { Py_tp_init, PyCompiler_init }, - { Py_tp_dealloc, PyCompiler_dealloc }, - { Py_tp_methods, PyCompiler_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PackageManager", PackageManager, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida Package Manager" }, - { Py_tp_init, PyPackageManager_init }, - { Py_tp_dealloc, PyPackageManager_dealloc }, - { Py_tp_repr, PyPackageManager_repr }, - { Py_tp_getset, PyPackageManager_getset }, - { Py_tp_methods, PyPackageManager_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Package", Package, GObject, PyPackage_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Package" }, - { Py_tp_init, PyPackage_init }, - { Py_tp_dealloc, PyPackage_dealloc }, - { Py_tp_repr, PyPackage_repr }, - { Py_tp_members, PyPackage_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PackageSearchResult", PackageSearchResult, GObject, PyPackageSearchResult_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Package Search Result" }, - { Py_tp_init, PyPackageSearchResult_init }, - { Py_tp_dealloc, PyPackageSearchResult_dealloc }, - { Py_tp_repr, PyPackageSearchResult_repr }, - { Py_tp_members, PyPackageSearchResult_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.PackageInstallResult", PackageInstallResult, GObject, PyPackageInstallResult_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida Package Install Result" }, - { Py_tp_init, PyPackageInstallResult_init }, - { Py_tp_dealloc, PyPackageInstallResult_dealloc }, - { Py_tp_repr, PyPackageInstallResult_repr }, - { Py_tp_members, PyPackageInstallResult_members }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.FileMonitor", FileMonitor, GObject, NULL, frida_unref, - { Py_tp_doc, "Frida File Monitor" }, - { Py_tp_init, PyFileMonitor_init }, - { Py_tp_dealloc, PyFileMonitor_dealloc }, - { Py_tp_methods, PyFileMonitor_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.IOStream", IOStream, GObject, PyIOStream_init_from_handle, g_object_unref, - { Py_tp_doc, "Frida IOStream" }, - { Py_tp_init, PyIOStream_init }, - { Py_tp_repr, PyIOStream_repr }, - { Py_tp_methods, PyIOStream_methods }, -); - -PYFRIDA_DEFINE_TYPE ("_frida.Cancellable", Cancellable, GObject, NULL, g_object_unref, - { Py_tp_doc, "Frida Cancellable" }, - { Py_tp_init, PyCancellable_init }, - { Py_tp_repr, PyCancellable_repr }, - { Py_tp_methods, PyCancellable_methods }, -); - - -static PyObject * -PyGObject_new_take_handle (gpointer handle, const PyGObjectType * pytype) -{ - PyObject * object; - - if (handle == NULL) - PyFrida_RETURN_NONE; - - object = PyGObject_try_get_from_handle (handle); - if (object == NULL) - { - object = PyObject_CallFunction (pytype->object, NULL); - PyGObject_take_handle (PY_GOBJECT (object), handle, pytype); - - if (pytype->init_from_handle != NULL) - pytype->init_from_handle (object, handle); - } - else - { - pytype->destroy (handle); - Py_IncRef (object); - } - - return object; -} - -static PyObject * -PyGObject_try_get_from_handle (gpointer handle) -{ - return g_object_get_data (handle, "pyobject"); -} - -static int -PyGObject_init (PyGObject * self) -{ - self->handle = NULL; - self->type = PYFRIDA_TYPE (GObject); - - self->signal_closures = NULL; - - return 0; -} - -static void -PyGObject_dealloc (PyGObject * self) -{ - gpointer handle; - - handle = PyGObject_steal_handle (self); - if (handle != NULL) - { - Py_BEGIN_ALLOW_THREADS - self->type->destroy (handle); - Py_END_ALLOW_THREADS - } - - ((freefunc) PyType_GetSlot (Py_TYPE (self), Py_tp_free)) (self); -} - -static void -PyGObject_take_handle (PyGObject * self, gpointer handle, const PyGObjectType * type) -{ - self->handle = handle; - self->type = type; - - if (handle != NULL) - g_object_set_data (G_OBJECT (handle), "pyobject", self); -} - -static gpointer -PyGObject_steal_handle (PyGObject * self) -{ - gpointer handle = self->handle; - GSList * entry; - - if (handle == NULL) - return NULL; - - for (entry = self->signal_closures; entry != NULL; entry = entry->next) - { - PyGObjectSignalClosure * closure = entry->data; - G_GNUC_UNUSED guint num_matches; - - num_matches = g_signal_handlers_disconnect_matched (handle, G_SIGNAL_MATCH_CLOSURE, closure->signal_id, 0, &closure->parent, NULL, NULL); - g_assert (num_matches == 1); - } - g_clear_pointer (&self->signal_closures, g_slist_free); - - g_object_set_data (G_OBJECT (handle), "pyobject", NULL); - - self->handle = NULL; - - return handle; -} - -static PyObject * -PyGObject_on (PyGObject * self, PyObject * args) -{ - GType instance_type; - guint signal_id; - PyObject * callback; - guint max_arg_count, allowed_arg_count_including_sender; - GSignalQuery query; - GClosure * closure; - - instance_type = G_OBJECT_TYPE (self->handle); - - if (!PyGObject_parse_signal_method_args (args, instance_type, &signal_id, &callback)) - return NULL; - - max_arg_count = PyFrida_get_max_argument_count (callback); - if (max_arg_count != G_MAXUINT) - { - g_signal_query (signal_id, &query); - - allowed_arg_count_including_sender = 1 + query.n_params; - - if (max_arg_count > allowed_arg_count_including_sender) - goto too_many_arguments; - } - - closure = PyGObject_make_closure_for_signal (signal_id, callback, max_arg_count); - g_signal_connect_closure_by_id (self->handle, signal_id, 0, closure, TRUE); - - self->signal_closures = g_slist_prepend (self->signal_closures, closure); - - PyFrida_RETURN_NONE; - -too_many_arguments: - { - return PyErr_Format (PyExc_TypeError, - "callback expects too many arguments, the '%s' signal only has %u but callback expects %u", - g_signal_name (signal_id), query.n_params, max_arg_count); - } -} - -static PyObject * -PyGObject_off (PyGObject * self, PyObject * args) -{ - guint signal_id; - PyObject * callback; - GSList * entry; - GClosure * closure; - G_GNUC_UNUSED guint num_matches; - - if (!PyGObject_parse_signal_method_args (args, G_OBJECT_TYPE (self->handle), &signal_id, &callback)) - return NULL; - - entry = g_slist_find_custom (self->signal_closures, callback, (GCompareFunc) PyGObject_compare_signal_closure_callback); - if (entry == NULL) - goto unknown_callback; - - closure = entry->data; - self->signal_closures = g_slist_delete_link (self->signal_closures, entry); - - num_matches = g_signal_handlers_disconnect_matched (self->handle, G_SIGNAL_MATCH_CLOSURE, signal_id, 0, closure, NULL, NULL); - g_assert (num_matches == 1); - - PyFrida_RETURN_NONE; - -unknown_callback: - { - PyErr_SetString (PyExc_ValueError, "unknown callback"); - return NULL; - } -} - -static gint -PyGObject_compare_signal_closure_callback (PyGObjectSignalClosure * closure, - PyObject * callback) -{ - int result; - - result = PyObject_RichCompareBool (closure->parent.data, callback, Py_EQ); - - return (result == 1) ? 0 : -1; -} - -static gboolean -PyGObject_parse_signal_method_args (PyObject * args, GType instance_type, guint * signal_id, PyObject ** callback) -{ - const gchar * signal_name; - - if (!PyArg_ParseTuple (args, "sO", &signal_name, callback)) - return FALSE; - - if (!PyCallable_Check (*callback)) - { - PyErr_SetString (PyExc_TypeError, "second argument must be callable"); - return FALSE; - } - - *signal_id = g_signal_lookup (signal_name, instance_type); - if (*signal_id == 0) - goto invalid_signal_name; - - return TRUE; - -invalid_signal_name: - { - GString * message; - guint * ids, n_ids, i; - - message = g_string_sized_new (128); - - g_string_append (message, PyGObject_class_name_from_c (g_type_name (instance_type))); - - ids = g_signal_list_ids (instance_type, &n_ids); - - if (n_ids > 0) - { - g_string_append_printf (message, " does not have a signal named '%s', it only has: ", signal_name); - - for (i = 0; i != n_ids; i++) - { - if (i != 0) - g_string_append (message, ", "); - g_string_append_c (message, '\''); - g_string_append (message, g_signal_name (ids[i])); - g_string_append_c (message, '\''); - } - } - else - { - g_string_append (message, " does not have any signals"); - } - - g_free (ids); - - PyErr_SetString (PyExc_ValueError, message->str); - - g_string_free (message, TRUE); - - return FALSE; - } -} - -static const gchar * -PyGObject_class_name_from_c (const gchar * cname) -{ - if (g_str_has_prefix (cname, "Frida")) - return cname + 5; - - return cname; -} - -static void -PyGObject_class_init (void) -{ - pygobject_type_spec_by_type = g_hash_table_new_full (NULL, NULL, NULL, NULL); -} - -static void -PyGObject_register_type (GType instance_type, PyGObjectType * python_type) -{ - g_hash_table_insert (pygobject_type_spec_by_type, GSIZE_TO_POINTER (instance_type), python_type); -} - -static GClosure * -PyGObject_make_closure_for_signal (guint signal_id, PyObject * callback, guint max_arg_count) -{ - GClosure * closure; - PyGObjectSignalClosure * pyclosure; - - closure = g_closure_new_simple (sizeof (PyGObjectSignalClosure), callback); - Py_IncRef (callback); - - g_closure_add_finalize_notifier (closure, callback, (GClosureNotify) PyGObjectSignalClosure_finalize); - g_closure_set_marshal (closure, PyGObjectSignalClosure_marshal); - - pyclosure = PY_GOBJECT_SIGNAL_CLOSURE (closure); - pyclosure->signal_id = signal_id; - pyclosure->max_arg_count = max_arg_count; - - return closure; -} - -static void -PyGObjectSignalClosure_finalize (PyObject * callback) -{ - PyGILState_STATE gstate; - - gstate = PyGILState_Ensure (); - Py_DecRef (callback); - PyGILState_Release (gstate); -} - -static void -PyGObjectSignalClosure_marshal (GClosure * closure, GValue * return_gvalue, guint n_param_values, const GValue * param_values, - gpointer invocation_hint, gpointer marshal_data) -{ - PyGObjectSignalClosure * self = PY_GOBJECT_SIGNAL_CLOSURE (closure); - PyObject * callback = closure->data; - PyGILState_STATE gstate; - PyObject * args, * result; - - (void) return_gvalue; - (void) invocation_hint; - (void) marshal_data; - - if (g_atomic_int_get (&toplevel_objects_alive) == 0) - return; - - gstate = PyGILState_Ensure (); - - if (PyGObject_try_get_from_handle (g_value_get_object (¶m_values[0])) == NULL) - goto beach; - - if (self->max_arg_count == n_param_values) - args = PyGObjectSignalClosure_marshal_params (param_values, n_param_values); - else - args = PyGObjectSignalClosure_marshal_params (param_values + 1, MIN (n_param_values - 1, self->max_arg_count)); - if (args == NULL) - { - PyErr_Print (); - goto beach; - } - - result = PyObject_CallObject (callback, args); - if (result != NULL) - Py_DecRef (result); - else - PyErr_Print (); - - Py_DecRef (args); - -beach: - PyGILState_Release (gstate); -} - -static PyObject * -PyGObjectSignalClosure_marshal_params (const GValue * params, guint params_length) -{ - PyObject * args; - guint i; - - args = PyTuple_New (params_length); - - for (i = 0; i != params_length; i++) - { - PyObject * arg; - - arg = PyGObject_marshal_value (¶ms[i]); - if (arg == NULL) - goto marshal_error; - - PyTuple_SetItem (args, i, arg); - } - - return args; - -marshal_error: - { - Py_DecRef (args); - return NULL; - } -} - -static PyObject * -PyGObject_marshal_value (const GValue * value) -{ - GType type; - - type = G_VALUE_TYPE (value); - - switch (type) - { - case G_TYPE_BOOLEAN: - return PyBool_FromLong (g_value_get_boolean (value)); - - case G_TYPE_INT: - return PyLong_FromLong (g_value_get_int (value)); - - case G_TYPE_UINT: - return PyLong_FromUnsignedLong (g_value_get_uint (value)); - - case G_TYPE_FLOAT: - return PyFloat_FromDouble (g_value_get_float (value)); - - case G_TYPE_DOUBLE: - return PyFloat_FromDouble (g_value_get_double (value)); - - case G_TYPE_STRING: - return PyGObject_marshal_string (g_value_get_string (value)); - - case G_TYPE_VARIANT: - return PyGObject_marshal_variant (g_value_get_variant (value)); - - default: - if (G_TYPE_IS_ENUM (type)) - return PyGObject_marshal_enum (g_value_get_enum (value), type); - - if (type == G_TYPE_BYTES) - return PyGObject_marshal_bytes (g_value_get_boxed (value)); - - if (G_TYPE_IS_OBJECT (type)) - return PyGObject_marshal_object (g_value_get_object (value), type); - - goto unsupported_type; - } - - g_assert_not_reached (); - -unsupported_type: - { - return PyErr_Format (PyExc_NotImplementedError, - "unsupported type: '%s'", - g_type_name (type)); - } -} - -static PyObject * -PyGObject_marshal_string (const gchar * str) -{ - if (str == NULL) - PyFrida_RETURN_NONE; - - return PyUnicode_FromString (str); -} - -static gboolean -PyGObject_unmarshal_string (PyObject * value, gchar ** str) -{ - PyObject * bytes; - - *str = NULL; - - bytes = PyUnicode_AsUTF8String (value); - if (bytes == NULL) - return FALSE; - - *str = g_strdup (PyBytes_AsString (bytes)); - - Py_DecRef (bytes); - - return *str != NULL; -} - -static PyObject * -PyGObject_marshal_datetime (const gchar * iso8601_text) -{ - PyObject * result; - GDateTime * raw_dt, * dt; - - raw_dt = g_date_time_new_from_iso8601 (iso8601_text, NULL); - if (raw_dt == NULL) - PyFrida_RETURN_NONE; - - dt = g_date_time_to_local (raw_dt); - - result = PyObject_CallFunction (datetime_constructor, "iiiiiii", - g_date_time_get_year (dt), - g_date_time_get_month (dt), - g_date_time_get_day_of_month (dt), - g_date_time_get_hour (dt), - g_date_time_get_minute (dt), - g_date_time_get_second (dt), - g_date_time_get_microsecond (dt)); - - g_date_time_unref (dt); - g_date_time_unref (raw_dt); - - return result; -} - -static PyObject * -PyGObject_marshal_strv (gchar * const * strv, gint length) -{ - PyObject * result; - gint i; - - if (strv == NULL) - PyFrida_RETURN_NONE; - - result = PyList_New (length); - - for (i = 0; i != length; i++) - { - PyList_SetItem (result, i, PyGObject_marshal_string (strv[i])); - } - - return result; -} - -static gboolean -PyGObject_unmarshal_strv (PyObject * value, gchar *** strv, gint * length) -{ - gint n, i; - gchar ** elements; - - if (!PyList_Check (value) && !PyTuple_Check (value)) - goto invalid_type; - - n = PySequence_Size (value); - elements = g_new0 (gchar *, n + 1); - - for (i = 0; i != n; i++) - { - PyObject * element; - - element = PySequence_GetItem (value, i); - if (PyUnicode_Check (element)) - { - Py_DecRef (element); - element = PyUnicode_AsUTF8String (element); - } - if (PyBytes_Check (element)) - elements[i] = g_strdup (PyBytes_AsString (element)); - Py_DecRef (element); - - if (elements[i] == NULL) - goto invalid_element; - } - - *strv = elements; - *length = n; - - return TRUE; - -invalid_type: - { - PyErr_SetString (PyExc_TypeError, "expected list or tuple of strings"); - return FALSE; - } -invalid_element: - { - g_strfreev (elements); - - PyErr_SetString (PyExc_TypeError, "expected list or tuple with string elements only"); - return FALSE; - } -} - -static PyObject * -PyGObject_marshal_envp (gchar * const * envp, gint length) -{ - PyObject * result; - gint i; - - if (envp == NULL) - PyFrida_RETURN_NONE; - - result = PyDict_New (); - - for (i = 0; i != length; i++) - { - gchar ** tokens; - - tokens = g_strsplit (envp[i], "=", 2); - - if (g_strv_length (tokens) == 2) - { - const gchar * name; - PyObject * value; - - name = tokens[0]; - value = PyGObject_marshal_string (tokens[1]); - - PyDict_SetItemString (result, name, value); - - Py_DecRef (value); - } - - g_strfreev (tokens); - } - - return result; -} - -static gboolean -PyGObject_unmarshal_envp (PyObject * dict, gchar *** envp, gint * length) -{ - gint n; - gchar ** elements; - gint i; - Py_ssize_t pos; - PyObject * name, * value; - gchar * raw_name = NULL; - gchar * raw_value = NULL; - - if (!PyDict_Check (dict)) - goto invalid_type; - - n = PyDict_Size (dict); - elements = g_new0 (gchar *, n + 1); - - i = 0; - pos = 0; - while (PyDict_Next (dict, &pos, &name, &value)) - { - if (!PyGObject_unmarshal_string (name, &raw_name)) - goto invalid_dict_key; - - if (!PyGObject_unmarshal_string (value, &raw_value)) - goto invalid_dict_value; - - elements[i] = g_strconcat (raw_name, "=", raw_value, NULL); - - g_free (g_steal_pointer (&raw_value)); - g_free (g_steal_pointer (&raw_name)); - - i++; - } - - *envp = elements; - *length = n; - - return TRUE; - -invalid_type: - { - PyErr_SetString (PyExc_TypeError, "expected dict"); - return FALSE; - } -invalid_dict_key: -invalid_dict_value: - { - g_free (raw_value); - g_free (raw_name); - g_strfreev (elements); - - PyErr_SetString (PyExc_TypeError, "expected dict with strings only"); - return FALSE; - } -} - -static PyObject * -PyGObject_marshal_enum (gint value, GType type) -{ - GEnumClass * enum_class; - GEnumValue * enum_value; - PyObject * result; - - enum_class = g_type_class_ref (type); - - enum_value = g_enum_get_value (enum_class, value); - g_assert (enum_value != NULL); - - result = PyUnicode_FromString (enum_value->value_nick); - - g_type_class_unref (enum_class); - - return result; -} - -static gboolean -PyGObject_unmarshal_enum (const gchar * str, GType type, gpointer value) -{ - GEnumClass * enum_class; - GEnumValue * enum_value; - - enum_class = g_type_class_ref (type); - - enum_value = g_enum_get_value_by_nick (enum_class, str); - if (enum_value == NULL) - goto invalid_value; - - *((gint *) value) = enum_value->value; - - g_type_class_unref (enum_class); - - return TRUE; - -invalid_value: - { - GString * message; - guint i; - - message = g_string_sized_new (128); - - g_string_append_printf (message, - "Enum type %s does not have a value named '%s', it only has: ", - PyGObject_class_name_from_c (g_type_name (type)), str); - - for (i = 0; i != enum_class->n_values; i++) - { - if (i != 0) - g_string_append (message, ", "); - g_string_append_c (message, '\''); - g_string_append (message, enum_class->values[i].value_nick); - g_string_append_c (message, '\''); - } - - PyErr_SetString (PyExc_ValueError, message->str); - - g_string_free (message, TRUE); - - g_type_class_unref (enum_class); - - return FALSE; - } -} - -static PyObject * -PyGObject_marshal_bytes (GBytes * bytes) -{ - if (bytes == NULL) - PyFrida_RETURN_NONE; - - return PyGObject_marshal_bytes_non_nullable (bytes); -} - -static PyObject * -PyGObject_marshal_bytes_non_nullable (GBytes * bytes) -{ - gconstpointer data; - gsize size; - - data = g_bytes_get_data (bytes, &size); - - return PyBytes_FromStringAndSize (data, size); -} - -static PyObject * -PyGObject_marshal_variant (GVariant * variant) -{ - switch (g_variant_classify (variant)) - { - case G_VARIANT_CLASS_STRING: - return PyGObject_marshal_string (g_variant_get_string (variant, NULL)); - case G_VARIANT_CLASS_BYTE: - return PyLong_FromLong (g_variant_get_byte (variant)); - case G_VARIANT_CLASS_INT16: - return PyLong_FromLong (g_variant_get_int16 (variant)); - case G_VARIANT_CLASS_UINT16: - return PyLong_FromUnsignedLong (g_variant_get_uint16 (variant)); - case G_VARIANT_CLASS_INT32: - return PyLong_FromLong (g_variant_get_int32 (variant)); - case G_VARIANT_CLASS_UINT32: - return PyLong_FromUnsignedLong (g_variant_get_uint32 (variant)); - case G_VARIANT_CLASS_INT64: - return PyLong_FromLongLong (g_variant_get_int64 (variant)); - case G_VARIANT_CLASS_UINT64: - return PyLong_FromUnsignedLongLong (g_variant_get_uint64 (variant)); - case G_VARIANT_CLASS_DOUBLE: - return PyFloat_FromDouble (g_variant_get_double (variant)); - case G_VARIANT_CLASS_BOOLEAN: - return PyBool_FromLong (g_variant_get_boolean (variant)); - case G_VARIANT_CLASS_ARRAY: - if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("ay"))) - return PyGObject_marshal_variant_byte_array (variant); - - if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT)) - return PyGObject_marshal_variant_dict (variant); - - return PyGObject_marshal_variant_array (variant); - case G_VARIANT_CLASS_TUPLE: - return PyGObject_marshal_variant_tuple (variant); - case G_VARIANT_CLASS_VARIANT: - { - GVariant * inner; - PyObject * result; - - inner = g_variant_get_variant (variant); - result = PyGObject_marshal_variant (inner); - g_variant_unref (inner); - - return result; - } - default: - break; - } - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyGObject_marshal_variant_byte_array (GVariant * variant) -{ - gconstpointer elements; - gsize n_elements; - - elements = g_variant_get_fixed_array (variant, &n_elements, sizeof (guint8)); - - return PyBytes_FromStringAndSize (elements, n_elements); -} - -static PyObject * -PyGObject_marshal_variant_dict (GVariant * variant) -{ - PyObject * dict; - GVariantIter iter; - gchar * key; - GVariant * raw_value; - - dict = PyDict_New (); - - g_variant_iter_init (&iter, variant); - - while (g_variant_iter_next (&iter, "{sv}", &key, &raw_value)) - { - PyObject * value = PyGObject_marshal_variant (raw_value); - - PyDict_SetItemString (dict, key, value); - - Py_DecRef (value); - g_variant_unref (raw_value); - g_free (key); - } - - return dict; -} - -static PyObject * -PyGObject_marshal_variant_array (GVariant * variant) -{ - GVariantIter iter; - PyObject * list; - guint i; - GVariant * child; - - g_variant_iter_init (&iter, variant); - - list = PyList_New (g_variant_iter_n_children (&iter)); - - for (i = 0; (child = g_variant_iter_next_value (&iter)) != NULL; i++) - { - PyList_SetItem (list, i, PyGObject_marshal_variant (child)); - - g_variant_unref (child); - } - - return list; -} - -static PyObject * -PyGObject_marshal_variant_tuple (GVariant * variant) -{ - GVariantIter iter; - PyObject * tuple; - guint i; - GVariant * child; - - g_variant_iter_init (&iter, variant); - - tuple = PyTuple_New (g_variant_iter_n_children (&iter)); - - for (i = 0; (child = g_variant_iter_next_value (&iter)) != NULL; i++) - { - PyTuple_SetItem (tuple, i, PyGObject_marshal_variant (child)); - - g_variant_unref (child); - } - - return tuple; -} - -static gboolean -PyGObject_unmarshal_variant (PyObject * value, GVariant ** variant) -{ - if (PyUnicode_Check (value)) - { - gchar * str; - - PyGObject_unmarshal_string (value, &str); - - *variant = g_variant_ref_sink (g_variant_new_take_string (str)); - - return TRUE; - } - - if (PyBool_Check (value)) - { - *variant = g_variant_ref_sink (g_variant_new_boolean (value == Py_True)); - - return TRUE; - } - - if (PyLong_Check (value)) - { - PY_LONG_LONG l; - - l = PyLong_AsLongLong (value); - if (l == -1 && PyErr_Occurred ()) - return FALSE; - - *variant = g_variant_ref_sink (g_variant_new_int64 (l)); - - return TRUE; - } - - if (PyFloat_Check (value)) - { - *variant = g_variant_ref_sink (g_variant_new_double (PyFloat_AsDouble (value))); - - return TRUE; - } - - if (PyBytes_Check (value)) - { - char * buffer; - Py_ssize_t length; - gpointer copy; - - PyBytes_AsStringAndSize (value, &buffer, &length); - - copy = g_memdup2 (buffer, length); - *variant = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE_BYTESTRING, copy, length, TRUE, g_free, copy)); - - return TRUE; - } - - if (PySequence_Check (value)) - return PyGObject_unmarshal_variant_from_sequence (value, variant); - - if (PyMapping_Check (value)) - return PyGObject_unmarshal_variant_from_mapping (value, variant); - - PyErr_SetString (PyExc_TypeError, "unsupported type"); - return FALSE; -} - -static gboolean -PyGObject_unmarshal_variant_from_mapping (PyObject * mapping, GVariant ** variant) -{ - GVariantBuilder builder; - PyObject * items = NULL; - Py_ssize_t n, i; - - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - - items = PyMapping_Items (mapping); - if (items == NULL) - goto propagate_error; - - n = PyList_Size (items); - - for (i = 0; i != n; i++) - { - PyObject * pair, * key, * val, * key_bytes; - GVariant * raw_value; - - pair = PyList_GetItem (items, i); - key = PyTuple_GetItem (pair, 0); - val = PyTuple_GetItem (pair, 1); - - if (!PyGObject_unmarshal_variant (val, &raw_value)) - goto propagate_error; - - key_bytes = PyUnicode_AsUTF8String (key); - - g_variant_builder_add (&builder, "{sv}", PyBytes_AsString (key_bytes), raw_value); - - g_variant_unref (raw_value); - Py_DecRef (key_bytes); - } - - Py_DecRef (items); - - *variant = g_variant_ref_sink (g_variant_builder_end (&builder)); - - return TRUE; - -propagate_error: - { - Py_DecRef (items); - g_variant_builder_clear (&builder); - - return FALSE; - } -} - -static gboolean -PyGObject_unmarshal_variant_from_sequence (PyObject * sequence, GVariant ** variant) -{ - gboolean is_tuple; - GVariantBuilder builder; - Py_ssize_t n, i; - PyObject * val = NULL; - - is_tuple = PyTuple_Check (sequence); - - g_variant_builder_init (&builder, is_tuple ? G_VARIANT_TYPE_TUPLE : G_VARIANT_TYPE ("av")); - - n = PySequence_Length (sequence); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - GVariant * raw_value; - - val = PySequence_GetItem (sequence, i); - if (val == NULL) - goto propagate_error; - - if (!PyGObject_unmarshal_variant (val, &raw_value)) - goto propagate_error; - - if (is_tuple) - g_variant_builder_add_value (&builder, raw_value); - else - g_variant_builder_add (&builder, "v", raw_value); - - g_variant_unref (raw_value); - Py_DecRef (val); - } - - *variant = g_variant_ref_sink (g_variant_builder_end (&builder)); - - return TRUE; - -propagate_error: - { - Py_DecRef (val); - g_variant_builder_clear (&builder); - - return FALSE; - } -} - -static PyObject * -PyGObject_marshal_parameters_dict (GHashTable * dict) -{ - PyObject * result; - GHashTableIter iter; - const gchar * key; - GVariant * raw_value; - - result = PyDict_New (); - - g_hash_table_iter_init (&iter, dict); - - while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &raw_value)) - { - PyObject * value = PyGObject_marshal_variant (raw_value); - - PyDict_SetItemString (result, key, value); - - Py_DecRef (value); - } - - return result; -} - -static PyObject * -PyGObject_marshal_object (gpointer handle, GType type) -{ - const PyGObjectType * pytype; - - if (handle == NULL) - PyFrida_RETURN_NONE; - - pytype = g_hash_table_lookup (pygobject_type_spec_by_type, GSIZE_TO_POINTER (type)); - if (pytype == NULL) - pytype = PYFRIDA_TYPE (GObject); - - if (G_IS_SOCKET_ADDRESS (handle)) - return PyGObject_marshal_socket_address (handle); - - return PyGObject_new_take_handle (g_object_ref (handle), pytype); -} - -static PyObject * -PyGObject_marshal_socket_address (GSocketAddress * address) -{ - PyObject * result = NULL; - - if (G_IS_INET_SOCKET_ADDRESS (address)) - { - GInetSocketAddress * sa; - GInetAddress * ia; - gchar * host; - guint16 port; - - sa = G_INET_SOCKET_ADDRESS (address); - ia = g_inet_socket_address_get_address (sa); - - host = g_inet_address_to_string (ia); - port = g_inet_socket_address_get_port (sa); - - if (g_socket_address_get_family (address) == G_SOCKET_FAMILY_IPV4) - result = Py_BuildValue ("(sH)", host, port); - else - result = Py_BuildValue ("(sHII)", host, port, g_inet_socket_address_get_flowinfo (sa), g_inet_socket_address_get_scope_id (sa)); - - g_free (host); - } - else if (G_IS_UNIX_SOCKET_ADDRESS (address)) - { - GUnixSocketAddress * sa = G_UNIX_SOCKET_ADDRESS (address); - - switch (g_unix_socket_address_get_address_type (sa)) - { - case G_UNIX_SOCKET_ADDRESS_ANONYMOUS: - { - result = PyUnicode_FromString (""); - break; - } - case G_UNIX_SOCKET_ADDRESS_PATH: - { - gchar * path = g_filename_to_utf8 (g_unix_socket_address_get_path (sa), -1, NULL, NULL, NULL); - result = PyUnicode_FromString (path); - g_free (path); - break; - } - case G_UNIX_SOCKET_ADDRESS_ABSTRACT: - case G_UNIX_SOCKET_ADDRESS_ABSTRACT_PADDED: - { - result = PyBytes_FromStringAndSize (g_unix_socket_address_get_path (sa), g_unix_socket_address_get_path_len (sa)); - break; - } - default: - { - Py_IncRef (Py_None); - result = Py_None; - break; - } - } - } - - if (result == NULL) - result = PyGObject_new_take_handle (g_object_ref (address), PYFRIDA_TYPE (GObject)); - - return result; -} - -static gboolean -PyGObject_unmarshal_certificate (const gchar * str, GTlsCertificate ** certificate) -{ - GError * error = NULL; - - if (strchr (str, '\n') != NULL) - *certificate = g_tls_certificate_new_from_pem (str, -1, &error); - else - *certificate = g_tls_certificate_new_from_file (str, &error); - if (error != NULL) - goto propagate_error; - - return TRUE; - -propagate_error: - { - PyFrida_raise (g_error_new_literal (FRIDA_ERROR, FRIDA_ERROR_INVALID_ARGUMENT, error->message)); - g_error_free (error); - - return FALSE; - } -} - - -static int -PyDeviceManager_init (PyDeviceManager * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - PyGObject_take_handle (&self->parent, frida_device_manager_new (), PYFRIDA_TYPE (DeviceManager)); - - return 0; -} - -static void -PyDeviceManager_dealloc (PyDeviceManager * self) -{ - FridaDeviceManager * handle; - - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - handle = PyGObject_steal_handle (&self->parent); - if (handle != NULL) - { - Py_BEGIN_ALLOW_THREADS - frida_device_manager_close_sync (handle, NULL, NULL); - frida_unref (handle); - Py_END_ALLOW_THREADS - } - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyDeviceManager_close (PyDeviceManager * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_manager_close_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDeviceManager_get_device_matching (PyDeviceManager * self, PyObject * args) -{ - PyObject * predicate; - gint timeout; - GError * error = NULL; - FridaDevice * result; - - if (!PyArg_ParseTuple (args, "Oi", &predicate, &timeout)) - return NULL; - - if (!PyCallable_Check (predicate)) - goto not_callable; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_manager_get_device_sync (PY_GOBJECT_HANDLE (self), (FridaDeviceManagerPredicate) PyDeviceManager_is_matching_device, - predicate, timeout, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - return PyDevice_new_take_handle (result); - -not_callable: - { - PyErr_SetString (PyExc_TypeError, "object must be callable"); - return NULL; - } -} - -static gboolean -PyDeviceManager_is_matching_device (FridaDevice * device, PyObject * predicate) -{ - gboolean is_matching = FALSE; - PyGILState_STATE gstate; - PyObject * device_object, * result; - - gstate = PyGILState_Ensure (); - - device_object = PyDevice_new_take_handle (g_object_ref (device)); - - result = PyObject_CallFunction (predicate, "O", device_object); - if (result != NULL) - { - is_matching = result == Py_True; - - Py_DecRef (result); - } - else - { - PyErr_Print (); - } - - Py_DecRef (device_object); - - PyGILState_Release (gstate); - - return is_matching; -} - -static PyObject * -PyDeviceManager_enumerate_devices (PyDeviceManager * self) -{ - GError * error = NULL; - FridaDeviceList * result; - gint result_length, i; - PyObject * devices; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_manager_enumerate_devices_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_device_list_size (result); - devices = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (devices, i, PyDevice_new_take_handle (frida_device_list_get (result, i))); - } - frida_unref (result); - - return devices; -} - -static PyObject * -PyDeviceManager_add_remote_device (PyDeviceManager * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "address", "certificate", "origin", "token", "keepalive_interval", NULL }; - char * address; - char * certificate = NULL; - char * origin = NULL; - char * token = NULL; - int keepalive_interval = -1; - FridaRemoteDeviceOptions * options; - GError * error = NULL; - FridaDevice * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esesesi", keywords, - "utf-8", &address, - "utf-8", &certificate, - "utf-8", &origin, - "utf-8", &token, - &keepalive_interval)) - return NULL; - - options = PyDeviceManager_parse_remote_device_options (certificate, origin, token, keepalive_interval); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_device_manager_add_remote_device_sync (PY_GOBJECT_HANDLE (self), address, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PyDevice_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - - PyMem_Free (token); - PyMem_Free (origin); - PyMem_Free (certificate); - PyMem_Free (address); - - return result; -} - -static PyObject * -PyDeviceManager_remove_remote_device (PyDeviceManager * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "address", NULL }; - char * address; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es", keywords, "utf-8", &address)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_manager_remove_remote_device_sync (PY_GOBJECT_HANDLE (self), address, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - PyMem_Free (address); - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static FridaRemoteDeviceOptions * -PyDeviceManager_parse_remote_device_options (const gchar * certificate_value, const gchar * origin, const gchar * token, - gint keepalive_interval) -{ - FridaRemoteDeviceOptions * options; - - options = frida_remote_device_options_new (); - - if (certificate_value != NULL) - { - GTlsCertificate * certificate; - - if (!PyGObject_unmarshal_certificate (certificate_value, &certificate)) - goto propagate_error; - - frida_remote_device_options_set_certificate (options, certificate); - - g_object_unref (certificate); - } - - if (origin != NULL) - frida_remote_device_options_set_origin (options, origin); - - if (token != NULL) - frida_remote_device_options_set_token (options, token); - - if (keepalive_interval != -1) - frida_remote_device_options_set_keepalive_interval (options, keepalive_interval); - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - - -static PyObject * -PyDevice_new_take_handle (FridaDevice * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Device)); -} - -static int -PyDevice_init (PyDevice * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->id = NULL; - self->name = NULL; - self->icon = NULL; - self->type = NULL; - self->bus = NULL; - - return 0; -} - -static void -PyDevice_init_from_handle (PyDevice * self, FridaDevice * handle) -{ - GVariant * icon; - - self->id = PyUnicode_FromString (frida_device_get_id (handle)); - self->name = PyUnicode_FromString (frida_device_get_name (handle)); - icon = frida_device_get_icon (handle); - if (icon != NULL) - { - self->icon = PyGObject_marshal_variant (icon); - } - else - { - self->icon = Py_None; - Py_IncRef (Py_None); - } - self->type = PyGObject_marshal_enum (frida_device_get_dtype (handle), FRIDA_TYPE_DEVICE_TYPE); - self->bus = PyBus_new_take_handle (g_object_ref (frida_device_get_bus (handle))); -} - -static void -PyDevice_dealloc (PyDevice * self) -{ - Py_DecRef (self->bus); - Py_DecRef (self->type); - Py_DecRef (self->icon); - Py_DecRef (self->name); - Py_DecRef (self->id); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyDevice_repr (PyDevice * self) -{ - PyObject * id_bytes, * name_bytes, * type_bytes, * result; - - id_bytes = PyUnicode_AsUTF8String (self->id); - name_bytes = PyUnicode_AsUTF8String (self->name); - type_bytes = PyUnicode_AsUTF8String (self->type); - - result = PyUnicode_FromFormat ("Device(id=\"%s\", name=\"%s\", type='%s')", - PyBytes_AsString (id_bytes), - PyBytes_AsString (name_bytes), - PyBytes_AsString (type_bytes)); - - Py_DecRef (type_bytes); - Py_DecRef (name_bytes); - Py_DecRef (id_bytes); - - return result; -} - -static PyObject * -PyDevice_is_lost (PyDevice * self) -{ - gboolean is_lost; - - Py_BEGIN_ALLOW_THREADS - is_lost = frida_device_is_lost (PY_GOBJECT_HANDLE (self)); - Py_END_ALLOW_THREADS - - return PyBool_FromLong (is_lost); -} - -static PyObject * -PyDevice_override_option (PyDevice * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "name", "value", NULL }; - const char * name; - PyObject * value; - GVariant * raw_value; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "sO", keywords, &name, &value)) - return NULL; - - if (!PyGObject_unmarshal_variant (value, &raw_value)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_override_option (PY_GOBJECT_HANDLE (self), name, raw_value, &error); - Py_END_ALLOW_THREADS - - g_variant_unref (raw_value); - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_query_system_parameters (PyDevice * self) -{ - GError * error = NULL; - GHashTable * result; - PyObject * parameters; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_query_system_parameters_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - parameters = PyGObject_marshal_parameters_dict (result); - g_hash_table_unref (result); - - return parameters; -} - -static PyObject * -PyDevice_get_frontmost_application (PyDevice * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "scope", NULL }; - const char * scope_value = NULL; - FridaFrontmostQueryOptions * options; - GError * error = NULL; - FridaApplication * result; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|s", keywords, &scope_value)) - return NULL; - - options = frida_frontmost_query_options_new (); - - if (scope_value != NULL) - { - FridaScope scope; - - if (!PyGObject_unmarshal_enum (scope_value, FRIDA_TYPE_SCOPE, &scope)) - goto invalid_argument; - - frida_frontmost_query_options_set_scope (options, scope); - } - - Py_BEGIN_ALLOW_THREADS - result = frida_device_get_frontmost_application_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - if (result != NULL) - return PyApplication_new_take_handle (result); - else - PyFrida_RETURN_NONE; - -invalid_argument: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PyDevice_enumerate_applications (PyDevice * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "identifiers", "scope", NULL }; - PyObject * identifiers = NULL; - const char * scope = NULL; - FridaApplicationQueryOptions * options; - GError * error = NULL; - FridaApplicationList * result; - gint result_length, i; - PyObject * applications; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|Os", keywords, &identifiers, &scope)) - return NULL; - - options = PyDevice_parse_application_query_options (identifiers, scope); - if (options == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_enumerate_applications_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_application_list_size (result); - applications = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (applications, i, PyApplication_new_take_handle (frida_application_list_get (result, i))); - } - g_object_unref (result); - - return applications; -} - -static FridaApplicationQueryOptions * -PyDevice_parse_application_query_options (PyObject * identifiers_value, const gchar * scope_value) -{ - FridaApplicationQueryOptions * options; - - options = frida_application_query_options_new (); - - if (identifiers_value != NULL) - { - gint n, i; - - n = PySequence_Size (identifiers_value); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - PyObject * element; - gchar * identifier = NULL; - - element = PySequence_GetItem (identifiers_value, i); - if (element == NULL) - goto propagate_error; - PyGObject_unmarshal_string (element, &identifier); - Py_DecRef (element); - if (identifier == NULL) - goto propagate_error; - - frida_application_query_options_select_identifier (options, identifier); - - g_free (identifier); - } - } - - if (scope_value != NULL) - { - FridaScope scope; - - if (!PyGObject_unmarshal_enum (scope_value, FRIDA_TYPE_SCOPE, &scope)) - goto propagate_error; - - frida_application_query_options_set_scope (options, scope); - } - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PyDevice_enumerate_processes (PyDevice * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "pids", "scope", NULL }; - PyObject * pids = NULL; - const char * scope = NULL; - FridaProcessQueryOptions * options; - GError * error = NULL; - FridaProcessList * result; - gint result_length, i; - PyObject * processes; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|Os", keywords, &pids, &scope)) - return NULL; - - options = PyDevice_parse_process_query_options (pids, scope); - if (options == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_enumerate_processes_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_process_list_size (result); - processes = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (processes, i, PyProcess_new_take_handle (frida_process_list_get (result, i))); - } - g_object_unref (result); - - return processes; -} - -static FridaProcessQueryOptions * -PyDevice_parse_process_query_options (PyObject * pids_value, const gchar * scope_value) -{ - FridaProcessQueryOptions * options; - - options = frida_process_query_options_new (); - - if (pids_value != NULL) - { - gint n, i; - - n = PySequence_Size (pids_value); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - PyObject * element; - long long pid; - - element = PySequence_GetItem (pids_value, i); - if (element == NULL) - goto propagate_error; - pid = PyLong_AsLongLong (element); - Py_DecRef (element); - if (pid == -1) - goto propagate_error; - - frida_process_query_options_select_pid (options, pid); - } - } - - if (scope_value != NULL) - { - FridaScope scope; - - if (!PyGObject_unmarshal_enum (scope_value, FRIDA_TYPE_SCOPE, &scope)) - goto propagate_error; - - frida_process_query_options_set_scope (options, scope); - } - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PyDevice_enable_spawn_gating (PyDevice * self) -{ - GError * error = NULL; - FridaSpawnGatingOptions * options; - - options = frida_spawn_gating_options_new (); - - Py_BEGIN_ALLOW_THREADS - frida_device_enable_spawn_gating_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_disable_spawn_gating (PyDevice * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_disable_spawn_gating_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_enumerate_pending_spawn (PyDevice * self) -{ - GError * error = NULL; - FridaSpawnList * result; - gint result_length, i; - PyObject * spawn; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_enumerate_pending_spawn_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_spawn_list_size (result); - spawn = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (spawn, i, PySpawn_new_take_handle (frida_spawn_list_get (result, i))); - } - g_object_unref (result); - - return spawn; -} - -static PyObject * -PyDevice_enumerate_pending_children (PyDevice * self) -{ - GError * error = NULL; - FridaChildList * result; - gint result_length, i; - PyObject * children; - - Py_BEGIN_ALLOW_THREADS - result = frida_device_enumerate_pending_children_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - result_length = frida_child_list_size (result); - children = PyList_New (result_length); - for (i = 0; i != result_length; i++) - { - PyList_SetItem (children, i, PyChild_new_take_handle (frida_child_list_get (result, i))); - } - g_object_unref (result); - - return children; -} - -static PyObject * -PyDevice_spawn (PyDevice * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "program", "argv", "envp", "env", "cwd", "stdio", "aux", NULL }; - const char * program; - PyObject * argv_value = Py_None; - PyObject * envp_value = Py_None; - PyObject * env_value = Py_None; - const char * cwd = NULL; - const char * stdio_value = NULL; - PyObject * aux_value = Py_None; - FridaSpawnOptions * options; - GError * error = NULL; - guint pid; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "s|OOOzzO", keywords, - &program, - &argv_value, - &envp_value, - &env_value, - &cwd, - &stdio_value, - &aux_value)) - return NULL; - - options = frida_spawn_options_new (); - - if (argv_value != Py_None) - { - gchar ** argv; - gint argv_length; - - if (!PyGObject_unmarshal_strv (argv_value, &argv, &argv_length)) - goto invalid_argument; - - frida_spawn_options_set_argv (options, argv, argv_length); - - g_strfreev (argv); - } - - if (envp_value != Py_None) - { - gchar ** envp; - gint envp_length; - - if (!PyGObject_unmarshal_envp (envp_value, &envp, &envp_length)) - goto invalid_argument; - - frida_spawn_options_set_envp (options, envp, envp_length); - - g_strfreev (envp); - } - - if (env_value != Py_None) - { - gchar ** env; - gint env_length; - - if (!PyGObject_unmarshal_envp (env_value, &env, &env_length)) - goto invalid_argument; - - frida_spawn_options_set_env (options, env, env_length); - - g_strfreev (env); - } - - if (cwd != NULL) - frida_spawn_options_set_cwd (options, cwd); - - if (stdio_value != NULL) - { - FridaStdio stdio; - - if (!PyGObject_unmarshal_enum (stdio_value, FRIDA_TYPE_STDIO, &stdio)) - goto invalid_argument; - - frida_spawn_options_set_stdio (options, stdio); - } - - if (aux_value != Py_None) - { - GHashTable * aux; - Py_ssize_t pos; - PyObject * key, * value; - - aux = frida_spawn_options_get_aux (options); - - if (!PyDict_Check (aux_value)) - goto invalid_aux_dict; - - pos = 0; - while (PyDict_Next (aux_value, &pos, &key, &value)) - { - gchar * raw_key; - GVariant * raw_value; - - if (!PyGObject_unmarshal_string (key, &raw_key)) - goto invalid_dict_key; - - if (!PyGObject_unmarshal_variant (value, &raw_value)) - { - g_free (raw_key); - goto invalid_dict_value; - } - - g_hash_table_insert (aux, raw_key, raw_value); - } - } - - Py_BEGIN_ALLOW_THREADS - pid = frida_device_spawn_sync (PY_GOBJECT_HANDLE (self), program, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - return PyLong_FromUnsignedLong (pid); - -invalid_argument: -invalid_dict_key: -invalid_dict_value: - { - g_object_unref (options); - - return NULL; - } -invalid_aux_dict: - { - g_object_unref (options); - - PyErr_SetString (PyExc_TypeError, "unsupported parameter"); - - return NULL; - } -} - -static PyObject * -PyDevice_input (PyDevice * self, PyObject * args) -{ - long pid; - gconstpointer data_buffer; - Py_ssize_t data_size; - GBytes * data; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "ly#", &pid, &data_buffer, &data_size)) - return NULL; - - data = g_bytes_new (data_buffer, data_size); - - Py_BEGIN_ALLOW_THREADS - frida_device_input_sync (PY_GOBJECT_HANDLE (self), (guint) pid, data, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_resume (PyDevice * self, PyObject * args) -{ - long pid; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "l", &pid)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_resume_sync (PY_GOBJECT_HANDLE (self), (guint) pid, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_kill (PyDevice * self, PyObject * args) -{ - long pid; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "l", &pid)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_kill_sync (PY_GOBJECT_HANDLE (self), (guint) pid, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyDevice_attach (PyDevice * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { - "pid", - "realm", - "persist_timeout", - "exceptor", - "unwind_broker", - "exit_monitor", - "thread_suspend_monitor", - "linker_notifier_offsets", - NULL - }; - long pid; - char * realm_value = NULL; - unsigned int persist_timeout = 0; - char * exceptor_value = NULL; - int unwind_broker = -1; - int exit_monitor = -1; - int thread_suspend_monitor = -1; - PyObject * linker_notifier_offsets_value = NULL; - FridaSessionOptions * options = NULL; - GError * error = NULL; - FridaSession * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "l|esIespppO", keywords, - &pid, - "utf-8", &realm_value, - &persist_timeout, - "utf-8", &exceptor_value, - &unwind_broker, - &exit_monitor, - &thread_suspend_monitor, - &linker_notifier_offsets_value)) - return NULL; - - options = PyDevice_parse_session_options (realm_value, persist_timeout, - exceptor_value, unwind_broker, exit_monitor, thread_suspend_monitor, - linker_notifier_offsets_value); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_device_attach_sync (PY_GOBJECT_HANDLE (self), (guint) pid, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PySession_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - - PyMem_Free (exceptor_value); - PyMem_Free (realm_value); - - return result; -} - -static FridaSessionOptions * -PyDevice_parse_session_options (const gchar * realm_value, - guint persist_timeout, - const gchar * exceptor_value, - gint unwind_broker, - gint exit_monitor, - gint thread_suspend_monitor, - PyObject * linker_notifier_offsets_value) -{ - FridaSessionOptions * options; - - options = frida_session_options_new (); - - if (realm_value != NULL) - { - FridaRealm realm; - - if (!PyGObject_unmarshal_enum (realm_value, FRIDA_TYPE_REALM, &realm)) - goto propagate_error; - - frida_session_options_set_realm (options, realm); - } - - frida_session_options_set_persist_timeout (options, persist_timeout); - - if (exceptor_value != NULL) - { - FridaExceptor exceptor; - - if (!PyGObject_unmarshal_enum (exceptor_value, FRIDA_TYPE_EXCEPTOR, &exceptor)) - goto propagate_error; - - frida_session_options_set_exceptor (options, exceptor); - } - - if (unwind_broker != -1) - frida_session_options_set_unwind_broker (options, unwind_broker); - - if (exit_monitor != -1) - frida_session_options_set_exit_monitor (options, exit_monitor); - - if (thread_suspend_monitor != -1) - frida_session_options_set_thread_suspend_monitor (options, thread_suspend_monitor); - - if (linker_notifier_offsets_value != NULL) - { - gint n, i; - - n = PySequence_Size (linker_notifier_offsets_value); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - PyObject * element; - unsigned long offset; - - element = PySequence_GetItem (linker_notifier_offsets_value, i); - if (element == NULL) - goto propagate_error; - offset = PyLong_AsUnsignedLong (element); - Py_DecRef (element); - if (offset == (unsigned long) -1 && PyErr_Occurred () != NULL) - goto propagate_error; - - frida_session_options_add_linker_notifier_offset (options, offset); - } - } - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PyDevice_inject_library_file (PyDevice * self, PyObject * args) -{ - long pid; - const char * path, * entrypoint, * data; - GError * error = NULL; - guint id; - - if (!PyArg_ParseTuple (args, "lsss", &pid, &path, &entrypoint, &data)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - id = frida_device_inject_library_file_sync (PY_GOBJECT_HANDLE (self), (guint) pid, path, entrypoint, data, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - return PyLong_FromUnsignedLong (id); -} - -static PyObject * -PyDevice_inject_library_blob (PyDevice * self, PyObject * args) -{ - long pid; - GBytes * blob; - gconstpointer blob_buffer; - Py_ssize_t blob_size; - const char * entrypoint, * data; - GError * error = NULL; - guint id; - - if (!PyArg_ParseTuple (args, "ly#ss", &pid, &blob_buffer, &blob_size, &entrypoint, &data)) - return NULL; - - blob = g_bytes_new (blob_buffer, blob_size); - - Py_BEGIN_ALLOW_THREADS - id = frida_device_inject_library_blob_sync (PY_GOBJECT_HANDLE (self), (guint) pid, blob, entrypoint, data, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_bytes_unref (blob); - - if (error != NULL) - return PyFrida_raise (error); - - return PyLong_FromUnsignedLong (id); -} - -static PyObject * -PyDevice_open_channel (PyDevice * self, PyObject * args) -{ - const char * address; - GError * error = NULL; - GIOStream * stream; - - if (!PyArg_ParseTuple (args, "s", &address)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - stream = frida_device_open_channel_sync (PY_GOBJECT_HANDLE (self), address, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - return PyIOStream_new_take_handle (stream); -} - -static PyObject * -PyDevice_open_service (PyDevice * self, PyObject * args) -{ - const char * address; - GError * error = NULL; - FridaService * service; - - if (!PyArg_ParseTuple (args, "s", &address)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - service = frida_device_open_service_sync (PY_GOBJECT_HANDLE (self), address, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - return PyService_new_take_handle (service); -} - -static PyObject * -PyDevice_unpair (PyDevice * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_device_unpair_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static PyObject * -PyApplication_new_take_handle (FridaApplication * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Application)); -} - -static int -PyApplication_init (PyApplication * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->identifier = NULL; - self->name = NULL; - self->pid = 0; - self->parameters = NULL; - - return 0; -} - -static void -PyApplication_init_from_handle (PyApplication * self, FridaApplication * handle) -{ - self->identifier = PyUnicode_FromString (frida_application_get_identifier (handle)); - self->name = PyUnicode_FromString (frida_application_get_name (handle)); - self->pid = frida_application_get_pid (handle); - self->parameters = PyApplication_marshal_parameters_dict (frida_application_get_parameters (handle)); -} - -static void -PyApplication_dealloc (PyApplication * self) -{ - Py_DecRef (self->parameters); - Py_DecRef (self->name); - Py_DecRef (self->identifier); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyApplication_repr (PyApplication * self) -{ - PyObject * result; - FridaApplication * handle; - GString * repr; - gchar * str; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_new ("Application("); - - g_string_append_printf (repr, "identifier=\"%s\", name=\"%s\"", - frida_application_get_identifier (handle), - frida_application_get_name (handle)); - - if (self->pid != 0) - g_string_append_printf (repr, ", pid=%u", self->pid); - - str = PyFrida_repr (self->parameters); - g_string_append_printf (repr, ", parameters=%s", str); - g_free (str); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - -static PyObject * -PyApplication_marshal_parameters_dict (GHashTable * dict) -{ - PyObject * result; - GHashTableIter iter; - const gchar * key; - GVariant * raw_value; - - result = PyDict_New (); - - g_hash_table_iter_init (&iter, dict); - - while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &raw_value)) - { - PyObject * value; - - if (strcmp (key, "started") == 0 && g_variant_is_of_type (raw_value, G_VARIANT_TYPE_STRING)) - value = PyGObject_marshal_datetime (g_variant_get_string (raw_value, NULL)); - else - value = PyGObject_marshal_variant (raw_value); - - PyDict_SetItemString (result, key, value); - - Py_DecRef (value); - } - - return result; -} - - -static PyObject * -PyProcess_new_take_handle (FridaProcess * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Process)); -} - -static int -PyProcess_init (PyProcess * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - self->name = NULL; - self->parameters = NULL; - - return 0; -} - -static void -PyProcess_init_from_handle (PyProcess * self, FridaProcess * handle) -{ - self->pid = frida_process_get_pid (handle); - self->name = PyUnicode_FromString (frida_process_get_name (handle)); - self->parameters = PyProcess_marshal_parameters_dict (frida_process_get_parameters (handle)); -} - -static void -PyProcess_dealloc (PyProcess * self) -{ - Py_DecRef (self->parameters); - Py_DecRef (self->name); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyProcess_repr (PyProcess * self) -{ - PyObject * result; - FridaProcess * handle; - GString * repr; - gchar * str; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_new ("Process("); - - g_string_append_printf (repr, "pid=%u, name=\"%s\"", - self->pid, - frida_process_get_name (handle)); - - str = PyFrida_repr (self->parameters); - g_string_append_printf (repr, ", parameters=%s", str); - g_free (str); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - -static PyObject * -PyProcess_marshal_parameters_dict (GHashTable * dict) -{ - PyObject * result; - GHashTableIter iter; - const gchar * key; - GVariant * raw_value; - - result = PyDict_New (); - - g_hash_table_iter_init (&iter, dict); - - while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &raw_value)) - { - PyObject * value; - - if (strcmp (key, "started") == 0 && g_variant_is_of_type (raw_value, G_VARIANT_TYPE_STRING)) - value = PyGObject_marshal_datetime (g_variant_get_string (raw_value, NULL)); - else - value = PyGObject_marshal_variant (raw_value); - - PyDict_SetItemString (result, key, value); - - Py_DecRef (value); - } - - return result; -} - - -static PyObject * -PySpawn_new_take_handle (FridaSpawn * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Spawn)); -} - -static int -PySpawn_init (PySpawn * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - self->identifier = NULL; - - return 0; -} - -static void -PySpawn_init_from_handle (PySpawn * self, FridaSpawn * handle) -{ - self->pid = frida_spawn_get_pid (handle); - self->identifier = PyGObject_marshal_string (frida_spawn_get_identifier (handle)); -} - -static void -PySpawn_dealloc (PySpawn * self) -{ - Py_DecRef (self->identifier); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PySpawn_repr (PySpawn * self) -{ - PyObject * result; - - if (self->identifier != Py_None) - { - PyObject * identifier_bytes; - - identifier_bytes = PyUnicode_AsUTF8String (self->identifier); - - result = PyUnicode_FromFormat ("Spawn(pid=%u, identifier=\"%s\")", - self->pid, - PyBytes_AsString (identifier_bytes)); - - Py_DecRef (identifier_bytes); - } - else - { - result = PyUnicode_FromFormat ("Spawn(pid=%u)", - self->pid); - } - - return result; -} - - -static PyObject * -PyChild_new_take_handle (FridaChild * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Child)); -} - -static int -PyChild_init (PyChild * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - self->parent_pid = 0; - self->origin = NULL; - self->identifier = NULL; - self->path = NULL; - self->argv = NULL; - self->envp = NULL; - - return 0; -} - -static void -PyChild_init_from_handle (PyChild * self, FridaChild * handle) -{ - gchar * const * argv, * const * envp; - gint argv_length, envp_length; - - self->pid = frida_child_get_pid (handle); - self->parent_pid = frida_child_get_parent_pid (handle); - - self->origin = PyGObject_marshal_enum (frida_child_get_origin (handle), FRIDA_TYPE_CHILD_ORIGIN); - - self->identifier = PyGObject_marshal_string (frida_child_get_identifier (handle)); - - self->path = PyGObject_marshal_string (frida_child_get_path (handle)); - - argv = frida_child_get_argv (handle, &argv_length); - self->argv = PyGObject_marshal_strv (argv, argv_length); - - envp = frida_child_get_envp (handle, &envp_length); - self->envp = PyGObject_marshal_envp (envp, envp_length); -} - -static void -PyChild_dealloc (PyChild * self) -{ - Py_DecRef (self->envp); - Py_DecRef (self->argv); - Py_DecRef (self->path); - Py_DecRef (self->identifier); - Py_DecRef (self->origin); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyChild_repr (PyChild * self) -{ - PyObject * result; - FridaChild * handle; - GString * repr; - FridaChildOrigin origin; - GEnumClass * origin_class; - GEnumValue * origin_value; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_new ("Child("); - - g_string_append_printf (repr, "pid=%u, parent_pid=%u", self->pid, self->parent_pid); - - origin = frida_child_get_origin (handle); - origin_class = g_type_class_ref (FRIDA_TYPE_CHILD_ORIGIN); - origin_value = g_enum_get_value (origin_class, origin); - g_string_append_printf (repr, ", origin=%s", origin_value->value_nick); - g_type_class_unref (origin_class); - - if (self->identifier != Py_None) - { - gchar * identifier; - - identifier = PyFrida_repr (self->identifier); - - g_string_append_printf (repr, ", identifier=%s", identifier); - - g_free (identifier); - } - - if (origin != FRIDA_CHILD_ORIGIN_FORK) - { - gchar * path, * argv, * envp; - - path = PyFrida_repr (self->path); - argv = PyFrida_repr (self->argv); - envp = PyFrida_repr (self->envp); - - g_string_append_printf (repr, ", path=%s, argv=%s, envp=%s", path, argv, envp); - - g_free (envp); - g_free (argv); - g_free (path); - } - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static int -PyCrash_init (PyCrash * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - self->process_name = NULL; - self->summary = NULL; - self->report = NULL; - self->parameters = NULL; - - return 0; -} - -static void -PyCrash_init_from_handle (PyCrash * self, FridaCrash * handle) -{ - self->pid = frida_crash_get_pid (handle); - self->process_name = PyGObject_marshal_string (frida_crash_get_process_name (handle)); - self->summary = PyGObject_marshal_string (frida_crash_get_summary (handle)); - self->report = PyGObject_marshal_string (frida_crash_get_report (handle)); - self->parameters = PyGObject_marshal_parameters_dict (frida_crash_get_parameters (handle)); -} - -static void -PyCrash_dealloc (PyCrash * self) -{ - Py_DecRef (self->parameters); - Py_DecRef (self->report); - Py_DecRef (self->summary); - Py_DecRef (self->process_name); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyCrash_repr (PyCrash * self) -{ - PyObject * result; - FridaCrash * handle; - GString * repr; - gchar * str; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_new ("Crash("); - - g_string_append_printf (repr, "pid=%u, process_name=\"%s\", summary=\"%s\", report=<%u bytes>", - self->pid, - frida_crash_get_process_name (handle), - frida_crash_get_summary (handle), - (guint) strlen (frida_crash_get_report (handle))); - - str = PyFrida_repr (self->parameters); - g_string_append_printf (repr, ", parameters=%s", str); - g_free (str); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static PyObject * -PyBus_new_take_handle (FridaBus * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Bus)); -} - -static PyObject * -PyBus_attach (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_bus_attach_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyBus_post (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "message", "data", NULL }; - char * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|z#", keywords, "utf-8", &message, &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_bus_post (PY_GOBJECT_HANDLE (self), message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - - PyFrida_RETURN_NONE; -} - - -static PyObject * -PyService_new_take_handle (FridaService * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Service)); -} - -static PyObject * -PyService_activate (PyService * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_service_activate_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyService_cancel (PyService * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_service_cancel_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyService_request (PyService * self, PyObject * args) -{ - PyObject * result, * params; - GVariant * raw_params, * raw_result; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "O", ¶ms)) - return NULL; - - if (!PyGObject_unmarshal_variant (params, &raw_params)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - raw_result = frida_service_request_sync (PY_GOBJECT_HANDLE (self), raw_params, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_variant_unref (raw_params); - - if (error != NULL) - return PyFrida_raise (error); - - result = PyGObject_marshal_variant (raw_result); - g_variant_unref (raw_result); - - return result; -} - - -static PyObject * -PySession_new_take_handle (FridaSession * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Session)); -} - -static int -PySession_init (PySession * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->pid = 0; - - return 0; -} - -static void -PySession_init_from_handle (PySession * self, FridaSession * handle) -{ - self->pid = frida_session_get_pid (handle); -} - -static PyObject * -PySession_repr (PySession * self) -{ - return PyUnicode_FromFormat ("Session(pid=%u)", self->pid); -} - -static PyObject * -PySession_is_detached (PySession * self) -{ - gboolean is_detached; - - Py_BEGIN_ALLOW_THREADS - is_detached = frida_session_is_detached (PY_GOBJECT_HANDLE (self)); - Py_END_ALLOW_THREADS - - return PyBool_FromLong (is_detached); -} - -static PyObject * -PySession_detach (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_session_detach_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PySession_resume (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_session_resume_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PySession_enable_child_gating (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_session_enable_child_gating_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PySession_disable_child_gating (PySession * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_session_disable_child_gating_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PySession_create_script (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "source", "name", "snapshot", "runtime", NULL }; - char * source; - char * name = NULL; - gconstpointer snapshot_data = NULL; - Py_ssize_t snapshot_size = 0; - const char * runtime_value = NULL; - FridaScriptOptions * options; - GError * error = NULL; - FridaScript * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esy#z", keywords, "utf-8", &source, "utf-8", &name, &snapshot_data, &snapshot_size, &runtime_value)) - return NULL; - - options = PySession_parse_script_options (name, snapshot_data, snapshot_size, runtime_value); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_session_create_script_sync (PY_GOBJECT_HANDLE (self), source, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PyScript_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - - PyMem_Free (name); - PyMem_Free (source); - - return result; -} - -static PyObject * -PySession_create_script_from_bytes (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "data", "name", "snapshot", "runtime", NULL }; - guint8 * data; - Py_ssize_t size; - char * name = NULL; - gconstpointer snapshot_data = NULL; - Py_ssize_t snapshot_size = 0; - const char * runtime_value = NULL; - GBytes * bytes; - FridaScriptOptions * options; - GError * error = NULL; - FridaScript * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "y#|esy#z", keywords, &data, &size, "utf-8", &name, &snapshot_data, &snapshot_size, &runtime_value)) - return NULL; - - bytes = g_bytes_new (data, size); - - options = PySession_parse_script_options (name, snapshot_data, snapshot_size, runtime_value); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_session_create_script_from_bytes_sync (PY_GOBJECT_HANDLE (self), bytes, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PyScript_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - g_bytes_unref (bytes); - - PyMem_Free (name); - - return result; -} - -static PyObject * -PySession_compile_script (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "source", "name", "runtime", NULL }; - char * source; - char * name = NULL; - const char * runtime_value = NULL; - FridaScriptOptions * options; - GError * error = NULL; - GBytes * bytes; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esz", keywords, "utf-8", &source, "utf-8", &name, &runtime_value)) - return NULL; - - options = PySession_parse_script_options (name, NULL, 0, runtime_value); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - bytes = frida_session_compile_script_sync (PY_GOBJECT_HANDLE (self), source, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error == NULL) - { - result = PyGObject_marshal_bytes_non_nullable (bytes); - - g_bytes_unref (bytes); - } - else - { - result = PyFrida_raise (error); - } - -beach: - g_clear_object (&options); - - PyMem_Free (name); - PyMem_Free (source); - - return result; -} - -static FridaScriptOptions * -PySession_parse_script_options (const gchar * name, gconstpointer snapshot_data, gsize snapshot_size, const gchar * runtime_value) -{ - FridaScriptOptions * options; - - options = frida_script_options_new (); - - if (name != NULL) - frida_script_options_set_name (options, name); - - if (snapshot_data != NULL) - { - GBytes * snapshot = g_bytes_new (snapshot_data, snapshot_size); - frida_script_options_set_snapshot (options, snapshot); - g_bytes_unref (snapshot); - } - - if (runtime_value != NULL) - { - FridaScriptRuntime runtime; - - if (!PyGObject_unmarshal_enum (runtime_value, FRIDA_TYPE_SCRIPT_RUNTIME, &runtime)) - goto invalid_argument; - - frida_script_options_set_runtime (options, runtime); - } - - return options; - -invalid_argument: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PySession_snapshot_script (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "embed_script", "warmup_script", "runtime", NULL }; - char * embed_script; - char * warmup_script = NULL; - const char * runtime_value = NULL; - FridaSnapshotOptions * options; - GError * error = NULL; - GBytes * bytes; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esz", keywords, "utf-8", &embed_script, "utf-8", &warmup_script, &runtime_value)) - return NULL; - - options = PySession_parse_snapshot_options (warmup_script, runtime_value); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - bytes = frida_session_snapshot_script_sync (PY_GOBJECT_HANDLE (self), embed_script, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error == NULL) - { - result = PyGObject_marshal_bytes_non_nullable (bytes); - - g_bytes_unref (bytes); - } - else - { - result = PyFrida_raise (error); - } - -beach: - g_clear_object (&options); - - PyMem_Free (warmup_script); - PyMem_Free (embed_script); - - return result; -} - -static FridaSnapshotOptions * -PySession_parse_snapshot_options (const gchar * warmup_script, const gchar * runtime_value) -{ - FridaSnapshotOptions * options; - - options = frida_snapshot_options_new (); - - if (warmup_script != NULL) - frida_snapshot_options_set_warmup_script (options, warmup_script); - - if (runtime_value != NULL) - { - FridaScriptRuntime runtime; - - if (!PyGObject_unmarshal_enum (runtime_value, FRIDA_TYPE_SCRIPT_RUNTIME, &runtime)) - goto invalid_argument; - - frida_snapshot_options_set_runtime (options, runtime); - } - - return options; - -invalid_argument: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PySession_setup_peer_connection (PySession * self, PyObject * args, PyObject * kw) -{ - gboolean success = FALSE; - static char * keywords[] = { "stun_server", "relays", NULL }; - char * stun_server = NULL; - PyObject * relays = NULL; - FridaPeerOptions * options = NULL; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|esO", keywords, - "utf-8", &stun_server, - &relays)) - return NULL; - - options = PySession_parse_peer_options (stun_server, relays); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - frida_session_setup_peer_connection_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error != NULL) - goto propagate_error; - - success = TRUE; - goto beach; - -propagate_error: - { - PyFrida_raise (error); - goto beach; - } -beach: - { - g_clear_object (&options); - - PyMem_Free (stun_server); - - if (!success) - return NULL; - - PyFrida_RETURN_NONE; - } -} - -static FridaPeerOptions * -PySession_parse_peer_options (const gchar * stun_server, PyObject * relays) -{ - FridaPeerOptions * options; - PyObject * relay; - - options = frida_peer_options_new (); - - frida_peer_options_set_stun_server (options, stun_server); - - if (relays != NULL) - { - Py_ssize_t n, i; - - n = PySequence_Length (relays); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - relay = PySequence_GetItem (relays, i); - if (relay == NULL) - goto propagate_error; - - if (!PyObject_IsInstance (relay, PYFRIDA_TYPE_OBJECT (Relay))) - goto expected_relay; - - frida_peer_options_add_relay (options, PY_GOBJECT_HANDLE (relay)); - - Py_DecRef (relay); - } - } - - return options; - -expected_relay: - { - Py_DecRef (relay); - - PyErr_SetString (PyExc_TypeError, "expected sequence of Relay objects"); - goto propagate_error; - } -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - -static PyObject * -PySession_join_portal (PySession * self, PyObject * args, PyObject * kw) -{ - PyObject * result = NULL; - static char * keywords[] = { "address", "certificate", "token", "acl", NULL }; - char * address; - char * certificate = NULL; - char * token = NULL; - PyObject * acl = NULL; - FridaPortalOptions * options; - GError * error = NULL; - FridaPortalMembership * handle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|esesO", keywords, - "utf-8", &address, - "utf-8", &certificate, - "utf-8", &token, - &acl)) - return NULL; - - options = PySession_parse_portal_options (certificate, token, acl); - if (options == NULL) - goto beach; - - Py_BEGIN_ALLOW_THREADS - handle = frida_session_join_portal_sync (PY_GOBJECT_HANDLE (self), address, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - result = (error == NULL) - ? PyPortalMembership_new_take_handle (handle) - : PyFrida_raise (error); - -beach: - g_clear_object (&options); - - PyMem_Free (token); - PyMem_Free (certificate); - PyMem_Free (address); - - return result; -} - -static FridaPortalOptions * -PySession_parse_portal_options (const gchar * certificate_value, const gchar * token, PyObject * acl_value) -{ - FridaPortalOptions * options; - - options = frida_portal_options_new (); - - if (certificate_value != NULL) - { - GTlsCertificate * certificate; - - if (!PyGObject_unmarshal_certificate (certificate_value, &certificate)) - goto propagate_error; - - frida_portal_options_set_certificate (options, certificate); - - g_object_unref (certificate); - } - - if (token != NULL) - frida_portal_options_set_token (options, token); - - if (acl_value != NULL) - { - gchar ** acl; - gint acl_length; - - if (!PyGObject_unmarshal_strv (acl_value, &acl, &acl_length)) - goto propagate_error; - - frida_portal_options_set_acl (options, acl, acl_length); - - g_strfreev (acl); - } - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - - -static PyObject * -PyScript_new_take_handle (FridaScript * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Script)); -} - -static PyObject * -PyScript_is_destroyed (PyScript * self) -{ - gboolean is_destroyed; - - Py_BEGIN_ALLOW_THREADS - is_destroyed = frida_script_is_destroyed (PY_GOBJECT_HANDLE (self)); - Py_END_ALLOW_THREADS - - return PyBool_FromLong (is_destroyed); -} - -static PyObject * -PyScript_load (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_load_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_interrupt (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_interrupt_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_unload (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_unload_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_terminate (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_terminate_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_eternalize (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_eternalize_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_post (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "message", "data", NULL }; - char * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|z#", keywords, "utf-8", &message, &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_post (PY_GOBJECT_HANDLE (self), message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_enable_debugger (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "port", NULL }; - unsigned short int port = 0; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|H", keywords, &port)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_enable_debugger_sync (PY_GOBJECT_HANDLE (self), port, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyScript_disable_debugger (PyScript * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_script_disable_debugger_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static int -PyRelay_init (PyRelay * self, PyObject * args, PyObject * kw) -{ - int result = -1; - static char * keywords[] = { "address", "username", "password", "kind", NULL }; - char * address = NULL; - char * username = NULL; - char * password = NULL; - char * kind_value = NULL; - FridaRelayKind kind; - FridaRelay * handle; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "eseseses", keywords, - "utf-8", &address, - "utf-8", &username, - "utf-8", &password, - "utf-8", &kind_value)) - return -1; - - if (!PyGObject_unmarshal_enum (kind_value, FRIDA_TYPE_RELAY_KIND, &kind)) - goto beach; - - handle = frida_relay_new (address, username, password, kind); - - PyGObject_take_handle (&self->parent, handle, PYFRIDA_TYPE (Relay)); - - PyRelay_init_from_handle (self, handle); - - result = 0; - -beach: - PyMem_Free (kind_value); - PyMem_Free (password); - PyMem_Free (username); - PyMem_Free (address); - - return result; -} - -static void -PyRelay_init_from_handle (PyRelay * self, FridaRelay * handle) -{ - self->address = PyUnicode_FromString (frida_relay_get_address (handle)); - self->username = PyUnicode_FromString (frida_relay_get_username (handle)); - self->password = PyUnicode_FromString (frida_relay_get_password (handle)); - self->kind = PyGObject_marshal_enum (frida_relay_get_kind (handle), FRIDA_TYPE_RELAY_KIND); -} - -static void -PyRelay_dealloc (PyRelay * self) -{ - Py_DecRef (self->kind); - Py_DecRef (self->password); - Py_DecRef (self->username); - Py_DecRef (self->address); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyRelay_repr (PyRelay * self) -{ - PyObject * result, * address_bytes, * username_bytes, * password_bytes, * kind_bytes; - - address_bytes = PyUnicode_AsUTF8String (self->address); - username_bytes = PyUnicode_AsUTF8String (self->username); - password_bytes = PyUnicode_AsUTF8String (self->password); - kind_bytes = PyUnicode_AsUTF8String (self->kind); - - result = PyUnicode_FromFormat ("Relay(address=\"%s\", username=\"%s\", password=\"%s\", kind='%s')", - PyBytes_AsString (address_bytes), - PyBytes_AsString (username_bytes), - PyBytes_AsString (password_bytes), - PyBytes_AsString (kind_bytes)); - - Py_DecRef (kind_bytes); - Py_DecRef (password_bytes); - Py_DecRef (username_bytes); - Py_DecRef (address_bytes); - - return result; -} - - -static PyObject * -PyPortalMembership_new_take_handle (FridaPortalMembership * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (PortalMembership)); -} - -static PyObject * -PyPortalMembership_terminate (PyPortalMembership * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_membership_terminate_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static int -PyPortalService_init (PyPortalService * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "cluster_params", "control_params", NULL }; - PyEndpointParameters * cluster_params; - PyEndpointParameters * control_params = NULL; - FridaPortalService * handle; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "O!|O!", keywords, - PYFRIDA_TYPE_OBJECT (EndpointParameters), &cluster_params, - PYFRIDA_TYPE_OBJECT (EndpointParameters), &control_params)) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - handle = frida_portal_service_new (PY_GOBJECT_HANDLE (cluster_params), - (control_params != NULL) ? PY_GOBJECT_HANDLE (control_params) : NULL); - - PyGObject_take_handle (&self->parent, handle, PYFRIDA_TYPE (PortalService)); - - PyPortalService_init_from_handle (self, handle); - - return 0; -} - -static void -PyPortalService_init_from_handle (PyPortalService * self, FridaPortalService * handle) -{ - self->device = PyDevice_new_take_handle (g_object_ref (frida_portal_service_get_device (handle))); -} - -static void -PyPortalService_dealloc (PyPortalService * self) -{ - FridaPortalService * handle; - - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - handle = PyGObject_steal_handle (&self->parent); - if (handle != NULL) - { - Py_BEGIN_ALLOW_THREADS - frida_portal_service_stop_sync (handle, NULL, NULL); - frida_unref (handle); - Py_END_ALLOW_THREADS - } - - Py_DecRef (self->device); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPortalService_start (PyPortalService * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_start_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_stop (PyPortalService * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_stop_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_kick (PyScript * self, PyObject * args) -{ - unsigned int connection_id; - - if (!PyArg_ParseTuple (args, "I", &connection_id)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_kick (PY_GOBJECT_HANDLE (self), connection_id); - Py_END_ALLOW_THREADS - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_post (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "connection_id", "message", "data", NULL }; - unsigned int connection_id; - char * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "Ies|z#", keywords, - &connection_id, - "utf-8", &message, - &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_post (PY_GOBJECT_HANDLE (self), connection_id, message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_narrowcast (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "tag", "message", "data", NULL }; - char * tag, * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "eses|z#", keywords, - "utf-8", &tag, - "utf-8", &message, - &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_narrowcast (PY_GOBJECT_HANDLE (self), tag, message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - PyMem_Free (tag); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_broadcast (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "message", "data", NULL }; - char * message; - gconstpointer data_buffer = NULL; - Py_ssize_t data_size = 0; - GBytes * data; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "es|z#", keywords, - "utf-8", &message, - &data_buffer, &data_size)) - return NULL; - - data = (data_buffer != NULL) ? g_bytes_new (data_buffer, data_size) : NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_broadcast (PY_GOBJECT_HANDLE (self), message, data); - Py_END_ALLOW_THREADS - - g_bytes_unref (data); - PyMem_Free (message); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_enumerate_tags (PyScript * self, PyObject * args) -{ - PyObject * result; - unsigned int connection_id; - gchar ** tags; - gint tags_length; - - if (!PyArg_ParseTuple (args, "I", &connection_id)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - tags = frida_portal_service_enumerate_tags (PY_GOBJECT_HANDLE (self), connection_id, &tags_length); - Py_END_ALLOW_THREADS - - result = PyGObject_marshal_strv (tags, tags_length); - g_strfreev (tags); - - return result; -} - -static PyObject * -PyPortalService_tag (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "connection_id", "tag", NULL }; - unsigned int connection_id; - char * tag; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "Ies", keywords, - &connection_id, - "utf-8", &tag)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_tag (PY_GOBJECT_HANDLE (self), connection_id, tag); - Py_END_ALLOW_THREADS - - PyMem_Free (tag); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyPortalService_untag (PyScript * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "connection_id", "tag", NULL }; - unsigned int connection_id; - char * tag; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "Ies", keywords, - &connection_id, - "utf-8", &tag)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - frida_portal_service_untag (PY_GOBJECT_HANDLE (self), connection_id, tag); - Py_END_ALLOW_THREADS - - PyMem_Free (tag); - - PyFrida_RETURN_NONE; -} - - -static int -PyEndpointParameters_init (PyEndpointParameters * self, PyObject * args, PyObject * kw) -{ - int result = -1; - static char * keywords[] = { "address", "port", "certificate", "origin", "auth_token", "auth_callback", "asset_root", NULL }; - char * address = NULL; - unsigned short int port = 0; - char * certificate_value = NULL; - char * origin = NULL; - char * auth_token = NULL; - PyObject * auth_callback = NULL; - char * asset_root_value = NULL; - GTlsCertificate * certificate = NULL; - FridaAuthenticationService * auth_service = NULL; - GFile * asset_root = NULL; - FridaEndpointParameters * handle; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|esHesesesOes", keywords, - "utf-8", &address, - &port, - "utf-8", &certificate_value, - "utf-8", &origin, - "utf-8", &auth_token, - &auth_callback, - "utf-8", &asset_root_value)) - return -1; - - if (certificate_value != NULL && !PyGObject_unmarshal_certificate (certificate_value, &certificate)) - goto beach; - - if (auth_token != NULL) - auth_service = FRIDA_AUTHENTICATION_SERVICE (frida_static_authentication_service_new (auth_token)); - else if (auth_callback != NULL) - auth_service = FRIDA_AUTHENTICATION_SERVICE (frida_python_authentication_service_new (auth_callback)); - - if (asset_root_value != NULL) - asset_root = g_file_new_for_path (asset_root_value); - - handle = frida_endpoint_parameters_new (address, port, certificate, origin, auth_service, asset_root); - - PyGObject_take_handle (&self->parent, handle, PYFRIDA_TYPE (EndpointParameters)); - - result = 0; - -beach: - g_clear_object (&asset_root); - g_clear_object (&auth_service); - g_clear_object (&certificate); - - PyMem_Free (asset_root_value); - PyMem_Free (auth_token); - PyMem_Free (origin); - PyMem_Free (certificate_value); - PyMem_Free (address); - - return result; -} - - -G_DEFINE_TYPE_EXTENDED (FridaPythonAuthenticationService, frida_python_authentication_service, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (FRIDA_TYPE_AUTHENTICATION_SERVICE, frida_python_authentication_service_iface_init)) - -static FridaPythonAuthenticationService * -frida_python_authentication_service_new (PyObject * callback) -{ - FridaPythonAuthenticationService * service; - - service = g_object_new (FRIDA_TYPE_PYTHON_AUTHENTICATION_SERVICE, NULL); - service->callback = callback; - Py_IncRef (callback); - - return service; -} - -static void -frida_python_authentication_service_class_init (FridaPythonAuthenticationServiceClass * klass) -{ - GObjectClass * object_class = G_OBJECT_CLASS (klass); - - object_class->dispose = frida_python_authentication_service_dispose; -} - -static void -frida_python_authentication_service_iface_init (gpointer g_iface, gpointer iface_data) -{ - FridaAuthenticationServiceIface * iface = g_iface; - - iface->authenticate = frida_python_authentication_service_authenticate; - iface->authenticate_finish = frida_python_authentication_service_authenticate_finish; -} - -static void -frida_python_authentication_service_init (FridaPythonAuthenticationService * self) -{ - self->pool = g_thread_pool_new ((GFunc) frida_python_authentication_service_do_authenticate, self, 1, FALSE, NULL); -} - -static void -frida_python_authentication_service_dispose (GObject * object) -{ - FridaPythonAuthenticationService * self = FRIDA_PYTHON_AUTHENTICATION_SERVICE (object); - - if (self->pool != NULL) - { - g_thread_pool_free (self->pool, FALSE, FALSE); - self->pool = NULL; - } - - if (self->callback != NULL) - { - PyGILState_STATE gstate; - - gstate = PyGILState_Ensure (); - - Py_DecRef (self->callback); - self->callback = NULL; - - PyGILState_Release (gstate); - } - - G_OBJECT_CLASS (frida_python_authentication_service_parent_class)->dispose (object); -} - -static void -frida_python_authentication_service_authenticate (FridaAuthenticationService * service, const gchar * token, GCancellable * cancellable, - GAsyncReadyCallback callback, gpointer user_data) -{ - FridaPythonAuthenticationService * self; - GTask * task; - - self = FRIDA_PYTHON_AUTHENTICATION_SERVICE (service); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_task_data (task, g_strdup (token), g_free); - - g_thread_pool_push (self->pool, task, NULL); -} - -static gchar * -frida_python_authentication_service_authenticate_finish (FridaAuthenticationService * service, GAsyncResult * result, GError ** error) -{ - return g_task_propagate_pointer (G_TASK (result), error); -} - -static void -frida_python_authentication_service_do_authenticate (GTask * task, FridaPythonAuthenticationService * self) -{ - const gchar * token; - PyGILState_STATE gstate; - PyObject * result; - gchar * session_info = NULL; - gchar * message = NULL; - - token = g_task_get_task_data (task); - - gstate = PyGILState_Ensure (); - - result = PyObject_CallFunction (self->callback, "s", token); - if (result == NULL || !PyGObject_unmarshal_string (result, &session_info)) - { - PyObject * type, * value, * traceback; - - PyErr_Fetch (&type, &value, &traceback); - - if (value != NULL) - { - PyObject * message_value = PyObject_Str (value); - PyGObject_unmarshal_string (message_value, &message); - Py_DecRef (message_value); - } - else - { - message = g_strdup ("Internal error"); - } - - Py_DecRef (type); - Py_DecRef (value); - Py_DecRef (traceback); - } - - Py_DecRef (result); - - PyGILState_Release (gstate); - - if (session_info != NULL) - g_task_return_pointer (task, session_info, g_free); - else - g_task_return_new_error (task, FRIDA_ERROR, FRIDA_ERROR_INVALID_ARGUMENT, "%s", message); - - g_free (message); - g_object_unref (task); -} - - -static int -PyCompiler_init (PyCompiler * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - PyGObject_take_handle (&self->parent, frida_compiler_new (NULL), PYFRIDA_TYPE (Compiler)); - - return 0; -} - -static void -PyCompiler_dealloc (PyCompiler * self) -{ - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyCompiler_build (PyCompiler * self, PyObject * args, PyObject * kw) -{ - PyObject * result; - static char * keywords[] = { "entrypoint", "project_root", "output_format", "bundle_format", "type_check", "source_maps", "compression", - "platform", "externals", NULL }; - const char * entrypoint; - const char * project_root = NULL; - const char * output_format = NULL; - const char * bundle_format = NULL; - const char * type_check = NULL; - const char * source_maps = NULL; - const char * compression = NULL; - const char * platform = NULL; - PyObject * externals = NULL; - FridaBuildOptions * options; - GError * error = NULL; - gchar * bundle; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "s|sssssssO", keywords, &entrypoint, &project_root, &output_format, &bundle_format, - &type_check, &source_maps, &compression, &platform, &externals)) - return NULL; - - options = frida_build_options_new (); - if (!PyCompiler_set_options (FRIDA_COMPILER_OPTIONS (options), project_root, output_format, bundle_format, type_check, source_maps, - compression, platform, externals)) - goto invalid_option_value; - - Py_BEGIN_ALLOW_THREADS - bundle = frida_compiler_build_sync (PY_GOBJECT_HANDLE (self), entrypoint, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - result = PyUnicode_FromString (bundle); - g_free (bundle); - - return result; - -invalid_option_value: - { - g_object_unref (options); - return NULL; - } -} - -static PyObject * -PyCompiler_watch (PyCompiler * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "entrypoint", "project_root", "output_format", "bundle_format", "type_check", "source_maps", "compression", - "platform", "externals", NULL }; - const char * entrypoint; - const char * project_root = NULL; - const char * output_format = NULL; - const char * bundle_format = NULL; - const char * type_check = NULL; - const char * source_maps = NULL; - const char * compression = NULL; - const char * platform = NULL; - PyObject * externals = NULL; - FridaWatchOptions * options; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "s|sssssssO", keywords, &entrypoint, &project_root, &output_format, &bundle_format, - &type_check, &source_maps, &compression, &platform, &externals)) - return NULL; - - options = frida_watch_options_new (); - if (!PyCompiler_set_options (FRIDA_COMPILER_OPTIONS (options), project_root, output_format, bundle_format, type_check, source_maps, - compression, platform, externals)) - goto invalid_option_value; - - Py_BEGIN_ALLOW_THREADS - frida_compiler_watch_sync (PY_GOBJECT_HANDLE (self), entrypoint, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; - -invalid_option_value: - { - g_object_unref (options); - return NULL; - } -} - -static gboolean -PyCompiler_set_options (FridaCompilerOptions * options, const gchar * project_root_value, const gchar * output_format_value, - const gchar * bundle_format_value, const gchar * type_check_value, const gchar * source_maps_value, const gchar * compression_value, - const gchar * platform_value, PyObject * externals_value) -{ - if (project_root_value != NULL) - frida_compiler_options_set_project_root (options, project_root_value); - - if (output_format_value != NULL) - { - FridaOutputFormat output_format; - - if (!PyGObject_unmarshal_enum (output_format_value, FRIDA_TYPE_OUTPUT_FORMAT, &output_format)) - return FALSE; - - frida_compiler_options_set_output_format (options, output_format); - } - - if (bundle_format_value != NULL) - { - FridaBundleFormat bundle_format; - - if (!PyGObject_unmarshal_enum (bundle_format_value, FRIDA_TYPE_BUNDLE_FORMAT, &bundle_format)) - return FALSE; - - frida_compiler_options_set_bundle_format (options, bundle_format); - } - - if (type_check_value != NULL) - { - FridaTypeCheckMode type_check; - - if (!PyGObject_unmarshal_enum (type_check_value, FRIDA_TYPE_TYPE_CHECK_MODE, &type_check)) - return FALSE; - - frida_compiler_options_set_type_check (options, type_check); - } - - if (source_maps_value != NULL) - { - FridaSourceMaps source_maps; - - if (!PyGObject_unmarshal_enum (source_maps_value, FRIDA_TYPE_SOURCE_MAPS, &source_maps)) - return FALSE; - - frida_compiler_options_set_source_maps (options, source_maps); - } - - if (compression_value != NULL) - { - FridaJsCompression compression; - - if (!PyGObject_unmarshal_enum (compression_value, FRIDA_TYPE_JS_COMPRESSION, &compression)) - return FALSE; - - frida_compiler_options_set_compression (options, compression); - } - - if (platform_value != NULL) - { - FridaJsPlatform platform; - - if (!PyGObject_unmarshal_enum (platform_value, FRIDA_TYPE_JS_PLATFORM, &platform)) - return FALSE; - - frida_compiler_options_set_platform (options, platform); - } - - if (externals_value != NULL) - { - gint n, i; - - n = PySequence_Size (externals_value); - if (n == -1) - return FALSE; - - for (i = 0; i != n; i++) - { - PyObject * element; - gchar * external = NULL; - - element = PySequence_GetItem (externals_value, i); - if (element == NULL) - return FALSE; - PyGObject_unmarshal_string (element, &external); - Py_DecRef (element); - if (external == NULL) - return FALSE; - - frida_compiler_options_add_external (options, external); - - g_free (external); - } - } - - return TRUE; -} - - -static int -PyPackageManager_init (PyPackageManager * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - PyGObject_take_handle (&self->parent, frida_package_manager_new (), PYFRIDA_TYPE (PackageManager)); - - return 0; -} - -static void -PyPackageManager_dealloc (PyPackageManager * self) -{ - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPackageManager_repr (PyPackageManager * self) -{ - PyObject * result; - gchar * repr; - - repr = g_strdup_printf ("PackageManager(registry=\"%s\")", frida_package_manager_get_registry (PY_GOBJECT_HANDLE (self))); - result = PyUnicode_FromString (repr); - g_free (repr); - - return result; -} - -static PyObject * -PyPackageManager_get_registry (PyPackageManager * self, void * closure) -{ - return PyUnicode_FromString (frida_package_manager_get_registry (PY_GOBJECT_HANDLE (self))); -} - -static int -PyPackageManager_set_registry (PyPackageManager * self, PyObject * val, void * closure) -{ - gchar * registry; - - if (!PyGObject_unmarshal_string (val, ®istry)) - return -1; - frida_package_manager_set_registry (PY_GOBJECT_HANDLE (self), registry); - g_free (registry); - - return 0; -} - -static PyObject * -PyPackageManager_search (PyPackageManager * self, PyObject * args, PyObject * kw) -{ - FridaPackageSearchResult * result; - static char * keywords[] = { "query", "offset", "limit", NULL }; - const char * query; - guint offset = G_MAXUINT; - guint limit = G_MAXUINT; - FridaPackageSearchOptions * options; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "s|II", keywords, &query, &offset, &limit)) - return NULL; - - options = frida_package_search_options_new (); - - if (offset != G_MAXUINT) - frida_package_search_options_set_offset (options, offset); - - if (limit != G_MAXUINT) - frida_package_search_options_set_limit (options, limit); - - Py_BEGIN_ALLOW_THREADS - result = frida_package_manager_search_sync (PY_GOBJECT_HANDLE (self), query, options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - return PyPackageSearchResult_new_take_handle (result); -} - -static PyObject * -PyPackageManager_install (PyPackageManager * self, PyObject * args, PyObject * kw) -{ - FridaPackageInstallResult * result; - static char * keywords[] = { "project_root", "role", "specs", "omits", NULL }; - const char * project_root = NULL; - const char * role_value = NULL; - PyObject * specs = NULL; - PyObject * omits = NULL; - FridaPackageInstallOptions * options; - GError * error = NULL; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|ssOO", keywords, &project_root, &role_value, &specs, &omits)) - return NULL; - - options = PyPackageManager_parse_install_options (project_root, role_value, specs, omits); - - Py_BEGIN_ALLOW_THREADS - result = frida_package_manager_install_sync (PY_GOBJECT_HANDLE (self), options, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - g_object_unref (options); - - if (error != NULL) - return PyFrida_raise (error); - - return PyPackageInstallResult_new_take_handle (result); -} - -static FridaPackageInstallOptions * -PyPackageManager_parse_install_options (const gchar * project_root, const char * role_value, PyObject * specs_value, PyObject * omits_value) -{ - FridaPackageInstallOptions * options; - - options = frida_package_install_options_new (); - - if (project_root != NULL) - frida_package_install_options_set_project_root (options, project_root); - - if (role_value != NULL) - { - FridaPackageRole role; - - if (!PyGObject_unmarshal_enum (role_value, FRIDA_TYPE_PACKAGE_ROLE, &role)) - goto propagate_error; - - frida_package_install_options_set_role (options, role); - } - - if (specs_value != NULL) - { - gint n, i; - - n = PySequence_Size (specs_value); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - PyObject * element; - gchar * spec = NULL; - - element = PySequence_GetItem (specs_value, i); - if (element == NULL) - goto propagate_error; - PyGObject_unmarshal_string (element, &spec); - Py_DecRef (element); - if (spec == NULL) - goto propagate_error; - - frida_package_install_options_add_spec (options, spec); - - g_free (spec); - } - } - - if (omits_value != NULL) - { - gint n, i; - - n = PySequence_Size (omits_value); - if (n == -1) - goto propagate_error; - - for (i = 0; i != n; i++) - { - PyObject * element; - gchar * str = NULL; - FridaPackageRole role; - - element = PySequence_GetItem (omits_value, i); - if (element == NULL) - goto propagate_error; - PyGObject_unmarshal_string (element, &str); - Py_DecRef (element); - if (str == NULL) - goto propagate_error; - - if (!PyGObject_unmarshal_enum (str, FRIDA_TYPE_PACKAGE_ROLE, &role)) - goto propagate_error; - - frida_package_install_options_add_omit (options, role); - } - } - - return options; - -propagate_error: - { - g_object_unref (options); - - return NULL; - } -} - - -static PyObject * -PyPackage_new_take_handle (FridaPackage * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (Package)); -} - -static int -PyPackage_init (PyPackage * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->name = NULL; - self->version = NULL; - self->description = NULL; - self->url = NULL; - - return 0; -} - -static void -PyPackage_init_from_handle (PyPackage * self, FridaPackage * handle) -{ - self->name = PyUnicode_FromString (frida_package_get_name (handle)); - self->version = PyUnicode_FromString (frida_package_get_version (handle)); - self->description = PyGObject_marshal_string (frida_package_get_description (handle)); - self->url = PyGObject_marshal_string (frida_package_get_url (handle)); -} - -static void -PyPackage_dealloc (PyPackage * self) -{ - Py_DecRef (self->url); - Py_DecRef (self->description); - Py_DecRef (self->version); - Py_DecRef (self->name); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPackage_repr (PyPackage * self) -{ - PyObject * result; - FridaPackage * handle; - GString * repr; - const gchar * description, * url; - - handle = PY_GOBJECT_HANDLE (self); - - repr = g_string_sized_new (256); - - g_string_append_printf (repr, "Package(name=\"%s\", version=\"%s\"", - frida_package_get_name (handle), - frida_package_get_version (handle)); - - description = frida_package_get_description (handle); - if (description != NULL) - { - gchar * escaped = g_strescape (description, NULL); - g_string_append_printf (repr, ", description=\"%s\"", escaped); - g_free (escaped); - } - - url = frida_package_get_url (handle); - if (url != NULL) - g_string_append_printf (repr, ", url=\"%s\"", url); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static PyObject * -PyPackageList_marshal (FridaPackageList * list) -{ - PyObject * result; - gint n, i; - - n = frida_package_list_size (list); - result = PyList_New (n); - for (i = 0; i != n; i++) - PyList_SetItem (result, i, PyPackage_new_take_handle (frida_package_list_get (list, i))); - - return result; -} - - -static PyObject * -PyPackageSearchResult_new_take_handle (FridaPackageSearchResult * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (PackageSearchResult)); -} - -static int -PyPackageSearchResult_init (PyPackageSearchResult * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->packages = NULL; - self->total = 0; - - return 0; -} - -static void -PyPackageSearchResult_init_from_handle (PyPackageSearchResult * self, FridaPackageSearchResult * handle) -{ - self->packages = PyPackageList_marshal (frida_package_search_result_get_packages (handle)); - self->total = frida_package_search_result_get_total (handle); -} - -static void -PyPackageSearchResult_dealloc (PyPackageSearchResult * self) -{ - Py_DecRef (self->packages); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPackageSearchResult_repr (PyPackageSearchResult * self) -{ - PyObject * result; - GString * repr; - gint num_packages; - - repr = g_string_new ("PackageSearchResult(packages="); - - num_packages = frida_package_list_size (frida_package_search_result_get_packages (PY_GOBJECT_HANDLE (self))); - if (num_packages != 0) - g_string_append_printf (repr, "[<%u package%s>]", num_packages, (num_packages == 1) ? "" : "s"); - else - g_string_append (repr, "[]"); - - g_string_append_printf (repr, ", total=%u)", self->total); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static PyObject * -PyPackageInstallResult_new_take_handle (FridaPackageInstallResult * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (PackageInstallResult)); -} - -static int -PyPackageInstallResult_init (PyPackageInstallResult * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->packages = NULL; - - return 0; -} - -static void -PyPackageInstallResult_init_from_handle (PyPackageInstallResult * self, FridaPackageInstallResult * handle) -{ - self->packages = PyPackageList_marshal (frida_package_install_result_get_packages (handle)); -} - -static void -PyPackageInstallResult_dealloc (PyPackageInstallResult * self) -{ - Py_DecRef (self->packages); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyPackageInstallResult_repr (PyPackageInstallResult * self) -{ - PyObject * result; - GString * repr; - gint num_packages; - - repr = g_string_new ("PackageInstallResult(packages="); - - num_packages = frida_package_list_size (frida_package_install_result_get_packages (PY_GOBJECT_HANDLE (self))); - if (num_packages != 0) - g_string_append_printf (repr, "[<%u package%s>]", num_packages, (num_packages == 1) ? "" : "s"); - else - g_string_append (repr, "[]"); - - g_string_append (repr, ")"); - - result = PyUnicode_FromString (repr->str); - - g_string_free (repr, TRUE); - - return result; -} - - -static int -PyFileMonitor_init (PyFileMonitor * self, PyObject * args, PyObject * kw) -{ - const char * path; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTuple (args, "s", &path)) - return -1; - - g_atomic_int_inc (&toplevel_objects_alive); - - PyGObject_take_handle (&self->parent, frida_file_monitor_new (path), PYFRIDA_TYPE (FileMonitor)); - - return 0; -} - -static void -PyFileMonitor_dealloc (PyFileMonitor * self) -{ - g_atomic_int_dec_and_test (&toplevel_objects_alive); - - PyGObject_tp_dealloc ((PyObject *) self); -} - -static PyObject * -PyFileMonitor_enable (PyFileMonitor * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_file_monitor_enable_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyFileMonitor_disable (PyFileMonitor * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - frida_file_monitor_disable_sync (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static PyObject * -PyIOStream_new_take_handle (GIOStream * handle) -{ - return PyGObject_new_take_handle (handle, PYFRIDA_TYPE (IOStream)); -} - -static int -PyIOStream_init (PyIOStream * self, PyObject * args, PyObject * kw) -{ - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - self->input = NULL; - self->output = NULL; - - return 0; -} - -static void -PyIOStream_init_from_handle (PyIOStream * self, GIOStream * handle) -{ - self->input = g_io_stream_get_input_stream (handle); - self->output = g_io_stream_get_output_stream (handle); -} - -static PyObject * -PyIOStream_repr (PyIOStream * self) -{ - GIOStream * handle = PY_GOBJECT_HANDLE (self); - - return PyUnicode_FromFormat ("IOStream(handle=%p, is_closed=%s)", - handle, - g_io_stream_is_closed (handle) ? "TRUE" : "FALSE"); -} - -static PyObject * -PyIOStream_is_closed (PyIOStream * self) -{ - return PyBool_FromLong (g_io_stream_is_closed (PY_GOBJECT_HANDLE (self))); -} - -static PyObject * -PyIOStream_close (PyIOStream * self) -{ - GError * error = NULL; - - Py_BEGIN_ALLOW_THREADS - g_io_stream_close (PY_GOBJECT_HANDLE (self), g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyIOStream_read (PyIOStream * self, PyObject * args) -{ - PyObject * result; - unsigned long count; - PyObject * buffer; - GError * error = NULL; - gssize bytes_read; - - if (!PyArg_ParseTuple (args, "k", &count)) - return NULL; - - buffer = PyBytes_FromStringAndSize (NULL, count); - if (buffer == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - bytes_read = g_input_stream_read (self->input, PyBytes_AsString (buffer), count, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error == NULL) - { - if ((unsigned long) bytes_read == count) - { - result = buffer; - } - else - { - result = PyBytes_FromStringAndSize (PyBytes_AsString (buffer), bytes_read); - - Py_DecRef (buffer); - } - } - else - { - result = PyFrida_raise (error); - - Py_DecRef (buffer); - } - - return result; -} - -static PyObject * -PyIOStream_read_all (PyIOStream * self, PyObject * args) -{ - PyObject * result; - unsigned long count; - PyObject * buffer; - gsize bytes_read; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "k", &count)) - return NULL; - - buffer = PyBytes_FromStringAndSize (NULL, count); - if (buffer == NULL) - return NULL; - - Py_BEGIN_ALLOW_THREADS - g_input_stream_read_all (self->input, PyBytes_AsString (buffer), count, &bytes_read, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error == NULL) - { - result = buffer; - } - else - { - result = PyFrida_raise (error); - - Py_DecRef (buffer); - } - - return result; -} - -static PyObject * -PyIOStream_write (PyIOStream * self, PyObject * args) -{ - const char * data; - Py_ssize_t size; - GError * error = NULL; - gssize bytes_written; - - if (!PyArg_ParseTuple (args, "y#", &data, &size)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - bytes_written = g_output_stream_write (self->output, data, size, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error != NULL) - return PyFrida_raise (error); - - return PyLong_FromSsize_t (bytes_written); -} - -static PyObject * -PyIOStream_write_all (PyIOStream * self, PyObject * args) -{ - const char * data; - Py_ssize_t size; - GError * error = NULL; - - if (!PyArg_ParseTuple (args, "y#", &data, &size)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - g_output_stream_write_all (self->output, data, size, NULL, g_cancellable_get_current (), &error); - Py_END_ALLOW_THREADS - - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - - -static PyObject * -PyCancellable_new_take_handle (GCancellable * handle) -{ - PyObject * object; - - object = (handle != NULL) ? PyGObject_try_get_from_handle (handle) : NULL; - if (object == NULL) - { - object = PyObject_CallFunction (PYFRIDA_TYPE_OBJECT (Cancellable), "z#", (char *) &handle, (Py_ssize_t) sizeof (handle)); - } - else - { - g_object_unref (handle); - Py_IncRef (object); - } - - return object; -} - -static int -PyCancellable_init (PyCancellable * self, PyObject * args, PyObject * kw) -{ - static char * keywords[] = { "handle", NULL }; - GCancellable ** handle_buffer = NULL; - Py_ssize_t handle_size = 0; - GCancellable * handle; - - if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) - return -1; - - if (!PyArg_ParseTupleAndKeywords (args, kw, "|z#", keywords, &handle_buffer, &handle_size)) - return -1; - - if (handle_size == sizeof (gpointer)) - handle = *handle_buffer; - else - handle = g_cancellable_new (); - - PyGObject_take_handle (&self->parent, handle, PYFRIDA_TYPE (Cancellable)); - - return 0; -} - -static PyObject * -PyCancellable_repr (PyCancellable * self) -{ - GCancellable * handle = PY_GOBJECT_HANDLE (self); - - return PyUnicode_FromFormat ("Cancellable(handle=%p, is_cancelled=%s)", - handle, - g_cancellable_is_cancelled (handle) ? "TRUE" : "FALSE"); -} - -static PyObject * -PyCancellable_is_cancelled (PyCancellable * self) -{ - return PyBool_FromLong (g_cancellable_is_cancelled (PY_GOBJECT_HANDLE (self))); -} - -static PyObject * -PyCancellable_raise_if_cancelled (PyCancellable * self) -{ - GError * error = NULL; - - g_cancellable_set_error_if_cancelled (PY_GOBJECT_HANDLE (self), &error); - if (error != NULL) - return PyFrida_raise (error); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyCancellable_get_fd (PyCancellable * self) -{ - return PyLong_FromLong (g_cancellable_get_fd (PY_GOBJECT_HANDLE (self))); -} - -static PyObject * -PyCancellable_release_fd (PyCancellable * self) -{ - g_cancellable_release_fd (PY_GOBJECT_HANDLE (self)); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyCancellable_get_current (PyCancellable * self) -{ - GCancellable * handle; - - handle = g_cancellable_get_current (); - - if (handle != NULL) - g_object_ref (handle); - - return PyCancellable_new_take_handle (handle); -} - -static PyObject * -PyCancellable_push_current (PyCancellable * self) -{ - g_cancellable_push_current (PY_GOBJECT_HANDLE (self)); - - PyFrida_RETURN_NONE; -} - -static PyObject * -PyCancellable_pop_current (PyCancellable * self) -{ - GCancellable * handle = PY_GOBJECT_HANDLE (self); - - if (g_cancellable_get_current () != handle) - goto invalid_operation; - - g_cancellable_pop_current (handle); - - PyFrida_RETURN_NONE; - -invalid_operation: - { - return PyFrida_raise (g_error_new ( - FRIDA_ERROR, - FRIDA_ERROR_INVALID_OPERATION, - "Cancellable is not on top of the stack")); - } -} - -static PyObject * -PyCancellable_connect (PyCancellable * self, PyObject * args) -{ - GCancellable * handle = PY_GOBJECT_HANDLE (self); - gulong handler_id; - PyObject * callback; - - if (!PyArg_ParseTuple (args, "O", &callback)) - return NULL; - - if (!PyCallable_Check (callback)) - goto not_callable; - - if (handle != NULL) - { - Py_IncRef (callback); - - Py_BEGIN_ALLOW_THREADS - handler_id = g_cancellable_connect (handle, G_CALLBACK (PyCancellable_on_cancelled), callback, - (GDestroyNotify) PyCancellable_destroy_callback); - Py_END_ALLOW_THREADS - } - else - { - handler_id = 0; - } - - return PyLong_FromUnsignedLong (handler_id); - -not_callable: - { - PyErr_SetString (PyExc_TypeError, "object must be callable"); - return NULL; - } -} - -static PyObject * -PyCancellable_disconnect (PyCancellable * self, PyObject * args) -{ - gulong handler_id; - - if (!PyArg_ParseTuple (args, "k", &handler_id)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - g_cancellable_disconnect (PY_GOBJECT_HANDLE (self), handler_id); - Py_END_ALLOW_THREADS - - PyFrida_RETURN_NONE; -} - -static void -PyCancellable_on_cancelled (GCancellable * cancellable, PyObject * callback) -{ - PyGILState_STATE gstate; - PyObject * result; - - gstate = PyGILState_Ensure (); - - result = PyObject_CallObject (callback, NULL); - if (result != NULL) - Py_DecRef (result); - else - PyErr_Print (); - - PyGILState_Release (gstate); -} - -static void -PyCancellable_destroy_callback (PyObject * callback) -{ - PyGILState_STATE gstate; - - gstate = PyGILState_Ensure (); - Py_DecRef (callback); - PyGILState_Release (gstate); -} - -static PyObject * -PyCancellable_cancel (PyCancellable * self) -{ - Py_BEGIN_ALLOW_THREADS - g_cancellable_cancel (PY_GOBJECT_HANDLE (self)); - Py_END_ALLOW_THREADS - - PyFrida_RETURN_NONE; -} - - -static void -PyFrida_object_decref (gpointer obj) -{ - PyObject * o = obj; - Py_DecRef (o); -} - -static PyObject * -PyFrida_raise (GError * error) -{ - PyObject * exception; - GString * message; - - if (error->domain == FRIDA_ERROR) - { - exception = g_hash_table_lookup (frida_exception_by_error_code, GINT_TO_POINTER (error->code)); - g_assert (exception != NULL); - } - else - { - g_assert (error->domain == G_IO_ERROR); - g_assert (error->code == G_IO_ERROR_CANCELLED); - exception = cancelled_exception; - } - - message = g_string_new (""); - g_string_append_unichar (message, g_unichar_tolower (g_utf8_get_char (error->message))); - g_string_append (message, g_utf8_offset_to_pointer (error->message, 1)); - - PyErr_SetString (exception, message->str); - - g_string_free (message, TRUE); - g_error_free (error); - - return NULL; -} - -static gchar * -PyFrida_repr (PyObject * obj) -{ - gchar * result; - PyObject * repr_value; - - repr_value = PyObject_Repr (obj); - - PyGObject_unmarshal_string (repr_value, &result); - - Py_DecRef (repr_value); - - return result; -} - -static guint -PyFrida_get_max_argument_count (PyObject * callable) -{ - guint result = G_MAXUINT; - PyObject * spec; - PyObject * varargs = NULL; - PyObject * args = NULL; - PyObject * is_method; - - spec = PyObject_CallFunction (inspect_getargspec, "O", callable); - if (spec == NULL) - { - PyErr_Clear (); - goto beach; - } - - varargs = PyTuple_GetItem (spec, 1); - if (varargs != Py_None) - goto beach; - - args = PyTuple_GetItem (spec, 0); - - result = PyObject_Size (args); - - is_method = PyObject_CallFunction (inspect_ismethod, "O", callable); - g_assert (is_method != NULL); - if (is_method == Py_True) - result--; - Py_DecRef (is_method); - -beach: - Py_DecRef (spec); - - return result; -} - - -PyMODINIT_FUNC -PyInit__frida (void) -{ - PyObject * inspect, * datetime, * module; - - inspect = PyImport_ImportModule ("inspect"); - inspect_getargspec = PyObject_GetAttrString (inspect, "getfullargspec"); - inspect_ismethod = PyObject_GetAttrString (inspect, "ismethod"); - Py_DecRef (inspect); - - datetime = PyImport_ImportModule ("datetime"); - datetime_constructor = PyObject_GetAttrString (datetime, "datetime"); - Py_DecRef (datetime); - - frida_init (); - - PyGObject_class_init (); - - module = PyModule_Create (&PyFrida_moduledef); - - PyModule_AddStringConstant (module, "__version__", frida_version_string ()); - - PYFRIDA_REGISTER_TYPE (GObject, G_TYPE_OBJECT); - PyGObject_tp_init = PyType_GetSlot ((PyTypeObject *) PYFRIDA_TYPE_OBJECT (GObject), Py_tp_init); - PyGObject_tp_dealloc = PyType_GetSlot ((PyTypeObject *) PYFRIDA_TYPE_OBJECT (GObject), Py_tp_dealloc); - - PYFRIDA_REGISTER_TYPE (DeviceManager, FRIDA_TYPE_DEVICE_MANAGER); - PYFRIDA_REGISTER_TYPE (Device, FRIDA_TYPE_DEVICE); - PYFRIDA_REGISTER_TYPE (Application, FRIDA_TYPE_APPLICATION); - PYFRIDA_REGISTER_TYPE (Process, FRIDA_TYPE_PROCESS); - PYFRIDA_REGISTER_TYPE (Spawn, FRIDA_TYPE_SPAWN); - PYFRIDA_REGISTER_TYPE (Child, FRIDA_TYPE_CHILD); - PYFRIDA_REGISTER_TYPE (Crash, FRIDA_TYPE_CRASH); - PYFRIDA_REGISTER_TYPE (Bus, FRIDA_TYPE_BUS); - PYFRIDA_REGISTER_TYPE (Service, FRIDA_TYPE_SERVICE); - PYFRIDA_REGISTER_TYPE (Session, FRIDA_TYPE_SESSION); - PYFRIDA_REGISTER_TYPE (Script, FRIDA_TYPE_SCRIPT); - PYFRIDA_REGISTER_TYPE (Relay, FRIDA_TYPE_RELAY); - PYFRIDA_REGISTER_TYPE (PortalMembership, FRIDA_TYPE_PORTAL_MEMBERSHIP); - PYFRIDA_REGISTER_TYPE (PortalService, FRIDA_TYPE_PORTAL_SERVICE); - PYFRIDA_REGISTER_TYPE (EndpointParameters, FRIDA_TYPE_ENDPOINT_PARAMETERS); - PYFRIDA_REGISTER_TYPE (Compiler, FRIDA_TYPE_COMPILER); - PYFRIDA_REGISTER_TYPE (PackageManager, FRIDA_TYPE_PACKAGE_MANAGER); - PYFRIDA_REGISTER_TYPE (Package, FRIDA_TYPE_PACKAGE); - PYFRIDA_REGISTER_TYPE (PackageSearchResult, FRIDA_TYPE_PACKAGE_SEARCH_RESULT); - PYFRIDA_REGISTER_TYPE (PackageInstallResult, FRIDA_TYPE_PACKAGE_INSTALL_RESULT); - PYFRIDA_REGISTER_TYPE (FileMonitor, FRIDA_TYPE_FILE_MONITOR); - PYFRIDA_REGISTER_TYPE (IOStream, G_TYPE_IO_STREAM); - PYFRIDA_REGISTER_TYPE (Cancellable, G_TYPE_CANCELLABLE); - - frida_exception_by_error_code = g_hash_table_new_full (NULL, NULL, NULL, PyFrida_object_decref); -#define PYFRIDA_DECLARE_EXCEPTION(code, name) \ - do \ - { \ - PyObject * exception = PyErr_NewException ("frida." name "Error", NULL, NULL); \ - g_hash_table_insert (frida_exception_by_error_code, GINT_TO_POINTER (G_PASTE (FRIDA_ERROR_, code)), exception); \ - Py_IncRef (exception); \ - PyModule_AddObject (module, name "Error", exception); \ - } while (FALSE) - PYFRIDA_DECLARE_EXCEPTION (SERVER_NOT_RUNNING, "ServerNotRunning"); - PYFRIDA_DECLARE_EXCEPTION (EXECUTABLE_NOT_FOUND, "ExecutableNotFound"); - PYFRIDA_DECLARE_EXCEPTION (EXECUTABLE_NOT_SUPPORTED, "ExecutableNotSupported"); - PYFRIDA_DECLARE_EXCEPTION (PROCESS_NOT_FOUND, "ProcessNotFound"); - PYFRIDA_DECLARE_EXCEPTION (PROCESS_NOT_RESPONDING, "ProcessNotResponding"); - PYFRIDA_DECLARE_EXCEPTION (INVALID_ARGUMENT, "InvalidArgument"); - PYFRIDA_DECLARE_EXCEPTION (INVALID_OPERATION, "InvalidOperation"); - PYFRIDA_DECLARE_EXCEPTION (PERMISSION_DENIED, "PermissionDenied"); - PYFRIDA_DECLARE_EXCEPTION (ADDRESS_IN_USE, "AddressInUse"); - PYFRIDA_DECLARE_EXCEPTION (TIMED_OUT, "TimedOut"); - PYFRIDA_DECLARE_EXCEPTION (NOT_SUPPORTED, "NotSupported"); - PYFRIDA_DECLARE_EXCEPTION (PROTOCOL, "Protocol"); - PYFRIDA_DECLARE_EXCEPTION (TRANSPORT, "Transport"); - - cancelled_exception = PyErr_NewException ("frida.OperationCancelledError", NULL, NULL); - Py_IncRef (cancelled_exception); - PyModule_AddObject (module, "OperationCancelledError", cancelled_exception); - - return module; -} diff --git a/frida/_frida/meson.build b/frida/_frida/meson.build deleted file mode 100644 index 21b02d4..0000000 --- a/frida/_frida/meson.build +++ /dev/null @@ -1,21 +0,0 @@ -py_sources = [ - '__init__.pyi', - 'py.typed', -] -python.install_sources(py_sources, subdir: 'frida' / '_frida', pure: false) - -extra_link_args = [] -if host_os_family == 'darwin' - extra_link_args += '-Wl,-exported_symbol,_PyInit__frida' -elif host_os_family != 'windows' - extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / 'extension.version' -endif - -extension = python.extension_module('_frida', 'extension.c', - limited_api: '3.7', - c_args: frida_component_cflags, - link_args: extra_link_args, - dependencies: [python_dep, frida_core_dep, os_deps], - install: true, - subdir: 'frida', -) diff --git a/frida/core.py b/frida/core.py deleted file mode 100644 index 026c572..0000000 --- a/frida/core.py +++ /dev/null @@ -1,1861 +0,0 @@ -from __future__ import annotations - -import asyncio -import dataclasses -import fnmatch -import functools -import json -import sys -import threading -import traceback -import warnings -from types import TracebackType -from typing import ( - Any, - AnyStr, - Awaitable, - Callable, - Dict, - List, - Mapping, - MutableMapping, - Optional, - Sequence, - Tuple, - Type, - TypeVar, - Union, - overload, -) - -if sys.version_info >= (3, 8): - from typing import Literal, TypedDict -else: - from typing_extensions import Literal, TypedDict - -if sys.version_info >= (3, 10): - from typing import ParamSpec -else: - from typing_extensions import ParamSpec - -if sys.version_info >= (3, 11): - from typing import NotRequired, cast -else: - from typing_extensions import NotRequired, cast - -from . import _frida - -_device_manager = None - -_Cancellable = _frida.Cancellable - -ProcessTarget = Union[int, str] -Spawn = _frida.Spawn - - -@dataclasses.dataclass -class RPCResult: - finished: bool = False - value: Any = None - error: Optional[Exception] = None - - -def get_device_manager() -> "DeviceManager": - """ - Get or create a singleton DeviceManager that let you manage all the devices - """ - - global _device_manager - if _device_manager is None: - _device_manager = DeviceManager(_frida.DeviceManager()) - return _device_manager - - -def _filter_missing_kwargs(d: MutableMapping[Any, Any]) -> None: - for key in list(d.keys()): - if d[key] is None: - d.pop(key) - - -P = ParamSpec("P") -R = TypeVar("R", covariant=True) - - -def cancellable(f: Callable[P, R]) -> Callable[P, R]: - # currently there is no way to type properly the extended callable with optional cancellable parameter - # ref: https://github.com/python/typing/discussions/1905#discussioncomment-11696995 - @functools.wraps(f) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - cancellable = kwargs.pop("cancellable", None) - if cancellable is not None: - with cast(Cancellable, cancellable): - return f(*args, **kwargs) - - return f(*args, **kwargs) - - return wrapper - - -class IOStream: - """ - Frida's own implementation of an input/output stream - """ - - def __init__(self, impl: _frida.IOStream) -> None: - self._impl = impl - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_closed(self) -> bool: - """ - Query whether the stream is closed - """ - - return self._impl.is_closed() - - @cancellable - def close(self) -> None: - """ - Close the stream. - """ - - self._impl.close() - - @cancellable - def read(self, count: int) -> bytes: - """ - Read up to the specified number of bytes from the stream - """ - - return self._impl.read(count) - - @cancellable - def read_all(self, count: int) -> bytes: - """ - Read exactly the specified number of bytes from the stream - """ - - return self._impl.read_all(count) - - @cancellable - def write(self, data: bytes) -> int: - """ - Write as much as possible of the provided data to the stream - """ - - return self._impl.write(data) - - @cancellable - def write_all(self, data: bytes) -> None: - """ - Write all of the provided data to the stream - """ - - self._impl.write_all(data) - - -class PortalMembership: - def __init__(self, impl: _frida.PortalMembership) -> None: - self._impl = impl - - @cancellable - def terminate(self) -> None: - """ - Terminate the membership - """ - - self._impl.terminate() - - -class ScriptExportsSync: - """ - Proxy object that expose all the RPC exports of a script as attributes on this class - - A method named exampleMethod in a script will be called with instance.example_method on this object - """ - - def __init__(self, script: "Script") -> None: - self._script = script - - def __getattr__(self, name: str) -> Callable[..., Any]: - script = self._script - js_name = _to_camel_case(name) - - def method(*args: Any, **kwargs: Any) -> Any: - request, data = make_rpc_call_request(js_name, args) - return script._rpc_request(request, data, **kwargs) - - return method - - def __dir__(self) -> List[str]: - return self._script.list_exports_sync() - - -ScriptExports = ScriptExportsSync - - -class ScriptExportsAsync: - """ - Proxy object that expose all the RPC exports of a script as attributes on this class - - A method named exampleMethod in a script will be called with instance.example_method on this object - """ - - def __init__(self, script: "Script") -> None: - self._script = script - - def __getattr__(self, name: str) -> Callable[..., Awaitable[Any]]: - script = self._script - js_name = _to_camel_case(name) - - async def method(*args: Any, **kwargs: Any) -> Any: - request, data = make_rpc_call_request(js_name, args) - return await script._rpc_request_async(request, data, **kwargs) - - return method - - def __dir__(self) -> List[str]: - return self._script.list_exports_sync() - - -def make_rpc_call_request(js_name: str, args: Sequence[Any]) -> Tuple[List[Any], Optional[bytes]]: - if args and isinstance(args[-1], bytes): - raw_args = args[:-1] - data = args[-1] - else: - raw_args = args - data = None - return (["call", js_name, raw_args], data) - - -class ScriptErrorMessage(TypedDict): - type: Literal["error"] - description: str - stack: NotRequired[str] - fileName: NotRequired[str] - lineNumber: NotRequired[int] - columnNumber: NotRequired[int] - - -class ScriptPayloadMessage(TypedDict): - type: Literal["send"] - payload: NotRequired[Any] - - -ScriptMessage = Union[ScriptPayloadMessage, ScriptErrorMessage] -ScriptMessageCallback = Callable[[ScriptMessage, Optional[bytes]], None] -ScriptDestroyedCallback = Callable[[], None] - - -class RPCException(Exception): - """ - Wraps remote errors from the script RPC - """ - - def __str__(self) -> str: - return str(self.args[2]) if len(self.args) >= 3 else str(self.args[0]) - - -class Script: - def __init__(self, impl: _frida.Script) -> None: - self.exports_sync = ScriptExportsSync(self) - self.exports_async = ScriptExportsAsync(self) - - self._impl = impl - - self._on_message_callbacks: List[ScriptMessageCallback] = [] - self._log_handler: Callable[[str, str], None] = self.default_log_handler - - self._pending: Dict[ - int, Callable[[Optional[Any], Optional[Union[RPCException, _frida.InvalidOperationError]]], None] - ] = {} - self._next_request_id = 1 - self._cond = threading.Condition() - - impl.on("destroyed", self._on_destroyed) - impl.on("message", self._on_message) - - @property - def exports(self) -> ScriptExportsSync: - """ - The old way of retrieving the synchronous exports caller - """ - - warnings.warn( - "Script.exports will become asynchronous in the future, use the explicit Script.exports_sync instead", - DeprecationWarning, - stacklevel=2, - ) - return self.exports_sync - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_destroyed(self) -> bool: - """ - Query whether the script has been destroyed - """ - - return self._impl.is_destroyed() - - @cancellable - def load(self) -> None: - """ - Load the script. - """ - - self._impl.load() - - @cancellable - def interrupt(self) -> None: - """ - Interrupt any JavaScript currently executing in the script, leaving it - loaded and able to run again - """ - - self._impl.interrupt() - - @cancellable - def unload(self) -> None: - """ - Unload the script - """ - - self._impl.unload() - - @cancellable - def terminate(self) -> None: - """ - Interrupt execution and unload the script, even if it is stuck in a - long-running or infinite operation - """ - - self._impl.terminate() - - @cancellable - def eternalize(self) -> None: - """ - Eternalize the script - """ - - self._impl.eternalize() - - def post(self, message: Any, data: Optional[AnyStr] = None) -> None: - """ - Post a JSON-encoded message to the script - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.post(raw_message, **kwargs) - - @cancellable - def enable_debugger(self, port: Optional[int] = None) -> None: - """ - Enable the Node.js compatible script debugger - """ - - kwargs = {"port": port} - _filter_missing_kwargs(kwargs) - self._impl.enable_debugger(**kwargs) - - @cancellable - def disable_debugger(self) -> None: - """ - Disable the Node.js compatible script debugger - """ - - self._impl.disable_debugger() - - @overload - def on(self, signal: Literal["destroyed"], callback: ScriptDestroyedCallback) -> None: ... - - @overload - def on(self, signal: Literal["message"], callback: ScriptMessageCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - if signal == "message": - self._on_message_callbacks.append(callback) - else: - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["destroyed"], callback: ScriptDestroyedCallback) -> None: ... - - @overload - def off(self, signal: Literal["message"], callback: ScriptMessageCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - if signal == "message": - self._on_message_callbacks.remove(callback) - else: - self._impl.off(signal, callback) - - def get_log_handler(self) -> Callable[[str, str], None]: - """ - Get the method that handles the script logs - """ - - return self._log_handler - - def set_log_handler(self, handler: Callable[[str, str], None]) -> None: - """ - Set the method that handles the script logs - :param handler: a callable that accepts two parameters: - 1. the log level name - 2. the log message - """ - - self._log_handler = handler - - def default_log_handler(self, level: str, text: str) -> None: - """ - The default implementation of the log handler, prints the message to stdout - or stderr, depending on the level - """ - - if level == "info": - print(text, file=sys.stdout) - else: - print(text, file=sys.stderr) - - async def list_exports_async(self) -> List[str]: - """ - Asynchronously list all the exported attributes from the script's rpc - """ - - result = await self._rpc_request_async(["list"]) - assert isinstance(result, list) - return result - - def list_exports_sync(self) -> List[str]: - """ - List all the exported attributes from the script's rpc - """ - - result = self._rpc_request(["list"]) - assert isinstance(result, list) - return result - - def list_exports(self) -> List[str]: - """ - List all the exported attributes from the script's rpc - """ - - warnings.warn( - "Script.list_exports will become asynchronous in the future, use the explicit Script.list_exports_sync instead", - DeprecationWarning, - stacklevel=2, - ) - return self.list_exports_sync() - - def _rpc_request_async(self, args: Any, data: Optional[bytes] = None) -> asyncio.Future[Any]: - loop = asyncio.get_event_loop() - future: asyncio.Future[Any] = asyncio.Future() - - def on_complete(value: Any, error: Optional[Union[RPCException, _frida.InvalidOperationError]]) -> None: - if error is not None: - loop.call_soon_threadsafe(future.set_exception, error) - else: - loop.call_soon_threadsafe(future.set_result, value) - - request_id = self._append_pending(on_complete) - - if not self.is_destroyed: - self._send_rpc_call(request_id, args, data) - else: - self._on_destroyed() - - return future - - @cancellable - def _rpc_request(self, args: Any, data: Optional[bytes] = None) -> Any: - result = RPCResult() - - def on_complete(value: Any, error: Optional[Union[RPCException, _frida.InvalidOperationError]]) -> None: - with self._cond: - result.finished = True - result.value = value - result.error = error - self._cond.notify_all() - - def on_cancelled() -> None: - self._pending.pop(request_id, None) - on_complete(None, None) - - request_id = self._append_pending(on_complete) - - if not self.is_destroyed: - self._send_rpc_call(request_id, args, data) - - cancellable = Cancellable.get_current() - cancel_handler = cancellable.connect(on_cancelled) - try: - with self._cond: - while not result.finished: - self._cond.wait() - finally: - cancellable.disconnect(cancel_handler) - - cancellable.raise_if_cancelled() - else: - self._on_destroyed() - - if result.error is not None: - raise result.error - - return result.value - - def _append_pending( - self, callback: Callable[[Any, Optional[Union[RPCException, _frida.InvalidOperationError]]], None] - ) -> int: - with self._cond: - request_id = self._next_request_id - self._next_request_id += 1 - self._pending[request_id] = callback - return request_id - - def _send_rpc_call(self, request_id: int, args: Any, data: Optional[bytes]) -> None: - self.post(["frida:rpc", request_id, *args], data) - - def _on_rpc_message(self, request_id: int, operation: str, params: List[Any], data: Optional[Any]) -> None: - if operation in ("ok", "error"): - callback = self._pending.pop(request_id, None) - if callback is None: - return - - value = None - error = None - if operation == "ok": - if data is not None: - value = (params[1], data) if len(params) > 1 else data - else: - value = params[0] - else: - error = RPCException(*params[0:3]) - - callback(value, error) - - def _on_destroyed(self) -> None: - while True: - next_pending = None - - with self._cond: - pending_ids = list(self._pending.keys()) - if len(pending_ids) > 0: - next_pending = self._pending.pop(pending_ids[0]) - - if next_pending is None: - break - - next_pending(None, _frida.InvalidOperationError("script has been destroyed")) - - def _on_message(self, raw_message: str, data: Optional[bytes]) -> None: - message = json.loads(raw_message) - - mtype = message["type"] - payload = message.get("payload", None) - if mtype == "log": - level = message["level"] - text = payload - self._log_handler(level, text) - elif mtype == "send" and isinstance(payload, list) and len(payload) > 0 and payload[0] == "frida:rpc": - request_id = payload[1] - operation = payload[2] - params = payload[3:] - self._on_rpc_message(request_id, operation, params, data) - else: - for callback in self._on_message_callbacks[:]: - try: - callback(message, data) - except: - traceback.print_exc() - - -SessionDetachedCallback = Callable[ - [ - Literal[ - "application-requested", "process-replaced", "process-terminated", "connection-terminated", "device-lost" - ], - Optional[_frida.Crash], - ], - None, -] - - -class Session: - def __init__(self, impl: _frida.Session) -> None: - self._impl = impl - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_detached(self) -> bool: - """ - Query whether the session is detached - """ - - return self._impl.is_detached() - - @cancellable - def detach(self) -> None: - """ - Detach session from the process - """ - - self._impl.detach() - - @cancellable - def resume(self) -> None: - """ - Resume session after network error - """ - - self._impl.resume() - - @cancellable - def enable_child_gating(self) -> None: - """ - Enable child gating - """ - - self._impl.enable_child_gating() - - @cancellable - def disable_child_gating(self) -> None: - """ - Disable child gating - """ - - self._impl.disable_child_gating() - - @cancellable - def create_script( - self, source: str, name: Optional[str] = None, snapshot: Optional[bytes] = None, runtime: Optional[str] = None - ) -> Script: - """ - Create a new script - """ - - kwargs = {"name": name, "snapshot": snapshot, "runtime": runtime} - _filter_missing_kwargs(kwargs) - return Script(self._impl.create_script(source, **kwargs)) # type: ignore - - @cancellable - def create_script_from_bytes( - self, data: bytes, name: Optional[str] = None, snapshot: Optional[bytes] = None, runtime: Optional[str] = None - ) -> Script: - """ - Create a new script from bytecode - """ - - kwargs = {"name": name, "snapshot": snapshot, "runtime": runtime} - _filter_missing_kwargs(kwargs) - return Script(self._impl.create_script_from_bytes(data, **kwargs)) # type: ignore - - @cancellable - def compile_script(self, source: str, name: Optional[str] = None, runtime: Optional[str] = None) -> bytes: - """ - Compile script source code to bytecode - """ - - kwargs = {"name": name, "runtime": runtime} - _filter_missing_kwargs(kwargs) - return self._impl.compile_script(source, **kwargs) - - @cancellable - def snapshot_script(self, embed_script: str, warmup_script: Optional[str], runtime: Optional[str] = None) -> bytes: - """ - Evaluate script and snapshot the resulting VM state - """ - kwargs = {"warmup_script": warmup_script, "runtime": runtime} - _filter_missing_kwargs(kwargs) - return self._impl.snapshot_script(embed_script, **kwargs) - - @cancellable - def setup_peer_connection( - self, stun_server: Optional[str] = None, relays: Optional[Sequence[_frida.Relay]] = None - ) -> None: - """ - Set up a peer connection with the target process - """ - - kwargs = {"stun_server": stun_server, "relays": relays} - _filter_missing_kwargs(kwargs) - self._impl.setup_peer_connection(**kwargs) # type: ignore - - @cancellable - def join_portal( - self, - address: str, - certificate: Optional[str] = None, - token: Optional[str] = None, - acl: Union[None, List[str], Tuple[str]] = None, - ) -> PortalMembership: - """ - Join a portal - """ - - kwargs: Dict[str, Any] = {"certificate": certificate, "token": token, "acl": acl} - _filter_missing_kwargs(kwargs) - return PortalMembership(self._impl.join_portal(address, **kwargs)) - - def on( - self, - signal: Literal["detached"], - callback: SessionDetachedCallback, - ) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - def off( - self, - signal: Literal["detached"], - callback: SessionDetachedCallback, - ) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - -BusDetachedCallback = Callable[[], None] -BusMessageCallback = Callable[[Mapping[Any, Any], Optional[bytes]], None] - - -class Bus: - def __init__(self, impl: _frida.Bus) -> None: - self._impl = impl - self._on_message_callbacks: List[Callable[..., Any]] = [] - - impl.on("message", self._on_message) - - @cancellable - def attach(self) -> None: - """ - Attach to the bus - """ - - self._impl.attach() - - def post(self, message: Any, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a JSON-encoded message to the bus - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.post(raw_message, **kwargs) - - @overload - def on(self, signal: Literal["detached"], callback: BusDetachedCallback) -> None: ... - - @overload - def on(self, signal: Literal["message"], callback: BusMessageCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - if signal == "message": - self._on_message_callbacks.append(callback) - else: - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["detached"], callback: BusDetachedCallback) -> None: ... - - @overload - def off(self, signal: Literal["message"], callback: BusMessageCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - if signal == "message": - self._on_message_callbacks.remove(callback) - else: - self._impl.off(signal, callback) - - def _on_message(self, raw_message: str, data: Any) -> None: - message = json.loads(raw_message) - - for callback in self._on_message_callbacks[:]: - try: - callback(message, data) - except: - traceback.print_exc() - - -ServiceCloseCallback = Callable[[], None] -ServiceMessageCallback = Callable[[Any], None] - - -class Service: - def __init__(self, impl: _frida.Service) -> None: - self._impl = impl - - @cancellable - def activate(self) -> None: - """ - Activate the service - """ - - self._impl.activate() - - @cancellable - def cancel(self) -> None: - """ - Cancel the service - """ - - self._impl.cancel() - - def request(self, parameters: Any) -> Any: - """ - Perform a request - """ - - return self._impl.request(parameters) - - @overload - def on(self, signal: Literal["close"], callback: ServiceCloseCallback) -> None: ... - - @overload - def on(self, signal: Literal["message"], callback: ServiceMessageCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["close"], callback: ServiceCloseCallback) -> None: ... - - @overload - def off(self, signal: Literal["message"], callback: ServiceMessageCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - -DeviceSpawnAddedCallback = Callable[[_frida.Spawn], None] -DeviceSpawnRemovedCallback = Callable[[_frida.Spawn], None] -DeviceChildAddedCallback = Callable[[_frida.Child], None] -DeviceChildRemovedCallback = Callable[[_frida.Child], None] -DeviceProcessCrashedCallback = Callable[[_frida.Crash], None] -DeviceOutputCallback = Callable[[int, int, bytes], None] -DeviceUninjectedCallback = Callable[[int], None] -DeviceLostCallback = Callable[[], None] - - -class Device: - """ - Represents a device that Frida connects to - """ - - def __init__(self, device: _frida.Device) -> None: - assert device.bus is not None - self.id = device.id - self.name = device.name - self.icon = device.icon - self.type = device.type - self.bus = Bus(device.bus) - - self._impl = device - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_lost(self) -> bool: - """ - Query whether the device has been lost - """ - - return self._impl.is_lost() - - @cancellable - def override_option(self, name: str, value: Any) -> None: - """ - Override a backend-specific option - """ - - self._impl.override_option(name, value) - - @cancellable - def query_system_parameters(self) -> Dict[str, Any]: - """ - Returns a dictionary of information about the host system - """ - - return self._impl.query_system_parameters() - - @cancellable - def get_frontmost_application(self, scope: Optional[str] = None) -> Optional[_frida.Application]: - """ - Get details about the frontmost application - """ - - kwargs = {"scope": scope} - _filter_missing_kwargs(kwargs) - return self._impl.get_frontmost_application(**kwargs) - - @cancellable - def enumerate_applications( - self, identifiers: Optional[Sequence[str]] = None, scope: Optional[str] = None - ) -> List[_frida.Application]: - """ - Enumerate applications - """ - - kwargs = {"identifiers": identifiers, "scope": scope} - _filter_missing_kwargs(kwargs) - return self._impl.enumerate_applications(**kwargs) # type: ignore - - @cancellable - def enumerate_processes( - self, pids: Optional[Sequence[int]] = None, scope: Optional[str] = None - ) -> List[_frida.Process]: - """ - Enumerate processes - """ - - kwargs = {"pids": pids, "scope": scope} - _filter_missing_kwargs(kwargs) - return self._impl.enumerate_processes(**kwargs) # type: ignore - - @cancellable - def get_process(self, process_name: str) -> _frida.Process: - """ - Get the process with the given name - :raises ProcessNotFoundError: if the process was not found or there were more than one process with the given name - """ - - process_name_lc = process_name.lower() - matching = [ - process - for process in self._impl.enumerate_processes() - if fnmatch.fnmatchcase(process.name.lower(), process_name_lc) - ] - if len(matching) == 1: - return matching[0] - elif len(matching) > 1: - matches_list = ", ".join([f"{process.name} (pid: {process.pid})" for process in matching]) - raise _frida.ProcessNotFoundError(f"ambiguous name; it matches: {matches_list}") - else: - raise _frida.ProcessNotFoundError(f"unable to find process with name '{process_name}'") - - @cancellable - def enable_spawn_gating(self) -> None: - """ - Enable spawn gating - """ - - self._impl.enable_spawn_gating() - - @cancellable - def disable_spawn_gating(self) -> None: - """ - Disable spawn gating - """ - - self._impl.disable_spawn_gating() - - @cancellable - def enumerate_pending_spawn(self) -> List[_frida.Spawn]: - """ - Enumerate pending spawn - """ - - return self._impl.enumerate_pending_spawn() - - @cancellable - def enumerate_pending_children(self) -> List[_frida.Child]: - """ - Enumerate pending children - """ - - return self._impl.enumerate_pending_children() - - @cancellable - def spawn( - self, - program: Union[str, List[Union[str, bytes]], Tuple[Union[str, bytes]]], - argv: Union[None, List[Union[str, bytes]], Tuple[Union[str, bytes]]] = None, - envp: Optional[Dict[str, str]] = None, - env: Optional[Dict[str, str]] = None, - cwd: Optional[str] = None, - stdio: Optional[str] = None, - **kwargs: Any, - ) -> int: - """ - Spawn a process into an attachable state - """ - - if not isinstance(program, str): - argv = program - if isinstance(argv[0], bytes): - program = argv[0].decode() - else: - program = argv[0] - if len(argv) == 1: - argv = None - - kwargs = {"argv": argv, "envp": envp, "env": env, "cwd": cwd, "stdio": stdio, "aux": kwargs} - _filter_missing_kwargs(kwargs) - return self._impl.spawn(program, **kwargs) - - @cancellable - def input(self, target: ProcessTarget, data: bytes) -> None: - """ - Input data on stdin of a spawned process - :param target: the PID or name of the process - """ - - self._impl.input(self._pid_of(target), data) - - @cancellable - def resume(self, target: ProcessTarget) -> None: - """ - Resume a process from the attachable state - :param target: the PID or name of the process - """ - - self._impl.resume(self._pid_of(target)) - - @cancellable - def kill(self, target: ProcessTarget) -> None: - """ - Kill a process - :param target: the PID or name of the process - """ - self._impl.kill(self._pid_of(target)) - - @cancellable - def attach( - self, - target: ProcessTarget, - realm: Optional[str] = None, - persist_timeout: Optional[int] = None, - exceptor: Optional[str] = None, - unwind_broker: Optional[bool] = None, - exit_monitor: Optional[bool] = None, - thread_suspend_monitor: Optional[bool] = None, - linker_notifier_offsets: Optional[Sequence[int]] = None, - ) -> Session: - """ - Attach to a process - :param target: the PID or name of the process - """ - - kwargs = { - "realm": realm, - "persist_timeout": persist_timeout, - "exceptor": exceptor, - "unwind_broker": unwind_broker, - "exit_monitor": exit_monitor, - "thread_suspend_monitor": thread_suspend_monitor, - "linker_notifier_offsets": linker_notifier_offsets, - } - _filter_missing_kwargs(kwargs) - return Session(self._impl.attach(self._pid_of(target), **kwargs)) # type: ignore - - @cancellable - def inject_library_file(self, target: ProcessTarget, path: str, entrypoint: str, data: str) -> int: - """ - Inject a library file to a process - :param target: the PID or name of the process - """ - - return self._impl.inject_library_file(self._pid_of(target), path, entrypoint, data) - - @cancellable - def inject_library_blob(self, target: ProcessTarget, blob: bytes, entrypoint: str, data: str) -> int: - """ - Inject a library blob to a process - :param target: the PID or name of the process - """ - - return self._impl.inject_library_blob(self._pid_of(target), blob, entrypoint, data) - - @cancellable - def open_channel(self, address: str) -> IOStream: - """ - Open a device-specific communication channel - """ - - return IOStream(self._impl.open_channel(address)) - - @cancellable - def open_service(self, address: str) -> Service: - """ - Open a device-specific service - """ - - return Service(self._impl.open_service(address)) - - @cancellable - def unpair(self) -> None: - """ - Unpair device - """ - - self._impl.unpair() - - @cancellable - def get_bus(self) -> Bus: - """ - Get the message bus of the device - """ - - return self.bus - - @overload - def on(self, signal: Literal["spawn-added"], callback: DeviceSpawnAddedCallback) -> None: ... - - @overload - def on(self, signal: Literal["spawn-removed"], callback: DeviceSpawnRemovedCallback) -> None: ... - - @overload - def on(self, signal: Literal["child-added"], callback: DeviceChildAddedCallback) -> None: ... - - @overload - def on(self, signal: Literal["child-removed"], callback: DeviceChildRemovedCallback) -> None: ... - - @overload - def on(self, signal: Literal["process-crashed"], callback: DeviceProcessCrashedCallback) -> None: ... - - @overload - def on(self, signal: Literal["output"], callback: DeviceOutputCallback) -> None: ... - - @overload - def on(self, signal: Literal["uninjected"], callback: DeviceUninjectedCallback) -> None: ... - - @overload - def on(self, signal: Literal["lost"], callback: DeviceLostCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["spawn-added"], callback: DeviceSpawnAddedCallback) -> None: ... - - @overload - def off(self, signal: Literal["spawn-removed"], callback: DeviceSpawnRemovedCallback) -> None: ... - - @overload - def off(self, signal: Literal["child-added"], callback: DeviceChildAddedCallback) -> None: ... - - @overload - def off(self, signal: Literal["child-removed"], callback: DeviceChildRemovedCallback) -> None: ... - - @overload - def off(self, signal: Literal["process-crashed"], callback: DeviceProcessCrashedCallback) -> None: ... - - @overload - def off(self, signal: Literal["output"], callback: DeviceOutputCallback) -> None: ... - - @overload - def off(self, signal: Literal["uninjected"], callback: DeviceUninjectedCallback) -> None: ... - - @overload - def off(self, signal: Literal["lost"], callback: DeviceLostCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - def _pid_of(self, target: ProcessTarget) -> int: - if isinstance(target, str): - return self.get_process(target).pid - else: - return target - - -DeviceManagerAddedCallback = Callable[[_frida.Device], None] -DeviceManagerRemovedCallback = Callable[[_frida.Device], None] -DeviceManagerChangedCallback = Callable[[], None] - - -class DeviceManager: - def __init__(self, impl: _frida.DeviceManager) -> None: - self._impl = impl - - def __repr__(self) -> str: - return repr(self._impl) - - def get_local_device(self) -> Device: - """ - Get the local device - """ - - return self.get_device_matching(lambda d: d.type == "local", timeout=0) - - def get_remote_device(self) -> Device: - """ - Get the first remote device in the devices list - """ - - return self.get_device_matching(lambda d: d.type == "remote", timeout=0) - - def get_usb_device(self, timeout: int = 0) -> Device: - """ - Get the first device connected over USB in the devices list - """ - - return self.get_device_matching(lambda d: d.type == "usb", timeout) - - def get_device(self, id: Optional[str], timeout: int = 0) -> Device: - """ - Get a device by its id - """ - - return self.get_device_matching(lambda d: d.id == id, timeout) - - @cancellable - def get_device_matching(self, predicate: Callable[[Device], bool], timeout: int = 0) -> Device: - """ - Get device matching predicate - :param predicate: a function to filter the devices - :param timeout: operation timeout in seconds - """ - - if timeout < 0: - raw_timeout = -1 - elif timeout == 0: - raw_timeout = 0 - else: - raw_timeout = int(timeout * 1000.0) - return Device(self._impl.get_device_matching(lambda d: predicate(Device(d)), raw_timeout)) - - @cancellable - def enumerate_devices(self) -> List[Device]: - """ - Enumerate devices - """ - - return [Device(device) for device in self._impl.enumerate_devices()] - - @cancellable - def add_remote_device( - self, - address: str, - certificate: Optional[str] = None, - origin: Optional[str] = None, - token: Optional[str] = None, - keepalive_interval: Optional[int] = None, - ) -> Device: - """ - Add a remote device - """ - - kwargs: Dict[str, Any] = { - "certificate": certificate, - "origin": origin, - "token": token, - "keepalive_interval": keepalive_interval, - } - _filter_missing_kwargs(kwargs) - return Device(self._impl.add_remote_device(address, **kwargs)) - - @cancellable - def remove_remote_device(self, address: str) -> None: - """ - Remove a remote device - """ - - self._impl.remove_remote_device(address=address) - - @overload - def on(self, signal: Literal["added"], callback: DeviceManagerAddedCallback) -> None: ... - - @overload - def on(self, signal: Literal["removed"], callback: DeviceManagerRemovedCallback) -> None: ... - - @overload - def on(self, signal: Literal["changed"], callback: DeviceManagerChangedCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["added"], callback: DeviceManagerAddedCallback) -> None: ... - - @overload - def off(self, signal: Literal["removed"], callback: DeviceManagerRemovedCallback) -> None: ... - - @overload - def off(self, signal: Literal["changed"], callback: DeviceManagerChangedCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - -class EndpointParameters: - def __init__( - self, - address: Optional[str] = None, - port: Optional[int] = None, - certificate: Optional[str] = None, - origin: Optional[str] = None, - authentication: Optional[Tuple[str, Union[str, Callable[[str], Any]]]] = None, - asset_root: Optional[str] = None, - ): - kwargs: Dict[str, Any] = {"address": address, "port": port, "certificate": certificate, "origin": origin} - if asset_root is not None: - kwargs["asset_root"] = str(asset_root) - _filter_missing_kwargs(kwargs) - - if authentication is not None: - (auth_scheme, auth_data) = authentication - if auth_scheme == "token": - kwargs["auth_token"] = auth_data - elif auth_scheme == "callback": - if not callable(auth_data): - raise ValueError( - "Authentication data must provide a Callable if the authentication scheme is callback" - ) - kwargs["auth_callback"] = make_auth_callback(auth_data) - else: - raise ValueError("invalid authentication scheme") - - self._impl = _frida.EndpointParameters(**kwargs) - - -PortalServiceNodeJoinedCallback = Callable[[int, _frida.Application], None] -PortalServiceNodeLeftCallback = Callable[[int, _frida.Application], None] -PortalServiceNodeConnectedCallback = Callable[[int, Tuple[str, int]], None] -PortalServiceNodeDisconnectedCallback = Callable[[int, Tuple[str, int]], None] -PortalServiceControllerConnectedCallback = Callable[[int, Tuple[str, int]], None] -PortalServiceControllerDisconnectedCallback = Callable[[int, Tuple[str, int]], None] -PortalServiceAuthenticatedCallback = Callable[[int, Mapping[Any, Any]], None] -PortalServiceSubscribeCallback = Callable[[int], None] -PortalServiceMessageCallback = Callable[[int, Mapping[Any, Any], Optional[bytes]], None] - - -class PortalService: - def __init__( - self, - cluster_params: EndpointParameters = EndpointParameters(), - control_params: Optional[EndpointParameters] = None, - ) -> None: - args = [cluster_params._impl] - if control_params is not None: - args.append(control_params._impl) - impl = _frida.PortalService(*args) - - self.device = impl.device - self._impl = impl - self._on_authenticated_callbacks: List[PortalServiceAuthenticatedCallback] = [] - self._on_message_callbacks: List[PortalServiceMessageCallback] = [] - - impl.on("authenticated", self._on_authenticated) - impl.on("message", self._on_message) - - @cancellable - def start(self) -> None: - """ - Start listening for incoming connections - :raises InvalidOperationError: if the service isn't stopped - :raises AddressInUseError: if the given address is already in use - """ - - self._impl.start() - - @cancellable - def stop(self) -> None: - """ - Stop listening for incoming connections, and kick any connected clients - :raises InvalidOperationError: if the service is already stopped - """ - - self._impl.stop() - - def post(self, connection_id: int, message: Any, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a message to a specific control channel. - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.post(connection_id, raw_message, **kwargs) - - def narrowcast(self, tag: str, message: Any, data: Optional[Union[str, bytes]] = None) -> None: - """ - Post a message to control channels with a specific tag - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.narrowcast(tag, raw_message, **kwargs) - - def broadcast(self, message: Any, data: Optional[Union[str, bytes]] = None) -> None: - """ - Broadcast a message to all control channels - """ - - raw_message = json.dumps(message) - kwargs = {"data": data} - _filter_missing_kwargs(kwargs) - self._impl.broadcast(raw_message, **kwargs) - - def enumerate_tags(self, connection_id: int) -> List[str]: - """ - Enumerate tags of a specific connection - """ - - return self._impl.enumerate_tags(connection_id) - - def tag(self, connection_id: int, tag: str) -> None: - """ - Tag a specific control channel - """ - - self._impl.tag(connection_id, tag) - - def untag(self, connection_id: int, tag: str) -> None: - """ - Untag a specific control channel - """ - - self._impl.untag(connection_id, tag) - - @overload - def on(self, signal: Literal["node-joined"], callback: PortalServiceNodeJoinedCallback) -> None: ... - - @overload - def on(self, signal: Literal["node-left"], callback: PortalServiceNodeLeftCallback) -> None: ... - - @overload - def on( - self, signal: Literal["controller-connected"], callback: PortalServiceControllerConnectedCallback - ) -> None: ... - - @overload - def on( - self, signal: Literal["controller-disconnected"], callback: PortalServiceControllerDisconnectedCallback - ) -> None: ... - - @overload - def on(self, signal: Literal["node-connected"], callback: PortalServiceNodeConnectedCallback) -> None: ... - - @overload - def on(self, signal: Literal["node-disconnected"], callback: PortalServiceNodeDisconnectedCallback) -> None: ... - - @overload - def on(self, signal: Literal["authenticated"], callback: PortalServiceAuthenticatedCallback) -> None: ... - - @overload - def on(self, signal: Literal["subscribe"], callback: PortalServiceSubscribeCallback) -> None: ... - - @overload - def on(self, signal: Literal["message"], callback: PortalServiceMessageCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - if signal == "authenticated": - self._on_authenticated_callbacks.append(callback) - elif signal == "message": - self._on_message_callbacks.append(callback) - else: - self._impl.on(signal, callback) - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - if signal == "authenticated": - self._on_authenticated_callbacks.remove(callback) - elif signal == "message": - self._on_message_callbacks.remove(callback) - else: - self._impl.off(signal, callback) - - def _on_authenticated(self, connection_id: int, raw_session_info: str) -> None: - session_info = json.loads(raw_session_info) - - for callback in self._on_authenticated_callbacks[:]: - try: - callback(connection_id, session_info) - except: - traceback.print_exc() - - def _on_message(self, connection_id: int, raw_message: str, data: Optional[bytes]) -> None: - message = json.loads(raw_message) - - for callback in self._on_message_callbacks[:]: - try: - callback(connection_id, message, data) - except: - traceback.print_exc() - - -class CompilerDiagnosticFile(TypedDict): - path: str - line: int - character: int - - -class CompilerDiagnostic(TypedDict): - category: str - code: int - file: NotRequired[CompilerDiagnosticFile] - text: str - - -CompilerStartingCallback = Callable[[], None] -CompilerFinishedCallback = Callable[[], None] -CompilerOutputCallback = Callable[[str], None] -CompilerDiagnosticsCallback = Callable[[List[CompilerDiagnostic]], None] - -CompilerOutputFormat = Literal["unescaped", "hex-bytes", "c-string"] -CompilerBundleFormat = Literal["esm", "iife"] -CompilerTypeCheck = Literal["full", "none"] -CompilerSourceMaps = Literal["included", "omitted"] -CompilerCompression = Literal["none", "terser"] -CompilerPlatform = Literal["neutral", "gum", "browser"] - - -class Compiler: - def __init__(self) -> None: - self._impl = _frida.Compiler() - - def __repr__(self) -> str: - return repr(self._impl) - - @cancellable - def build( - self, - entrypoint: str, - project_root: Optional[str] = None, - output_format: Optional[CompilerOutputFormat] = None, - bundle_format: Optional[CompilerBundleFormat] = None, - type_check: Optional[CompilerTypeCheck] = None, - source_maps: Optional[CompilerSourceMaps] = None, - compression: Optional[CompilerCompression] = None, - platform: Optional[CompilerPlatform] = None, - externals: Optional[Sequence[str]] = None, - ) -> str: - kwargs: dict[str, Any] = { - "project_root": project_root, - "output_format": output_format, - "bundle_format": bundle_format, - "type_check": type_check, - "source_maps": source_maps, - "compression": compression, - "platform": platform, - "externals": externals, - } - _filter_missing_kwargs(kwargs) - return self._impl.build(entrypoint, **kwargs) - - @cancellable - def watch( - self, - entrypoint: str, - project_root: Optional[str] = None, - output_format: Optional[CompilerOutputFormat] = None, - bundle_format: Optional[CompilerBundleFormat] = None, - type_check: Optional[CompilerTypeCheck] = None, - source_maps: Optional[CompilerSourceMaps] = None, - compression: Optional[CompilerCompression] = None, - platform: Optional[CompilerPlatform] = None, - externals: Optional[Sequence[str]] = None, - ) -> None: - kwargs: dict[str, Any] = { - "project_root": project_root, - "output_format": output_format, - "bundle_format": bundle_format, - "type_check": type_check, - "source_maps": source_maps, - "compression": compression, - "platform": platform, - "externals": externals, - } - _filter_missing_kwargs(kwargs) - return self._impl.watch(entrypoint, **kwargs) - - @overload - def on(self, signal: Literal["starting"], callback: CompilerStartingCallback) -> None: ... - - @overload - def on(self, signal: Literal["finished"], callback: CompilerFinishedCallback) -> None: ... - - @overload - def on(self, signal: Literal["output"], callback: CompilerOutputCallback) -> None: ... - - @overload - def on(self, signal: Literal["diagnostics"], callback: CompilerDiagnosticsCallback) -> None: ... - - def on(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Add a signal handler - """ - - self._impl.on(signal, callback) - - @overload - def off(self, signal: Literal["starting"], callback: CompilerStartingCallback) -> None: ... - - @overload - def off(self, signal: Literal["finished"], callback: CompilerFinishedCallback) -> None: ... - - @overload - def off(self, signal: Literal["output"], callback: CompilerOutputCallback) -> None: ... - - @overload - def off(self, signal: Literal["diagnostics"], callback: CompilerDiagnosticsCallback) -> None: ... - - def off(self, signal: str, callback: Callable[..., Any]) -> None: - """ - Remove a signal handler - """ - - self._impl.off(signal, callback) - - -PackageManagerInstallProgressCallback = Callable[ - [ - Literal[ - "initializing", - "preparing-dependencies", - "resolving-package", - "fetching-resource", - "package-already-installed", - "downloading-package", - "package-installed", - "resolving-and-installing-all", - "complete", - ], - float, - Optional[str], - ], - None, -] - -PackageRole = Literal["runtime", "development", "optional", "peer"] - - -class PackageManager: - def __init__(self) -> None: - self._impl = _frida.PackageManager() - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def registry(self): - return self._impl.registry - - @registry.setter - def registry(self, value): - self._impl.registry = value - - @cancellable - def search( - self, - query: str, - offset: Optional[int] = None, - limit: Optional[int] = None, - ) -> _frida.PackageSearchResult: - kwargs = { - "offset": offset, - "limit": limit, - } - _filter_missing_kwargs(kwargs) - return self._impl.search(query, **kwargs) - - @cancellable - def install( - self, - project_root: Optional[str] = None, - role: Optional[PackageRole] = None, - specs: Optional[Sequence[str]] = None, - omits: Optional[Sequence[PackageRole]] = None, - ) -> _frida.PackageInstallResult: - kwargs: Dict[str, Any] = { - "project_root": project_root, - "role": role, - "specs": specs, - "omits": omits, - } - _filter_missing_kwargs(kwargs) - return self._impl.install(**kwargs) - - def on(self, signal: Literal["install-progress"], callback: PackageManagerInstallProgressCallback) -> None: - self._impl.on(signal, callback) - - def off(self, signal: Literal["install-progress"], callback: PackageManagerInstallProgressCallback) -> None: - self._impl.off(signal, callback) - - -class CancellablePollFD: - def __init__(self, cancellable: _Cancellable) -> None: - self.handle = cancellable.get_fd() - self._cancellable: Optional[_Cancellable] = cancellable - - def __del__(self) -> None: - self.release() - - def release(self) -> None: - if self._cancellable is not None: - if self.handle != -1: - self._cancellable.release_fd() - self.handle = -1 - self._cancellable = None - - def __repr__(self) -> str: - return repr(self.handle) - - def __enter__(self) -> int: - return self.handle - - def __exit__( - self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - trace: Optional[TracebackType], - ) -> None: - self.release() - - -class Cancellable: - def __init__(self) -> None: - self._impl = _Cancellable() - - def __repr__(self) -> str: - return repr(self._impl) - - @property - def is_cancelled(self) -> bool: - """ - Query whether cancellable has been cancelled - """ - - return self._impl.is_cancelled() - - def raise_if_cancelled(self) -> None: - """ - Raise an exception if cancelled - :raises OperationCancelledError: - """ - - self._impl.raise_if_cancelled() - - def get_pollfd(self) -> CancellablePollFD: - return CancellablePollFD(self._impl) - - @classmethod - def get_current(cls) -> _frida.Cancellable: - """ - Get the top cancellable from the stack - """ - - return _Cancellable.get_current() - - def __enter__(self) -> None: - self._impl.push_current() - - def __exit__( - self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - trace: Optional[TracebackType], - ) -> None: - self._impl.pop_current() - - def connect(self, callback: Callable[..., Any]) -> int: - """ - Register notification callback - :returns: the created handler id - """ - - return self._impl.connect(callback) - - def disconnect(self, handler_id: int) -> None: - """ - Unregister notification callback. - """ - - self._impl.disconnect(handler_id) - - def cancel(self) -> None: - """ - Set cancellable to cancelled - """ - - self._impl.cancel() - - -def make_auth_callback(callback: Callable[[str], Any]) -> Callable[[Any], str]: - """ - Wraps authenticated callbacks with JSON marshaling - """ - - def authenticate(token: str) -> str: - session_info = callback(token) - return json.dumps(session_info) - - return authenticate - - -def _to_camel_case(name: str) -> str: - result = "" - uppercase_next = False - for c in name: - if c == "_": - uppercase_next = True - elif uppercase_next: - result += c.upper() - uppercase_next = False - else: - result += c.lower() - return result diff --git a/frida/_frida/extension.version b/frida/extension.version similarity index 100% rename from frida/_frida/extension.version rename to frida/extension.version diff --git a/frida/_frida/py.typed b/frida/frida_bindgen/__init__.py similarity index 100% rename from frida/_frida/py.typed rename to frida/frida_bindgen/__init__.py diff --git a/frida/frida_bindgen/__main__.py b/frida/frida_bindgen/__main__.py new file mode 100644 index 0000000..9ae637f --- /dev/null +++ b/frida/frida_bindgen/__main__.py @@ -0,0 +1,4 @@ +from .cli import main + +if __name__ == "__main__": + main() diff --git a/frida/frida_bindgen/assets/codegen_endpoint_parameters.c b/frida/frida_bindgen/assets/codegen_endpoint_parameters.c new file mode 100644 index 0000000..61ea7ef --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_endpoint_parameters.c @@ -0,0 +1,81 @@ +static gboolean +PyGObject_unmarshal_certificate (const gchar * str, + GTlsCertificate ** certificate) +{ + GError * error = NULL; + + if (strchr (str, '\n') != NULL) + *certificate = g_tls_certificate_new_from_pem (str, -1, &error); + else + *certificate = g_tls_certificate_new_from_file (str, &error); + if (error != NULL) + goto propagate_error; + + return TRUE; + +propagate_error: + { + PyFrida_raise (g_error_new_literal (FRIDA_ERROR, FRIDA_ERROR_INVALID_ARGUMENT, error->message)); + g_error_free (error); + + return FALSE; + } +} + +static int +PyEndpointParameters_init (PyEndpointParameters * self, + PyObject * args, + PyObject * kw) +{ + int result = -1; + static char * keywords[] = { "address", "port", "certificate", "origin", "auth_service", "asset_root", NULL }; + char * address = NULL; + unsigned short int port = 0; + char * certificate_value = NULL; + char * origin = NULL; + PyObject * auth_service_obj = NULL; + char * asset_root_value = NULL; + GTlsCertificate * certificate = NULL; + FridaAuthenticationService * auth_service = NULL; + GFile * asset_root = NULL; + FridaEndpointParameters * handle; + + if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) + return -1; + + if (!PyArg_ParseTupleAndKeywords (args, kw, "|esHesesOes", keywords, + "utf-8", &address, + &port, + "utf-8", &certificate_value, + "utf-8", &origin, + &auth_service_obj, + "utf-8", &asset_root_value)) + return -1; + + if (certificate_value != NULL && !PyGObject_unmarshal_certificate (certificate_value, &certificate)) + goto beach; + + if (auth_service_obj != NULL && auth_service_obj != Py_None) + auth_service = g_object_ref (PY_GOBJECT_HANDLE (auth_service_obj)); + + if (asset_root_value != NULL) + asset_root = g_file_new_for_path (asset_root_value); + + handle = frida_endpoint_parameters_new (address, port, certificate, origin, auth_service, asset_root); + + PyGObject_take_handle ((PyGObject *) self, handle, PYFRIDA_TYPE (EndpointParameters)); + + result = 0; + +beach: + g_clear_object (&asset_root); + g_clear_object (&auth_service); + g_clear_object (&certificate); + + PyMem_Free (asset_root_value); + PyMem_Free (origin); + PyMem_Free (certificate_value); + PyMem_Free (address); + + return result; +} diff --git a/frida/frida_bindgen/assets/codegen_gobject_globals.c b/frida/frida_bindgen/assets/codegen_gobject_globals.c new file mode 100644 index 0000000..c12bd66 --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_gobject_globals.c @@ -0,0 +1,5 @@ +static GHashTable * pygobject_type_spec_by_type; +static GHashTable * frida_exception_by_error_code; +static PyObject * cancelled_exception; +static PyObject * inspect_getargspec; +static PyObject * inspect_ismethod; diff --git a/frida/frida_bindgen/assets/codegen_gobject_methods.c b/frida/frida_bindgen/assets/codegen_gobject_methods.c new file mode 100644 index 0000000..ebd2d3e --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_gobject_methods.c @@ -0,0 +1,1164 @@ +static void +PyGObject_class_init (void) +{ + pygobject_type_spec_by_type = g_hash_table_new_full (NULL, NULL, NULL, NULL); +} + +static int +PyGObject_init (PyGObject * self) +{ + self->handle = NULL; + self->type = PYFRIDA_TYPE (GObject); + + self->signal_closures = NULL; + + return 0; +} + +static void +PyGObject_dealloc (PyGObject * self) +{ + gpointer handle; + + handle = PyGObject_steal_handle (self); + if (handle != NULL) + { + Py_BEGIN_ALLOW_THREADS + self->type->destroy (handle); + Py_END_ALLOW_THREADS + } + + ((freefunc) PyType_GetSlot (Py_TYPE (self), Py_tp_free)) (self); +} + +static void +PyGObject_gc_dealloc (PyGObject * self) +{ + PyObject_GC_UnTrack (self); + + PyGObject_dealloc (self); +} + +static void +PyGObject_take_handle (PyGObject * self, + gpointer handle, + const PyGObjectType * type) +{ + self->handle = handle; + self->type = type; + + if (handle != NULL) + g_object_set_data (G_OBJECT (handle), "pyobject", self); +} + +static gpointer +PyGObject_steal_handle (PyGObject * self) +{ + gpointer handle = self->handle; + GSList * entry; + + if (handle == NULL) + return NULL; + + for (entry = self->signal_closures; entry != NULL; entry = entry->next) + { + GClosure * closure = entry->data; + + g_signal_handlers_disconnect_matched (handle, G_SIGNAL_MATCH_CLOSURE, 0, 0, closure, NULL, NULL); + } + g_clear_pointer (&self->signal_closures, g_slist_free); + + g_object_set_data (G_OBJECT (handle), "pyobject", NULL); + + self->handle = NULL; + + return handle; +} + +static void +PyGObject_register_type (GType instance_type, + PyGObjectType * python_type) +{ + g_hash_table_insert (pygobject_type_spec_by_type, GSIZE_TO_POINTER (instance_type), python_type); +} + +static PyObject * +PyGObject_marshal_object (gpointer handle, + GType type) +{ + const PyGObjectType * pytype; + + if (handle == NULL) + PyFrida_RETURN_NONE; + + pytype = g_hash_table_lookup (pygobject_type_spec_by_type, GSIZE_TO_POINTER (type)); + if (pytype == NULL) + pytype = PYFRIDA_TYPE (GObject); + + return PyGObject_new_take_handle (g_object_ref (handle), pytype); +} + +static PyObject * +PyGObject_new_take_handle (gpointer handle, + const PyGObjectType * pytype) +{ + PyObject * object; + + if (handle == NULL) + PyFrida_RETURN_NONE; + + object = PyGObject_try_get_from_handle (handle); + if (object == NULL) + { + object = PyObject_CallFunction (pytype->object, NULL); + PyGObject_take_handle (PY_GOBJECT (object), handle, pytype); + + if (pytype->init_from_handle != NULL) + pytype->init_from_handle (object, handle); + } + else + { + pytype->destroy (handle); + Py_IncRef (object); + } + + return object; +} + +static PyObject * +PyGObject_try_get_from_handle (gpointer handle) +{ + return g_object_get_data (handle, "pyobject"); +} + +static PyObject * +PyGObject_on (PyGObject * self, + PyObject * args) +{ + guint signal_id; + PyObject * callback; + guint max_arg_count, allowed_arg_count_including_sender; + GSignalQuery query; + GClosure * closure; + + if (!PyGObject_parse_signal_method_args (args, G_OBJECT_TYPE (self->handle), &signal_id, &callback)) + return NULL; + + max_arg_count = PyFrida_get_max_argument_count (callback); + if (max_arg_count != G_MAXUINT) + { + g_signal_query (signal_id, &query); + + allowed_arg_count_including_sender = 1 + query.n_params; + + if (max_arg_count > allowed_arg_count_including_sender) + goto too_many_arguments; + } + + closure = PyGObject_make_closure_for_signal (callback, max_arg_count); + g_signal_connect_closure_by_id (self->handle, signal_id, 0, closure, TRUE); + + self->signal_closures = g_slist_prepend (self->signal_closures, closure); + + PyFrida_RETURN_NONE; + +too_many_arguments: + { + return PyErr_Format (PyExc_TypeError, + "callback expects too many arguments, the '%s' signal only has %u but callback expects %u", + g_signal_name (signal_id), query.n_params, max_arg_count); + } +} + +static PyObject * +PyGObject_off (PyGObject * self, + PyObject * args) +{ + guint signal_id; + PyObject * callback; + GSList * entry; + GClosure * closure; + + if (!PyGObject_parse_signal_method_args (args, G_OBJECT_TYPE (self->handle), &signal_id, &callback)) + return NULL; + + entry = g_slist_find_custom (self->signal_closures, callback, (GCompareFunc) PyGObject_compare_signal_closure_callback); + if (entry == NULL) + goto unknown_callback; + + closure = entry->data; + self->signal_closures = g_slist_delete_link (self->signal_closures, entry); + + g_signal_handlers_disconnect_matched (self->handle, G_SIGNAL_MATCH_CLOSURE, signal_id, 0, closure, NULL, NULL); + + PyFrida_RETURN_NONE; + +unknown_callback: + { + PyErr_SetString (PyExc_ValueError, "unknown callback"); + return NULL; + } +} + +static gboolean +PyGObject_parse_signal_method_args (PyObject * args, + GType instance_type, + guint * signal_id, + PyObject ** callback) +{ + const gchar * signal_name; + + if (!PyArg_ParseTuple (args, "sO", &signal_name, callback)) + return FALSE; + + if (!PyCallable_Check (*callback)) + { + PyErr_SetString (PyExc_TypeError, "second argument must be callable"); + return FALSE; + } + + *signal_id = g_signal_lookup (signal_name, instance_type); + if (*signal_id == 0) + goto invalid_signal_name; + + return TRUE; + +invalid_signal_name: + { + GString * message; + guint * ids, n_ids, i; + + message = g_string_sized_new (128); + + g_string_append (message, PyGObject_class_name_from_c (g_type_name (instance_type))); + + ids = g_signal_list_ids (instance_type, &n_ids); + + if (n_ids > 0) + { + g_string_append_printf (message, " does not have a signal named '%s', it only has: ", signal_name); + + for (i = 0; i != n_ids; i++) + { + if (i != 0) + g_string_append (message, ", "); + g_string_append_c (message, '\''); + g_string_append (message, g_signal_name (ids[i])); + g_string_append_c (message, '\''); + } + } + else + { + g_string_append (message, " does not have any signals"); + } + + g_free (ids); + + PyErr_SetString (PyExc_ValueError, message->str); + + g_string_free (message, TRUE); + + return FALSE; + } +} + +static gint +PyGObject_compare_signal_closure_callback (GClosure * closure, + PyObject * callback) +{ + PyObject * registered = closure->data; + PyObject * original; + gboolean matches; + + if (PyObject_RichCompareBool (registered, callback, Py_EQ) == 1) + return 0; + + original = PyObject_GetAttrString (registered, "_frida_original"); + if (original == NULL) + { + PyErr_Clear (); + return -1; + } + + matches = PyObject_RichCompareBool (original, callback, Py_EQ) == 1; + Py_DecRef (original); + + return matches ? 0 : -1; +} + +static GClosure * +PyGObject_make_closure_for_signal (PyObject * callback, + guint max_arg_count) +{ + GClosure * closure; + + closure = g_closure_new_simple (sizeof (PyGObjectSignalClosure), callback); + Py_IncRef (callback); + + g_closure_add_finalize_notifier (closure, callback, (GClosureNotify) PyGObjectSignalClosure_finalize); + g_closure_set_marshal (closure, PyGObjectSignalClosure_marshal); + + PY_GOBJECT_SIGNAL_CLOSURE (closure)->max_arg_count = max_arg_count; + + return closure; +} + +static void +PyGObjectSignalClosure_finalize (PyObject * callback) +{ + PyGILState_STATE gstate; + + gstate = PyGILState_Ensure (); + Py_DecRef (callback); + PyGILState_Release (gstate); +} + +static void +PyGObjectSignalClosure_marshal (GClosure * closure, + GValue * return_gvalue, + guint n_param_values, + const GValue * param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + PyGObjectSignalClosure * self = PY_GOBJECT_SIGNAL_CLOSURE (closure); + PyObject * callback = closure->data; + PyGILState_STATE gstate; + PyObject * args, * result; + + gstate = PyGILState_Ensure (); + + if (self->max_arg_count == n_param_values) + args = PyGObjectSignalClosure_marshal_params (param_values, n_param_values); + else + args = PyGObjectSignalClosure_marshal_params (param_values + 1, MIN (n_param_values - 1, self->max_arg_count)); + if (args == NULL) + { + PyErr_Print (); + goto beach; + } + + result = PyObject_CallObject (callback, args); + if (result != NULL) + Py_DecRef (result); + else + PyErr_Print (); + + Py_DecRef (args); + +beach: + PyGILState_Release (gstate); +} + +static PyObject * +PyGObjectSignalClosure_marshal_params (const GValue * params, + guint params_length) +{ + PyObject * args; + guint i; + + args = PyTuple_New (params_length); + + for (i = 0; i != params_length; i++) + { + PyObject * arg; + + arg = PyGObject_marshal_value (¶ms[i]); + if (arg == NULL) + goto marshal_error; + + PyTuple_SetItem (args, i, arg); + } + + return args; + +marshal_error: + { + Py_DecRef (args); + return NULL; + } +} + +static PyObject * +PyGObject_marshal_value (const GValue * value) +{ + GType type = G_VALUE_TYPE (value); + + switch (type) + { + case G_TYPE_BOOLEAN: + return PyBool_FromLong (g_value_get_boolean (value)); + case G_TYPE_INT: + return PyLong_FromLong (g_value_get_int (value)); + case G_TYPE_UINT: + return PyLong_FromUnsignedLong (g_value_get_uint (value)); + case G_TYPE_FLOAT: + return PyFloat_FromDouble (g_value_get_float (value)); + case G_TYPE_DOUBLE: + return PyFloat_FromDouble (g_value_get_double (value)); + case G_TYPE_STRING: + return PyGObject_marshal_string (g_value_get_string (value)); + case G_TYPE_VARIANT: + return PyGObject_marshal_variant (g_value_get_variant (value)); + default: + break; + } + + if (G_TYPE_IS_ENUM (type)) + return PyGObject_marshal_enum (g_value_get_enum (value), type); + + if (type == G_TYPE_BYTES) + return PyGObject_marshal_bytes (g_value_get_boxed (value)); + + if (G_TYPE_IS_OBJECT (type)) + return PyGObject_marshal_object (g_value_get_object (value), type); + + return PyErr_Format (PyExc_NotImplementedError, "unsupported type: '%s'", g_type_name (type)); +} + +static const gchar * +PyGObject_class_name_from_c (const gchar * cname) +{ + if (g_str_has_prefix (cname, "Frida")) + return cname + 5; + + return cname; +} + +static guint +PyFrida_get_max_argument_count (PyObject * callable) +{ + guint result = G_MAXUINT; + PyObject * spec; + PyObject * varargs; + PyObject * args; + PyObject * is_method; + + spec = PyObject_CallFunction (inspect_getargspec, "O", callable); + if (spec == NULL) + { + PyErr_Clear (); + goto beach; + } + + varargs = PyTuple_GetItem (spec, 1); + if (varargs != Py_None) + goto beach; + + args = PyTuple_GetItem (spec, 0); + result = PyObject_Size (args); + + is_method = PyObject_CallFunction (inspect_ismethod, "O", callable); + if (is_method == Py_True) + result--; + Py_XDECREF (is_method); + +beach: + Py_XDECREF (spec); + + return result; +} + +static gboolean +PyGObject_unmarshal_enum (const gchar * str, + GType type, + gpointer value) +{ + GEnumClass * enum_class; + GEnumValue * enum_value; + + enum_class = g_type_class_ref (type); + + enum_value = g_enum_get_value_by_nick (enum_class, str); + if (enum_value == NULL) + goto invalid_value; + + *((gint *) value) = enum_value->value; + + g_type_class_unref (enum_class); + + return TRUE; + +invalid_value: + { + GString * message; + guint i; + + message = g_string_sized_new (128); + + g_string_append_printf (message, + "enum type %s does not have a value named '%s', it only has: ", + g_type_name (type), str); + + for (i = 0; i != enum_class->n_values; i++) + { + if (i != 0) + g_string_append (message, ", "); + g_string_append_c (message, '\''); + g_string_append (message, enum_class->values[i].value_nick); + g_string_append_c (message, '\''); + } + + PyErr_SetString (PyExc_ValueError, message->str); + + g_string_free (message, TRUE); + + g_type_class_unref (enum_class); + + return FALSE; + } +} + +static PyObject * +PyGObject_marshal_enum (gint value, + GType type) +{ + GEnumClass * enum_class; + GEnumValue * enum_value; + PyObject * result; + + enum_class = g_type_class_ref (type); + + enum_value = g_enum_get_value (enum_class, value); + result = PyUnicode_FromString (enum_value->value_nick); + + g_type_class_unref (enum_class); + + return result; +} + +static gboolean +PyGObject_unmarshal_string (PyObject * value, + gchar ** str) +{ + PyObject * bytes; + + *str = NULL; + + bytes = PyUnicode_AsUTF8String (value); + if (bytes == NULL) + return FALSE; + + *str = g_strdup (PyBytes_AsString (bytes)); + + Py_DecRef (bytes); + + return *str != NULL; +} + +static PyObject * +PyGObject_marshal_string (const gchar * str) +{ + if (str == NULL) + PyFrida_RETURN_NONE; + + return PyUnicode_FromString (str); +} + +static gboolean +PyGObject_unmarshal_strv (PyObject * value, + gchar *** strv, + gint * length) +{ + gint n, i; + gchar ** elements; + + if (value == Py_None) + { + *strv = NULL; + *length = 0; + return TRUE; + } + + if (!PyList_Check (value) && !PyTuple_Check (value)) + goto invalid_type; + + n = PySequence_Size (value); + elements = g_new0 (gchar *, n + 1); + + for (i = 0; i != n; i++) + { + PyObject * element, * bytes; + + element = PySequence_GetItem (value, i); + bytes = PyUnicode_Check (element) ? PyUnicode_AsUTF8String (element) : NULL; + Py_DecRef (element); + if (bytes == NULL) + goto invalid_element; + + elements[i] = g_strdup (PyBytes_AsString (bytes)); + Py_DecRef (bytes); + } + + *strv = elements; + *length = n; + + return TRUE; + +invalid_type: + { + PyErr_SetString (PyExc_TypeError, "expected a list or tuple of strings"); + return FALSE; + } +invalid_element: + { + g_strfreev (elements); + + PyErr_SetString (PyExc_TypeError, "expected a list or tuple of strings only"); + return FALSE; + } +} + +static PyObject * +PyGObject_marshal_strv (gchar * const * strv, + gint length) +{ + PyObject * result; + gint i; + + if (strv == NULL) + PyFrida_RETURN_NONE; + + result = PyList_New (length); + + for (i = 0; i != length; i++) + PyList_SetItem (result, i, PyGObject_marshal_string (strv[i])); + + return result; +} + +static PyObject * +PyGObject_marshal_bytes (GBytes * bytes) +{ + gconstpointer data; + gsize size; + + if (bytes == NULL) + PyFrida_RETURN_NONE; + + data = g_bytes_get_data (bytes, &size); + + return PyBytes_FromStringAndSize (data, size); +} + +static gboolean +PyGObject_str_equal (gconstpointer a, + gconstpointer b) +{ + return g_str_equal (a, b); +} + +static gboolean +PyGObject_unmarshal_vardict (PyObject * value, + GHashTable ** dict) +{ + GHashTable * result; + Py_ssize_t pos; + PyObject * key, * val; + + if (!PyDict_Check (value)) + goto invalid_type; + + result = g_hash_table_new_full (g_str_hash, PyGObject_str_equal, g_free, + (GDestroyNotify) g_variant_unref); + + pos = 0; + while (PyDict_Next (value, &pos, &key, &val)) + { + PyObject * key_bytes; + GVariant * variant; + + key_bytes = PyUnicode_Check (key) ? PyUnicode_AsUTF8String (key) : NULL; + if (key_bytes == NULL) + goto invalid_key; + + if (!PyGObject_unmarshal_variant (val, &variant)) + { + Py_DecRef (key_bytes); + goto propagate_error; + } + + g_hash_table_insert (result, g_strdup (PyBytes_AsString (key_bytes)), variant); + Py_DecRef (key_bytes); + } + + *dict = result; + + return TRUE; + +invalid_type: + { + PyErr_SetString (PyExc_TypeError, "expected a dictionary"); + return FALSE; + } +invalid_key: + { + g_hash_table_unref (result); + PyErr_SetString (PyExc_TypeError, "dictionary keys must be strings"); + return FALSE; + } +propagate_error: + { + g_hash_table_unref (result); + return FALSE; + } +} + +static PyObject * +PyGObject_marshal_vardict (GHashTable * dict) +{ + PyObject * result; + GHashTableIter iter; + const gchar * key; + GVariant * raw_value; + + result = PyDict_New (); + + g_hash_table_iter_init (&iter, dict); + + while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &raw_value)) + { + PyObject * value = PyGObject_marshal_variant (raw_value); + + PyDict_SetItemString (result, key, value); + + Py_DecRef (value); + } + + return result; +} + +static gboolean +PyGObject_unmarshal_variant (PyObject * value, + GVariant ** variant) +{ + if (PyUnicode_Check (value)) + { + PyObject * bytes = PyUnicode_AsUTF8String (value); + if (bytes == NULL) + return FALSE; + *variant = g_variant_ref_sink (g_variant_new_string (PyBytes_AsString (bytes))); + Py_DecRef (bytes); + return TRUE; + } + + if (PyBool_Check (value)) + { + *variant = g_variant_ref_sink (g_variant_new_boolean (value == Py_True)); + return TRUE; + } + + if (PyLong_Check (value)) + { + long long l = PyLong_AsLongLong (value); + if (l == -1 && PyErr_Occurred () != NULL) + return FALSE; + *variant = g_variant_ref_sink (g_variant_new_int64 (l)); + return TRUE; + } + + if (PyFloat_Check (value)) + { + *variant = g_variant_ref_sink (g_variant_new_double (PyFloat_AsDouble (value))); + return TRUE; + } + + if (PyBytes_Check (value)) + { + char * data; + Py_ssize_t size; + + PyBytes_AsStringAndSize (value, &data, &size); + *variant = g_variant_ref_sink ( + g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, data, size, sizeof (guint8))); + return TRUE; + } + + if (PySequence_Check (value)) + return PyGObject_unmarshal_variant_from_sequence (value, variant); + + if (PyMapping_Check (value)) + return PyGObject_unmarshal_variant_from_mapping (value, variant); + + PyErr_SetString (PyExc_TypeError, "unsupported value type"); + return FALSE; +} + +static gboolean +PyGObject_unmarshal_variant_from_sequence (PyObject * sequence, + GVariant ** variant) +{ + gboolean is_tuple; + GVariantBuilder builder; + Py_ssize_t n, i; + PyObject * item = NULL; + + is_tuple = PyTuple_Check (sequence); + + if (is_tuple && PyTuple_Size (sequence) == 2) + { + PyObject * type = PyTuple_GetItem (sequence, 0); + + if (PyUnicode_Check (type)) + { + PyObject * type_bytes = PyUnicode_AsUTF8String (type); + gboolean is_uint64_cast; + + if (type_bytes == NULL) + return FALSE; + is_uint64_cast = strcmp (PyBytes_AsString (type_bytes), "uint64") == 0; + Py_DecRef (type_bytes); + + if (is_uint64_cast) + { + unsigned long long l = PyLong_AsUnsignedLongLong (PyTuple_GetItem (sequence, 1)); + if (l == (unsigned long long) -1 && PyErr_Occurred () != NULL) + return FALSE; + *variant = g_variant_ref_sink (g_variant_new_uint64 (l)); + return TRUE; + } + } + } + + g_variant_builder_init (&builder, is_tuple ? G_VARIANT_TYPE_TUPLE : G_VARIANT_TYPE ("av")); + + n = PySequence_Length (sequence); + if (n == -1) + goto propagate_error; + + for (i = 0; i != n; i++) + { + GVariant * child; + + item = PySequence_GetItem (sequence, i); + if (item == NULL) + goto propagate_error; + + if (!PyGObject_unmarshal_variant (item, &child)) + goto propagate_error; + + if (is_tuple) + g_variant_builder_add_value (&builder, child); + else + g_variant_builder_add (&builder, "v", child); + + g_variant_unref (child); + Py_DecRef (item); + item = NULL; + } + + *variant = g_variant_ref_sink (g_variant_builder_end (&builder)); + + return TRUE; + +propagate_error: + { + Py_XDECREF (item); + g_variant_builder_clear (&builder); + return FALSE; + } +} + +static gboolean +PyGObject_unmarshal_variant_from_mapping (PyObject * mapping, + GVariant ** variant) +{ + GVariantBuilder builder; + PyObject * items; + Py_ssize_t n, i; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + + items = PyMapping_Items (mapping); + if (items == NULL) + goto propagate_error; + + n = PyList_Size (items); + + for (i = 0; i != n; i++) + { + PyObject * pair, * key, * val, * key_bytes; + GVariant * child; + + pair = PyList_GetItem (items, i); + key = PyTuple_GetItem (pair, 0); + val = PyTuple_GetItem (pair, 1); + + key_bytes = PyUnicode_Check (key) ? PyUnicode_AsUTF8String (key) : NULL; + if (key_bytes == NULL) + goto propagate_error; + + if (!PyGObject_unmarshal_variant (val, &child)) + { + Py_DecRef (key_bytes); + goto propagate_error; + } + + g_variant_builder_add (&builder, "{sv}", PyBytes_AsString (key_bytes), child); + + g_variant_unref (child); + Py_DecRef (key_bytes); + } + + Py_DecRef (items); + + *variant = g_variant_ref_sink (g_variant_builder_end (&builder)); + + return TRUE; + +propagate_error: + { + Py_XDECREF (items); + g_variant_builder_clear (&builder); + return FALSE; + } +} + +static PyObject * +PyGObject_marshal_variant (GVariant * variant) +{ + switch (g_variant_classify (variant)) + { + case G_VARIANT_CLASS_STRING: + return PyGObject_marshal_string (g_variant_get_string (variant, NULL)); + case G_VARIANT_CLASS_BYTE: + return PyLong_FromLong (g_variant_get_byte (variant)); + case G_VARIANT_CLASS_INT16: + return PyLong_FromLong (g_variant_get_int16 (variant)); + case G_VARIANT_CLASS_UINT16: + return PyLong_FromUnsignedLong (g_variant_get_uint16 (variant)); + case G_VARIANT_CLASS_INT32: + return PyLong_FromLong (g_variant_get_int32 (variant)); + case G_VARIANT_CLASS_UINT32: + return PyLong_FromUnsignedLong (g_variant_get_uint32 (variant)); + case G_VARIANT_CLASS_INT64: + return PyLong_FromLongLong (g_variant_get_int64 (variant)); + case G_VARIANT_CLASS_UINT64: + return PyLong_FromUnsignedLongLong (g_variant_get_uint64 (variant)); + case G_VARIANT_CLASS_DOUBLE: + return PyFloat_FromDouble (g_variant_get_double (variant)); + case G_VARIANT_CLASS_BOOLEAN: + return PyBool_FromLong (g_variant_get_boolean (variant)); + case G_VARIANT_CLASS_ARRAY: + if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("ay"))) + return PyGObject_marshal_variant_byte_array (variant); + + if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT)) + return PyGObject_marshal_variant_dict (variant); + + return PyGObject_marshal_variant_array (variant); + case G_VARIANT_CLASS_TUPLE: + return PyGObject_marshal_variant_tuple (variant); + case G_VARIANT_CLASS_VARIANT: + { + GVariant * inner; + PyObject * result; + + inner = g_variant_get_variant (variant); + result = PyGObject_marshal_variant (inner); + g_variant_unref (inner); + + return result; + } + default: + break; + } + + PyFrida_RETURN_NONE; +} + +static PyObject * +PyGObject_marshal_variant_byte_array (GVariant * variant) +{ + gconstpointer elements; + gsize n_elements; + + elements = g_variant_get_fixed_array (variant, &n_elements, sizeof (guint8)); + + return PyBytes_FromStringAndSize (elements, n_elements); +} + +static PyObject * +PyGObject_marshal_variant_dict (GVariant * variant) +{ + PyObject * dict; + GVariantIter iter; + gchar * key; + GVariant * raw_value; + + dict = PyDict_New (); + + g_variant_iter_init (&iter, variant); + + while (g_variant_iter_next (&iter, "{sv}", &key, &raw_value)) + { + PyObject * value = PyGObject_marshal_variant (raw_value); + + PyDict_SetItemString (dict, key, value); + + Py_DecRef (value); + g_variant_unref (raw_value); + g_free (key); + } + + return dict; +} + +static PyObject * +PyGObject_marshal_variant_array (GVariant * variant) +{ + GVariantIter iter; + PyObject * list; + guint i; + GVariant * child; + + g_variant_iter_init (&iter, variant); + + list = PyList_New (g_variant_iter_n_children (&iter)); + + for (i = 0; (child = g_variant_iter_next_value (&iter)) != NULL; i++) + { + PyList_SetItem (list, i, PyGObject_marshal_variant (child)); + + g_variant_unref (child); + } + + return list; +} + +static PyObject * +PyGObject_marshal_variant_tuple (GVariant * variant) +{ + GVariantIter iter; + PyObject * tuple; + guint i; + GVariant * child; + + g_variant_iter_init (&iter, variant); + + tuple = PyTuple_New (g_variant_iter_n_children (&iter)); + + for (i = 0; (child = g_variant_iter_next_value (&iter)) != NULL; i++) + { + PyTuple_SetItem (tuple, i, PyGObject_marshal_variant (child)); + + g_variant_unref (child); + } + + return tuple; +} + +static PyObject * +PyFrida_raise (GError * error) +{ + PyObject * exception = PyFrida_marshal_error (error); + + PyErr_SetObject ((PyObject *) Py_TYPE (exception), exception); + + Py_DecRef (exception); + + return NULL; +} + +static PyObject * +PyFrida_marshal_error (GError * error) +{ + PyObject * exception_class; + GString * message; + PyObject * instance; + + if (error->domain == FRIDA_ERROR) + exception_class = g_hash_table_lookup (frida_exception_by_error_code, GINT_TO_POINTER (error->code)); + else + exception_class = cancelled_exception; + if (exception_class == NULL) + exception_class = cancelled_exception; + + message = g_string_new (""); + g_string_append_unichar (message, g_unichar_tolower (g_utf8_get_char (error->message))); + g_string_append (message, g_utf8_offset_to_pointer (error->message, 1)); + + instance = PyObject_CallFunction (exception_class, "s", message->str); + + g_string_free (message, TRUE); + g_error_free (error); + + return instance; +} + +static void +PyFrida_object_decref (gpointer obj) +{ + PyObject * o = obj; + Py_DecRef (o); +} + +static void +PyFrida_deliver (PyObject * callback, + PyObject * result, + PyObject * error) +{ + PyObject * outcome = PyObject_CallFunctionObjArgs (callback, result, error, NULL); + if (outcome != NULL) + Py_DecRef (outcome); + else + PyErr_Print (); +} + +static PyObject * +PyFrida_make_completion (GTask * task, + PyFridaCompleteFunc complete) +{ + PyFridaRequest * request = g_slice_new (PyFridaRequest); + request->task = task; + request->complete = complete; + + return PyCapsule_New (request, "frida.request", NULL); +} + +static PyObject * +PyFrida_complete_request (PyObject * module, + PyObject * args) +{ + PyObject * capsule, * value, * error; + PyFridaRequest * request; + + if (!PyArg_ParseTuple (args, "OOO", &capsule, &value, &error)) + return NULL; + + request = PyCapsule_GetPointer (capsule, "frida.request"); + + request->complete (request->task, value, error); + g_object_unref (request->task); + + g_slice_free (PyFridaRequest, request); + + PyFrida_RETURN_NONE; +} + +static gboolean +PyFrida_return_error (GTask * task, + PyObject * error) +{ + gchar * message; + + if (error == Py_None) + return FALSE; + + message = NULL; + { + PyObject * text = PyObject_Str (error); + if (text != NULL) + { + PyGObject_unmarshal_string (text, &message); + Py_DecRef (text); + } + } + + g_task_return_new_error (task, FRIDA_ERROR, FRIDA_ERROR_INVALID_ARGUMENT, + "%s", (message != NULL) ? message : "internal error"); + + g_free (message); + + return TRUE; +} diff --git a/frida/frida_bindgen/assets/codegen_gobject_prototypes.h b/frida/frida_bindgen/assets/codegen_gobject_prototypes.h new file mode 100644 index 0000000..0675acc --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_gobject_prototypes.h @@ -0,0 +1,45 @@ +static void PyGObject_class_init (void); +static int PyGObject_init (PyGObject * self); +static void PyGObject_dealloc (PyGObject * self); +static void PyGObject_gc_dealloc (PyGObject * self); +static gpointer PyGObject_steal_handle (PyGObject * self); +static void PyGObject_register_type (GType instance_type, PyGObjectType * python_type); +static PyObject * PyGObject_marshal_object (gpointer handle, GType type); +static PyObject * PyGObject_new_take_handle (gpointer handle, const PyGObjectType * pytype); +static PyObject * PyGObject_try_get_from_handle (gpointer handle); +static PyObject * PyGObject_on (PyGObject * self, PyObject * args); +static PyObject * PyGObject_off (PyGObject * self, PyObject * args); +static gboolean PyGObject_parse_signal_method_args (PyObject * args, GType instance_type, guint * signal_id, PyObject ** callback); +static gint PyGObject_compare_signal_closure_callback (GClosure * closure, PyObject * callback); +static GClosure * PyGObject_make_closure_for_signal (PyObject * callback, guint max_arg_count); +static void PyGObjectSignalClosure_finalize (PyObject * callback); +static void PyGObjectSignalClosure_marshal (GClosure * closure, GValue * return_gvalue, guint n_param_values, const GValue * param_values, gpointer invocation_hint, gpointer marshal_data); +static PyObject * PyGObjectSignalClosure_marshal_params (const GValue * params, guint params_length); +static PyObject * PyGObject_marshal_value (const GValue * value); +static const gchar * PyGObject_class_name_from_c (const gchar * cname); +static guint PyFrida_get_max_argument_count (PyObject * callable); +static gboolean PyGObject_unmarshal_enum (const gchar * str, GType type, gpointer value); +static PyObject * PyGObject_marshal_enum (gint value, GType type); +static gboolean PyGObject_unmarshal_string (PyObject * value, gchar ** str); +static PyObject * PyGObject_marshal_string (const gchar * str); +static gboolean PyGObject_unmarshal_strv (PyObject * value, gchar *** strv, gint * length); +static PyObject * PyGObject_marshal_strv (gchar * const * strv, gint length); +static PyObject * PyGObject_marshal_bytes (GBytes * bytes); +static gboolean PyGObject_unmarshal_vardict (PyObject * value, GHashTable ** dict); +static PyObject * PyGObject_marshal_vardict (GHashTable * dict); +static gboolean PyGObject_unmarshal_variant (PyObject * value, GVariant ** variant); +static gboolean PyGObject_unmarshal_variant_from_sequence (PyObject * sequence, GVariant ** variant); +static gboolean PyGObject_unmarshal_variant_from_mapping (PyObject * mapping, GVariant ** variant); +static PyObject * PyGObject_marshal_variant (GVariant * variant); +static PyObject * PyGObject_marshal_variant_byte_array (GVariant * variant); +static PyObject * PyGObject_marshal_variant_dict (GVariant * variant); +static PyObject * PyGObject_marshal_variant_array (GVariant * variant); +static PyObject * PyGObject_marshal_variant_tuple (GVariant * variant); + +static PyObject * PyFrida_raise (GError * error); +static PyObject * PyFrida_marshal_error (GError * error); +static void PyFrida_object_decref (gpointer obj); +static void PyFrida_deliver (PyObject * callback, PyObject * result, PyObject * error); +static PyObject * PyFrida_make_completion (GTask * task, PyFridaCompleteFunc complete); +static PyObject * PyFrida_complete_request (PyObject * module, PyObject * args); +static gboolean PyFrida_return_error (GTask * task, PyObject * error); diff --git a/frida/frida_bindgen/assets/codegen_macros.h b/frida/frida_bindgen/assets/codegen_macros.h new file mode 100644 index 0000000..7c0d82e --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_macros.h @@ -0,0 +1,81 @@ +#define PYFRIDA_TYPE(name) \ + (&_PYFRIDA_TYPE_VAR (name, type)) +#define PYFRIDA_TYPE_OBJECT(name) \ + PYFRIDA_TYPE (name)->object +#define _PYFRIDA_TYPE_VAR(name, var) \ + G_PASTE (G_PASTE (G_PASTE (Py, name), _), var) +#define PYFRIDA_DEFINE_BASETYPE(pyname, cname, destroy_func, ...) \ + _PYFRIDA_DEFINE_TYPE_SLOTS (cname, __VA_ARGS__); \ + _PYFRIDA_DEFINE_TYPE_SPEC (cname, pyname, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE); \ + static PyGObjectType _PYFRIDA_TYPE_VAR (cname, type) = \ + { \ + .parent = NULL, \ + .object = NULL, \ + .destroy = destroy_func, \ + } +#define PYFRIDA_DEFINE_TYPE(pyname, cname, parent_cname, destroy_func, ...) \ + _PYFRIDA_DEFINE_TYPE_SLOTS (cname, __VA_ARGS__); \ + _PYFRIDA_DEFINE_TYPE_SPEC (cname, pyname, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE); \ + static PyGObjectType _PYFRIDA_TYPE_VAR (cname, type) = \ + { \ + .parent = PYFRIDA_TYPE (parent_cname), \ + .object = NULL, \ + .destroy = destroy_func, \ + } +#define PYFRIDA_DEFINE_GC_TYPE(pyname, cname, parent_cname, destroy_func, ...) \ + _PYFRIDA_DEFINE_TYPE_SLOTS (cname, __VA_ARGS__); \ + _PYFRIDA_DEFINE_TYPE_SPEC (cname, pyname, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC); \ + static PyGObjectType _PYFRIDA_TYPE_VAR (cname, type) = \ + { \ + .parent = PYFRIDA_TYPE (parent_cname), \ + .object = NULL, \ + .destroy = destroy_func, \ + } +#define PYFRIDA_REGISTER_TYPE(cname, gtype) \ + G_BEGIN_DECLS \ + { \ + PyGObjectType * t = PYFRIDA_TYPE (cname); \ + t->object = PyType_FromSpecWithBases (&_PYFRIDA_TYPE_VAR (cname, spec), \ + (t->parent != NULL) ? PyTuple_Pack (1, t->parent->object) : NULL); \ + PyGObject_register_type (gtype, t); \ + Py_IncRef (t->object); \ + PyModule_AddObject (module, G_STRINGIFY (cname), t->object); \ + } \ + G_END_DECLS +#define _PYFRIDA_DEFINE_TYPE_SPEC(cname, pyname, type_flags) \ + static PyType_Spec _PYFRIDA_TYPE_VAR (cname, spec) = \ + { \ + .name = pyname, \ + .basicsize = sizeof (G_PASTE (Py, cname)), \ + .itemsize = 0, \ + .flags = type_flags, \ + .slots = _PYFRIDA_TYPE_VAR (cname, slots), \ + } +#define _PYFRIDA_DEFINE_TYPE_SLOTS(cname, ...) \ + static PyType_Slot _PYFRIDA_TYPE_VAR (cname, slots)[] = \ + { \ + __VA_ARGS__ \ + { 0 }, \ + } + +#define PYFRIDA_DECLARE_EXCEPTION(error_code, name) \ + G_STMT_START \ + { \ + PyObject * exception = PyErr_NewException ("frida." name "Error", NULL, NULL); \ + g_hash_table_insert (frida_exception_by_error_code, GINT_TO_POINTER (error_code), exception); \ + Py_IncRef (exception); \ + PyModule_AddObject (module, name "Error", exception); \ + } \ + G_STMT_END + +#define PY_GOBJECT(o) ((PyGObject *) (o)) +#define PY_GOBJECT_HANDLE(o) (PY_GOBJECT (o)->handle) +#define PY_GOBJECT_SIGNAL_CLOSURE(o) ((PyGObjectSignalClosure *) (o)) + +#define PyFrida_RETURN_NONE \ + G_STMT_START \ + { \ + Py_IncRef (Py_None); \ + return Py_None; \ + } \ + G_STMT_END diff --git a/frida/frida_bindgen/assets/codegen_structs.h b/frida/frida_bindgen/assets/codegen_structs.h new file mode 100644 index 0000000..7400648 --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_structs.h @@ -0,0 +1,29 @@ +struct _PyGObject +{ + PyObject_HEAD + + gpointer handle; + const PyGObjectType * type; + + GSList * signal_closures; +}; + +struct _PyGObjectType +{ + PyGObjectType * parent; + PyObject * object; + PyGObjectInitFromHandleFunc init_from_handle; + GDestroyNotify destroy; +}; + +struct _PyGObjectSignalClosure +{ + GClosure parent; + guint max_arg_count; +}; + +struct _PyFridaRequest +{ + GTask * task; + PyFridaCompleteFunc complete; +}; diff --git a/frida/frida_bindgen/assets/codegen_typedefs.h b/frida/frida_bindgen/assets/codegen_typedefs.h new file mode 100644 index 0000000..d2b69a2 --- /dev/null +++ b/frida/frida_bindgen/assets/codegen_typedefs.h @@ -0,0 +1,6 @@ +typedef struct _PyGObject PyGObject; +typedef void (* PyGObjectInitFromHandleFunc) (PyObject * self, gpointer handle); +typedef struct _PyGObjectType PyGObjectType; +typedef struct _PyGObjectSignalClosure PyGObjectSignalClosure; +typedef struct _PyFridaRequest PyFridaRequest; +typedef void (* PyFridaCompleteFunc) (GTask * task, PyObject * value, PyObject * error); diff --git a/frida/frida_bindgen/assets/facade_bus.py b/frida/frida_bindgen/assets/facade_bus.py new file mode 100644 index 0000000..85d04c3 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_bus.py @@ -0,0 +1,23 @@ +def _setup(self): + self._message_handlers = [] + self._impl.on("message", self._on_message) + +def on(self, signal, callback): + if signal == "message": + self._message_handlers.append(callback) + else: + self._impl.on(signal, _make_signal_handler(callback)) + +def off(self, signal, callback): + if signal == "message": + self._message_handlers.remove(callback) + else: + self._impl.off(signal, callback) + +def _on_message(self, raw_message, data): + message = json.loads(raw_message) + for handler in list(self._message_handlers): + try: + handler(message, data) + except Exception: + traceback.print_exc() diff --git a/frida/frida_bindgen/assets/facade_bus_aio.py b/frida/frida_bindgen/assets/facade_bus_aio.py new file mode 100644 index 0000000..8215358 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_bus_aio.py @@ -0,0 +1,27 @@ +def _setup(self): + self._message_handlers = [] + self._loop = asyncio.get_event_loop() + self._impl.on("message", self._on_message) + +def on(self, signal, callback): + if signal == "message": + self._message_handlers.append(callback) + else: + self._impl.on(signal, _make_signal_handler(callback)) + +def off(self, signal, callback): + if signal == "message": + self._message_handlers.remove(callback) + else: + self._impl.off(signal, callback) + +def _on_message(self, raw_message, data): + message = json.loads(raw_message) + for handler in list(self._message_handlers): + _dispatch(self._loop, self._deliver_message, handler, message, data) + +def _deliver_message(self, handler, message, data): + try: + handler(message, data) + except Exception: + traceback.print_exc() diff --git a/frida/frida_bindgen/assets/facade_cancellable.py b/frida/frida_bindgen/assets/facade_cancellable.py new file mode 100644 index 0000000..f7f3c94 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_cancellable.py @@ -0,0 +1,22 @@ +def __enter__(self): + stack = getattr(_current_cancellable, "stack", None) + if stack is None: + stack = [] + _current_cancellable.stack = stack + stack.append(self) + return self + +def __exit__(self, *exc): + _current_cancellable.stack.pop() + return False + +@classmethod +def get_current(cls): + return _current_cancellable_get() + +def raise_if_cancelled(self): + if self._impl.is_cancelled(): + raise _frida.OperationCancelledError("operation was cancelled") + +def get_pollfd(self): + return CancellablePollFD(self._impl) diff --git a/frida/frida_bindgen/assets/facade_cancellable_aio.py b/frida/frida_bindgen/assets/facade_cancellable_aio.py new file mode 100644 index 0000000..9287418 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_cancellable_aio.py @@ -0,0 +1,18 @@ +def __enter__(self): + self._token = _current_cancellable.set(self) + return self + +def __exit__(self, *exc): + _current_cancellable.reset(self._token) + return False + +@classmethod +def get_current(cls): + return _current_cancellable.get() + +def raise_if_cancelled(self): + if self._impl.is_cancelled(): + raise _frida.OperationCancelledError("operation was cancelled") + +def get_pollfd(self): + return CancellablePollFD(self._impl) diff --git a/frida/frida_bindgen/assets/facade_cancellable_helpers.py b/frida/frida_bindgen/assets/facade_cancellable_helpers.py new file mode 100644 index 0000000..54c0a2a --- /dev/null +++ b/frida/frida_bindgen/assets/facade_cancellable_helpers.py @@ -0,0 +1,23 @@ +class CancellablePollFD: + def __init__(self, cancellable): + self.handle = cancellable.get_fd() + self._cancellable = cancellable + + def __del__(self): + self.release() + + def release(self): + if self._cancellable is not None: + if self.handle != -1: + self._cancellable.release_fd() + self.handle = -1 + self._cancellable = None + + def __repr__(self): + return repr(self.handle) + + def __enter__(self): + return self.handle + + def __exit__(self, *exc): + self.release() diff --git a/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py b/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py new file mode 100644 index 0000000..54c0a2a --- /dev/null +++ b/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py @@ -0,0 +1,23 @@ +class CancellablePollFD: + def __init__(self, cancellable): + self.handle = cancellable.get_fd() + self._cancellable = cancellable + + def __del__(self): + self.release() + + def release(self): + if self._cancellable is not None: + if self.handle != -1: + self._cancellable.release_fd() + self.handle = -1 + self._cancellable = None + + def __repr__(self): + return repr(self.handle) + + def __enter__(self): + return self.handle + + def __exit__(self, *exc): + self.release() diff --git a/frida/frida_bindgen/assets/facade_device_manager_members.py b/frida/frida_bindgen/assets/facade_device_manager_members.py new file mode 100644 index 0000000..38f8ae8 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_device_manager_members.py @@ -0,0 +1,9 @@ +def get_device_matching(self, predicate, timeout=0): + deadline = time.monotonic() + timeout + while True: + for device in self.enumerate_devices(): + if predicate(device): + return device + if time.monotonic() >= deadline: + raise _frida.InvalidArgumentError("no matching device found") + time.sleep(0.05) diff --git a/frida/frida_bindgen/assets/facade_device_manager_members_aio.py b/frida/frida_bindgen/assets/facade_device_manager_members_aio.py new file mode 100644 index 0000000..4060aaf --- /dev/null +++ b/frida/frida_bindgen/assets/facade_device_manager_members_aio.py @@ -0,0 +1,9 @@ +async def get_device_matching(self, predicate, timeout=0): + deadline = time.monotonic() + timeout + while True: + for device in await self.enumerate_devices(): + if predicate(device): + return device + if time.monotonic() >= deadline: + raise _frida.InvalidArgumentError("no matching device found") + await asyncio.sleep(0.05) diff --git a/frida/frida_bindgen/assets/facade_device_members.py b/frida/frida_bindgen/assets/facade_device_members.py new file mode 100644 index 0000000..fc302c0 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_device_members.py @@ -0,0 +1,25 @@ +@property +def type(self): + return self.dtype + +def get_bus(self): + return self.bus + +def get_process(self, process_name): + process_name_lc = process_name.lower() + matching = [ + process + for process in self.enumerate_processes() + if fnmatch.fnmatchcase(process.name.lower(), process_name_lc) + ] + if len(matching) == 1: + return matching[0] + if len(matching) > 1: + matches = ", ".join(f"{p.name} (pid: {p.pid})" for p in matching) + raise _frida.ProcessNotFoundError(f"ambiguous name; it matches: {matches}") + raise _frida.ProcessNotFoundError(f"unable to find process with name '{process_name}'") + +def _pid_of(self, target): + if isinstance(target, str): + return self.get_process(target).pid + return target diff --git a/frida/frida_bindgen/assets/facade_device_members_aio.py b/frida/frida_bindgen/assets/facade_device_members_aio.py new file mode 100644 index 0000000..9f4fc01 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_device_members_aio.py @@ -0,0 +1,25 @@ +@property +def type(self): + return self.dtype + +def get_bus(self): + return self.bus + +async def get_process(self, process_name): + process_name_lc = process_name.lower() + matching = [ + process + for process in await self.enumerate_processes() + if fnmatch.fnmatchcase(process.name.lower(), process_name_lc) + ] + if len(matching) == 1: + return matching[0] + if len(matching) > 1: + matches = ", ".join(f"{p.name} (pid: {p.pid})" for p in matching) + raise _frida.ProcessNotFoundError(f"ambiguous name; it matches: {matches}") + raise _frida.ProcessNotFoundError(f"unable to find process with name '{process_name}'") + +async def _pid_of(self, target): + if isinstance(target, str): + return (await self.get_process(target)).pid + return target diff --git a/frida/frida_bindgen/assets/facade_interface.py b/frida/frida_bindgen/assets/facade_interface.py new file mode 100644 index 0000000..8520e69 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_interface.py @@ -0,0 +1,12 @@ +class _Implementation: + def _frida_dispatch(self, name, args, completion): + def run(): + try: + result = _unwrap(getattr(self, name)(*[_wrap(a) for a in args])) + error = None + except Exception as e: + result = None + error = e + _frida._complete_request(completion, result, error) + + threading.Thread(target=run, daemon=True).start() diff --git a/frida/frida_bindgen/assets/facade_interface_aio.py b/frida/frida_bindgen/assets/facade_interface_aio.py new file mode 100644 index 0000000..331edad --- /dev/null +++ b/frida/frida_bindgen/assets/facade_interface_aio.py @@ -0,0 +1,17 @@ +class _Implementation: + def _frida_dispatch(self, name, args, completion): + loop = self._loop + + async def run(): + try: + result = getattr(self, name)(*[_wrap(a) for a in args]) + if asyncio.iscoroutine(result): + result = await result + result = _unwrap(result) + error = None + except Exception as e: + result = None + error = e + _frida._complete_request(completion, result, error) + + _dispatch(loop, lambda: loop.create_task(run())) diff --git a/frida/frida_bindgen/assets/facade_iostream.py b/frida/frida_bindgen/assets/facade_iostream.py new file mode 100644 index 0000000..517c163 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_iostream.py @@ -0,0 +1,25 @@ +def close(self): + _invoke(self._impl.close_async, (0,)) + +def read(self, count): + return _invoke(self._impl.input_stream.read_bytes_async, (count, 0)) + +def read_all(self, count): + chunks = [] + remaining = count + while remaining > 0: + chunk = _invoke(self._impl.input_stream.read_bytes_async, (remaining, 0)) + if not chunk: + break + chunks.append(chunk) + remaining -= len(chunk) + return b"".join(chunks) + +def write(self, data): + return _invoke(self._impl.output_stream.write_bytes_async, (data, 0)) + +def write_all(self, data): + view = memoryview(data) + while len(view) != 0: + written = _invoke(self._impl.output_stream.write_bytes_async, (bytes(view), 0)) + view = view[written:] diff --git a/frida/frida_bindgen/assets/facade_iostream_aio.py b/frida/frida_bindgen/assets/facade_iostream_aio.py new file mode 100644 index 0000000..e18a85a --- /dev/null +++ b/frida/frida_bindgen/assets/facade_iostream_aio.py @@ -0,0 +1,25 @@ +async def close(self): + await _invoke(self._impl.close_async, (0,)) + +async def read(self, count): + return await _invoke(self._impl.input_stream.read_bytes_async, (count, 0)) + +async def read_all(self, count): + chunks = [] + remaining = count + while remaining > 0: + chunk = await _invoke(self._impl.input_stream.read_bytes_async, (remaining, 0)) + if not chunk: + break + chunks.append(chunk) + remaining -= len(chunk) + return b"".join(chunks) + +async def write(self, data): + return await _invoke(self._impl.output_stream.write_bytes_async, (data, 0)) + +async def write_all(self, data): + view = memoryview(data) + while len(view) != 0: + written = await _invoke(self._impl.output_stream.write_bytes_async, (bytes(view), 0)) + view = view[written:] diff --git a/frida/frida_bindgen/assets/facade_portal.py b/frida/frida_bindgen/assets/facade_portal.py new file mode 100644 index 0000000..8a49964 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_portal.py @@ -0,0 +1,37 @@ +def _setup(self): + self._authenticated_handlers = [] + self._message_handlers = [] + self._impl.on("authenticated", self._on_authenticated) + self._impl.on("message", self._on_message) + +def on(self, signal, callback): + if signal == "authenticated": + self._authenticated_handlers.append(callback) + elif signal == "message": + self._message_handlers.append(callback) + else: + self._impl.on(signal, _make_signal_handler(callback)) + +def off(self, signal, callback): + if signal == "authenticated": + self._authenticated_handlers.remove(callback) + elif signal == "message": + self._message_handlers.remove(callback) + else: + self._impl.off(signal, callback) + +def _on_authenticated(self, connection_id, raw_session_info): + session_info = json.loads(raw_session_info) + for handler in list(self._authenticated_handlers): + try: + handler(connection_id, session_info) + except Exception: + traceback.print_exc() + +def _on_message(self, connection_id, raw_message, data): + message = json.loads(raw_message) + for handler in list(self._message_handlers): + try: + handler(connection_id, message, data) + except Exception: + traceback.print_exc() diff --git a/frida/frida_bindgen/assets/facade_portal_aio.py b/frida/frida_bindgen/assets/facade_portal_aio.py new file mode 100644 index 0000000..1ee98b7 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_portal_aio.py @@ -0,0 +1,38 @@ +def _setup(self): + self._authenticated_handlers = [] + self._message_handlers = [] + self._loop = asyncio.get_event_loop() + self._impl.on("authenticated", self._on_authenticated) + self._impl.on("message", self._on_message) + +def on(self, signal, callback): + if signal == "authenticated": + self._authenticated_handlers.append(callback) + elif signal == "message": + self._message_handlers.append(callback) + else: + self._impl.on(signal, _make_signal_handler(callback)) + +def off(self, signal, callback): + if signal == "authenticated": + self._authenticated_handlers.remove(callback) + elif signal == "message": + self._message_handlers.remove(callback) + else: + self._impl.off(signal, callback) + +def _on_authenticated(self, connection_id, raw_session_info): + session_info = json.loads(raw_session_info) + for handler in list(self._authenticated_handlers): + _dispatch(self._loop, self._deliver, handler, connection_id, session_info) + +def _on_message(self, connection_id, raw_message, data): + message = json.loads(raw_message) + for handler in list(self._message_handlers): + _dispatch(self._loop, self._deliver, handler, connection_id, message, data) + +def _deliver(self, handler, *args): + try: + handler(*args) + except Exception: + traceback.print_exc() diff --git a/frida/frida_bindgen/assets/facade_rpc_helpers.py b/frida/frida_bindgen/assets/facade_rpc_helpers.py new file mode 100644 index 0000000..31fb5ff --- /dev/null +++ b/frida/frida_bindgen/assets/facade_rpc_helpers.py @@ -0,0 +1,38 @@ +def _to_camel_case(name): + result = "" + capitalize = False + for ch in name: + if ch == "_": + capitalize = True + elif capitalize: + result += ch.upper() + capitalize = False + else: + result += ch + return result + + +class RPCException(Exception): + def __str__(self): + return str(self.args[2]) if len(self.args) >= 3 else str(self.args[0]) + + +class _ScriptExports: + def __init__(self, script): + self._script = script + + def __getattr__(self, name): + script = self._script + js_name = _to_camel_case(name) + + def method(*args): + if args and isinstance(args[-1], bytes): + params, data = list(args[:-1]), args[-1] + else: + params, data = list(args), None + return script._rpc_request(["call", js_name, params], data) + + return method + + def __dir__(self): + return self._script._list_exports() diff --git a/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py b/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py new file mode 100644 index 0000000..aedeeee --- /dev/null +++ b/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py @@ -0,0 +1,35 @@ +def _to_camel_case(name): + result = "" + capitalize = False + for ch in name: + if ch == "_": + capitalize = True + elif capitalize: + result += ch.upper() + capitalize = False + else: + result += ch + return result + + +class RPCException(Exception): + def __str__(self): + return str(self.args[2]) if len(self.args) >= 3 else str(self.args[0]) + + +class _ScriptExports: + def __init__(self, script): + self._script = script + + def __getattr__(self, name): + script = self._script + js_name = _to_camel_case(name) + + def method(*args): + if args and isinstance(args[-1], bytes): + params, data = list(args[:-1]), args[-1] + else: + params, data = list(args), None + return script._rpc_request(["call", js_name, params], data) + + return method diff --git a/frida/frida_bindgen/assets/facade_rpc_members.py b/frida/frida_bindgen/assets/facade_rpc_members.py new file mode 100644 index 0000000..d4c20d7 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_rpc_members.py @@ -0,0 +1,111 @@ +def _setup(self): + self.exports_sync = _ScriptExports(self) + self._message_handlers = [] + self._log_handler = self.default_log_handler + self._pending = {} + self._next_request_id = 1 + self._cond = threading.Condition() + self._impl.on("message", self._on_message) + self._impl.on("destroyed", self._on_destroyed) + +@property +def exports(self): + return self.exports_sync + +def on(self, signal, callback): + if signal == "message": + self._message_handlers.append(callback) + else: + self._impl.on(signal, _make_signal_handler(callback)) + +def off(self, signal, callback): + if signal == "message": + self._message_handlers.remove(callback) + else: + self._impl.off(signal, callback) + +def get_log_handler(self): + return self._log_handler + +def set_log_handler(self, handler): + self._log_handler = handler + +def default_log_handler(self, level, text): + if level == "info": + print(text, file=sys.stdout) + else: + print(text, file=sys.stderr) + +def list_exports_sync(self): + return self._rpc_request(["list"]) + +def list_exports(self): + return self.list_exports_sync() + +def _list_exports(self): + return self.list_exports_sync() + +def _rpc_request(self, args, data=None): + outcome = {} + cond = self._cond + + def complete(value, error): + with cond: + outcome["value"] = value + outcome["error"] = error + cond.notify_all() + + with cond: + request_id = self._next_request_id + self._next_request_id += 1 + self._pending[request_id] = complete + + if self._impl.is_destroyed(): + self._on_destroyed() + else: + self.post(["frida:rpc", request_id] + list(args), data) + + with cond: + while not outcome: + cond.wait() + + if outcome["error"] is not None: + raise outcome["error"] + return outcome["value"] + +def _on_message(self, raw_message, data): + message = json.loads(raw_message) + mtype = message["type"] + payload = message.get("payload") + if mtype == "log": + self._log_handler(message["level"], payload) + elif mtype == "send" and isinstance(payload, list) and payload[:1] == ["frida:rpc"]: + self._on_rpc_message(payload[1], payload[2], payload[3:], data) + else: + for handler in list(self._message_handlers): + try: + handler(message, data) + except Exception: + traceback.print_exc() + +def _on_destroyed(self): + while True: + with self._cond: + pending_ids = list(self._pending.keys()) + complete = self._pending.pop(pending_ids[0]) if pending_ids else None + if complete is None: + break + complete(None, _frida.InvalidOperationError("script has been destroyed")) + +def _on_rpc_message(self, request_id, operation, params, data): + if operation not in ("ok", "error"): + return + complete = self._pending.pop(request_id, None) + if complete is None: + return + if operation == "error": + complete(None, RPCException(*params[0:3])) + elif data is not None: + complete((params[1], data) if len(params) > 1 else data, None) + else: + complete(params[0], None) diff --git a/frida/frida_bindgen/assets/facade_rpc_members_aio.py b/frida/frida_bindgen/assets/facade_rpc_members_aio.py new file mode 100644 index 0000000..672194c --- /dev/null +++ b/frida/frida_bindgen/assets/facade_rpc_members_aio.py @@ -0,0 +1,98 @@ +def _setup(self): + self.exports = _ScriptExports(self) + self._message_handlers = [] + self._log_handler = self.default_log_handler + self._pending = {} + self._next_request_id = 1 + self._loop = asyncio.get_event_loop() + self._impl.on("message", self._on_message) + self._impl.on("destroyed", self._on_destroyed) + +def on(self, signal, callback): + if signal == "message": + self._message_handlers.append(callback) + else: + self._impl.on(signal, _make_signal_handler(callback)) + +def off(self, signal, callback): + if signal == "message": + self._message_handlers.remove(callback) + else: + self._impl.off(signal, callback) + +def get_log_handler(self): + return self._log_handler + +def set_log_handler(self, handler): + self._log_handler = handler + +def default_log_handler(self, level, text): + if level == "info": + print(text, file=sys.stdout) + else: + print(text, file=sys.stderr) + +async def list_exports(self): + return await self._rpc_request(["list"]) + +def _rpc_request(self, args, data=None): + loop = asyncio.get_running_loop() + future = loop.create_future() + + def complete(value, error): + if future.done(): + return + if error is not None: + future.set_exception(error) + else: + future.set_result(value) + + request_id = self._next_request_id + self._next_request_id += 1 + self._pending[request_id] = lambda value, error: _dispatch(loop, complete, value, error) + + if self._impl.is_destroyed(): + self._on_destroyed() + else: + self.post(["frida:rpc", request_id] + list(args), data) + + return future + +def _on_message(self, raw_message, data): + message = json.loads(raw_message) + mtype = message["type"] + payload = message.get("payload") + if mtype == "log": + _dispatch(self._loop, self._log_handler, message["level"], payload) + elif mtype == "send" and isinstance(payload, list) and payload[:1] == ["frida:rpc"]: + self._on_rpc_message(payload[1], payload[2], payload[3:], data) + else: + for handler in list(self._message_handlers): + _dispatch(self._loop, self._deliver_message, handler, message, data) + +def _deliver_message(self, handler, message, data): + try: + handler(message, data) + except Exception: + traceback.print_exc() + +def _on_destroyed(self): + while True: + pending_ids = list(self._pending.keys()) + complete = self._pending.pop(pending_ids[0]) if pending_ids else None + if complete is None: + break + complete(None, _frida.InvalidOperationError("script has been destroyed")) + +def _on_rpc_message(self, request_id, operation, params, data): + if operation not in ("ok", "error"): + return + complete = self._pending.pop(request_id, None) + if complete is None: + return + if operation == "error": + complete(None, RPCException(*params[0:3])) + elif data is not None: + complete((params[1], data) if len(params) > 1 else data, None) + else: + complete(params[0], None) diff --git a/frida/frida_bindgen/assets/facade_toplevel.py b/frida/frida_bindgen/assets/facade_toplevel.py new file mode 100644 index 0000000..90fa625 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_toplevel.py @@ -0,0 +1,63 @@ +__version__ = _frida.__version__ + + +def get_device_manager(): + return _wrap(_frida.get_device_manager()) + + +def get_device(id, timeout=0): + return get_device_manager().get_device_by_id(id, int(timeout * 1000)) + + +def get_device_matching(predicate, timeout=0): + return get_device_manager().get_device_matching(predicate, timeout) + + +def get_local_device(): + return get_device_manager().get_device_by_type("local", 0) + + +def get_remote_device(): + return get_device_manager().get_device_by_type("remote", 0) + + +def get_usb_device(timeout=0): + return get_device_manager().get_device_by_type("usb", int(timeout * 1000)) + + +def enumerate_devices(): + return get_device_manager().enumerate_devices() + + +def query_system_parameters(): + return get_local_device().query_system_parameters() + + +def spawn(program, argv=None, envp=None, env=None, cwd=None, stdio=None, **aux): + return get_local_device().spawn( + program, argv=argv, envp=envp, env=env, cwd=cwd, stdio=stdio, **aux + ) + + +def resume(target): + return get_local_device().resume(target) + + +def kill(target): + return get_local_device().kill(target) + + +def attach(target, **kwargs): + return get_local_device().attach(target, **kwargs) + + +def inject_library_file(target, path, entrypoint, data): + return get_local_device().inject_library_file(target, path, entrypoint, data) + + +def inject_library_blob(target, blob, entrypoint, data): + return get_local_device().inject_library_blob(target, blob, entrypoint, data) + + +def shutdown(): + get_device_manager().close() diff --git a/frida/frida_bindgen/assets/facade_toplevel_aio.py b/frida/frida_bindgen/assets/facade_toplevel_aio.py new file mode 100644 index 0000000..e601a4e --- /dev/null +++ b/frida/frida_bindgen/assets/facade_toplevel_aio.py @@ -0,0 +1,64 @@ +__version__ = _frida.__version__ + + +def get_device_manager(): + return _wrap(_frida.get_device_manager()) + + +async def get_device(id, timeout=0): + return await get_device_manager().get_device_by_id(id, int(timeout * 1000)) + + +async def get_device_matching(predicate, timeout=0): + return await get_device_manager().get_device_matching(predicate, timeout) + + +async def get_local_device(): + return await get_device_manager().get_device_by_type("local", 0) + + +async def get_remote_device(): + return await get_device_manager().get_device_by_type("remote", 0) + + +async def get_usb_device(timeout=0): + return await get_device_manager().get_device_by_type("usb", int(timeout * 1000)) + + +async def enumerate_devices(): + return await get_device_manager().enumerate_devices() + + +async def query_system_parameters(): + return await (await get_local_device()).query_system_parameters() + + +async def spawn(program, argv=None, envp=None, env=None, cwd=None, stdio=None, **aux): + device = await get_local_device() + return await device.spawn( + program, argv=argv, envp=envp, env=env, cwd=cwd, stdio=stdio, **aux + ) + + +async def resume(target): + return await (await get_local_device()).resume(target) + + +async def kill(target): + return await (await get_local_device()).kill(target) + + +async def attach(target, **kwargs): + return await (await get_local_device()).attach(target, **kwargs) + + +async def inject_library_file(target, path, entrypoint, data): + return await (await get_local_device()).inject_library_file(target, path, entrypoint, data) + + +async def inject_library_blob(target, blob, entrypoint, data): + return await (await get_local_device()).inject_library_blob(target, blob, entrypoint, data) + + +async def shutdown(): + await get_device_manager().close() diff --git a/frida/frida_bindgen/assets/module_get_device_manager.c b/frida/frida_bindgen/assets/module_get_device_manager.c new file mode 100644 index 0000000..2445c7d --- /dev/null +++ b/frida/frida_bindgen/assets/module_get_device_manager.c @@ -0,0 +1,13 @@ +static PyObject * +PyFrida_get_device_manager (PyObject * module, + PyObject * args) +{ + static PyObject * manager = NULL; + + if (manager == NULL) + manager = PyObject_CallFunction ((PyObject *) PYFRIDA_TYPE_OBJECT (DeviceManager), NULL); + + Py_IncRef (manager); + + return manager; +} diff --git a/frida/frida_bindgen/cli.py b/frida/frida_bindgen/cli.py new file mode 100644 index 0000000..78bde8e --- /dev/null +++ b/frida/frida_bindgen/cli.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import argparse +from io import StringIO +from pathlib import Path + +from . import codegen +from .customization import load_customizations +from .loader import compute_model + + +def main(): + run(build_arguments()) + + +def build_arguments() -> argparse.Namespace: + p = argparse.ArgumentParser(description="Generate the Python bindings for Frida.") + p.add_argument( + "--frida-gir", + required=True, + type=Path, + help="Path to the Frida .gir file.", + ) + p.add_argument( + "--frida-header", + type=Path, + help="Path to the Frida C header; when provided, unavailable GIR types are omitted.", + ) + p.add_argument( + "--glib-gir", + required=True, + type=Path, + help="Path to the GLib .gir file.", + ) + p.add_argument( + "--gobject-gir", + required=True, + type=Path, + help="Path to the GObject .gir file.", + ) + p.add_argument( + "--gio-gir", + required=True, + type=Path, + help="Path to the GIO .gir file.", + ) + p.add_argument( + "--output-py", + required=True, + type=Path, + help="Path to the output .py file.", + ) + p.add_argument( + "--output-pyi", + required=True, + type=Path, + help="Path to the output .pyi file.", + ) + p.add_argument( + "--output-aio", + required=True, + type=Path, + help="Path to the output asyncio facade .py file.", + ) + p.add_argument( + "--output-c", + required=True, + type=Path, + help="Path to the output C file for the Python extension.", + ) + return p.parse_args() + + +def run(args: argparse.Namespace) -> None: + customizations = load_customizations() + model = compute_model( + args.frida_gir, + args.glib_gir, + args.gobject_gir, + args.gio_gir, + customizations, + args.frida_header, + ) + + artefacts = codegen.generate_all(model) + + with OutputFile(args.output_py) as f: + f.write(artefacts["py"]) + with OutputFile(args.output_aio) as f: + f.write(artefacts["aio"]) + with OutputFile(args.output_pyi) as f: + f.write(artefacts["pyi"]) + with OutputFile(args.output_c) as f: + f.write(artefacts["c"]) + + +class OutputFile: + def __init__(self, output_path): + self._output_path = output_path + self._io = StringIO() + + def __enter__(self): + return self._io + + def __exit__(self, *exc): + result = self._io.getvalue() + if self._output_path.exists(): + existing_contents = self._output_path.read_text(encoding="utf-8") + if existing_contents == result: + return False + self._output_path.write_text(result, encoding="utf-8") + return False diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py new file mode 100644 index 0000000..ef0101a --- /dev/null +++ b/frida/frida_bindgen/codegen.py @@ -0,0 +1,2472 @@ +from __future__ import annotations + +import textwrap +from pathlib import Path +from typing import Dict, List, Optional, Tuple, Union + +from frida_bindgen_core import Procedure, Type + +from .model import ( + Enumeration, + InterfaceObjectType, + Method, + Model, + ObjectType, + Parameter, + to_snake_case, +) + +ASSETS_DIR = Path(__file__).resolve().parent / "assets" +CODEGEN_MACROS_H = (ASSETS_DIR / "codegen_macros.h").read_text(encoding="utf-8") +CODEGEN_TYPEDEFS_H = (ASSETS_DIR / "codegen_typedefs.h").read_text(encoding="utf-8") +CODEGEN_STRUCTS_H = (ASSETS_DIR / "codegen_structs.h").read_text(encoding="utf-8") +CODEGEN_GOBJECT_PROTOTYPES = (ASSETS_DIR / "codegen_gobject_prototypes.h").read_text(encoding="utf-8") +CODEGEN_GOBJECT_GLOBALS = (ASSETS_DIR / "codegen_gobject_globals.c").read_text(encoding="utf-8") +CODEGEN_GOBJECT_METHODS = (ASSETS_DIR / "codegen_gobject_methods.c").read_text(encoding="utf-8") +FACADE_INTERFACE = (ASSETS_DIR / "facade_interface.py").read_text(encoding="utf-8") +FACADE_INTERFACE_AIO = (ASSETS_DIR / "facade_interface_aio.py").read_text(encoding="utf-8") + +LEGACY_ERROR_MEMBERS = [ + ("FRIDA_ERROR_SERVER_NOT_RUNNING", "ServerNotRunning"), + ("FRIDA_ERROR_EXECUTABLE_NOT_FOUND", "ExecutableNotFound"), + ("FRIDA_ERROR_EXECUTABLE_NOT_SUPPORTED", "ExecutableNotSupported"), + ("FRIDA_ERROR_PROCESS_NOT_FOUND", "ProcessNotFound"), + ("FRIDA_ERROR_PROCESS_NOT_RESPONDING", "ProcessNotResponding"), + ("FRIDA_ERROR_INVALID_ARGUMENT", "InvalidArgument"), + ("FRIDA_ERROR_INVALID_OPERATION", "InvalidOperation"), + ("FRIDA_ERROR_PERMISSION_DENIED", "PermissionDenied"), + ("FRIDA_ERROR_ADDRESS_IN_USE", "AddressInUse"), + ("FRIDA_ERROR_TIMED_OUT", "TimedOut"), + ("FRIDA_ERROR_NOT_SUPPORTED", "NotSupported"), + ("FRIDA_ERROR_PROTOCOL", "Protocol"), + ("FRIDA_ERROR_TRANSPORT", "Transport"), +] + + +def read_asset(name: str) -> str: + return (ASSETS_DIR / name).read_text(encoding="utf-8") + + +def error_members(model: Model) -> List[Tuple[str, str]]: + if model.error_domain is None: + return LEGACY_ERROR_MEMBERS + return [(member.c_identifier, member.js_name) for member in model.error_domain.members] + + +FACADE_RUNTIME = """ +_to_json = json.dumps + + +def _wrap(impl): + if impl is None: + return None + cls = _WRAPPERS.get(type(impl).__name__) + if cls is None: + return impl + wrapper = cls.__new__(cls) + wrapper._impl = impl + setup = getattr(wrapper, "_setup", None) + if setup is not None: + setup() + return wrapper + + +def _unwrap(obj): + if obj is None: + return None + return getattr(obj, "_impl", obj) + + +def _make_signal_handler(callback): + def handler(*args): + return callback(*[_wrap(arg) for arg in args]) + + handler._frida_original = callback + handler.__signature__ = inspect.signature(callback) + return handler + + +def _to_envp(value): + if isinstance(value, dict): + return [f"{key}={val}" for key, val in value.items()] + return value + + +def _make_options(cls, values, selectors): + options = cls() + for name, value in values.items(): + if value is None: + continue + select = selectors.get(name) + if select is None: + setattr(options, name, value) + else: + add = getattr(options, select) + for element in value: + add(element) + return options + + +_current_cancellable = threading.local() + + +def _current_cancellable_get(): + stack = getattr(_current_cancellable, "stack", None) + return stack[-1] if stack else None + + +def _invoke(method, args): + completed = threading.Event() + outcome = {} + + def on_complete(result, error): + outcome["result"] = result + outcome["error"] = error + completed.set() + + cancellable = _current_cancellable_get() + if cancellable is None: + method(*args, on_complete) + completed.wait() + else: + def on_cancelled(): + completed.set() + + method(*args, on_complete, cancellable._impl) + cancellable._impl.on("cancelled", on_cancelled) + try: + completed.wait() + finally: + cancellable._impl.off("cancelled", on_cancelled) + + if "error" not in outcome: + raise _frida.OperationCancelledError("operation was cancelled") + error = outcome["error"] + if error is not None: + raise error + return outcome["result"] +""" + +AIO_RUNTIME = """ +_to_json = json.dumps + + +def _wrap(impl): + if impl is None: + return None + cls = _WRAPPERS.get(type(impl).__name__) + if cls is None: + return impl + wrapper = cls.__new__(cls) + wrapper._impl = impl + setup = getattr(wrapper, "_setup", None) + if setup is not None: + setup() + return wrapper + + +def _unwrap(obj): + if obj is None: + return None + return getattr(obj, "_impl", obj) + + +def _make_signal_handler(callback): + def handler(*args): + return callback(*[_wrap(arg) for arg in args]) + + handler._frida_original = callback + handler.__signature__ = inspect.signature(callback) + return handler + + +def _to_envp(value): + if isinstance(value, dict): + return [f"{key}={val}" for key, val in value.items()] + return value + + +def _make_options(cls, values, selectors): + options = cls() + for name, value in values.items(): + if value is None: + continue + select = selectors.get(name) + if select is None: + setattr(options, name, value) + else: + add = getattr(options, select) + for element in value: + add(element) + return options + + +_current_cancellable = contextvars.ContextVar("frida_current_cancellable", default=None) + + +def _dispatch(loop, callback, *args): + try: + loop.call_soon_threadsafe(callback, *args) + except RuntimeError: + pass + + +async def _invoke(method, args): + loop = asyncio.get_running_loop() + future = loop.create_future() + ambient = _current_cancellable.get() + cancellable = ambient._impl if ambient is not None else _frida.Cancellable() + + def deliver(result, error): + if future.done(): + return + if error is not None: + future.set_exception(error) + else: + future.set_result(result) + + def on_complete(result, error): + _dispatch(loop, deliver, result, error) + + method(*args, on_complete, cancellable) + + try: + return await future + except asyncio.CancelledError: + cancellable.cancel() + raise +""" + + +def generate_all(model: Model) -> Dict[str, str]: + return { + "py": generate_py(model), + "aio": generate_aio(model), + "pyi": generate_extension_pyi(model), + "c": generate_extension_c(model), + } + + +def generate_py(model: Model) -> str: + lines = [ + "from . import _frida", + "import fnmatch", + "import inspect", + "import time", + "import json", + "import sys", + "import threading", + "import traceback", + "", + "", + *generate_py_exception_reexports(model), + "", + FACADE_RUNTIME.strip(), + "", + "", + FACADE_INTERFACE.strip(), + ] + + for helper in facade_module_helpers(model, aio=False): + lines += ["", "", read_asset(helper).strip()] + + for otype in facade_object_types(model): + lines.append("") + lines.append("") + lines.append(generate_py_class(otype, model)) + + lines.append("") + lines.append("") + lines.append("_WRAPPERS = {") + for otype in facade_object_types(model): + lines.append(f' "{otype.py_name}": {otype.py_name},') + lines.append("}") + lines.append("") + lines.append("") + lines.append("from . import aio") + lines.append("") + lines.append("core = sys.modules[__name__]") + lines.append('sys.modules[__name__ + ".core"] = core') + lines.append("") + + return "\n".join(lines) + + +def facade_object_types(model: Model) -> List[ObjectType]: + return [t for t in model.regular_object_types if not t.is_frida_options] + + +def facade_module_helpers(model: Model, aio: bool) -> List[str]: + seen = set() + result = [] + for otype in model.object_types.values(): + custom_code = otype.custom_code + if custom_code is None or custom_code.helpers is None: + continue + asset = custom_code.helpers[1] if aio else custom_code.helpers[0] + if asset not in seen: + seen.add(asset) + result.append(asset) + return result + + +def generate_aio(model: Model) -> str: + lines = [ + "from . import _frida", + "import asyncio", + "import contextvars", + "import time", + "import fnmatch", + "import inspect", + "import json", + "import sys", + "import traceback", + "", + "", + *generate_py_exception_reexports(model), + "", + AIO_RUNTIME.strip(), + "", + "", + FACADE_INTERFACE_AIO.strip(), + ] + + for helper in facade_module_helpers(model, aio=True): + lines += ["", "", read_asset(helper).strip()] + + for otype in facade_object_types(model): + lines.append("") + lines.append("") + lines.append(generate_aio_class(otype, model)) + + lines.append("") + lines.append("") + lines.append("_WRAPPERS = {") + for otype in facade_object_types(model): + lines.append(f' "{otype.py_name}": {otype.py_name},') + lines.append("}") + lines.append("") + + return "\n".join(lines) + + +def generate_aio_class(otype: ObjectType, model: Model) -> str: + if implementable_interface(otype): + return generate_aio_interface_facade(otype) + + members = [generate_facade_init(otype)] + if otype.signals and not otype.provides_signals: + members.append(generate_facade_signals()) + for method in otype.methods: + if method.suppress_facade: + continue + member = generate_py_property(method, model) or generate_aio_method(method, model) + if member is not None: + members.append(member) + custom_code = otype.custom_code + if custom_code is not None and custom_code.members is not None: + members.append(generate_custom_members(custom_code.members, aio=True)) + repr_member = generate_facade_repr(otype) + if repr_member is not None: + members.append(repr_member) + if not members: + members.append(" pass") + + return f"class {otype.py_name}:\n" + "\n\n".join(members) + + +def generate_custom_members(members: Tuple[str, str], aio: bool) -> str: + asset = members[1] if aio else members[0] + return textwrap.indent(read_asset(asset).strip(), " ") + + +def generate_aio_method(method: Method, model: Model) -> Optional[str]: + if method.is_property_accessor: + return None + if method.as_property: + return generate_facade_bool_property(method) + custom_logic = method.custom_logic + if custom_logic is not None: + return generate_custom_facade_method(method, custom_logic, model, awaitable=method.is_async) + if method.is_async: + return generate_aio_async_method(method, model) + return generate_py_sync_method(method, model) + + +def generate_facade_bool_property(method: Method) -> str: + return f""" @property + def {method.name}(self): + return self._impl.{method.name}()""" + + +def generate_aio_async_method(method: Method, model: Model) -> Optional[str]: + parts = build_facade_async_parts(method, model) + if parts is None: + return None + signature, args = parts + + call = f"await _invoke(self._impl.{method.name}, ({args}))" + if method.return_value is not None: + call = wrap_result(call, method.return_value.type, model) + + return f""" async def {method.name}({signature}): + return {call}""" + + +def generate_custom_facade_method( + method: Method, logic: Union[str, Tuple[str, str]], model: Model, awaitable: bool +) -> str: + signature = ", ".join(["self"] + method.custom_facade_params) + if method.is_async: + invoke = f"_invoke(self._impl.{method.name}, ({method.facade_call_args}))" + call = f"await {invoke}" if awaitable else invoke + else: + call = f"self._impl.{method.name}({method.facade_call_args})" + if method.return_value is not None: + call = wrap_result(call, method.return_value.type, model) + + if isinstance(logic, tuple): + logic = logic[1] if awaitable else logic[0] + body = textwrap.indent(logic.strip(), " " * 8) + keyword = "async def" if awaitable else "def" + + return f""" {keyword} {method.name}({signature}): +{body} + return {call}""" + + +def generate_py_exception_reexports(model: Model) -> List[str]: + names = [f"{name}Error" for _, name in error_members(model)] + names.append("OperationCancelledError") + return [f"{name} = _frida.{name}" for name in names] + + +def generate_py_class(otype: ObjectType, model: Model) -> str: + if implementable_interface(otype): + return generate_interface_facade(otype) + + members = [generate_facade_init(otype)] + if otype.signals and not otype.provides_signals: + members.append(generate_facade_signals()) + for method in otype.methods: + if method.suppress_facade: + continue + member = generate_py_property(method, model) or generate_py_method(method, model) + if member is not None: + members.append(member) + custom_code = otype.custom_code + if custom_code is not None and custom_code.members is not None: + members.append(generate_custom_members(custom_code.members, aio=False)) + repr_member = generate_facade_repr(otype) + if repr_member is not None: + members.append(repr_member) + if not members: + members.append(" pass") + + return f"class {otype.py_name}:\n" + "\n\n".join(members) + + +def generate_interface_facade(otype: ObjectType) -> str: + return f"""class {otype.py_name}(_Implementation): + def __init__(self): + self._impl = _frida.{otype.py_name}(self)""" + + +def generate_aio_interface_facade(otype: ObjectType) -> str: + return f"""class {otype.py_name}(_Implementation): + def __init__(self): + self._loop = asyncio.get_event_loop() + self._impl = _frida.{otype.py_name}(self)""" + + +def generate_facade_init(otype: ObjectType) -> str: + custom_logic = otype.constructor_custom_logic + if custom_logic is not None: + signature = ", ".join(["self"] + otype.constructor_custom_params) + body = textwrap.indent(custom_logic.strip(), " " * 8) + return f""" def __init__({signature}): +{body}""" + + return f""" def __init__(self, *args, **kwargs): + self._impl = _frida.{otype.py_name}( + *[_unwrap(a) for a in args], + **{{k: _unwrap(v) for k, v in kwargs.items()}}, + ) + setup = getattr(self, "_setup", None) + if setup is not None: + setup()""" + + +def generate_facade_signals() -> str: + return """ def on(self, signal, callback): + self._impl.on(signal, _make_signal_handler(callback)) + + def off(self, signal, callback): + self._impl.off(signal, callback)""" + + +def facade_repr_property_names(otype: ObjectType) -> List[str]: + names = [] + for method in otype.methods: + name = property_name_from_accessor(method) + if name is None or property_getter_marshal(method) is None: + continue + type_name = method.return_value.type.name + is_scalar = ( + type_name == "utf8" + or type_name == "gboolean" + or type_name in PYARG_NUMERIC_FORMATS + or resolve_enumeration(method.return_value.type, otype.model) is not None + ) + if is_scalar: + names.append(name) + return names + + +def generate_facade_repr(otype: ObjectType) -> Optional[str]: + names = facade_repr_property_names(otype) + if not names: + return None + fields = ", ".join(f"{name}={{self.{name}!r}}" for name in names) + return f''' def __repr__(self): + return f"{otype.py_name}({fields})"''' + + +def generate_py_property(method: Method, model: Model) -> Optional[str]: + name = property_name_from_accessor(method) + if name is None or property_getter_marshal(method) is None: + return None + + access = wrap_result(f"self._impl.{name}", method.return_value.type, model) + prop = f""" @property + def {name}(self): + return {access}""" + + set_method = next((m for m in method.object_type.methods if m.name == f"set_{name}"), None) + if set_method is not None and property_setter_supported(set_method): + prop += f""" + + @{name}.setter + def {name}(self, value): + self._impl.{name} = _unwrap(value)""" + + return prop + + +def generate_py_method(method: Method, model: Model) -> Optional[str]: + if method.is_property_accessor: + return None + if method.as_property: + return generate_facade_bool_property(method) + custom_logic = method.custom_logic + if custom_logic is not None: + return generate_custom_facade_method(method, custom_logic, model, awaitable=False) + if method.is_async: + return generate_py_async_method(method, model) + return generate_py_sync_method(method, model) + + +def generate_py_sync_method(method: Method, model: Model) -> Optional[str]: + if synchronous_method_return_marshal(method) is None: + return None + if any(build_sync_param(param) is None for param in method.input_parameters): + return None + + signature = ["self"] + for param in method.input_parameters: + signature.append(f"{param.name}=None" if param.nullable else param.name) + names = ", ".join(param.name for param in method.input_parameters) + + call = f"self._impl.{method.name}({names})" + if method.return_value is not None: + call = wrap_result(call, method.return_value.type, model) + + return f""" def {method.name}({", ".join(signature)}): + return {call}""" + + +def generate_py_async_method(method: Method, model: Model) -> Optional[str]: + if method.is_property_accessor: + return None + + parts = build_facade_async_parts(method, model) + if parts is None: + return None + signature, args = parts + + call = f"_invoke(self._impl.{method.name}, ({args}))" + if method.return_value is not None: + call = wrap_result(call, method.return_value.type, model) + + return f""" def {method.name}({signature}): + return {call}""" + + +def build_facade_async_parts(method: Method, model: Model) -> Optional[Tuple[str, str]]: + signature = ["self"] + args = [] + for param in method.input_parameters: + if param.type.name == "Gio.Cancellable": + continue + if build_async_param(param) is None: + return None + options = resolve_options_type(param.type, model) + if options is not None: + signature.append("**kwargs") + args.append(f"_make_options(_frida.{options.py_name}, kwargs, {build_option_selectors(options)})") + elif resolve_input_object_type(param.type, model) is not None: + signature.append(f"{param.name}=None") + args.append(f"_unwrap({param.name})") + else: + signature.append(param.name) + args.append(param.name) + + if method.return_value is not None: + if build_return_marshal(method.return_value.type, model) is None: + return None + + return ", ".join(signature), "".join(arg + ", " for arg in args) + + +def build_option_selectors(options: ObjectType) -> str: + selectors = {} + otype = options + while otype is not None: + for method in otype.methods: + if method.is_select_method: + selectors[method.select_plural_noun] = method.name + otype = otype.parent + return repr(selectors) + + +def wrap_result(call: str, type: Type, model: Model) -> str: + if resolve_object_type(type, model) is not None: + return f"_wrap({call})" + if resolve_list_type(type, model) is not None: + return f"[_wrap(element) for element in {call}]" + return call + + +def generate_extension_pyi(model: Model) -> str: + lines = [ + "from typing import Any, Callable, Optional", + "", + "", + "__version__: str", + "", + "", + ] + + for _, name in error_members(model): + lines.append(f"class {name}Error(Exception): ...") + lines.append("class OperationCancelledError(Exception): ...") + + for otype in model.regular_object_types: + lines.append("") + lines.append("") + lines += generate_pyi_class(otype, model) + + lines.append("") + + return "\n".join(lines) + + +def generate_pyi_class(otype: ObjectType, model: Model) -> List[str]: + parent = otype.parent + header = f"class {otype.py_name}({parent.py_name}):" if parent is not None else f"class {otype.py_name}:" + + body = [] + + ctor = next(iter(otype.constructors), None) + if ctor is not None and not ctor.throws: + params = pyi_parameters(ctor.input_parameters, model) + if params is not None: + signature = ", ".join(["self"] + params) + body.append(f" def __init__({signature}) -> None: ...") + + if otype.name == "Object": + body.append(" def on(self, signal: str, callback: Callable[..., Any]) -> None: ...") + body.append(" def off(self, signal: str, callback: Callable[..., Any]) -> None: ...") + + for method in otype.methods: + name = property_name_from_accessor(method) + if name is None or property_getter_marshal(method) is None: + continue + body.append(" @property") + body.append(f" def {name}(self) -> {pyi_type(method.return_value.type, model)}: ...") + + for method in otype.methods: + stub = pyi_method(method, model) + if stub is not None: + body.append(stub) + + if not body: + body.append(" ...") + + return [header] + body + + +def pyi_method(method: Method, model: Model) -> Optional[str]: + if method.is_property_accessor: + return None + + if method.is_async: + params = pyi_parameters( + [p for p in method.input_parameters if p.type.name != "Gio.Cancellable"], + model, + builder=build_async_param, + ) + if params is None: + return None + cancellable = next( + (p for p in method.input_parameters if p.type.name == "Gio.Cancellable"), + None, + ) + cancellable_typing = pyi_type(cancellable.type, model) if cancellable is not None else "Any" + signature = ", ".join( + ["self"] + + params + + [ + "callback: Callable[[Any, Optional[Exception]], None]", + f"cancellable: Optional[{cancellable_typing}] = ...", + ] + ) + return f" def {method.name}({signature}) -> None: ..." + + if synchronous_method_return_marshal(method) is None: + return None + params = pyi_parameters(method.input_parameters, model, builder=build_sync_param) + if params is None: + return None + signature = ", ".join(["self"] + params) + ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + return f" def {method.name}({signature}) -> {ret}: ..." + + +def pyi_parameters(params, model: Model, builder=None) -> Optional[List[str]]: + result = [] + for param in params: + if builder is not None and builder(param) is None: + return None + annotation = pyi_type(param.type, model) + result.append(f"{param.name}: {annotation}") + return result + + +def pyi_type(type: Type, model: Model) -> str: + name = type.name + if name == "utf8": + return "str" + if name == "utf8[]": + return "list[str]" + if name == "gboolean": + return "bool" + if name in { + "gint8", + "gint16", + "gint", + "gint32", + "gint64", + "guint8", + "guint16", + "guint", + "guint32", + "guint64", + "gsize", + "gssize", + }: + return "int" + if name in {"gfloat", "gdouble"}: + return "float" + if name == "GLib.Bytes": + return "bytes" + if name == "GLib.HashTable": + return "dict" + if name == "GLib.Variant": + return "Any" + if resolve_enumeration(type, model) is not None: + return "str" + obj = resolve_object_type(type, model) + if obj is not None: + return f'"{obj.py_name}"' + list_type = resolve_list_type(type, model) + if list_type is not None: + _, get = list_accessors(list_type) + return f"list[{pyi_type(get.return_value.type, model)}]" + return "Any" + + +def generate_extension_c(model: Model) -> str: + code = generate_includes() + code += CODEGEN_MACROS_H + code += "\n" + CODEGEN_TYPEDEFS_H + code += generate_object_type_typedefs(model) + code += "\n\n" + CODEGEN_STRUCTS_H + code += "\n" + generate_object_type_structs(model) + code += "\n" + generate_prototypes(model) + code += "\n" + generate_shared_globals(model) + code += "\n" + CODEGEN_GOBJECT_GLOBALS + code += "\n" + generate_object_type_method_definitions(model) + code += "\n" + generate_object_type_getset_definitions(model) + code += "\n" + generate_object_type_toplevel_definitions(model) + for fn in module_functions(model): + code += "\n" + read_asset(fn.asset).strip() + "\n" + code += "\n" + generate_init_function(model) + + for otype in model.object_types.values(): + if otype.name == "Object": + code += "\n" + CODEGEN_GOBJECT_METHODS + continue + if otype.is_frida_list: + code += generate_list_conversion_functions(otype, model) + continue + + if implementable_interface(otype): + code += generate_interface_implementation(otype) + + code += generate_object_type_constructor(otype) + + return code + + +def implementable_interface(otype: ObjectType) -> bool: + if not isinstance(otype, InterfaceObjectType) or not otype.has_abstract_base: + return False + return all(interface_method_supported(method, otype.model) for method in otype.methods) + + +def interface_type_marshalable(type: Type, model: Model) -> bool: + return type.name == "utf8" or resolve_object_type(type, model) is not None + + +def interface_method_supported(method: Method, model: Model) -> bool: + if not method.is_async: + return False + if method.return_value is None: + return False + if not interface_type_marshalable(method.return_value.type, model): + return False + return all( + p.type.name == "Gio.Cancellable" or interface_type_marshalable(p.type, model) for p in method.input_parameters + ) + + +def interface_impl_names(otype: ObjectType): + snake = to_snake_case(otype.name) + return { + "type": f"FridaPython{otype.name}", + "prefix": f"frida_python_{snake}", + "upper": snake.upper(), + "cast": f"FRIDA_PYTHON_{snake.upper()}", + "is": f"FRIDA_IS_PYTHON_{snake.upper()}", + } + + +def generate_interface_implementation(otype: ObjectType) -> str: + n = interface_impl_names(otype) + methods = otype.methods + + forward = "".join( + f"static void {n['prefix']}_{m.name} ({', '.join(m.param_ctypings)});\n" + f"static {m.return_value.type.c} {n['prefix']}_{m.name}_finish ({', '.join(m.finish_param_ctypings)});\n" + f"static void {n['prefix']}_complete_{m.name} (GTask * task, PyObject * value, PyObject * error);\n" + for m in methods + ) + + iface_assignments = "\n".join( + f" iface->{m.name} = {n['prefix']}_{m.name};\n" f" iface->{m.name}_finish = {n['prefix']}_{m.name}_finish;" + for m in methods + ) + + method_functions = "".join(generate_interface_method(otype, m, n) for m in methods) + + return f""" +G_DECLARE_FINAL_TYPE ({n['type']}, {n['prefix']}, FRIDA, PYTHON_{n['upper']}, GObject) + +struct _{n['type']} +{{ + GObject parent; + PyObject * wrapper; +}}; + +static void {n['prefix']}_iface_init (gpointer g_iface, gpointer iface_data); +static void {n['prefix']}_dispose (GObject * object); +{forward} +G_DEFINE_TYPE_EXTENDED ({n['type']}, {n['prefix']}, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE ({otype.get_type} (), {n['prefix']}_iface_init)) + +static {n['type']} * +{n['prefix']}_new (PyObject * wrapper) +{{ + {n['type']} * self = g_object_new ({n['prefix']}_get_type (), NULL); + + self->wrapper = wrapper; + Py_IncRef (wrapper); + + return self; +}} + +static void +{n['prefix']}_class_init ({n['type']}Class * klass) +{{ + G_OBJECT_CLASS (klass)->dispose = {n['prefix']}_dispose; +}} + +static void +{n['prefix']}_iface_init (gpointer g_iface, +{" " * (len(n['prefix']) + len("_iface_init") + 2)}gpointer iface_data) +{{ + {otype.type_struct} * iface = g_iface; + +{iface_assignments} +}} + +static void +{n['prefix']}_init ({n['type']} * self) +{{ +}} + +static void +{n['prefix']}_dispose (GObject * object) +{{ + {n['type']} * self = {n['cast']} (object); + + if (self->wrapper != NULL) + {{ + PyGILState_STATE gstate = PyGILState_Ensure (); + Py_DecRef (self->wrapper); + self->wrapper = NULL; + PyGILState_Release (gstate); + }} + + G_OBJECT_CLASS ({n['prefix']}_parent_class)->dispose (object); +}} + +static int +{otype.c_symbol_prefix}_traverse (PyObject * self, +{" " * (len(otype.c_symbol_prefix) + len("_traverse ("))}visitproc visit, +{" " * (len(otype.c_symbol_prefix) + len("_traverse ("))}void * arg) +{{ + gpointer handle = PY_GOBJECT_HANDLE (self); + + /* + * Only expose the wrapper back-reference to the cyclic GC while nothing but + * this wrapper holds the handle. If frida-core still references it the extra + * ref keeps the wrapper reachable, so hiding the edge prevents the GC from + * collecting an interface implementation that is still in use. + */ + if (handle != NULL && {n['is']} (handle) && + g_atomic_int_get (&((GObject *) handle)->ref_count) == 1) + Py_VISIT ({n['cast']} (handle)->wrapper); + + return 0; +}} + +static int +{otype.c_symbol_prefix}_clear (PyObject * self) +{{ + gpointer handle = PY_GOBJECT_HANDLE (self); + + if (handle != NULL && {n['is']} (handle)) + Py_CLEAR ({n['cast']} (handle)->wrapper); + + return 0; +}} +{method_functions}""" + + +def interface_arg_expr(param: Parameter, model: Model) -> str: + if param.type.name == "utf8": + return f"PyGObject_marshal_string ({param.name})" + objtype = resolve_object_type(param.type, model) + assert objtype is not None + return f"PyGObject_marshal_object ({param.name}, {objtype.get_type} ())" + + +def generate_interface_method(otype: ObjectType, method: Method, n) -> str: + model = otype.model + params = [p for p in method.input_parameters if p.type.name != "Gio.Cancellable"] + arg_exprs = ", ".join(interface_arg_expr(p, model) for p in params) + fmt = "(" + "N" * len(params) + ")" + build_args = f'Py_BuildValue ("{fmt}"{", " + arg_exprs if arg_exprs else ""})' + + cancellable = next( + (p.name for p in method.input_parameters if p.type.name == "Gio.Cancellable"), + "NULL", + ) + + return f""" +static void +{n['prefix']}_{method.name} ({', '.join(method.param_ctypings)}) +{{ + {n['type']} * self = {n['cast']} ({method.cself_name}); + GTask * task; + PyGILState_STATE gstate; + PyObject * completion, * args, * outcome; + + task = g_task_new (self, {cancellable}, callback, user_data); + + gstate = PyGILState_Ensure (); + + completion = PyFrida_make_completion (task, {n['prefix']}_complete_{method.name}); + args = {build_args}; + outcome = PyObject_CallMethod (self->wrapper, "_frida_dispatch", "sNN", "{method.name}", args, completion); + if (outcome != NULL) + Py_DecRef (outcome); + else + PyErr_Print (); + + PyGILState_Release (gstate); +}} + +static {method.return_value.type.c} +{n['prefix']}_{method.name}_finish ({', '.join(method.finish_param_ctypings)}) +{{ + return g_task_propagate_pointer (G_TASK (result), error); +}} + +static void +{n['prefix']}_complete_{method.name} (GTask * task, +{" " * (len(n['prefix']) + len(method.name) + len("_complete_") + 2)}PyObject * value, +{" " * (len(n['prefix']) + len(method.name) + len("_complete_") + 2)}PyObject * error) +{{ +{generate_interface_complete_body(otype, method)} +}} +""" + + +def generate_interface_complete_body(otype: ObjectType, method: Method) -> str: + if method.return_value.type.name == "utf8": + return """\ + gchar * result; + + if (PyFrida_return_error (task, error)) + return; + + if (PyGObject_unmarshal_string (value, &result)) + g_task_return_pointer (task, result, g_free); + else + g_task_return_new_error (task, FRIDA_ERROR, FRIDA_ERROR_INVALID_ARGUMENT, "invalid result");""" + + return f"""\ + {method.return_value.type.c}result; + + if (PyFrida_return_error (task, error)) + return; + + result = (value != Py_None) ? g_object_ref (PY_GOBJECT_HANDLE (value)) : NULL; + g_task_return_pointer (task, result, g_object_unref);""" + + +def generate_includes() -> str: + return """\ +#include +#include + +#define PY_SSIZE_T_CLEAN + +/* + * Don't propagate _DEBUG state to pyconfig as it incorrectly attempts to load + * debug libraries that don't normally ship with Python (e.g. 2.x). Debuggers + * wishing to spelunk the Python core can override this workaround by defining + * _FRIDA_ENABLE_PYDEBUG. + */ +#if defined (_DEBUG) && !defined (_FRIDA_ENABLE_PYDEBUG) +# undef _DEBUG +# include +# define _DEBUG +#else +# include +#endif + +#include + +""" + + +def generate_prototypes(model: Model) -> str: + prototypes = [] + + for otype in model.regular_object_types: + otype_cprefix = otype.c_symbol_prefix + + if otype.name == "Object": + prototypes += [ + "", + CODEGEN_GOBJECT_PROTOTYPES.rstrip(), + ] + else: + prototypes += [ + "", + f"static int {otype_cprefix}_init ({otype_cprefix} * self, PyObject * args, PyObject * kw);", + ] + if implementable_interface(otype): + prototypes += [ + f"static int {otype_cprefix}_traverse (PyObject * self, visitproc visit, void * arg);", + f"static int {otype_cprefix}_clear (PyObject * self);", + ] + + for otype in model.object_types.values(): + if otype.is_frida_list: + prototypes += [ + "", + f"static PyObject * {otype.c_symbol_prefix}_to_value ({otype.c_type} * self);", + ] + + for fn in module_functions(model): + prototypes += [ + "", + f"static PyObject * {fn.c_symbol} (PyObject * module, PyObject * args);", + ] + + return "\n".join(prototypes) + + +def module_functions(model: Model) -> List: + result = [] + for otype in model.object_types.values(): + custom_code = otype.custom_code + if custom_code is None or custom_code.module_functions is None: + continue + result += custom_code.module_functions + return result + + +def generate_shared_globals(model: Model) -> str: + method_entries = [' { "_complete_request", PyFrida_complete_request, METH_VARARGS, NULL },'] + for fn in module_functions(model): + method_entries.append(f' {{ "{fn.py_name}", {fn.c_symbol}, METH_NOARGS, NULL }},') + + return "\n".join( + [ + "", + "static PyMethodDef PyFrida_functions[] =", + "{", + *method_entries, + " { NULL }", + "};", + 'static struct PyModuleDef PyFrida_moduledef = { PyModuleDef_HEAD_INIT, "_frida", "Frida", -1, PyFrida_functions, };', + "", + "static initproc PyGObject_tp_init;", + "static destructor PyGObject_tp_dealloc;", + ] + ) + + +def generate_object_type_toplevel_definitions(model: Model) -> str: + defs = [] + + for otype in model.regular_object_types: + cprefix = otype.c_symbol_prefix + + if otype.name == "Object": + defs.append(f"""PYFRIDA_DEFINE_BASETYPE ("_frida.{otype.py_name}", {otype.py_name}, g_object_unref, + {{ Py_tp_doc, "{otype.name}" }}, + {{ Py_tp_init, {cprefix}_init }}, + {{ Py_tp_dealloc, {cprefix}_dealloc }}, + {{ Py_tp_methods, {cprefix}_methods }}, + {{ Py_tp_getset, {cprefix}_getsets }}, +);""") + continue + + parent = otype.parent + parent_name = parent.py_name if parent is not None else "GObject" + + if implementable_interface(otype): + defs.append( + f"""PYFRIDA_DEFINE_GC_TYPE ("_frida.{otype.py_name}", {otype.py_name}, {parent_name}, g_object_unref, + {{ Py_tp_doc, "{otype.name}" }}, + {{ Py_tp_init, {cprefix}_init }}, + {{ Py_tp_dealloc, PyGObject_gc_dealloc }}, + {{ Py_tp_traverse, {cprefix}_traverse }}, + {{ Py_tp_clear, {cprefix}_clear }}, + {{ Py_tp_methods, {cprefix}_methods }}, + {{ Py_tp_getset, {cprefix}_getsets }}, +);""" + ) + continue + + defs.append(f"""PYFRIDA_DEFINE_TYPE ("_frida.{otype.py_name}", {otype.py_name}, {parent_name}, g_object_unref, + {{ Py_tp_doc, "{otype.name}" }}, + {{ Py_tp_init, {cprefix}_init }}, + {{ Py_tp_methods, {cprefix}_methods }}, + {{ Py_tp_getset, {cprefix}_getsets }}, +);""") + + return "\n\n".join(defs) + + +def generate_object_type_method_definitions(model: Model) -> str: + return "\n\n".join(generate_object_type_methods(otype) for otype in model.regular_object_types) + + +def generate_object_type_methods(otype: ObjectType) -> str: + functions = [] + entries = [] + + if otype.name == "Object": + entries.append(' { "on", (PyCFunction) PyGObject_on, METH_VARARGS, "Add a signal handler." },') + entries.append(' { "off", (PyCFunction) PyGObject_off, METH_VARARGS, "Remove a signal handler." },') + + for method in otype.methods: + emitted = generate_method(otype, method) + if emitted is None: + continue + function, entry = emitted + functions.append(function) + entries.append(entry) + + return f"""{"".join(functions)} +static PyMethodDef {otype.c_symbol_prefix}_methods[] = +{{ +{chr(10).join(entries)} + {{ NULL }} +}};""" + + +def generate_method(otype: ObjectType, method: Method) -> Optional[Tuple[str, str]]: + if method.is_async: + return generate_async_method(otype, method) + + marshal = synchronous_method_return_marshal(method) + if marshal is None: + return None + params = [] + for param in method.input_parameters: + sync_param = build_sync_param(param) + if sync_param is None: + return None + params.append(sync_param) + function = generate_synchronous_method(otype, method, marshal, params) + flags = "METH_VARARGS" if params else "METH_NOARGS" + entry = f' {{ "{method.name}", (PyCFunction) {otype.c_symbol_prefix}_{method.name}, {flags}, NULL }},' + return function, entry + + +def generate_async_method(otype: ObjectType, method: Method) -> Optional[Tuple[str, str]]: + if method.is_property_accessor: + return None + + params = [] + for param in method.input_parameters: + if param.type.name == "Gio.Cancellable": + continue + async_param = build_async_param(param) + if async_param is None: + return None + params.append(async_param) + + marshal = "" + if method.return_value is not None: + return_marshal = build_return_marshal(method.return_value.type, method.object_type.model) + if return_marshal is None: + return None + marshal = return_marshal + + cprefix = otype.c_symbol_prefix + op = method.operation_type_name + entry_fn = f"{cprefix}_{method.name}" + begin_fn = f"{cprefix}_{method.name}_begin" + end_fn = f"{cprefix}_{method.name}_end" + has_cancellable = any(p.type.name == "Gio.Cancellable" for p in method.input_parameters) + + return ( + generate_async_operation_struct(otype, op, params, has_cancellable) + + generate_async_forward_declarations(begin_fn, end_fn) + + generate_async_entry(otype, method, params, has_cancellable, op, entry_fn, begin_fn) + + generate_async_begin(method, params, has_cancellable, op, begin_fn, end_fn) + + generate_async_end(method, marshal, op, end_fn), + f' {{ "{method.name}", (PyCFunction) {entry_fn}, METH_VARARGS, NULL }},', + ) + + +def generate_async_operation_struct( + otype: ObjectType, op: str, params: List["AsyncParam"], has_cancellable: bool +) -> str: + fields = [f" {p.field}" for p in params] + if has_cancellable: + fields.append(" GCancellable * cancellable;") + return f""" +typedef struct {{ + PyObject * callback; + {otype.c_type} * handle; +{chr(10).join(fields)} +}} {op}; +""" + + +def generate_async_forward_declarations(begin_fn: str, end_fn: str) -> str: + return f""" +static gboolean {begin_fn} (gpointer user_data); +static void {end_fn} (GObject * source_object, GAsyncResult * res, gpointer user_data); +""" + + +def generate_async_entry( + otype: ObjectType, + method: Method, + params: List["AsyncParam"], + has_cancellable: bool, + op: str, + entry_fn: str, + begin_fn: str, +) -> str: + local_decls = "".join(f" {p.local_decl}\n" for p in params) + cancellable_decl = " PyObject * cancellable = NULL;\n" if has_cancellable else "" + + fmt = "".join(p.fmt for p in params) + "O" + ("|O" if has_cancellable else "") + parse_args = ", ".join( + [f'"{fmt}"'] + + [arg for p in params for arg in p.parse_args] + + ["&callback"] + + (["&cancellable"] if has_cancellable else []) + ) + + validations = "".join(f"\n {p.validate}\n" for p in params if p.validate is not None) + stores = "".join(f" {p.store}\n" for p in params) + cancellable_store = "" + if has_cancellable: + cancellable_store = """ if (cancellable != NULL && cancellable != Py_None) + operation->cancellable = (GCancellable *) g_object_ref (PY_GOBJECT_HANDLE (cancellable)); +""" + + return f""" +static PyObject * +{entry_fn} ({otype.c_symbol_prefix} * self, +{" " * (len(entry_fn) + 2)}PyObject * args) +{{ +{local_decls} PyObject * callback; +{cancellable_decl} {op} * operation; + GSource * idle; + + if (!PyArg_ParseTuple (args, {parse_args})) + return NULL; +{validations} + operation = g_slice_new0 ({op}); + operation->callback = callback; + Py_IncRef (callback); + operation->handle = ({otype.c_type} *) g_object_ref (PY_GOBJECT_HANDLE (self)); +{stores}{cancellable_store} + idle = g_idle_source_new (); + g_source_set_callback (idle, {begin_fn}, operation, NULL); + g_source_attach (idle, frida_get_main_context ()); + g_source_unref (idle); + + PyFrida_RETURN_NONE; +}} +""" + + +def generate_async_begin( + method: Method, + params: List["AsyncParam"], + has_cancellable: bool, + op: str, + begin_fn: str, + end_fn: str, +) -> str: + start_args = ", ".join( + ["operation->handle"] + + [p.start_arg for p in params] + + (["operation->cancellable"] if has_cancellable else []) + + [end_fn, "operation"] + ) + return f""" +static gboolean +{begin_fn} (gpointer user_data) +{{ + {op} * operation = user_data; + + {method.c_identifier} ({start_args}); + + return FALSE; +}} +""" + + +def generate_async_end(method: Method, marshal: str, op: str, end_fn: str) -> str: + finish_args = ", ".join(["operation->handle", "res"] + (["&error"] if method.throws else [])) + error_decl = "\n GError * error = NULL;" if method.throws else "" + + if method.return_value is None: + finish_call = f"{method.finish_c_identifier} ({finish_args});" + success = " Py_IncRef (Py_None);\n value = Py_None;" + else: + finish_call = f"{method.return_value.type.c} retval = {method.finish_c_identifier} ({finish_args});" + release_retval = "" + destroy = method.return_value.destroy_func + if destroy is not None: + release_retval = f"\n {generate_destruction_code('retval', destroy)}" + success = f" value = {marshal.format(value='retval')};{release_retval}" + + error_delivery = "" + if method.throws: + error_delivery = """ if (error != NULL) + { + PyObject * exception = PyFrida_marshal_error (error); + PyFrida_deliver (operation->callback, Py_None, exception); + Py_DecRef (exception); + } + else +""" + + cleanups = "".join(f" {p.free}\n" for p in method_async_params(method) if p.free) + cancellable_cleanup = ( + " g_clear_object (&operation->cancellable);\n" + if any(p.type.name == "Gio.Cancellable" for p in method.input_parameters) + else "" + ) + + return f""" +static void +{end_fn} (GObject * source_object, +{" " * (len(end_fn) + 2)}GAsyncResult * res, +{" " * (len(end_fn) + 2)}gpointer user_data) +{{ + {op} * operation = user_data;{error_decl} + PyGILState_STATE gstate; + PyObject * value; + + {finish_call} + + gstate = PyGILState_Ensure (); + +{error_delivery} {{ +{success} + PyFrida_deliver (operation->callback, value, Py_None); + Py_DecRef (value); + }} + + Py_DecRef (operation->callback); + g_object_unref (operation->handle); +{cleanups}{cancellable_cleanup} g_slice_free ({op}, operation); + + PyGILState_Release (gstate); +}} +""" + + +def method_async_params(method: Method) -> List["AsyncParam"]: + result = [] + for param in method.input_parameters: + if param.type.name == "Gio.Cancellable": + continue + async_param = build_async_param(param) + if async_param is not None: + result.append(async_param) + return result + + +def build_async_param(param: Parameter) -> Optional["AsyncParam"]: + name = param.name + type_name = param.type.name + + if type_name == "utf8": + fmt = "z" if param.nullable else "s" + return AsyncParam( + field=f"gchar * {name};", + local_decl=f"const char * {name};", + fmt=fmt, + parse_args=[f"&{name}"], + store=f"operation->{name} = g_strdup ({name});", + free=f"g_free (operation->{name});", + start_arg=f"operation->{name}", + ) + + numeric = PYARG_NUMERIC_FORMATS.get(type_name) + if numeric is not None: + fmt, c_type = numeric + return AsyncParam( + field=f"{param.type.c} {name};", + local_decl=f"{c_type} {name};", + fmt=fmt, + parse_args=[f"&{name}"], + store=f"operation->{name} = {name};", + start_arg=f"operation->{name}", + ) + + enum = resolve_enumeration(param.type, param.object_type.model) + if enum is not None: + return AsyncParam( + field=f"{param.type.c} {name};", + local_decl=f"const char * {name}_value;\n {param.type.c} {name};", + fmt="s", + parse_args=[f"&{name}_value"], + validate=f"""if (!PyGObject_unmarshal_enum ({name}_value, {enum.get_type} (), &{name})) + return NULL;""", + store=f"operation->{name} = {name};", + start_arg=f"operation->{name}", + ) + + if type_name == "GLib.Bytes": + return AsyncParam( + field=f"GBytes * {name};", + local_decl=f"const char * {name}_data;\n Py_ssize_t {name}_size;", + fmt="z#", + parse_args=[f"&{name}_data", f"&{name}_size"], + store=f"""if ({name}_data != NULL) + operation->{name} = g_bytes_new ({name}_data, {name}_size);""", + free=f"g_clear_pointer (&operation->{name}, g_bytes_unref);", + start_arg=f"operation->{name}", + ) + + if type_name == "GLib.Variant": + return AsyncParam( + field=f"GVariant * {name};", + local_decl=f"PyObject * {name}_obj;\n GVariant * {name};", + fmt="O", + parse_args=[f"&{name}_obj"], + validate=f"""if (!PyGObject_unmarshal_variant ({name}_obj, &{name})) + return NULL;""", + store=f"operation->{name} = {name};", + free=f"g_clear_pointer (&operation->{name}, g_variant_unref);", + start_arg=f"operation->{name}", + ) + + if resolve_input_object_type(param.type, param.object_type.model) is not None: + return AsyncParam( + field=f"{param.type.c} {name};", + local_decl=f"PyObject * {name}_obj;", + fmt="O", + parse_args=[f"&{name}_obj"], + store=f"""if ({name}_obj != Py_None) + operation->{name} = ({param.type.c}) g_object_ref (PY_GOBJECT_HANDLE ({name}_obj));""", + free=f"g_clear_object (&operation->{name});", + start_arg=f"operation->{name}", + ) + + return None + + +def resolve_input_object_type(type: Type, model: Model) -> Optional[ObjectType]: + tokens = type.name.split(".", maxsplit=1) + if len(tokens) != 2: + return None + otype = model.object_types.get(tokens[1]) + if otype is None or otype.is_frida_list: + return None + return otype + + +class AsyncParam: + def __init__( + self, + field: str, + local_decl: str, + fmt: str, + parse_args: List[str], + store: str, + start_arg: str, + validate: Optional[str] = None, + free: str = "", + ): + self.field = field + self.local_decl = local_decl + self.fmt = fmt + self.parse_args = parse_args + self.store = store + self.start_arg = start_arg + self.validate = validate + self.free = free + + +def generate_synchronous_method(otype: ObjectType, method: Method, marshal: str, params: List["SyncParam"]) -> str: + indent = " " * (len(otype.c_symbol_prefix) + len(method.name) + 3) + handle = f"({otype.c_type} *) PY_GOBJECT_HANDLE (self)" + returns_strv = method.return_value is not None and method.return_value.type.name == "utf8[]" + call_arg_list = [handle] + [p.call_arg for p in params] + if returns_strv: + call_arg_list.append("&retval_length") + if method.throws: + call_arg_list.append("&error") + call = f"{method.c_identifier} ({', '.join(call_arg_list)})" + + if not params and not method.throws and not returns_strv: + if method.return_value is None: + body = f" {call};\n\n PyFrida_RETURN_NONE;" + else: + body = f" return {marshal.format(value=call)};" + return f""" +static PyObject * +{otype.c_symbol_prefix}_{method.name} ({otype.c_symbol_prefix} * self, +{indent}PyObject * args) +{{ +{body} +}} +""" + + decls = [" PyObject * result;"] + if method.throws: + decls.append(" GError * error = NULL;") + if returns_strv: + decls.append(" gint retval_length;") + for p in params: + decls += [f" {line}" for line in p.decl.splitlines()] + + parse = "" + if params: + fmt = "".join(p.fmt for p in params) + parse_args = ", ".join(["args", f'"{fmt}"'] + [a for p in params for a in p.parse_args]) + parse = f""" + if (!PyArg_ParseTuple ({parse_args})) + return NULL; +""" + + pre = "".join(f"\n {p.pre}\n" for p in params if p.pre) + + if method.return_value is None: + invocation = f" {call};" + success = " Py_IncRef (Py_None);\n result = Py_None;" + elif returns_strv: + invocation = f" gchar ** retval = {call};" + success = " result = PyGObject_marshal_strv (retval, retval_length);\n g_strfreev (retval);" + else: + invocation = f" {method.return_value.type.c} retval = {call};" + release = "" + if method.return_value.destroy_func is not None: + release = f"\n {generate_destruction_code('retval', method.return_value.destroy_func)}" + success = f" result = {marshal.format(value='retval')};{release}" + + cleanups = "".join(f" {p.cleanup}\n" for p in params if p.cleanup) + + if method.throws: + tail = f""" + if (error != NULL) + {{ + result = PyFrida_raise (error); + goto beach; + }} + +{success} + +beach: +{cleanups} return result;""" + else: + tail = f"""{success} + +{cleanups} return result;""" + + return f""" +static PyObject * +{otype.c_symbol_prefix}_{method.name} ({otype.c_symbol_prefix} * self, +{indent}PyObject * args) +{{ +{chr(10).join(decls)} +{parse}{pre} +{invocation} +{tail} +}} +""" + + +def synchronous_method_return_marshal(method: Method) -> Optional[str]: + if method.is_async or method.is_property_accessor: + return None + if method.return_value is None: + return "" + if method.return_value.type.name == "utf8[]": + return "PyGObject_marshal_strv" + return build_return_marshal(method.return_value.type, method.object_type.model) + + +class SyncParam: + def __init__( + self, + decl: str, + fmt: str, + parse_args: List[str], + call_arg: str, + pre: str = "", + cleanup: str = "", + ): + self.decl = decl + self.fmt = fmt + self.parse_args = parse_args + self.call_arg = call_arg + self.pre = pre + self.cleanup = cleanup + + +def build_sync_param(param: Parameter) -> Optional["SyncParam"]: + name = param.name + type_name = param.type.name + + if type_name == "utf8": + return SyncParam( + decl=f"const char * {name};", + fmt="z" if param.nullable else "s", + parse_args=[f"&{name}"], + call_arg=name, + ) + + numeric = PYARG_NUMERIC_FORMATS.get(type_name) + if numeric is not None: + fmt, c_type = numeric + return SyncParam( + decl=f"{c_type} {name};", + fmt=fmt, + parse_args=[f"&{name}"], + call_arg=name, + ) + + enum = resolve_enumeration(param.type, param.object_type.model) + if enum is not None: + return SyncParam( + decl=f"const char * {name}_value;\n{param.type.c} {name};", + fmt="s", + parse_args=[f"&{name}_value"], + pre=f"""if (!PyGObject_unmarshal_enum ({name}_value, {enum.get_type} (), &{name})) + return NULL;""", + call_arg=name, + ) + + if type_name == "GLib.Bytes": + return SyncParam( + decl=f"const char * {name}_data;\nPy_ssize_t {name}_size;\nGBytes * {name} = NULL;", + fmt="z#", + parse_args=[f"&{name}_data", f"&{name}_size"], + pre=f"if ({name}_data != NULL)\n {name} = g_bytes_new ({name}_data, {name}_size);", + call_arg=name, + cleanup=f"g_clear_pointer (&{name}, g_bytes_unref);", + ) + + if type_name == "GLib.Variant": + return SyncParam( + decl=f"PyObject * {name}_obj;\nGVariant * {name} = NULL;", + fmt="O", + parse_args=[f"&{name}_obj"], + pre=f"""if (!PyGObject_unmarshal_variant ({name}_obj, &{name})) + return NULL;""", + call_arg=name, + cleanup=f"g_clear_pointer (&{name}, g_variant_unref);", + ) + + return None + + +def generate_object_type_getset_definitions(model: Model) -> str: + return "\n\n".join(generate_object_type_getset(otype) for otype in model.regular_object_types) + + +def generate_object_type_getset(otype: ObjectType) -> str: + accessors = [ + (name, method, marshal) + for method in otype.methods + for name in [property_name_from_accessor(method)] + for marshal in [property_getter_marshal(method)] + if name is not None and marshal is not None + ] + set_methods = { + method.name[len("set_") :]: method + for method in otype.methods + if method.is_property_accessor and method.name.startswith("set_") + } + + functions = [] + entries = [] + for name, method, marshal in accessors: + functions.append(generate_property_getter(otype, name, method, marshal)) + + setter = "NULL" + set_method = set_methods.get(name) + if set_method is not None: + setter_fn = generate_property_setter(otype, name, set_method) + if setter_fn is not None: + functions.append(setter_fn) + setter = f"(setter) {otype.c_symbol_prefix}_set_{name}" + + entries.append(f' {{ "{name}", (getter) {otype.c_symbol_prefix}_get_{name}, {setter}, NULL, NULL }},') + + return f"""{"".join(functions)} +static PyGetSetDef {otype.c_symbol_prefix}_getsets[] = +{{ +{chr(10).join(entries)} + {{ NULL }} +}};""" + + +def generate_property_setter(otype: ObjectType, name: str, set_method: Method) -> Optional[str]: + handle = f"({otype.c_type} *) PY_GOBJECT_HANDLE (self)" + indent = " " * (len(otype.c_symbol_prefix) + len(name) + len("_set_ (") + 1) + param = set_method.input_parameters[0] + + if param.type.name == "utf8[]": + return f""" +static int +{otype.c_symbol_prefix}_set_{name} ({otype.c_symbol_prefix} * self, +{indent}PyObject * value, +{indent}void * closure) +{{ + gchar ** strv; + gint length; + + if (!PyGObject_unmarshal_strv (value, &strv, &length)) + return -1; + + {set_method.c_identifier} ({handle}, strv, length); + + g_strfreev (strv); + + return 0; +}} +""" + + body = build_setter_body(param, lambda arg: f"{set_method.c_identifier} ({handle}, {arg});") + if body is None: + return None + + return f""" +static int +{otype.c_symbol_prefix}_set_{name} ({otype.c_symbol_prefix} * self, +{indent}PyObject * value, +{indent}void * closure) +{{ +{body} + return 0; +}} +""" + + +def property_setter_supported(set_method: Method) -> bool: + param = set_method.input_parameters[0] + if param.type.name == "utf8[]": + return True + return build_setter_body(param, lambda arg: "") is not None + + +def build_setter_body(param: Parameter, set_call) -> Optional[str]: + type_name = param.type.name + + if type_name == "utf8": + return f""" PyObject * bytes = NULL; + const char * str = NULL; + + if (value != Py_None) + {{ + bytes = PyUnicode_AsUTF8String (value); + if (bytes == NULL) + return -1; + str = PyBytes_AsString (bytes); + }} + + {set_call("str")} + + Py_XDECREF (bytes); +""" + + if type_name == "gboolean": + return f" {set_call('PyObject_IsTrue (value)')}\n" + + if type_name in {"gint8", "gint16", "gint", "gint32", "guint8", "guint16", "guint", "guint32"}: + return f""" long number = PyLong_AsLong (value); + if (number == -1 && PyErr_Occurred () != NULL) + return -1; + + {set_call("number")} +""" + + if type_name in {"gint64", "guint64"}: + return f""" long long number = PyLong_AsLongLong (value); + if (number == -1 && PyErr_Occurred () != NULL) + return -1; + + {set_call("number")} +""" + + enum = resolve_enumeration(param.type, param.object_type.model) + if enum is not None: + return f""" PyObject * bytes; + {param.type.c} enum_value; + gboolean valid; + + bytes = PyUnicode_AsUTF8String (value); + if (bytes == NULL) + return -1; + + valid = PyGObject_unmarshal_enum (PyBytes_AsString (bytes), {enum.get_type} (), &enum_value); + Py_DecRef (bytes); + if (!valid) + return -1; + + {set_call("enum_value")} +""" + + if type_name == "GLib.Bytes": + return f""" char * data = NULL; + Py_ssize_t size = 0; + GBytes * bytes = NULL; + + if (value != Py_None) + {{ + if (PyBytes_AsStringAndSize (value, &data, &size) < 0) + return -1; + bytes = g_bytes_new (data, size); + }} + + {set_call("bytes")} + + g_clear_pointer (&bytes, g_bytes_unref); +""" + + if type_name == "GLib.HashTable": + return f""" GHashTable * dict; + + if (!PyGObject_unmarshal_vardict (value, &dict)) + return -1; + + {set_call("dict")} + + g_hash_table_unref (dict); +""" + + if resolve_input_object_type(param.type, param.object_type.model) is not None: + handle = f"({param.type.c}) ((value != Py_None) ? PY_GOBJECT_HANDLE (value) : NULL)" + return f" {set_call(handle)}\n" + + return None + + +def generate_property_getter(otype: ObjectType, name: str, method: Method, marshal: str) -> str: + handle = f"({otype.c_type} *) PY_GOBJECT_HANDLE (self)" + indent = " " * (len(otype.c_symbol_prefix) + len(name) + len("_get_ (") + 1) + + if method.return_value.type.name == "utf8[]": + return f""" +static PyObject * +{otype.c_symbol_prefix}_get_{name} ({otype.c_symbol_prefix} * self, +{indent}void * closure) +{{ + gint length; + gchar ** value = {method.c_identifier} ({handle}, &length); + + return PyGObject_marshal_strv (value, length); +}} +""" + + value = f"{method.c_identifier} ({handle})" + return f""" +static PyObject * +{otype.c_symbol_prefix}_get_{name} ({otype.c_symbol_prefix} * self, +{indent}void * closure) +{{ + return {marshal.format(value=value)}; +}} +""" + + +def property_name_from_accessor(method: Method) -> Optional[str]: + if not method.is_property_accessor: + return None + if method.name.startswith("get_"): + return method.name[len("get_") :] + if method.name.startswith("is_"): + return method.name + return None + + +def property_getter_marshal(method: Method) -> Optional[str]: + if property_name_from_accessor(method) is None: + return None + if method.return_value is None: + return None + if method.return_value.type.name == "utf8[]": + return "PyGObject_marshal_strv (...)" + return build_return_marshal(method.return_value.type, method.object_type.model) + + +def build_return_marshal(type: Type, model: Model) -> Optional[str]: + name = type.name + + if name == "utf8": + return "PyGObject_marshal_string ({value})" + if name == "gboolean": + return "PyBool_FromLong ({value})" + if name in {"gint8", "gint16", "gint", "gint32"}: + return "PyLong_FromLong ({value})" + if name in {"guint8", "guint16", "guint", "guint32"}: + return "PyLong_FromUnsignedLong ({value})" + if name == "gint64": + return "PyLong_FromLongLong ({value})" + if name == "guint64": + return "PyLong_FromUnsignedLongLong ({value})" + if name in {"gssize", "gsize"}: + return "PyLong_FromSsize_t ({value})" + + if name == "GLib.Bytes": + return "PyGObject_marshal_bytes ({value})" + if name == "GLib.HashTable": + return "PyGObject_marshal_vardict ({value})" + if name == "GLib.Variant": + return "PyGObject_marshal_variant ({value})" + + enum = resolve_enumeration(type, model) + if enum is not None: + return "PyGObject_marshal_enum ({value}, " + enum.get_type + " ())" + + obj = resolve_object_type(type, model) + if obj is not None: + return "PyGObject_marshal_object ({value}, " + obj.get_type + " ())" + + list_type = resolve_list_type(type, model) + if list_type is not None: + return f"{list_type.c_symbol_prefix}_to_value ({{value}})" + + return None + + +def resolve_object_type(type: Type, model: Model) -> Optional[ObjectType]: + tokens = type.name.split(".", maxsplit=1) + if len(tokens) != 2: + return None + otype = model.object_types.get(tokens[1]) + if otype is None or otype.is_frida_options or otype.is_frida_list: + return None + return otype + + +def resolve_options_type(type: Type, model: Model) -> Optional[ObjectType]: + tokens = type.name.split(".", maxsplit=1) + if len(tokens) != 2: + return None + otype = model.object_types.get(tokens[1]) + if otype is None or not otype.is_frida_options: + return None + return otype + + +def resolve_list_type(type: Type, model: Model) -> Optional[ObjectType]: + tokens = type.name.split(".", maxsplit=1) + if len(tokens) != 2: + return None + otype = model.object_types.get(tokens[1]) + if otype is None or not otype.is_frida_list: + return None + return otype + + +def list_accessors(otype: ObjectType) -> Tuple[Method, Method]: + size = next(method for method in otype.methods if method.name == "size") + get = next(method for method in otype.methods if method.name == "get") + return size, get + + +def generate_list_conversion_functions(otype: ObjectType, model: Model) -> str: + size, get = list_accessors(otype) + element = get.return_value.type + element_return_marshal = build_return_marshal(element, model) + assert element_return_marshal is not None + element_marshal = element_return_marshal.format(value="element") + + release = "" + if get.return_value.destroy_func is not None: + release = f"\n {generate_destruction_code('element', get.return_value.destroy_func)}" + + return f""" +static PyObject * +{otype.c_symbol_prefix}_to_value ({otype.c_type} * self) +{{ + gint size, i; + PyObject * result; + + if (self == NULL) + PyFrida_RETURN_NONE; + + size = {size.c_identifier} (self); + result = PyList_New (size); + + for (i = 0; i != size; i++) + {{ + {element.c} element = {get.c_identifier} (self, i); + PyList_SetItem (result, i, {element_marshal});{release} + }} + + return result; +}} +""" + + +def generate_init_function(model: Model) -> str: + registration_calls = [ + f"PYFRIDA_REGISTER_TYPE ({otype.py_name}, {otype.get_type} ());" for otype in model.regular_object_types + ] + subtype_registration_calls = "\n ".join(registration_calls[1:]) + exception_registration = indent_c_code(generate_exception_registration(model), 1, prologue="\n") + + return f""" +PyMODINIT_FUNC +PyInit__frida (void) +{{ + PyObject * module, * inspect; + + frida_init (); + + PyGObject_class_init (); + + module = PyModule_Create (&PyFrida_moduledef); + + PyModule_AddStringConstant (module, "__version__", frida_version_string ()); + + inspect = PyImport_ImportModule ("inspect"); + inspect_getargspec = PyObject_GetAttrString (inspect, "getfullargspec"); + inspect_ismethod = PyObject_GetAttrString (inspect, "ismethod"); + Py_DecRef (inspect); + + {registration_calls[0]} + PyGObject_tp_init = PyType_GetSlot ((PyTypeObject *) PYFRIDA_TYPE_OBJECT (GObject), Py_tp_init); + PyGObject_tp_dealloc = PyType_GetSlot ((PyTypeObject *) PYFRIDA_TYPE_OBJECT (GObject), Py_tp_dealloc); + + {subtype_registration_calls} +{exception_registration} + + return module; +}} +""" + + +def generate_exception_registration(model: Model) -> str: + lines = ["frida_exception_by_error_code = g_hash_table_new_full (NULL, NULL, NULL, PyFrida_object_decref);"] + lines += [ + f'PYFRIDA_DECLARE_EXCEPTION ({c_identifier}, "{name}");' for c_identifier, name in error_members(model) + ] + lines += [ + 'cancelled_exception = PyErr_NewException ("frida.OperationCancelledError", NULL, NULL);', + "Py_IncRef (cancelled_exception);", + 'PyModule_AddObject (module, "OperationCancelledError", cancelled_exception);', + ] + return "\n".join(lines) + + +def generate_object_type_constructor(otype: ObjectType) -> str: + custom_constructor = otype.custom_constructor + if custom_constructor is not None: + return "\n" + read_asset(custom_constructor) + + if implementable_interface(otype): + return generate_interface_init(otype) + + ctor = next(iter(otype.constructors), None) + + if ctor is None: + return generate_bare_init(otype) + + marshals: Optional[List[ParamMarshal]] = None + if not ctor.throws: + marshals = [] + for param in ctor.input_parameters: + marshal = build_param_marshal(param) + if marshal is None: + marshals = None + break + marshal.optional = param.optional + marshals.append(marshal) + + if marshals is None: + return generate_unconstructable_init(otype) + + return generate_constructor_init(otype, ctor, marshals) + + +def generate_interface_init(otype: ObjectType) -> str: + n = interface_impl_names(otype) + indent = constructor_indent(otype) + return f""" +static int +{otype.c_symbol_prefix}_init ({otype.c_symbol_prefix} * self, +{indent}PyObject * args, +{indent}PyObject * kw) +{{ + PyObject * wrapper; + + if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) + return -1; + + if (!PyArg_ParseTuple (args, "O", &wrapper)) + return -1; + + PyGObject_take_handle ((PyGObject *) self, {n['prefix']}_new (wrapper), PYFRIDA_TYPE ({otype.py_name})); + + return 0; +}} +""" + + +def generate_bare_init(otype: ObjectType) -> str: + indent = constructor_indent(otype) + return f""" +static int +{otype.c_symbol_prefix}_init ({otype.c_symbol_prefix} * self, +{indent}PyObject * args, +{indent}PyObject * kw) +{{ + return PyGObject_tp_init ((PyObject *) self, args, kw); +}} +""" + + +def generate_unconstructable_init(otype: ObjectType) -> str: + indent = constructor_indent(otype) + return f""" +static int +{otype.c_symbol_prefix}_init ({otype.c_symbol_prefix} * self, +{indent}PyObject * args, +{indent}PyObject * kw) +{{ + if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) + return -1; + + PyErr_SetString (PyExc_NotImplementedError, "{otype.py_name} cannot be constructed yet"); + return -1; +}} +""" + + +def generate_constructor_init(otype: ObjectType, ctor: Procedure, marshals: List["ParamMarshal"]) -> str: + indent = constructor_indent(otype) + + decls = [] + for m in marshals: + decls += m.decls + if any(m.cleanup for m in marshals) and constructor_needs_beach(marshals): + decls.insert(0, "int result = -1;") + decls.append(f"{otype.c_type} * handle;") + decls_block = indent_c_code("\n".join(decls), 1, prologue="\n") + + keyword_decl = "" + parse_block = "" + if marshals: + keywords = ", ".join([f'"{m.keyword}"' for m in marshals] + ["NULL"]) + keyword_decl = f"\n static char * keywords[] = {{ {keywords} }};" + parse_block = "\n" + generate_argument_parse(marshals) + + post_block = "" + for m in marshals: + if m.post is not None: + post_block += indent_c_code(m.post, 1, prologue="\n") + "\n" + + call_args = ", ".join(m.call_arg for m in marshals) + constructor_call = f"handle = ({otype.c_type} *) {ctor.c_identifier} ({call_args});" + take_handle = ( + "PyGObject_take_handle ((PyGObject *) self, g_steal_pointer (&handle), " f"PYFRIDA_TYPE ({otype.py_name}));" + ) + + return f""" +static int +{otype.c_symbol_prefix}_init ({otype.c_symbol_prefix} * self, +{indent}PyObject * args, +{indent}PyObject * kw) +{{{keyword_decl}{decls_block} + + if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) + return -1; +{parse_block}{post_block} + {constructor_call} + + {take_handle} + +{generate_constructor_tail(marshals)}""" + + +def generate_argument_parse(marshals: List["ParamMarshal"]) -> str: + fmt = "" + optional_started = False + for m in marshals: + if m.optional and not optional_started: + fmt += "|" + optional_started = True + fmt += m.fmt + parse_args = ["args", "kw", f'"{fmt}"', "keywords"] + for m in marshals: + parse_args += m.parse_args + indent = " " * len(" if (!PyArg_ParseTupleAndKeywords (") + parse_args_str = (",\n" + indent).join(parse_args) + return f""" if (!PyArg_ParseTupleAndKeywords ({parse_args_str})) + return -1; +""" + + +def constructor_needs_beach(marshals: List["ParamMarshal"]) -> bool: + return any(m.post is not None and "goto beach" in m.post for m in marshals) + + +def generate_constructor_tail(marshals: List["ParamMarshal"]) -> str: + cleanups = [m.cleanup for m in marshals if m.cleanup is not None] + if not cleanups: + return """ return 0; +} +""" + + cleanup_block = indent_c_code("\n".join(reversed(cleanups)), 1) + if constructor_needs_beach(marshals): + return f""" result = 0; + +beach: +{cleanup_block} + + return result; +}} +""" + return f"""{cleanup_block} + + return 0; +}} +""" + + +def constructor_indent(otype: ObjectType) -> str: + return " " * (len(otype.c_symbol_prefix) + len("_init") + 2) + + +def build_param_marshal(param: Parameter) -> Optional["ParamMarshal"]: + name = param.name + type_name = param.type.name + + if type_name == "utf8": + return ParamMarshal( + keyword=name, + fmt="es", + decls=[f"char * {name} = NULL;"], + parse_args=['"utf-8"', f"&{name}"], + call_arg=name, + cleanup=f"PyMem_Free ({name});", + ) + + numeric = PYARG_NUMERIC_FORMATS.get(type_name) + if numeric is not None: + fmt, c_type = numeric + return ParamMarshal( + keyword=name, + fmt=fmt, + decls=[f"{c_type} {name} = 0;"], + parse_args=[f"&{name}"], + call_arg=name, + ) + + enum = resolve_enumeration(param.type, param.object_type.model) + if enum is not None: + raw = f"{name}_value" + return ParamMarshal( + keyword=name, + fmt="es", + decls=[f"char * {raw} = NULL;", f"{param.type.c} {name} = 0;"], + parse_args=['"utf-8"', f"&{raw}"], + call_arg=name, + post=f"""if ({raw} != NULL && !PyGObject_unmarshal_enum ({raw}, {enum.get_type} (), &{name})) + goto beach;""", + cleanup=f"PyMem_Free ({raw});", + ) + + if type_name == "GLib.Bytes": + data = f"{name}_data" + size = f"{name}_size" + return ParamMarshal( + keyword=name, + fmt="z#", + decls=[ + f"const char * {data} = NULL;", + f"Py_ssize_t {size} = 0;", + f"GBytes * {name} = NULL;", + ], + parse_args=[f"&{data}", f"&{size}"], + call_arg=name, + post=f"if ({data} != NULL)\n {name} = g_bytes_new ({data}, {size});", + cleanup=f"g_clear_pointer (&{name}, g_bytes_unref);", + ) + + if resolve_input_object_type(param.type, param.object_type.model) is not None: + obj = f"{name}_obj" + return ParamMarshal( + keyword=name, + fmt="O", + decls=[f"PyObject * {obj} = NULL;", f"{param.type.c} {name} = NULL;"], + parse_args=[f"&{obj}"], + call_arg=name, + post=f"""if ({obj} != NULL && {obj} != Py_None) + {name} = ({param.type.c}) PY_GOBJECT_HANDLE ({obj});""", + ) + + return None + + +def resolve_enumeration(type: Type, model: Model) -> Optional[Enumeration]: + tokens = type.name.split(".", maxsplit=1) + if len(tokens) != 2: + return None + return model.enumerations.get(tokens[1]) + + +class ParamMarshal: + def __init__( + self, + keyword: str, + fmt: str, + decls: List[str], + parse_args: List[str], + call_arg: str, + post: Optional[str] = None, + cleanup: Optional[str] = None, + ): + self.keyword = keyword + self.fmt = fmt + self.decls = decls + self.parse_args = parse_args + self.call_arg = call_arg + self.post = post + self.cleanup = cleanup + self.optional = False + + +PYARG_NUMERIC_FORMATS = { + "gint8": ("b", "int"), + "guint8": ("B", "unsigned int"), + "gint16": ("h", "int"), + "guint16": ("H", "unsigned int"), + "gint": ("i", "int"), + "gint32": ("i", "int"), + "guint": ("I", "unsigned int"), + "guint32": ("I", "unsigned int"), + "gint64": ("L", "long long"), + "guint64": ("K", "unsigned long long"), + "gssize": ("n", "Py_ssize_t"), + "gsize": ("n", "Py_ssize_t"), +} + + +def generate_destruction_code(variable: str, destroy_func: str): + if destroy_func == "g_free": + return f"g_free ({variable});" + return f"g_clear_pointer (&{variable}, {destroy_func});" + + +def generate_object_type_typedefs(model: Model) -> str: + return "\n".join( + [ + f"typedef struct _{t.c_symbol_prefix} {t.c_symbol_prefix};" + for t in model.regular_object_types + if t.name != "Object" + ] + ) + + +def generate_object_type_structs(model: Model) -> str: + structs = [] + + for otype in model.regular_object_types: + if otype.name == "Object": + continue + structs.append(f"""struct _{otype.c_symbol_prefix} +{{ + {otype.parent_c_symbol_prefix} parent; +}};""") + + return "\n\n".join(structs) + + +def indent_c_code(code: str, level: int, prologue: str = "") -> str: + prefix = (level * 2) * " " + return indent_code(code, prefix, prologue) + + +def indent_code(code: str, prefix: str, prologue: str = "") -> str: + if not code: + return "" + return prologue + textwrap.indent(code, prefix, lambda line: line.strip() != "") diff --git a/frida/frida_bindgen/customization.py b/frida/frida_bindgen/customization.py new file mode 100644 index 0000000..7bf1afe --- /dev/null +++ b/frida/frida_bindgen/customization.py @@ -0,0 +1,400 @@ +from __future__ import annotations + +from typing import Mapping + +from .model import ( + ConstructorCustomizations, + CustomCode, + Customizations, + EnumerationCustomizations, + EnumerationMemberCustomizations, + MethodCustomizations, + ModuleFunction, + ObjectTypeCustomizations, + PropertyCustomizations, + SignalCustomizations, + TypeCustomizations, +) + +_RESOLVE_PID = ("pid = self._pid_of(target)", "pid = await self._pid_of(target)") + +LEGACY_PRIVATE_TYPES = ( + "GDBBreakpoint", + "GDBClient", + "GDBClientPacket", + "GDBClientPacketBuilder", + "GDBClientPropertyDictionary", + "GDBClientRegister", + "GDBClientTargetSpec", + "GDBException", + "GDBThread", +) + + +def load_customizations() -> Customizations: + type_customizations: Mapping[str, TypeCustomizations] = { + "DeviceManager": ObjectTypeCustomizations( + custom_code=CustomCode( + members=( + "facade_device_manager_members.py", + "facade_device_manager_members_aio.py", + ), + helpers=("facade_toplevel.py", "facade_toplevel_aio.py"), + module_functions=( + ModuleFunction( + py_name="get_device_manager", + c_symbol="PyFrida_get_device_manager", + asset="module_get_device_manager.c", + ), + ), + ), + ), + "Bus": ObjectTypeCustomizations( + provides_signals=True, + custom_code=CustomCode( + members=("facade_bus.py", "facade_bus_aio.py"), + ), + methods={ + "post": MethodCustomizations( + param_typings=["message", "data=None"], + custom_logic="json = _to_json(message)", + ), + }, + ), + "PortalService": ObjectTypeCustomizations( + provides_signals=True, + custom_code=CustomCode( + members=("facade_portal.py", "facade_portal_aio.py"), + ), + methods={ + "post": MethodCustomizations( + param_typings=["connection_id", "message", "data=None"], + custom_logic="json = _to_json(message)", + ), + "narrowcast": MethodCustomizations( + param_typings=["tag", "message", "data=None"], + custom_logic="json = _to_json(message)", + ), + "broadcast": MethodCustomizations( + param_typings=["message", "data=None"], + custom_logic="json = _to_json(message)", + ), + }, + ), + "Device": ObjectTypeCustomizations( + methods={ + "spawn": MethodCustomizations( + param_typings=[ + "program", + "argv=None", + "envp=None", + "env=None", + "cwd=None", + "stdio=None", + "**aux", + ], + custom_logic="""\ +if not isinstance(program, str): + argv = program + program = argv[0] + if len(argv) == 1: + argv = None +if isinstance(program, bytes): + program = program.decode() + +options = _frida.SpawnOptions() +if argv is not None: + options.argv = [arg.decode() if isinstance(arg, bytes) else arg for arg in argv] +if envp is not None: + options.envp = _to_envp(envp) +if env is not None: + options.env = _to_envp(env) +if cwd is not None: + options.cwd = cwd +if stdio is not None: + options.stdio = stdio +if aux: + options.aux = aux +""", + ), + "is_lost": MethodCustomizations(as_property=True), + "resume": MethodCustomizations(param_typings=["target"], custom_logic=_RESOLVE_PID), + "kill": MethodCustomizations(param_typings=["target"], custom_logic=_RESOLVE_PID), + "input": MethodCustomizations(param_typings=["target", "data"], custom_logic=_RESOLVE_PID), + "inject_library_file": MethodCustomizations( + param_typings=["target", "path", "entrypoint", "data"], + custom_logic=_RESOLVE_PID, + ), + "inject_library_blob": MethodCustomizations( + param_typings=["target", "blob", "entrypoint", "data"], + custom_logic=_RESOLVE_PID, + ), + "attach": MethodCustomizations( + param_typings=["target", "**kwargs"], + custom_logic=( + "pid = self._pid_of(target)\n" "options = _make_options(_frida.SessionOptions, kwargs, {})", + "pid = await self._pid_of(target)\n" "options = _make_options(_frida.SessionOptions, kwargs, {})", + ), + ), + }, + custom_code=CustomCode( + members=("facade_device_members.py", "facade_device_members_aio.py"), + ), + ), + "Script": ObjectTypeCustomizations( + provides_signals=True, + custom_code=CustomCode( + members=("facade_rpc_members.py", "facade_rpc_members_aio.py"), + helpers=("facade_rpc_helpers.py", "facade_rpc_helpers_aio.py"), + ), + methods={ + "post": MethodCustomizations( + param_typings=["message", "data=None"], + custom_logic="json = _to_json(message)", + ), + "is_destroyed": MethodCustomizations(as_property=True), + }, + ), + "Session": ObjectTypeCustomizations( + methods={ + "is_detached": MethodCustomizations(as_property=True), + "create_script": MethodCustomizations( + param_typings=["source", "name=None", "snapshot=None", "runtime=None"], + custom_logic="""\ +options = _frida.ScriptOptions() +if name is not None: + options.name = name +if snapshot is not None: + options.snapshot = snapshot +if runtime is not None: + options.runtime = runtime""", + ), + "create_script_from_bytes": MethodCustomizations( + param_typings=["data", "name=None", "snapshot=None", "runtime=None"], + custom_logic="""\ +bytes = data +options = _frida.ScriptOptions() +if name is not None: + options.name = name +if snapshot is not None: + options.snapshot = snapshot +if runtime is not None: + options.runtime = runtime""", + ), + "compile_script": MethodCustomizations( + param_typings=["source", "name=None", "runtime=None"], + custom_logic="""\ +options = _frida.ScriptOptions() +if name is not None: + options.name = name +if runtime is not None: + options.runtime = runtime""", + ), + "snapshot_script": MethodCustomizations( + param_typings=["embed_script", "warmup_script=None", "runtime=None"], + custom_logic="""\ +options = _frida.SnapshotOptions() +if warmup_script is not None: + options.warmup_script = warmup_script +if runtime is not None: + options.runtime = runtime""", + ), + }, + ), + "Service": ObjectTypeCustomizations( + methods={ + "is_closed": MethodCustomizations(as_property=True), + }, + ), + "EndpointParameters": ObjectTypeCustomizations( + custom_constructor="codegen_endpoint_parameters.c", + constructor=ConstructorCustomizations( + param_typings=[ + "address=None", + "port=None", + "certificate=None", + "origin=None", + "authentication=None", + "asset_root=None", + "request_handler=None", + ], + custom_logic="""\ +auth_service = None +if authentication is not None: + if isinstance(authentication, tuple): + scheme, data = authentication + if scheme == "token": + auth_service = _frida.StaticAuthenticationService(data) + elif scheme == "callback": + if not callable(data): + raise ValueError("authentication data must be callable for the callback scheme") + service = AuthenticationService.__new__(AuthenticationService) + service.authenticate = lambda token: json.dumps(data(token)) + AuthenticationService.__init__(service) + auth_service = service._impl + else: + raise ValueError("invalid authentication scheme") + else: + auth_service = _unwrap(authentication) + +kwargs = {} +if address is not None: + kwargs["address"] = address +if port is not None: + kwargs["port"] = port +if certificate is not None: + kwargs["certificate"] = certificate +if origin is not None: + kwargs["origin"] = origin +if auth_service is not None: + kwargs["auth_service"] = auth_service +if asset_root is not None: + kwargs["asset_root"] = str(asset_root) + +self._impl = _frida.EndpointParameters(**kwargs) + +if request_handler is not None: + self._impl.request_handler = _unwrap(request_handler) +""", + ), + ), + "RelayKind": EnumerationCustomizations( + members={ + "turn_udp": EnumerationMemberCustomizations(js_name="TurnUDP"), + "turn_tcp": EnumerationMemberCustomizations(js_name="TurnTCP"), + "turn_tls": EnumerationMemberCustomizations(js_name="TurnTLS"), + }, + ), + "ScriptRuntime": EnumerationCustomizations( + members={ + "qjs": EnumerationMemberCustomizations(js_name="QJS"), + }, + ), + "ControlService": ObjectTypeCustomizations( + drop=True, + methods={ + "get_endpoint_params": MethodCustomizations(drop=True), + }, + properties={ + "endpoint-params": PropertyCustomizations(drop=True), + }, + ), + "Injector": ObjectTypeCustomizations(drop=True), + "RpcClient": ObjectTypeCustomizations(drop=True), + "RpcPeer": ObjectTypeCustomizations(drop=True), + "Cancellable": ObjectTypeCustomizations( + custom_code=CustomCode( + members=("facade_cancellable.py", "facade_cancellable_aio.py"), + helpers=("facade_cancellable_helpers.py", "facade_cancellable_helpers_aio.py"), + ), + methods={ + "is_cancelled": MethodCustomizations(as_property=True), + "make_pollfd": MethodCustomizations(drop=True), + "source_new": MethodCustomizations(drop=True), + }, + ), + "IOStream": ObjectTypeCustomizations( + custom_code=CustomCode( + members=("facade_iostream.py", "facade_iostream_aio.py"), + ), + methods={ + "close": MethodCustomizations(drop=True), + "close_async": MethodCustomizations(suppress_facade=True), + "splice_async": MethodCustomizations(drop=True), + "has_pending": MethodCustomizations(drop=True), + "set_pending": MethodCustomizations(drop=True), + "clear_pending": MethodCustomizations(drop=True), + }, + ), + "InputStream": ObjectTypeCustomizations( + methods={ + "close": MethodCustomizations(drop=True), + "read": MethodCustomizations(drop=True), + "read_async": MethodCustomizations(drop=True), + "read_all": MethodCustomizations(drop=True), + "read_all_async": MethodCustomizations(drop=True), + "read_bytes": MethodCustomizations(drop=True), + "skip": MethodCustomizations(drop=True), + "is_closed": MethodCustomizations(drop=True), + "has_pending": MethodCustomizations(drop=True), + "set_pending": MethodCustomizations(drop=True), + "clear_pending": MethodCustomizations(drop=True), + }, + ), + "OutputStream": ObjectTypeCustomizations( + methods={ + "close": MethodCustomizations(drop=True), + "flush": MethodCustomizations(drop=True), + "write": MethodCustomizations(drop=True), + "write_async": MethodCustomizations(drop=True), + "write_all": MethodCustomizations(drop=True), + "write_all_async": MethodCustomizations(drop=True), + "write_bytes": MethodCustomizations(drop=True), + "writev": MethodCustomizations(drop=True), + "writev_async": MethodCustomizations(drop=True), + "writev_all": MethodCustomizations(drop=True), + "writev_all_async": MethodCustomizations(drop=True), + "splice": MethodCustomizations(drop=True), + "is_closing": MethodCustomizations(drop=True), + "is_closed": MethodCustomizations(drop=True), + "has_pending": MethodCustomizations(drop=True), + "set_pending": MethodCustomizations(drop=True), + "clear_pending": MethodCustomizations(drop=True), + }, + ), + "UnixSocketAddress": ObjectTypeCustomizations( + methods={ + "get_path_len": MethodCustomizations(drop=True), + "get_is_abstract": MethodCustomizations(drop=True), + }, + properties={ + "abstract": PropertyCustomizations(drop=True), + "path-as-array": PropertyCustomizations(drop=True), + }, + ), + "SocketAddress": ObjectTypeCustomizations( + constructor=ConstructorCustomizations(drop=True), + methods={ + "to_native": MethodCustomizations(drop=True), + }, + ), + "SocketConnectable": ObjectTypeCustomizations(drop_abstract_base=True), + "InetAddress": ObjectTypeCustomizations( + properties={ + "bytes": PropertyCustomizations(drop=True), + }, + ), + "Object": ObjectTypeCustomizations( + methods={ + "is_floating": MethodCustomizations(drop=True), + "ref": MethodCustomizations(drop=True), + "ref_sink": MethodCustomizations(drop=True), + "unref": MethodCustomizations(drop=True), + "getv": MethodCustomizations(drop=True), + "get_property": MethodCustomizations(drop=True), + "set_property": MethodCustomizations(drop=True), + "notify_by_pspec": MethodCustomizations(drop=True), + "freeze_notify": MethodCustomizations(drop=True), + "thaw_notify": MethodCustomizations(drop=True), + "bind_property": MethodCustomizations(drop=True), + "bind_property_full": MethodCustomizations(drop=True), + "bind_property_with_closures": MethodCustomizations(drop=True), + "force_floating": MethodCustomizations(drop=True), + "get_data": MethodCustomizations(drop=True), + "get_qdata": MethodCustomizations(drop=True), + "run_dispose": MethodCustomizations(drop=True), + "set_data": MethodCustomizations(drop=True), + "steal_data": MethodCustomizations(drop=True), + "steal_qdata": MethodCustomizations(drop=True), + "watch_closure": MethodCustomizations(drop=True), + }, + signals={ + "notify": SignalCustomizations(drop=True), + }, + ), + } + + for name in LEGACY_PRIVATE_TYPES: + type_customizations.setdefault(name, ObjectTypeCustomizations(drop=True)) + + return Customizations(type_customizations=type_customizations) diff --git a/frida/frida_bindgen/loader.py b/frida/frida_bindgen/loader.py new file mode 100644 index 0000000..b6fb3bd --- /dev/null +++ b/frida/frida_bindgen/loader.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from collections import OrderedDict +from pathlib import Path +from typing import Optional + +import frida_bindgen_core as core + +from .model import FACTORY, Customizations, Model + +INCLUDED_GIO_OBJECT_TYPES = [ + "Cancellable", + "IOStream", + "InputStream", + "OutputStream", + "SocketAddress", + "SocketAddressEnumerator", + "SocketConnectable", + "InetSocketAddress", + "UnixSocketAddress", +] +INCLUDED_GIO_ENUMERATIONS = [ + "FileMonitorEvent", + "SocketFamily", + "UnixSocketAddressType", +] + + +def compute_model( + frida_gir: Path, + glib_gir: Path, + gobject_gir: Path, + gio_gir: Path, + customizations: Customizations, + frida_header: Optional[Path] = None, +) -> Model: + model = core.compute_model( + frida_gir, + glib_gir, + gobject_gir, + gio_gir, + customizations, + FACTORY, + INCLUDED_GIO_OBJECT_TYPES, + INCLUDED_GIO_ENUMERATIONS, + seed_object_first=True, + ) + if frida_header is not None: + available_symbols = frida_header.read_text(encoding="utf-8") + model.available_symbols = available_symbols + model._object_types = OrderedDict( + (name, otype) + for name, otype in model._object_types.items() + if otype.c_type in available_symbols and otype.get_type in available_symbols + ) + model.enumerations = OrderedDict( + (name, enum) + for name, enum in model.enumerations.items() + if enum.c_type in available_symbols and enum.get_type in available_symbols + ) + return model diff --git a/frida/frida_bindgen/model.py b/frida/frida_bindgen/model.py new file mode 100644 index 0000000..e7911ed --- /dev/null +++ b/frida/frida_bindgen/model.py @@ -0,0 +1,349 @@ +from __future__ import annotations + +from collections import OrderedDict +from dataclasses import dataclass, field +from functools import cached_property +from typing import List, Mapping, Optional, Sequence, Tuple, Union + +import frida_bindgen_core as core +from frida_bindgen_core import TransferOwnership +from frida_bindgen_core.naming import to_pascal_case, to_snake_case + + +class Model(core.Model): + @cached_property + def regular_object_types(self) -> List[ObjectType]: + return [t for t in self.object_types.values() if not t.is_frida_list] + + +class ObjectType(core.ObjectType): + @cached_property + def py_name(self) -> str: + custom = self.customizations + if custom is not None and custom.py_name is not None: + return custom.py_name + return "GObject" if self.name == "Object" else self.name + + @property + def custom_code(self) -> Optional[CustomCode]: + custom = self.customizations + return custom.custom_code if custom is not None else None + + @property + def provides_signals(self) -> bool: + custom = self.customizations + return custom is not None and custom.provides_signals + + @property + def custom_constructor(self) -> Optional[str]: + custom = self.customizations + return custom.custom_constructor if custom is not None else None + + @property + def constructor_custom_params(self) -> List[str]: + custom = self.customizations + if custom is None or custom.constructor is None: + return [] + return custom.constructor.param_typings or [] + + @property + def constructor_custom_logic(self) -> Optional[str]: + custom = self.customizations + if custom is None or custom.constructor is None: + return None + return custom.constructor.custom_logic + + @cached_property + def c_symbol_prefix(self) -> str: + return f"Py{self.py_name}" + + @cached_property + def parent_c_symbol_prefix(self) -> str: + parent = self.parent + return parent.c_symbol_prefix if parent is not None else "PyGObject" + + +class ClassObjectType(ObjectType): + pass + + +class InterfaceObjectType(ObjectType): + @cached_property + def has_abstract_base(self) -> bool: + custom = self.customizations + if custom is None: + return True + return not custom.drop_abstract_base + + +class Constructor(core.Constructor): + pass + + +class Method(core.Method): + @cached_property + def cself_name(self) -> str: + return to_snake_case(self.object_type.name).split("_")[-1] + + @cached_property + def param_ctypings(self) -> List[str]: + result = [f"{self.object_type.c_type} * {self.cself_name}"] + result += [param.ctyping for param in self.parameters] + if self.is_async: + result += ["GAsyncReadyCallback callback", "gpointer user_data"] + return result + + @cached_property + def finish_param_ctypings(self) -> List[str]: + result = [ + f"{self.object_type.c_type} * {self.cself_name}", + "GAsyncResult * result", + ] + if self.throws: + result.append("GError ** error") + return result + + @cached_property + def operation_type_name(self) -> str: + return f"Py{self.object_type.name}{to_pascal_case(self.name)}Operation" + + @cached_property + def is_select_method(self) -> bool: + return self.name.startswith("select_") or self.name.startswith("add_") + + @cached_property + def select_plural_noun(self) -> str: + return f"{self.name.split('_', maxsplit=1)[1]}s" + + @cached_property + def customizations(self) -> Optional[MethodCustomizations]: + custom = self.object_type.customizations + if custom is None: + return None + return custom.methods.get(self.name) + + @property + def suppress_facade(self) -> bool: + custom = self.customizations + return custom is not None and custom.suppress_facade + + @property + def as_property(self) -> bool: + custom = self.customizations + return custom is not None and custom.as_property + + @property + def custom_facade_params(self) -> List[str]: + custom = self.customizations + if custom is None: + return [] + return custom.param_typings or [] + + @property + def custom_logic(self) -> Optional[Union[str, Tuple[str, str]]]: + custom = self.customizations + return custom.custom_logic if custom is not None else None + + @cached_property + def facade_call_args(self) -> str: + return "".join(f"{param.name}, " for param in self.input_parameters if param.type.name != "Gio.Cancellable") + + +class Property(core.Property): + pass + + +class Signal(core.Signal): + pass + + +class Parameter(core.Parameter): + @cached_property + def ctyping(self) -> str: + return f"{self.type.c} {self.name}" + + +class ReturnValue(core.ReturnValue): + @cached_property + def destroy_func(self) -> Optional[str]: + if self.transfer_ownership == TransferOwnership.none: + return None + return self.type.destroy_func + + +class Enumeration(core.Enumeration): + @cached_property + def c_symbol_prefix(self) -> str: + return f"Py{self.name}" + + +class EnumerationMember(core.EnumerationMember): + @cached_property + def js_name(self) -> str: + custom = self.customizations + if custom is not None and custom.js_name is not None: + return custom.js_name + return to_pascal_case(self.name) + + @cached_property + def nick(self) -> str: + return self.name.replace("_", "-") + + @cached_property + def customizations(self) -> Optional[EnumerationMemberCustomizations]: + custom = self.enumeration.customizations + if custom is None: + return None + return custom.members.get(self.name) + + +@dataclass +class Customizations: + type_customizations: Mapping[str, TypeCustomizations] = field(default_factory=OrderedDict) + + +@dataclass +class TypeCustomizations: + pass + + +@dataclass +class ModuleFunction: + py_name: str + c_symbol: str + asset: str + + +@dataclass +class CustomCode: + members: Optional[Tuple[str, str]] = None + helpers: Optional[Tuple[str, str]] = None + module_functions: Optional[Tuple[ModuleFunction, ...]] = None + + +@dataclass +class ObjectTypeCustomizations(TypeCustomizations): + py_name: Optional[str] = None + drop: bool = False + drop_abstract_base: bool = False + custom_code: Optional[CustomCode] = None + provides_signals: bool = False + custom_constructor: Optional[str] = None + constructor: Optional[ConstructorCustomizations] = None + methods: Mapping[str, MethodCustomizations] = field(default_factory=dict) + properties: Mapping[str, PropertyCustomizations] = field(default_factory=dict) + signals: Mapping[str, SignalCustomizations] = field(default_factory=dict) + + +@dataclass +class ConstructorCustomizations: + drop: bool = False + param_typings: Optional[List[str]] = None + custom_logic: Optional[str] = None + + +@dataclass +class MethodCustomizations: + drop: bool = False + suppress_facade: bool = False + as_property: bool = False + param_typings: Optional[List[str]] = None + custom_logic: Optional[Union[str, Tuple[str, str]]] = None + + +@dataclass +class PropertyCustomizations: + drop: bool = False + + +@dataclass +class SignalCustomizations: + drop: bool = False + + +@dataclass +class EnumerationCustomizations(TypeCustomizations): + members: Mapping[str, EnumerationMemberCustomizations] = field(default_factory=dict) + + +@dataclass +class EnumerationMemberCustomizations: + js_name: Optional[str] = None + + +def _make_class( + *, + name, + c_type, + get_type, + type_struct, + parent, + constructors, + methods, + properties, + signals, + implements, + resolve_type, + model, +): + return ClassObjectType( + name, + c_type, + get_type, + type_struct, + parent, + constructors, + methods, + properties, + signals, + resolve_type, + model, + ) + + +def _make_interface( + *, + name, + c_type, + get_type, + type_struct, + parent, + constructors, + methods, + properties, + signals, + resolve_type, + model, +): + return InterfaceObjectType( + name, + c_type, + get_type, + type_struct, + parent, + constructors, + methods, + properties, + signals, + resolve_type, + model, + ) + + +FACTORY = core.Factory( + class_object_type=_make_class, + interface_object_type=_make_interface, + constructor=Constructor, + method=Method, + parameter=Parameter, + return_value=ReturnValue, + signal=Signal, + property_=Property, + enumeration=Enumeration, + enumeration_member=EnumerationMember, + model=Model, +) + + +def parse_gir(file_path: str, dependencies: Sequence[Model]) -> Model: + return core.parse_gir(file_path, dependencies, FACTORY) diff --git a/frida/frida_bindgen_core/__init__.py b/frida/frida_bindgen_core/__init__.py new file mode 100644 index 0000000..49654b2 --- /dev/null +++ b/frida/frida_bindgen_core/__init__.py @@ -0,0 +1,8 @@ +from . import model, naming +from .loader import compute_model +from .model import (Constructor, Direction, Enumeration, EnumerationMember, + Factory, InterfaceObjectType, Method, Model, Namespace, + ObjectType, Parameter, Procedure, Property, ReturnValue, + Signal, TransferOwnership, Type, ClassObjectType, + parse_gir) +from .naming import to_camel_case, to_macro_case, to_pascal_case, to_snake_case diff --git a/frida/frida_bindgen_core/loader.py b/frida/frida_bindgen_core/loader.py new file mode 100644 index 0000000..c0a6f65 --- /dev/null +++ b/frida/frida_bindgen_core/loader.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from collections import OrderedDict +from pathlib import Path + +from typing import List + +from .model import Factory, Model, parse_gir + + +def compute_model( + frida_gir: Path, + glib_gir: Path, + gobject_gir: Path, + gio_gir: Path, + customizations: object, + factory: Factory, + included_gio_object_types: List[str], + included_gio_enumerations: List[str], + seed_object_first: bool, +) -> Model: + glib = parse_gir(glib_gir, [], factory) + gobject = parse_gir(gobject_gir, [glib], factory) + gio = parse_gir(gio_gir, [glib, gobject], factory) + frida = parse_gir(frida_gir, [glib, gobject, gio], factory) + + object_types = OrderedDict() + if seed_object_first: + object_types["Object"] = gobject.object_types["Object"] + object_types.update(frida.object_types) + else: + object_types.update(frida.object_types) + object_types["Object"] = gobject.object_types["Object"] + for t in included_gio_object_types: + object_types[t] = gio.object_types[t] + + enumerations = OrderedDict(frida.enumerations) + for t in included_gio_enumerations: + enumerations[t] = gio.enumerations[t] + + model = factory.model( + frida.namespace, + object_types, + enumerations, + customizations, + error_domain=frida.error_domain, + factory=factory, + ) + + for t in object_types.values(): + t.model = model + for t in enumerations.values(): + t.model = model + if model.error_domain is not None: + model.error_domain.model = model + + return model diff --git a/frida/frida_bindgen_core/model.py b/frida/frida_bindgen_core/model.py new file mode 100644 index 0000000..e9b1484 --- /dev/null +++ b/frida/frida_bindgen_core/model.py @@ -0,0 +1,882 @@ +from __future__ import annotations + +import xml.etree.ElementTree as ET +from collections import OrderedDict +from dataclasses import dataclass +from enum import Enum +from functools import cached_property +from typing import (Callable, Iterator, List, Optional, Sequence, Tuple) + +from .naming import to_snake_case + +CORE_NAMESPACE = "http://www.gtk.org/introspection/core/1.0" +C_NAMESPACE = "http://www.gtk.org/introspection/c/1.0" +GLIB_NAMESPACE = "http://www.gtk.org/introspection/glib/1.0" +GIR_NAMESPACES = {"": CORE_NAMESPACE, "glib": GLIB_NAMESPACE} + +CORE_TAG_PREFIX = f"{{{CORE_NAMESPACE}}}" + +NUMERIC_GIR_TYPES = { + "gsize", + "gssize", + "gint", + "guint", + "glong", + "gulong", + "gint8", + "gint16", + "gint32", + "gint64", + "guint8", + "guint16", + "guint32", + "guint64", + "gfloat", + "gdouble", + "GType", + "GQuark", +} + +PRIMITIVE_GIR_TYPES = NUMERIC_GIR_TYPES | { + "gpointer", + "gboolean", + "gchar", + "utf8", + "utf8[]", +} + +ResolveTypeCallback = Callable[[str], Tuple[str, ET.Element]] + + +@dataclass +class Factory: + class_object_type: Callable + interface_object_type: Callable + constructor: Callable + method: Callable + parameter: Callable + return_value: Callable + signal: Callable + property_: Callable + enumeration: Callable + enumeration_member: Callable + model: Callable + + +@dataclass +class Model: + namespace: Namespace + _object_types: OrderedDict + enumerations: OrderedDict + customizations: Optional[object] = None + error_domain: Optional[object] = None + factory: Optional[Factory] = None + available_symbols: Optional[str] = None + + @cached_property + def object_types(self) -> OrderedDict: + result = OrderedDict() + if self.customizations is not None: + type_customizations = self.customizations.type_customizations + else: + type_customizations = {} + for k, v in self._object_types.items(): + custom = type_customizations.get(k) + if custom is None or not custom.drop: + result[k] = v + return result + + def resolve_object_type(self, name: str) -> ObjectType: + bare_name = name.split(".", maxsplit=1)[-1] + return self.object_types[bare_name] + + +@dataclass +class Namespace: + name: str + identifier_prefixes: str + element: ET.Element + + @cached_property + def type_elements(self): + result = {} + for toplevel in self.element.findall("./*[@name]", GIR_NAMESPACES): + name = toplevel.get("name") + result[name] = toplevel + for callback in toplevel.findall("./callback", GIR_NAMESPACES): + result[name + callback.get("name")] = callback + return result + + +@dataclass +class ObjectType: + name: str + c_type: str + get_type: str + type_struct: str + _parent: Optional[str] + _constructors: List[ET.Element] + _methods: List[ET.Element] + _properties: List[ET.Element] + _signals: List[ET.Element] + resolve_type: ResolveTypeCallback + + model: Optional[Model] + + @cached_property + def parent(self) -> ObjectType: + if self._parent is None: + return None + return self.model.resolve_object_type(self._parent) + + @cached_property + def is_frida_options(self) -> bool: + return self.c_type.startswith("Frida") and self.c_type.endswith("Options") + + @cached_property + def is_frida_list(self) -> bool: + return self.c_type.startswith("Frida") and self.c_type.endswith("List") + + @cached_property + def customizations(self): + return self.model.customizations.type_customizations.get(self.name) + + @cached_property + def constructors(self) -> List[Constructor]: + factory = self.model.factory + constructors = [] + custom = self.customizations + for element in self._constructors: + if element.get("introspectable") == "0" or element.get("deprecated") == "1": + continue + + name = element.get("name") + + if custom is not None: + ccust = custom.constructor + if ccust is not None and ccust.drop: + continue + + try: + ( + c_identifier, + finish_c_identifier, + param_list, + has_closure_param, + throws, + result_element, + ) = extract_callable_details(element, element, self, self.resolve_type) + except AssertionError: + if self.model.error_domain is not None: + raise + continue + if not self._has_available_symbols(c_identifier, finish_c_identifier): + continue + if has_closure_param or finish_c_identifier is not None: + continue + + constructors.append( + factory.constructor( + name, c_identifier, finish_c_identifier, param_list, throws, self + ) + ) + return constructors + + @cached_property + def methods(self) -> List[Method]: + factory = self.model.factory + methods = [] + c_prop_names = {prop.c_name for prop in self.properties} + custom = self.customizations + for element in self._methods: + name = element.get("name") + + if ( + element.get("introspectable") == "0" + or name.startswith("_") + or name.endswith("_sync") + or name.endswith("_finish") + ): + continue + + if custom is not None: + mcust = custom.methods.get(name, None) + if mcust is not None and mcust.drop: + continue + + finish_func = element.get(f"{{{GLIB_NAMESPACE}}}finish-func") + if finish_func is None: + finish_func = f"{name}_finish" + result_element = next( + (m for m in self._methods if m.get("name") == finish_func), element + ) + + try: + ( + c_identifier, + finish_c_identifier, + param_list, + has_closure_param, + throws, + result_element, + ) = extract_callable_details( + element, result_element, self, self.resolve_type + ) + retval_element = result_element.find(".//return-value", GIR_NAMESPACES) + rettype = extract_type_from_entity(retval_element, self.resolve_type) + except AssertionError: + if self.model.error_domain is not None: + raise + continue + if not self._has_available_symbols(c_identifier, finish_c_identifier): + continue + if has_closure_param: + continue + + if rettype is not None: + if rettype.is_frida_options: + continue + + nullable = retval_element.get("nullable") == "1" + + ownership_val = retval_element.get("transfer-ownership") + transfer_ownership = ( + TransferOwnership[ownership_val] + if ownership_val is not None + else TransferOwnership.none + ) + + retval = factory.return_value( + rettype, nullable, transfer_ownership, self + ) + else: + retval = None + + if element.get(f"{{{GLIB_NAMESPACE}}}get-property") is not None: + is_property_accessor = True + else: + tokens = name.split("_", maxsplit=1) + is_property_accessor = ( + len(tokens) == 2 + and tokens[0] in {"get", "set"} + and tokens[1] in c_prop_names + ) + + methods.append( + factory.method( + name, + c_identifier, + finish_c_identifier, + param_list, + throws, + retval, + is_property_accessor, + self, + ) + ) + return methods + + def _has_available_symbols(self, *symbols: Optional[str]) -> bool: + available_symbols = self.model.available_symbols + return available_symbols is None or all(symbol is None or symbol in available_symbols for symbol in symbols) + + @cached_property + def properties(self) -> List[Property]: + factory = self.model.factory + properties = [] + custom = self.customizations + for element in self._properties: + name = element.get("name") + + if custom is not None: + pcust = custom.properties.get(name, None) + if pcust is not None and pcust.drop: + continue + + c_name = name.replace("-", "_") + try: + type = extract_type_from_entity(element, self.resolve_type) + except AssertionError: + if self.model.error_domain is not None: + raise + continue + if type.is_frida_options: + continue + writable = element.get("writable") == "1" + construct_only = element.get("construct-only") == "1" + + getter = element.get("getter") + if getter is None: + getter = f"get_{c_name}" + + setter = element.get("setter") + if setter is None and writable and not construct_only: + setter = f"set_{c_name}" + + properties.append( + factory.property_( + name, + c_name, + type, + writable, + construct_only, + getter, + setter, + self, + ) + ) + return properties + + @cached_property + def signals(self) -> List[Signal]: + factory = self.model.factory + signals = [] + custom = self.customizations + for element in self._signals: + name = element.get("name") + + if custom is not None: + scust = custom.signals.get(name, None) + if scust is not None and scust.drop: + continue + + c_name = name.replace("-", "_") + try: + param_list = extract_parameters( + element.findall("./parameters/parameter", GIR_NAMESPACES), + nullable_implies_optional=False, + object_type=self, + resolve_type=self.resolve_type, + ) + except AssertionError: + if self.model.error_domain is not None: + raise + continue + signals.append(factory.signal(name, c_name, param_list, self)) + return signals + + +@dataclass +class ClassObjectType(ObjectType): + pass + + +@dataclass +class InterfaceObjectType(ObjectType): + pass + + +@dataclass +class Procedure: + name: str + c_identifier: str + finish_c_identifier: Optional[str] + parameters: List[Parameter] + throws: bool + + @property + def is_async(self) -> bool: + return self.finish_c_identifier is not None + + @cached_property + def input_parameters(self) -> List[Parameter]: + return [p for p in self.parameters if p.direction != Direction.OUT] + + +@dataclass +class Constructor(Procedure): + object_type: ObjectType + + +@dataclass +class Method(Procedure): + return_value: Optional[ReturnValue] + is_property_accessor: bool + + object_type: ObjectType + + +@dataclass +class Property: + name: str + c_name: str + type: Type + writable: bool + construct_only: bool + getter: Optional[str] + setter: Optional[str] + + object_type: ObjectType + + +@dataclass +class Signal: + name: str + c_name: str + parameters: List[Parameter] + + object_type: ObjectType + + +TransferOwnership = Enum("TransferOwnership", ["none", "full", "container"]) + + +@dataclass +class Parameter: + name: str + type: Type + optional: bool + nullable: bool + transfer_ownership: TransferOwnership + direction: Direction + + object_type: ObjectType + + +@dataclass +class ReturnValue: + type: Type + nullable: bool + transfer_ownership: TransferOwnership + + object_type: ObjectType + + +@dataclass +class Type: + name: str + nick: str + c: str + default_value: Optional[str] + copy_func: Optional[str] + destroy_func: Optional[str] + + @cached_property + def is_frida_options(self) -> bool: + return self.c.startswith("Frida") and self.c.endswith("Options *") + + @cached_property + def from_pointer_func(self) -> Optional[str]: + if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}: + return "GPOINTER_TO_SIZE" + if self.name in {"gint", "gint8", "gint16", "gint32"}: + return "GPOINTER_TO_INT" + if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}: + return "GPOINTER_TO_UINT" + return None + + @cached_property + def to_pointer_func(self) -> Optional[str]: + if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}: + return "GSIZE_TO_POINTER" + if self.name in {"gint", "gint8", "gint16", "gint32"}: + return "GINT_TO_POINTER" + if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}: + return "GUINT_TO_POINTER" + return None + + +class Direction(Enum): + IN = "in" + OUT = "out" + INOUT = "inout" + + +@dataclass +class Enumeration: + name: str + c_type: str + get_type: str + _members: List[ET.Element] + + model: Optional[Model] + + @cached_property + def members(self) -> List[EnumerationMember]: + factory = self.model.factory + members = [] + for element in self._members: + c_identifier = element.get(f"{{{C_NAMESPACE}}}identifier") + members.append( + factory.enumeration_member(element.get("name"), c_identifier, self) + ) + return members + + @cached_property + def customizations(self): + return self.model.customizations.type_customizations.get(self.name) + + +@dataclass +class EnumerationMember: + name: str + c_identifier: Optional[str] + + enumeration: Enumeration + + +def parse_gir( + file_path: str, dependencies: Sequence[Model], factory: Factory +) -> Model: + tree = ET.parse(file_path) + + el = tree.getroot().find("./namespace", GIR_NAMESPACES) + namespace = Namespace( + el.get("name"), el.get(f"{{{C_NAMESPACE}}}identifier-prefixes"), el + ) + + def resolve_type(name: str) -> Tuple[str, ET.Element]: + assert ( + name not in PRIMITIVE_GIR_TYPES + ), f"unexpectedly asked to resolve primitive type: {name}" + + tokens = name.split(".", maxsplit=1) + if len(tokens) == 2: + ns_name, bare_name = tokens + if ns_name == namespace.name: + ns = namespace + else: + ns = next( + ( + dep.namespace + for dep in dependencies + if dep.namespace.name == ns_name + ), + None, + ) + if ns is None: + assert ns is not None, f"unable to resolve namespace {ns_name}" + else: + ns = namespace + bare_name = name + qualified_name = f"{ns.name}.{bare_name}" + + element = ns.type_elements.get(bare_name) + assert element is not None, f"unable to resolve type {bare_name}" + + return (qualified_name, element) + + object_types = OrderedDict() + + for element in namespace.element.findall("./class", GIR_NAMESPACES): + name = element.get("name") + c_type = element.get(f"{{{C_NAMESPACE}}}type") + get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type") + type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct") + if type_struct is not None: + type_struct = namespace.identifier_prefixes + type_struct + else: + type_struct = c_type + "Class" + parent = element.get("parent") + if parent is not None: + parent, _ = resolve_type(parent) + constructors = element.findall(".//constructor", GIR_NAMESPACES) + methods = element.findall(".//method", GIR_NAMESPACES) + properties = element.findall(".//property", GIR_NAMESPACES) + signals = element.findall(".//glib:signal", GIR_NAMESPACES) + implements = [ + e.get("name") for e in element.findall(".//implements", GIR_NAMESPACES) + ] + + object_types[name] = factory.class_object_type( + name=name, + c_type=c_type, + get_type=get_type, + type_struct=type_struct, + parent=parent, + constructors=constructors, + methods=methods, + properties=properties, + signals=signals, + implements=implements, + resolve_type=resolve_type, + model=None, + ) + + for element in namespace.element.findall("./interface", GIR_NAMESPACES): + name = element.get("name") + c_type = element.get(f"{{{C_NAMESPACE}}}type") + get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type") + type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct") + if type_struct is not None: + type_struct = namespace.identifier_prefixes + type_struct + else: + type_struct = c_type + "Iface" + prereq = element.find(".//prerequisite", GIR_NAMESPACES) + parent = prereq.get("name") if prereq is not None else None + if parent is not None: + parent, _ = resolve_type(parent) + constructors = [] + methods = element.findall(".//method", GIR_NAMESPACES) + properties = element.findall(".//property", GIR_NAMESPACES) + signals = element.findall(".//glib:signal", GIR_NAMESPACES) + + object_types[name] = factory.interface_object_type( + name=name, + c_type=c_type, + get_type=get_type, + type_struct=type_struct, + parent=parent, + constructors=constructors, + methods=methods, + properties=properties, + signals=signals, + resolve_type=resolve_type, + model=None, + ) + + enumerations = OrderedDict() + error_domain = None + + for element in namespace.element.findall("./enumeration", GIR_NAMESPACES): + enum_name = element.get("name") + enum_c_type = element.get(f"{{{C_NAMESPACE}}}type") + get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type") + members = element.findall(".//member", GIR_NAMESPACES) + enumeration = factory.enumeration( + enum_name, enum_c_type, get_type, members, None + ) + if element.get(f"{{{GLIB_NAMESPACE}}}error-domain") is not None: + error_domain = enumeration + continue + enumerations[enum_name] = enumeration + + model = factory.model( + namespace, + object_types, + enumerations, + error_domain=error_domain, + factory=factory, + ) + + for t in object_types.values(): + t.model = model + for t in enumerations.values(): + t.model = model + + return model + + +def extract_callable_details( + element: ET.Element, + result_element: ET.Element, + object_type: ObjectType, + resolve_type: ResolveTypeCallback, +) -> Tuple[str, Optional[str], List[Parameter], bool, bool, ET.Element]: + c_identifier = element.get(f"{{{C_NAMESPACE}}}identifier") + + parameters = element.findall("./parameters/parameter", GIR_NAMESPACES) + full_param_list = extract_parameters( + parameters, + nullable_implies_optional=True, + object_type=object_type, + resolve_type=resolve_type, + ) + param_list = list(all_regular_parameters(full_param_list)) + has_closure_param = any((param.get("closure") == "1" for param in parameters)) + + is_async = any( + param.type.name == "Gio.AsyncReadyCallback" for param in full_param_list + ) + if not is_async: + result_element = element + + finish_c_identifier = ( + result_element.get(f"{{{C_NAMESPACE}}}identifier") if is_async else None + ) + + throws = result_element.get("throws") == "1" + + return ( + c_identifier, + finish_c_identifier, + param_list, + has_closure_param, + throws, + result_element, + ) + + +def extract_parameters( + parameter_elements: List[ET.Element], + nullable_implies_optional: bool, + object_type: ObjectType, + resolve_type: ResolveTypeCallback, +) -> List[Parameter]: + factory = object_type.model.factory + entries = [] + for param in parameter_elements: + nullable = param.get("nullable") == "1" + entries.append((param, nullable)) + + last_required_index = None + for i, (param, nullable) in enumerate(entries): + optional = nullable and nullable_implies_optional + if not optional: + last_required_index = i + + param_list = [] + for i, (param, nullable) in enumerate(entries): + name = param.get("name") + type = extract_type_from_entity(param, resolve_type) + + if last_required_index is None or i > last_required_index: + optional = nullable and nullable_implies_optional + else: + optional = False + + ownership_val = param.get("transfer-ownership") + transfer_ownership = ( + TransferOwnership[ownership_val] + if ownership_val is not None + else TransferOwnership.none + ) + + raw_direction = param.get("direction") + direction = ( + Direction(raw_direction) if raw_direction is not None else Direction.IN + ) + + param_list.append( + factory.parameter( + name, + type, + optional, + nullable, + transfer_ownership, + direction, + object_type, + ) + ) + return param_list + + +def all_regular_parameters(parameters: List[Parameter]) -> Iterator[Parameter]: + callback_index = None + for i, param in enumerate(parameters): + if param.type.name == "Gio.AsyncReadyCallback": + callback_index = i + continue + + if callback_index is not None and i == callback_index + 1: + continue + + yield param + + +def extract_type_from_entity( + parent_element: ET.Element, resolve_type: ResolveTypeCallback +) -> Optional[Type]: + child = parent_element.find("type", GIR_NAMESPACES) + if child is None: + child = parent_element.find("array", GIR_NAMESPACES) + assert child is not None + element_type = extract_type_from_entity(child, resolve_type) + if element_type.name == "utf8": + return Type( + "utf8[]", + "strv", + "gchar **", + "NULL", + "g_strdupv", + "g_strfreev", + ) + elif element_type.name == "gchar": + return Type("char[]", "chararray", "gchar *", "NULL", "NULL", "NULL") + elif element_type.name == "GObject.Value": + return Type("Value[]", "valuearray", "GValue *", "NULL", "NULL", "NULL") + else: + assert ( + element_type.name == "guint8" + ), f"unsupported array type: {element_type.name}" + return Type("uint8[]", "bytearray", "guint8 *", "NULL", "NULL", "NULL") + + return parse_type(child, resolve_type) + + +def parse_type( + element: ET.Element, resolve_type: ResolveTypeCallback +) -> Optional[Type]: + name = element.get("name") + assert name is not None + if name == "none": + return None + + is_primitive = name in PRIMITIVE_GIR_TYPES + c_type = element.get(f"{{{C_NAMESPACE}}}type") + + core_tag = None + if is_primitive: + type_element = element + if c_type is None: + c_type = name + else: + name, type_element = resolve_type(name) + if type_element.tag.startswith(CORE_TAG_PREFIX): + core_tag = type_element.tag[len(CORE_TAG_PREFIX) :] + c_type = type_element.get(f"{{{C_NAMESPACE}}}type") + if core_tag in {"class", "interface", "record"}: + c_type += "*" + + nick = type_nick_from_name(name, element, resolve_type) + c = c_type.replace("*", " *") + + default_value = "NULL" if "*" in c else None + + if name == "utf8": + copy_func = "g_strdup" + destroy_func = "g_free" + elif name == "utf8[]": + copy_func = "g_strdupv" + destroy_func = "g_strfreev" + elif name == "GLib.HashTable": + copy_func = "g_hash_table_ref" + destroy_func = "g_hash_table_unref" + elif name == "GLib.Quark": + copy_func = None + destroy_func = None + elif name == "GObject.Value": + copy_func = "g_value_copy" + destroy_func = "g_value_reset" + elif name == "GObject.Closure": + copy_func = "g_closure_ref" + destroy_func = "g_closure_unref" + elif core_tag in {"class", "interface"}: + copy_func = "g_object_ref" + destroy_func = "g_object_unref" + elif is_primitive or core_tag in {"bitfield", "callback", "enumeration"}: + copy_func = None + destroy_func = None + else: + copy_func = type_element.get("copy-function") + destroy_func = type_element.get("free-function") + assert ( + destroy_func is not None + ), f"unable to resolve destroy function for {name}, core_tag={core_tag}" + + return Type(name, nick, c, default_value, copy_func, destroy_func) + + +def type_nick_from_name( + name: str, element: ET.Element, resolve_type: ResolveTypeCallback +) -> str: + if name == "GLib.PollFD": + return "pollfd" + + tokens = name.split(".", maxsplit=1) + if len(tokens) == 1: + result = tokens[0] + if result.startswith("g"): + result = result[1:] + else: + result = to_snake_case(tokens[1]) + + if result == "hash_table": + key_type = parse_type(element[0], resolve_type) + value_type = parse_type(element[1], resolve_type) + assert ( + key_type.name == "utf8" and value_type.name == "GLib.Variant" + ), "only GHashTable is supported for now" + result = "vardict" + + return result diff --git a/frida/frida_bindgen_core/naming.py b/frida/frida_bindgen_core/naming.py new file mode 100644 index 0000000..df3ce85 --- /dev/null +++ b/frida/frida_bindgen_core/naming.py @@ -0,0 +1,40 @@ +def to_snake_case(name: str) -> str: + result = [] + i = 0 + n = len(name) + while i < n: + if name[i].isupper(): + if i > 0: + result.append("_") + start = i + if i + 1 < n and name[i + 1].islower(): + while i + 1 < n and name[i + 1].islower(): + i += 1 + else: + while i + 1 < n and name[i + 1].isupper(): + i += 1 + if i + 1 < n: + i -= 1 + result.append(name[start : i + 1].lower()) + else: + result.append(name[i]) + i += 1 + return "".join(result) + + +def to_pascal_case(name: str) -> str: + return "".join(word.capitalize() for word in name.split("_")) + + +def to_camel_case(name: str) -> str: + words = name.split("_") + return words[0] + "".join(word.capitalize() for word in words[1:]) + + +def to_macro_case(identifier: str) -> str: + result = [] + for i, char in enumerate(identifier): + if char.isupper() and i != 0: + result.append("_") + result.append(char) + return "".join(result).upper() diff --git a/frida/meson.build b/frida/meson.build index 1def27c..4539798 100644 --- a/frida/meson.build +++ b/frida/meson.build @@ -1,8 +1,115 @@ -subdir('_frida') +if frida_core_dep.type_name() == 'internal' + frida_core_subprj = subproject('frida-core') + frida_gir = frida_core_subprj.get_variable('core_public_gir') + glib_gir = frida_core_subprj.get_variable('glib_gir') + gobject_gir = frida_core_subprj.get_variable('gobject_gir') + gio_gir = frida_core_subprj.get_variable('gio_gir') +else + girdir = frida_core_dep.get_variable('frida_girdir') + frida_gir = girdir / 'Frida-1.0.gir' + glib_gir = girdir / 'GLib-2.0.gir' + gobject_gir = girdir / 'GObject-2.0.gir' + gio_gir = girdir / 'Gio-2.0.gir' +endif + +env = environment() +env.set('PYTHONPATH', meson.current_source_dir()) + +code = custom_target('binding-code', + output: [ + '__init__.py', + '_frida.pyi', + 'extension.c', + 'aio.py', + ], + input: [ + frida_gir, + glib_gir, + gobject_gir, + gio_gir, + files( + 'frida_bindgen' / '__init__.py', + 'frida_bindgen' / '__main__.py', + 'frida_bindgen' / 'cli.py', + 'frida_bindgen' / 'codegen.py', + 'frida_bindgen' / 'customization.py', + 'frida_bindgen' / 'loader.py', + 'frida_bindgen' / 'model.py', + 'frida_bindgen' / 'assets' / 'codegen_gobject_globals.c', + 'frida_bindgen' / 'assets' / 'codegen_gobject_methods.c', + 'frida_bindgen' / 'assets' / 'codegen_gobject_prototypes.h', + 'frida_bindgen' / 'assets' / 'codegen_macros.h', + 'frida_bindgen' / 'assets' / 'codegen_structs.h', + 'frida_bindgen' / 'assets' / 'codegen_typedefs.h', + 'frida_bindgen' / 'assets' / 'facade_toplevel.py', + 'frida_bindgen' / 'assets' / 'facade_toplevel_aio.py', + 'frida_bindgen' / 'assets' / 'facade_cancellable.py', + 'frida_bindgen' / 'assets' / 'facade_cancellable_aio.py', + 'frida_bindgen' / 'assets' / 'facade_cancellable_helpers.py', + 'frida_bindgen' / 'assets' / 'facade_cancellable_helpers_aio.py', + 'frida_bindgen' / 'assets' / 'facade_device_members.py', + 'frida_bindgen' / 'assets' / 'facade_device_members_aio.py', + 'frida_bindgen' / 'assets' / 'facade_device_manager_members.py', + 'frida_bindgen' / 'assets' / 'facade_device_manager_members_aio.py', + 'frida_bindgen' / 'assets' / 'facade_bus.py', + 'frida_bindgen' / 'assets' / 'facade_bus_aio.py', + 'frida_bindgen' / 'assets' / 'facade_portal.py', + 'frida_bindgen' / 'assets' / 'facade_portal_aio.py', + 'frida_bindgen' / 'assets' / 'facade_rpc_helpers.py', + 'frida_bindgen' / 'assets' / 'facade_rpc_helpers_aio.py', + 'frida_bindgen' / 'assets' / 'facade_rpc_members.py', + 'frida_bindgen' / 'assets' / 'facade_rpc_members_aio.py', + 'frida_bindgen' / 'assets' / 'facade_iostream.py', + 'frida_bindgen' / 'assets' / 'facade_iostream_aio.py', + 'frida_bindgen' / 'assets' / 'facade_interface.py', + 'frida_bindgen' / 'assets' / 'facade_interface_aio.py', + 'frida_bindgen' / 'assets' / 'codegen_endpoint_parameters.c', + ), + files( + 'frida_bindgen_core' / '__init__.py', + 'frida_bindgen_core' / 'loader.py', + 'frida_bindgen_core' / 'model.py', + 'frida_bindgen_core' / 'naming.py', + ), + ], + command: [ + python, '-m', 'frida_bindgen', + '--frida-gir=@INPUT0@', + '--glib-gir=@INPUT1@', + '--gobject-gir=@INPUT2@', + '--gio-gir=@INPUT3@', + '--output-py=@OUTPUT0@', + '--output-pyi=@OUTPUT1@', + '--output-c=@OUTPUT2@', + '--output-aio=@OUTPUT3@', + ], + env: env, + install: true, + install_dir: [ + python.get_install_dir() / 'frida', + python.get_install_dir() / 'frida', + false, + python.get_install_dir() / 'frida', + ] +) py_sources = [ - '__init__.py', - 'core.py', 'py.typed', ] python.install_sources(py_sources, subdir: 'frida', pure: false) + +extra_link_args = [] +if host_os_family == 'darwin' + extra_link_args += '-Wl,-exported_symbol,_PyInit__frida' +elif host_os_family != 'windows' + extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / 'extension.version' +endif + +extension = python.extension_module('_frida', code[2], + limited_api: '3.7', + c_args: frida_component_cflags, + link_args: extra_link_args, + dependencies: [python_dep, frida_core_dep, os_deps], + install: true, + subdir: 'frida', +) diff --git a/meson.build b/meson.build index 7455677..efec51f 100644 --- a/meson.build +++ b/meson.build @@ -5,15 +5,13 @@ project('frida-python', 'c', meson_version: '>=1.3.0', ) -host_os = host_machine.system() -if host_os == 'android' +python = import('python').find_installation() +cc = meson.get_compiler('c') +host_os_family = host_machine.system() +if host_os_family == 'android' host_os_family = 'linux' -else - host_os_family = host_os endif -cc = meson.get_compiler('c') - frida_component_cflags = [] ndebug = get_option('b_ndebug') if ndebug == 'true' or (ndebug == 'if-release' and not get_option('debug')) @@ -24,80 +22,12 @@ if ndebug == 'true' or (ndebug == 'if-release' and not get_option('debug')) ] endif -target_conditionals_prefix = '#include ' - -is_macos_src = target_conditionals_prefix + ''' -#if !TARGET_OS_OSX -# error Not macOS -#endif -''' -if cc.compiles(is_macos_src, name: 'compiling for macOS') - host_os = 'macos' -endif - -is_ios_src = target_conditionals_prefix + ''' -#if !TARGET_OS_IOS -# error Not iOS -#endif -''' -if cc.compiles(is_ios_src, name: 'compiling for iOS') - host_os = 'ios' -endif - -if cc.has_header('android/api-level.h') - host_os = 'android' -endif - -python_incdir = get_option('python_incdir') -if python_incdir == '' - python = get_option('python') - if python == '' - python = find_program('python3', required: false) - if not python.found() - python = find_program('python') - endif - endif - - result = run_command(python, '-c', - 'import sys; sys.stdout.write(f"{sys.version_info[0]}.{sys.version_info[1]}")', - check: true) - python_version = result.stdout() - python_name = 'python' + python_version - - result = run_command(python, '-c', - 'from distutils import sysconfig; import sys; sys.stdout.write(sysconfig.get_python_inc())', - check: true) - python_incdir = result.stdout() -else - py_major_v = cc.get_define('PY_MAJOR_VERSION', - prefix: '#include ', - args: ['-I' + python_incdir]) - - py_minor_v = cc.get_define('PY_MINOR_VERSION', - prefix: '#include ', - args: ['-I' + python_incdir]) - - python_version = py_major_v + '.' + py_minor_v - python_name = 'python' + python_version -endif - -python_site_packages = join_paths(get_option('libdir'), python_name, 'site-packages') - -cdata = configuration_data() - -cdata.set('HAVE_' + host_os_family.to_upper(), 1) -if host_os != host_os_family - cdata.set('HAVE_' + host_os.to_upper(), 1) -endif - +python_dep = python.dependency() frida_core_dep = dependency('frida-core-1.0') + os_deps = [] -if host_os_family != 'windows' +if host_machine.system() != 'windows' os_deps += dependency('gio-unix-2.0') endif -configure_file(input: 'config.h.in', - output: 'config.h', - configuration: cdata) - subdir('frida') diff --git a/pyproject.toml b/pyproject.toml index 85966e1..eb48229 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,21 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 120 +extend-exclude = "frida/frida_bindgen/assets/" [tool.isort] profile = "black" line_length = 120 +extend_skip_glob = ["frida/frida_bindgen/assets/*"] +known_first_party = ["frida", "frida_bindgen_core"] + +[tool.mypy] +exclude = ["frida/frida_bindgen/assets/"] + +[[tool.mypy.overrides]] +module = "frida_bindgen_core.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = ["_frida", "frida._frida", "frida.aio", "frida.core"] +ignore_missing_imports = true diff --git a/setup.py b/setup.py index 25fd1e1..c9c2a4f 100644 --- a/setup.py +++ b/setup.py @@ -1,109 +1,200 @@ import os import platform import shutil +import subprocess +import sys +from pathlib import Path from setuptools import setup from setuptools.command.build_ext import build_ext +from setuptools.command.build_py import build_py from setuptools.extension import Extension -package_dir = os.path.dirname(os.path.realpath(__file__)) -pkg_info = os.path.join(package_dir, "PKG-INFO") -in_source_package = os.path.isfile(pkg_info) -if in_source_package: - with open(pkg_info, "r", encoding="utf-8") as f: - version_line = [line for line in f if line.startswith("Version: ")][0].strip() - frida_version = version_line[9:] -else: - frida_version = os.environ.get("FRIDA_VERSION", "0.0.0") -with open(os.path.join(package_dir, "README.md"), "r", encoding="utf-8") as f: - long_description = f.read() -frida_extension = os.environ.get("FRIDA_EXTENSION", None) +SOURCE_ROOT = Path(__file__).resolve().parent +FRIDA_EXTENSION = os.environ.get("FRIDA_EXTENSION") +FRIDA_CORE_DEVKIT = os.environ.get("FRIDA_CORE_DEVKIT") +FRIDA_GIR_DIR = os.environ.get("FRIDA_GIR_DIR") + +GENERATED_PACKAGE_FILES = ["__init__.py", "aio.py", "_frida.pyi"] +SYSTEM_GIR_FILES = ["GLib-2.0.gir", "GObject-2.0.gir", "Gio-2.0.gir"] + + +def detect_version(): + pkg_info = SOURCE_ROOT / "PKG-INFO" + if pkg_info.exists(): + version_line = next( + line + for line in pkg_info.read_text(encoding="utf-8").splitlines() + if line.startswith("Version: ") + ) + return version_line[9:] + return os.environ.get("FRIDA_VERSION", "0.0.0") + + +def compute_long_description(): + return (SOURCE_ROOT / "README.md").read_text(encoding="utf-8") + + +def find_devkit_gir(devkit_dir): + for name in ("frida-core.gir", "Frida-1.0.gir"): + path = devkit_dir / name + if path.is_file(): + return path + raise RuntimeError(f"Frida devkit at {devkit_dir} does not contain frida-core.gir or Frida-1.0.gir") + + +def find_system_gir_dir(): + candidates = [] + if FRIDA_GIR_DIR: + candidates.extend(Path(entry) for entry in FRIDA_GIR_DIR.split(os.pathsep) if entry) + candidates.extend( + [ + Path(sys.prefix) / "share" / "gir-1.0", + Path(sys.base_prefix) / "share" / "gir-1.0", + Path("/usr/share/gir-1.0"), + ] + ) + + for directory in candidates: + if all((directory / name).is_file() for name in SYSTEM_GIR_FILES): + return directory + + expected = ", ".join(SYSTEM_GIR_FILES) + raise RuntimeError( + f"Unable to find {expected}. Install GObject introspection data or set " + "FRIDA_GIR_DIR to its gir-1.0 directory." + ) + + +def generated_paths(directory): + return { + "py": directory / "__init__.py", + "pyi": directory / "_frida.pyi", + "aio": directory / "aio.py", + "c": directory / "extension.c", + } + + +def copy_generated_package(destination, generated): + destination.mkdir(parents=True, exist_ok=True) + for name in GENERATED_PACKAGE_FILES: + shutil.copy2(generated / name, destination / name) + + +class FridaGeneratedBuildPy(build_py): + """The public Python package is generated by build_ext from the devkit GIR.""" + + def find_package_modules(self, package, package_dir): + if package == "frida": + return [] + return super().find_package_modules(package, package_dir) class FridaPrebuiltExt(build_ext): def build_extension(self, ext): - target = self.get_ext_fullpath(ext.name) - target_dir = os.path.dirname(target) - os.makedirs(target_dir, exist_ok=True) + source = Path(FRIDA_EXTENSION) + generated = source.parent + target = Path(self.get_ext_fullpath(ext.name)) + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, target) + copy_generated_package(target.parent, generated) + + +class FridaDevkitExt(build_ext): + def build_extension(self, ext): + devkit_dir = Path(FRIDA_CORE_DEVKIT).resolve() + if not (devkit_dir / "frida-core.h").is_file(): + raise RuntimeError(f"Frida devkit at {devkit_dir} does not contain frida-core.h") + + generator_dir = SOURCE_ROOT / "frida" / "frida_bindgen" + core_dir = SOURCE_ROOT / "frida" / "frida_bindgen_core" + if not generator_dir.is_dir() or not core_dir.is_dir(): + raise RuntimeError("The bundled Frida binding generator is incomplete; re-checkout this source tree.") - shutil.copyfile(frida_extension, target) + gir_dir = find_system_gir_dir() + output_dir = Path(self.build_temp) / "frida-bindgen" + output_dir.mkdir(parents=True, exist_ok=True) + outputs = generated_paths(output_dir) + + env = os.environ.copy() + source_path = str(SOURCE_ROOT / "frida") + env["PYTHONPATH"] = source_path + ( + os.pathsep + env["PYTHONPATH"] if env.get("PYTHONPATH") else "" + ) + subprocess.run( + [ + sys.executable, + "-m", + "frida_bindgen", + f"--frida-gir={find_devkit_gir(devkit_dir)}", + f"--frida-header={devkit_dir / 'frida-core.h'}", + f"--glib-gir={gir_dir / 'GLib-2.0.gir'}", + f"--gobject-gir={gir_dir / 'GObject-2.0.gir'}", + f"--gio-gir={gir_dir / 'Gio-2.0.gir'}", + f"--output-py={outputs['py']}", + f"--output-pyi={outputs['pyi']}", + f"--output-aio={outputs['aio']}", + f"--output-c={outputs['c']}", + ], + cwd=SOURCE_ROOT, + env=env, + check=True, + ) + + ext.sources = [str(outputs["c"])] + ext.include_dirs = [str(devkit_dir)] + ext.library_dirs = [str(devkit_dir)] + ext.libraries = ["frida-core"] + ext.extra_link_args = extra_link_args() + super().build_extension(ext) + + copy_generated_package(Path(self.get_ext_fullpath(ext.name)).parent, output_dir) class FridaMissingDevkitBuildExt(build_ext): def build_extension(self, ext): raise RuntimeError( - "Need frida-core devkit to build from source.\n" - "Download one from https://github.com/frida/frida/releases, " - "extract it to a directory,\n" - "and then add an environment variable named FRIDA_CORE_DEVKIT " - "pointing at the directory." + "Need FRIDA_CORE_DEVKIT to build from source. Download a frida-core devkit from " + "https://github.com/frida/frida/releases and point FRIDA_CORE_DEVKIT at it." ) -include_dirs = [] -library_dirs = [] -libraries = [] -extra_link_args = [] - -cmdclass = {} -if frida_extension is not None: - cmdclass["build_ext"] = FridaPrebuiltExt -else: - devkit_dir = os.environ.get("FRIDA_CORE_DEVKIT", None) - if devkit_dir is not None: - include_dirs += [devkit_dir] - library_dirs += [devkit_dir] - libraries += ["frida-core"] - - system = platform.system() - if system == "Windows": - pass - elif system == "Darwin": - extra_link_args += [ - "-Wl,-exported_symbol,_PyInit__frida", - "-Wl,-dead_strip", - ] - if "_PYTHON_HOST_PLATFORM" not in os.environ: - if platform.machine() == "arm64": - host_arch = "arm64" - macos_req = "11.0" - else: - host_arch = "x86_64" - macos_req = "10.9" - os.environ["_PYTHON_HOST_PLATFORM"] = f"macosx-{macos_req}-{host_arch}" - os.environ["ARCHFLAGS"] = f"-arch {host_arch}" - os.environ["MACOSX_DEPLOYMENT_TARGET"] = macos_req - else: - version_script = os.path.join(package_dir, "frida/_frida", "extension.version") - if not os.path.exists(version_script): - with open(version_script, "w", encoding="utf-8") as f: - f.write( - "\n".join( - [ - "{", - " global:", - " PyInit__frida;", - "", - " local:", - " *;", - "};", - ] - ) - ) - extra_link_args += [ - f"-Wl,--version-script,{version_script}", - "-Wl,--gc-sections", - ] - else: - cmdclass["build_ext"] = FridaMissingDevkitBuildExt +def extra_link_args(): + system = platform.system() + if system == "Windows": + return [] + if system == "Darwin": + if "_PYTHON_HOST_PLATFORM" not in os.environ: + if platform.machine() == "arm64": + host_arch = "arm64" + macos_req = "11.0" + else: + host_arch = "x86_64" + macos_req = "10.9" + os.environ["_PYTHON_HOST_PLATFORM"] = f"macosx-{macos_req}-{host_arch}" + os.environ["ARCHFLAGS"] = f"-arch {host_arch}" + os.environ["MACOSX_DEPLOYMENT_TARGET"] = macos_req + return ["-Wl,-exported_symbol,_PyInit__frida", "-Wl,-dead_strip"] + return [ + f"-Wl,--version-script,{SOURCE_ROOT / 'frida' / 'extension.version'}", + "-Wl,--gc-sections", + ] + + +def select_build_ext(): + if FRIDA_EXTENSION is not None: + return FridaPrebuiltExt + if FRIDA_CORE_DEVKIT is not None: + return FridaDevkitExt + return FridaMissingDevkitBuildExt if __name__ == "__main__": setup( name="frida", - version=frida_version, + version=detect_version(), description="Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers", - long_description=long_description, + long_description=compute_long_description(), long_description_content_type="text/markdown", author="Frida Developers", author_email="oleavr@frida.re", @@ -111,7 +202,9 @@ def build_extension(self, ext): install_requires=["typing_extensions; python_version<'3.11'"], python_requires=">=3.7", license="wxWindows Library Licence, Version 3.1", - keywords="frida debugger dynamic instrumentation inject javascript windows macos linux ios iphone ipad android qnx", + keywords=( + "frida debugger dynamic instrumentation inject javascript windows macos linux ios iphone ipad android qnx" + ), classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -134,19 +227,9 @@ def build_extension(self, ext): "Topic :: Software Development :: Debuggers", "Topic :: Software Development :: Libraries :: Python Modules", ], - packages=["frida", "frida._frida"], - package_data={"frida": ["py.typed"], "frida._frida": ["py.typed", "__init__.pyi"]}, - ext_modules=[ - Extension( - name="frida._frida", - sources=["frida/_frida/extension.c"], - include_dirs=include_dirs, - library_dirs=library_dirs, - libraries=libraries, - extra_link_args=extra_link_args, - py_limited_api=True, - ) - ], - cmdclass=cmdclass, + packages=["frida"], + package_data={"frida": ["py.typed"]}, + ext_modules=[Extension(name="frida._frida", sources=[], py_limited_api=True)], + cmdclass={"build_ext": select_build_ext(), "build_py": FridaGeneratedBuildPy}, zip_safe=False, ) diff --git a/tests/__init__.py b/tests/__init__.py index 8da1466..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +0,0 @@ -from .test_core import TestCore -from .test_rpc import TestRpc - -__all__ = ["TestCore", "TestRpc"] diff --git a/tests/compile_bindgen.sh b/tests/compile_bindgen.sh new file mode 100755 index 0000000..a8942b1 --- /dev/null +++ b/tests/compile_bindgen.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# Quick feedback loop: regenerate bindings via run_bindgen.sh, then compile the +# generated extension.c with the exact flags ninja would use, without linking. +set -e + +repo=$(cd "$(dirname "$0")/.." && pwd) + +"$repo/tests/run_bindgen.sh" + +python3 - "$repo" "$@" <<'EOF' +import json +import shlex +import subprocess +import sys + +repo = sys.argv[1] +extra = sys.argv[2:] + +cdb = json.load(open(f"{repo}/build/compile_commands.json")) +entry = next(e for e in cdb if e["file"].endswith("extension.c")) +args = shlex.split(entry["command"]) + +cmd = [] +i = 0 +while i < len(args): + a = args[i] + if a in ("-o", "-MF", "-MQ", "-c"): + i += 2 + continue + if a == "-MD": + i += 1 + continue + cmd.append(a) + i += 1 +cmd += ["-fsyntax-only", f"{repo}/build/bindgen-out/extension.c"] + extra + +sys.exit(subprocess.run(cmd, cwd=entry["directory"]).returncode) +EOF diff --git a/tests/run_bindgen.sh b/tests/run_bindgen.sh new file mode 100755 index 0000000..1054c5f --- /dev/null +++ b/tests/run_bindgen.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Quick feedback loop: run the bindgen against the .gir files from the build +# tree, without going through ninja. Outputs land in tests/bindgen-out/. +set -e + +repo=$(dirname "$0")/.. +girdir=$repo/build/subprojects/frida-core/src/api +outdir=$repo/build/bindgen-out +mkdir -p "$outdir" + +PYTHONPATH=$repo/frida exec python3 -m frida_bindgen \ + --frida-gir="$girdir/Frida-1.0.gir" \ + --glib-gir="$girdir/GLib-2.0.gir" \ + --gobject-gir="$girdir/GObject-2.0.gir" \ + --gio-gir="$girdir/Gio-2.0.gir" \ + --output-py="$outdir/__init__.py" \ + --output-aio="$outdir/aio.py" \ + --output-pyi="$outdir/_frida.pyi" \ + --output-c="$outdir/extension.c" \ + "$@" diff --git a/tests/test_bindgen.py b/tests/test_bindgen.py new file mode 100644 index 0000000..6cef41d --- /dev/null +++ b/tests/test_bindgen.py @@ -0,0 +1,934 @@ +import os +import socket +import subprocess +import sys +import unittest +from pathlib import Path + + +def free_port(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("127.0.0.1", 0)) + return s.getsockname()[1] + + +REPO = Path(__file__).resolve().parent.parent +BINDGEN_CORE = REPO / "frida-bindgen" +GIRDIR = REPO / "build" / "subprojects" / "frida-core" / "src" / "api" +OUTDIR = REPO / "build" / "bindgen-out" +BUILT_EXTENSION = REPO / "build" / "frida" / "_frida.abi3.so" + + +@unittest.skipUnless( + (GIRDIR / "Frida-1.0.gir").exists(), + "requires a configured build tree with generated .gir files", +) +class TestBindgen(unittest.TestCase): + def test_generates_all_outputs(self): + OUTDIR.mkdir(exist_ok=True) + subprocess.run( + [ + sys.executable, + "-m", + "frida_bindgen", + f"--frida-gir={GIRDIR / 'Frida-1.0.gir'}", + f"--glib-gir={GIRDIR / 'GLib-2.0.gir'}", + f"--gobject-gir={GIRDIR / 'GObject-2.0.gir'}", + f"--gio-gir={GIRDIR / 'Gio-2.0.gir'}", + f"--output-py={OUTDIR / '__init__.py'}", + f"--output-aio={OUTDIR / 'aio.py'}", + f"--output-pyi={OUTDIR / '_frida.pyi'}", + f"--output-c={OUTDIR / 'extension.c'}", + ], + check=True, + env={"PYTHONPATH": os.pathsep.join([str(REPO / "frida"), str(BINDGEN_CORE)])}, + ) + for name in ("__init__.py", "aio.py", "_frida.pyi", "extension.c"): + self.assertGreater((OUTDIR / name).stat().st_size, 0) + + def test_generated_python_compiles(self): + for name in ("__init__.py", "aio.py"): + path = OUTDIR / name + if not path.exists(): + self.skipTest("run test_generates_all_outputs first") + compile(path.read_text(), str(path), "exec") + + +@unittest.skipUnless( + BUILT_EXTENSION.exists(), + "requires a built _frida extension (run `make`)", +) +class TestExtension(unittest.TestCase): + def setUp(self): + sys.path.insert(0, str(BUILT_EXTENSION.parent)) + import _frida + + self._frida = _frida + + def tearDown(self): + sys.path.remove(str(BUILT_EXTENSION.parent)) + + def test_exposes_version(self): + self.assertRegex(self._frida.__version__, r"\d+\.\d+") + + def test_constructs_object_with_string_and_enum_parameters(self): + relay = self._frida.Relay("1.2.3.4:5", "user", "pass", "turn-udp") + self.assertIsInstance(relay, self._frida.Relay) + + def test_constructs_object_without_parameters(self): + self.assertIsInstance(self._frida.DeviceManager(), self._frida.DeviceManager) + + def test_rejects_invalid_enum_value(self): + with self.assertRaises(ValueError): + self._frida.Relay("1.2.3.4:5", "user", "pass", "bogus-kind") + + def test_reads_back_string_and_enum_properties(self): + relay = self._frida.Relay("1.2.3.4:5", "bob", "secret", "turn-tcp") + self.assertEqual(relay.address, "1.2.3.4:5") + self.assertEqual(relay.username, "bob") + self.assertEqual(relay.password, "secret") + self.assertEqual(relay.kind, "turn-tcp") + + def test_invokes_synchronous_methods(self): + cancellable = self._frida.Cancellable() + self.assertFalse(cancellable.is_cancelled()) + cancellable.cancel() + self.assertTrue(cancellable.is_cancelled()) + + def test_registers_frida_exceptions(self): + for name in ( + "ServerNotRunningError", + "ProcessNotFoundError", + "InvalidArgumentError", + "TransportError", + "OperationCancelledError", + ): + exception = getattr(self._frida, name) + self.assertTrue(issubclass(exception, Exception)) + self.assertEqual(exception.__module__, "frida") + + def test_throwing_method_raises_mapped_exception(self): + cancellable = self._frida.Cancellable() + cancellable.cancel() + with self.assertRaises(self._frida.OperationCancelledError): + cancellable.set_error_if_cancelled() + + def test_instantiates_handle_only_types(self): + # Types with no .gir constructor are created by the marshalling layer + # and must accept a bare, argument-free construction. + self.assertIsInstance(self._frida.Device(), self._frida.Device) + self.assertIsInstance(self._frida.Session(), self._frida.Session) + + def test_exposes_object_typed_property(self): + self.assertTrue(hasattr(self._frida.IOStream, "input_stream")) + self.assertTrue(hasattr(self._frida.IOStream, "output_stream")) + + def test_string_array_property_round_trips(self): + options = self._frida.SpawnOptions() + self.assertIsNone(options.argv) + options.argv = ["/bin/ls", "-la"] + self.assertEqual(options.argv, ["/bin/ls", "-la"]) + options.argv = None + self.assertIsNone(options.argv) + + def test_vardict_property_round_trips(self): + options = self._frida.SpawnOptions() + options.aux = {"name": "value", "count": 42, "flag": True} + self.assertEqual(options.aux, {"name": "value", "count": 42, "flag": True}) + + def test_variant_marshalling_types_and_casts(self): + options = self._frida.SpawnOptions() + options.aux = { + "int": 42, + "uint64": ("uint64", 0xFFFFFFFFFFFFFFFF), + "float": 3.5, + "string": "hi", + "bool": True, + "bytes": b"\x00\x01\x02", + "list": [1, 2, 3], + "nested": {"x": 1, "y": "z"}, + "tuple": ("a", 1), + } + aux = options.aux + self.assertEqual(aux["int"], 42) + # the ("uint64", ...) cast preserves the value; as int64 it would be -1 + self.assertEqual(aux["uint64"], 0xFFFFFFFFFFFFFFFF) + self.assertEqual(aux["float"], 3.5) + self.assertEqual(aux["string"], "hi") + self.assertIs(aux["bool"], True) + self.assertEqual(aux["bytes"], b"\x00\x01\x02") + self.assertEqual(aux["list"], [1, 2, 3]) + self.assertEqual(aux["nested"], {"x": 1, "y": "z"}) + self.assertEqual(aux["tuple"], ("a", 1)) + + def test_async_method_delivers_result_to_callback(self): + import threading + + manager = self._frida.DeviceManager() + done = threading.Event() + box = {} + + def on_complete(result, error): + box["result"] = result + box["error"] = error + done.set() + + manager.close(on_complete) + self.assertTrue(done.wait(10), "callback was not invoked") + self.assertIsNone(box["error"]) + self.assertIsNone(box["result"]) + + def test_raising_async_callback_does_not_corrupt_thread(self): + import contextlib + import io + import threading + + raised = threading.Event() + + def bad_callback(result, error): + raised.set() + raise RuntimeError("handler blew up") + + with contextlib.redirect_stderr(io.StringIO()): + self._frida.DeviceManager().close(bad_callback) + self.assertTrue(raised.wait(10)) + + # A subsequent operation must still complete cleanly. + box = self._await_async(lambda cb: self._frida.DeviceManager().close(cb)) + self.assertIsNone(box["error"]) + + def _await_async(self, invoke): + import threading + + done = threading.Event() + box = {} + + def on_complete(result, error): + box["result"] = result + box["error"] = error + done.set() + + invoke(on_complete) + self.assertTrue(done.wait(10), "callback was not invoked") + return box + + def test_async_method_passes_parameters_and_maps_errors(self): + manager = self._frida.DeviceManager() + box = self._await_async(lambda cb: manager.get_device_by_id("no-such-device", 1, cb)) + self.assertIsNone(box["result"]) + self.assertIsInstance(box["error"], self._frida.InvalidArgumentError) + + def test_async_method_honors_cancellable(self): + manager = self._frida.DeviceManager() + cancellable = self._frida.Cancellable() + cancellable.cancel() + box = self._await_async(lambda cb: manager.get_device_by_id("anything", 5000, cb, cancellable)) + self.assertIsInstance(box["error"], self._frida.OperationCancelledError) + + +@unittest.skipUnless( + (REPO / "build" / "frida" / "__init__.py").exists(), + "requires a built facade (run `make`)", +) +class TestFacade(unittest.TestCase): + def setUp(self): + sys.path.insert(0, str(REPO / "build")) + self._purge_frida() + import frida + + self.frida = frida + + def tearDown(self): + sys.path.remove(str(REPO / "build")) + self._purge_frida() + + @staticmethod + def _purge_frida(): + # The source tree's frida/ is a namespace package the stale test_core + # imports; drop it so the built facade under build/ is what loads. + for name in [n for n in sys.modules if n == "frida" or n.startswith("frida.")]: + del sys.modules[name] + + def test_reexports_exceptions(self): + self.assertTrue(issubclass(self.frida.ServerNotRunningError, Exception)) + + def test_synchronous_method_blocks(self): + self.frida.DeviceManager().close() + + def test_synchronous_method_raises_mapped_error(self): + with self.assertRaises(self.frida.InvalidArgumentError): + self.frida.DeviceManager().get_device_by_id("no-such-device", 1) + + def test_aio_awaitable_completes(self): + import asyncio + + import frida.aio + + asyncio.run(frida.aio.DeviceManager().close()) + + def test_aio_awaitable_raises_mapped_error(self): + import asyncio + + import frida.aio + + async def scenario(): + with self.assertRaises(self.frida.InvalidArgumentError): + await frida.aio.DeviceManager().get_device_by_id("no-such-device", 1) + + asyncio.run(scenario()) + + def test_aio_cancellation_propagates(self): + import asyncio + + import frida.aio + + async def scenario(): + manager = frida.aio.DeviceManager() + task = asyncio.ensure_future(manager.get_device_by_id("nope", 60000)) + await asyncio.sleep(0.1) + task.cancel() + with self.assertRaises(asyncio.CancelledError): + await task + + asyncio.run(scenario()) + + def test_unreferenced_interface_impl_is_collected(self): + import gc + import weakref + + class MyAuth(self.frida.AuthenticationService): + def authenticate(self, token): + return "{}" + + refs = [weakref.ref(MyAuth()) for _ in range(3)] + gc.collect() + self.assertTrue(all(ref() is None for ref in refs)) + + def test_interface_impl_survives_gc_while_referenced_by_core(self): + import gc + + class MyAuth(self.frida.AuthenticationService): + def authenticate(self, token): + return "{}" + + params = self.frida.EndpointParameters(address="127.0.0.1", port=0, authentication=MyAuth()) + gc.collect() + gc.collect() + self.assertIsNotNone(params._impl.auth_service) + + def test_interface_can_be_implemented(self): + import json + + class MyAuth(self.frida.AuthenticationService): + def authenticate(self, token): + return json.dumps({"token": token}) + + service = MyAuth() + self.assertIsInstance(service, self.frida.AuthenticationService) + self.assertEqual(type(service._impl).__name__, "AuthenticationService") + + def test_endpoint_parameters_authentication_schemes(self): + self.frida.EndpointParameters(address="::1", authentication=("token", "secret")) + self.frida.EndpointParameters(address="::1", authentication=("callback", lambda token: {"token": token})) + + class MyAuth(self.frida.AuthenticationService): + def authenticate(self, token): + return "{}" + + self.frida.EndpointParameters(address="::1", authentication=MyAuth()) + with self.assertRaises(ValueError): + self.frida.EndpointParameters(authentication=("bogus", "x")) + + def test_object_constructor_parameters(self): + cluster = self.frida.EndpointParameters(address="127.0.0.1", port=0) + service = self.frida.PortalService(cluster, None) + self.assertIsInstance(service.device, self.frida.Device) + + def test_implemented_interface_receives_dispatch(self): + frida = self.frida + calls = [] + + def authenticate(token): + calls.append(token) + if token != "secret": + raise ValueError("wrong token") + return "{}" + + control_port = free_port() + control = frida.EndpointParameters( + address="127.0.0.1", port=control_port, authentication=("callback", authenticate) + ) + cluster = frida.EndpointParameters(address="127.0.0.1", port=free_port()) + service = frida.PortalService(cluster, control) + service.start() + try: + with self.assertRaises(frida.InvalidArgumentError): + frida.DeviceManager().add_remote_device( + f"127.0.0.1:{control_port}", token="wrong" + ).enumerate_processes() + frida.DeviceManager().add_remote_device(f"127.0.0.1:{control_port}", token="secret").enumerate_processes() + self.assertEqual(calls, ["wrong", "secret"]) + finally: + service.stop() + + def test_aio_implemented_interface_receives_dispatch(self): + import asyncio + + import frida.aio + + calls = [] + + async def scenario(): + class MyAuth(frida.aio.AuthenticationService): + async def authenticate(self, token): + calls.append(token) + await asyncio.sleep(0) + if token != "secret": + raise ValueError("wrong token") + return "{}" + + control_port = free_port() + control = frida.aio.EndpointParameters(address="127.0.0.1", port=control_port, authentication=MyAuth()) + cluster = frida.aio.EndpointParameters(address="127.0.0.1", port=free_port()) + service = frida.aio.PortalService(cluster, control) + await service.start() + try: + with self.assertRaises(self.frida.InvalidArgumentError): + manager = frida.aio.DeviceManager() + device = await manager.add_remote_device(f"127.0.0.1:{control_port}", token="wrong") + await device.enumerate_processes() + manager = frida.aio.DeviceManager() + device = await manager.add_remote_device(f"127.0.0.1:{control_port}", token="secret") + await device.enumerate_processes() + self.assertEqual(calls, ["wrong", "secret"]) + finally: + await service.stop() + + asyncio.run(scenario()) + + def test_web_request_handler_serves_response(self): + import urllib.request + + frida = self.frida + seen = [] + + class Handler(frida.WebRequestHandler): + def handle_request(self, request): + seen.append(request.path) + return frida.WebResponse(200, b"pong") + + control_port = free_port() + control = frida.EndpointParameters(address="127.0.0.1", port=control_port, request_handler=Handler()) + cluster = frida.EndpointParameters(address="127.0.0.1", port=free_port()) + service = frida.PortalService(cluster, control) + service.start() + try: + with urllib.request.urlopen(f"http://127.0.0.1:{control_port}/ping") as response: + status = response.status + body = response.read() + finally: + service.stop() + + self.assertEqual(status, 200) + self.assertEqual(body, b"pong") + self.assertEqual(seen, ["/ping"]) + + def test_web_request_handler_returning_none_yields_not_found(self): + import urllib.error + import urllib.request + + frida = self.frida + + class Handler(frida.WebRequestHandler): + def handle_request(self, request): + return None + + control_port = free_port() + control = frida.EndpointParameters(address="127.0.0.1", port=control_port, request_handler=Handler()) + cluster = frida.EndpointParameters(address="127.0.0.1", port=free_port()) + service = frida.PortalService(cluster, control) + service.start() + try: + with self.assertRaises(urllib.error.HTTPError) as raised: + urllib.request.urlopen(f"http://127.0.0.1:{control_port}/ping") + self.assertEqual(raised.exception.code, 404) + finally: + service.stop() + + def test_web_request_handler_error_propagates(self): + import urllib.error + import urllib.request + + frida = self.frida + + class Handler(frida.WebRequestHandler): + def handle_request(self, request): + raise ValueError("boom") + + control_port = free_port() + control = frida.EndpointParameters(address="127.0.0.1", port=control_port, request_handler=Handler()) + cluster = frida.EndpointParameters(address="127.0.0.1", port=free_port()) + service = frida.PortalService(cluster, control) + service.start() + try: + with self.assertRaises(urllib.error.HTTPError) as raised: + urllib.request.urlopen(f"http://127.0.0.1:{control_port}/ping") + self.assertEqual(raised.exception.code, 500) + self.assertEqual(raised.exception.read(), b"boom") + finally: + service.stop() + + @unittest.skipIf(sys.platform == "win32", "requires a POSIX shell") + def test_spawn_accepts_program_as_list(self): + import os + import tempfile + import time + + device = self.frida.get_local_device() + fd, path = tempfile.mkstemp() + os.close(fd) + try: + pid = device.spawn(["/bin/sh", "-c", f'printf hi > "{path}"']) + device.resume(pid) + + deadline = time.time() + 5 + content = "" + while time.time() < deadline: + with open(path) as f: + content = f.read() + if content: + break + time.sleep(0.05) + finally: + os.unlink(path) + + self.assertEqual(content, "hi") + + @unittest.skipIf(sys.platform == "win32", "requires a POSIX shell") + def test_spawn_accepts_env_as_dict(self): + import os + import tempfile + import time + + device = self.frida.get_local_device() + fd, path = tempfile.mkstemp() + os.close(fd) + try: + pid = device.spawn( + "/bin/sh", + argv=["/bin/sh", "-c", f'printf %s "$FRIDA_ENVP_TEST" > "{path}"'], + env={"FRIDA_ENVP_TEST": "envp-as-dict"}, + ) + device.resume(pid) + + deadline = time.time() + 5 + content = "" + while time.time() < deadline: + with open(path) as f: + content = f.read() + if content: + break + time.sleep(0.05) + finally: + os.unlink(path) + + self.assertEqual(content, "envp-as-dict") + + @unittest.skipIf(sys.platform == "win32", "requires a POSIX shell") + def test_spawn_accepts_envp_and_bytes_argv(self): + import os + import tempfile + import time + + device = self.frida.get_local_device() + fd, path = tempfile.mkstemp() + os.close(fd) + try: + pid = device.spawn( + [b"/bin/sh", b"-c", f'printf %s "$FRIDA_ENVP2" > "{path}"'], + envp={"FRIDA_ENVP2": "envp-full"}, + ) + device.resume(pid) + + deadline = time.time() + 5 + content = "" + while time.time() < deadline: + with open(path) as f: + content = f.read() + if content: + break + time.sleep(0.05) + finally: + os.unlink(path) + + self.assertEqual(content, "envp-full") + + def test_toplevel_convenience_functions(self): + frida = self.frida + self.assertRegex(frida.__version__, r"\d+\.\d+") + for name in ( + "spawn", + "resume", + "kill", + "attach", + "inject_library_file", + "inject_library_blob", + "enumerate_devices", + "get_device_matching", + "shutdown", + "query_system_parameters", + ): + self.assertTrue(callable(getattr(frida, name)), name) + self.assertIn("local", [d.id for d in frida.enumerate_devices()]) + self.assertEqual(frida.get_device_matching(lambda d: d.type == "local").id, "local") + + @unittest.skipIf(sys.platform == "win32", "requires a POSIX shell") + def test_toplevel_attach_and_query(self): + frida = self.frida + pid = frida.spawn(["/bin/sh", "-c", "sleep 30"]) + try: + session = frida.attach(pid) + session.detach() + finally: + frida.kill(pid) + params = frida.query_system_parameters() + self.assertIn("arch", params) + + def test_device_type_property(self): + device = self.frida.get_local_device() + self.assertEqual(device.type, "local") + + def test_bool_accessors_are_properties(self): + device = self.frida.get_local_device() + self.assertIs(device.is_lost, False) + session = device.attach(0) + self.assertIs(session.is_detached, False) + session.detach() + self.assertIs(session.is_detached, True) + + def test_device_get_process_by_name(self): + device = self.frida.get_local_device() + with self.assertRaises(self.frida.ProcessNotFoundError): + device.get_process("frida-nonexistent-process-zzz") + + @unittest.skipIf(sys.platform == "win32", "requires a POSIX shell") + def test_attach_accepts_pid(self): + device = self.frida.get_local_device() + pid = device.spawn(["/bin/sh", "-c", "sleep 30"]) + try: + session = device.attach(pid) + session.detach() + finally: + device.kill(pid) + + def test_facade_repr(self): + device = self.frida.get_local_device() + self.assertRegex(repr(device), r"Device\(id='local'") + process = self.frida.get_local_device().enumerate_processes()[0] + self.assertRegex(repr(process), r"Process\(pid=\d+") + + def test_create_script_positional_name(self): + session = self.frida.get_local_device().attach(0) + try: + script = session.create_script("rpc.exports={ping:()=>1};", "my-script") + script.load() + self.assertEqual(script.exports_sync.ping(), 1) + finally: + session.detach() + + def test_create_script_from_bytes_positional_name(self): + session = self.frida.get_local_device().attach(0) + try: + bytecode = session.compile_script("rpc.exports={ping:()=>1};", "n") + script = session.create_script_from_bytes(bytecode, "my-script") + script.load() + self.assertEqual(script.exports_sync.ping(), 1) + finally: + session.detach() + + def test_file_monitor_reports_changes(self): + import os + import tempfile + import time + + directory = tempfile.mkdtemp() + target = os.path.join(directory, "watched.txt") + monitor = self.frida.FileMonitor(target) + events = [] + monitor.on("change", lambda *args: events.append(args)) + monitor.enable() + try: + with open(target, "w") as f: + f.write("hi") + deadline = time.time() + 3 + while not events and time.time() < deadline: + time.sleep(0.05) + finally: + monitor.disable() + self.assertTrue(events) + self.assertEqual(events[0][0], target) + + def test_frida_core_alias(self): + import frida.core + + self.assertIs(frida.core.Session, self.frida.Session) + self.assertIs(frida.core.Device, self.frida.Device) + + def test_device_bus_post_accepts_dict(self): + bus = self.frida.get_local_device().bus + self.assertTrue(callable(bus.post)) + bus.post({"type": "ping"}) + + def test_script_options_snapshot_setter(self): + options = self._new_script_options() + options.snapshot = b"\x00\x01\x02" + self.assertEqual(options.snapshot, b"\x00\x01\x02") + + def _new_script_options(self): + from frida import _frida + + return _frida.ScriptOptions() + + def test_iostream_exposes_old_api(self): + for name in ("is_closed", "close", "read", "read_all", "write", "write_all"): + self.assertTrue(hasattr(self.frida.IOStream, name)) + + def test_get_local_device_returns_wrapped_device(self): + device = self.frida.get_local_device() + self.assertIsInstance(device, self.frida.Device) + self.assertEqual(device.id, "local") + + def test_device_manager_is_a_singleton(self): + import frida._frida as _frida + import frida.aio + + underlying = _frida.get_device_manager() + self.assertIs(_frida.get_device_manager(), underlying) + self.assertIs(self.frida.get_device_manager()._impl, underlying) + self.assertIs(frida.aio.get_device_manager()._impl, underlying) + + def test_aio_get_local_device(self): + import asyncio + + import frida.aio + + device = asyncio.run(frida.aio.get_local_device()) + self.assertIsInstance(device, frida.aio.Device) + self.assertEqual(device.id, "local") + + def test_method_with_options_parameter_returns_object(self): + import os + + device = self.frida.get_local_device() + process = device.get_process_by_pid(os.getpid()) + self.assertIsInstance(process, self.frida.Process) + self.assertEqual(process.pid, os.getpid()) + + def test_method_with_options_parameter_maps_errors(self): + device = self.frida.get_local_device() + with self.assertRaises(self.frida.ProcessNotFoundError): + device.attach(999999) + + def test_list_return_marshals_to_wrapped_objects(self): + devices = self.frida.get_device_manager().enumerate_devices() + self.assertIsInstance(devices, list) + self.assertTrue(all(isinstance(d, self.frida.Device) for d in devices)) + self.assertIn("local", [d.id for d in devices]) + + def test_vardict_return_marshals_to_dict(self): + params = self.frida.get_local_device().query_system_parameters() + self.assertIsInstance(params, dict) + self.assertIn("arch", params) + self.assertIsInstance(params["os"], dict) + + def test_signal_handler_is_invoked(self): + cancellable = self.frida.Cancellable() + fired = [] + cancellable.on("cancelled", lambda: fired.append(True)) + cancellable.cancel() + self.assertEqual(fired, [True]) + + def test_removed_signal_handler_is_not_invoked(self): + cancellable = self.frida.Cancellable() + hits = [] + + def handler(): + hits.append(True) + + cancellable.on("cancelled", handler) + cancellable.off("cancelled", handler) + cancellable.cancel() + self.assertEqual(hits, []) + + def test_custom_base_type_still_exposes_signals(self): + device = self.frida.get_local_device() + callback = lambda *args: None + device.on("spawn-added", callback) + device.off("spawn-added", callback) + + def test_unknown_signal_name_raises(self): + with self.assertRaises(ValueError): + self.frida.get_device_manager().on("nonexistent", lambda: None) + + def test_signal_handler_can_receive_the_emitter(self): + cancellable = self.frida.Cancellable() + received = [] + cancellable.on("cancelled", lambda sender: received.append(sender)) + cancellable.cancel() + self.assertEqual(len(received), 1) + self.assertIsInstance(received[0], self.frida.Cancellable) + + def test_signal_handler_with_too_many_arguments_raises(self): + cancellable = self.frida.Cancellable() + with self.assertRaises(TypeError): + cancellable.on("cancelled", lambda a, b, c: None) + + def test_cancellable_is_cancelled_is_a_property(self): + cancellable = self.frida.Cancellable() + self.assertFalse(cancellable.is_cancelled) + cancellable.cancel() + self.assertTrue(cancellable.is_cancelled) + + def test_cancellable_pollfd_round_trips(self): + cancellable = self.frida.Cancellable() + pollfd = cancellable.get_pollfd() + with pollfd as fd: + self.assertIsInstance(fd, int) + pollfd.release() + pollfd.release() + + def test_rpc_round_trip(self): + session = self.frida.get_local_device().attach(0) + try: + script = session.create_script("rpc.exports = { add: (a, b) => a + b, echo: (x) => x };") + script.load() + self.assertEqual(sorted(dir(script.exports_sync)), ["add", "echo"]) + self.assertEqual(script.exports_sync.add(3, 4), 7) + self.assertEqual(script.exports_sync.echo("hello"), "hello") + finally: + session.detach() + + def test_script_rpc_error_propagates(self): + session = self.frida.get_local_device().attach(0) + try: + script = session.create_script("rpc.exports = { boom: () => { throw new Error('kaboom'); } };") + script.load() + with self.assertRaises(self.frida.RPCException): + script.exports_sync.boom() + finally: + session.detach() + + def test_script_log_handler_receives_console_log(self): + session = self.frida.get_local_device().attach(0) + try: + script = session.create_script("console.log('hello from script');") + logs = [] + script.set_log_handler(lambda level, text: logs.append((level, text))) + script.load() + self.assertIn(("info", "hello from script"), logs) + finally: + session.detach() + + def test_script_list_exports(self): + session = self.frida.get_local_device().attach(0) + try: + script = session.create_script("rpc.exports = { a: () => 1, b: () => 2 };") + script.load() + self.assertEqual(sorted(script.list_exports_sync()), ["a", "b"]) + finally: + session.detach() + + def test_script_message_round_trip(self): + session = self.frida.get_local_device().attach(0) + try: + script = session.create_script("send({ hi: 1 });") + received = [] + script.on("message", lambda message, data: received.append(message)) + script.load() + self.assertEqual(received[0]["type"], "send") + self.assertEqual(received[0]["payload"], {"hi": 1}) + finally: + session.detach() + + def test_script_rpc_raises_after_destroy(self): + session = self.frida.get_local_device().attach(0) + try: + script = session.create_script("rpc.exports = { ping: () => 1 };") + script.load() + script.unload() + with self.assertRaises(self.frida.InvalidOperationError): + script.exports_sync.ping() + finally: + session.detach() + + def test_rpc_exception_str_surfaces_message(self): + self.assertEqual(str(self.frida.RPCException("type", "name", "boom")), "boom") + self.assertEqual(str(self.frida.RPCException("boom")), "boom") + + def test_sync_cancellable_context_cancels_blocking_call(self): + import threading + + device = self.frida.get_local_device() + cancellable = self.frida.Cancellable() + threading.Timer(0.2, cancellable.cancel).start() + with self.assertRaises(self.frida.OperationCancelledError): + with cancellable: + device.get_process_by_name("frida-nonexistent-zzz", timeout=5000) + + def test_aio_cancellable_context_cancels_awaitable(self): + import asyncio + + import frida.aio + + async def scenario(): + device = await frida.aio.get_local_device() + cancellable = frida.aio.Cancellable() + + async def cancel_soon(): + await asyncio.sleep(0.2) + cancellable.cancel() + + asyncio.ensure_future(cancel_soon()) + with self.assertRaises(self.frida.OperationCancelledError): + with cancellable: + await device.get_process_by_name("frida-nonexistent-zzz", timeout=5000) + + asyncio.run(scenario()) + + def test_attach_accepts_options_as_kwargs(self): + session = self.frida.get_local_device().attach(0, realm="native", persist_timeout=0) + session.detach() + + def test_invalid_option_is_reported(self): + with self.assertRaises(AttributeError): + self.frida.get_local_device().attach(0, no_such_option=123) + + def test_compile_script_returns_bytes(self): + session = self.frida.get_local_device().attach(0) + try: + data = session.compile_script("const x = 42;") + self.assertIsInstance(data, bytes) + self.assertGreater(len(data), 0) + finally: + session.detach() + + def test_aio_rpc_round_trip(self): + import asyncio + + import frida.aio + + async def scenario(): + device = await frida.aio.get_local_device() + session = await device.attach(0) + try: + script = await session.create_script("rpc.exports = { add: (a, b) => a + b };") + await script.load() + self.assertEqual(await script.exports.add(3, 4), 7) + finally: + await session.detach() + + asyncio.run(scenario()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_core.py b/tests/test_core.py deleted file mode 100644 index 3072713..0000000 --- a/tests/test_core.py +++ /dev/null @@ -1,49 +0,0 @@ -import threading -import time -import unittest - -import frida - - -class TestCore(unittest.TestCase): - def test_enumerate_devices(self): - devices = frida.get_device_manager().enumerate_devices() - self.assertTrue(len(devices) > 0) - - def test_get_existing_device(self): - device = frida.get_device_matching(lambda d: d.id == "local") - self.assertEqual(device.name, "Local System") - - device = frida.get_device_manager().get_device_matching(lambda d: d.id == "local") - self.assertEqual(device.name, "Local System") - - def test_get_nonexistent_device(self): - def get_nonexistent(): - frida.get_device_manager().get_device_matching(lambda device: device.type == "lol") - - self.assertRaisesRegex(frida.InvalidArgumentError, "device not found", get_nonexistent) - - def test_wait_for_nonexistent_device(self): - def wait_for_nonexistent(): - frida.get_device_manager().get_device_matching(lambda device: device.type == "lol", timeout=0.1) - - self.assertRaisesRegex(frida.InvalidArgumentError, "device not found", wait_for_nonexistent) - - def test_cancel_wait_for_nonexistent_device(self): - cancellable = frida.Cancellable() - - def wait_for_nonexistent(): - frida.get_device_manager().get_device_matching( - lambda device: device.type == "lol", timeout=-1, cancellable=cancellable - ) - - def cancel_after_100ms(): - time.sleep(0.1) - cancellable.cancel() - - threading.Thread(target=cancel_after_100ms).start() - self.assertRaisesRegex(frida.OperationCancelledError, "operation was cancelled", wait_for_nonexistent) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_pep503_page_parser.py b/tests/test_pep503_page_parser.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_rpc.py b/tests/test_rpc.py deleted file mode 100644 index b5b1b7b..0000000 --- a/tests/test_rpc.py +++ /dev/null @@ -1,157 +0,0 @@ -import subprocess -import threading -import time -import unittest - -import frida - -from .data import target_program - - -class TestRpc(unittest.TestCase): - target: subprocess.Popen - session: frida.core.Session - - @classmethod - def setUp(cls): - cls.target = subprocess.Popen([target_program], stdin=subprocess.PIPE) - # TODO: improve injectors to handle injection into a process that hasn't yet finished initializing - time.sleep(0.05) - cls.session = frida.attach(cls.target.pid) - - @classmethod - def tearDown(cls): - cls.session.detach() - cls.target.terminate() - cls.target.stdin.close() - cls.target.wait() - - def test_basics(self): - script = self.session.create_script( - name="test-rpc", - source="""\ -rpc.exports = { - add: function (a, b) { - var result = a + b; - if (result < 0) - throw new Error("No"); - return result; - }, - sub: function (a, b) { - return a - b; - }, - speak: function () { - var buf = Memory.allocUtf8String("Yo"); - return Memory.readByteArray(buf, 2); - } -}; -""", - ) - script.load() - self.assertEqual(script.exports.add(2, 3), 5) - self.assertEqual(script.exports.sub(5, 3), 2) - self.assertRaises(Exception, lambda: script.exports.add(1, -2)) - self.assertListEqual([x for x in iter(script.exports.speak())], [0x59, 0x6F]) - - def test_post_failure(self): - script = self.session.create_script( - name="test-rpc", - source="""\ -rpc.exports = { - init: function () { - }, -}; -""", - ) - script.load() - agent = script.exports - - self.session.detach() - self.assertRaisesScriptDestroyed(lambda: agent.init()) - self.assertEqual(script._pending, {}) - - def test_unload_mid_request(self): - script = self.session.create_script( - name="test-rpc", - source="""\ -rpc.exports = { - waitForever: function () { - return new Promise(function () {}); - }, -}; -""", - ) - script.load() - agent = script.exports - - def unload_script_after_100ms(): - time.sleep(0.1) - script.unload() - - threading.Thread(target=unload_script_after_100ms).start() - self.assertRaisesScriptDestroyed(lambda: agent.wait_forever()) - self.assertEqual(script._pending, {}) - - def test_detach_mid_request(self): - script = self.session.create_script( - name="test-rpc", - source="""\ -rpc.exports = { - waitForever: function () { - return new Promise(function () {}); - }, -}; -""", - ) - script.load() - agent = script.exports - - def terminate_target_after_100ms(): - time.sleep(0.1) - self.target.terminate() - - threading.Thread(target=terminate_target_after_100ms).start() - self.assertRaisesScriptDestroyed(lambda: agent.wait_forever()) - self.assertEqual(script._pending, {}) - - def test_cancellation_mid_request(self): - script = self.session.create_script( - name="test-rpc", - source="""\ -rpc.exports = { - waitForever: function () { - return new Promise(function () {}); - }, -}; -""", - ) - script.load() - agent = script.exports - - def cancel_after_100ms(): - time.sleep(0.1) - cancellable.cancel() - - cancellable = frida.Cancellable() - threading.Thread(target=cancel_after_100ms).start() - self.assertRaisesOperationCancelled(lambda: agent.wait_forever(cancellable=cancellable)) - self.assertEqual(script._pending, {}) - - def call_wait_forever_with_cancellable(): - with cancellable: - agent.wait_forever() - - cancellable = frida.Cancellable() - threading.Thread(target=cancel_after_100ms).start() - self.assertRaisesOperationCancelled(call_wait_forever_with_cancellable) - self.assertEqual(script._pending, {}) - - def assertRaisesScriptDestroyed(self, operation): - self.assertRaisesRegex(frida.InvalidOperationError, "script has been destroyed", operation) - - def assertRaisesOperationCancelled(self, operation): - self.assertRaisesRegex(frida.OperationCancelledError, "operation was cancelled", operation) - - -if __name__ == "__main__": - unittest.main()