Compare commits

..

92 Commits

Author SHA1 Message Date
Ole André Vadla Ravnås e513eca572 subprojects: Prepare for release 2026-04-28 16:54:48 +02:00
Ole André Vadla Ravnås 15260bfa4a subprojects: Bump outdated 2026-04-28 16:38:55 +02:00
Ole André Vadla Ravnås dec8aca9ae subprojects: Bump outdated 2026-04-28 12:50:19 +02:00
Ole André Vadla Ravnås 8c69b9d032 submodules: Bump releng 2026-04-28 12:50:19 +02:00
Ole André Vadla Ravnås f57b0c1cfa subprojects: Prepare for release 2026-04-28 12:23:14 +02:00
Ole André Vadla Ravnås fd2c1f2424 subprojects: Bump outdated 2026-04-28 12:22:18 +02:00
Ole André Vadla Ravnås a9caf62a0c Reformat with black 26.3.1 2026-04-26 17:47:34 +02:00
Ole André Vadla Ravnås 8bc4415f34 subprojects: Bump outdated 2026-04-26 17:36:09 +02:00
Ole André Vadla Ravnås e3eca3179c subprojects: Bump outdated 2026-04-26 17:21:24 +02:00
Ole André Vadla Ravnås ef190d9957 submodules: Bump releng 2026-04-26 17:21:24 +02:00
Ole André Vadla Ravnås 1e06ebe9a3 subprojects: Bump outdated 2026-04-26 14:06:52 +02:00
Ole André Vadla Ravnås 925378439c subprojects: Bump outdated 2026-04-26 13:34:40 +02:00
Ole André Vadla Ravnås 7ae323a3cd subprojects: Bump outdated 2026-04-26 13:17:42 +02:00
Ole André Vadla Ravnås 9c5985c254 submodules: Bump releng 2026-04-26 13:17:42 +02:00
Ole André Vadla Ravnås b469134be4 subprojects: Bump outdated 2026-04-26 11:29:31 +02:00
Ole André Vadla Ravnås 9933d916ae subprojects: Bump outdated 2026-04-26 11:12:20 +02:00
Ole André Vadla Ravnås 09cac4e2a5 subprojects: Bump outdated 2026-04-26 10:28:31 +02:00
Ole André Vadla Ravnås 6b49e14ce1 subprojects: Bump outdated 2026-04-26 10:22:40 +02:00
Ole André Vadla Ravnås 7711fa12ac subprojects: Bump outdated 2026-04-26 10:16:43 +02:00
Ole André Vadla Ravnås d468dc038c submodules: Bump releng 2026-04-26 10:16:43 +02:00
Ole André Vadla Ravnås 58ff000596 subprojects: Bump outdated 2026-04-20 17:20:15 +02:00
Ole André Vadla Ravnås 3652e985d8 subprojects: Bump outdated 2026-04-20 17:16:46 +02:00
Ole André Vadla Ravnås 210c56e1b2 subprojects: Bump outdated 2026-04-20 16:31:57 +02:00
Ole André Vadla Ravnås b3d6aa8ffe submodules: Bump releng 2026-04-20 16:31:57 +02:00
Ole André Vadla Ravnås 16eb645844 subprojects: Bump outdated 2026-04-20 14:52:30 +02:00
Ole André Vadla Ravnås b7b1fcf0ff submodules: Bump releng 2026-04-20 14:52:30 +02:00
Ole André Vadla Ravnås 978b756134 subprojects: Bump outdated 2026-04-20 14:41:14 +02:00
Ole André Vadla Ravnås 7b1fc43a42 submodules: Bump releng 2026-04-20 14:41:14 +02:00
Ole André Vadla Ravnås 244cf6926b subprojects: Bump outdated 2026-04-13 14:36:51 +02:00
Ole André Vadla Ravnås 24b941fb8a submodules: Bump releng 2026-04-13 14:36:51 +02:00
Ole André Vadla Ravnås e2b340eda1 subprojects: Bump outdated 2026-04-10 23:55:22 +02:00
Ole André Vadla Ravnås 2b306c6d30 submodules: Bump releng 2026-04-10 23:55:22 +02:00
Ole André Vadla Ravnås 57cee13b43 subprojects: Bump outdated 2026-04-10 13:21:14 +02:00
Ole André Vadla Ravnås 23aa442826 subprojects: Bump outdated 2026-04-09 13:41:43 +02:00
Ole André Vadla Ravnås 65236f3b05 submodules: Bump releng 2026-04-09 13:41:43 +02:00
Ole André Vadla Ravnås e52e7a95bd subprojects: Bump outdated 2026-04-09 12:14:45 +02:00
Ole André Vadla Ravnås 3b57222cfa submodules: Bump releng 2026-04-09 12:14:45 +02:00
Ole André Vadla Ravnås 319ac9e699 subprojects: Bump outdated 2026-04-09 11:50:42 +02:00
Ole André Vadla Ravnås 2e842a4e44 submodules: Bump releng 2026-04-09 11:50:42 +02:00
Ole André Vadla Ravnås f9bf571497 subprojects: Bump outdated 2026-04-08 16:22:39 +02:00
Ole André Vadla Ravnås 8f50f743e1 submodules: Bump releng 2026-04-08 16:22:39 +02:00
Ole André Vadla Ravnås 6da337df6f subprojects: Bump outdated 2026-04-08 01:15:57 +02:00
Ole André Vadla Ravnås e29d41806c submodules: Bump releng 2026-04-08 01:15:57 +02:00
Ole André Vadla Ravnås 8faf01dc2e subprojects: Bump outdated 2026-04-06 15:37:09 +02:00
Ole André Vadla Ravnås 4286667bc6 submodules: Bump releng 2026-04-06 15:37:09 +02:00
Ole André Vadla Ravnås 86d4b4b4bf subprojects: Prepare for release 2026-03-27 11:36:40 +01:00
Ole André Vadla Ravnås b5808d7b27 subprojects: Bump outdated 2026-03-27 11:35:06 +01:00
Ole André Vadla Ravnås 5466050755 subprojects: Prepare for release 2026-03-26 20:43:48 +01:00
Ole André Vadla Ravnås 4db854a02c subprojects: Bump outdated 2026-03-26 20:42:58 +01:00
Ole André Vadla Ravnås cc9a78ef19 device: Add override_option()
Expose Device.override_option() for overriding backend-specific
options on a device.

Overrides apply when establishing a host session. If one already
exists, changes take effect on the next connection.
2026-03-26 20:30:10 +01:00
Ole André Vadla Ravnås 22ed1fac3c examples: Add a spawn gating example 2026-03-26 15:27:31 +01:00
Ole André Vadla Ravnås 9b15bde24e examples: Fix the child gating example 2026-03-25 21:04:14 +01:00
Ole André Vadla Ravnås a613d87b08 subprojects: Prepare for release 2026-03-24 15:40:55 +01:00
Ole André Vadla Ravnås 9ef240eff4 subprojects: Bump outdated 2026-03-24 15:39:30 +01:00
Ole André Vadla Ravnås 43a443f582 subprojects: Prepare for release 2026-03-14 11:52:06 +01:00
Ole André Vadla Ravnås dcc95216fd subprojects: Bump outdated 2026-03-14 11:51:33 +01:00
Ole André Vadla Ravnås 69e7c6c0ce subprojects: Prepare for release 2026-03-13 13:22:09 +01:00
Ole André Vadla Ravnås 3278173be8 subprojects: Bump outdated 2026-03-13 13:21:27 +01:00
Ole André Vadla Ravnås d8fa422062 subprojects: Bump outdated 2026-03-13 13:12:14 +01:00
Ole André Vadla Ravnås 3943d2eed2 subprojects: Bump outdated 2026-03-13 00:01:32 +01:00
Ole André Vadla Ravnås 8030b5aa68 submodules: Bump releng 2026-03-13 00:01:32 +01:00
Ole André Vadla Ravnås fa5f378176 subprojects: Prepare for release 2026-03-08 20:35:48 +01:00
Ole André Vadla Ravnås a814a4aa9b Revert "meson: Switch Python resolution strategy (#270)"
Turns out this breaks our CI. We should probably make this an option
instead.

This reverts commit 60a4a032d6.
2026-03-08 20:07:43 +01:00
Ole André Vadla Ravnås a48ff22b6f subprojects: Bump outdated 2026-03-08 19:44:15 +01:00
Ole André Vadla Ravnås eb4f8d37f3 subprojects: Bump outdated 2026-03-07 23:51:50 +01:00
James 60a4a032d6 meson: Switch Python resolution strategy (#270)
Use the `system` strategy, as .pc metadata is often wrong.
2026-03-07 14:49:23 +01:00
Ole André Vadla Ravnås 9f46dfaa80 subprojects: Bump outdated 2026-02-18 22:54:10 +01:00
Ole André Vadla Ravnås e57ca181f8 submodules: Bump releng 2026-02-18 22:54:10 +01:00
Ole André Vadla Ravnås 0fccd75bc7 subprojects: Prepare for release 2026-02-16 22:45:45 +01:00
Ole André Vadla Ravnås dea4ffb1c9 subprojects: Bump outdated 2026-02-16 22:08:19 +01:00
Ole André Vadla Ravnås da17d3badb subprojects: Prepare for release 2026-02-14 10:52:14 +01:00
Ole André Vadla Ravnås 33922241fc subprojects: Bump outdated 2026-02-14 10:50:37 +01:00
Ole André Vadla Ravnås fb8fdbb231 variant: Support cast to uint64 2026-02-14 10:49:06 +01:00
Ole André Vadla Ravnås 0f50b58a04 subprojects: Prepare for release 2026-02-13 22:48:36 +01:00
Ole André Vadla Ravnås f63ea5903f subprojects: Bump outdated 2026-02-13 22:47:43 +01:00
Ole André Vadla Ravnås a99767e501 subprojects: Prepare for release 2026-02-13 15:12:36 +01:00
Ole André Vadla Ravnås 07bbd3d488 subprojects: Bump outdated 2026-02-13 15:09:57 +01:00
Ole André Vadla Ravnås 81403cf383 subprojects: Bump outdated 2026-02-12 20:46:27 +01:00
Ole André Vadla Ravnås 2ea0b944a2 subprojects: Bump outdated 2026-02-12 13:27:14 +01:00
Ole André Vadla Ravnås 35c412c6cf subprojects: Bump outdated 2026-02-07 21:19:57 +01:00
Ole André Vadla Ravnås ec4524d155 submodules: Bump releng 2026-02-07 21:19:57 +01:00
Ole André Vadla Ravnås d9d53a89cc service: Fix Service.request() GVariant lifetime
The variant unmarshal helpers returned floating GVariants. When these
values were stored in containers, the floating reference was consumed.

Service.request() later unref'd the same GVariant, leading to refcount
underflow and memory corruption, or appearing to work by accident when
the memory was not yet reused.

Ensure all unmarshalled GVariants are non-floating before returning them
so that Service.request() always owns a full reference.
2026-01-29 14:59:10 +01:00
Ole André Vadla Ravnås dfb002de49 variant: Fix signedness in uint64 marshaling 2026-01-29 12:46:38 +01:00
Ole André Vadla Ravnås 0f56e44abd variant: Add support for additional GVariant types
This adds support for:
- Additional integer types: byte, int16, uint16, int32, uint32
- Tuples
- Nested variants in any context
2026-01-29 12:45:54 +01:00
Ole André Vadla Ravnås d28a2a4988 subprojects: Prepare for release 2026-01-23 22:31:27 +01:00
Ole André Vadla Ravnås 1fc39289ef subprojects: Bump outdated 2026-01-23 22:30:37 +01:00
Ole André Vadla Ravnås 2c757a08f3 subprojects: Prepare for release 2026-01-20 23:12:51 +01:00
Ole André Vadla Ravnås fc735c048f subprojects: Bump outdated 2026-01-20 23:09:30 +01:00
Ole André Vadla Ravnås 4d84536160 subprojects: Bump outdated 2026-01-20 20:26:47 +01:00
Ole André Vadla Ravnås 9e51c78c5c submodules: Bump releng 2026-01-20 20:26:47 +01:00
Ole André Vadla Ravnås a6d51e8384 subprojects: Bump outdated 2026-01-20 18:54:35 +01:00
Ole André Vadla Ravnås 3e80ed726d submodules: Bump releng 2026-01-20 18:54:35 +01:00
36 changed files with 9220 additions and 5660 deletions
+5 -7
View File
@@ -42,18 +42,16 @@ class Application:
print("✔ enable_child_gating()")
session.enable_child_gating()
print("✔ create_script()")
script = session.create_script(
"""\
Interceptor.attach(Module.getExportByName(null, 'open'), {
onEnter: function (args) {
script = session.create_script("""\
Interceptor.attach(Module.getGlobalExportByName('open'), {
onEnter(args) {
send({
type: 'open',
path: Memory.readUtf8String(args[0])
path: args[0].readUtf8String()
});
}
});
"""
)
""")
script.on("message", lambda message, data: self._reactor.schedule(lambda: self._on_message(pid, message)))
print("✔ load()")
script.load()
+1 -1
View File
@@ -15,7 +15,7 @@ def on_uninjected(id):
print("on_uninjected id=%u" % id)
(target, library_path) = sys.argv[1:]
target, library_path = sys.argv[1:]
device = frida.get_local_device()
device.on("uninjected", on_uninjected)
+1 -1
View File
@@ -15,7 +15,7 @@ def on_uninjected(id):
print("on_uninjected id=%u" % id)
(target, library_path) = sys.argv[1:]
target, library_path = sys.argv[1:]
device = frida.get_local_device()
device.on("uninjected", on_uninjected)
+2 -4
View File
@@ -1,8 +1,7 @@
import frida
session = frida.attach("Twitter")
script = session.create_script(
"""\
script = session.create_script("""\
rpc.exports = {
hello: function () {
return 'Hello';
@@ -11,8 +10,7 @@ rpc.exports = {
oops;
}
};
"""
)
""")
script.load()
api = script.exports_sync
print("api.hello() =>", api.hello())
+2 -4
View File
@@ -22,8 +22,7 @@ class Application:
self._session = session
session.on("detached", lambda *args: self._reactor.schedule(lambda: self._on_detached(*args)))
script = session.create_script(
"""
script = session.create_script("""
let _puts = null;
Interceptor.attach(DebugSymbol.getFunctionByName('f'), {
@@ -48,8 +47,7 @@ function puts(s) {
}
_puts(Memory.allocUtf8String(s));
}
"""
)
""")
self._script = script
script.on("message", lambda *args: self._reactor.schedule(lambda: self._on_message(*args)))
script.load()
+78
View File
@@ -0,0 +1,78 @@
import threading
from frida_tools.application import Reactor
import frida
class Application:
def __init__(self):
self._stop_requested = threading.Event()
self._reactor = Reactor(run_until_return=lambda reactor: self._stop_requested.wait())
self._device = frida.get_usb_device()
self._sessions = set()
self._sessions_lock = threading.Lock()
self._device.on("spawn-added", lambda spawn: self._reactor.schedule(lambda: self._on_spawn_added(spawn)))
self._device.on("spawn-removed", lambda spawn: self._reactor.schedule(lambda: self._on_spawn_removed(spawn)))
def run(self):
self._reactor.schedule(lambda: self._start())
self._reactor.run()
def _start(self):
self._device.enable_spawn_gating()
def _instrument(self, pid):
print(f"✔ attach(pid={pid})")
session = self._device.attach(pid)
session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason)))
print("✔ create_script()")
script = session.create_script("""\
const puts = new NativeFunction(Module.getGlobalExportByName('puts'), 'int', ['pointer']);
puts(Memory.allocUtf8String('Hello from Frida agent'));
Interceptor.attach(Module.getGlobalExportByName('open'), {
onEnter(args) {
send({
type: 'open',
path: args[0].readUtf8String()
});
}
});
""")
script.on("message", lambda message, data: self._reactor.schedule(lambda: self._on_message(pid, message)))
print("✔ load()")
script.load()
print(f"✔ resume(pid={pid})")
self._device.resume(pid)
with self._sessions_lock:
self._sessions.add(session)
def _on_spawn_added(self, spawn):
print(f"⚡ spawn_added: {spawn}")
t = threading.Thread(target=self._handle_spawn, args=(spawn,))
t.start()
def _handle_spawn(self, spawn):
if "/bin/ls" in spawn.identifier:
self._instrument(spawn.pid)
else:
pid = spawn.pid
print(f"✔ resume(pid={pid})")
self._device.resume(pid)
def _on_spawn_removed(self, spawn):
print(f"⚡ spawn_removed: {spawn}")
def _on_detached(self, pid, session, reason):
print(f"⚡ detached: pid={pid}, reason='{reason}'")
with self._sessions_lock:
self._sessions.remove(session)
def _on_message(self, pid, message):
print(f"⚡ message: pid={pid}, payload={message['payload']}")
app = Application()
app.run()
+173
View File
@@ -0,0 +1,173 @@
from typing import Any, Callable, Dict, List, Optional, 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
) -> 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)
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()
+877
View File
@@ -0,0 +1,877 @@
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) -> "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 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 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 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
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
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',
)
+1830
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -1,4 +0,0 @@
from .cli import main
if __name__ == "__main__":
main()
@@ -1 +0,0 @@
static GHashTable * pygobject_type_spec_by_type;
@@ -1,77 +0,0 @@
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_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 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);
}
@@ -1,5 +0,0 @@
static void PyGObject_class_init (void);
static int PyGObject_init (PyGObject * self);
static void PyGObject_dealloc (PyGObject * self);
static gpointer PyGObject_steal_handle (PyGObject * self);
static void PyGObject_register_type (GType instance_type, PyGObjectType * python_type);
@@ -1,100 +0,0 @@
type SignalTransformer<
Source extends SignalHandler,
Target extends SignalHandler
> = (...args: Parameters<Source>) => Parameters<Target>;
type SignalInterceptor<H extends SignalHandler> = (...args: Parameters<H>) => boolean;
interface SignalWrapperOptionsNoTransform<H extends SignalHandler> {
transform?: undefined;
intercept?: SignalInterceptor<H>;
}
interface SignalWrapperOptionsTransform<
Source extends SignalHandler,
Target extends SignalHandler
> {
transform: SignalTransformer<Source, Target>;
intercept?: SignalInterceptor<Target>;
}
type SignalWrapperOptions<
Source extends SignalHandler,
Target extends SignalHandler
> =
| SignalWrapperOptionsNoTransform<Source & Target>
| SignalWrapperOptionsTransform<Source, Target>;
class SignalWrapper<
SourceHandler extends SignalHandler,
TargetHandler extends SignalHandler
> {
#source: Signal<SourceHandler>;
#transform?: SignalTransformer<SourceHandler, TargetHandler>;
#intercept?: SignalInterceptor<any>;
#handlers = new Set<TargetHandler>();
constructor(
source: Signal<SourceHandler>,
options?: SignalWrapperOptions<SourceHandler, TargetHandler>
) {
this.#source = source;
if (options === undefined || options.transform === undefined) {
this.#intercept = options?.intercept;
} else {
this.#transform = options.transform;
this.#intercept = options.intercept;
}
}
connect(handler: TargetHandler): void {
this.#handlers.add(handler);
if (this.#handlers.size === 1) {
this.#source.connect(this.#wrappedHandler);
}
}
disconnect(handler: TargetHandler): void {
this.#handlers.delete(handler);
if (this.#handlers.size === 0) {
this.#source.disconnect(this.#wrappedHandler);
}
}
#wrappedHandler = ((...sourceArgs: Parameters<SourceHandler>) => {
let targetArgs: Parameters<TargetHandler>;
const transform = this.#transform;
if (transform === undefined) {
targetArgs = sourceArgs as unknown as Parameters<TargetHandler>;
} else {
targetArgs = transform(...sourceArgs);
}
const intercept = this.#intercept;
if (intercept !== undefined) {
if (!intercept(...targetArgs)) {
return;
}
}
for (const handler of this.#handlers) {
handler(...targetArgs);
}
}) as SourceHandler;
}
function inspectWrapper(object: any, name: string, properties: string[], depth: number, options: util.InspectOptionsStylized): string {
if (depth < 0) {
return options.stylize(`[${name}]`, "special");
}
const summary = Object.fromEntries(properties.map(name => [name, object[name]]));
const nextOptions = Object.assign({}, options, {
depth: (options.depth === null) ? null : depth - 1
});
return name + " " + inspect(summary, nextOptions);
}
@@ -1,60 +0,0 @@
#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, ...) \
_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, \
}
#define PYFRIDA_DEFINE_TYPE(pyname, cname, parent_cname, ...) \
_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, \
}
#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
@@ -1,78 +0,0 @@
G_GNUC_UNUSED static gboolean fdn_is_null (napi_env env, napi_value value);
static gboolean fdn_is_undefined_or_null (napi_env env, napi_value value);
static gboolean fdn_is_function (napi_env env, napi_value value);
static gboolean fdn_boolean_from_value (napi_env env, napi_value value, gboolean * b);
static napi_value fdn_boolean_to_value (napi_env env, gboolean b);
static gboolean fdn_size_from_value (napi_env env, napi_value value, gsize * s);
static napi_value fdn_size_to_value (napi_env env, gsize s);
static napi_value fdn_ssize_to_value (napi_env env, gssize s);
static gboolean fdn_int_from_value (napi_env env, napi_value value, gint * i);
static napi_value fdn_int_to_value (napi_env env, gint i);
static gboolean fdn_uint_from_value (napi_env env, napi_value value, guint * u);
static napi_value fdn_uint_to_value (napi_env env, guint u);
static gboolean fdn_uint16_from_value (napi_env env, napi_value value, guint16 * u);
static napi_value fdn_uint16_to_value (napi_env env, guint16 u);
static napi_value fdn_uint32_to_value (napi_env env, guint32 u);
static gboolean fdn_int64_from_value (napi_env env, napi_value value, gint64 * i);
static napi_value fdn_int64_to_value (napi_env env, gint64 i);
static napi_value fdn_uint64_to_value (napi_env env, guint64 u);
static gboolean fdn_ulong_from_value (napi_env env, napi_value value, gulong * u);
static gboolean fdn_double_from_value (napi_env env, napi_value value, gdouble * d);
static napi_value fdn_double_to_value (napi_env env, gdouble d);
static gboolean fdn_enum_from_value (napi_env env, GType enum_type, napi_value value, gint * e);
static napi_value fdn_enum_to_value (napi_env env, GType enum_type, gint e);
static gboolean fdn_utf8_from_value (napi_env env, napi_value value, gchar ** str);
static napi_value fdn_utf8_to_value (napi_env env, const gchar * str);
static gboolean fdn_strv_from_value (napi_env env, napi_value value, gchar *** strv);
static napi_value fdn_strv_to_value (napi_env env, gchar ** strv);
static napi_value fdn_buffer_to_value (napi_env env, const guint8 * data, gsize size);
static gboolean fdn_bytes_from_value (napi_env env, napi_value value, GBytes ** bytes);
static napi_value fdn_bytes_to_value (napi_env env, GBytes * bytes);
static gboolean fdn_vardict_from_value (napi_env env, napi_value value, GHashTable ** vardict);
static napi_value fdn_vardict_to_value (napi_env env, GHashTable * vardict);
static gboolean fdn_variant_from_value (napi_env env, napi_value value, GVariant ** variant);
static napi_value fdn_variant_to_value (napi_env env, GVariant * variant);
static gboolean fdn_gvalue_from_value (napi_env env, GType type, napi_value js_value, GValue * value);
static napi_value fdn_gvalue_to_value (napi_env env, GValue * value);
static gboolean fdn_error_from_value (napi_env env, napi_value value, GError ** error);
static napi_value fdn_error_to_value (napi_env env, GError * error);
static gboolean fdn_file_from_value (napi_env env, napi_value value, GFile ** file);
static napi_value fdn_file_to_value (napi_env env, GFile * file);
static gboolean fdn_tls_certificate_from_value (napi_env env, napi_value value, GTlsCertificate ** certificate);
static napi_value fdn_tls_certificate_to_value (napi_env env, GTlsCertificate * certificate);
static gboolean fdn_options_from_value (napi_env env, GType object_type, napi_value value, gpointer * options);
static napi_value fdn_object_subclass_to_value (napi_env env, GObject * object);
static napi_value fdn_object_new (napi_env env, GObject * handle, napi_ref constructor);
static gboolean fdn_object_wrap (napi_env env, napi_value wrapper, GObject * handle, napi_finalize finalizer);
static gboolean fdn_object_unwrap (napi_env env, napi_value wrapper, GType expected_type, GObject ** handle);
static void fdn_object_finalize (napi_env env, void * finalize_data, void * finalize_hint);
static napi_value fdn_object_get_signal (napi_env env, napi_callback_info info, const gchar * name, const gchar * js_storage_name, FdnSignalBehavior behavior);
static napi_value fdn_signal_new (napi_env env, GObject * handle, const gchar * name, FdnSignalBehavior behavior);
static void fdn_signal_register (napi_env env, napi_value exports);
static napi_value fdn_signal_construct (napi_env env, napi_callback_info info);
static void fdn_signal_finalize (napi_env env, void * finalize_data, void * finalize_hint);
static napi_value fdn_signal_connect (napi_env env, napi_callback_info info);
static napi_value fdn_signal_disconnect (napi_env env, napi_callback_info info);
static void fdn_signal_disconnect_closure (FdnSignal * self, FdnSignalClosure * closure);
static gboolean fdn_signal_parse_arguments (napi_env env, napi_callback_info info, FdnSignal ** self, napi_value * js_self, napi_value * handler);
static FdnSignalClosure * fdn_signal_closure_new (napi_env env, FdnSignal * sig, napi_value js_sig, napi_value handler);
static void fdn_signal_closure_finalize (gpointer data, GClosure * closure);
static void fdn_signal_closure_marshal (GClosure * closure, GValue * return_gvalue, guint n_param_values, const GValue * param_values, gpointer invocation_hint, gpointer marshal_data);
static void fdn_signal_closure_deliver (napi_env env, napi_value js_cb, void * context, void * data);
static void fdn_keep_alive_until (napi_env env, napi_value js_object, GObject * handle, FdnIsDestroyedFunc is_destroyed, const gchar * destroy_signal_name);
static void fdn_keep_alive_on_finalize (napi_env env, void * finalize_data, void * finalize_hint);
static void fdn_keep_alive_on_destroy_signal (GObject * handle, gpointer user_data);
static void fdn_keep_alive_on_destroy_signal_handler_detached (gpointer data, GClosure * closure);
static void fdn_keep_alive_schedule_cleanup (FdnKeepAliveContext * context);
static void fdn_keep_alive_on_tsfn_invoke (napi_env env, napi_value js_cb, void * context, void * data);
static void fdn_inherit_val_val (napi_env env, napi_value sub_ctor, napi_value super_ctor, napi_value object_ctor, napi_value set_proto);
G_GNUC_UNUSED static void fdn_inherit_val_ref (napi_env env, napi_value sub_ctor, napi_ref super_ctor, napi_value object_ctor, napi_value set_proto);
static void fdn_inherit_ref_val (napi_env env, napi_ref sub_ctor, napi_value super_ctor, napi_value object_ctor, napi_value set_proto);
static void fdn_inherit_ref_ref (napi_env env, napi_ref sub_ctor, napi_ref super_ctor, napi_value object_ctor, napi_value set_proto);
static gchar * fdn_camel_case_to_kebab_case (const gchar * name);
@@ -1,24 +0,0 @@
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;
};
@@ -1,4 +0,0 @@
typedef struct _PyGObject PyGObject;
typedef void (* PyGObjectInitFromHandleFunc) (PyObject * self, gpointer handle);
typedef struct _PyGObjectType PyGObjectType;
typedef struct _PyGObjectSignalClosure PyGObjectSignalClosure;
@@ -1,13 +0,0 @@
querySystemParameters
spawn
resume
kill
attach
injectLibraryFile
injectLibraryBlob
enumerateDevices
getDeviceManager
getLocalDevice
getRemoteDevice
getUsbDevice
getDevice
@@ -1,157 +0,0 @@
let sharedDeviceManager: DeviceManager | null = null;
export async function querySystemParameters(cancellable?: Cancellable | null): Promise<SystemParameters> {
const device = await getLocalDevice(cancellable);
return await device.querySystemParameters(cancellable);
}
export async function spawn(program: string | string[], options?: SpawnOptions, cancellable?: Cancellable | null): Promise<number> {
const device = await getLocalDevice(cancellable);
return await device.spawn(program, options, cancellable);
}
export async function resume(target: TargetProcess, cancellable?: Cancellable | null): Promise<void> {
const device = await getLocalDevice(cancellable);
await device.resume(target, cancellable);
}
export async function kill(target: TargetProcess, cancellable?: Cancellable | null): Promise<void> {
const device = await getLocalDevice(cancellable);
await device.kill(target, cancellable);
}
export async function attach(target: TargetProcess, options?: SessionOptions, cancellable?: Cancellable | null): Promise<Session> {
const device = await getLocalDevice(cancellable);
return await device.attach(target, options, cancellable);
}
export async function injectLibraryFile(target: TargetProcess, path: string, entrypoint: string, data: string,
cancellable?: Cancellable | null): Promise<number> {
const device = await getLocalDevice(cancellable);
return await device.injectLibraryFile(target, path, entrypoint, data, cancellable);
}
export async function injectLibraryBlob(target: TargetProcess, blob: Buffer, entrypoint: string, data: string,
cancellable?: Cancellable | null): Promise<number> {
const device = await getLocalDevice(cancellable);
return await device.injectLibraryBlob(target, blob, entrypoint, data, cancellable);
}
export async function enumerateDevices(cancellable?: Cancellable | null): Promise<Device[]> {
const deviceManager = getDeviceManager();
return await deviceManager.enumerateDevices(cancellable);
};
export function getDeviceManager(): DeviceManager {
if (sharedDeviceManager === null) {
sharedDeviceManager = new DeviceManager();
}
return sharedDeviceManager;
}
export function getLocalDevice(cancellable?: Cancellable | null): Promise<Device> {
return getMatchingDevice(device => device.type === DeviceType.Local, {}, cancellable);
}
export function getRemoteDevice(cancellable?: Cancellable | null): Promise<Device> {
return getMatchingDevice(device => device.type === DeviceType.Remote, {}, cancellable);
}
export function getUsbDevice(options?: GetDeviceOptions, cancellable?: Cancellable | null): Promise<Device> {
return getMatchingDevice(device => device.type === DeviceType.Usb, options, cancellable);
}
export function getDevice(id: string, options?: GetDeviceOptions, cancellable?: Cancellable | null): Promise<Device> {
return getMatchingDevice(device => device.id === id, options, cancellable);
}
export interface GetDeviceOptions {
timeout?: number | null;
}
async function getMatchingDevice(predicate: DevicePredicate, options: GetDeviceOptions = {}, cancellable: Cancellable | null = null): Promise<Device> {
const device = await findMatchingDevice(predicate, cancellable);
if (device !== null) {
return device;
}
const { timeout = 0 } = options;
if (timeout === 0) {
throw new Error("Device not found");
}
const getDeviceEventually = new Promise((resolve: (device: Device) => void, reject: (error: Error) => void) => {
const deviceManager = getDeviceManager();
deviceManager.added.connect(onDeviceAdded);
const timer = (timeout !== null) ? setTimeout(onTimeout, timeout) : null;
if (cancellable !== null) {
cancellable.cancelled.connect(onCancel);
if (cancellable.isCancelled) {
onCancel();
return;
}
}
findMatchingDevice(predicate, cancellable)
.then(device => {
if (device !== null) {
onSuccess(device);
}
})
.catch(onError);
function onDeviceAdded(device: Device): void {
if (predicate(device)) {
onSuccess(device);
}
}
function onSuccess(device: Device): void {
stopMonitoring();
resolve(device);
}
function onError(error: Error): void {
stopMonitoring();
reject(error);
}
function onTimeout(): void {
onError(new Error("Timed out while waiting for device to appear"));
}
function onCancel(): void {
onError(new Error("Operation was cancelled"));
}
function stopMonitoring(): void {
cancellable?.cancelled.disconnect(onCancel);
if (timer !== null) {
clearTimeout(timer);
}
deviceManager.added.disconnect(onDeviceAdded);
}
});
return await getDeviceEventually;
}
async function findMatchingDevice(predicate: DevicePredicate, cancellable?: Cancellable | null): Promise<Device | null> {
const deviceManager = getDeviceManager();
const devices = await deviceManager.enumerateDevices(cancellable);
const matching = devices.filter(predicate);
if (matching.length === 0) {
return null;
}
return matching[0];
}
type DevicePredicate = (device: Device) => boolean;
@@ -1,2 +0,0 @@
import { Minimatch } from "minimatch";
import { Duplex } from "stream";
@@ -1,396 +0,0 @@
const STANDARD_SPAWN_OPTION_NAMES = new Set<keyof SpawnOptions>([
"argv",
"envp",
"env",
"cwd",
"stdio",
]);
interface LogMessage {
type: "log";
level: LogLevel;
payload: string;
}
class ScriptServices implements RpcController {
exportsProxy = new ScriptExportsProxy(this);
#script: Script;
#pendingRequests = new Map<number, (error: Error | null, result?: any) => void>();
#nextRequestId = 1;
constructor(script: Script) {
this.#script = script;
process.nextTick(() => {
script.message.connect(() => {});
});
}
handleMessageIntercept = (message: Message, data: Buffer | null): boolean => {
if (message.type === MessageType.Send && isRpcSendMessage(message)) {
const [ , id, operation, ...params ] = message.payload;
this.#onRpcMessage(id, operation, params, data);
return false;
} else if (isLogMessage(message)) {
const opaqueMessage: any = message;
const logMessage: LogMessage = opaqueMessage;
this.#script.logHandler(logMessage.level, logMessage.payload);
return false;
}
return true;
};
request(operation: string, params: any[], data: Buffer | null, cancellable: Cancellable | null = null): Promise<any> {
return new Promise((resolve, reject) => {
const id = this.#nextRequestId++;
const complete = (error: Error | null, result?: any): void => {
if (cancellable !== null) {
cancellable.cancelled.disconnect(onOperationCancelled);
}
this.#script.destroyed.disconnect(onScriptDestroyed);
this.#pendingRequests.delete(id);
if (error === null) {
resolve(result);
} else {
reject(error);
}
};
function onScriptDestroyed(): void {
complete(new Error("Script is destroyed"));
}
function onOperationCancelled(): void {
complete(new Error("Operation was cancelled"));
}
this.#pendingRequests.set(id, complete);
this.#script.post(["frida:rpc", id, operation, ...params], data);
this.#script.destroyed.connect(onScriptDestroyed);
if (cancellable !== null) {
cancellable.cancelled.connect(onOperationCancelled);
if (cancellable.isCancelled) {
onOperationCancelled();
return;
}
}
if (this.#script.isDestroyed) {
onScriptDestroyed();
}
});
}
#onRpcMessage(id: number, operation: RpcOperation, params: any[], data: Buffer | null) {
if (operation === RpcOperation.Ok || operation === RpcOperation.Error) {
const callback = this.#pendingRequests.get(id);
if (callback === undefined) {
return;
}
let value = null;
let error = null;
if (operation === RpcOperation.Ok) {
if (data !== null) {
value = (params.length > 1) ? [params[1], data] : data;
} else {
value = params[0];
}
} else {
const [message, name, stack, rawErr] = params;
error = new Error(message);
error.name = name;
error.stack = stack;
Object.assign(error, rawErr);
}
callback(error, value);
}
}
}
class ScriptExportsProxy implements ScriptExports {
[name: string]: (...args: any[]) => Promise<any>;
constructor(rpcController: RpcController) {
return new Proxy(this, {
has(target, property) {
return !isReservedMethodName(property);
},
get(target, property, receiver) {
if (typeof property === "symbol") {
if (property === inspect.custom) {
return inspectProxy;
}
return undefined;
}
if (property in target) {
return target[property];
}
if (isReservedMethodName(property)) {
return undefined;
}
return (...args: any[]): Promise<any> => {
let cancellable: Cancellable | null = null;
if (args[args.length - 1] instanceof Cancellable) {
cancellable = args.pop();
}
let data: Buffer | null = null;
if (Buffer.isBuffer(args[args.length - 1])) {
data = args.pop();
}
return rpcController.request("call", [property, args], data, cancellable);
};
},
set(target, property, value, receiver) {
if (typeof property === "symbol") {
return false;
}
target[property] = value;
return true;
},
ownKeys(target) {
return Object.getOwnPropertyNames(target);
},
getOwnPropertyDescriptor(target, property) {
if (property in target) {
return Object.getOwnPropertyDescriptor(target, property);
}
if (isReservedMethodName(property)) {
return undefined;
}
return {
writable: true,
configurable: true,
enumerable: true
};
},
});
}
}
function inspectProxy() {
return "ScriptExportsProxy {}";
}
interface RpcController {
request(operation: string, params: any[], data: ArrayBuffer | null, cancellable: Cancellable | null): Promise<any>;
}
enum RpcOperation {
Ok = "ok",
Error = "error"
}
function isInternalMessage(message: Message): boolean {
return isRpcMessage(message) || isLogMessage(message);
}
function isRpcMessage(message: Message): boolean {
return message.type === MessageType.Send && isRpcSendMessage(message);
}
function isRpcSendMessage(message: SendMessage): boolean {
const payload = message.payload;
if (!Array.isArray(payload)) {
return false;
}
return payload[0] === "frida:rpc";
}
function isLogMessage(message: Message): boolean {
return message.type as string === "log";
}
function log(level: LogLevel, text: string): void {
switch (level) {
case LogLevel.Info:
console.log(text);
break;
case LogLevel.Warning:
console.warn(text);
break;
case LogLevel.Error:
console.error(text);
break;
}
}
const reservedMethodNames = new Set<string>([
"then",
"catch",
"finally",
]);
function isReservedMethodName(name: string | number | symbol): boolean {
return reservedMethodNames.has(name.toString());
}
const IO_PRIORITY_DEFAULT = 0;
class IOStreamAdapter extends Duplex {
#impl: IOStream;
#input: InputStream;
#output: OutputStream;
#pending = new Set<Promise<void>>();
#cancellable = new Cancellable();
constructor(impl: IOStream) {
super({});
this.#impl = impl;
this.#input = impl.inputStream;
this.#output = impl.outputStream;
}
async _destroy(error: Error | null, callback: (error: Error | null) => void): Promise<void> {
this.#cancellable.cancel();
for (const operation of this.#pending) {
try {
await operation;
} catch (e) {
}
}
try {
await this.#impl.close(IO_PRIORITY_DEFAULT);
} catch (e) {
}
callback(error);
}
_read(size: number): void {
const operation = this.#input.read(size, IO_PRIORITY_DEFAULT, this.#cancellable)
.then((data: Buffer): void => {
const isEof = data.length === 0;
if (isEof) {
this.push(null);
return;
}
this.push(data);
})
.catch((error: Error): void => {
if (this.#impl.closed) {
this.push(null);
}
this.emit("error", error);
});
this.#track(operation);
}
_write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void): void {
let data: Buffer;
if (Buffer.isBuffer(chunk)) {
data = chunk;
} else {
data = Buffer.from(chunk, encoding);
}
const operation = this.#writeAll(data)
.then((): void => {
callback(null);
})
.catch((error: Error): void => {
callback(error);
});
this.#track(operation);
}
async #writeAll(data: Buffer): Promise<void> {
let offset = 0;
do {
const n = await this.#output.write(data.slice(offset), IO_PRIORITY_DEFAULT, this.#cancellable);
offset += n;
} while (offset !== data.length);
}
#track(operation: Promise<void>): void {
this.#pending.add(operation);
operation
.catch(_ => {})
.finally(() => {
this.#pending.delete(operation);
});
}
}
class CallbackAuthenticationService extends binding.AbstractAuthenticationService {
#callback: AuthenticationCallback;
constructor(callback: AuthenticationCallback) {
super();
this.#callback = callback;
}
async authenticate(token: string, cancellable: Cancellable | null): Promise<string> {
const info = await this.#callback(token);
return JSON.stringify(info);
}
}
function parseSocketAddress(address: BaseSocketAddress): SocketAddress {
const family = address.family;
switch (family) {
case SocketFamily.Unix: {
const addr = address as UnixSocketAddress;
switch (addr.addressType) {
case UnixSocketAddressType.Anonymous:
return {
family: "unix:anonymous",
};
case UnixSocketAddressType.Path:
return {
family: "unix:path",
path: addr.path.toString(),
};
case UnixSocketAddressType.Abstract:
case UnixSocketAddressType.AbstractPadded:
return {
family: "unix:abstract",
path: addr.path,
};
}
break;
}
case SocketFamily.Ipv4: {
const addr = address as InetSocketAddress;
return {
family: "ipv4",
address: addr.address.toString(),
port: addr.port,
};
}
case SocketFamily.Ipv6: {
const addr = address as InetSocketAddress;
return {
family: "ipv6",
address: addr.address.toString(),
port: addr.port,
flowlabel: addr.flowinfo,
scopeid: addr.scopeId,
};
}
}
throw new Error("invalid BaseSocketAddress");
}
function objectToStrv(object: { [name: string]: string }): string[] {
return Object.entries(object).map(([k, v]) => `${k}=${v}`);
}
-96
View File
@@ -1,96 +0,0 @@
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 TypeScript and Node-API bindings for Frida."
)
p.add_argument(
"--frida-gir",
required=True,
type=Path,
help="Path to the Frida .gir file.",
)
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-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
)
artefacts = codegen.generate_all(model)
with OutputFile(args.output_py) as f:
f.write(artefacts["py"])
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
File diff suppressed because it is too large Load Diff
-924
View File
@@ -1,924 +0,0 @@
from __future__ import annotations
from pathlib import Path
from typing import List, Mapping
from .model import (ConstructorCustomizations, CustomCode, CustomDeclaration,
Customizations, CustomMethod, CustomType, CustomTypeKind,
EnumerationCustomizations, EnumerationMemberCustomizations,
KeepAliveCustomization, MethodCustomizations,
ObjectTypeCustomizations, PropertyCustomizations,
SignalCustomizations, TypeCustomizations)
ASSETS_DIR = Path(__file__).resolve().parent / "assets"
CUSTOMIZATION_FACADE_EXPORTS = (
(ASSETS_DIR / "customization_facade.exports")
.read_text(encoding="utf-8")
.strip()
.split("\n")
)
CUSTOMIZATION_FACADE_TS = (ASSETS_DIR / "customization_facade.ts").read_text(
encoding="utf-8"
)
CUSTOMIZATION_HELPERS_IMPORTS = (
(ASSETS_DIR / "customization_helpers.imports")
.read_text(encoding="utf-8")
.strip()
.split("\n")
)
CUSTOMIZATION_HELPERS_TS = (ASSETS_DIR / "customization_helpers.ts").read_text(
encoding="utf-8"
)
def load_customizations() -> Customizations:
custom_types: List[CustomType] = {
"TargetProcess": CustomType(CustomTypeKind.TYPE, "ProcessID | ProcessName"),
"ProcessID": CustomType(CustomTypeKind.TYPE, "number"),
"InjecteeID": CustomType(CustomTypeKind.TYPE, "number"),
"FileDescriptor": CustomType(CustomTypeKind.TYPE, "number"),
"ProcessName": CustomType(CustomTypeKind.TYPE, "string"),
"SystemParameters": CustomType(
CustomTypeKind.INTERFACE,
"""
/**
* Operating System details.
*/
os: {
/**
* ID, e.g.: windows, macos, linux, ios, android, qnx, fedora, ubuntu, etc.
*/
id: string;
/**
* Human-readable name, e.g. `"macOS"`.
*/
name: string;
/**
* Human-readable version string, e.g. `"11.2.2"`.
*/
version?: string;
/**
* Build version, e.g. `"21B91"`.
*/
build?: string;
}
/**
* Platform, same as `Process.platform` in GumJS.
*/
platform: "windows" | "darwin" | "linux" | "freebsd" | "qnx";
/**
* Architecture, same as `Process.arch` in GumJS.
*/
arch: "ia32" | "x64" | "arm" | "arm64" | "mips";
/**
* Hardware details.
*/
hardware?: {
/**
* Product type, e.g. `"iPad6,3"`.
*/
product?: string;
/**
* Hardware platform, e.g. `"t8010"`.
*/
platform?: string;
/**
* Hardware model, e.g. `"J71bAP"`.
*/
model?: string;
}
/**
* Level of access.
*/
access: "full" | "jailed";
/**
* System name, e.g. `"Ole André’s iPhone"`.
*/
name?: string;
/**
* iOS UDID (Unique Device ID).
*/
udid?: string;
/**
* Details about cellular and networking interfaces.
*/
interfaces?: SystemInterface[];
/**
* Android API level, e.g.: `30`.
*/
apiLevel?: number;
[name: string]: any;
""",
),
"SystemInterface": CustomType(
CustomTypeKind.TYPE, "NetworkInterface | CellularInterface"
),
"NetworkInterface": CustomType(
CustomTypeKind.INTERFACE,
"""
type: "ethernet" | "wifi" | "bluetooth";
/**
* MAC address, e.g.: `"aa:bb:cc:dd:ee:ff"`.
*/
address: string;
""",
),
"CellularInterface": CustomType(
CustomTypeKind.INTERFACE,
"""
type: "cellular";
/**
* Phone number, e.g. `"+47 123 45 678"`.
*/
phoneNumber: string;
""",
),
"SpawnOptions": CustomType(
CustomTypeKind.INTERFACE,
"""
argv?: string[];
envp?: { [name: string]: string };
env?: { [name: string]: string };
cwd?: string;
stdio?: Stdio;
[name: string]: any;
""",
),
"RelayProperties": CustomType(
CustomTypeKind.INTERFACE,
"""
address: string;
username: string;
password: string;
kind: RelayKind;
""",
),
"Message": CustomType(CustomTypeKind.TYPE, "SendMessage | ErrorMessage"),
"MessageType": CustomType(
CustomTypeKind.ENUM,
"""
Send = "send",
Error = "error"
""",
),
"SendMessage": CustomType(
CustomTypeKind.INTERFACE,
"""
type: MessageType.Send;
payload: any;
""",
),
"ErrorMessage": CustomType(
CustomTypeKind.INTERFACE,
"""
type: MessageType.Error;
description: string;
stack?: string;
fileName?: string;
lineNumber?: number;
columnNumber?: number;
""",
),
"ScriptLogHandler": CustomType(
CustomTypeKind.TYPE, "(level: LogLevel, text: string) => void"
),
"ScriptExports": CustomType(
CustomTypeKind.INTERFACE,
"""
[name: string]: (...args: any[]) => Promise<any>;
""",
),
"LogLevel": CustomType(
CustomTypeKind.ENUM,
"""
Info = "info",
Warning = "warning",
Error = "error",
""",
),
"EnableDebuggerOptions": CustomType(
CustomTypeKind.INTERFACE,
"""
port?: number;
""",
),
"PortalServiceOptions": CustomType(
CustomTypeKind.INTERFACE,
"""
clusterParams?: EndpointParameters;
controlParams?: EndpointParameters;
""",
),
"PortalConnectionId": CustomType(CustomTypeKind.TYPE, "number"),
"PortalConnectionTag": CustomType(CustomTypeKind.TYPE, "string"),
"EndpointParametersSubset": CustomType(
CustomTypeKind.INTERFACE,
"""
address?: string;
port?: number;
certificate?: string;
origin?: string;
authentication?: AuthenticationScheme;
assetRoot?: string;
""",
),
"AuthenticationScheme": CustomType(
CustomTypeKind.TYPE,
"TokenAuthenticationScheme | CallbackAuthenticationScheme",
),
"TokenAuthenticationScheme": CustomType(
CustomTypeKind.INTERFACE,
"""
scheme: "token";
token: string;
""",
),
"CallbackAuthenticationScheme": CustomType(
CustomTypeKind.INTERFACE,
"""
scheme: "callback";
callback: AuthenticationCallback;
""",
),
"AuthenticationCallback": CustomType(
CustomTypeKind.TYPE,
"(token: string) => Promise<AuthenticatedSessionInfo>",
),
"AuthenticatedSessionInfo": CustomType(
CustomTypeKind.INTERFACE,
"""
[key: string]: any;
""",
),
"SocketAddress": CustomType(
CustomTypeKind.TYPE,
"IPV4SocketAddress | IPV6SocketAddress | AnonymousUnixSocketAddress | PathUnixSocketAddress | AbstractUnixSocketAddress",
),
"IPV4SocketAddress": CustomType(
CustomTypeKind.INTERFACE,
"""
family: "ipv4";
address: string;
port: number;
""",
),
"IPV6SocketAddress": CustomType(
CustomTypeKind.INTERFACE,
"""
family: "ipv6";
address: string;
port: number;
flowlabel: number;
scopeid: number;
""",
),
"AnonymousUnixSocketAddress": CustomType(
CustomTypeKind.INTERFACE,
"""
family: "unix:anonymous";
""",
),
"PathUnixSocketAddress": CustomType(
CustomTypeKind.INTERFACE,
"""
family: "unix:path";
path: string;
""",
),
"AbstractUnixSocketAddress": CustomType(
CustomTypeKind.INTERFACE,
"""
family: "unix:abstract";
path: Buffer;
""",
),
"Variant": CustomType(
CustomTypeKind.TYPE, "VariantValue | [type: symbol, value: VariantValue]"
),
"VariantValue": CustomType(
CustomTypeKind.TYPE,
"""
| boolean
| number
| string
| Buffer
| Variant[]
| VariantDict
""",
),
"VariantDict": CustomType(
CustomTypeKind.INTERFACE,
"""
[key: string]: Variant;
""",
),
}
type_customizations: Mapping[str, TypeCustomizations] = {
"DeviceManager": ObjectTypeCustomizations(
signals={
"added": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"),
"removed": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"),
"changed": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"),
},
cleanup="close",
),
"Device": ObjectTypeCustomizations(
methods={
"query_system_parameters": MethodCustomizations(
return_typing="Promise<SystemParameters>",
return_wrapper="as SystemParameters",
),
"spawn": MethodCustomizations(
param_typings=[
"programOrArgv: string | string[]",
"opts?: SpawnOptions",
"cancellable?: Cancellable | null",
],
return_typing="Promise<ProcessID>",
custom_logic="""
const options: RawSpawnOptions = {};
let program: string;
let argv;
if (typeof programOrArgv === "string") {
program = programOrArgv;
argv = opts?.argv;
} else {
program = programOrArgv[0];
argv = programOrArgv;
if (argv.length === 1) {
argv = undefined;
}
}
if (argv !== undefined) {
options.argv = argv;
}
if (opts !== undefined) {
const envp = opts.envp;
if (envp !== undefined) {
options.envp = objectToStrv(envp);
}
const env = opts.env;
if (env !== undefined) {
options.env = objectToStrv(env);
}
const cwd = opts.cwd;
if (cwd !== undefined) {
options.cwd = cwd;
}
options.aux = Object.fromEntries(Object.entries(opts).filter(([k, v]) => !STANDARD_SPAWN_OPTION_NAMES.has(k)));
}
""",
),
"input": MethodCustomizations(
param_typings=[
"target: TargetProcess",
"data: Buffer",
"cancellable?: Cancellable | null",
],
custom_logic="const pid = await this.#getPid(target, cancellable);",
),
"resume": MethodCustomizations(
param_typings=[
"target: TargetProcess",
"cancellable?: Cancellable | null",
],
custom_logic="const pid = await this.#getPid(target, cancellable);",
),
"kill": MethodCustomizations(
param_typings=[
"target: TargetProcess",
"cancellable?: Cancellable | null",
],
custom_logic="const pid = await this.#getPid(target, cancellable);",
),
"attach": MethodCustomizations(
param_typings=[
"target: TargetProcess",
"options?: SessionOptions",
"cancellable?: Cancellable | null",
],
custom_logic="const pid = await this.#getPid(target, cancellable);",
),
"inject_library_file": MethodCustomizations(
param_typings=[
"target: TargetProcess",
"path: string",
"entrypoint: string",
"data: string",
"cancellable?: Cancellable | null",
],
return_typing="Promise<InjecteeID>",
custom_logic="const pid = await this.#getPid(target, cancellable);",
),
"inject_library_blob": MethodCustomizations(
param_typings=[
"target: TargetProcess",
"blob: Buffer",
"entrypoint: string",
"data: string",
"cancellable?: Cancellable | null",
],
return_typing="Promise<InjecteeID>",
custom_logic="const pid = await this.#getPid(target, cancellable);",
),
"open_channel": MethodCustomizations(
return_typing='Promise<import("stream").Duplex>',
return_wrapper="new IOStreamAdapter",
),
},
properties={
"dtype": PropertyCustomizations(
js_name="type",
),
},
signals={
"spawn-added": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"),
"spawn-removed": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"),
"child-added": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"),
"child-removed": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"),
"process-crashed": SignalCustomizations(
behavior="FDN_SIGNAL_KEEP_ALIVE"
),
"output": SignalCustomizations(
behavior="FDN_SIGNAL_KEEP_ALIVE",
transform={
0: ("pid: ProcessID", None),
1: ("fd: FileDescriptor", None),
},
),
"uninjected": SignalCustomizations(
behavior="FDN_SIGNAL_KEEP_ALIVE",
transform={
0: ("id: InjecteeID", None),
},
),
},
custom_code=CustomCode(
methods=[
CustomMethod(
typing="getProcess(name: string, options?: ProcessMatchOptions, cancellable?: Cancellable | null): Promise<Process>",
code="""
async getProcess(name: string, options: ProcessMatchOptions = {}, cancellable?: Cancellable | null): Promise<Process> {
const {
scope = Scope.Minimal,
} = options;
const processes = await this.enumerateProcesses({ scope }, cancellable);
const mm = new Minimatch(name.toLowerCase());
const matching = processes.filter(process => mm.match(process.name.toLowerCase()));
if (matching.length === 1) {
return matching[0];
} else if (matching.length > 1) {
throw new Error("Ambiguous name; it matches: " + matching.map(process => `${process.name} (pid: ${process.pid})`).join(", "));
} else {
throw new Error("Process not found");
}
}
""",
),
CustomMethod(
typing=None,
code="""
async #getPid(target: TargetProcess, cancellable?: Cancellable | null): Promise<ProcessID> {
if (typeof target === "number") {
return target;
}
const process = await this.getProcess(target, {}, cancellable);
return process.pid;
}
""",
),
],
),
),
"SpawnOptions": ObjectTypeCustomizations(js_name="RawSpawnOptions"),
"Bus": ObjectTypeCustomizations(
methods={
"post": MethodCustomizations(
param_typings=[
"message: any",
"data?: Buffer | null",
],
custom_logic="const json = JSON.stringify(message);",
),
},
signals={
"detached": SignalCustomizations(behavior="FDN_SIGNAL_KEEP_ALIVE"),
"message": SignalCustomizations(
behavior="FDN_SIGNAL_KEEP_ALIVE",
transform={
0: ("message: any", "JSON.parse"),
},
),
},
),
"Service": ObjectTypeCustomizations(
keep_alive=KeepAliveCustomization(
is_destroyed_function="is_closed", destroy_signal_name="close"
),
),
"Relay": ObjectTypeCustomizations(
constructor=ConstructorCustomizations(
param_typings=[
"properties: RelayProperties",
],
custom_logic="const { address, username, password, kind } = properties;",
),
),
"RelayKind": EnumerationCustomizations(
members={
"turn_udp": EnumerationMemberCustomizations(js_name="TurnUDP"),
"turn_tcp": EnumerationMemberCustomizations(js_name="TurnTCP"),
"turn_tls": EnumerationMemberCustomizations(js_name="TurnTLS"),
},
),
"Script": ObjectTypeCustomizations(
methods={
"is_destroyed": MethodCustomizations(hide=True),
"post": MethodCustomizations(
param_typings=[
"message: any",
"data?: Buffer | null",
],
custom_logic="const json = JSON.stringify(message);",
),
"enable_debugger": MethodCustomizations(
param_typings=[
"options?: EnableDebuggerOptions",
"cancellable?: Cancellable | null",
],
custom_logic="const port = options?.port ?? 0;",
),
},
signals={
"message": SignalCustomizations(
transform={
0: ("message: Message", "JSON.parse"),
},
intercept="this.#services.handleMessageIntercept",
),
},
custom_code=CustomCode(
declarations=[
CustomDeclaration(
typing=None, code="#services = new ScriptServices(this);"
),
CustomDeclaration(
typing="logHandler: ScriptLogHandler",
code="logHandler: ScriptLogHandler = log;",
),
],
methods=[
CustomMethod(
typing="readonly isDestroyed: boolean",
code="""
get isDestroyed(): boolean {
return this._isDestroyed();
}
""",
),
CustomMethod(
typing="readonly exports: ScriptExports",
code="""
get exports(): ScriptExports {
return this.#services.exportsProxy;
}
""",
),
CustomMethod(
typing="readonly defaultLogHandler: ScriptLogHandler",
code="""
get defaultLogHandler(): ScriptLogHandler {
return log;
}
""",
),
],
),
keep_alive=KeepAliveCustomization(
is_destroyed_function="is_destroyed", destroy_signal_name="destroyed"
),
),
"ScriptRuntime": EnumerationCustomizations(
members={
"qjs": EnumerationMemberCustomizations(js_name="QJS"),
},
),
"ControlService": ObjectTypeCustomizations(
methods={
"start": MethodCustomizations(ref_keep_alive=True),
"stop": MethodCustomizations(unref_keep_alive=True),
"get_endpoint_params": MethodCustomizations(drop=True),
},
properties={
"endpoint-params": PropertyCustomizations(drop=True),
},
),
"PortalService": ObjectTypeCustomizations(
constructor=ConstructorCustomizations(
param_typings=[
"options?: PortalServiceOptions",
],
custom_logic="""
const clusterParams = options?.clusterParams ?? new EndpointParameters();
const controlParams = options?.controlParams ?? null;
""",
),
methods={
"start": MethodCustomizations(ref_keep_alive=True),
"stop": MethodCustomizations(unref_keep_alive=True),
"kick": MethodCustomizations(
param_typings=[
"connectionId: PortalConnectionId",
],
),
"post": MethodCustomizations(
param_typings=[
"connectionId: PortalConnectionId",
"message: any",
"data?: Buffer | null",
],
custom_logic="const json = JSON.stringify(message);",
),
"narrowcast": MethodCustomizations(
param_typings=[
"tag: string",
"message: any",
"data?: Buffer | null",
],
custom_logic="const json = JSON.stringify(message);",
),
"broadcast": MethodCustomizations(
param_typings=[
"message: any",
"data?: Buffer | null",
],
custom_logic="const json = JSON.stringify(message);",
),
"enumerate_tags": MethodCustomizations(
param_typings=[
"connectionId: PortalConnectionId",
],
),
"tag": MethodCustomizations(
param_typings=[
"connectionId: PortalConnectionId",
"tag: string",
],
),
"untag": MethodCustomizations(
param_typings=[
"connectionId: PortalConnectionId",
"tag: string",
],
),
},
signals={
"node-connected": SignalCustomizations(
transform={
0: ("connectionId: PortalConnectionId", None),
1: ("remoteAddress: SocketAddress", "parseSocketAddress"),
},
),
"node-joined": SignalCustomizations(
transform={
0: ("connectionId: PortalConnectionId", None),
},
),
"node-left": SignalCustomizations(
transform={
0: ("connectionId: PortalConnectionId", None),
},
),
"node-disconnected": SignalCustomizations(
transform={
0: ("connectionId: PortalConnectionId", None),
1: ("remoteAddress: SocketAddress", "parseSocketAddress"),
},
),
"controller-connected": SignalCustomizations(
transform={
0: ("connectionId: PortalConnectionId", None),
1: ("remoteAddress: SocketAddress", "parseSocketAddress"),
},
),
"controller-disconnected": SignalCustomizations(
transform={
0: ("connectionId: PortalConnectionId", None),
1: ("remoteAddress: SocketAddress", "parseSocketAddress"),
},
),
"authenticated": SignalCustomizations(
transform={
0: ("connectionId: PortalConnectionId", None),
1: ("sessionInfo: AuthenticatedSessionInfo", "JSON.parse"),
},
),
"subscribe": SignalCustomizations(
transform={
0: ("connectionId: PortalConnectionId", None),
},
),
"message": SignalCustomizations(
transform={
0: ("connectionId: PortalConnectionId", None),
1: ("message: any", "JSON.parse"),
},
),
},
),
"EndpointParameters": ObjectTypeCustomizations(
constructor=ConstructorCustomizations(
param_typings=[
"params?: EndpointParametersSubset",
],
custom_logic="""
const address = params?.address ?? null;
const port = params?.port ?? 0;
const certificate = params?.certificate ?? null;
const origin = params?.origin ?? null;
let authService: AuthenticationService | null = null;
const auth = params?.authentication;
if (auth !== undefined) {
if (auth.scheme === "token") {
authService = new StaticAuthenticationService(auth.token);
} else {
authService = new CallbackAuthenticationService(auth.callback);
}
}
const assetRoot = params?.assetRoot ?? null;
""",
),
),
"Injector": ObjectTypeCustomizations(drop=True),
"RpcClient": ObjectTypeCustomizations(drop=True),
"RpcPeer": ObjectTypeCustomizations(drop=True),
"Cancellable": ObjectTypeCustomizations(
methods={
"is_cancelled": MethodCustomizations(hide=True),
"set_error_if_cancelled": MethodCustomizations(
js_name="throwIfCancelled",
return_typing="void",
),
"make_pollfd": MethodCustomizations(drop=True),
"release_fd": MethodCustomizations(drop=True),
"source_new": MethodCustomizations(drop=True),
},
custom_code=CustomCode(
methods=[
CustomMethod(
typing="readonly isCancelled: boolean",
code="""
get isCancelled(): boolean {
return this._isCancelled();
}
""",
),
],
),
),
"IOStream": ObjectTypeCustomizations(
methods={
"close": MethodCustomizations(drop=True),
"close_async": MethodCustomizations(js_name="close"),
"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),
"close_async": MethodCustomizations(js_name="close"),
"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),
"read_bytes_async": MethodCustomizations(js_name="read"),
"skip": MethodCustomizations(drop=True),
"skip_async": MethodCustomizations(js_name="skip"),
"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),
"close_async": MethodCustomizations(js_name="close"),
"flush": MethodCustomizations(drop=True),
"flush_async": MethodCustomizations(js_name="flush"),
"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),
"write_bytes_async": MethodCustomizations(js_name="write"),
"writev": MethodCustomizations(drop=True),
"writev_async": MethodCustomizations(drop=True),
"writev_all": MethodCustomizations(drop=True),
"writev_all_async": MethodCustomizations(drop=True),
"splice": MethodCustomizations(drop=True),
"splice_async": 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": MethodCustomizations(
return_typing="Buffer",
return_cconversion="fdn_buffer_to_value (env, (const guint8 *) retval, g_unix_socket_address_get_path_len (handle))",
),
"get_path_len": MethodCustomizations(drop=True),
"get_is_abstract": MethodCustomizations(drop=True),
},
properties={
"path": PropertyCustomizations(typing="path: Buffer"),
"abstract": PropertyCustomizations(drop=True),
"path-as-array": PropertyCustomizations(drop=True),
},
),
"SocketAddress": ObjectTypeCustomizations(
js_name="BaseSocketAddress",
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(
js_name="BaseObject",
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),
},
),
}
return Customizations(
custom_types,
type_customizations,
CUSTOMIZATION_FACADE_EXPORTS,
CUSTOMIZATION_FACADE_TS,
CUSTOMIZATION_HELPERS_IMPORTS,
CUSTOMIZATION_HELPERS_TS,
)
-61
View File
@@ -1,61 +0,0 @@
from __future__ import annotations
from collections import OrderedDict
from pathlib import Path
from .model import Customizations, Model, parse_gir
INCLUDED_GIO_OBJECT_TYPES = [
"Cancellable",
"IOStream",
"InputStream",
"OutputStream",
"SocketAddress",
"SocketAddressEnumerator",
"SocketConnectable",
"InetSocketAddress",
"InetAddress",
"UnixSocketAddress",
]
INCLUDED_GIO_ENUMERATIONS = [
"FileMonitorEvent",
"SocketFamily",
"UnixSocketAddressType",
]
def compute_model(
frida_gir: Path,
glib_gir: Path,
gobject_gir: Path,
gio_gir: Path,
customizations: Customizations,
) -> Model:
glib = parse_gir(glib_gir, [])
gobject = parse_gir(gobject_gir, [glib])
gio = parse_gir(gio_gir, [glib, gobject])
frida = parse_gir(frida_gir, [glib, gobject, gio])
object_types = OrderedDict()
object_types["Object"] = gobject.object_types["Object"]
object_types.update(frida.object_types)
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 = Model(
frida.namespace,
object_types,
enumerations,
customizations,
)
for t in object_types.values():
t.model = model
for t in enumerations.values():
t.model = model
return model
File diff suppressed because it is too large Load Diff
+3 -90
View File
@@ -1,95 +1,8 @@
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',
'__init__.pyi',
'extension.c',
],
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_helpers.c',
'frida_bindgen' / 'assets' / 'codegen_helpers.ts',
'frida_bindgen' / 'assets' / 'codegen_macros.h',
'frida_bindgen' / 'assets' / 'codegen_prototypes.h',
'frida_bindgen' / 'assets' / 'codegen_structs.h',
'frida_bindgen' / 'assets' / 'codegen_typedefs.h',
'frida_bindgen' / 'assets' / 'customization_facade.exports',
'frida_bindgen' / 'assets' / 'customization_facade.ts',
'frida_bindgen' / 'assets' / 'customization_helpers.imports',
'frida_bindgen' / 'assets' / 'customization_helpers.ts',
)
],
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@',
],
env: env,
install: true,
install_dir: [
python.get_install_dir() / 'frida',
python.get_install_dir() / 'frida' / '_frida',
false,
]
)
subdir('_frida')
py_sources = [
'__init__.py',
'core.py',
'py.typed',
]
python.install_sources(py_sources, subdir: 'frida', pure: false)
extension_py_sources = [
'py.typed',
]
python.install_sources(extension_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', 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',
)
+6 -6
View File
@@ -32,9 +32,9 @@ endif
subdir('frida')
#test('frida-python', python,
# args: ['-m', 'unittest', 'discover'],
# workdir: meson.current_source_dir(),
# env: {'PYTHONPATH': meson.current_build_dir() / 'src'},
# timeout: 30,
#)
test('frida-python', python,
args: ['-m', 'unittest', 'discover'],
workdir: meson.current_source_dir(),
env: {'PYTHONPATH': meson.current_build_dir() / 'src'},
timeout: 30,
)
+1 -1
Submodule releng updated: 71f828f7c8...c1c778ea2d
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-core.git
revision = 17.6.0
revision = 17.9.3
depth = 1
[provide]