Compare commits

...
316 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 01d8a48185 subprojects: Bump outdated 2026-11-09 22:03:58 +01:00
Ole André Vadla Ravnås 27ed54ecbc submodules: Bump releng 2026-11-09 22:03:58 +01:00
Ole André Vadla Ravnås afe44b1a01 subprojects: Prepare for release 2026-11-07 15:45:39 +01:00
Ole André Vadla Ravnås 6e8e4b7b33 subprojects: Bump outdated 2026-11-07 15:44:28 +01:00
Ole André Vadla Ravnås 118883ed4a subprojects: Prepare for release 2026-11-06 13:15:17 +01:00
Ole André Vadla Ravnås 953c7b8536 subprojects: Bump outdated 2026-11-06 12:19:50 +01:00
Ole André Vadla Ravnås 2d99d286d0 submodules: Bump releng 2026-11-06 12:19:50 +01:00
Ole André Vadla Ravnås c1f4eb997b subprojects: Prepare for release 2026-11-05 22:46:00 +01:00
Ole André Vadla Ravnås 692c411fbd subprojects: Bump outdated 2026-11-05 22:44:13 +01:00
Ole André Vadla Ravnås 00c0d152e7 subprojects: Bump outdated 2026-11-05 22:00:10 +01:00
Ole André Vadla Ravnås 4efb1e1e65 subprojects: Bump outdated 2026-11-05 21:01:05 +01:00
Ole André Vadla Ravnås 388915ca1e subprojects: Bump outdated 2026-11-04 12:54:07 +01:00
Ole André Vadla Ravnås 1c36106f8c submodules: Bump releng 2026-11-04 12:54:07 +01:00
Ole André Vadla Ravnås 50ad5f65dd subprojects: Prepare for release 2026-11-02 22:10:40 +01:00
Ole André Vadla Ravnås 9a9ccbad64 subprojects: Bump outdated 2026-11-02 22:09:07 +01:00
Ole André Vadla Ravnås 35a37f395a subprojects: Prepare for release 2026-11-01 15:11:47 +01:00
Ole André Vadla Ravnås 135d9a2ac7 subprojects: Bump outdated 2026-11-01 15:10:35 +01:00
Ole André Vadla Ravnås 3b0570d829 subprojects: Bump outdated 2026-10-31 00:48:22 +01:00
Ole André Vadla Ravnås 95ede66f87 subprojects: Bump outdated 2026-10-30 23:13:01 +01:00
Ole André Vadla Ravnås 9c424b152d subprojects: Bump outdated 2026-10-30 21:59:16 +01:00
Ole André Vadla Ravnås 4d4d594f45 submodules: Bump releng 2026-10-30 21:59:16 +01:00
Ole André Vadla Ravnås 556c44df84 examples: Use frida-forks URL for dbus-next 2026-10-30 21:53:04 +01:00
Ole André Vadla Ravnås c686293400 subprojects: Prepare for release 2026-10-30 14:34:01 +01:00
Ole André Vadla Ravnås ee02bd8af2 subprojects: Bump outdated 2026-10-30 14:01:37 +01:00
Ole André Vadla Ravnås 5f224307bb bindgen: Narrow factory marshals for mypy 2026-10-30 13:03:31 +01:00
Ole André Vadla Ravnås 7357b0b5b4 Format with Black 26 2026-10-30 13:03:17 +01:00
Ole André Vadla Ravnås 2f2bb64f36 subprojects: Bump outdated 2026-10-30 07:08:42 +01:00
Ole André Vadla Ravnås 5a0894aaac subprojects: Bump outdated 2026-10-30 07:00:11 +01:00
Ole André Vadla Ravnås 0bcb9da6c5 submodules: Bump releng 2026-10-30 07:00:11 +01:00
Ole André Vadla Ravnås 2316a9f36d subprojects: Bump outdated 2026-10-29 23:02:21 +01:00
Ole André Vadla Ravnås 720d09bbbc subprojects: Bump outdated 2026-10-29 21:53:15 +01:00
Ole André Vadla Ravnås 1269168a06 subprojects: Bump outdated 2026-10-29 21:28:24 +01:00
Ole André Vadla Ravnås b6c8f04757 submodules: Bump releng 2026-10-29 21:28:24 +01:00
Ole André Vadla Ravnås a0213a48e4 subprojects: Bump outdated 2026-10-23 13:51:43 +02:00
Ole André Vadla Ravnås b978ae1cf0 submodules: Bump releng 2026-10-23 13:51:43 +02:00
Ole André Vadla Ravnås 8017678437 subprojects: Bump outdated 2026-10-20 21:57:18 +02:00
Ole André Vadla Ravnås caef01a609 subprojects: Bump outdated 2026-10-20 21:39:25 +02:00
Ole André Vadla Ravnås 688b5d5c4d submodules: Bump releng 2026-10-20 21:39:25 +02:00
Ole André Vadla Ravnås bd8ea3369b subprojects: Bump outdated 2026-10-20 21:33:27 +02:00
Ole André Vadla Ravnås 1ecf3cf2a6 subprojects: Bump outdated 2026-10-20 20:23:49 +02:00
Ole André Vadla Ravnås 58ca39d36d build: Sync wrappers with releng 2026-10-20 20:23:49 +02:00
Ole André Vadla Ravnås 79c0305e66 submodules: Bump releng 2026-10-20 20:23:49 +02:00
Ole André Vadla Ravnås c1464592c8 subprojects: Bump outdated 2026-10-20 14:26:04 +02:00
Ole André Vadla Ravnås 1b21ea127b submodules: Bump releng 2026-10-20 14:26:04 +02:00
Ole André Vadla Ravnås b43e681df3 subprojects: Prepare for release 2026-10-19 10:33:32 +02:00
Ole André Vadla Ravnås 15d2d930dc subprojects: Bump outdated 2026-10-19 10:30:59 +02:00
Ole André Vadla Ravnås 24bdbab9cb submodules: Bump releng 2026-10-19 10:30:59 +02:00
Ole André Vadla Ravnås d0267d9f38 subprojects: Prepare for release 2026-10-18 13:53:42 +02:00
Ole André Vadla Ravnås 922dbfad3e subprojects: Bump outdated 2026-10-18 13:52:22 +02:00
Ole André Vadla Ravnås 03cf741fe0 subprojects: Prepare for release 2026-10-17 12:54:33 +02:00
Ole André Vadla Ravnås fdcea1076a subprojects: Bump outdated 2026-10-17 12:53:48 +02:00
Ole André Vadla Ravnås 1e1bea3512 submodules: Bump releng 2026-10-17 12:53:48 +02:00
Ole André Vadla Ravnås 95cbbd197c subprojects: Prepare for release 2026-10-16 15:37:47 +02:00
Ole André Vadla Ravnås 9bc7087d74 subprojects: Bump outdated 2026-10-16 15:36:33 +02:00
Ole André Vadla Ravnås ffe970e655 subprojects: Bump outdated 2026-10-16 13:54:46 +02:00
Ole André Vadla Ravnås 7ce14f42fa subprojects: Bump outdated 2026-10-16 12:42:52 +02:00
Ole André Vadla Ravnås 14c8d33b37 submodules: Bump releng 2026-10-16 12:42:52 +02:00
Ole André Vadla Ravnås ff0f50f8a6 subprojects: Bump outdated 2026-10-15 18:18:18 +02:00
Ole André Vadla Ravnås 8b526bb25d subprojects: Bump outdated 2026-10-15 15:46:11 +02:00
Ole André Vadla Ravnås 7c422c233d submodules: Bump releng 2026-10-15 15:46:11 +02:00
Ole André Vadla Ravnås bd7e80c209 subprojects: Bump outdated 2026-10-15 15:14:38 +02:00
Ole André Vadla Ravnås f80a06d7ae submodules: Bump releng 2026-10-15 15:14:38 +02:00
Ole André Vadla Ravnås 82febc16ba subprojects: Prepare for release 2026-10-09 18:57:36 +02:00
Ole André Vadla Ravnås 76d3b504fe subprojects: Bump outdated 2026-10-09 18:56:41 +02:00
Ole André Vadla Ravnås 735d34d175 subprojects: Prepare for release 2026-10-09 14:19:26 +02:00
Ole André Vadla Ravnås a6feef0770 subprojects: Bump outdated 2026-10-09 14:16:52 +02:00
Ole André Vadla Ravnås 418c5e3f43 subprojects: Prepare for release 2026-10-08 14:49:02 +02:00
Ole André Vadla Ravnås a1929a4dc5 subprojects: Bump outdated 2026-10-08 14:32:45 +02:00
Ole André Vadla Ravnås aacd1913e0 subprojects: Prepare for release 2026-10-07 14:27:03 +02:00
Ole André Vadla Ravnås ea6c66da96 subprojects: Bump outdated 2026-10-07 14:24:00 +02:00
Ole André Vadla Ravnås 57b6cbae38 submodules: Bump releng 2026-10-07 14:24:00 +02:00
Ole André Vadla Ravnås 1b18f0174d subprojects: Prepare for release 2026-10-05 10:00:33 +02:00
Ole André Vadla Ravnås 7f04d40687 subprojects: Bump outdated 2026-10-05 09:57:11 +02:00
Ole André Vadla Ravnås c69db7eb40 submodules: Bump releng 2026-10-05 09:57:10 +02:00
Ole André Vadla Ravnås ce49a3102a subprojects: Prepare for release 2026-10-04 11:35:34 +02:00
Ole André Vadla Ravnås 288133374a subprojects: Bump outdated 2026-10-04 11:34:54 +02:00
Ole André Vadla Ravnås 56843bb3f8 subprojects: Prepare for release 2026-10-03 07:56:17 +02:00
Ole André Vadla Ravnås d90f991948 subprojects: Bump outdated 2026-10-03 07:55:28 +02:00
Ole André Vadla Ravnås c744c2a12a subprojects: Prepare for release 2026-10-02 20:28:27 +02:00
Ole André Vadla Ravnås 48631baafa subprojects: Bump outdated 2026-10-02 20:27:05 +02:00
Ole André Vadla Ravnås c711f8f040 subprojects: Prepare for release 2026-10-02 01:36:43 +02:00
Ole André Vadla Ravnås 049674da0a subprojects: Bump outdated 2026-10-02 01:32:18 +02:00
Ole André Vadla Ravnås e1c24e8187 bindgen: Marshal null variants as None 2026-10-01 18:24:45 +02:00
Ole André Vadla Ravnås 9947655e6a subprojects: Prepare for release 2026-09-25 13:15:47 +02:00
Ole André Vadla Ravnås e5b15dce63 subprojects: Bump outdated 2026-09-25 13:14:11 +02:00
Ole André Vadla Ravnås e846eb2815 subprojects: Bump outdated 2026-09-25 12:18:59 +02:00
Ole André Vadla Ravnås bc685fd8c1 subprojects: Bump outdated 2026-09-24 21:35:04 +02:00
Ole André Vadla Ravnås bcaa1a099e subprojects: Bump outdated 2026-09-23 00:02:59 +02:00
Ole André Vadla Ravnås 4e782ccbdf submodules: Bump releng 2026-09-23 00:02:58 +02:00
Ole André Vadla Ravnås 9823368745 subprojects: Bump outdated 2026-09-22 23:47:25 +02:00
Ole André Vadla Ravnås c6e5a8d4d9 submodules: Bump releng 2026-09-22 23:47:25 +02:00
Ole André Vadla Ravnås eb15035950 subprojects: Bump outdated 2026-09-22 21:04:23 +02:00
Ole André Vadla Ravnås ec509287e1 subprojects: Bump outdated 2026-09-22 15:57:26 +02:00
Ole André Vadla Ravnås 0c52c00bad submodules: Bump releng 2026-09-22 15:57:26 +02:00
Ole André Vadla Ravnås 8f31efacf2 Format with the current Black
Black has changed how it lays out a call whose argument is a
multiline string, and a `with` statement holding several contexts.
2026-09-17 19:01:25 +02:00
Ole André Vadla Ravnås 01fd680353 bindgen: Fix dropped constructors and out params
A static function that makes the class it sits on was not read at all,
so every named constructor in the API was missing. One that throws was
then missing twice over: skipped for __init__ because it throws, and
skipped as a factory for being the primary, leaving nothing to make
the class with.

An out parameter was dropped along with the value it carried, so a
`gboolean` method that answers through one now binds as Optional, and
any other out parameter is refused rather than bound as a result the
caller cannot read.
2026-09-17 18:54:58 +02:00
Ole André Vadla Ravnås 00e6231b89 subprojects: Bump outdated 2026-09-16 19:28:53 +02:00
Ole André Vadla Ravnås aee40b044d subprojects: Prepare for release 2026-09-09 20:37:20 +02:00
Ole André Vadla Ravnås b8c22c18f1 subprojects: Bump outdated 2026-09-09 20:33:13 +02:00
Ole André Vadla Ravnås b0a080cd14 subprojects: Bump outdated 2026-09-09 20:16:39 +02:00
Ole André Vadla Ravnås e071ead36d subprojects: Bump outdated 2026-09-09 00:52:10 +02:00
Ole André Vadla Ravnås 2ff7776289 submodules: Bump releng 2026-09-09 00:52:10 +02:00
Ole André Vadla Ravnås df1b3af738 subprojects: Bump outdated 2026-09-09 00:47:02 +02:00
Ole André Vadla Ravnås 03d3b8ab0c subprojects: Bump outdated 2026-09-08 22:34:08 +02:00
Ole André Vadla Ravnås dd0639b6a0 submodules: Bump releng 2026-09-08 22:34:08 +02:00
Ole André Vadla Ravnås bfc6d48b72 bindgen: Fix typing imports on Python < 3.11
NotRequired, ParamSpec and TypedDict come from typing_extensions
there.
2026-09-08 14:14:07 +02:00
Ole André Vadla Ravnås 903143cfd2 subprojects: Bump outdated 2026-09-08 12:14:50 +02:00
Ole André Vadla Ravnås c91bd2d443 subprojects: Bump outdated 2026-09-08 11:39:29 +02:00
Ole André Vadla Ravnås c918c1e516 subprojects: Bump outdated 2026-09-07 17:39:36 +02:00
Ole André Vadla Ravnås ec645f2118 subprojects: Bump outdated 2026-09-07 11:47:57 +02:00
Ole André Vadla Ravnås 41a658af3f bindgen: Blacken model.py 2026-09-06 12:14:30 +02:00
Ole André Vadla Ravnås 9a86bd589b bindgen: Expose named constructors as factories
Emit them all rather than the first alone, and marshal a bytes
argument through GBytes. Keep new as the primary constructor.
2026-09-06 12:04:31 +02:00
Ole André Vadla Ravnås 82293f6c56 subprojects: Bump outdated 2026-09-06 12:02:40 +02:00
Ole André Vadla Ravnås b5c8757514 subprojects: Bump outdated 2026-09-04 17:27:46 +02:00
Ole André Vadla Ravnås 8c7b77cf52 subprojects: Bump outdated 2026-09-04 12:26:26 +02:00
Ole André Vadla Ravnås bbb129ac8d subprojects: Bump outdated 2026-09-03 22:40:11 +02:00
Ole André Vadla Ravnås 51c411d3f4 subprojects: Bump outdated 2026-09-03 18:19:36 +02:00
Ole André Vadla Ravnås a0dd2b7463 subprojects: Bump outdated 2026-09-03 14:43:19 +02:00
Ole André Vadla Ravnås fcc203f604 subprojects: Bump outdated 2026-09-02 18:42:33 +02:00
Ole André Vadla Ravnås a04b4a328e submodules: Bump releng 2026-09-02 18:42:33 +02:00
Ole André Vadla Ravnås f4109be14e subprojects: Bump outdated 2026-09-02 00:30:41 +02:00
Ole André Vadla Ravnås 786398a682 submodules: Bump releng 2026-09-02 00:30:41 +02:00
Ole André Vadla Ravnås 738060a8c5 subprojects: Bump outdated 2026-09-01 21:38:50 +02:00
Ole André Vadla Ravnås b5fb9987fc submodules: Bump releng 2026-09-01 21:38:50 +02:00
Ole André Vadla Ravnås b69d19f673 subprojects: Bump outdated 2026-09-01 15:16:05 +02:00
Ole André Vadla Ravnås 6622f997cd submodules: Bump releng 2026-09-01 15:16:05 +02:00
Ole André Vadla Ravnås 6eeeae51c8 subprojects: Bump outdated 2026-09-01 14:05:16 +02:00
Ole André Vadla Ravnås 679805bc8c submodules: Bump releng 2026-09-01 14:05:16 +02:00
Ole André Vadla Ravnås ec58231298 subprojects: Bump outdated 2026-08-27 18:26:09 +02:00
Ole André Vadla Ravnås 0caa5d4cf6 submodules: Bump releng 2026-08-27 18:26:09 +02:00
Ole André Vadla Ravnås 97d79c14a3 subprojects: Bump outdated 2026-08-18 15:32:21 +02:00
Ole André Vadla Ravnås e46178a118 submodules: Bump releng 2026-08-18 15:32:21 +02:00
Ole André Vadla Ravnås 1e631075e4 subprojects: Bump outdated 2026-08-18 12:50:57 +02:00
Ole André Vadla Ravnås 9cd85d0cfc subprojects: Bump outdated 2026-08-18 00:13:05 +02:00
Ole André Vadla Ravnås 4baee2713e subprojects: Bump outdated 2026-08-17 21:10:56 +02:00
Ole André Vadla Ravnås 4f33ee3945 submodules: Bump releng 2026-08-17 21:10:56 +02:00
Ole André Vadla Ravnås 6f890ee8d4 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.
2026-08-17 20:40:41 +02:00
Ole André Vadla Ravnås f27a8dcc73 subprojects: Bump outdated 2026-08-16 22:23:39 +02:00
Ole André Vadla Ravnås 7c81781550 submodules: Bump releng 2026-08-16 22:23:39 +02:00
Ole André Vadla Ravnås 317a796f7d subprojects: Bump outdated 2026-08-13 18:00:26 +02:00
Ole André Vadla Ravnås 01f691c075 subprojects: Bump outdated 2026-08-13 17:55:09 +02:00
Ole André Vadla Ravnås feb036961e submodules: Bump releng 2026-08-13 17:55:09 +02:00
Ole André Vadla Ravnås 62f066389e subprojects: Bump outdated 2026-08-13 16:21:25 +02:00
Ole André Vadla Ravnås 8deda449c3 submodules: Bump releng 2026-08-13 16:21:25 +02:00
Ole André Vadla Ravnås 5aa3fda547 subprojects: Bump outdated 2026-08-13 13:35:02 +02:00
Ole André Vadla Ravnås d7acc26d9d submodules: Bump releng 2026-08-13 13:35:02 +02:00
Ole André Vadla Ravnås 8cb6565adf subprojects: Bump outdated 2026-08-13 13:02:05 +02:00
Ole André Vadla Ravnås 5ba902508e submodules: Bump releng 2026-08-13 13:02:05 +02:00
Ole André Vadla Ravnås 4ee43bc18c subprojects: Bump outdated 2026-08-13 00:19:40 +02:00
Ole André Vadla Ravnås 4414c049f4 submodules: Bump releng 2026-08-13 00:19:40 +02:00
Ole André Vadla Ravnås 1649a5a4cf subprojects: Bump outdated 2026-08-12 13:00:22 +02:00
Ole André Vadla Ravnås b17f7704a3 submodules: Bump releng 2026-08-12 13:00:22 +02:00
Ole André Vadla Ravnås 4aca013c22 subprojects: Bump outdated 2026-08-11 11:41:53 +02:00
Ole André Vadla Ravnås 1cc0bcae64 subprojects: Prepare for release 2026-08-05 00:32:55 +02:00
Ole André Vadla Ravnås 14849bcb1e subprojects: Bump outdated 2026-08-05 00:32:13 +02:00
Ole André Vadla Ravnås 20def06046 submodules: Bump releng 2026-08-05 00:32:13 +02:00
Ole André Vadla Ravnås 3ffd66c91d subprojects: Bump outdated 2026-08-02 18:29:21 +02:00
Ole André Vadla Ravnås da4e16c726 submodules: Bump releng 2026-08-02 18:29:21 +02:00
Ole André Vadla Ravnås 3353755177 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.
2026-08-02 18:19:14 +02:00
Ole André Vadla Ravnås 82b4f9559f 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.
2026-08-02 18:13:33 +02:00
Ole André Vadla Ravnås db4590d85c 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 <[email protected]>.
2026-07-31 00:31:04 +02:00
Ole André Vadla Ravnås 2a3a04b9be subprojects: Prepare for release 2026-07-22 00:30:14 +02:00
Ole André Vadla Ravnås 89e8222424 subprojects: Bump outdated 2026-07-22 00:29:03 +02:00
Ole André Vadla Ravnås e35a085928 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.
2026-07-22 00:23:55 +02:00
Ole André Vadla Ravnås d1716f4a6c 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.
2026-07-22 00:23:55 +02:00
Ole André Vadla Ravnås 0646972686 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.
2026-07-22 00:23:55 +02:00
Ole André Vadla Ravnås b51406984d 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.
2026-07-22 00:23:55 +02:00
Ole André Vadla Ravnås 082c9dfbc9 subprojects: Prepare for release 2026-07-20 17:56:01 +02:00
Ole André Vadla Ravnås 274c52291b bindgen: Fix formatting 2026-07-20 17:54:25 +02:00
Ole André Vadla Ravnås b9e24033ed subprojects: Bump outdated 2026-07-20 17:47:03 +02:00
Ole André Vadla Ravnås 10aeb056c6 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.
2026-07-20 17:45:06 +02:00
Ole André Vadla Ravnås e5e0558250 subprojects: Prepare for release 2026-07-19 22:25:25 +02:00
Ole André Vadla Ravnås cd74f3618a subprojects: Bump outdated 2026-07-19 22:24:35 +02:00
Ole André Vadla Ravnås 9fff6507af subprojects: Prepare for release 2026-07-18 13:30:31 +02:00
Ole André Vadla Ravnås 37badc7959 subprojects: Bump outdated 2026-07-18 13:29:18 +02:00
Ole André Vadla Ravnås 8b05045785 submodules: Bump releng 2026-07-18 13:29:18 +02:00
Ole André Vadla Ravnås 063c130df6 subprojects: Prepare for release 2026-07-17 23:28:18 +02:00
Ole André Vadla Ravnås b85319e7cc subprojects: Bump outdated 2026-07-17 23:07:41 +02:00
Ole André Vadla Ravnås b67e4a3db8 subprojects: Bump outdated 2026-07-17 20:48:16 +02:00
Ole André Vadla Ravnås 811a4b2217 submodules: Bump releng 2026-07-17 20:48:16 +02:00
Ole André Vadla Ravnås dc5f78e660 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.
2026-07-16 19:44:06 +02:00
Ole André Vadla Ravnås e17e55a8f5 Sort customization.py imports 2026-07-16 17:55:13 +02:00
Ole André Vadla Ravnås fbda8a6950 subprojects: Bump outdated 2026-07-16 17:50:54 +02:00
Ole André Vadla Ravnås a01c4dfb1e submodules: Bump releng 2026-07-16 17:50:54 +02:00
Ole André Vadla Ravnås 3e88443647 ci: Bump GitHub Actions to latest versions 2026-07-16 17:07:24 +02:00
Ole André Vadla Ravnås 2d5f9b0f7a Auto-generate the bindings from Frida's .gir 2026-07-16 17:07:24 +02:00
Ole André Vadla Ravnås 0988dca83e subprojects: Prepare for release 2026-07-13 12:55:54 +02:00
Ole André Vadla Ravnås 0a803237fc subprojects: Bump outdated 2026-07-13 12:54:51 +02:00
Ole André Vadla Ravnås 69ce0e4910 subprojects: Prepare for release 2026-07-06 19:05:16 +02:00
Ole André Vadla Ravnås 6528882671 subprojects: Bump outdated 2026-07-06 19:04:30 +02:00
Ole André Vadla Ravnås 7e3728b457 subprojects: Prepare for release 2026-06-22 23:31:27 +02:00
Ole André Vadla Ravnås 83fdc4d0af subprojects: Bump outdated 2026-06-22 23:30:43 +02:00
Ole André Vadla Ravnås 01b4662207 subprojects: Prepare for release 2026-06-22 11:46:25 +02:00
Ole André Vadla Ravnås df7635122f subprojects: Bump outdated 2026-06-22 11:45:46 +02:00
Ole André Vadla Ravnås 726fc2e8d3 subprojects: Prepare for release 2026-06-20 15:56:57 +02:00
Ole André Vadla Ravnås f240d70be2 subprojects: Bump outdated 2026-06-20 15:55:26 +02:00
Ole André Vadla Ravnås 254c16c5fe subprojects: Prepare for release 2026-06-19 12:38:34 +02:00
Ole André Vadla Ravnås c1c5db7632 subprojects: Bump outdated 2026-06-19 11:56:00 +02:00
Ole André Vadla Ravnås d34ce2d9f4 subprojects: Prepare for release 2026-06-17 12:42:34 +02:00
Ole André Vadla Ravnås 5fbd498d96 subprojects: Bump outdated 2026-06-17 12:39:33 +02:00
Ole André Vadla Ravnås 7480f5d919 subprojects: Bump outdated 2026-06-17 12:33:14 +02:00
Ole André Vadla Ravnås bca04adce4 subprojects: Prepare for release 2026-06-16 16:40:22 +02:00
Ole André Vadla Ravnås a9c2672611 subprojects: Bump outdated 2026-06-16 16:38:35 +02:00
Ole André Vadla Ravnås abe0be49f0 script: Add interrupt() and terminate()
Wire up the new frida-core Script.interrupt() and terminate() methods:

  - interrupt() aborts any JavaScript currently executing, leaving the
    script loaded and able to run again.
  - terminate() interrupts execution and unloads the script, so one
    stuck in a long-running or infinite operation can still be torn
    down.
2026-06-16 16:36:43 +02:00
Ole André Vadla Ravnås a3c832be83 subprojects: Prepare for release 2026-06-15 19:09:31 +02:00
Ole André Vadla Ravnås 6580a0492b subprojects: Bump outdated 2026-06-15 19:08:48 +02:00
Ole André Vadla Ravnås c5f80e2c38 subprojects: Prepare for release 2026-06-12 00:46:13 +02:00
Ole André Vadla Ravnås 2c1bccddd0 typing: Add Device.override_option()
The wrapper in frida.core calls it, but the _frida stub lacked it,
tripping up mypy.
2026-06-12 00:46:13 +02:00
Ole André Vadla Ravnås 6f6940b097 subprojects: Bump outdated 2026-06-12 00:14:03 +02:00
Ole André Vadla Ravnås 308bcd7328 subprojects: Bump outdated 2026-06-11 22:56:42 +02:00
Ole André Vadla Ravnås c2f541aa7f submodules: Bump releng 2026-06-11 22:56:42 +02:00
Ole André Vadla Ravnås 2e73698ac7 subprojects: Bump outdated 2026-06-11 20:26:14 +02:00
Ole André Vadla Ravnås 18f2749037 submodules: Bump releng 2026-06-11 20:26:14 +02:00
Ole André Vadla Ravnås 80bd66fd6d subprojects: Prepare for release 2026-06-10 00:48:02 +02:00
Ole André Vadla Ravnås 32d1c921c9 subprojects: Bump outdated 2026-06-10 00:47:02 +02:00
Ole André Vadla Ravnås dbe7868509 subprojects: Prepare for release 2026-06-05 17:08:11 +02:00
Ole André Vadla Ravnås a0867d30b9 subprojects: Bump outdated 2026-06-05 17:06:50 +02:00
Ole André Vadla Ravnås 0666e8d33c subprojects: Bump outdated 2026-06-05 17:00:09 +02:00
Ole André Vadla Ravnås 60c30102bf subprojects: Bump outdated 2026-06-05 01:14:18 +02:00
Ole André Vadla Ravnås d50d5cc6d0 subprojects: Bump outdated 2026-06-05 00:56:11 +02:00
Ole André Vadla Ravnås ddeaad5254 submodules: Bump releng 2026-06-05 00:56:11 +02:00
Ole André Vadla Ravnås c68cd908d3 subprojects: Prepare for release 2026-06-02 02:10:44 +02:00
Ole André Vadla Ravnås 5b6d9f378a subprojects: Bump outdated 2026-06-02 02:09:03 +02:00
Ole André Vadla Ravnås 0839e57d3b subprojects: Bump outdated 2026-06-02 01:41:18 +02:00
Ole André Vadla Ravnås 5215b5b2c8 subprojects: Prepare for release 2026-05-31 21:03:59 +02:00
Ole André Vadla Ravnås 54dca1d5da subprojects: Bump outdated 2026-05-31 21:03:09 +02:00
Ole André Vadla Ravnås 8eafeb24d9 submodules: Bump releng 2026-05-31 21:03:09 +02:00
Ole André Vadla Ravnås 1b75d93858 subprojects: Bump outdated 2026-05-31 13:13:33 +02:00
Ole André Vadla Ravnås c2145f3589 Add support for new attach options 2026-05-31 12:32:06 +02:00
Ole André Vadla Ravnås 0a9995df51 subprojects: Prepare for release 2026-05-23 14:22:35 +02:00
Ole André Vadla Ravnås 0f3ffd4032 subprojects: Bump outdated 2026-05-23 14:21:34 +02:00
Ole André Vadla Ravnås 2da64cb7ca subprojects: Bump outdated 2026-05-21 23:19:27 +02:00
Ole André Vadla Ravnås f81c3183a1 subprojects: Prepare for release 2026-05-15 14:23:34 +02:00
Ole André Vadla Ravnås d2861db65b subprojects: Bump outdated 2026-05-15 14:22:01 +02:00
Ole André Vadla Ravnås 06210bec40 subprojects: Prepare for release 2026-05-14 20:21:16 +02:00
Ole André Vadla Ravnås 4934e18a0e subprojects: Bump outdated 2026-05-14 20:20:05 +02:00
Ole André Vadla Ravnås fe987827e9 subprojects: Prepare for release 2026-05-11 13:22:50 +02:00
Ole André Vadla Ravnås 0ef38236e8 subprojects: Bump outdated 2026-05-11 13:22:11 +02:00
Ole André Vadla Ravnås 0fab765f91 subprojects: Prepare for release 2026-05-08 10:40:06 +02:00
Ole André Vadla Ravnås a6c02cb7ee subprojects: Bump outdated 2026-05-08 10:37:49 +02:00
Ole André Vadla Ravnås 6033dd0904 subprojects: Prepare for release 2026-05-04 15:25:33 +02:00
Ole André Vadla Ravnås 77e94e24a1 subprojects: Bump outdated 2026-05-04 15:24:42 +02:00
Ole André Vadla Ravnås c97343dc83 subprojects: Prepare for release 2026-05-02 21:13:32 +02:00
Ole André Vadla Ravnås e1ffe5fd50 subprojects: Bump outdated 2026-05-02 21:12:51 +02:00
Ole André Vadla Ravnås c7314098c6 subprojects: Prepare for release 2026-05-02 15:48:41 +02:00
Ole André Vadla Ravnås 41d769fa4d subprojects: Bump outdated 2026-05-02 15:46:28 +02:00
Ole André Vadla Ravnås 044d23eef7 subprojects: Bump outdated 2026-04-29 00:15:57 +02:00
Ole André Vadla Ravnås 1641af6ca3 submodules: Bump releng 2026-04-29 00:15:57 +02:00
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
75 changed files with 7546 additions and 9361 deletions

No files matched your search

+5 -6
View File
@@ -4,13 +4,12 @@ jobs:
black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v7
- uses: psf/black@stable
isort:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.8
- uses: jamescurtin/isort-action@master
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
- run: pip install isort
- run: isort --check-only --diff .
+5 -4
View File
@@ -4,12 +4,13 @@ jobs:
pyflakes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: lgeiger/pyflakes-action@master
- uses: actions/checkout@v7
- run: pip install pyflakes
- run: pyflakes $(git ls-files '*.py' | grep -v '^frida/frida_bindgen/assets/')
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v7
- uses: jpetrucciani/mypy-check@master
with:
mypy_flags: '--exclude examples --exclude setup'
mypy_flags: '--exclude examples --exclude setup --exclude frida/frida_bindgen/assets'
+19
View File
@@ -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
+2
View File
@@ -1,5 +1,7 @@
*.pyc
*.abi3.so
/*.egg-info/
/.cache/
/build/
/deps/
/dist/
+3
View File
@@ -1,3 +1,6 @@
[submodule "releng"]
path = releng
url = https://github.com/frida/releng.git
[submodule "frida-bindgen"]
path = frida-bindgen
url = https://github.com/frida/frida-bindgen.git
+3 -3
View File
@@ -1,8 +1,8 @@
include meson.build
include frida/meson.build
include frida/_frida/extension.c
include frida/_frida/extension.version
include frida/_frida/meson.build
include frida/extension.version
recursive-include frida/frida_bindgen *.py *.c *.h
recursive-include frida-bindgen *.py
include subprojects/frida-core.wrap
include BSDmakefile
+2 -2
View File
@@ -1,4 +1,4 @@
PYTHON ?= $(shell which python3 >/dev/null && echo python3 || echo python)
PYTHON ?= $(shell command -v python3 >/dev/null && echo python3 || echo python)
all $(MAKECMDGOALS):
@$(PYTHON) \
@@ -8,7 +8,7 @@ all $(MAKECMDGOALS):
$(MAKECMDGOALS)
git-submodules:
@if [ ! -f releng/meson/meson.py ]; then \
@if [ ! -f releng/meson/meson.py ] || git submodule status 2>/dev/null | grep -q '^-'; then \
git submodule update --init --recursive --depth 1; \
fi
-include git-submodules
Vendored
+2 -2
View File
@@ -1,12 +1,12 @@
#!/bin/sh
[ -z "$PYTHON" ] && PYTHON=$(which python3 >/dev/null && echo python3 || echo python)
[ -z "$PYTHON" ] && PYTHON=$(command -v python3 >/dev/null && echo python3 || echo python)
cd $(dirname $0)
srcroot=$(pwd)
if [ ! -f releng/meson/meson.py ]; then
if [ ! -f releng/meson/meson.py ] || git submodule status 2>/dev/null | grep -q '^-'; then
git submodule update --init --recursive --depth 1 || exit $?
fi
+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()
+4 -4
View File
@@ -9,7 +9,7 @@
"version": "1.0.0",
"dependencies": {
"core-js": "^3.6.5",
"dbus-next": "frida/node-dbus-next",
"dbus-next": "frida-forks/node-dbus-next",
"duplexify": "^4.1.1",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
@@ -6509,7 +6509,7 @@
},
"node_modules/dbus-next": {
"version": "0.9.2",
"resolved": "git+ssh://[email protected]/frida/node-dbus-next.git#0e12559b2298c140916541f57fdf9f83cf0b7975",
"resolved": "git+ssh://[email protected]/frida-forks/node-dbus-next.git#0e12559b2298c140916541f57fdf9f83cf0b7975",
"license": "MIT",
"dependencies": {
"@nornagon/put": "0.0.8",
@@ -20437,8 +20437,8 @@
"peer": true
},
"dbus-next": {
"version": "git+ssh://[email protected]/frida/node-dbus-next.git#0e12559b2298c140916541f57fdf9f83cf0b7975",
"from": "dbus-next@frida/node-dbus-next",
"version": "git+ssh://[email protected]/frida-forks/node-dbus-next.git#0e12559b2298c140916541f57fdf9f83cf0b7975",
"from": "dbus-next@frida-forks/node-dbus-next",
"requires": {
"@nornagon/put": "0.0.8",
"abstract-socket": "^2.0.0",
+1 -1
View File
@@ -8,7 +8,7 @@
},
"dependencies": {
"core-js": "^3.6.5",
"dbus-next": "frida/node-dbus-next",
"dbus-next": "frida-forks/node-dbus-next",
"duplexify": "^4.1.1",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
Submodule
+1
Submodule frida-bindgen added at eace04901e.
-173
View File
@@ -1,173 +0,0 @@
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
@@ -1,877 +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) -> "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
@@ -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',
)
-1822
View File
File diff suppressed because it is too large. Load diff
File renamed without changes.
File renamed without changes.
+4
View File
@@ -0,0 +1,4 @@
from .cli import main
if __name__ == "__main__":
main()
@@ -0,0 +1,56 @@
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;
PyObject * 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, "|esHOesOes", keywords,
"utf-8", &address,
&port,
&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 (address);
return result;
}
@@ -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;
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,47 @@
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 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);
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);
@@ -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
@@ -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;
};
@@ -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);
+23
View File
@@ -0,0 +1,23 @@
def _setup(self) -> None:
self._message_handlers: List[BusMessageCallback] = []
self._impl.on("message", self._on_message)
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: 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: str, data: Optional[bytes]) -> None:
message = json.loads(raw_message)
for handler in list(self._message_handlers):
try:
handler(message, data)
except Exception:
traceback.print_exc()
@@ -0,0 +1,27 @@
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: 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: 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: 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: BusMessageCallback, message: Mapping[Any, Any], data: Optional[bytes]) -> None:
try:
handler(message, data)
except Exception:
traceback.print_exc()
@@ -0,0 +1,49 @@
def __enter__(self) -> "Cancellable":
stack = getattr(_current_cancellable, "stack", None)
if stack is None:
stack = []
_current_cancellable.stack = stack
stack.append(self)
return self
def __exit__(self, *exc: Any) -> Literal[False]:
_current_cancellable.stack.pop()
return False
@classmethod
def get_current(cls) -> "Cancellable":
return _current_cancellable_get()
def raise_if_cancelled(self) -> None:
if self._impl.is_cancelled():
raise _frida.OperationCancelledError("operation was cancelled")
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)
@@ -0,0 +1,45 @@
def __enter__(self) -> "Cancellable":
self._token = _current_cancellable.set(self)
return self
def __exit__(self, *exc: Any) -> Literal[False]:
_current_cancellable.reset(self._token)
return False
@classmethod
def get_current(cls) -> "Cancellable":
return _current_cancellable.get()
def raise_if_cancelled(self) -> None:
if self._impl.is_cancelled():
raise _frida.OperationCancelledError("operation was cancelled")
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)
@@ -0,0 +1,40 @@
class CancellablePollFD:
def __init__(self, cancellable: _frida.Cancellable) -> None:
self.handle = cancellable.get_fd()
self._cancellable: Optional[_frida.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: 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
@@ -0,0 +1,40 @@
class CancellablePollFD:
def __init__(self, cancellable: _frida.Cancellable) -> None:
self.handle = cancellable.get_fd()
self._cancellable: Optional[_frida.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: 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
@@ -0,0 +1,25 @@
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():
if predicate(device):
return device
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)
@@ -0,0 +1,25 @@
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():
if predicate(device):
return device
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)
@@ -0,0 +1,25 @@
@property
def type(self) -> str:
return self.dtype
def get_bus(self) -> "Bus":
return self.bus
def get_process(self, process_name: str) -> "Process":
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: ProcessTarget) -> int:
if isinstance(target, str):
return self.get_process(target).pid
return target
@@ -0,0 +1,25 @@
@property
def type(self) -> str:
return self.dtype
def get_bus(self) -> "Bus":
return self.bus
async def get_process(self, process_name: str) -> "Process":
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: ProcessTarget) -> int:
if isinstance(target, str):
return (await self.get_process(target)).pid
return target
@@ -0,0 +1 @@
ScriptExportsAsync = aio._ScriptExports
@@ -0,0 +1,12 @@
class _Implementation:
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
except Exception as e:
result = None
error = e
_frida._complete_request(completion, result, error)
threading.Thread(target=run, daemon=True).start()
@@ -0,0 +1,19 @@
class _Implementation:
_loop: Any
def _frida_dispatch(self, name: str, args: List[Any], completion: Any) -> None:
loop = self._loop
async def run() -> None:
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()))
@@ -0,0 +1,25 @@
def close(self) -> None:
_invoke(self._impl.close_async, (0,))
def read(self, count: int) -> bytes:
return _invoke(self._impl.input_stream.read_bytes_async, (count, 0))
def read_all(self, count: int) -> bytes:
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: bytes) -> int:
return _invoke(self._impl.output_stream.write_bytes_async, (data, 0))
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))
view = view[written:]
@@ -0,0 +1,25 @@
async def close(self) -> None:
await _invoke(self._impl.close_async, (0,))
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: int) -> bytes:
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: bytes) -> int:
return await _invoke(self._impl.output_stream.write_bytes_async, (data, 0))
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))
view = view[written:]
@@ -0,0 +1,37 @@
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: str, callback: Callable[..., Any]) -> None:
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: str, callback: Callable[..., Any]) -> None:
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: int, raw_session_info: str) -> None:
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: int, raw_message: str, data: Optional[bytes]) -> None:
message = json.loads(raw_message)
for handler in list(self._message_handlers):
try:
handler(connection_id, message, data)
except Exception:
traceback.print_exc()
@@ -0,0 +1,38 @@
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: str, callback: Callable[..., Any]) -> None:
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: str, callback: Callable[..., Any]) -> None:
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: 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: 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: Callable[..., Any], *args: Any) -> None:
try:
handler(*args)
except Exception:
traceback.print_exc()
@@ -0,0 +1,59 @@
def _to_camel_case(name: str) -> str:
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) -> str:
return str(self.args[2]) if len(self.args) >= 3 else str(self.args[0])
class _ScriptExports:
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) -> Any:
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) -> List[str]:
return self._script._list_exports()
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)
@@ -0,0 +1,55 @@
def _to_camel_case(name: str) -> str:
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) -> str:
return str(self.args[2]) if len(self.args) >= 3 else str(self.args[0])
class _ScriptExports:
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) -> Any:
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
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)
@@ -0,0 +1,111 @@
def _setup(self) -> None:
self.exports_sync = _ScriptExports(self)
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) -> "_ScriptExports":
return self.exports_sync
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: str, callback: Callable[..., Any]) -> None:
if signal == "message":
self._message_handlers.remove(callback)
else:
self._impl.off(signal, callback)
def get_log_handler(self) -> Callable[[str, str], None]:
return self._log_handler
def set_log_handler(self, handler: Callable[[str, str], None]) -> None:
self._log_handler = handler
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) -> List[str]:
return self._rpc_request(["list"])
def list_exports(self) -> List[str]:
return self.list_exports_sync()
def _list_exports(self) -> List[str]:
return self.list_exports_sync()
def _rpc_request(self, args: Any, data: Optional[bytes] = None) -> Any:
outcome: Dict[str, Any] = {}
cond = self._cond
def complete(value: Any, error: Optional[Exception]) -> None:
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: str, data: Optional[bytes]) -> None:
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) -> None:
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: int, operation: str, params: List[Any], data: Optional[Any]) -> None:
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)
@@ -0,0 +1,98 @@
def _setup(self) -> None:
self.exports = _ScriptExports(self)
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: 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: str, callback: Callable[..., Any]) -> None:
if signal == "message":
self._message_handlers.remove(callback)
else:
self._impl.off(signal, callback)
def get_log_handler(self) -> Callable[[str, str], None]:
return self._log_handler
def set_log_handler(self, handler: Callable[[str, str], None]) -> None:
self._log_handler = handler
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) -> List[str]:
return await self._rpc_request(["list"])
def _rpc_request(self, args: Any, data: Optional[bytes] = None) -> "asyncio.Future[Any]":
loop = asyncio.get_running_loop()
future: "asyncio.Future[Any]" = loop.create_future()
def complete(value: Any, error: Optional[Exception]) -> None:
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: str, data: Optional[bytes]) -> None:
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: ScriptMessageCallback, message: ScriptMessage, data: Optional[bytes]) -> None:
try:
handler(message, data)
except Exception:
traceback.print_exc()
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
if complete is None:
break
complete(None, _frida.InvalidOperationError("script has been destroyed"))
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)
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)
@@ -0,0 +1,79 @@
__version__ = _frida.__version__
def get_device_manager() -> "DeviceManager":
return _wrap(_frida.get_device_manager())
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: Callable[["Device"], bool], timeout: Union[int, float] = 0) -> "Device":
return get_device_manager().get_device_matching(predicate, timeout)
def get_local_device() -> "Device":
return get_device_manager().get_device_by_type("local", 0)
def get_remote_device() -> "Device":
return get_device_manager().get_device_by_type("remote", 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() -> List["Device"]:
return get_device_manager().enumerate_devices()
def query_system_parameters() -> Dict[str, Any]:
return get_local_device().query_system_parameters()
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: ProcessTarget) -> None:
return get_local_device().resume(target)
def kill(target: ProcessTarget) -> None:
return get_local_device().kill(target)
def attach(target: ProcessTarget, **kwargs: Any) -> "Session":
return get_local_device().attach(target, **kwargs)
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: ProcessTarget, blob: bytes, entrypoint: str, data: str) -> int:
return get_local_device().inject_library_blob(target, blob, entrypoint, data)
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
@@ -0,0 +1,80 @@
__version__ = _frida.__version__
def get_device_manager() -> "DeviceManager":
return _wrap(_frida.get_device_manager())
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: Callable[["Device"], bool], timeout: Union[int, float] = 0) -> "Device":
return await get_device_manager().get_device_matching(predicate, timeout)
async def get_local_device() -> "Device":
return await get_device_manager().get_device_by_type("local", 0)
async def get_remote_device() -> "Device":
return await get_device_manager().get_device_by_type("remote", 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() -> List["Device"]:
return await get_device_manager().enumerate_devices()
async def query_system_parameters() -> Dict[str, Any]:
return await (await get_local_device()).query_system_parameters()
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: ProcessTarget) -> None:
return await (await get_local_device()).resume(target)
async def kill(target: ProcessTarget) -> None:
return await (await get_local_device()).kill(target)
async def attach(target: ProcessTarget, **kwargs: Any) -> "Session":
return await (await get_local_device()).attach(target, **kwargs)
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: ProcessTarget, blob: bytes, entrypoint: str, data: str) -> int:
return await (await get_local_device()).inject_library_blob(target, blob, entrypoint, data)
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
+106
View File
@@ -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"]
@@ -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;
}
+100
View File
@@ -0,0 +1,100 @@
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(
"--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)
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
File diff suppressed because it is too large. Load diff
+400
View File
@@ -0,0 +1,400 @@
from __future__ import annotations
from typing import Mapping
from .model import (
ConstructorCustomizations,
CustomCode,
Customizations,
EnumerationCustomizations,
EnumerationMemberCustomizations,
MethodCustomizations,
ModuleFunction,
ObjectTypeCustomizations,
PropertyCustomizations,
SignalCustomizations,
TypeCustomizations,
)
_RESOLVE_PID = ("pid = self._pid_of(target)", "pid = await self._pid_of(target)")
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: Any", "data: Optional[bytes] = 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: int", "message: Any", "data: Optional[bytes] = None"],
custom_logic="json = _to_json(message)",
),
"narrowcast": MethodCustomizations(
param_typings=["tag: str", "message: Any", "data: Optional[bytes] = None"],
custom_logic="json = _to_json(message)",
),
"broadcast": MethodCustomizations(
param_typings=["message: Any", "data: Optional[bytes] = None"],
custom_logic="json = _to_json(message)",
),
},
),
"Device": ObjectTypeCustomizations(
methods={
"spawn": MethodCustomizations(
param_typings=[
"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):
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:
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: 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: ProcessTarget", "path: str", "entrypoint: str", "data: str"],
custom_logic=_RESOLVE_PID,
),
"inject_library_blob": MethodCustomizations(
param_typings=["target: ProcessTarget", "blob: bytes", "entrypoint: str", "data: str"],
custom_logic=_RESOLVE_PID,
),
"attach": MethodCustomizations(
param_typings=["target: ProcessTarget", "**kwargs: Any"],
custom_logic=_RESOLVE_PID,
),
},
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: 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),
},
),
"Session": ObjectTypeCustomizations(
methods={
"is_detached": MethodCustomizations(as_property=True),
"create_script": MethodCustomizations(
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:
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: bytes",
"name: Optional[str] = None",
"snapshot: Optional[bytes] = None",
"runtime: Optional[str] = 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: str", "name: Optional[str] = None", "runtime: Optional[str] = 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: str",
"warmup_script: Optional[str] = None",
"runtime: Optional[str] = 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: 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
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)) # type: ignore[attr-defined]
AuthenticationService.__init__(service)
auth_service = service._impl
else:
raise ValueError("invalid authentication scheme")
else:
auth_service = _unwrap(authentication)
kwargs: Dict[str, Any] = {}
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(
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),
},
),
}
return Customizations(
type_customizations=type_customizations,
facade_preludes=("facade_types.py",),
facade_epilogues=("facade_epilogue.py",),
)
+45
View File
@@ -0,0 +1,45 @@
from __future__ import annotations
from pathlib import Path
import frida_bindgen_core as core
from .model import FACTORY, Customizations, Model
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:
return core.compute_model(
frida_gir,
glib_gir,
gobject_gir,
gio_gir,
customizations,
FACTORY,
INCLUDED_GIO_OBJECT_TYPES,
INCLUDED_GIO_ENUMERATIONS,
seed_object_first=True,
)
+355
View File
@@ -0,0 +1,355 @@
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 methods(self) -> List["Method"]:
return [m for m in super().methods if not m.out_parameters or m.optional_out_parameter is not None]
@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
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)
facade_preludes: Tuple[str, ...] = ()
facade_epilogues: Tuple[str, ...] = ()
@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,
functions,
methods,
properties,
signals,
implements,
resolve_type,
model,
):
return ClassObjectType(
name,
c_type,
get_type,
type_struct,
parent,
constructors,
functions,
methods,
properties,
signals,
resolve_type,
model,
)
def _make_interface(
*,
name,
c_type,
get_type,
type_struct,
parent,
constructors,
functions,
methods,
properties,
signals,
resolve_type,
model,
):
return InterfaceObjectType(
name,
c_type,
get_type,
type_struct,
parent,
constructors,
functions,
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)
+110 -3
View File
@@ -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(), meson.project_source_root() / 'frida-bindgen'])
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(
meson.project_source_root() / 'frida-bindgen' / 'frida_bindgen_core' / '__init__.py',
meson.project_source_root() / 'frida-bindgen' / 'frida_bindgen_core' / 'loader.py',
meson.project_source_root() / 'frida-bindgen' / 'frida_bindgen_core' / 'model.py',
meson.project_source_root() / 'frida-bindgen' / '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',
)
+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,
#)
+14
View File
@@ -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
+1 -1
Submodule releng updated: 7ed66fe687...fe7d31dfbc.
+19 -4
View File
@@ -13,6 +13,8 @@ from setuptools.extension import Extension
SOURCE_ROOT = Path(__file__).resolve().parent
FRIDA_EXTENSION = os.environ.get("FRIDA_EXTENSION", None)
GENERATED_PACKAGE_FILES = ["__init__.py", "aio.py", "_frida.pyi"]
def main():
setup(
@@ -50,12 +52,12 @@ def main():
"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"]},
packages=["frida"],
package_data={"frida": ["py.typed"]},
ext_modules=[
Extension(
name="frida._frida",
sources=["frida/_frida/extension.c"],
sources=[],
py_limited_api=True,
)
],
@@ -112,6 +114,7 @@ class FridaPrebuiltExt(build_ext):
target = self.get_ext_fullpath(ext.name)
Path(target).parent.mkdir(parents=True, exist_ok=True)
shutil.copy(FRIDA_EXTENSION, target)
copy_generated_package(self.build_lib, Path(FRIDA_EXTENSION).parent)
class FridaDemandBuiltExt(build_ext):
@@ -119,11 +122,23 @@ class FridaDemandBuiltExt(build_ext):
make = SOURCE_ROOT / "make.bat" if platform.system() == "Windows" else "make"
subprocess.run([make], check=True)
outputs = [entry for entry in (SOURCE_ROOT / "build" / "frida" / "_frida").glob("_frida.*") if entry.is_file()]
outputs = [
entry
for entry in (SOURCE_ROOT / "build" / "frida").glob("_frida.*")
if entry.is_file() and entry.suffix != ".pyi"
]
assert len(outputs) == 1
target = self.get_ext_fullpath(ext.name)
Path(target).parent.mkdir(parents=True, exist_ok=True)
shutil.copy(outputs[0], target)
copy_generated_package(self.build_lib, SOURCE_ROOT / "build" / "frida")
def copy_generated_package(build_lib, generated):
destination = Path(build_lib) / "frida"
destination.mkdir(parents=True, exist_ok=True)
for name in GENERATED_PACKAGE_FILES:
shutil.copy(generated / name, destination / name)
if __name__ == "__main__":
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-core.git
revision = 17.7.3
revision = 588ac3b3ded6779f095825df5c64cab59f0c6774
depth = 1
[provide]
-4
View File
@@ -1,4 +0,0 @@
from .test_core import TestCore
from .test_rpc import TestRpc
__all__ = ["TestCore", "TestRpc"]
+38
View File
@@ -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
+20
View File
@@ -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:$repo/frida-bindgen 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" \
"$@"
+988
View File
@@ -0,0 +1,988 @@
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"
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(
(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_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}
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_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
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
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()
-49
View File
@@ -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()
View File
Whitespace-only changes.
-171
View File
@@ -1,171 +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(a, b) {
const result = a + b;
if (result < 0)
throw new Error("No");
return result;
},
sub(a, b) {
return a - b;
},
speak() {
const buf = Memory.allocUtf8String("Yo");
return Memory.readByteArray(buf, 2);
},
speakWithMetadata() {
const buf = Memory.allocUtf8String("Yo");
return ['soft', Memory.readByteArray(buf, 2)];
},
processData(val, data) {
return { val, dump: hexdump(data, { header: false }) };
},
};
""",
)
script.load()
agent = script.exports_sync
self.assertEqual(agent.add(2, 3), 5)
self.assertEqual(agent.sub(5, 3), 2)
self.assertRaises(Exception, lambda: agent.add(1, -2))
self.assertEqual(agent.speak(), b"\x59\x6f")
meta, data = agent.speak_with_metadata()
self.assertEqual(meta, "soft")
self.assertEqual(data, b"\x59\x6f")
result = agent.process_data(1337, b"\x13\x37")
self.assertEqual(result["val"], 1337)
self.assertEqual(result["dump"], "00000000 13 37 .7")
def test_post_failure(self):
script = self.session.create_script(
name="test-rpc",
source="""\
rpc.exports = {
init: function () {
},
};
""",
)
script.load()
agent = script.exports_sync
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_sync
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_sync
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_sync
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()