From fd37fea2d21f9050d22ad757157e9303847adeb9 Mon Sep 17 00:00:00 2001 From: Abhi <85984486+AbhiTheModder@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:44:37 +0530 Subject: [PATCH 01/14] build: generate bindings from the Frida devkit --- .github/workflows/code-style.yml | 5 +- .github/workflows/linting.yml | 5 +- .github/workflows/tests.yml | 19 + .gitignore | 2 + MANIFEST.in | 4 + README.md | 6 +- frida/__init__.py | 189 - frida/_frida/__init__.pyi | 905 --- frida/_frida/extension.c | 6298 ----------------- frida/_frida/meson.build | 21 - frida/core.py | 1861 ----- frida/{_frida => }/extension.version | 0 .../py.typed => frida_bindgen/__init__.py} | 0 frida/frida_bindgen/__main__.py | 4 + .../assets/codegen_endpoint_parameters.c | 81 + .../assets/codegen_gobject_globals.c | 5 + .../assets/codegen_gobject_methods.c | 1164 +++ .../assets/codegen_gobject_prototypes.h | 45 + frida/frida_bindgen/assets/codegen_macros.h | 81 + frida/frida_bindgen/assets/codegen_structs.h | 29 + frida/frida_bindgen/assets/codegen_typedefs.h | 6 + frida/frida_bindgen/assets/facade_bus.py | 23 + frida/frida_bindgen/assets/facade_bus_aio.py | 27 + .../assets/facade_cancellable.py | 22 + .../assets/facade_cancellable_aio.py | 18 + .../assets/facade_cancellable_helpers.py | 23 + .../assets/facade_cancellable_helpers_aio.py | 23 + .../assets/facade_device_manager_members.py | 9 + .../facade_device_manager_members_aio.py | 9 + .../assets/facade_device_members.py | 25 + .../assets/facade_device_members_aio.py | 25 + .../frida_bindgen/assets/facade_interface.py | 12 + .../assets/facade_interface_aio.py | 17 + frida/frida_bindgen/assets/facade_iostream.py | 25 + .../assets/facade_iostream_aio.py | 25 + frida/frida_bindgen/assets/facade_portal.py | 37 + .../frida_bindgen/assets/facade_portal_aio.py | 38 + .../assets/facade_rpc_helpers.py | 38 + .../assets/facade_rpc_helpers_aio.py | 35 + .../assets/facade_rpc_members.py | 111 + .../assets/facade_rpc_members_aio.py | 98 + frida/frida_bindgen/assets/facade_toplevel.py | 63 + .../assets/facade_toplevel_aio.py | 64 + .../assets/module_get_device_manager.c | 13 + frida/frida_bindgen/cli.py | 112 + frida/frida_bindgen/codegen.py | 2472 +++++++ frida/frida_bindgen/customization.py | 401 ++ frida/frida_bindgen/loader.py | 61 + frida/frida_bindgen/model.py | 349 + frida/frida_bindgen_core/__init__.py | 8 + frida/frida_bindgen_core/loader.py | 57 + frida/frida_bindgen_core/model.py | 882 +++ frida/frida_bindgen_core/naming.py | 40 + frida/meson.build | 113 +- meson.build | 84 +- pyproject.toml | 14 + setup.py | 271 +- tests/__init__.py | 4 - tests/compile_bindgen.sh | 38 + tests/run_bindgen.sh | 20 + tests/test_bindgen.py | 934 +++ tests/test_core.py | 49 - tests/test_pep503_page_parser.py | 0 tests/test_rpc.py | 157 - 64 files changed, 7911 insertions(+), 9665 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 MANIFEST.in delete mode 100644 frida/__init__.py delete mode 100644 frida/_frida/__init__.pyi delete mode 100644 frida/_frida/extension.c delete mode 100644 frida/_frida/meson.build delete mode 100644 frida/core.py rename frida/{_frida => }/extension.version (100%) rename frida/{_frida/py.typed => frida_bindgen/__init__.py} (100%) create mode 100644 frida/frida_bindgen/__main__.py create mode 100644 frida/frida_bindgen/assets/codegen_endpoint_parameters.c create mode 100644 frida/frida_bindgen/assets/codegen_gobject_globals.c create mode 100644 frida/frida_bindgen/assets/codegen_gobject_methods.c create mode 100644 frida/frida_bindgen/assets/codegen_gobject_prototypes.h create mode 100644 frida/frida_bindgen/assets/codegen_macros.h create mode 100644 frida/frida_bindgen/assets/codegen_structs.h create mode 100644 frida/frida_bindgen/assets/codegen_typedefs.h create mode 100644 frida/frida_bindgen/assets/facade_bus.py create mode 100644 frida/frida_bindgen/assets/facade_bus_aio.py create mode 100644 frida/frida_bindgen/assets/facade_cancellable.py create mode 100644 frida/frida_bindgen/assets/facade_cancellable_aio.py create mode 100644 frida/frida_bindgen/assets/facade_cancellable_helpers.py create mode 100644 frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py create mode 100644 frida/frida_bindgen/assets/facade_device_manager_members.py create mode 100644 frida/frida_bindgen/assets/facade_device_manager_members_aio.py create mode 100644 frida/frida_bindgen/assets/facade_device_members.py create mode 100644 frida/frida_bindgen/assets/facade_device_members_aio.py create mode 100644 frida/frida_bindgen/assets/facade_interface.py create mode 100644 frida/frida_bindgen/assets/facade_interface_aio.py create mode 100644 frida/frida_bindgen/assets/facade_iostream.py create mode 100644 frida/frida_bindgen/assets/facade_iostream_aio.py create mode 100644 frida/frida_bindgen/assets/facade_portal.py create mode 100644 frida/frida_bindgen/assets/facade_portal_aio.py create mode 100644 frida/frida_bindgen/assets/facade_rpc_helpers.py create mode 100644 frida/frida_bindgen/assets/facade_rpc_helpers_aio.py create mode 100644 frida/frida_bindgen/assets/facade_rpc_members.py create mode 100644 frida/frida_bindgen/assets/facade_rpc_members_aio.py create mode 100644 frida/frida_bindgen/assets/facade_toplevel.py create mode 100644 frida/frida_bindgen/assets/facade_toplevel_aio.py create mode 100644 frida/frida_bindgen/assets/module_get_device_manager.c create mode 100644 frida/frida_bindgen/cli.py create mode 100644 frida/frida_bindgen/codegen.py create mode 100644 frida/frida_bindgen/customization.py create mode 100644 frida/frida_bindgen/loader.py create mode 100644 frida/frida_bindgen/model.py create mode 100644 frida/frida_bindgen_core/__init__.py create mode 100644 frida/frida_bindgen_core/loader.py create mode 100644 frida/frida_bindgen_core/model.py create mode 100644 frida/frida_bindgen_core/naming.py create mode 100755 tests/compile_bindgen.sh create mode 100755 tests/run_bindgen.sh create mode 100644 tests/test_bindgen.py delete mode 100644 tests/test_core.py delete mode 100644 tests/test_pep503_page_parser.py delete mode 100644 tests/test_rpc.py diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 3598378..665c0c1 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -11,6 +11,5 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 - with: - python-version: 3.8 - - uses: jamescurtin/isort-action@master + - run: pip install isort + - run: isort --check-only --diff . diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 3022fd0..a115591 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -5,11 +5,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: lgeiger/pyflakes-action@master + - 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: 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..46aa349 --- /dev/null +++ b/frida/frida_bindgen/customization.py @@ -0,0 +1,401 @@ +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() From bbd90f625581a28c9ad34e647e3b9f3272420d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Thu, 16 Jul 2026 17:54:42 +0200 Subject: [PATCH 02/14] Sort customization.py imports From b1ec5962b826ddbbd189959935d70418ae76391b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Thu, 16 Jul 2026 19:44:06 +0200 Subject: [PATCH 03/14] setup: Find generated files next to the extension The prebuilt wheel path installs the generated __init__.py, aio.py and _frida.pyi alongside the extension, not under build/frida/, so copy them from there. # Conflicts: # setup.py From c59fb740aa4552809127d09f8322870e862f84ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Mon, 20 Jul 2026 17:45:06 +0200 Subject: [PATCH 04/14] bindgen: Fix options kwargs marshalling Skip None values instead of passing them to typed setters. Map collection kwargs onto their select_/add_ methods, walking the parent chain. Affects specs, omits, externals, pids and identifiers. --- frida/frida_bindgen/customization.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frida/frida_bindgen/customization.py b/frida/frida_bindgen/customization.py index 46aa349..7bf1afe 100644 --- a/frida/frida_bindgen/customization.py +++ b/frida/frida_bindgen/customization.py @@ -133,8 +133,7 @@ def load_customizations() -> Customizations: 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, {})", + "pid = await self._pid_of(target)\n" "options = _make_options(_frida.SessionOptions, kwargs, {})", ), ), }, From 8e4e99e9fd9d65c6448014df933b668aff5456ce Mon Sep 17 00:00:00 2001 From: Abhi <85984486+AbhiTheModder@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:52:22 +0530 Subject: [PATCH 05/14] Update GitHub Actions to latest versions --- .github/workflows/code-style.yml | 6 +++--- .github/workflows/linting.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 665c0c1..ea5d884 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -4,12 +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 + - 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 a115591..9a747f5 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -4,13 +4,13 @@ jobs: pyflakes: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - 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 --exclude frida/frida_bindgen/assets' From 113e684f4b01d26d595478e115b63c98013b3657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Mon, 20 Jul 2026 17:53:39 +0200 Subject: [PATCH 06/14] bindgen: Fix formatting --- frida/frida_bindgen/customization.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frida/frida_bindgen/customization.py b/frida/frida_bindgen/customization.py index 7bf1afe..46aa349 100644 --- a/frida/frida_bindgen/customization.py +++ b/frida/frida_bindgen/customization.py @@ -133,7 +133,8 @@ def load_customizations() -> Customizations: 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, {})", + "pid = await self._pid_of(target)\n" + "options = _make_options(_frida.SessionOptions, kwargs, {})", ), ), }, From ff18f9fab2d88da1af29cd9805347e481b7c17dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Wed, 22 Jul 2026 00:23:55 +0200 Subject: [PATCH 07/14] bindgen: Restore facade typing surface The .gir rewrite emitted only GObject-derived classes and no annotations, so py.typed shipped an untyped facade. Reinstate the aliases as a prelude asset and annotate the facade from the existing pyi type mapping. Also fixes read-only options properties and the module functions missing from _frida.pyi. --- frida/frida_bindgen/assets/facade_bus.py | 10 +- frida/frida_bindgen/assets/facade_bus_aio.py | 12 +- .../assets/facade_cancellable.py | 10 +- .../assets/facade_cancellable_aio.py | 10 +- .../assets/facade_cancellable_helpers.py | 14 +-- .../assets/facade_cancellable_helpers_aio.py | 14 +-- .../assets/facade_device_manager_members.py | 2 +- .../facade_device_manager_members_aio.py | 2 +- .../assets/facade_device_members.py | 8 +- .../assets/facade_device_members_aio.py | 8 +- .../frida_bindgen/assets/facade_interface.py | 4 +- .../assets/facade_interface_aio.py | 6 +- frida/frida_bindgen/assets/facade_iostream.py | 10 +- .../assets/facade_iostream_aio.py | 10 +- frida/frida_bindgen/assets/facade_portal.py | 14 +-- .../frida_bindgen/assets/facade_portal_aio.py | 16 +-- .../assets/facade_rpc_helpers.py | 16 ++- .../assets/facade_rpc_helpers_aio.py | 13 ++- .../assets/facade_rpc_members.py | 38 +++---- .../assets/facade_rpc_members_aio.py | 34 +++--- frida/frida_bindgen/assets/facade_toplevel.py | 38 ++++--- .../assets/facade_toplevel_aio.py | 38 ++++--- frida/frida_bindgen/assets/facade_types.py | 106 ++++++++++++++++++ frida/frida_bindgen/codegen.py | 66 +++++++++-- frida/frida_bindgen/customization.py | 93 ++++++++------- frida/frida_bindgen/model.py | 1 + 26 files changed, 392 insertions(+), 201 deletions(-) create mode 100644 frida/frida_bindgen/assets/facade_types.py diff --git a/frida/frida_bindgen/assets/facade_bus.py b/frida/frida_bindgen/assets/facade_bus.py index 85d04c3..2ed4105 100644 --- a/frida/frida_bindgen/assets/facade_bus.py +++ b/frida/frida_bindgen/assets/facade_bus.py @@ -1,20 +1,20 @@ -def _setup(self): - self._message_handlers = [] +def _setup(self) -> None: + self._message_handlers: List[BusMessageCallback] = [] self._impl.on("message", self._on_message) -def on(self, signal, callback): +def on(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "message": self._message_handlers.append(callback) else: self._impl.on(signal, _make_signal_handler(callback)) -def off(self, signal, callback): +def off(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "message": self._message_handlers.remove(callback) else: self._impl.off(signal, callback) -def _on_message(self, raw_message, data): +def _on_message(self, raw_message: str, data: Optional[bytes]) -> None: message = json.loads(raw_message) for handler in list(self._message_handlers): try: diff --git a/frida/frida_bindgen/assets/facade_bus_aio.py b/frida/frida_bindgen/assets/facade_bus_aio.py index 8215358..85f58a9 100644 --- a/frida/frida_bindgen/assets/facade_bus_aio.py +++ b/frida/frida_bindgen/assets/facade_bus_aio.py @@ -1,26 +1,26 @@ -def _setup(self): - self._message_handlers = [] +def _setup(self) -> None: + self._message_handlers: List[BusMessageCallback] = [] self._loop = asyncio.get_event_loop() self._impl.on("message", self._on_message) -def on(self, signal, callback): +def on(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "message": self._message_handlers.append(callback) else: self._impl.on(signal, _make_signal_handler(callback)) -def off(self, signal, callback): +def off(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "message": self._message_handlers.remove(callback) else: self._impl.off(signal, callback) -def _on_message(self, raw_message, data): +def _on_message(self, raw_message: str, data: Optional[bytes]) -> None: 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): +def _deliver_message(self, handler: BusMessageCallback, message: Mapping[Any, Any], data: Optional[bytes]) -> None: try: handler(message, data) except Exception: diff --git a/frida/frida_bindgen/assets/facade_cancellable.py b/frida/frida_bindgen/assets/facade_cancellable.py index f7f3c94..9b3f2d2 100644 --- a/frida/frida_bindgen/assets/facade_cancellable.py +++ b/frida/frida_bindgen/assets/facade_cancellable.py @@ -1,4 +1,4 @@ -def __enter__(self): +def __enter__(self) -> "Cancellable": stack = getattr(_current_cancellable, "stack", None) if stack is None: stack = [] @@ -6,17 +6,17 @@ def __enter__(self): stack.append(self) return self -def __exit__(self, *exc): +def __exit__(self, *exc: Any) -> Literal[False]: _current_cancellable.stack.pop() return False @classmethod -def get_current(cls): +def get_current(cls) -> "Cancellable": return _current_cancellable_get() -def raise_if_cancelled(self): +def raise_if_cancelled(self) -> None: if self._impl.is_cancelled(): raise _frida.OperationCancelledError("operation was cancelled") -def get_pollfd(self): +def get_pollfd(self) -> "CancellablePollFD": return CancellablePollFD(self._impl) diff --git a/frida/frida_bindgen/assets/facade_cancellable_aio.py b/frida/frida_bindgen/assets/facade_cancellable_aio.py index 9287418..97b68d6 100644 --- a/frida/frida_bindgen/assets/facade_cancellable_aio.py +++ b/frida/frida_bindgen/assets/facade_cancellable_aio.py @@ -1,18 +1,18 @@ -def __enter__(self): +def __enter__(self) -> "Cancellable": self._token = _current_cancellable.set(self) return self -def __exit__(self, *exc): +def __exit__(self, *exc: Any) -> Literal[False]: _current_cancellable.reset(self._token) return False @classmethod -def get_current(cls): +def get_current(cls) -> "Cancellable": return _current_cancellable.get() -def raise_if_cancelled(self): +def raise_if_cancelled(self) -> None: if self._impl.is_cancelled(): raise _frida.OperationCancelledError("operation was cancelled") -def get_pollfd(self): +def get_pollfd(self) -> "CancellablePollFD": return CancellablePollFD(self._impl) diff --git a/frida/frida_bindgen/assets/facade_cancellable_helpers.py b/frida/frida_bindgen/assets/facade_cancellable_helpers.py index 54c0a2a..f53df5f 100644 --- a/frida/frida_bindgen/assets/facade_cancellable_helpers.py +++ b/frida/frida_bindgen/assets/facade_cancellable_helpers.py @@ -1,23 +1,23 @@ class CancellablePollFD: - def __init__(self, cancellable): + def __init__(self, cancellable: _frida.Cancellable) -> None: self.handle = cancellable.get_fd() - self._cancellable = cancellable + self._cancellable: Optional[_frida.Cancellable] = cancellable - def __del__(self): + def __del__(self) -> None: self.release() - def release(self): + 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): + def __repr__(self) -> str: return repr(self.handle) - def __enter__(self): + def __enter__(self) -> int: return self.handle - def __exit__(self, *exc): + def __exit__(self, *exc: Any) -> None: self.release() diff --git a/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py b/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py index 54c0a2a..f53df5f 100644 --- a/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py +++ b/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py @@ -1,23 +1,23 @@ class CancellablePollFD: - def __init__(self, cancellable): + def __init__(self, cancellable: _frida.Cancellable) -> None: self.handle = cancellable.get_fd() - self._cancellable = cancellable + self._cancellable: Optional[_frida.Cancellable] = cancellable - def __del__(self): + def __del__(self) -> None: self.release() - def release(self): + 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): + def __repr__(self) -> str: return repr(self.handle) - def __enter__(self): + def __enter__(self) -> int: return self.handle - def __exit__(self, *exc): + def __exit__(self, *exc: Any) -> None: self.release() diff --git a/frida/frida_bindgen/assets/facade_device_manager_members.py b/frida/frida_bindgen/assets/facade_device_manager_members.py index 38f8ae8..54d182a 100644 --- a/frida/frida_bindgen/assets/facade_device_manager_members.py +++ b/frida/frida_bindgen/assets/facade_device_manager_members.py @@ -1,4 +1,4 @@ -def get_device_matching(self, predicate, timeout=0): +def get_device_matching(self, predicate: Callable[["Device"], bool], timeout: Union[int, float] = 0) -> "Device": deadline = time.monotonic() + timeout while True: for device in self.enumerate_devices(): diff --git a/frida/frida_bindgen/assets/facade_device_manager_members_aio.py b/frida/frida_bindgen/assets/facade_device_manager_members_aio.py index 4060aaf..5563a88 100644 --- a/frida/frida_bindgen/assets/facade_device_manager_members_aio.py +++ b/frida/frida_bindgen/assets/facade_device_manager_members_aio.py @@ -1,4 +1,4 @@ -async def get_device_matching(self, predicate, timeout=0): +async def get_device_matching(self, predicate: Callable[["Device"], bool], timeout: Union[int, float] = 0) -> "Device": deadline = time.monotonic() + timeout while True: for device in await self.enumerate_devices(): diff --git a/frida/frida_bindgen/assets/facade_device_members.py b/frida/frida_bindgen/assets/facade_device_members.py index fc302c0..ec7c88a 100644 --- a/frida/frida_bindgen/assets/facade_device_members.py +++ b/frida/frida_bindgen/assets/facade_device_members.py @@ -1,11 +1,11 @@ @property -def type(self): +def type(self) -> str: return self.dtype -def get_bus(self): +def get_bus(self) -> "Bus": return self.bus -def get_process(self, process_name): +def get_process(self, process_name: str) -> "Process": process_name_lc = process_name.lower() matching = [ process @@ -19,7 +19,7 @@ def get_process(self, process_name): 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): +def _pid_of(self, target: ProcessTarget) -> int: 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 index 9f4fc01..eb955b5 100644 --- a/frida/frida_bindgen/assets/facade_device_members_aio.py +++ b/frida/frida_bindgen/assets/facade_device_members_aio.py @@ -1,11 +1,11 @@ @property -def type(self): +def type(self) -> str: return self.dtype -def get_bus(self): +def get_bus(self) -> "Bus": return self.bus -async def get_process(self, process_name): +async def get_process(self, process_name: str) -> "Process": process_name_lc = process_name.lower() matching = [ process @@ -19,7 +19,7 @@ async def get_process(self, process_name): 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): +async def _pid_of(self, target: ProcessTarget) -> int: 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 index 8520e69..bbeeaa2 100644 --- a/frida/frida_bindgen/assets/facade_interface.py +++ b/frida/frida_bindgen/assets/facade_interface.py @@ -1,6 +1,6 @@ class _Implementation: - def _frida_dispatch(self, name, args, completion): - def run(): + def _frida_dispatch(self, name: str, args: List[Any], completion: Any) -> None: + def run() -> None: try: result = _unwrap(getattr(self, name)(*[_wrap(a) for a in args])) error = None diff --git a/frida/frida_bindgen/assets/facade_interface_aio.py b/frida/frida_bindgen/assets/facade_interface_aio.py index 331edad..59d98b0 100644 --- a/frida/frida_bindgen/assets/facade_interface_aio.py +++ b/frida/frida_bindgen/assets/facade_interface_aio.py @@ -1,8 +1,10 @@ class _Implementation: - def _frida_dispatch(self, name, args, completion): + _loop: Any + + def _frida_dispatch(self, name: str, args: List[Any], completion: Any) -> None: loop = self._loop - async def run(): + async def run() -> None: try: result = getattr(self, name)(*[_wrap(a) for a in args]) if asyncio.iscoroutine(result): diff --git a/frida/frida_bindgen/assets/facade_iostream.py b/frida/frida_bindgen/assets/facade_iostream.py index 517c163..0773709 100644 --- a/frida/frida_bindgen/assets/facade_iostream.py +++ b/frida/frida_bindgen/assets/facade_iostream.py @@ -1,10 +1,10 @@ -def close(self): +def close(self) -> None: _invoke(self._impl.close_async, (0,)) -def read(self, count): +def read(self, count: int) -> bytes: return _invoke(self._impl.input_stream.read_bytes_async, (count, 0)) -def read_all(self, count): +def read_all(self, count: int) -> bytes: chunks = [] remaining = count while remaining > 0: @@ -15,10 +15,10 @@ def read_all(self, count): remaining -= len(chunk) return b"".join(chunks) -def write(self, data): +def write(self, data: bytes) -> int: return _invoke(self._impl.output_stream.write_bytes_async, (data, 0)) -def write_all(self, data): +def write_all(self, data: bytes) -> None: view = memoryview(data) while len(view) != 0: written = _invoke(self._impl.output_stream.write_bytes_async, (bytes(view), 0)) diff --git a/frida/frida_bindgen/assets/facade_iostream_aio.py b/frida/frida_bindgen/assets/facade_iostream_aio.py index e18a85a..d954c0e 100644 --- a/frida/frida_bindgen/assets/facade_iostream_aio.py +++ b/frida/frida_bindgen/assets/facade_iostream_aio.py @@ -1,10 +1,10 @@ -async def close(self): +async def close(self) -> None: await _invoke(self._impl.close_async, (0,)) -async def read(self, count): +async def read(self, count: int) -> bytes: return await _invoke(self._impl.input_stream.read_bytes_async, (count, 0)) -async def read_all(self, count): +async def read_all(self, count: int) -> bytes: chunks = [] remaining = count while remaining > 0: @@ -15,10 +15,10 @@ async def read_all(self, count): remaining -= len(chunk) return b"".join(chunks) -async def write(self, data): +async def write(self, data: bytes) -> int: return await _invoke(self._impl.output_stream.write_bytes_async, (data, 0)) -async def write_all(self, data): +async def write_all(self, data: bytes) -> None: view = memoryview(data) while len(view) != 0: written = await _invoke(self._impl.output_stream.write_bytes_async, (bytes(view), 0)) diff --git a/frida/frida_bindgen/assets/facade_portal.py b/frida/frida_bindgen/assets/facade_portal.py index 8a49964..cd38560 100644 --- a/frida/frida_bindgen/assets/facade_portal.py +++ b/frida/frida_bindgen/assets/facade_portal.py @@ -1,10 +1,10 @@ -def _setup(self): - self._authenticated_handlers = [] - self._message_handlers = [] +def _setup(self) -> None: + self._authenticated_handlers: List[PortalServiceAuthenticatedCallback] = [] + self._message_handlers: List[PortalServiceMessageCallback] = [] self._impl.on("authenticated", self._on_authenticated) self._impl.on("message", self._on_message) -def on(self, signal, callback): +def on(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "authenticated": self._authenticated_handlers.append(callback) elif signal == "message": @@ -12,7 +12,7 @@ def on(self, signal, callback): else: self._impl.on(signal, _make_signal_handler(callback)) -def off(self, signal, callback): +def off(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "authenticated": self._authenticated_handlers.remove(callback) elif signal == "message": @@ -20,7 +20,7 @@ def off(self, signal, callback): else: self._impl.off(signal, callback) -def _on_authenticated(self, connection_id, raw_session_info): +def _on_authenticated(self, connection_id: int, raw_session_info: str) -> None: session_info = json.loads(raw_session_info) for handler in list(self._authenticated_handlers): try: @@ -28,7 +28,7 @@ def _on_authenticated(self, connection_id, raw_session_info): except Exception: traceback.print_exc() -def _on_message(self, connection_id, raw_message, data): +def _on_message(self, connection_id: int, raw_message: str, data: Optional[bytes]) -> None: message = json.loads(raw_message) for handler in list(self._message_handlers): try: diff --git a/frida/frida_bindgen/assets/facade_portal_aio.py b/frida/frida_bindgen/assets/facade_portal_aio.py index 1ee98b7..73072db 100644 --- a/frida/frida_bindgen/assets/facade_portal_aio.py +++ b/frida/frida_bindgen/assets/facade_portal_aio.py @@ -1,11 +1,11 @@ -def _setup(self): - self._authenticated_handlers = [] - self._message_handlers = [] +def _setup(self) -> None: + self._authenticated_handlers: List[PortalServiceAuthenticatedCallback] = [] + self._message_handlers: List[PortalServiceMessageCallback] = [] 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): +def on(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "authenticated": self._authenticated_handlers.append(callback) elif signal == "message": @@ -13,7 +13,7 @@ def on(self, signal, callback): else: self._impl.on(signal, _make_signal_handler(callback)) -def off(self, signal, callback): +def off(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "authenticated": self._authenticated_handlers.remove(callback) elif signal == "message": @@ -21,17 +21,17 @@ def off(self, signal, callback): else: self._impl.off(signal, callback) -def _on_authenticated(self, connection_id, raw_session_info): +def _on_authenticated(self, connection_id: int, raw_session_info: str) -> None: 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): +def _on_message(self, connection_id: int, raw_message: str, data: Optional[bytes]) -> None: 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): +def _deliver(self, handler: Callable[..., Any], *args: Any) -> None: try: handler(*args) except Exception: diff --git a/frida/frida_bindgen/assets/facade_rpc_helpers.py b/frida/frida_bindgen/assets/facade_rpc_helpers.py index 31fb5ff..641dc1f 100644 --- a/frida/frida_bindgen/assets/facade_rpc_helpers.py +++ b/frida/frida_bindgen/assets/facade_rpc_helpers.py @@ -1,4 +1,4 @@ -def _to_camel_case(name): +def _to_camel_case(name: str) -> str: result = "" capitalize = False for ch in name: @@ -13,19 +13,19 @@ def _to_camel_case(name): class RPCException(Exception): - def __str__(self): + def __str__(self) -> str: return str(self.args[2]) if len(self.args) >= 3 else str(self.args[0]) class _ScriptExports: - def __init__(self, script): + def __init__(self, script: "Script") -> None: self._script = script - def __getattr__(self, name): + def __getattr__(self, name: str) -> Callable[..., Any]: script = self._script js_name = _to_camel_case(name) - def method(*args): + def method(*args: Any) -> Any: if args and isinstance(args[-1], bytes): params, data = list(args[:-1]), args[-1] else: @@ -34,5 +34,9 @@ def method(*args): return method - def __dir__(self): + def __dir__(self) -> List[str]: return self._script._list_exports() + + +ScriptExportsSync = _ScriptExports +ScriptExports = ScriptExportsSync diff --git a/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py b/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py index aedeeee..1df1316 100644 --- a/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py +++ b/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py @@ -1,4 +1,4 @@ -def _to_camel_case(name): +def _to_camel_case(name: str) -> str: result = "" capitalize = False for ch in name: @@ -13,19 +13,19 @@ def _to_camel_case(name): class RPCException(Exception): - def __str__(self): + def __str__(self) -> str: return str(self.args[2]) if len(self.args) >= 3 else str(self.args[0]) class _ScriptExports: - def __init__(self, script): + def __init__(self, script: "Script") -> None: self._script = script - def __getattr__(self, name): + def __getattr__(self, name: str) -> Callable[..., Any]: script = self._script js_name = _to_camel_case(name) - def method(*args): + def method(*args: Any) -> Any: if args and isinstance(args[-1], bytes): params, data = list(args[:-1]), args[-1] else: @@ -33,3 +33,6 @@ def method(*args): return script._rpc_request(["call", js_name, params], data) return method + + +ScriptExportsAsync = _ScriptExports diff --git a/frida/frida_bindgen/assets/facade_rpc_members.py b/frida/frida_bindgen/assets/facade_rpc_members.py index d4c20d7..ea85838 100644 --- a/frida/frida_bindgen/assets/facade_rpc_members.py +++ b/frida/frida_bindgen/assets/facade_rpc_members.py @@ -1,55 +1,55 @@ -def _setup(self): +def _setup(self) -> None: self.exports_sync = _ScriptExports(self) - self._message_handlers = [] - self._log_handler = self.default_log_handler - self._pending = {} + self._message_handlers: List[ScriptMessageCallback] = [] + self._log_handler: Callable[[str, str], None] = self.default_log_handler + self._pending: Dict[int, Callable[[Any, Optional[Exception]], None]] = {} 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): +def exports(self) -> "_ScriptExports": return self.exports_sync -def on(self, signal, callback): +def on(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "message": self._message_handlers.append(callback) else: self._impl.on(signal, _make_signal_handler(callback)) -def off(self, signal, callback): +def off(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "message": self._message_handlers.remove(callback) else: self._impl.off(signal, callback) -def get_log_handler(self): +def get_log_handler(self) -> Callable[[str, str], None]: return self._log_handler -def set_log_handler(self, handler): +def set_log_handler(self, handler: Callable[[str, str], None]) -> None: self._log_handler = handler -def default_log_handler(self, level, text): +def default_log_handler(self, level: str, text: str) -> None: if level == "info": print(text, file=sys.stdout) else: print(text, file=sys.stderr) -def list_exports_sync(self): +def list_exports_sync(self) -> List[str]: return self._rpc_request(["list"]) -def list_exports(self): +def list_exports(self) -> List[str]: return self.list_exports_sync() -def _list_exports(self): +def _list_exports(self) -> List[str]: return self.list_exports_sync() -def _rpc_request(self, args, data=None): - outcome = {} +def _rpc_request(self, args: Any, data: Optional[bytes] = None) -> Any: + outcome: Dict[str, Any] = {} cond = self._cond - def complete(value, error): + def complete(value: Any, error: Optional[Exception]) -> None: with cond: outcome["value"] = value outcome["error"] = error @@ -73,7 +73,7 @@ def complete(value, error): raise outcome["error"] return outcome["value"] -def _on_message(self, raw_message, data): +def _on_message(self, raw_message: str, data: Optional[bytes]) -> None: message = json.loads(raw_message) mtype = message["type"] payload = message.get("payload") @@ -88,7 +88,7 @@ def _on_message(self, raw_message, data): except Exception: traceback.print_exc() -def _on_destroyed(self): +def _on_destroyed(self) -> None: while True: with self._cond: pending_ids = list(self._pending.keys()) @@ -97,7 +97,7 @@ def _on_destroyed(self): break complete(None, _frida.InvalidOperationError("script has been destroyed")) -def _on_rpc_message(self, request_id, operation, params, data): +def _on_rpc_message(self, request_id: int, operation: str, params: List[Any], data: Optional[Any]) -> None: if operation not in ("ok", "error"): return complete = self._pending.pop(request_id, None) diff --git a/frida/frida_bindgen/assets/facade_rpc_members_aio.py b/frida/frida_bindgen/assets/facade_rpc_members_aio.py index 672194c..9dc757b 100644 --- a/frida/frida_bindgen/assets/facade_rpc_members_aio.py +++ b/frida/frida_bindgen/assets/facade_rpc_members_aio.py @@ -1,45 +1,45 @@ -def _setup(self): +def _setup(self) -> None: self.exports = _ScriptExports(self) - self._message_handlers = [] - self._log_handler = self.default_log_handler - self._pending = {} + self._message_handlers: List[ScriptMessageCallback] = [] + self._log_handler: Callable[[str, str], None] = self.default_log_handler + self._pending: Dict[int, Callable[[Any, Optional[Exception]], None]] = {} 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): +def on(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "message": self._message_handlers.append(callback) else: self._impl.on(signal, _make_signal_handler(callback)) -def off(self, signal, callback): +def off(self, signal: str, callback: Callable[..., Any]) -> None: if signal == "message": self._message_handlers.remove(callback) else: self._impl.off(signal, callback) -def get_log_handler(self): +def get_log_handler(self) -> Callable[[str, str], None]: return self._log_handler -def set_log_handler(self, handler): +def set_log_handler(self, handler: Callable[[str, str], None]) -> None: self._log_handler = handler -def default_log_handler(self, level, text): +def default_log_handler(self, level: str, text: str) -> None: if level == "info": print(text, file=sys.stdout) else: print(text, file=sys.stderr) -async def list_exports(self): +async def list_exports(self) -> List[str]: return await self._rpc_request(["list"]) -def _rpc_request(self, args, data=None): +def _rpc_request(self, args: Any, data: Optional[bytes] = None) -> "asyncio.Future[Any]": loop = asyncio.get_running_loop() - future = loop.create_future() + future: "asyncio.Future[Any]" = loop.create_future() - def complete(value, error): + def complete(value: Any, error: Optional[Exception]) -> None: if future.done(): return if error is not None: @@ -58,7 +58,7 @@ def complete(value, error): return future -def _on_message(self, raw_message, data): +def _on_message(self, raw_message: str, data: Optional[bytes]) -> None: message = json.loads(raw_message) mtype = message["type"] payload = message.get("payload") @@ -70,13 +70,13 @@ def _on_message(self, raw_message, data): for handler in list(self._message_handlers): _dispatch(self._loop, self._deliver_message, handler, message, data) -def _deliver_message(self, handler, message, data): +def _deliver_message(self, handler: ScriptMessageCallback, message: ScriptMessage, data: Optional[bytes]) -> None: try: handler(message, data) except Exception: traceback.print_exc() -def _on_destroyed(self): +def _on_destroyed(self) -> None: while True: pending_ids = list(self._pending.keys()) complete = self._pending.pop(pending_ids[0]) if pending_ids else None @@ -84,7 +84,7 @@ def _on_destroyed(self): break complete(None, _frida.InvalidOperationError("script has been destroyed")) -def _on_rpc_message(self, request_id, operation, params, data): +def _on_rpc_message(self, request_id: int, operation: str, params: List[Any], data: Optional[Any]) -> None: if operation not in ("ok", "error"): return complete = self._pending.pop(request_id, None) diff --git a/frida/frida_bindgen/assets/facade_toplevel.py b/frida/frida_bindgen/assets/facade_toplevel.py index 90fa625..e77e39d 100644 --- a/frida/frida_bindgen/assets/facade_toplevel.py +++ b/frida/frida_bindgen/assets/facade_toplevel.py @@ -1,63 +1,71 @@ __version__ = _frida.__version__ -def get_device_manager(): +def get_device_manager() -> "DeviceManager": return _wrap(_frida.get_device_manager()) -def get_device(id, timeout=0): +def get_device(id: str, timeout: Union[int, float] = 0) -> "Device": return get_device_manager().get_device_by_id(id, int(timeout * 1000)) -def get_device_matching(predicate, timeout=0): +def get_device_matching(predicate: Callable[["Device"], bool], timeout: Union[int, float] = 0) -> "Device": return get_device_manager().get_device_matching(predicate, timeout) -def get_local_device(): +def get_local_device() -> "Device": return get_device_manager().get_device_by_type("local", 0) -def get_remote_device(): +def get_remote_device() -> "Device": return get_device_manager().get_device_by_type("remote", 0) -def get_usb_device(timeout=0): +def get_usb_device(timeout: Union[int, float] = 0) -> "Device": return get_device_manager().get_device_by_type("usb", int(timeout * 1000)) -def enumerate_devices(): +def enumerate_devices() -> List["Device"]: return get_device_manager().enumerate_devices() -def query_system_parameters(): +def query_system_parameters() -> Dict[str, Any]: return get_local_device().query_system_parameters() -def spawn(program, argv=None, envp=None, env=None, cwd=None, stdio=None, **aux): +def spawn( + program: Union[str, List[Union[str, bytes]]], + argv: Optional[List[Union[str, bytes]]] = None, + envp: Optional[Dict[str, str]] = None, + env: Optional[Dict[str, str]] = None, + cwd: Optional[str] = None, + stdio: Optional[str] = None, + **aux: Any, +) -> int: return get_local_device().spawn( program, argv=argv, envp=envp, env=env, cwd=cwd, stdio=stdio, **aux ) -def resume(target): +def resume(target: ProcessTarget) -> None: return get_local_device().resume(target) -def kill(target): +def kill(target: ProcessTarget) -> None: return get_local_device().kill(target) -def attach(target, **kwargs): +def attach(target: ProcessTarget, **kwargs: Any) -> "Session": return get_local_device().attach(target, **kwargs) -def inject_library_file(target, path, entrypoint, data): +def inject_library_file(target: ProcessTarget, path: str, entrypoint: str, data: str) -> int: return get_local_device().inject_library_file(target, path, entrypoint, data) -def inject_library_blob(target, blob, entrypoint, data): +def inject_library_blob(target: ProcessTarget, blob: bytes, entrypoint: str, data: str) -> int: return get_local_device().inject_library_blob(target, blob, entrypoint, data) -def shutdown(): +def shutdown() -> None: get_device_manager().close() diff --git a/frida/frida_bindgen/assets/facade_toplevel_aio.py b/frida/frida_bindgen/assets/facade_toplevel_aio.py index e601a4e..035550d 100644 --- a/frida/frida_bindgen/assets/facade_toplevel_aio.py +++ b/frida/frida_bindgen/assets/facade_toplevel_aio.py @@ -1,64 +1,72 @@ __version__ = _frida.__version__ -def get_device_manager(): +def get_device_manager() -> "DeviceManager": return _wrap(_frida.get_device_manager()) -async def get_device(id, timeout=0): +async def get_device(id: str, timeout: Union[int, float] = 0) -> "Device": return await get_device_manager().get_device_by_id(id, int(timeout * 1000)) -async def get_device_matching(predicate, timeout=0): +async def get_device_matching(predicate: Callable[["Device"], bool], timeout: Union[int, float] = 0) -> "Device": return await get_device_manager().get_device_matching(predicate, timeout) -async def get_local_device(): +async def get_local_device() -> "Device": return await get_device_manager().get_device_by_type("local", 0) -async def get_remote_device(): +async def get_remote_device() -> "Device": return await get_device_manager().get_device_by_type("remote", 0) -async def get_usb_device(timeout=0): +async def get_usb_device(timeout: Union[int, float] = 0) -> "Device": return await get_device_manager().get_device_by_type("usb", int(timeout * 1000)) -async def enumerate_devices(): +async def enumerate_devices() -> List["Device"]: return await get_device_manager().enumerate_devices() -async def query_system_parameters(): +async def query_system_parameters() -> Dict[str, Any]: return await (await get_local_device()).query_system_parameters() -async def spawn(program, argv=None, envp=None, env=None, cwd=None, stdio=None, **aux): +async def spawn( + program: Union[str, List[Union[str, bytes]]], + argv: Optional[List[Union[str, bytes]]] = None, + envp: Optional[Dict[str, str]] = None, + env: Optional[Dict[str, str]] = None, + cwd: Optional[str] = None, + stdio: Optional[str] = None, + **aux: Any, +) -> int: 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): +async def resume(target: ProcessTarget) -> None: return await (await get_local_device()).resume(target) -async def kill(target): +async def kill(target: ProcessTarget) -> None: return await (await get_local_device()).kill(target) -async def attach(target, **kwargs): +async def attach(target: ProcessTarget, **kwargs: Any) -> "Session": return await (await get_local_device()).attach(target, **kwargs) -async def inject_library_file(target, path, entrypoint, data): +async def inject_library_file(target: ProcessTarget, path: str, entrypoint: str, data: str) -> int: return await (await get_local_device()).inject_library_file(target, path, entrypoint, data) -async def inject_library_blob(target, blob, entrypoint, data): +async def inject_library_blob(target: ProcessTarget, blob: bytes, entrypoint: str, data: str) -> int: return await (await get_local_device()).inject_library_blob(target, blob, entrypoint, data) -async def shutdown(): +async def shutdown() -> None: await get_device_manager().close() diff --git a/frida/frida_bindgen/assets/facade_types.py b/frida/frida_bindgen/assets/facade_types.py new file mode 100644 index 0000000..ff2de75 --- /dev/null +++ b/frida/frida_bindgen/assets/facade_types.py @@ -0,0 +1,106 @@ +ProcessTarget = Union[int, str] + + +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] + +SessionDetachedCallback = Callable[ + [ + Literal[ + "application-requested", "process-replaced", "process-terminated", "connection-terminated", "device-lost" + ], + Optional[_frida.Crash], + ], + None, +] + +BusDetachedCallback = Callable[[], None] +BusMessageCallback = Callable[[Mapping[Any, Any], Optional[bytes]], None] + +ServiceCloseCallback = Callable[[], None] +ServiceMessageCallback = Callable[[Any], None] + +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] + +DeviceManagerAddedCallback = Callable[[_frida.Device], None] +DeviceManagerRemovedCallback = Callable[[_frida.Device], None] +DeviceManagerChangedCallback = Callable[[], None] + +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 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"] + +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"] diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py index ef0101a..35cbf3d 100644 --- a/frida/frida_bindgen/codegen.py +++ b/frida/frida_bindgen/codegen.py @@ -53,6 +53,18 @@ def error_members(model: Model) -> List[Tuple[str, str]]: return [(member.c_identifier, member.js_name) for member in model.error_domain.members] +FACADE_TYPING_IMPORTS = ( + "from typing import Any, Callable, Dict, List, Literal, Mapping, NotRequired, Optional, Tuple, TypedDict, Union" +) + + +def generate_facade_preludes(model: Model) -> List[str]: + lines = [] + for asset in model.customizations.facade_preludes: + lines += ["", read_asset(asset).strip(), ""] + return lines + + FACADE_RUNTIME = """ _to_json = json.dumps @@ -201,7 +213,7 @@ def _make_options(cls, values, selectors): return options -_current_cancellable = contextvars.ContextVar("frida_current_cancellable", default=None) +_current_cancellable: contextvars.ContextVar = contextvars.ContextVar("frida_current_cancellable", default=None) def _dispatch(loop, callback, *args): @@ -257,9 +269,11 @@ def generate_py(model: Model) -> str: "import sys", "import threading", "import traceback", + FACADE_TYPING_IMPORTS, "", "", *generate_py_exception_reexports(model), + *generate_facade_preludes(model), "", FACADE_RUNTIME.strip(), "", @@ -321,9 +335,11 @@ def generate_aio(model: Model) -> str: "import json", "import sys", "import traceback", + FACADE_TYPING_IMPORTS, "", "", *generate_py_exception_reexports(model), + *generate_facade_preludes(model), "", AIO_RUNTIME.strip(), "", @@ -395,7 +411,7 @@ def generate_aio_method(method: Method, model: Model) -> Optional[str]: def generate_facade_bool_property(method: Method) -> str: return f""" @property - def {method.name}(self): + def {method.name}(self) -> bool: return self._impl.{method.name}()""" @@ -409,7 +425,9 @@ def generate_aio_async_method(method: Method, model: Model) -> Optional[str]: if method.return_value is not None: call = wrap_result(call, method.return_value.type, model) - return f""" async def {method.name}({signature}): + ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + + return f""" async def {method.name}({signature}) -> {ret}: return {call}""" @@ -430,7 +448,9 @@ def generate_custom_facade_method( body = textwrap.indent(logic.strip(), " " * 8) keyword = "async def" if awaitable else "def" - return f""" {keyword} {method.name}({signature}): + ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + + return f""" {keyword} {method.name}({signature}) -> {ret}: {body} return {call}""" @@ -538,8 +558,9 @@ def generate_py_property(method: Method, model: Model) -> Optional[str]: return None access = wrap_result(f"self._impl.{name}", method.return_value.type, model) + ret = pyi_type(method.return_value.type, model) prop = f""" @property - def {name}(self): + def {name}(self) -> {ret}: return {access}""" set_method = next((m for m in method.object_type.methods if m.name == f"set_{name}"), None) @@ -547,7 +568,7 @@ def {name}(self): prop += f""" @{name}.setter - def {name}(self, value): + def {name}(self, value: {ret}) -> None: self._impl.{name} = _unwrap(value)""" return prop @@ -574,17 +595,26 @@ def generate_py_sync_method(method: Method, model: Model) -> Optional[str]: signature = ["self"] for param in method.input_parameters: - signature.append(f"{param.name}=None" if param.nullable else param.name) + signature.append(facade_param(param, model)) 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)}): + ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + + return f""" def {method.name}({", ".join(signature)}) -> {ret}: return {call}""" +def facade_param(param, model: Model) -> str: + annotation = pyi_type(param.type, model) + if param.nullable: + return f"{param.name}: Optional[{annotation}] = None" + return f"{param.name}: {annotation}" + + def generate_py_async_method(method: Method, model: Model) -> Optional[str]: if method.is_property_accessor: return None @@ -598,7 +628,9 @@ def generate_py_async_method(method: Method, model: Model) -> Optional[str]: if method.return_value is not None: call = wrap_result(call, method.return_value.type, model) - return f""" def {method.name}({signature}): + ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) + + return f""" def {method.name}({signature}) -> {ret}: return {call}""" @@ -615,10 +647,10 @@ def build_facade_async_parts(method: Method, model: Model) -> Optional[Tuple[str 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") + signature.append(f"{param.name}: Optional[{pyi_type(param.type, model)}] = None") args.append(f"_unwrap({param.name})") else: - signature.append(param.name) + signature.append(facade_param(param, model)) args.append(param.name) if method.return_value is not None: @@ -667,6 +699,11 @@ def generate_extension_pyi(model: Model) -> str: lines += generate_pyi_class(otype, model) lines.append("") + lines.append("") + for fn in module_functions(model): + lines.append(f"def {fn.py_name}() -> Any: ...") + lines.append("def _complete_request(request: Any, result: Any, error: Any) -> None: ...") + lines.append("") return "\n".join(lines) @@ -692,8 +729,13 @@ def generate_pyi_class(otype: ObjectType, model: Model) -> List[str]: name = property_name_from_accessor(method) if name is None or property_getter_marshal(method) is None: continue + typing = pyi_type(method.return_value.type, model) body.append(" @property") - body.append(f" def {name}(self) -> {pyi_type(method.return_value.type, model)}: ...") + body.append(f" def {name}(self) -> {typing}: ...") + set_method = next((m for m in otype.methods if m.name == f"set_{name}"), None) + if set_method is not None and property_setter_supported(set_method): + body.append(f" @{name}.setter") + body.append(f" def {name}(self, value: {typing}) -> None: ...") for method in otype.methods: stub = pyi_method(method, model) diff --git a/frida/frida_bindgen/customization.py b/frida/frida_bindgen/customization.py index 46aa349..7c92170 100644 --- a/frida/frida_bindgen/customization.py +++ b/frida/frida_bindgen/customization.py @@ -56,7 +56,7 @@ def load_customizations() -> Customizations: ), methods={ "post": MethodCustomizations( - param_typings=["message", "data=None"], + param_typings=["message: Any", "data: Optional[bytes] = None"], custom_logic="json = _to_json(message)", ), }, @@ -68,15 +68,15 @@ def load_customizations() -> Customizations: ), methods={ "post": MethodCustomizations( - param_typings=["connection_id", "message", "data=None"], + param_typings=["connection_id: int", "message: Any", "data: Optional[bytes] = None"], custom_logic="json = _to_json(message)", ), "narrowcast": MethodCustomizations( - param_typings=["tag", "message", "data=None"], + param_typings=["tag: str", "message: Any", "data: Optional[bytes] = None"], custom_logic="json = _to_json(message)", ), "broadcast": MethodCustomizations( - param_typings=["message", "data=None"], + param_typings=["message: Any", "data: Optional[bytes] = None"], custom_logic="json = _to_json(message)", ), }, @@ -85,22 +85,20 @@ def load_customizations() -> Customizations: methods={ "spawn": MethodCustomizations( param_typings=[ - "program", - "argv=None", - "envp=None", - "env=None", - "cwd=None", - "stdio=None", - "**aux", + "program: Union[str, List[Union[str, bytes]]]", + "argv: Optional[List[Union[str, bytes]]] = None", + "envp: Optional[Mapping[str, str]] = None", + "env: Optional[Mapping[str, str]] = None", + "cwd: Optional[str] = None", + "stdio: Optional[str] = None", + "**aux: Any", ], 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() + args = list(program) + argv = args if len(args) > 1 else None + first = args[0] + program = first.decode() if isinstance(first, bytes) else first options = _frida.SpawnOptions() if argv is not None: @@ -118,19 +116,21 @@ def load_customizations() -> Customizations: """, ), "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), + "resume": MethodCustomizations(param_typings=["target: ProcessTarget"], custom_logic=_RESOLVE_PID), + "kill": MethodCustomizations(param_typings=["target: ProcessTarget"], custom_logic=_RESOLVE_PID), + "input": MethodCustomizations( + param_typings=["target: ProcessTarget", "data: bytes"], custom_logic=_RESOLVE_PID + ), "inject_library_file": MethodCustomizations( - param_typings=["target", "path", "entrypoint", "data"], + param_typings=["target: ProcessTarget", "path: str", "entrypoint: str", "data: str"], custom_logic=_RESOLVE_PID, ), "inject_library_blob": MethodCustomizations( - param_typings=["target", "blob", "entrypoint", "data"], + param_typings=["target: ProcessTarget", "blob: bytes", "entrypoint: str", "data: str"], custom_logic=_RESOLVE_PID, ), "attach": MethodCustomizations( - param_typings=["target", "**kwargs"], + param_typings=["target: ProcessTarget", "**kwargs: Any"], custom_logic=( "pid = self._pid_of(target)\n" "options = _make_options(_frida.SessionOptions, kwargs, {})", "pid = await self._pid_of(target)\n" @@ -150,7 +150,7 @@ def load_customizations() -> Customizations: ), methods={ "post": MethodCustomizations( - param_typings=["message", "data=None"], + param_typings=["message: Any", "data: Optional[bytes] = None"], custom_logic="json = _to_json(message)", ), "is_destroyed": MethodCustomizations(as_property=True), @@ -160,7 +160,12 @@ def load_customizations() -> Customizations: methods={ "is_detached": MethodCustomizations(as_property=True), "create_script": MethodCustomizations( - param_typings=["source", "name=None", "snapshot=None", "runtime=None"], + param_typings=[ + "source: str", + "name: Optional[str] = None", + "snapshot: Optional[bytes] = None", + "runtime: Optional[str] = None", + ], custom_logic="""\ options = _frida.ScriptOptions() if name is not None: @@ -171,7 +176,12 @@ def load_customizations() -> Customizations: options.runtime = runtime""", ), "create_script_from_bytes": MethodCustomizations( - param_typings=["data", "name=None", "snapshot=None", "runtime=None"], + param_typings=[ + "data: bytes", + "name: Optional[str] = None", + "snapshot: Optional[bytes] = None", + "runtime: Optional[str] = None", + ], custom_logic="""\ bytes = data options = _frida.ScriptOptions() @@ -183,7 +193,7 @@ def load_customizations() -> Customizations: options.runtime = runtime""", ), "compile_script": MethodCustomizations( - param_typings=["source", "name=None", "runtime=None"], + param_typings=["source: str", "name: Optional[str] = None", "runtime: Optional[str] = None"], custom_logic="""\ options = _frida.ScriptOptions() if name is not None: @@ -192,7 +202,11 @@ def load_customizations() -> Customizations: options.runtime = runtime""", ), "snapshot_script": MethodCustomizations( - param_typings=["embed_script", "warmup_script=None", "runtime=None"], + param_typings=[ + "embed_script: str", + "warmup_script: Optional[str] = None", + "runtime: Optional[str] = None", + ], custom_logic="""\ options = _frida.SnapshotOptions() if warmup_script is not None: @@ -211,13 +225,13 @@ def load_customizations() -> Customizations: 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", + "address: Optional[str] = None", + "port: Optional[int] = None", + "certificate: Optional[str] = None", + "origin: Optional[str] = None", + "authentication: Optional[Tuple[str, Any]] = None", + "asset_root: Optional[str] = None", + "request_handler: Optional[Callable[..., Any]] = None", ], custom_logic="""\ auth_service = None @@ -230,7 +244,7 @@ def load_customizations() -> Customizations: 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)) + service.authenticate = lambda token: json.dumps(data(token)) # type: ignore[attr-defined] AuthenticationService.__init__(service) auth_service = service._impl else: @@ -238,7 +252,7 @@ def load_customizations() -> Customizations: else: auth_service = _unwrap(authentication) -kwargs = {} +kwargs: Dict[str, Any] = {} if address is not None: kwargs["address"] = address if port is not None: @@ -398,4 +412,7 @@ def load_customizations() -> Customizations: for name in LEGACY_PRIVATE_TYPES: type_customizations.setdefault(name, ObjectTypeCustomizations(drop=True)) - return Customizations(type_customizations=type_customizations) + return Customizations( + type_customizations=type_customizations, + facade_preludes=("facade_types.py",), + ) diff --git a/frida/frida_bindgen/model.py b/frida/frida_bindgen/model.py index e7911ed..db616c8 100644 --- a/frida/frida_bindgen/model.py +++ b/frida/frida_bindgen/model.py @@ -200,6 +200,7 @@ def customizations(self) -> Optional[EnumerationMemberCustomizations]: @dataclass class Customizations: type_customizations: Mapping[str, TypeCustomizations] = field(default_factory=OrderedDict) + facade_preludes: Tuple[str, ...] = () @dataclass From 4c5f5f920934a1c1ccf8494677e2923949e9691f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Wed, 22 Jul 2026 00:23:55 +0200 Subject: [PATCH 08/14] bindgen: Restore missing facade members The .gir yields only get_device_by_id/by_type, and drops Cancellable.connect/disconnect, whose callback is not marshallable. Reinstate all six. connect/disconnect go over the cancelled signal, keeping g_cancellable_connect's return-0-when-cancelled contract. --- .../assets/facade_cancellable.py | 27 +++++++++++++++++++ .../assets/facade_cancellable_aio.py | 27 +++++++++++++++++++ .../assets/facade_device_manager_members.py | 16 +++++++++++ .../facade_device_manager_members_aio.py | 16 +++++++++++ 4 files changed, 86 insertions(+) diff --git a/frida/frida_bindgen/assets/facade_cancellable.py b/frida/frida_bindgen/assets/facade_cancellable.py index 9b3f2d2..52a6a03 100644 --- a/frida/frida_bindgen/assets/facade_cancellable.py +++ b/frida/frida_bindgen/assets/facade_cancellable.py @@ -20,3 +20,30 @@ def raise_if_cancelled(self) -> None: def get_pollfd(self) -> "CancellablePollFD": return CancellablePollFD(self._impl) + + +def _setup(self) -> None: + self._cancel_handlers: Dict[int, Callable[..., Any]] = {} + self._next_cancel_handler_id = 1 + + +def connect(self, callback: Callable[..., Any]) -> int: + if self._impl.is_cancelled(): + callback() + return 0 + + handler_id = self._next_cancel_handler_id + self._next_cancel_handler_id = handler_id + 1 + + def handler(*args: Any) -> None: + callback() + + self._cancel_handlers[handler_id] = handler + self._impl.on("cancelled", handler) + return handler_id + + +def disconnect(self, handler_id: int) -> None: + handler = self._cancel_handlers.pop(handler_id, None) + if handler is not None: + self._impl.off("cancelled", handler) diff --git a/frida/frida_bindgen/assets/facade_cancellable_aio.py b/frida/frida_bindgen/assets/facade_cancellable_aio.py index 97b68d6..dd84e1c 100644 --- a/frida/frida_bindgen/assets/facade_cancellable_aio.py +++ b/frida/frida_bindgen/assets/facade_cancellable_aio.py @@ -16,3 +16,30 @@ def raise_if_cancelled(self) -> None: def get_pollfd(self) -> "CancellablePollFD": return CancellablePollFD(self._impl) + + +def _setup(self) -> None: + self._cancel_handlers: Dict[int, Callable[..., Any]] = {} + self._next_cancel_handler_id = 1 + + +def connect(self, callback: Callable[..., Any]) -> int: + if self._impl.is_cancelled(): + callback() + return 0 + + handler_id = self._next_cancel_handler_id + self._next_cancel_handler_id = handler_id + 1 + + def handler(*args: Any) -> None: + callback() + + self._cancel_handlers[handler_id] = handler + self._impl.on("cancelled", handler) + return handler_id + + +def disconnect(self, handler_id: int) -> None: + handler = self._cancel_handlers.pop(handler_id, None) + if handler is not None: + self._impl.off("cancelled", handler) diff --git a/frida/frida_bindgen/assets/facade_device_manager_members.py b/frida/frida_bindgen/assets/facade_device_manager_members.py index 54d182a..dc06109 100644 --- a/frida/frida_bindgen/assets/facade_device_manager_members.py +++ b/frida/frida_bindgen/assets/facade_device_manager_members.py @@ -7,3 +7,19 @@ def get_device_matching(self, predicate: Callable[["Device"], bool], timeout: Un if time.monotonic() >= deadline: raise _frida.InvalidArgumentError("no matching device found") time.sleep(0.05) + + +def get_device(self, id: Optional[str], timeout: Union[int, float] = 0) -> "Device": + return self.get_device_matching(lambda d: d.id == id, timeout) + + +def get_local_device(self) -> "Device": + return self.get_device_matching(lambda d: d.type == "local", 0) + + +def get_remote_device(self) -> "Device": + return self.get_device_matching(lambda d: d.type == "remote", 0) + + +def get_usb_device(self, timeout: Union[int, float] = 0) -> "Device": + return self.get_device_matching(lambda d: d.type == "usb", timeout) diff --git a/frida/frida_bindgen/assets/facade_device_manager_members_aio.py b/frida/frida_bindgen/assets/facade_device_manager_members_aio.py index 5563a88..296323d 100644 --- a/frida/frida_bindgen/assets/facade_device_manager_members_aio.py +++ b/frida/frida_bindgen/assets/facade_device_manager_members_aio.py @@ -7,3 +7,19 @@ async def get_device_matching(self, predicate: Callable[["Device"], bool], timeo if time.monotonic() >= deadline: raise _frida.InvalidArgumentError("no matching device found") await asyncio.sleep(0.05) + + +async def get_device(self, id: Optional[str], timeout: Union[int, float] = 0) -> "Device": + return await self.get_device_matching(lambda d: d.id == id, timeout) + + +async def get_local_device(self) -> "Device": + return await self.get_device_matching(lambda d: d.type == "local", 0) + + +async def get_remote_device(self) -> "Device": + return await self.get_device_matching(lambda d: d.type == "remote", 0) + + +async def get_usb_device(self, timeout: Union[int, float] = 0) -> "Device": + return await self.get_device_matching(lambda d: d.type == "usb", timeout) From e82619be58046a673e1255c6b98bb65a90da087f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Wed, 22 Jul 2026 00:23:55 +0200 Subject: [PATCH 09/14] bindgen: Generate signal callback overloads Facade on/off took any callable, so misshapen handlers type-checked. Derive the stacks from the .gir: the alias name follows from the type and signal name. Undefined aliases and single-signal types are skipped. --- frida/frida_bindgen/codegen.py | 57 +++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py index 35cbf3d..82dd40a 100644 --- a/frida/frida_bindgen/codegen.py +++ b/frida/frida_bindgen/codegen.py @@ -1,10 +1,13 @@ from __future__ import annotations +import ast +import re import textwrap from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Set, Tuple, Union from frida_bindgen_core import Procedure, Type +from frida_bindgen_core.naming import to_pascal_case from .model import ( Enumeration, @@ -54,7 +57,8 @@ def error_members(model: Model) -> List[Tuple[str, str]]: FACADE_TYPING_IMPORTS = ( - "from typing import Any, Callable, Dict, List, Literal, Mapping, NotRequired, Optional, Tuple, TypedDict, Union" + "from typing import (Any, Callable, Dict, List, Literal, Mapping, NotRequired, Optional, Tuple, TypedDict, " + "Union, overload)" ) @@ -388,6 +392,8 @@ def generate_aio_class(otype: ObjectType, model: Model) -> str: if not members: members.append(" pass") + members = apply_signal_overloads(members, otype, model) + return f"class {otype.py_name}:\n" + "\n\n".join(members) @@ -483,6 +489,8 @@ def generate_py_class(otype: ObjectType, model: Model) -> str: if not members: members.append(" pass") + members = apply_signal_overloads(members, otype, model) + return f"class {otype.py_name}:\n" + "\n\n".join(members) @@ -518,13 +526,54 @@ def generate_facade_init(otype: ObjectType) -> str: def generate_facade_signals() -> str: - return """ def on(self, signal, callback): + return """ def on(self, signal: str, callback: Callable[..., Any]) -> None: self._impl.on(signal, _make_signal_handler(callback)) - def off(self, signal, callback): + def off(self, signal: str, callback: Callable[..., Any]) -> None: self._impl.off(signal, callback)""" +def facade_prelude_names(model: Model) -> Set[str]: + names = set() + for asset in model.customizations.facade_preludes: + for node in ast.parse(read_asset(asset)).body: + if isinstance(node, ast.ClassDef): + names.add(node.name) + elif isinstance(node, ast.Assign): + names.update(t.id for t in node.targets if isinstance(t, ast.Name)) + return names + + +def signal_callback_alias(otype: ObjectType, signal) -> str: + return f"{otype.py_name}{to_pascal_case(signal.name.replace('-', '_'))}Callback" + + +def signal_overload_block(otype: ObjectType, model: Model, name: str) -> Optional[str]: + aliases = facade_prelude_names(model) + lines = [] + for signal in otype.signals: + alias = signal_callback_alias(otype, signal) + if alias in aliases: + lines.append(" @overload") + lines.append(f' def {name}(self, signal: Literal["{signal.name}"], callback: {alias}) -> None: ...') + return "\n".join(lines) if len(lines) > 2 else None + + +def apply_signal_overloads(members: List[str], otype: ObjectType, model: Model) -> List[str]: + result = [] + for member in members: + for name in ("on", "off"): + block = signal_overload_block(otype, model, name) + if block is None: + continue + pattern = re.compile(rf"^ (?:async )?def {name}\(self, signal", re.MULTILINE) + match = pattern.search(member) + if match is not None: + member = member[: match.start()] + block + "\n" + member[match.start() :] + result.append(member) + return result + + def facade_repr_property_names(otype: ObjectType) -> List[str]: names = [] for method in otype.methods: From 2e48e03ea05df3014dfeddab70c4b406bf881d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Wed, 22 Jul 2026 00:23:55 +0200 Subject: [PATCH 10/14] bindgen: Restore remaining public names Reinstate the cancellable decorator, RPCResult, make_rpc_call_request and make_auth_callback, and expose ScriptExportsAsync from frida.core via a new facade_epilogues customization. --- .../assets/facade_cancellable_helpers.py | 17 +++++++++++++++++ .../assets/facade_cancellable_helpers_aio.py | 17 +++++++++++++++++ frida/frida_bindgen/assets/facade_epilogue.py | 1 + .../frida_bindgen/assets/facade_rpc_helpers.py | 17 +++++++++++++++++ .../assets/facade_rpc_helpers_aio.py | 17 +++++++++++++++++ frida/frida_bindgen/assets/facade_toplevel.py | 8 ++++++++ .../frida_bindgen/assets/facade_toplevel_aio.py | 8 ++++++++ frida/frida_bindgen/codegen.py | 9 ++++++++- frida/frida_bindgen/customization.py | 1 + frida/frida_bindgen/model.py | 1 + 10 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 frida/frida_bindgen/assets/facade_epilogue.py diff --git a/frida/frida_bindgen/assets/facade_cancellable_helpers.py b/frida/frida_bindgen/assets/facade_cancellable_helpers.py index f53df5f..a46c144 100644 --- a/frida/frida_bindgen/assets/facade_cancellable_helpers.py +++ b/frida/frida_bindgen/assets/facade_cancellable_helpers.py @@ -21,3 +21,20 @@ def __enter__(self) -> int: def __exit__(self, *exc: Any) -> None: self.release() + + +P = ParamSpec("P") +R = TypeVar("R", covariant=True) + + +def cancellable(f: Callable[P, R]) -> Callable[P, R]: + @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 diff --git a/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py b/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py index f53df5f..a46c144 100644 --- a/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py +++ b/frida/frida_bindgen/assets/facade_cancellable_helpers_aio.py @@ -21,3 +21,20 @@ def __enter__(self) -> int: def __exit__(self, *exc: Any) -> None: self.release() + + +P = ParamSpec("P") +R = TypeVar("R", covariant=True) + + +def cancellable(f: Callable[P, R]) -> Callable[P, R]: + @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 diff --git a/frida/frida_bindgen/assets/facade_epilogue.py b/frida/frida_bindgen/assets/facade_epilogue.py new file mode 100644 index 0000000..93a308e --- /dev/null +++ b/frida/frida_bindgen/assets/facade_epilogue.py @@ -0,0 +1 @@ +ScriptExportsAsync = aio._ScriptExports diff --git a/frida/frida_bindgen/assets/facade_rpc_helpers.py b/frida/frida_bindgen/assets/facade_rpc_helpers.py index 641dc1f..907cfb4 100644 --- a/frida/frida_bindgen/assets/facade_rpc_helpers.py +++ b/frida/frida_bindgen/assets/facade_rpc_helpers.py @@ -40,3 +40,20 @@ def __dir__(self) -> List[str]: ScriptExportsSync = _ScriptExports ScriptExports = ScriptExportsSync + + +@dataclasses.dataclass +class RPCResult: + finished: bool = False + value: Any = None + error: Optional[Exception] = None + + +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) diff --git a/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py b/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py index 1df1316..154b8e5 100644 --- a/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py +++ b/frida/frida_bindgen/assets/facade_rpc_helpers_aio.py @@ -36,3 +36,20 @@ def method(*args: Any) -> Any: ScriptExportsAsync = _ScriptExports + + +@dataclasses.dataclass +class RPCResult: + finished: bool = False + value: Any = None + error: Optional[Exception] = None + + +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) diff --git a/frida/frida_bindgen/assets/facade_toplevel.py b/frida/frida_bindgen/assets/facade_toplevel.py index e77e39d..e8120f3 100644 --- a/frida/frida_bindgen/assets/facade_toplevel.py +++ b/frida/frida_bindgen/assets/facade_toplevel.py @@ -69,3 +69,11 @@ def inject_library_blob(target: ProcessTarget, blob: bytes, entrypoint: str, dat def shutdown() -> None: get_device_manager().close() + + +def make_auth_callback(callback: Callable[[str], Any]) -> Callable[[Any], str]: + def authenticate(token: str) -> str: + session_info = callback(token) + return json.dumps(session_info) + + return authenticate diff --git a/frida/frida_bindgen/assets/facade_toplevel_aio.py b/frida/frida_bindgen/assets/facade_toplevel_aio.py index 035550d..b6dd8e5 100644 --- a/frida/frida_bindgen/assets/facade_toplevel_aio.py +++ b/frida/frida_bindgen/assets/facade_toplevel_aio.py @@ -70,3 +70,11 @@ async def inject_library_blob(target: ProcessTarget, blob: bytes, entrypoint: st async def shutdown() -> None: await get_device_manager().close() + + +def make_auth_callback(callback: Callable[[str], Any]) -> Callable[[Any], str]: + def authenticate(token: str) -> str: + session_info = callback(token) + return json.dumps(session_info) + + return authenticate diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py index 82dd40a..e6d9c60 100644 --- a/frida/frida_bindgen/codegen.py +++ b/frida/frida_bindgen/codegen.py @@ -58,7 +58,7 @@ def error_members(model: Model) -> List[Tuple[str, str]]: FACADE_TYPING_IMPORTS = ( "from typing import (Any, Callable, Dict, List, Literal, Mapping, NotRequired, Optional, Tuple, TypedDict, " - "Union, overload)" + "Sequence, Union, ParamSpec, TypeVar, cast, overload)" ) @@ -266,7 +266,9 @@ def generate_all(model: Model) -> Dict[str, str]: def generate_py(model: Model) -> str: lines = [ "from . import _frida", + "import dataclasses", "import fnmatch", + "import functools", "import inspect", "import time", "import json", @@ -302,6 +304,9 @@ def generate_py(model: Model) -> str: lines.append("") lines.append("") lines.append("from . import aio") + for asset in model.customizations.facade_epilogues: + lines.append("") + lines.append(read_asset(asset).strip()) lines.append("") lines.append("core = sys.modules[__name__]") lines.append('sys.modules[__name__ + ".core"] = core') @@ -334,7 +339,9 @@ def generate_aio(model: Model) -> str: "import asyncio", "import contextvars", "import time", + "import dataclasses", "import fnmatch", + "import functools", "import inspect", "import json", "import sys", diff --git a/frida/frida_bindgen/customization.py b/frida/frida_bindgen/customization.py index 7c92170..b49bbae 100644 --- a/frida/frida_bindgen/customization.py +++ b/frida/frida_bindgen/customization.py @@ -415,4 +415,5 @@ def load_customizations() -> Customizations: return Customizations( type_customizations=type_customizations, facade_preludes=("facade_types.py",), + facade_epilogues=("facade_epilogue.py",), ) diff --git a/frida/frida_bindgen/model.py b/frida/frida_bindgen/model.py index db616c8..fe385bd 100644 --- a/frida/frida_bindgen/model.py +++ b/frida/frida_bindgen/model.py @@ -201,6 +201,7 @@ def customizations(self) -> Optional[EnumerationMemberCustomizations]: class Customizations: type_customizations: Mapping[str, TypeCustomizations] = field(default_factory=OrderedDict) facade_preludes: Tuple[str, ...] = () + facade_epilogues: Tuple[str, ...] = () @dataclass From e74298d7e54c7898dfcf2775d82747a381de114a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Fri, 31 Jul 2026 00:31:04 +0200 Subject: [PATCH 11/14] Fix certificate option on remote device et al The GTlsCertificate-typed properties had no marshaller, so their setters were never generated. Marshal them to and from a string holding either a PEM blob or a filename, like the old hand-written bindings did. Reported and diagnosed by rev1si0n . --- .../assets/codegen_endpoint_parameters.c | 31 +--------- .../assets/codegen_gobject_methods.c | 58 +++++++++++++++++++ .../assets/codegen_gobject_prototypes.h | 2 + frida/frida_bindgen/codegen.py | 15 +++++ tests/test_bindgen.py | 44 ++++++++++++++ 5 files changed, 122 insertions(+), 28 deletions(-) diff --git a/frida/frida_bindgen/assets/codegen_endpoint_parameters.c b/frida/frida_bindgen/assets/codegen_endpoint_parameters.c index 61ea7ef..bba93bd 100644 --- a/frida/frida_bindgen/assets/codegen_endpoint_parameters.c +++ b/frida/frida_bindgen/assets/codegen_endpoint_parameters.c @@ -1,27 +1,3 @@ -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, @@ -31,7 +7,7 @@ PyEndpointParameters_init (PyEndpointParameters * self, static char * keywords[] = { "address", "port", "certificate", "origin", "auth_service", "asset_root", NULL }; char * address = NULL; unsigned short int port = 0; - char * certificate_value = NULL; + PyObject * certificate_value = NULL; char * origin = NULL; PyObject * auth_service_obj = NULL; char * asset_root_value = NULL; @@ -43,10 +19,10 @@ PyEndpointParameters_init (PyEndpointParameters * self, if (PyGObject_tp_init ((PyObject *) self, args, kw) < 0) return -1; - if (!PyArg_ParseTupleAndKeywords (args, kw, "|esHesesOes", keywords, + if (!PyArg_ParseTupleAndKeywords (args, kw, "|esHOesOes", keywords, "utf-8", &address, &port, - "utf-8", &certificate_value, + &certificate_value, "utf-8", &origin, &auth_service_obj, "utf-8", &asset_root_value)) @@ -74,7 +50,6 @@ PyEndpointParameters_init (PyEndpointParameters * self, 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_methods.c b/frida/frida_bindgen/assets/codegen_gobject_methods.c index ebd2d3e..793e544 100644 --- a/frida/frida_bindgen/assets/codegen_gobject_methods.c +++ b/frida/frida_bindgen/assets/codegen_gobject_methods.c @@ -1048,6 +1048,64 @@ PyGObject_marshal_variant_tuple (GVariant * variant) return tuple; } +static gboolean +PyGObject_unmarshal_certificate (PyObject * value, + GTlsCertificate ** certificate) +{ + PyObject * bytes; + const gchar * str; + GError * error = NULL; + + if (value == Py_None) + { + *certificate = NULL; + return TRUE; + } + + bytes = PyUnicode_AsUTF8String (value); + if (bytes == NULL) + return FALSE; + str = PyBytes_AsString (bytes); + + 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); + + Py_DecRef (bytes); + + 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 PyObject * +PyGObject_marshal_certificate (GTlsCertificate * certificate) +{ + PyObject * result; + gchar * pem; + + if (certificate == NULL) + PyFrida_RETURN_NONE; + + g_object_get (certificate, "certificate-pem", &pem, NULL); + + result = PyGObject_marshal_string (pem); + + g_free (pem); + + return result; +} + static PyObject * PyFrida_raise (GError * error) { diff --git a/frida/frida_bindgen/assets/codegen_gobject_prototypes.h b/frida/frida_bindgen/assets/codegen_gobject_prototypes.h index 0675acc..5087b39 100644 --- a/frida/frida_bindgen/assets/codegen_gobject_prototypes.h +++ b/frida/frida_bindgen/assets/codegen_gobject_prototypes.h @@ -35,6 +35,8 @@ 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_certificate (PyObject * value, GTlsCertificate ** certificate); +static PyObject * PyGObject_marshal_certificate (GTlsCertificate * certificate); static PyObject * PyFrida_raise (GError * error); static PyObject * PyFrida_marshal_error (GError * error); diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py index e6d9c60..807b723 100644 --- a/frida/frida_bindgen/codegen.py +++ b/frida/frida_bindgen/codegen.py @@ -882,6 +882,8 @@ def pyi_type(type: Type, model: Model) -> str: return "dict" if name == "GLib.Variant": return "Any" + if name == "Gio.TlsCertificate": + return "str" if resolve_enumeration(type, model) is not None: return "str" obj = resolve_object_type(type, model) @@ -2017,6 +2019,17 @@ def build_setter_body(param: Parameter, set_call) -> Optional[str]: g_hash_table_unref (dict); """ + if type_name == "Gio.TlsCertificate": + return f""" GTlsCertificate * certificate; + + if (!PyGObject_unmarshal_certificate (value, &certificate)) + return -1; + + {set_call("certificate")} + + g_clear_object (&certificate); +""" + 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" @@ -2096,6 +2109,8 @@ def build_return_marshal(type: Type, model: Model) -> Optional[str]: return "PyGObject_marshal_vardict ({value})" if name == "GLib.Variant": return "PyGObject_marshal_variant ({value})" + if name == "Gio.TlsCertificate": + return "PyGObject_marshal_certificate ({value})" enum = resolve_enumeration(type, model) if enum is not None: diff --git a/tests/test_bindgen.py b/tests/test_bindgen.py index 6cef41d..19a7992 100644 --- a/tests/test_bindgen.py +++ b/tests/test_bindgen.py @@ -17,6 +17,27 @@ def free_port(): GIRDIR = REPO / "build" / "subprojects" / "frida-core" / "src" / "api" OUTDIR = REPO / "build" / "bindgen-out" BUILT_EXTENSION = REPO / "build" / "frida" / "_frida.abi3.so" +CERTIFICATE = """\ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIUEoCZX+FRWNTzcvvrCE2chFvE0EgwDQYJKoZIhvcNAQEL +BQAwHDEaMBgGA1UEAwwRZnJpZGEtcHl0aG9uLXRlc3QwIBcNMjYwNzMwMjIyNjAy +WhgPMjEyNjA3MDYyMjI2MDJaMBwxGjAYBgNVBAMMEWZyaWRhLXB5dGhvbi10ZXN0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtKTIUbHLhkqRKdKqXuMc +u7q8OZD2eKdykp4Qvv+I5JcmNW7nlW+58zTCLaqUCsSrojDb8T2qvtRJbaJYAofE +P7bhb5ROOVwsyOjhvP7rZ4zRXIDAUlB7zE15yNkWL+Rh4l3wRxMOMIB2HQlurtDk +PIqPOITwRvrADKZt/73iMASwrkkqKnl/F3rLT0JQrd907sBdGGZNbJM9L4TUBT9t +MBBl6qXtDEFyBfeoqlnrLUTN/Jwyka+/6n6eZCN8g2g5tS1Lc7LFjX4iBG9ibgaH +hrNSTM+haLImFS3dtXFr4rOCAHnDYYvkCY1nuZOXannCvZowv0xczqpGLbj5IYJK +MQIDAQABo1MwUTAdBgNVHQ4EFgQUi83cV5yMTdWaVeRpgNO3vyeitMMwHwYDVR0j +BBgwFoAUi83cV5yMTdWaVeRpgNO3vyeitMMwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAQEADScSFuasB4mgOb/kktq3O09rf/uUIa6OEjdFRec/i2+E +ZAUd5onsWQTHm4yhjqQwqVBte58tTqZB7S1HMCy5sEgEVUFnTrMUqtrf/WAnsWA6 +SdjNr7h/aRnLHHysLkbl0Zo4fOIWIYSJpLn39e0OzcNBjEHN5U9HE/m/2LFku1Kb +KMTYXrhBKvVPpZLxZyoLz00EM7euaNx3juAgfQdSDfKGahf5CqqMJ/VmITISDJDR +jtN3wcv0/nZA5x/b73QCj0jqIEWqguYf4GLWitxGsSdT2dBEpT88Kz3Ibwb8uRT+ +Ls3eRsdoqxiOT0E7Z44O8gjbqsYOr/jdMShwEytDwQ== +-----END CERTIFICATE----- +""" @unittest.skipUnless( @@ -131,6 +152,29 @@ def test_string_array_property_round_trips(self): options.argv = None self.assertIsNone(options.argv) + def test_certificate_property_accepts_pem_and_path(self): + import tempfile + + options = self._frida.RemoteDeviceOptions() + self.assertIsNone(options.certificate) + + options.certificate = CERTIFICATE + self.assertEqual(options.certificate, CERTIFICATE) + + with tempfile.NamedTemporaryFile("w", suffix=".pem") as f: + f.write(CERTIFICATE) + f.flush() + options.certificate = f.name + self.assertEqual(options.certificate, CERTIFICATE) + + options.certificate = None + self.assertIsNone(options.certificate) + + def test_certificate_property_rejects_missing_file(self): + options = self._frida.RemoteDeviceOptions() + with self.assertRaises(self._frida.InvalidArgumentError): + options.certificate = "/nonexistent.pem" + def test_vardict_property_round_trips(self): options = self._frida.SpawnOptions() options.aux = {"name": "value", "count": 42, "flag": True} From 9c309172d70d9e0d9be17b52c6e30f5a718520d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Sun, 2 Aug 2026 18:13:33 +0200 Subject: [PATCH 12/14] Fix Script.enable_debugger() port default Vala's default argument is absent from the .gir, so the generated facade made the port mandatory. Declare the default in the customizations, and let param_typings override the facade signature also when there is no custom logic. --- frida/frida_bindgen/codegen.py | 20 +++++++++++--------- frida/frida_bindgen/customization.py | 1 + tests/test_bindgen.py | 6 ++++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py index 807b723..8c829b5 100644 --- a/frida/frida_bindgen/codegen.py +++ b/frida/frida_bindgen/codegen.py @@ -649,9 +649,7 @@ def generate_py_sync_method(method: Method, model: Model) -> Optional[str]: 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(facade_param(param, model)) + params = [facade_param(param, model) for param in method.input_parameters] names = ", ".join(param.name for param in method.input_parameters) call = f"self._impl.{method.name}({names})" @@ -660,10 +658,14 @@ def generate_py_sync_method(method: Method, model: Model) -> Optional[str]: ret = "None" if method.return_value is None else pyi_type(method.return_value.type, model) - return f""" def {method.name}({", ".join(signature)}) -> {ret}: + return f""" def {method.name}({facade_signature(method, params)}) -> {ret}: return {call}""" +def facade_signature(method: Method, params: List[str]) -> str: + return ", ".join(["self"] + (method.custom_facade_params or params)) + + def facade_param(param, model: Model) -> str: annotation = pyi_type(param.type, model) if param.nullable: @@ -691,7 +693,7 @@ def generate_py_async_method(method: Method, model: Model) -> Optional[str]: def build_facade_async_parts(method: Method, model: Model) -> Optional[Tuple[str, str]]: - signature = ["self"] + params = [] args = [] for param in method.input_parameters: if param.type.name == "Gio.Cancellable": @@ -700,20 +702,20 @@ def build_facade_async_parts(method: Method, model: Model) -> Optional[Tuple[str return None options = resolve_options_type(param.type, model) if options is not None: - signature.append("**kwargs") + params.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}: Optional[{pyi_type(param.type, model)}] = None") + params.append(f"{param.name}: Optional[{pyi_type(param.type, model)}] = None") args.append(f"_unwrap({param.name})") else: - signature.append(facade_param(param, model)) + params.append(facade_param(param, model)) 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) + return facade_signature(method, params), "".join(arg + ", " for arg in args) def build_option_selectors(options: ObjectType) -> str: diff --git a/frida/frida_bindgen/customization.py b/frida/frida_bindgen/customization.py index b49bbae..eb7b13f 100644 --- a/frida/frida_bindgen/customization.py +++ b/frida/frida_bindgen/customization.py @@ -153,6 +153,7 @@ def load_customizations() -> Customizations: param_typings=["message: Any", "data: Optional[bytes] = None"], custom_logic="json = _to_json(message)", ), + "enable_debugger": MethodCustomizations(param_typings=["port: int = 0"]), "is_destroyed": MethodCustomizations(as_property=True), }, ), diff --git a/tests/test_bindgen.py b/tests/test_bindgen.py index 19a7992..a8a338e 100644 --- a/tests/test_bindgen.py +++ b/tests/test_bindgen.py @@ -690,6 +690,12 @@ def test_create_script_from_bytes_positional_name(self): finally: session.detach() + def test_enable_debugger_defaults_to_any_port(self): + import inspect + + for script in [self.frida.Script, self.frida.aio.Script]: + self.assertEqual(inspect.signature(script.enable_debugger).parameters["port"].default, 0) + def test_file_monitor_reports_changes(self): import os import tempfile From 0ab30eac6727667146178b801503e55befdfae6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Sun, 2 Aug 2026 18:19:14 +0200 Subject: [PATCH 13/14] Fix attach() linker_notifier_offsets kwarg Its customization hardcoded an empty selector map instead of the one computed from the options type, so the plural kwarg was rejected. Let codegen build the options argument for custom facade methods that take **kwargs. --- frida/frida_bindgen/codegen.py | 25 ++++++++++++++++++++++--- frida/frida_bindgen/customization.py | 6 +----- frida/frida_bindgen/model.py | 5 ----- tests/test_bindgen.py | 4 ++++ 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py index 8c829b5..ba5c5ba 100644 --- a/frida/frida_bindgen/codegen.py +++ b/frida/frida_bindgen/codegen.py @@ -448,11 +448,12 @@ 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) + args = facade_call_args(method, model) if method.is_async: - invoke = f"_invoke(self._impl.{method.name}, ({method.facade_call_args}))" + invoke = f"_invoke(self._impl.{method.name}, ({args}))" call = f"await {invoke}" if awaitable else invoke else: - call = f"self._impl.{method.name}({method.facade_call_args})" + call = f"self._impl.{method.name}({args})" if method.return_value is not None: call = wrap_result(call, method.return_value.type, model) @@ -468,6 +469,20 @@ def generate_custom_facade_method( return {call}""" +def facade_call_args(method: Method, model: Model) -> str: + takes_kwargs = any(param.split(":")[0].strip() == "**kwargs" for param in method.custom_facade_params) + args = [] + for param in method.input_parameters: + if param.type.name == "Gio.Cancellable": + continue + options = resolve_options_type(param.type, model) + if options is not None and takes_kwargs: + args.append(make_options_expr(options)) + else: + args.append(param.name) + return "".join(arg + ", " for arg in args) + + def generate_py_exception_reexports(model: Model) -> List[str]: names = [f"{name}Error" for _, name in error_members(model)] names.append("OperationCancelledError") @@ -703,7 +718,7 @@ def build_facade_async_parts(method: Method, model: Model) -> Optional[Tuple[str options = resolve_options_type(param.type, model) if options is not None: params.append("**kwargs") - args.append(f"_make_options(_frida.{options.py_name}, kwargs, {build_option_selectors(options)})") + args.append(make_options_expr(options)) elif resolve_input_object_type(param.type, model) is not None: params.append(f"{param.name}: Optional[{pyi_type(param.type, model)}] = None") args.append(f"_unwrap({param.name})") @@ -718,6 +733,10 @@ def build_facade_async_parts(method: Method, model: Model) -> Optional[Tuple[str return facade_signature(method, params), "".join(arg + ", " for arg in args) +def make_options_expr(options: ObjectType) -> str: + return f"_make_options(_frida.{options.py_name}, kwargs, {build_option_selectors(options)})" + + def build_option_selectors(options: ObjectType) -> str: selectors = {} otype = options diff --git a/frida/frida_bindgen/customization.py b/frida/frida_bindgen/customization.py index eb7b13f..c1399a8 100644 --- a/frida/frida_bindgen/customization.py +++ b/frida/frida_bindgen/customization.py @@ -131,11 +131,7 @@ def load_customizations() -> Customizations: ), "attach": MethodCustomizations( param_typings=["target: ProcessTarget", "**kwargs: Any"], - 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_logic=_RESOLVE_PID, ), }, custom_code=CustomCode( diff --git a/frida/frida_bindgen/model.py b/frida/frida_bindgen/model.py index fe385bd..fe9cbeb 100644 --- a/frida/frida_bindgen/model.py +++ b/frida/frida_bindgen/model.py @@ -144,11 +144,6 @@ 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 diff --git a/tests/test_bindgen.py b/tests/test_bindgen.py index a8a338e..e33f3a8 100644 --- a/tests/test_bindgen.py +++ b/tests/test_bindgen.py @@ -690,6 +690,10 @@ def test_create_script_from_bytes_positional_name(self): finally: session.detach() + def test_attach_accepts_linker_notifier_offsets(self): + session = self.frida.get_local_device().attach(0, linker_notifier_offsets=[0x100]) + session.detach() + def test_enable_debugger_defaults_to_any_port(self): import inspect From 8613e61023a6d8da4af11c0802eae95c59cfc2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Mon, 17 Aug 2026 20:40:41 +0200 Subject: [PATCH 14/14] Take object args and settings kwargs Sync methods dropped parameters of object type, so PeerOptions.add_relay never reached the extension even though the facade referred to it. Constructor kwargs were forwarded to an extension init that ignores them, leaving them silently unapplied. Apply them to the impl instead, reusing the options selectors so adders take a list, or a dict when they take a name and a value. --- frida/frida_bindgen/codegen.py | 56 ++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/frida/frida_bindgen/codegen.py b/frida/frida_bindgen/codegen.py index ba5c5ba..5d6b944 100644 --- a/frida/frida_bindgen/codegen.py +++ b/frida/frida_bindgen/codegen.py @@ -110,17 +110,25 @@ def _to_envp(value): def _make_options(cls, values, selectors): options = cls() + _apply_settings(options, values, selectors) + return options + + +def _apply_settings(impl, values, selectors): for name, value in values.items(): if value is None: continue select = selectors.get(name) if select is None: - setattr(options, name, value) + setattr(impl, name, _unwrap(value)) + elif isinstance(value, dict): + add = getattr(impl, select) + for key, element in value.items(): + add(key, _unwrap(element)) else: - add = getattr(options, select) + add = getattr(impl, select) for element in value: - add(element) - return options + add(_unwrap(element)) _current_cancellable = threading.local() @@ -204,17 +212,25 @@ def _to_envp(value): def _make_options(cls, values, selectors): options = cls() + _apply_settings(options, values, selectors) + return options + + +def _apply_settings(impl, values, selectors): for name, value in values.items(): if value is None: continue select = selectors.get(name) if select is None: - setattr(options, name, value) + setattr(impl, name, _unwrap(value)) + elif isinstance(value, dict): + add = getattr(impl, select) + for key, element in value.items(): + add(key, _unwrap(element)) else: - add = getattr(options, select) + add = getattr(impl, select) for element in value: - add(element) - return options + add(_unwrap(element)) _current_cancellable: contextvars.ContextVar = contextvars.ContextVar("frida_current_cancellable", default=None) @@ -538,10 +554,8 @@ def generate_facade_init(otype: ObjectType) -> str: {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()}}, - ) + self._impl = _frida.{otype.py_name}(*[_unwrap(a) for a in args]) + _apply_settings(self._impl, kwargs, {build_option_selectors(otype)}) setup = getattr(self, "_setup", None) if setup is not None: setup()""" @@ -665,7 +679,7 @@ def generate_py_sync_method(method: Method, model: Model) -> Optional[str]: return None params = [facade_param(param, model) for param in method.input_parameters] - names = ", ".join(param.name for param in method.input_parameters) + names = ", ".join(facade_argument(param, model) for param in method.input_parameters) call = f"self._impl.{method.name}({names})" if method.return_value is not None: @@ -681,6 +695,12 @@ def facade_signature(method: Method, params: List[str]) -> str: return ", ".join(["self"] + (method.custom_facade_params or params)) +def facade_argument(param, model: Model) -> str: + if resolve_input_object_type(param.type, model) is not None: + return f"_unwrap({param.name})" + return param.name + + def facade_param(param, model: Model) -> str: annotation = pyi_type(param.type, model) if param.nullable: @@ -1862,6 +1882,16 @@ def build_sync_param(param: Parameter) -> Optional["SyncParam"]: cleanup=f"g_clear_pointer (&{name}, g_variant_unref);", ) + if resolve_input_object_type(param.type, param.object_type.model) is not None: + return SyncParam( + decl=f"PyObject * {name}_obj;\n{param.type.c} {name} = NULL;", + fmt="O", + parse_args=[f"&{name}_obj"], + pre=f"""if ({name}_obj != Py_None) + {name} = ({param.type.c}) PY_GOBJECT_HANDLE ({name}_obj);""", + call_arg=name, + ) + return None