Compare commits

...
109 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 8c62357b94 subprojects: Prepare for release 2026-01-23 22:31:22 +01:00
Ole André Vadla Ravnås af43551888 subprojects: Bump outdated 2026-01-23 22:30:22 +01:00
Francesco Tamagni f332bddbfc fruity: Fix PortableCoreDevice flush infinite loop (#1207)
By reviving the call to `handle_events_completed` during FLUSHING state,
so that `pending_usb_ops` can get emptied eventually.
2026-01-23 22:29:05 +01:00
NSEcho e1bb883ba0 compiler: Rename conflictful Go symbols (#1205) 2026-01-23 12:40:11 +01:00
Ole André Vadla Ravnås a341cd1292 compiler: Load backend through memfd with fallback
So we retain the benefits of avoiding temporary files when possible.
2026-01-22 15:18:48 +01:00
Ole André Vadla Ravnås 70881a4e40 linux: Fix system session when memfd is restricted
Observed to happen in the Termux environment on Android.
2026-01-22 14:31:19 +01:00
Ole André Vadla Ravnås e0cae3fded subprojects: Prepare for release 2026-01-20 23:12:46 +01:00
Ole André Vadla Ravnås ff28ff33d9 subprojects: Bump outdated 2026-01-20 23:09:17 +01:00
Ole André Vadla Ravnås d072d48dfa subprojects: Bump outdated 2026-01-20 20:26:34 +01:00
Ole André Vadla Ravnås d8557b2c86 submodules: Bump releng 2026-01-20 20:26:34 +01:00
Ole André Vadla Ravnås de8a63b1f3 subprojects: Bump outdated 2026-01-20 18:54:21 +01:00
Ole André Vadla Ravnås 0f53d1b2ee submodules: Bump releng 2026-01-20 18:54:20 +01:00
Ole André Vadla Ravnås a638218794 compiler: Fix loading of backend on Android
The memfd path would typically fail because we were trying to open the
same underlying file with more restrictive permissions.

Avoid this and simplify things by using a temporary file even on systems
where memfd is supported.
2026-01-20 18:48:41 +01:00
Ole André Vadla Ravnås 5ea6e8507a file-descriptor: Add pwrite_all() 2026-01-20 18:45:16 +01:00
Ole André Vadla Ravnås 0ef2a0859b file-descriptor: Fix a stylistic inconsistency 2026-01-20 18:45:06 +01:00
Ole André Vadla Ravnås 01af5b3041 android: Place zymbiote payload on a better range
Picking a range not used by Zygote. The forked app/service might use it,
but by then we will have reverted it.
2026-01-20 13:02:54 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 3df3834f17 android: Fix zymbiote payload on BTI systems
The zymbiote payload must be built with BTI enabled on arm64, otherwise
indirect branches into it will fault.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-01-20 01:12:56 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 31d11965e0 linux: Fix alignment in the helper linker script
Ensure .payload is 4 KiB–aligned to match ADRP page semantics on arm64.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-01-20 01:10:06 +01:00
Ole André Vadla Ravnås 2e705e3f44 subprojects: Prepare for release 2026-01-17 08:43:29 +01:00
Ole André Vadla Ravnås 13c3e00be3 subprojects: Bump outdated 2026-01-17 00:24:19 +01:00
Ole André Vadla Ravnås 90164ccf7a subprojects: Bump outdated 2026-01-16 15:31:46 +01:00
Ole André Vadla Ravnås 3f9159e80a libc-shim: Revive FreeBSD support 2026-01-16 12:58:13 +00:00
Ole André Vadla Ravnås d4ca46a213 subprojects: Bump outdated 2026-01-16 13:44:36 +01:00
Ole André Vadla Ravnås 126ab29dce submodules: Bump releng 2026-01-16 13:44:36 +01:00
Ole André Vadla Ravnås 5104b0d08e libc-shim: Fix portability regressions
- Make stdio handling compatible with musl by treating FILE as
  opaque and fixing stdin/stdout/stderr definitions.
- Improve Linux compatibility.
- Add Darwin dirent shim.
- Fix MSVC support.
2026-01-16 13:34:11 +01:00
Ole André Vadla Ravnås 55c8bcb8ec fruity: Fix USB startup/shutdown race
Fix a race where stopping shortly after startup could leave the USB
worker blocked inside libusb_handle_events_completed().

Restructure the USB event loop to observe state transitions before
entering blocking libusb calls, ensuring FLUSHING and STOPPING are
handled deterministically.

Make initial USB enumeration during startup robust by gating completion
on a startup barrier and tracking in-flight arrivals, deduplicated by
device ID. This accounts for libusb HOTPLUG_ENUMERATE semantics where
arrival callbacks may be duplicated or delayed.

Together these changes ensure startup only completes once all initially
present devices are known, and shutdown cannot deadlock the USB thread.
2026-01-16 10:47:21 +01:00
Ole André Vadla Ravnås 4fbfae9b92 subprojects: Bump outdated 2026-01-15 18:07:56 +01:00
Ole André Vadla Ravnås 4d1e9f1113 submodules: Bump releng 2026-01-15 18:07:56 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 3e778a99c0 android: Move to light-weight Zygote hooking
Instead of injecting an internal agent into the Zygote/USAP processes
and using child gating, rethink this completely:

1. Write a tiny payload into a code region through /proc/$pid/mem.
2. Patch the ArtMethod of android.os.Process.setArgV0Native() so it
   jumps to the payload.
3. The payload acts as a proxy, chaining to the original setArgV0Native.
4. It also makes a UNIX socket connection back to frida-core, giving it
   a chance to instrument it.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-01-15 18:06:05 +01:00
Ole André Vadla Ravnås 2d2a944d8e file-descriptor: Add pread{,_all}() and pwrite() 2026-01-15 18:06:00 +01:00
Ole André Vadla Ravnås 0ba8b05fef linux: Fix the WaitResult constructor
Early returns from a struct constructor results in the Vala compiler
generating invalid C code. We'll just avoid this pattern for now.
2026-01-15 18:05:56 +01:00
Ole André Vadla Ravnås 783626e8fb android: Ignore noisy frida-helper.dex warnings
Coming from the dynamic linker on older Android OSes.
2026-01-15 12:53:39 +01:00
Ole André Vadla Ravnås 7399cc1092 android: Improve frida-helper.dex Android compat
- Support Android OS versions as far back as 4.4.
- Improve get_frontmost_application() to support a wider range of
  versions, not just Android >= 10.
2026-01-15 12:53:34 +01:00
Ole André Vadla Ravnås cefa0d0567 libc-shim: Improve stylistic consistency 2026-01-15 12:02:13 +01:00
Ole André Vadla Ravnås 0824ee85d9 libc-shim: Fix Android SELinux getline() UB
Fix a long-standing heap corruption/UB on Android caused by the SELinux
userspace library running a constructor that calls getline() and then
free().

Only free() went through our libc shim, while getline() allocated using
libc's heap, so the pointer was freed by the wrong allocator.

Add a minimal stdio shim (including getline()/getdelim()) and related
FILE*/__sF handling so allocations stay on the shim heap and are cleaned
up correctly.
2026-01-15 01:24:32 +01:00
Ole André Vadla Ravnås bea37618d1 android: Fix app spawn() on older Android versions 2026-01-13 22:45:36 +01:00
Ole André Vadla Ravnås cae7be99f8 meson: Fix Android dirty build from backend output 2026-01-11 14:21:56 +01:00
Ole André Vadla Ravnås 4406c8d146 android: Unlink frida-helper.dex on early error 2026-01-11 14:21:56 +01:00
Ole André Vadla Ravnås c89190e4f6 android: Explicitly set frida-helper.dex file mode 2026-01-11 14:21:56 +01:00
Ole André Vadla Ravnås dd68e84217 android: Improve frida-helper.dex spawn logic
- Include stderr and logcat output when unable to spawn.
- Process stdout and stderr. There shouldn't be any output on either,
  so if there is we definitely want to see it. This also makes it easier
  to add temporary logging to the helper.
2026-01-11 14:21:56 +01:00
Ole André Vadla Ravnås dc56b2eb16 subprojects: Bump outdated 2026-01-11 00:48:04 +01:00
Ole André Vadla Ravnås 1b908e69f3 android: Migrate to frida-helper.dex
Previously this helper was only used on non-rooted systems.
We now use it universally, which lets us stop injecting code
into system_server and drop the dependency on frida-java-bridge.

The only functionality lost is launch-timeout suppression.
2026-01-11 00:14:55 +01:00
Ole André Vadla Ravnås fae32353e1 linux: Fix ptrace signal waiter forwarding
Signal wait helpers could incorrectly forward ptrace-internal stops
(e.g. event SIGTRAPs) or syscall-stops back into the tracee while
waiting for a specific signal. This can perturb execution and break
spawn/attach flows.

Use WaitResult.should_deliver_to_tracee to gate signal reinjection.
Waiters now always satisfy the requested stop first, and only pass
through unmatched stops when they represent a real deliverable
signal.
2026-01-08 14:08:01 +01:00
Ole André Vadla Ravnås babe40274f linux: Fix ptrace syscall tracing desync
Syscall tracing could become desynchronized when the tracee stopped for
non-syscall reasons (e.g. PTRACE_EVENT_EXEC or real signals). These
SIGTRAP stops were incorrectly treated as syscall entry/exit points,
leading to bogus syscall IDs and eventual timeouts.

Fix this by decoding the raw waitpid() status into a structured
WaitResult, distinguishing syscall-stops, ptrace event stops, and real
signal stops. The syscall entry/exit phase is now advanced only on true
syscall-stops, and ptrace-internal traps are no longer reinjected into
the tracee.
2026-01-08 13:42:14 +01:00
Ole André Vadla Ravnås d19036305d subprojects: Bump outdated 2026-01-06 22:40:20 +01:00
Ole André Vadla Ravnås 1572fb6594 submodules: Bump releng 2026-01-06 22:40:19 +01:00
wyxhjr 747a9cd172 Add preliminary support for RISC-V (#1201) 2026-01-06 20:06:58 +01:00
Francesco Tamagni 8ff3a58612 fruity: Throw CLOSED on tcp write in closed state
In this way the stream stops being polled and an infinite loop is
avoided.
2026-01-06 20:03:50 +01:00
Ole André Vadla Ravnås bcd4a3d586 subprojects: Prepare for release 2025-12-15 21:28:35 +01:00
Ole André Vadla Ravnås 9937b40032 subprojects: Bump outdated 2025-12-15 21:16:49 +01:00
Ole André Vadla Ravnås 81299af1ef darwin: Use DO_NOT_REAP_CHILD when spawning helper
When the system is booted without -arm64e_preview_abi, the helper
binary is killed very early during startup. Enabling
DO_NOT_REAP_CHILD allows GLib to use posix_spawn(), insulating the
host process from signal-related side effects during helper launch.

This means the child process is no longer reaped automatically.
Since this code may run inside a host process that installs its own
signal handlers, GLib's child-watch support cannot be used. Instead,
the helper is reaped explicitly using non-blocking polling.
2025-12-15 17:54:19 +01:00
Ole André Vadla Ravnås f76a3315e7 submodules: Bump releng 2025-12-15 15:06:33 +01:00
Ole André Vadla Ravnås f5c01842c9 meson: Avoid repeating the GLib requirement 2025-12-15 15:05:40 +01:00
Ole André Vadla Ravnås 40879413c0 meson: Remove the unused frida_profile variable 2025-12-15 12:43:25 +01:00
Ole André Vadla Ravnås ab4a7f43f6 meson: Update version logic to support snapshot tags 2025-12-15 12:31:16 +01:00
Ole André Vadla Ravnås 8ed4a69e03 meson: Restore support for GLib being a subproject
Which was broken when gio-unix-2.0 became part of the public API on
non-Windows systems.
2025-12-15 12:09:12 +01:00
Ole André Vadla Ravnås d93557625d ci: Move xcframework .modulemap inside a subdir
To avoid clashes when an app links with multiple static frameworks, each
with their own modulemap.
2025-12-11 15:12:50 +01:00
Ole André Vadla Ravnås 7ecebf23b1 ci: Remove snapshot-tag.yml
Actually triggering ci.yml seems like a rabbit-hole. Leaving for later.
2025-12-08 23:48:06 +01:00
Ole André Vadla Ravnås e2b4b120d7 ci: Add modulemap to xcframework for FridaCore
Add a Clang module.modulemap to each header slice in the generated
FridaCore.xcframework so SwiftPM can load the FridaCore C module.
2025-12-08 23:44:34 +01:00
Ole André Vadla Ravnås e1cb645805 ci: Fix some stylistic inconsistencies 2025-12-08 23:43:35 +01:00
Ole André Vadla Ravnås a0ea09d4d6 ci: Improve snapshot-tag.yml so it triggers ci.yml 2025-12-08 22:46:53 +01:00
Ole André Vadla Ravnås 0f32cc8f4e darwin: Emit 128px app icons on macOS
Instead of two small sizes, we'll emit just one larger one.
2025-12-08 22:29:15 +01:00
Ole André Vadla Ravnås 46a566655f submodules: Bump releng 2025-12-08 22:21:08 +01:00
Ole André Vadla Ravnås 690597f908 ci: Add workflow for automated snapshot tagging 2025-12-08 22:21:08 +01:00
Ole André Vadla Ravnås 0c49c7ee62 ci: Publish releases with .xcframework 2025-12-08 22:21:08 +01:00
Ole André Vadla Ravnås ce40c30a20 ci: Build and upload .xcframework artifact 2025-12-08 22:21:08 +01:00
Ole André Vadla Ravnås 3bb44c222a ci: Add ios-arm64-simulator to the mobile matrix 2025-12-08 22:21:07 +01:00
Ole André Vadla Ravnås 5ec73cab32 ci: Temporarily disable mobile tests
As the backend infrastructure needs some love.
2025-12-08 18:05:39 +01:00
Ole André Vadla Ravnås 257f198659 ci: Fix the emulator job 2025-12-08 18:05:39 +01:00
Ole André Vadla Ravnås 5fca68ebe0 ci: Fix the macOS/x86_64 job
As --host was omitted, we ended up building for macOS/arm64.
2025-12-08 18:05:39 +01:00
Ole André Vadla Ravnås 9eab4f8975 linux: Add missing Gum #include
Which only worked in the past due to luck, i.e. some unrelated code
including it.
2025-11-12 13:18:00 +00:00
Ole André Vadla Ravnås afd8c8789f subprojects: Prepare for release 2025-11-05 18:59:04 +01:00
Ole André Vadla Ravnås cfd5a730e4 subprojects: Bump outdated 2025-11-05 18:57:16 +01:00
Ole André Vadla Ravnås 3f411908e7 subprojects: Prepare for release 2025-11-04 10:30:52 +01:00
Ole André Vadla Ravnås 4e44f4d243 subprojects: Bump outdated 2025-11-04 10:29:26 +01:00
Leo Conforti 9573f1aa22 compiler: Add platform and externals options
Introduce a platform property and externals API to CompilerOptions, and
plumb them through to the Go backend. The backend maps the platform to an
esbuild platform and passes externals to esbuild for exclusion from the
bundle.
2025-11-04 09:52:09 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 79a08e4767 simmy: Wire up spawn() argv and env options
Though `argv` was already wired up for the program case, we didn't wire
it up for the app case.

Co-authored-by: Håvard Sørbø <[email protected]>
2025-11-03 19:30:56 +01:00
Ole André Vadla Ravnås 57dfe9d5ad subprojects: Prepare for release 2025-11-01 00:38:11 +01:00
Ole André Vadla Ravnås 347662b45b subprojects: Bump outdated 2025-11-01 00:36:24 +01:00
Ole André Vadla Ravnås cc95375136 darwin: Revive app listing/launching on iOS >= 16
When running on rootful systems. This brings dccb612 back to life.
It was broken in 8108d4d, which fixed two Interceptor singleton leaks.
Turns out the one in springboard.m was intentional, as it was done as
part of init-once logic, with no teardown intended. And since nothing
else uses Interceptor in this case, the instrumentation we just
applied gets reverted right away.

Kudo to @alexhude for reporting.
2025-11-01 00:23:17 +01:00
Ole André Vadla Ravnås 329c4075d0 subprojects: Prepare for release 2025-10-31 20:24:06 +01:00
Ole André Vadla Ravnås c4198498d5 subprojects: Bump outdated 2025-10-31 20:21:36 +01:00
Ole André Vadla Ravnås 20ef7bb318 simmy: Degrade functionality when SIP is enabled
As get_frontmost_application() and icons rely on being able to inject
our internal agent into SpringBoard.

Down the road we might be able to reimplement this functionality as a
helper program that we spawn inside the Simulator runtime. The uncertain
part is whether we'll be able to give that program the appropriate
entitlements.
2025-10-31 20:09:20 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 367f8cf22b simmy: Handle app enumeration with selected IDs
Co-authored-by: Håvard Sørbø <[email protected]>
2025-10-30 14:58:35 +01:00
Ole André Vadla Ravnås 9f04675b87 subprojects: Prepare for release 2025-10-29 16:47:53 +01:00
Ole André Vadla Ravnås cd8bda0513 subprojects: Bump outdated 2025-10-29 16:45:40 +01:00
Ole André Vadla RavnåsandHåvard Sørbø e09e09fb85 simmy: Wire up application icons
Co-authored-by: Håvard Sørbø <[email protected]>
2025-10-29 15:08:50 +01:00
Ole André Vadla RavnåsandHåvard Sørbø c24a5da82c simmy: Implement get_frontmost_application()
Co-authored-by: Håvard Sørbø <[email protected]>
2025-10-29 15:01:15 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 05ec6563d1 internal-agent: Support non-local HostSession
So we can use it in the Simmy backend, too.

Co-authored-by: Håvard Sørbø <[email protected]>
2025-10-28 14:12:01 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 9dc9d30fa4 simmy: Fix the reported hardware.product
In query_system_parameters().

So it's e.g. `iPhone18,2` instead of `iPhone 17 Pro Max`.

Co-authored-by: Håvard Sørbø <[email protected]>
2025-10-28 13:15:38 +01:00
Ole André Vadla Ravnås 0b57936285 subprojects: Prepare for release 2025-10-24 15:25:15 +02:00
Ole André Vadla Ravnås 41be857a91 subprojects: Bump outdated 2025-10-24 15:23:37 +02:00
Ole André Vadla Ravnås afa847b898 submodules: Bump releng 2025-10-24 15:23:37 +02:00
Ole André Vadla Ravnås 3e43478de0 android: Bump frida-java-bridge in system-server 2025-10-24 15:22:18 +02:00
Ole André Vadla Ravnås a847a012c9 freebsd: Wire up PTY support 2025-10-12 08:11:13 +02:00
Ole André Vadla Ravnås c34fad1ff0 freebsd: Port remaining bits to the new stdio API 2025-10-12 07:50:00 +02:00
Ole André Vadla Ravnås ceef1d696b darwin: Fix a stylistic inconsistency 2025-10-12 07:47:09 +02:00
Ole André Vadla Ravnås d0cfe69dba subprojects: Prepare for release 2025-10-11 19:22:49 +02:00
Ole André Vadla Ravnås a35aff1865 api: Exclude HostSessionHub from .gir
As it's an internal interface.
2025-10-11 11:46:28 +02:00
Ole André Vadla Ravnås be8a59607a android: Bump frida-java-bridge in system-server 2025-10-11 11:07:51 +02:00
Ole André Vadla Ravnås 726740f96a simmy: Move to CoreSimulator.framework 2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås 9e11d57a76 Add new Simmy backend
For interacting with Apple's simulators on macOS.
2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås d8c3c9bfb5 darwin: Support early instrumentation of dyld_sim 2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås 2720968791 base: Add StdioPipes and FileDescriptor helpers
Factored out of the Darwin and Linux backends.
2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås b8998e52cd host-session: Support cross-backend communication 2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås c23f046968 value: Add VariantReader.list_members() 2025-10-10 12:34:41 +02:00
Francesco Tamagni 2d521bbeb7 fruity: Unpair on InvalidHostID
So the error doesn't become persistent, and the
device can be paired again.
2025-10-07 11:37:57 +02:00
Ole André Vadla Ravnås cdae790ba9 darwin: Port to the latest Gum.Darwin API 2025-10-01 10:52:33 +02:00
Ole André Vadla Ravnås 90d2d93799 subprojects: Bump outdated 2025-10-01 10:49:57 +02:00
87 changed files with 8241 additions and 4637 deletions

No files matched your search

+145 -6
View File
@@ -2,11 +2,15 @@ name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
inputs:
tag:
description: "Tag to build/test"
required: false
type: string
env:
FRIDA_CORE_OPTIONS: '--with-devkits=core --enable-tests'
@@ -71,6 +75,7 @@ jobs:
if: ${{ !startsWith(matrix.id, 'windows-') && matrix.id != 'linux-x86' }}
run: |
./configure \
--host=${{ matrix.id }} \
--enable-compiler-backend \
${{ env.FRIDA_CORE_OPTIONS }}
make
@@ -205,7 +210,7 @@ jobs:
/sbin/entropy
cat /etc/motd
export PS1='\u@frida:\w\$ '
/root/frida-tests
/root/tests/frida-tests
status=\$?
echo "exit status: \$status"
if [ \$status -eq 0 ]; then
@@ -281,7 +286,8 @@ jobs:
COPY initscript /tmp/initramfs/init
RUN chmod +x /tmp/initramfs/init
COPY build/tests /tmp/initramfs/root/
COPY build/tests /tmp/initramfs/root/tests/
COPY build/lib/agent/frida-agent.so /tmp/initramfs/root/lib/agent/
RUN find . | cpio -o --format=newc -R root:root > /root/initramfs.img
COPY launch.sh /root/launch.sh
@@ -297,6 +303,7 @@ jobs:
matrix:
id:
- ios-arm64
- ios-arm64-simulator
- android-x86
- android-x86_64
- android-arm
@@ -341,6 +348,7 @@ jobs:
name: core-devkit-${{ matrix.id }}
path: build/src/devkit/
- name: Package tests
if: false
run: |
mkdir -p /tmp/pkg
cd build
@@ -360,7 +368,7 @@ jobs:
esac
tar -C /tmp/pkg -czf /tmp/runner.tar.gz .
- name: Test on Corellium iOS device
if: matrix.id == 'ios-arm64'
if: false
continue-on-error: true
uses: frida/corellium-action@v4
with:
@@ -376,7 +384,7 @@ jobs:
tar xf $ASSET_PATH
./frida-tests
- name: Test on Corellium Android device
if: matrix.id == 'android-arm64'
if: false
continue-on-error: true
uses: frida/corellium-action@v4
with:
@@ -480,3 +488,134 @@ jobs:
else
echo "No unexpected changes detected."
fi
xcframework:
name: Build FridaCore.xcframework
runs-on: macos-latest
needs:
- native
- mobile
steps:
- name: Check out repo
uses: actions/checkout@v4
- name: Download macOS/x86_64 devkit
uses: actions/download-artifact@v4
with:
name: core-devkit-macos-x86_64
path: devkits/macos-x86_64
- name: Download macOS/arm64 devkit
uses: actions/download-artifact@v4
with:
name: core-devkit-macos-arm64
path: devkits/macos-arm64
- name: Download iOS/arm64 devkit
uses: actions/download-artifact@v4
with:
name: core-devkit-ios-arm64
path: devkits/ios-arm64
- name: Download iOS/arm64-simulator devkit
uses: actions/download-artifact@v4
with:
name: core-devkit-ios-arm64-simulator
path: devkits/ios-arm64-simulator
- name: Create FridaCore.xcframework
shell: bash
run: |
set -euo pipefail
mkdir -p devkits/macos-universal
lipo -create \
devkits/macos-x86_64/libfrida-core.a \
devkits/macos-arm64/libfrida-core.a \
-output devkits/macos-universal/libfrida-core.a
platforms=(
"macos-universal"
"ios-arm64"
"ios-arm64-simulator"
)
for platform in "${platforms[@]}"; do
mkdir -p "xcframework_headers/${platform}/FridaCore"
done
cp devkits/macos-x86_64/frida-core.h \
xcframework_headers/macos-universal/frida-core-x86_64.h
cp devkits/macos-arm64/frida-core.h \
xcframework_headers/macos-universal/frida-core-arm64.h
cat > xcframework_headers/macos-universal/frida-core.h <<'EOF'
#if defined (__x86_64__)
# include "frida-core-x86_64.h"
#elif defined (__arm64__)
# include "frida-core-arm64.h"
#else
# error "Unsupported macOS architecture for FridaCore"
#endif
EOF
cp devkits/ios-arm64/frida-core.h \
xcframework_headers/ios-arm64/frida-core.h
cp devkits/ios-arm64-simulator/frida-core.h \
xcframework_headers/ios-arm64-simulator/frida-core.h
for platform in "${platforms[@]}"; do
cat > "xcframework_headers/${platform}/FridaCore/module.modulemap" <<'EOF'
module FridaCore [extern_c] {
header "../frida-core.h"
export *
}
EOF
done
xcodebuild -create-xcframework \
-library devkits/macos-universal/libfrida-core.a \
-headers xcframework_headers/macos-universal \
-library devkits/ios-arm64/libfrida-core.a \
-headers xcframework_headers/ios-arm64 \
-library devkits/ios-arm64-simulator/libfrida-core.a \
-headers xcframework_headers/ios-arm64-simulator \
-output FridaCore.xcframework
- name: Upload FridaCore.xcframework
uses: actions/upload-artifact@v4
with:
name: FridaCore.xcframework
path: FridaCore.xcframework
publish-xcframework:
if: github.ref_type == 'tag'
runs-on: macos-latest
needs:
- xcframework
steps:
- name: Download FridaCore.xcframework artifact
uses: actions/download-artifact@v4
with:
name: FridaCore.xcframework
path: dist
- name: Rewrap into FridaCore.xcframework directory
shell: bash
run: |
set -euo pipefail
cd dist
mkdir -p FridaCore.xcframework
shopt -s dotglob
for f in *; do
if [[ "$f" != "FridaCore.xcframework" ]]; then
mv "$f" FridaCore.xcframework/
fi
done
- name: Zip FridaCore.xcframework
run: |
cd dist
zip -r FridaCore.xcframework.zip FridaCore.xcframework
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
files: |
dist/FridaCore.xcframework.zip
prerelease: ${{ contains(github.ref_name, 'snapshot') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -1,5 +1,6 @@
/build/
/deps/
/src/android-helper/build/
/src/barebone/agent/target/
/src/barebone/agent/xpacks/
/src/barebone/helpers/memory-scanner/target/
@@ -11,7 +12,6 @@
/src/darwin/agent/osanalytics/osanalytics.js
/src/darwin/agent/reportcrash/node_modules/
/src/darwin/agent/reportcrash/reportcrash.js
/src/droidy/helper/build/
/src/fruity/helpers/build/
/src/linux/agent/system-server/node_modules/
/src/linux/agent/system-server/system-server.js
+5 -5
View File
@@ -24,7 +24,9 @@ _frida_agent_environment_init (void)
been_here = TRUE;
#endif
gum_init_embedded ();
#ifdef _MSC_VER
frida_libc_shim_init ();
#endif
gio_init ();
g_thread_set_garbage_handler (_frida_agent_on_pending_thread_garbage, NULL);
@@ -54,13 +56,11 @@ _frida_agent_environment_deinit (void)
glib_shutdown ();
gio_deinit ();
gum_deinit_embedded ();
frida_run_atexit_handlers ();
# ifdef HAVE_DARWIN
/* Do what frida_deinit_memory() does on the other platforms. */
gum_internal_heap_unref ();
# if defined (_MSC_VER) || defined (HAVE_DARWIN)
frida_libc_shim_deinit ();
# endif
#endif
}
+2 -2
View File
@@ -50,9 +50,9 @@ raw_agent = shared_library('frida-agent-raw', agent_sources,
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
)
operations = ['--move', 'constructor', 'frida_init_memory', 'first']
operations = ['--move', 'constructor', 'frida_libc_shim_init', 'first']
if host_os_family != 'darwin'
operations += ['--move', 'destructor', 'frida_deinit_memory', 'first']
operations += ['--move', 'destructor', 'frida_libc_shim_deinit', 'first']
endif
modulated_agent = custom_target('frida-agent-modulated',
input: raw_agent,
+10
View File
@@ -56,6 +56,16 @@
# else
# error Unexpected MIPS ABI
# endif
#elif defined (HAVE_RISCV)
# if GLIB_SIZEOF_VOID_P == 8
# define SYS_process_vm_readv 270
# define SYS_process_vm_writev 271
# define SYS_memfd_create 279
# define SYS_pidfd_open 434
# define SYS_pidfd_getfd 438
# else
# error RISC-V 32-bit not yet supported
# endif
#else
# error FIXME
#endif
-23
View File
@@ -11,29 +11,6 @@ namespace Frida {
return (linux_major == major && linux_minor >= minor) || linux_major > major;
}
public class FileDescriptor : Object, FileDescriptorBased {
public int handle;
public FileDescriptor (int handle) {
this.handle = handle;
}
~FileDescriptor () {
if (handle != -1)
Posix.close (handle);
}
public int steal () {
int result = handle;
handle = -1;
return result;
}
public int get_fd () {
return handle;
}
}
public sealed class PidFileDescriptor : FileDescriptor {
private uint pid;
+17 -2
View File
@@ -19,6 +19,13 @@ base_sources = [
extra_vala_args = []
if host_os == 'windows'
base_sources += 'windows.vala'
else
base_sources += 'posix.vala'
extra_vala_args += '--pkg=posix'
endif
if host_os == 'macos'
base_sources += [
'xpc.vala',
@@ -28,6 +35,7 @@ endif
if host_os_family == 'darwin'
base_sources += 'corefoundation.vala'
extra_vala_args += '--pkg=darwin-xnu'
endif
if host_os_family == 'linux'
@@ -35,13 +43,15 @@ if host_os_family == 'linux'
extra_vala_args += [
'--pkg=linux',
'--pkg=linux-system',
'--pkg=posix',
]
endif
if host_os_family == 'freebsd'
extra_vala_args += '--pkg=freebsd'
endif
extra_deps = []
if host_os_family != 'windows'
extra_vala_args += '--pkg=gio-unix-2.0'
extra_deps += gio_unix_dep
endif
@@ -49,6 +59,11 @@ base_vala_args = [gum_vala_args, '--pkg=gio-2.0', '--pkg=json-glib-1.0']
if nice_dep.found()
base_vala_args += '--pkg=nice'
endif
if host_os_family != 'windows'
base_vala_args += [
'--pkg=gio-unix-2.0',
]
endif
if host_os_family == 'darwin'
base_vala_args += [
'--pkg=corefoundation',
+223
View File
@@ -0,0 +1,223 @@
namespace Frida {
public sealed class StdioPipes : Object {
public OutputStream? input {
get;
construct;
}
public InputStream output {
get;
construct;
}
public InputStream error {
get;
construct;
}
public StdioPipes (FileDescriptor? input, FileDescriptor output, FileDescriptor error) {
Object (
input: (input != null) ? new UnixOutputStream (ensure_nonblocking (input.steal ()), true) : null,
output: new UnixInputStream (ensure_nonblocking (output.steal ()), true),
error: new UnixInputStream (ensure_nonblocking (error.steal ()), true)
);
}
private static int ensure_nonblocking (int fd) {
try {
Unix.set_fd_nonblocking (fd, true);
return fd;
} catch (GLib.Error e) {
assert_not_reached ();
}
}
}
public class FileDescriptor : Object, FileDescriptorBased {
public int handle;
public FileDescriptor (int handle) {
this.handle = handle;
}
~FileDescriptor () {
if (handle != -1)
Posix.close (handle);
}
public int steal () {
int result = handle;
handle = -1;
return result;
}
public int get_fd () {
return handle;
}
public size_t pread (uint8[] buf, uint64 offset) throws Error {
while (true) {
ssize_t res = Posix.pread (handle, buf, buf.length, (Posix.off_t) offset);
if (res == -1) {
if (errno == Posix.EINTR)
continue;
throw new Error.TRANSPORT ("%s", strerror (errno));
}
return res;
}
}
public void pread_all (uint8[] buf, uint64 offset) throws Error {
size_t total = 0;
while (total != buf.length) {
void * dst = (uint8 *) buf + total;
size_t remaining = buf.length - total;
while (true) {
ssize_t res = Posix.pread (handle, dst, remaining, (Posix.off_t) (offset + total));
if (res == -1) {
if (errno == Posix.EINTR)
continue;
throw new Error.TRANSPORT ("%s", strerror (errno));
}
if (res == 0)
throw new Error.TRANSPORT ("Unexpected EOF: read %zu of %zu bytes", total, buf.length);
total += (size_t) res;
break;
}
}
}
public size_t pwrite (uint8[] buf, uint64 offset) throws Error {
while (true) {
ssize_t res = Posix.pwrite (handle, buf, buf.length, (Posix.off_t) offset);
if (res == -1) {
if (errno == Posix.EINTR)
continue;
throw new Error.TRANSPORT ("%s", strerror (errno));
}
return res;
}
}
public void pwrite_all (uint8[] buf, uint64 offset) throws Error {
size_t total = 0;
while (total != buf.length) {
uint8[] chunk = buf[total : buf.length];
while (true) {
ssize_t res = Posix.pwrite (handle, chunk, chunk.length, (Posix.off_t) (offset + total));
if (res == -1) {
if (errno == Posix.EINTR)
continue;
throw new Error.TRANSPORT ("%s", strerror (errno));
}
total += (size_t) res;
break;
}
}
}
}
public StdioPipes? make_stdio_pipes (Stdio stdio, bool in_supported, out FileDescriptor? in_fd, out string? in_name,
out FileDescriptor? out_fd, out string? out_name, out FileDescriptor? err_fd, out string? err_name) throws Error {
if (stdio == PIPE) {
FileDescriptor? in_write = null;
FileDescriptor? out_read, err_read;
if (in_supported) {
make_stdio_pipe (out in_fd, out in_write, out in_name);
} else {
in_fd = null;
in_name = null;
}
make_stdio_pipe (out out_read, out out_fd, out out_name);
make_stdio_pipe (out err_read, out err_fd, out err_name);
return new StdioPipes (in_write, out_read, err_read);
} else {
in_fd = null;
in_name = null;
out_fd = null;
out_name = null;
err_fd = null;
err_name = null;
return null;
}
}
public void make_stdio_pipe (out FileDescriptor read, out FileDescriptor write, out string? name = null) throws Error {
#if HAVE_OPENPTY
int rfd = -1, wfd = -1;
char buf[Posix.Limits.PATH_MAX];
var res =
#if DARWIN
Darwin.XNU.openpty (out rfd, out wfd, buf, null, null)
#elif LINUX
Linux.openpty (out rfd, out wfd, buf, null, null)
#elif FREEBSD
FreeBSD.openpty (out rfd, out wfd, buf, null, null)
#endif
;
if (res == -1)
throw new Error.NOT_SUPPORTED ("Unable to open PTY: %s", strerror (errno));
name = (string) buf;
enable_close_on_exec (rfd);
enable_close_on_exec (wfd);
disable_sigpipe (rfd);
disable_sigpipe (wfd);
configure_terminal_attributes (rfd);
read = new FileDescriptor (rfd);
write = new FileDescriptor (wfd);
#else
name = null;
try {
int fds[2];
Unix.open_pipe (fds, Posix.FD_CLOEXEC);
read = new FileDescriptor (fds[0]);
write = new FileDescriptor (fds[1]);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to open pipe: %s", e.message);
}
#endif
}
#if HAVE_OPENPTY
private void enable_close_on_exec (int fd) {
Posix.fcntl (fd, Posix.F_SETFD, Posix.fcntl (fd, Posix.F_GETFD) | Posix.FD_CLOEXEC);
}
private void disable_sigpipe (int fd) {
#if DARWIN
Posix.fcntl (fd, Darwin.XNU.F_SETNOSIGPIPE, true);
#endif
}
private void configure_terminal_attributes (int fd) {
var tios = Posix.termios ();
Posix.tcgetattr (fd, out tios);
tios.c_oflag &= ~Posix.ONLCR;
tios.c_cflag = (tios.c_cflag & Posix.CLOCAL) | Posix.CS8 | Posix.CREAD | Posix.HUPCL;
tios.c_lflag &= ~Posix.ECHO;
Posix.tcsetattr (fd, 0, tios);
}
#endif
}
+16
View File
@@ -57,6 +57,22 @@ namespace Frida {
push_scope (v);
}
public string[] list_members () throws Error {
var scope = peek_scope ();
if (scope.dict == null)
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
var members = new Gee.ArrayList<string> ();
foreach (var item in scope.val) {
string k;
Variant v;
item.get ("{sv}", out k, out v);
members.add (k);
}
return members.to_array ();
}
public bool has_member (string name) throws Error {
var scope = peek_scope ();
if (scope.dict == null)
+22
View File
@@ -0,0 +1,22 @@
namespace Frida {
public sealed class StdioPipes : Object {
public OutputStream input {
get;
construct;
}
public InputStream output {
get;
construct;
}
public InputStream error {
get;
construct;
}
public StdioPipes (OutputStream input, InputStream output, InputStream error) {
Object (input: input, output: output, error: error);
}
}
}
+5 -5
View File
@@ -84,7 +84,9 @@ frida_on_unload (void)
void
frida_gadget_environment_init (void)
{
gum_init_embedded ();
#ifdef _MSC_VER
frida_libc_shim_init ();
#endif
gio_init ();
g_thread_set_garbage_handler (_frida_gadget_on_pending_thread_garbage, NULL);
@@ -136,13 +138,11 @@ frida_gadget_environment_deinit (void)
glib_shutdown ();
gio_deinit ();
gum_deinit_embedded ();
frida_run_atexit_handlers ();
#ifdef HAVE_DARWIN
/* Do what frida_deinit_memory() does on the other platforms. */
gum_internal_heap_unref ();
#if defined (_MSC_VER) || defined (HAVE_DARWIN)
frida_libc_shim_deinit ();
#endif
}
+2 -2
View File
@@ -69,12 +69,12 @@ raw_gadget = shared_library('frida-gadget-raw', gadget_sources,
)
operations = [
'--move', 'constructor', 'frida_init_memory', 'first',
'--move', 'constructor', 'frida_libc_shim_init', 'first',
'--move', 'constructor', 'frida_on_load', 'last',
]
if host_os_family != 'darwin'
operations += [
'--move', 'destructor', 'frida_deinit_memory', 'first',
'--move', 'destructor', 'frida_libc_shim_deinit', 'first',
'--move', 'destructor', 'frida_on_unload', 'last',
]
endif
+1692 -68
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -1,4 +1,7 @@
namespace Frida {
#if DARWIN
public extern void libc_shim_deinit ();
#endif
public extern void run_atexit_handlers ();
public extern uint get_process_id ();
+45 -12
View File
@@ -32,18 +32,34 @@ frida_version = get_option('frida_version')
if frida_version == ''
frida_version = run_command(releng / 'frida_version.py', meson.project_source_root(), check: true).stdout().strip()
endif
tokens = frida_version.split('.')
frida_major_version = tokens[0].to_int()
frida_minor_version = tokens[1].to_int()
if tokens.length() == 4
frida_micro_version = tokens[2].split('-')[0].to_int() - 1
frida_nano_version = tokens[3].to_int() + 1
is_dev = frida_version.contains('-dev.')
if is_dev
dev_parts = frida_version.split('-dev.')
assert(dev_parts.length() == 2)
prefix = dev_parts[0]
frida_nano_version = dev_parts[1].to_int() + 1
else
assert(tokens.length() == 3)
frida_micro_version = tokens[2].to_int()
prefix = frida_version
frida_nano_version = 0
endif
frida_profile = (frida_version != '0.0.0' and frida_nano_version == 0) ? 'prod' : 'dev'
parts = prefix.split('-')
core = parts[0]
has_suffix = parts.length() > 1
core_tokens = core.split('.')
assert(core_tokens.length() == 3)
frida_major_version = core_tokens[0].to_int()
frida_minor_version = core_tokens[1].to_int()
micro_encoded = core_tokens[2].to_int()
if is_dev and not has_suffix
frida_micro_version = micro_encoded - 1
else
frida_micro_version = micro_encoded
endif
api_version = '1.0'
@@ -92,6 +108,12 @@ elif host_machine.cpu_family() == 'mips64'
else
host_abi = 'mips64'
endif
elif host_machine.cpu_family() == 'riscv32'
host_arch = 'riscv'
host_abi = 'riscv32'
elif host_machine.cpu_family() == 'riscv64'
host_arch = 'riscv'
host_abi = 'riscv64'
else
host_arch = host_machine.cpu_family()
host_abi = host_arch
@@ -236,6 +258,7 @@ cpu_defines = [
['arm', 'HAVE_ARM'],
['arm64', 'HAVE_ARM64'],
['mips', 'HAVE_MIPS'],
['riscv', 'HAVE_RISCV'],
]
foreach d : cpu_defines
if d.get(0) == host_arch
@@ -292,6 +315,7 @@ gum_dep = dependency('frida-gum-1.0', default_options: [
gumjs_dep = dependency('frida-gumjs-1.0')
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0')
glib_version_req = '>=2.74'
glib_options = [
'cocoa=disabled',
'selinux=disabled',
@@ -315,7 +339,7 @@ if optimize_for_prod
gee_options += 'disable-internal-asserts=true'
endif
glib_dep = dependency('glib-2.0', version: '>=2.72', default_options: glib_options)
glib_dep = dependency('glib-2.0', version: glib_version_req, default_options: glib_options)
gobject_dep = dependency('gobject-2.0')
gmodule_dep = dependency('gmodule-2.0')
gio_dep = dependency('gio-2.0')
@@ -346,7 +370,7 @@ quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), de
])
lzfse_dep = dependency('lzfse', required: get_option('barebone_backend'))
native_glib_dep = dependency('glib-2.0', version: '>=2.74', native: true, default_options: glib_options)
native_glib_dep = dependency('glib-2.0', version: glib_version_req, native: true, default_options: glib_options)
native_gio_dep = dependency('gio-2.0', native: true)
native_gee_dep = dependency('gee-0.8', native: true, default_options: gee_options)
native_brotlienc_dep = dependency('libbrotlienc', native: true)
@@ -403,6 +427,10 @@ else
endif
have_local_backend = get_option('local_backend').allowed()
have_simmy_backend = get_option('simmy_backend') \
.disable_auto_if(host_os != 'macos') \
.require(host_os == 'macos', error_message: 'Simmy backend is only available on macOS') \
.allowed()
have_fruity_backend = get_option('fruity_backend') \
.disable_auto_if(not host_docks_mobile_devices) \
.allowed()
@@ -491,6 +519,7 @@ build_tests = get_option('tests') \
.allowed()
foreach b : [['local', have_local_backend],
['simmy', have_simmy_backend],
['fruity', have_fruity_backend],
['droidy', have_droidy_backend],
['socket', have_socket_backend],
@@ -581,7 +610,7 @@ if lzfse_dep.found()
backend_reqs_private += 'lzfse'
endif
if host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>')
if host_os_family in ['darwin', 'freebsd'] or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
vala_flags += '--define=HAVE_OPENPTY'
endif
@@ -653,6 +682,9 @@ endif
if host_os in ['ios', 'tvos']
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
endif
if host_os_family == 'freebsd'
backend_libs_private += ['-lutil']
endif
if get_option('devkits').length() != 0
if get_option('default_library') != 'static'
@@ -786,6 +818,7 @@ endif
summary(
{
'local': have_local_backend,
'simmy': have_simmy_backend,
'fruity': have_fruity_backend,
'droidy': have_droidy_backend,
'socket': have_socket_backend,
+6
View File
@@ -10,6 +10,12 @@ option('local_backend',
description: 'Include backend for the local system',
)
option('simmy_backend',
type: 'feature',
value: 'auto',
description: 'Include the iDevice Simulator backend',
)
option('fruity_backend',
type: 'feature',
value: 'auto',
+1 -1
Submodule releng updated: c24becede0...60585cf5cf.
@@ -1,11 +1,15 @@
ANDROID_API_LEVEL ?= 21
ANDROID_SDK_ROOT ?= $(shell echo ~/Library/Android/sdk)
ANDROID_API_LEVEL ?= 19
ANDROID_SDK_ROOT ?= $(shell echo ~/Android/Sdk)
android_platform := android-$(ANDROID_API_LEVEL)
android_jar := $(ANDROID_SDK_ROOT)/platforms/$(android_platform)/android.jar
build/frida-helper.dex: build/frida-helper.jar
dx --dex --output=$@ $<
helper.dex: build/frida-helper.jar
d8 \
--classpath $(android_jar) \
--output build/ \
$<
mv build/classes.dex $@
build/frida-helper.jar: re/frida/Helper.java
javac \
+738
View File
@@ -0,0 +1,738 @@
namespace Frida {
public sealed class AndroidHelperClient : Object, AsyncInitable {
public signal void closed ();
public IOStream stream {
get;
construct;
}
private BufferedInputStream input;
private OutputStream output;
private Cancellable io_cancellable = new Cancellable ();
private State state = CLOSED;
private Gee.Queue<Request> pending_requests = new Gee.ArrayQueue<Request> ();
private ByteArray pending_output = new ByteArray ();
private bool writing = false;
private enum State {
CLOSED,
OPEN
}
private const uint32 MAX_RESPONSE_SIZE = 100 * 1024 * 1024;
public AndroidHelperClient (IOStream stream) {
Object (stream: stream);
}
construct {
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
"base-stream", stream.get_input_stream (),
"close-base-stream", false,
"buffer-size", 128 * 1024);
output = stream.get_output_stream ();
state = OPEN;
process_incoming_responses.begin ();
}
public async void close (Cancellable? cancellable) throws IOError {
io_cancellable.cancel ();
var source = new IdleSource ();
source.set_callback (close.callback);
source.attach (MainContext.get_thread_default ());
yield;
try {
yield stream.close_async (Priority.DEFAULT, cancellable);
} catch (IOError e) {
}
ensure_closed ();
}
private void ensure_closed () {
if (state == CLOSED)
return;
state = CLOSED;
closed ();
}
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("get-frontmost-application")
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
if (response.is_null ())
return HostApplicationInfo.empty ();
Json.Reader reader = new Json.Reader (response);
string? identifier = null;
string? name = null;
uint pid = 0;
HashTable<string, Variant> parameters = make_parameters_dict ();
if (reader.read_element (0)) {
identifier = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (1)) {
name = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (2)) {
pid = (uint) reader.get_int_value ();
reader.end_element ();
}
if (reader.read_element (3)) {
if (reader.is_object ())
add_parameters_from_json (parameters, reader);
reader.end_element ();
}
GLib.Error? error = reader.get_error ();
if (error != null)
throw new Error.PROTOCOL ("%s", error.message);
return HostApplicationInfo (identifier, name, pid, (owned) parameters);
}
public async HostApplicationInfo[] enumerate_applications (ApplicationQueryOptions options, Cancellable? cancellable)
throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("enumerate-applications");
stanza.begin_array ();
options.enumerate_selected_identifiers (identifier => stanza.add_string_value (identifier));
stanza.end_array ();
stanza
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Reader reader = new Json.Reader (response);
int num_apps = reader.count_elements ();
if (num_apps == -1)
throw new Error.PROTOCOL ("Invalid response from helper service");
var result = new HostApplicationInfo[0];
for (int i = 0; i != num_apps; i++) {
reader.read_element (i);
string? identifier = null;
string? name = null;
uint pid = 0;
HashTable<string, Variant> parameters = make_parameters_dict ();
if (reader.read_element (0)) {
identifier = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (1)) {
name = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (2)) {
pid = (uint) reader.get_int_value ();
reader.end_element ();
}
if (reader.read_element (3)) {
if (reader.is_object ())
add_parameters_from_json (parameters, reader);
reader.end_element ();
}
GLib.Error? error = reader.get_error ();
if (error != null)
throw new Error.PROTOCOL ("%s", error.message);
result += HostApplicationInfo (identifier, name, pid, (owned) parameters);
reader.end_element ();
}
return result;
}
public async HostProcessInfo[] enumerate_processes (ProcessQueryOptions options, Cancellable? cancellable)
throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("enumerate-processes");
stanza.begin_array ();
options.enumerate_selected_pids (pid => stanza.add_int_value (pid));
stanza.end_array ();
stanza
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Reader reader = new Json.Reader (response);
int num_processes = reader.count_elements ();
if (num_processes == -1)
throw new Error.PROTOCOL ("Invalid response from helper service");
var result = new HostProcessInfo[0];
for (int i = 0; i != num_processes; i++) {
reader.read_element (i);
uint pid = 0;
string? name = null;
HashTable<string, Variant> parameters = make_parameters_dict ();
if (reader.read_element (0)) {
pid = (uint) reader.get_int_value ();
reader.end_element ();
}
if (reader.read_element (1)) {
name = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (2)) {
if (reader.is_object ())
add_parameters_from_json (parameters, reader);
reader.end_element ();
}
GLib.Error? error = reader.get_error ();
if (error != null)
throw new Error.PROTOCOL ("%s", error.message);
result += HostProcessInfo (pid, name, (owned) parameters);
reader.end_element ();
}
return result;
}
public async string get_process_name (string package, int uid, Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("get-process-name")
.add_string_value (package)
.add_int_value (uid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
r.read_element (1);
string? process_name = r.get_string_value ();
if (process_name == null)
throw new Error.PROTOCOL ("Malformed response");
return process_name;
}
public async void start_package (string package, PackageEntrypoint entrypoint, Cancellable? cancellable)
throws Error, IOError {
if (entrypoint is DefaultActivityEntrypoint) {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("start-activity")
.add_string_value (package)
.add_null_value ()
.add_int_value (entrypoint.uid)
.end_array ();
yield request_ok (stanza, cancellable);
return;
}
if (entrypoint is ActivityEntrypoint) {
var e = (ActivityEntrypoint) entrypoint;
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("start-activity")
.add_string_value (package)
.add_string_value (e.activity)
.add_int_value (entrypoint.uid)
.end_array ();
yield request_ok (stanza, cancellable);
return;
}
if (entrypoint is BroadcastReceiverEntrypoint) {
var e = (BroadcastReceiverEntrypoint) entrypoint;
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("send-broadcast")
.add_string_value (package)
.add_string_value (e.receiver)
.add_string_value (e.action)
.add_int_value (entrypoint.uid)
.end_array ();
yield request_ok (stanza, cancellable);
return;
}
assert_not_reached ();
}
public async void stop_package (string package, int uid, Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("stop-package")
.add_string_value (package)
.add_int_value (uid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
}
public async bool try_stop_package_by_pid (uint pid, Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("try-stop-package-by-pid")
.add_int_value ((int) pid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
r.read_element (1);
var val = r.get_value ();
if (val == null || val.get_value_type () != typeof (bool))
throw new Error.PROTOCOL ("Malformed response");
return r.get_boolean_value ();
}
private void parse_envelope_or_throw (Json.Reader r) throws Error {
r.read_element (0);
string? kind = r.get_string_value ();
if (kind == null)
throw new Error.PROTOCOL ("Malformed response");
r.end_element ();
if (kind == "ok")
return;
if (kind == "error") {
r.read_element (1);
string? code = r.get_string_value ();
if (code == null)
throw new Error.PROTOCOL ("Malformed response");
r.end_element ();
r.read_element (2);
string? message = r.get_string_value ();
if (message == null)
throw new Error.PROTOCOL ("Malformed response");
r.end_element ();
if (code == "INVALID_ARGUMENT")
throw new Error.INVALID_ARGUMENT ("%s", message);
throw new Error.NOT_SUPPORTED ("%s", message);
}
throw new Error.PROTOCOL ("Malformed response");
}
private static void add_parameters_from_json (HashTable<string, Variant> parameters, Json.Reader reader) throws Error {
foreach (string name in reader.list_members ()) {
reader.read_member (name);
if (reader.is_value ()) {
Json.Node val = reader.get_value ();
parameters[name] = variant_from_json_value (val);
} else if (reader.is_array ()) {
int length = reader.count_elements ();
if (length == 0)
throw new Error.PROTOCOL ("Unexpected JSON array element shape");
if (name == "$icons") {
var icons = new VariantBuilder (new VariantType.array (VariantType.VARDICT));
for (int i = 0; i != length; i++) {
reader.read_element (i);
string? png_str = reader.get_string_value ();
if (png_str == null)
throw new Error.PROTOCOL ("Unexpected JSON icon type");
var png = new Bytes.take (Base64.decode (png_str));
icons.open (VariantType.VARDICT);
icons.add ("{sv}", "format", new Variant.string ("png"));
icons.add ("{sv}", "image", Variant.new_from_data (new VariantType ("ay"),
png.get_data (), true, png));
icons.close ();
reader.end_element ();
}
parameters["icons"] = icons.end ();
} else {
reader.read_element (0);
if (!reader.is_value ())
throw new Error.PROTOCOL ("Unexpected JSON array element type");
Variant first_element = variant_from_json_value (reader.get_value ());
reader.end_element ();
var builder = new VariantBuilder (new VariantType.array (first_element.get_type ()));
builder.add_value (first_element);
for (int i = 1; i != length; i++) {
reader.read_element (i);
if (!reader.is_value ())
throw new Error.PROTOCOL ("Unexpected JSON array element type");
builder.add_value (variant_from_json_value (reader.get_value ()));
reader.end_element ();
}
parameters[name] = builder.end ();
}
} else {
throw new Error.PROTOCOL ("Unexpected JSON type");
}
reader.end_member ();
}
}
private static Variant variant_from_json_value (Json.Node node) throws Error {
Type type = node.get_value_type ();
if (type == typeof (string))
return new Variant.string (node.get_string ());
if (type == typeof (int64))
return new Variant.int64 (node.get_int ());
if (type == typeof (bool))
return new Variant.boolean (node.get_boolean ());
throw new Error.PROTOCOL ("Unexpected JSON type: %s", type.name ());
}
public async Json.Node request (Json.Node stanza, Cancellable? cancellable) throws Error, IOError {
if (state == CLOSED)
throw new Error.INVALID_OPERATION ("Helper client is closed");
var r = new Request (request.callback);
pending_requests.offer (r);
var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (() => {
r.complete_with_error (new IOError.CANCELLED ("Operation was cancelled"));
return false;
});
cancel_source.attach (MainContext.get_thread_default ());
write_request (stanza);
yield;
cancel_source.destroy ();
cancellable.set_error_if_cancelled ();
GLib.Error? e = r.error;
if (e != null) {
if (e is Error)
throw (Error) e;
if (e is IOError.CANCELLED)
throw (IOError) e;
throw new Error.TRANSPORT ("%s", e.message);
}
return r.response;
}
private async void request_ok (Json.Builder stanza, Cancellable? cancellable) throws Error, IOError {
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
}
private void write_request (Json.Node request) {
string request_str = Json.to_string (request, false);
uint8[] request_data = request_str.data;
uint offset = pending_output.len;
pending_output.set_size ((uint) (offset + sizeof (uint32) + request_data.length));
uint8 * blob = (uint8 *) pending_output.data + offset;
uint32 * size = blob;
*size = request_data.length.to_big_endian ();
Memory.copy (blob + 4, request_data, request_data.length);
if (!writing) {
writing = true;
var source = new IdleSource ();
source.set_callback (() => {
process_pending_output.begin ();
return false;
});
source.attach (MainContext.get_thread_default ());
}
}
private async void process_pending_output () {
while (pending_output.len > 0) {
uint8[] batch = pending_output.steal ();
size_t bytes_written;
try {
yield output.write_all_async (batch, Priority.DEFAULT, io_cancellable, out bytes_written);
} catch (GLib.Error e) {
break;
}
}
writing = false;
}
private async void process_incoming_responses () {
while (true) {
try {
Json.Node response = yield read_response ();
Request? request = pending_requests.poll ();
if (request == null)
throw new Error.PROTOCOL ("Unexpected response");
request.complete_with_response (response);
} catch (GLib.Error e) {
foreach (Request r in pending_requests)
r.complete_with_error (e);
pending_requests.clear ();
ensure_closed ();
return;
}
}
}
private async Json.Node read_response () throws GLib.Error {
size_t header_size = sizeof (uint32);
if (input.get_available () < header_size)
yield fill_until_n_bytes_available (header_size, io_cancellable);
uint32 response_size = 0;
unowned uint8[] response_size_buf = ((uint8[]) &response_size)[0:4];
input.peek (response_size_buf);
response_size = uint32.from_big_endian (response_size);
if (response_size < 1 || response_size > MAX_RESPONSE_SIZE)
throw new Error.PROTOCOL ("Invalid response size");
size_t frame_size = header_size + response_size;
if (input.get_available () < frame_size)
yield fill_until_n_bytes_available (frame_size, io_cancellable);
var response_data = new uint8[response_size + 1];
response_data.length = (int) response_size;
input.peek (response_data, header_size);
input.skip (frame_size, io_cancellable);
return Json.from_string ((string) response_data);
}
private async void fill_until_n_bytes_available (size_t minimum, Cancellable? cancellable) throws GLib.Error {
size_t available = input.get_available ();
while (available < minimum) {
if (input.get_buffer_size () < minimum)
input.set_buffer_size (minimum);
ssize_t n = yield input.fill_async ((ssize_t) (input.get_buffer_size () - available),
Priority.DEFAULT, cancellable);
if (n == 0)
throw new IOError.CONNECTION_CLOSED ("Connection closed");
available += n;
}
}
private class Request {
private SourceFunc? handler;
public Json.Node? response;
public GLib.Error? error;
public Request (owned SourceFunc handler) {
this.handler = (owned) handler;
}
public void complete_with_response (Json.Node r) {
if (handler == null)
return;
response = r;
handler ();
handler = null;
}
public void complete_with_error (GLib.Error e) {
if (handler == null)
return;
error = e;
handler ();
handler = null;
}
}
}
public abstract class PackageEntrypoint : Object {
public int uid {
get;
set;
}
public static PackageEntrypoint parse (string package, HostSpawnOptions options) throws Error {
PackageEntrypoint? entrypoint = null;
HashTable<string, Variant> aux = options.aux;
Variant? activity_value = aux["activity"];
if (activity_value != null) {
if (!activity_value.is_of_type (VariantType.STRING))
throw new Error.INVALID_ARGUMENT ("The 'activity' option must be a string");
string activity = canonicalize_class_name (activity_value.get_string (), package);
if (aux.contains ("action")) {
throw new Error.INVALID_ARGUMENT (
"The 'action' option should only be specified when a 'receiver' is specified");
}
entrypoint = new ActivityEntrypoint (activity);
}
Variant? receiver_value = aux["receiver"];
if (receiver_value != null) {
if (!receiver_value.is_of_type (VariantType.STRING))
throw new Error.INVALID_ARGUMENT ("The 'receiver' option must be a string");
string receiver = canonicalize_class_name (receiver_value.get_string (), package);
if (entrypoint != null) {
throw new Error.INVALID_ARGUMENT (
"Only one of 'activity' or 'receiver' (with 'action') may be specified");
}
Variant? action_value = aux["action"];
if (action_value == null)
throw new Error.INVALID_ARGUMENT ("The 'action' option is required when 'receiver' is specified");
if (!action_value.is_of_type (VariantType.STRING))
throw new Error.INVALID_ARGUMENT ("The 'action' option must be a string");
string action = action_value.get_string ();
entrypoint = new BroadcastReceiverEntrypoint (receiver, action);
}
if (entrypoint == null)
entrypoint = new DefaultActivityEntrypoint ();
Variant? uid_value = aux["uid"];
if (uid_value != null) {
if (!uid_value.is_of_type (VariantType.INT64))
throw new Error.INVALID_ARGUMENT ("The 'uid' option must be an integer");
entrypoint.uid = (int) uid_value.get_int64 ();
}
return entrypoint;
}
}
public sealed class DefaultActivityEntrypoint : PackageEntrypoint {
public DefaultActivityEntrypoint () {
Object ();
}
}
public sealed class ActivityEntrypoint : PackageEntrypoint {
public string activity {
get;
construct;
}
public ActivityEntrypoint (string activity) {
Object (activity: activity);
}
}
public sealed class BroadcastReceiverEntrypoint : PackageEntrypoint {
public string receiver {
get;
construct;
}
public string action {
get;
construct;
}
public BroadcastReceiverEntrypoint (string receiver, string action) {
Object (receiver: receiver, action: action);
}
}
private static string canonicalize_class_name (string klass, string package) {
var result = new StringBuilder (klass);
if (klass.has_prefix (".")) {
result.prepend (package);
} else if (klass.index_of (".") == -1) {
result.prepend_c ('.');
result.prepend (package);
}
return result.str;
}
}
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
backend_sources += files('client.vala')
android_helper_dex = files('helper.dex')
File diff suppressed because it is too large. Load diff
File renamed without changes.
+3 -1
View File
@@ -238,7 +238,7 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
for elem in core_elements + base_elements:
if tag_name == "class":
for child in list(elem):
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") == "FridaBase.AgentMessageSink") \
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in {"Frida.HostSessionHub", "FridaBase.AgentMessageSink"}) \
or child.tag == CORE_TAG_FIELD \
or child.get("name").startswith("_"):
elem.remove(child)
@@ -512,6 +512,8 @@ def function_is_public(name):
"parse_socket_address",
"negotiate_connection",
"check_kernel_version",
"make_stdio_pipes",
"make_stdio_pipe",
]
def parse_vala_object_types(source) -> List[ApiObjectType]:
+2 -1
View File
@@ -40,7 +40,8 @@ namespace Frida {
}
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (host_session != null)
throw new Error.INVALID_OPERATION ("Already created");
+39 -3
View File
@@ -106,6 +106,8 @@ type BuildOptions struct {
DisableTypeCheck bool
SourceMap bool
Compress bool
Platform esbuild.Platform
Externals []string
}
type OutputFormat C.FridaOutputFormat
@@ -146,7 +148,7 @@ type BuildDiagnosticCallback func(d Diagnostic)
//export _frida_compiler_backend_build
func _frida_compiler_backend_build(cProjectRoot, cEntrypoint *C.char, outputFormat C.FridaOutputFormat, bundleFormat C.FridaBundleFormat,
disableTypeCheck, sourceMap, compress uintptr,
disableTypeCheck, sourceMap, compress uintptr, cPlatform *C.char, cExternals **C.char, numExternals C.int,
onCompleteFn C.FridaBuildCompleteFunc, onCompleteData unsafe.Pointer, onCompleteDataDestroy C.FridaDestroyFunc,
onDiagnosticFn C.FridaDiagnosticFunc, onDiagnosticData unsafe.Pointer) {
options := BuildOptions{
@@ -157,6 +159,8 @@ func _frida_compiler_backend_build(cProjectRoot, cEntrypoint *C.char, outputForm
DisableTypeCheck: disableTypeCheck != 0,
SourceMap: sourceMap != 0,
Compress: compress != 0,
Platform: platformFromFrida(C.GoString(cPlatform)),
Externals: parseCExternals(cExternals, numExternals),
}
onComplete := NewCDelegate(onCompleteFn, onCompleteData, onCompleteDataDestroy)
onDiagnostic := NewCDelegate(onDiagnosticFn, onDiagnosticData, nil)
@@ -181,7 +185,7 @@ func _frida_compiler_backend_build(cProjectRoot, cEntrypoint *C.char, outputForm
//export _frida_compiler_backend_watch
func _frida_compiler_backend_watch(cProjectRoot, cEntrypoint *C.char, outputFormat C.FridaOutputFormat, bundleFormat C.FridaBundleFormat,
disableTypeCheck, sourceMap, compress uintptr,
disableTypeCheck, sourceMap, compress uintptr, cPlatform *C.char, cExternals **C.char, numExternals C.int,
onReadyFn C.FridaWatchReadyFunc, onReadyData unsafe.Pointer, onReadyDataDestroy C.FridaDestroyFunc,
onStartingFn C.FridaStartingFunc, onStartingData unsafe.Pointer,
onFinishedFn C.FridaFinishedFunc, onFinishedData unsafe.Pointer,
@@ -195,6 +199,8 @@ func _frida_compiler_backend_watch(cProjectRoot, cEntrypoint *C.char, outputForm
DisableTypeCheck: disableTypeCheck != 0,
SourceMap: sourceMap != 0,
Compress: compress != 0,
Platform: platformFromFrida(C.GoString(cPlatform)),
Externals: parseCExternals(cExternals, numExternals),
}
onReady := NewCDelegate(onReadyFn, onReadyData, onReadyDataDestroy)
onStarting := NewCDelegate(onStartingFn, onStartingData, nil)
@@ -246,6 +252,35 @@ func _frida_compiler_backend_watch_session_dispose(h uintptr) {
handle.Delete()
}
func platformFromFrida(s string) esbuild.Platform {
switch s {
case "gum":
return esbuild.PlatformNode
case "browser":
return esbuild.PlatformBrowser
case "neutral":
return esbuild.PlatformNeutral
default:
panic(fmt.Sprintf("Unknown platform: %q", s))
}
}
func parseCExternals(cExternals **C.char, numExternals C.int) []string {
if numExternals == 0 {
return nil
}
n := int(numExternals)
cArray := (*[1 << 20]*C.char)(unsafe.Pointer(cExternals))[:n:n]
externals := make([]string, n)
for i := 0; i < n; i++ {
externals[i] = C.GoString(cArray[i])
}
return externals
}
func makeCBuildDiagnosticCallback(onDiagnostic *CDelegate[C.FridaDiagnosticFunc]) BuildDiagnosticCallback {
return func(d Diagnostic) {
var cPath *C.char
@@ -445,9 +480,10 @@ func makeContext(options BuildOptions, callbacks BuildEventCallbacks) (ctx esbui
Bundle: true,
Outdir: projectRoot,
AbsWorkingDir: projectRoot,
Platform: esbuild.PlatformNode,
Platform: options.Platform,
Format: format,
Inject: []string{"frida-builtins:///node-globals.js"},
External: options.Externals,
EntryPoints: []string{entrypoint},
Write: false,
Plugins: plugins,
+25 -45
View File
@@ -85,8 +85,31 @@ def build_backend(
)
if mode == "c-archive":
symbols_to_scramble = detect_conflictful_symbols_in_archive(backend_a, config["nm"])
scramble_symbols_in_archive(backend_a, symbols_to_scramble, config["ranlib"])
symbol_dest = priv_dir / "symbol-replacer"
symbol_dest.mkdir(parents=True, exist_ok=True)
src_dir = base_dir / "symbol-replacer"
for name in ("main.go", "trie.go", "go.mod"):
shutil.copy(src_dir / name, symbol_dest)
env_copy = config["env"].copy()
env_copy.pop("GOOS", None)
env_copy.pop("GOARCH", None)
symbol_replacer_name = "frida-symbol-replacer"
subprocess.run(
[go, "build", "-buildvcs=false", "-o", symbol_replacer_name, "."],
cwd=symbol_dest,
env=env_copy,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding="utf-8",
check=True,
)
run(priv_dir / "symbol-replacer" / symbol_replacer_name,
backend_a.name, *config["nm"], *config["ranlib"], config["env"]["CC"])
if (mingw := config.get("mingw")) is not None and (abi := config["abi"]) in {
"x86",
@@ -133,49 +156,6 @@ def build_backend(
shutil.copy(priv_dir / backend_h.name, output_dir / backend_h.name)
def detect_conflictful_symbols_in_archive(
archive: Path, nm_cmd_array: List[str]
) -> List[str]:
result = []
for line in subprocess.run(
[*nm_cmd_array, str(archive)], capture_output=True, encoding="utf-8", check=True
).stdout.splitlines():
tokens = line.lstrip().split(" ")
if len(tokens) >= 3 and tokens[1] in {"S", "T"}:
symbol = tokens[2]
if all(sub not in symbol for sub in ("/", ".", ":", "frida")):
result.append(symbol)
return result
def scramble_symbols_in_archive(
archive: Path, symbols: List[str], ranlib_cmd_array: List[str]
):
data = archive.read_bytes()
for old_sym in symbols:
new_sym = roll_first_alpha(old_sym)
data = data.replace(old_sym.encode("ascii"), new_sym.encode("ascii"))
archive.write_bytes(data)
subprocess.run([*ranlib_cmd_array, str(archive)], check=True)
def roll_first_alpha(s: str) -> str:
chars = list(s)
for i, c in enumerate(chars):
if c.isalpha():
if c == "z":
chars[i] = "a"
elif c == "Z":
chars[i] = "A"
else:
chars[i] = chr(ord(c) + 1)
break
return "".join(chars)
# Work around the Go toolchain's almost-MSVC-compatible object files
# until our patch is merged upstream:
# https://go-review.googlesource.com/c/go/+/678795
+65 -21
View File
@@ -46,7 +46,8 @@ namespace Frida {
CompilerBackend.build (project_root, entrypoint, opts.output_format, opts.bundle_format,
(size_t) (opts.type_check == NONE), (size_t) (opts.source_maps == INCLUDED),
(size_t) (opts.compression == TERSER), (owned) on_complete, on_diagnostic);
(size_t) (opts.compression == TERSER), opts.platform.to_nick (),
opts.externals.to_array (), (owned) on_complete, on_diagnostic);
yield;
if (error_message != null)
@@ -96,7 +97,8 @@ namespace Frida {
CompilerBackend.watch (project_root, entrypoint, opts.output_format, opts.bundle_format,
(size_t) (opts.type_check == NONE), (size_t) (opts.source_maps == INCLUDED),
(size_t) (opts.compression == TERSER), (owned) on_ready, on_starting, on_finished,
(size_t) (opts.compression == TERSER), opts.platform.to_nick (),
opts.externals.to_array (), (owned) on_ready, on_starting, on_finished,
on_output, on_diagnostic);
yield;
@@ -241,30 +243,36 @@ namespace Frida {
#else
unowned uint8[] backend_so = Frida.Data.Compiler.get_frida_compiler_backend_so_blob ().data;
FileDescriptor fd;
Module? backend = null;
if (MemoryFileDescriptor.is_supported ()) {
fd = MemoryFileDescriptor.from_bytes ("frida-compiler-backend.so", new Bytes.static (backend_so));
} else {
string name_used;
var fd = MemoryFileDescriptor.from_bytes ("frida-compiler-backend.so", new Bytes.static (backend_so));
try {
fd = new FileDescriptor (FileUtils.open_tmp ("frida-compiler-backend-XXXXXX.so", out name_used));
backend = new Module ("/proc/self/fd/%d".printf (fd.handle), LOCAL);
} catch (ModuleError e) {
}
}
if (backend == null) {
try {
string name_used;
{
var fd = new FileDescriptor (FileUtils.open_tmp ("frida-compiler-backend-XXXXXX.so", out name_used));
fd.pwrite_all (backend_so, 0);
}
try {
backend = new Module (name_used, LOCAL);
} catch (ModuleError e) {
assert_not_reached ();
}
FileUtils.unlink (name_used);
var output = new UnixOutputStream (fd.handle, false);
output.write_all (backend_so, null);
Posix.lseek (fd.handle, 0, Posix.SEEK_SET);
} catch (GLib.Error e) {
assert_not_reached ();
}
}
Module backend;
try {
backend = new Module ("/proc/self/fd/%d".printf (fd.handle), LOCAL);
} catch (ModuleError e) {
assert_not_reached ();
}
build = resolve_symbol (backend, "_frida_compiler_backend_build");
watch = resolve_symbol (backend, "_frida_compiler_backend_watch");
WatchSession.dispose = resolve_symbol (backend, "_frida_compiler_backend_watch_session_dispose");
@@ -283,13 +291,14 @@ namespace Frida {
[CCode (has_target = false)]
private delegate void BuildFunc (string project_root, string entrypoint, OutputFormat output_format,
BundleFormat bundle_format, size_t disable_type_check, size_t source_map, size_t compress,
owned BuildCompleteFunc on_complete, DiagnosticFunc on_diagnostic);
string platform, string[] externals, owned BuildCompleteFunc on_complete,
DiagnosticFunc on_diagnostic);
[CCode (has_target = false)]
private delegate void WatchFunc (string project_root, string entrypoint, OutputFormat output_format,
BundleFormat bundle_format, size_t disable_type_check, size_t source_map, size_t compress,
owned WatchReadyFunc on_ready, StartingFunc on_starting, FinishedFunc on_finished, OutputFunc on_output,
DiagnosticFunc on_diagnostic);
string platform, string[] externals, owned WatchReadyFunc on_ready, StartingFunc on_starting,
FinishedFunc on_finished, OutputFunc on_output, DiagnosticFunc on_diagnostic);
#if COMPILER_BACKEND_STATIC_COMPILATION
private extern void _init_go_runtime ();
@@ -373,6 +382,27 @@ namespace Frida {
set;
default = NONE;
}
public JsPlatform platform {
get;
set;
default = GUM;
}
internal Gee.List<string> externals = new Gee.ArrayList<string> ();
public void clear_externals () {
externals.clear ();
}
public void add_external (string external) {
externals.add (external);
}
public void enumerate_externals (Func<string> func) {
foreach (var external in externals)
func (external);
}
}
public sealed class BuildOptions : CompilerOptions {
@@ -446,4 +476,18 @@ namespace Frida {
return Marshal.enum_to_nick<JsCompression> (this);
}
}
public enum JsPlatform {
GUM,
BROWSER,
NEUTRAL;
public static JsPlatform from_nick (string nick) throws Error {
return Marshal.enum_from_nick<JsPlatform> (nick);
}
public string to_nick () {
return Marshal.enum_to_nick<JsPlatform> (this);
}
}
}
+3 -2
View File
@@ -4,6 +4,7 @@ compiler_sources = files(
)
if have_compiler_backend
output_suffix = compiler_backend_static_compilation ? '' : '-raw'
compiler_backend = custom_target('frida-compiler-backend',
input: [
'go.mod',
@@ -17,8 +18,8 @@ if have_compiler_backend
'package-lock.json',
],
output: [
'frida-compiler-backend' + (compiler_backend_static_compilation ? '.a' : ('-raw' + shlib_suffix)),
'frida-compiler-backend.h',
'frida-compiler-backend' + output_suffix + (compiler_backend_static_compilation ? '.a' : shlib_suffix),
'frida-compiler-backend' + output_suffix + '.h',
],
command: [
python,
+3
View File
@@ -0,0 +1,3 @@
module github.com/frida/frida-symbol-replacer
go 1.24.4
+135
View File
@@ -0,0 +1,135 @@
package main
import (
"bytes"
"io"
"os"
"os/exec"
"runtime"
"strings"
"unicode"
)
var (
excludeSymbols = [][]byte{
[]byte("fprintf"),
[]byte("pthread"),
[]byte("cas"),
}
)
func main() {
archive := os.Args[1]
nm := os.Args[2]
ranlib := os.Args[3]
cc := os.Args[4]
buf := new(bytes.Buffer)
c := exec.Command(nm, archive)
c.Stdout = buf
if err := c.Run(); err != nil {
panic(err)
}
t := newTrie()
t.insert([]byte("internal/"), flipAlpha([]byte("internal/")))
var content [][]byte
if runtime.GOOS == "windows" {
if strings.Contains(cc, "clangarm64") {
content = bytes.Split(buf.Bytes(), []byte{'\n'})
} else {
content = bytes.Split(buf.Bytes(), []byte("\r\n"))
}
} else {
content = bytes.Split(buf.Bytes(), []byte{'\n'})
}
for _, line := range content {
splitted := bytes.Split(line, []byte{' '})
if len(splitted) >= 3 {
symbolType := splitted[1]
symbol := splitted[2]
symbolLower := bytes.ToLower(symbol)
if bytes.ContainsAny(symbolType, "TSt") {
if symbolIsOkay(symbolLower) {
t.insert(symbol, flipAlpha(symbol))
}
} else if bytes.ContainsAny(symbolType, "Rrd") {
if bytes.HasPrefix(symbolLower, []byte("go:")) {
t.insert(symbol, flipAlpha(symbol))
}
}
}
}
f, err := os.OpenFile(archive, os.O_RDWR, 0755)
if err != nil {
panic(err)
}
data, _ := io.ReadAll(f)
modifiedData := make([]byte, len(data))
for i := 0; i < len(data); {
if l, replacement, ok := t.search(data, i); ok {
copy(modifiedData[i:], replacement)
i += l
} else {
modifiedData[i] = data[i]
i++
}
}
f.Truncate(0)
f.Seek(0, 0)
f.Write(modifiedData)
f.Close()
ranl := exec.Command(ranlib, archive)
if err := ranl.Run(); err != nil {
panic(err)
}
}
func flipAlpha(s []byte) []byte {
dt := make([]byte, len(s))
copy(dt, s)
for i, r := range s {
if unicode.IsLetter(rune(r)) || unicode.IsNumber(rune(r)) {
if r == 'z' {
dt[i] = 'a'
} else if r == 'Z' {
dt[i] = 'A'
} else {
dt[i] = r + 1
}
break
}
}
return dt
}
func symbolIsOkay(symbol []byte) bool {
switch {
case bytes.Contains(symbol, []byte("frida")):
return false
case bytes.Contains(symbol, []byte("rt0")):
return true
case bytes.Contains(symbol, []byte("cgo")):
return true
case bytes.ContainsAny(symbol, "./"):
return true
default:
for _, s := range excludeSymbols {
if bytes.Contains(symbol, s) {
return false
}
}
return true
}
}
+52
View File
@@ -0,0 +1,52 @@
package main
type trieNode struct {
children map[byte]*trieNode
isEnd bool
replacement []byte
}
type trie struct {
root *trieNode
}
func newTrie() *trie {
return &trie{
root: &trieNode{
children: make(map[byte]*trieNode),
},
}
}
func (t *trie) insert(pattern, value []byte) {
node := t.root
for _, b := range pattern {
if node.children[b] == nil {
node.children[b] = &trieNode{children: make(map[byte]*trieNode)}
}
node = node.children[b]
}
node.isEnd = true
node.replacement = value
}
func (t *trie) search(data []byte, start int) (int, []byte, bool) {
node := t.root
maxLen := 0
var match []byte
for i := start; i < len(data); i++ {
b := data[i]
node = node.children[b]
if node == nil {
break
}
if node.isEnd {
maxLen = i - start + 1
match = node.replacement
}
}
if maxLen > 0 {
return maxLen, match, true
}
return 0, nil, false
}
+13 -12
View File
@@ -845,8 +845,8 @@ namespace Frida {
construct;
}
public LaunchdAgent (DarwinHostSession host_session, Cancellable io_cancellable) {
Object (host_session: host_session, io_cancellable: io_cancellable);
public LaunchdAgent (HostSessionConnection connection, Cancellable io_cancellable) {
Object (connection: connection, io_cancellable: io_cancellable);
}
construct {
@@ -905,7 +905,7 @@ namespace Frida {
try {
if (path == XPC_PROXY_PATH) {
var agent = new XpcProxyAgent ((DarwinHostSession) host_session, identifier, pid);
var agent = new XpcProxyAgent (connection, identifier, pid);
yield agent.run_until_exec (io_cancellable);
}
@@ -921,7 +921,7 @@ namespace Frida {
try {
if (path == XPC_PROXY_PATH) {
var agent = new XpcProxyAgent ((DarwinHostSession) host_session, identifier, pid);
var agent = new XpcProxyAgent (connection, identifier, pid);
yield agent.run_until_exec (io_cancellable);
}
@@ -937,7 +937,8 @@ namespace Frida {
protected override async string? load_source (Cancellable? cancellable) throws Error, IOError {
string * raw_source = Frida.Data.Darwin.get_launchd_js_blob ().data;
return raw_source->replace ("@REPORT_CRASHES@", ((DarwinHostSession) host_session).report_crashes.to_string ());
return raw_source->replace ("@REPORT_CRASHES@",
((DarwinHostSession) connection.host_session).report_crashes.to_string ());
}
}
@@ -952,8 +953,8 @@ namespace Frida {
construct;
}
public XpcProxyAgent (DarwinHostSession host_session, string identifier, uint pid) {
Object (host_session: host_session, identifier: identifier, pid: pid);
public XpcProxyAgent (HostSessionConnection connection, string identifier, uint pid) {
Object (connection: connection, identifier: identifier, pid: pid);
}
construct {
@@ -965,7 +966,7 @@ namespace Frida {
public async void run_until_exec (Cancellable? cancellable) throws Error, IOError {
yield ensure_loaded (cancellable);
var helper = ((DarwinHostSession) host_session).helper;
var helper = ((DarwinHostSession) connection.host_session).helper;
yield helper.resume (pid, cancellable);
yield wait_for_unload (cancellable);
@@ -1002,10 +1003,10 @@ namespace Frida {
construct;
}
public ReportCrashAgent (DarwinHostSession host_session, uint pid, MappedAgentContainer mapped_agent_container,
public ReportCrashAgent (HostSessionConnection connection, uint pid, MappedAgentContainer mapped_agent_container,
Cancellable io_cancellable) {
Object (
host_session: host_session,
connection: connection,
pid: pid,
mapped_agent_container: mapped_agent_container,
io_cancellable: io_cancellable
@@ -1226,9 +1227,9 @@ namespace Frida {
construct;
}
public OSAnalyticsAgent (DarwinHostSession host_session, uint pid, Cancellable io_cancellable) {
public OSAnalyticsAgent (HostSessionConnection connection, uint pid, Cancellable io_cancellable) {
Object (
host_session: host_session,
connection: connection,
pid: pid,
io_cancellable: io_cancellable
);
+52 -93
View File
@@ -30,7 +30,6 @@
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <unistd.h>
#include <util.h>
#ifndef _POSIX_SPAWN_DISABLE_ASLR
# define _POSIX_SPAWN_DISABLE_ASLR 0x0100
@@ -181,6 +180,7 @@ struct _FridaSpawnInstance
mach_vm_address_t dyld_data;
GumAddress modern_entry_address;
GumAddress sim_notify_address;
/* V4+ */
GumAddress notify_objc_init;
@@ -405,9 +405,6 @@ static void frida_spawn_instance_disable_nth_breakpoint (FridaSpawnInstance * se
static guint32 frida_spawn_instance_put_software_breakpoint (FridaSpawnInstance * self, GumAddress where, guint index);
static guint32 frida_spawn_instance_overwrite_arm64_instruction (FridaSpawnInstance * self, GumAddress address, guint32 new_instruction);
static void frida_make_pty (int fds[2]);
static void frida_configure_terminal_attributes (gint fd);
static FridaInjectInstance * frida_inject_instance_new (FridaDarwinHelperBackend * backend, guint id, guint pid);
static FridaInjectInstance * frida_inject_instance_clone (const FridaInjectInstance * instance, guint id);
static void frida_inject_instance_close (FridaInjectInstance * instance);
@@ -804,12 +801,17 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
posix_spawnattr_t attributes;
sigset_t signal_mask_set;
short flags;
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
FridaFileDescriptor * in_fd = NULL;
FridaFileDescriptor * out_fd = NULL;
FridaFileDescriptor * err_fd = NULL;
GError * stdio_error = NULL;
FridaAslr aslr = FRIDA_ASLR_AUTO;
GVariant * aslr_value;
gchar * old_cwd = NULL;
int result, spawn_errno;
*pipes = NULL;
instance = frida_spawn_instance_new (self);
argv = frida_host_spawn_options_compute_argv (options, path, NULL);
@@ -823,33 +825,13 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
flags = POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_START_SUSPENDED;
switch (options->stdio)
{
case FRIDA_STDIO_INHERIT:
*pipes = NULL;
*pipes = frida_make_stdio_pipes (options->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &stdio_error);
if (stdio_error != NULL)
goto propagate_stdio_error;
posix_spawn_file_actions_adddup2 (&file_actions, 0, 0);
posix_spawn_file_actions_adddup2 (&file_actions, 1, 1);
posix_spawn_file_actions_adddup2 (&file_actions, 2, 2);
break;
case FRIDA_STDIO_PIPE:
frida_make_pty (stdin_pipe);
frida_make_pty (stdout_pipe);
frida_make_pty (stderr_pipe);
*pipes = frida_stdio_pipes_new (stdin_pipe[1], stdout_pipe[0], stderr_pipe[0]);
posix_spawn_file_actions_adddup2 (&file_actions, stdin_pipe[0], 0);
posix_spawn_file_actions_adddup2 (&file_actions, stdout_pipe[1], 1);
posix_spawn_file_actions_adddup2 (&file_actions, stderr_pipe[1], 2);
break;
default:
g_assert_not_reached ();
}
posix_spawn_file_actions_adddup2 (&file_actions, (in_fd != NULL) ? in_fd->handle : 0, 0);
posix_spawn_file_actions_adddup2 (&file_actions, (out_fd != NULL) ? out_fd->handle : 1, 1);
posix_spawn_file_actions_adddup2 (&file_actions, (err_fd != NULL) ? err_fd->handle : 2, 2);
aslr_value = g_hash_table_lookup (options->aux, "aslr");
if (aslr_value != NULL && !frida_parse_aslr_option (aslr_value, &aslr, error))
@@ -872,13 +854,6 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
if (old_cwd != NULL)
chdir (old_cwd);
if (options->stdio == FRIDA_STDIO_PIPE)
{
close (stdin_pipe[0]);
close (stdout_pipe[1]);
close (stderr_pipe[1]);
}
posix_spawnattr_destroy (&attributes);
posix_spawn_file_actions_destroy (&file_actions);
@@ -892,6 +867,11 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
goto beach;
propagate_stdio_error:
{
g_propagate_error (error, stdio_error);
goto early_failure;
}
chdir_failed:
{
g_set_error (error,
@@ -936,12 +916,18 @@ any_failure:
kill (instance->pid, SIGKILL);
frida_spawn_instance_close (instance);
g_clear_object (pipes);
pid = 0;
goto beach;
}
beach:
{
g_clear_object (&in_fd);
g_clear_object (&out_fd);
g_clear_object (&err_fd);
g_free (old_cwd);
g_strfreev (envp);
g_strfreev (argv);
@@ -1057,6 +1043,8 @@ frida_darwin_helper_backend_launch_using_fbs (NSString * identifier, NSURL * url
FridaSpringboardApi * api;
NSMutableDictionary * debug_options, * open_options;
FridaStdioPipes * pipes = NULL;
gchar * stdout_name = NULL;
gchar * stderr_name = NULL;
GError * error = NULL;
FridaAslr aslr = FRIDA_ASLR_AUTO;
GVariant * aslr_value;
@@ -1092,22 +1080,12 @@ frida_darwin_helper_backend_launch_using_fbs (NSString * identifier, NSURL * url
if (strlen (spawn_options->cwd) > 0)
goto cwd_not_supported;
pipes = frida_make_stdio_pipes (spawn_options->stdio, FALSE, NULL, NULL, NULL, &stdout_name, NULL, &stderr_name, &error);
if (error != NULL)
goto failure;
if (spawn_options->stdio == FRIDA_STDIO_PIPE)
{
gint stdout_master, stdout_slave, stderr_master, stderr_slave;
gchar stdout_name[PATH_MAX], stderr_name[PATH_MAX];
openpty (&stdout_master, &stdout_slave, stdout_name, NULL, NULL);
openpty (&stderr_master, &stderr_slave, stderr_name, NULL, NULL);
pipes = frida_stdio_pipes_new (-1, stdout_master, stderr_master);
frida_configure_terminal_attributes (stdout_master);
frida_configure_terminal_attributes (stderr_master);
frida_stdio_pipes_retain (pipes, stdout_slave);
frida_stdio_pipes_retain (pipes, stderr_slave);
chmod (stdout_name, 0666);
chmod (stderr_name, 0666);
@@ -1174,7 +1152,7 @@ frida_darwin_helper_backend_launch_using_fbs (NSString * identifier, NSURL * url
}
});
return;
goto beach;
envp_not_supported:
{
@@ -1195,6 +1173,16 @@ cwd_not_supported:
failure:
{
on_complete (NULL, error, on_complete_target);
g_clear_object (&pipes);
goto beach;
}
beach:
{
g_free (stdout_name);
g_free (stderr_name);
return;
}
}
@@ -1881,7 +1869,7 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
mach_msg_type_number_t state_count = GUM_DARWIN_THREAD_STATE_COUNT;
thread_state_flavor_t state_flavor = GUM_DARWIN_THREAD_STATE_FLAVOR;
GumAddress dyld_start, dyld_granularity, dyld_chunk, dyld_header;
GumAddress modern_entry_address, legacy_entry_address;
GumAddress modern_entry_address, legacy_entry_address, sim_notify_address;
const gchar * launch_with_closure_names[] = {
"__ZN4dyldL17launchWithClosureEPKN5dyld37closure13LaunchClosureEPK15DyldSharedCachePKNS0_11MachOLoadedEmiPPKcSD_SD_R11DiagnosticsPmSG_PbSH_",
"__ZN4dyldL17launchWithClosureEPKN5dyld312launch_cache13binary_format7ClosureEPK15DyldSharedCachePK11mach_headermiPPKcSE_SE_PmSF_",
@@ -2101,6 +2089,9 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
instance->ret_gadget |= 1;
}
sim_notify_address = gum_darwin_module_resolve_symbol_address (dyld, "__ZN5dyld4L29sim_notifyMonitorOfMainCalledEv");
instance->sim_notify_address = sim_notify_address;
instance->ret_state = FRIDA_RET_FROM_HELPER;
kr = frida_get_debug_state (child_thread, &instance->previous_debug_state, instance->cpu_type);
@@ -2112,6 +2103,8 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
frida_spawn_instance_set_nth_breakpoint (instance, i++, legacy_entry_address, FRIDA_BREAKPOINT_REPEAT_ALWAYS);
if (modern_entry_address != 0)
frida_spawn_instance_set_nth_breakpoint (instance, i++, modern_entry_address, FRIDA_BREAKPOINT_REPEAT_ALWAYS);
if (sim_notify_address != 0)
frida_spawn_instance_set_nth_breakpoint (instance, i++, sim_notify_address, FRIDA_BREAKPOINT_REPEAT_ALWAYS);
if (instance->dyld_flavor == FRIDA_DYLD_V4_PLUS)
{
GumAddress restart_with_dyld_in_cache = gum_darwin_module_resolve_symbol_address (dyld,
@@ -3104,7 +3097,11 @@ frida_spawn_instance_handle_breakpoint (FridaSpawnInstance * self, FridaBreakpoi
if (self->breakpoint_phase == FRIDA_BREAKPOINT_DETECT_FLAVOR)
{
if (self->dyld_flavor == FRIDA_DYLD_V4_PLUS)
if (pc == self->sim_notify_address)
{
self->breakpoint_phase = FRIDA_BREAKPOINT_LIBSYSTEM_INITIALIZED;
}
else if (self->dyld_flavor == FRIDA_DYLD_V4_PLUS)
{
if (pc == self->modern_entry_address)
{
@@ -4065,44 +4062,6 @@ frida_spawn_instance_overwrite_arm64_instruction (FridaSpawnInstance * self, Gum
return original_instruction;
}
static void
frida_make_pty (int fds[2])
{
gboolean pipe_opened;
int i;
pipe_opened = openpty (&fds[0], &fds[1], NULL, NULL, NULL) != -1;
g_assert (pipe_opened);
for (i = 0; i != 2; i++)
{
const int fd = fds[i];
int res;
res = fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC);
g_assert (res == 0);
res = fcntl (fd, F_SETNOSIGPIPE, TRUE);
g_assert (res == 0);
}
frida_configure_terminal_attributes (fds[0]);
}
static void
frida_configure_terminal_attributes (gint fd)
{
struct termios tios;
tcgetattr (fd, &tios);
tios.c_oflag &= ~ONLCR;
tios.c_cflag = (tios.c_cflag & CLOCAL) | CS8 | CREAD | HUPCL;
tios.c_lflag &= ~ECHO;
tcsetattr (fd, 0, &tios);
}
static FridaInjectInstance *
frida_inject_instance_new (FridaDarwinHelperBackend * backend, guint id, guint pid)
{
+10 -62
View File
@@ -136,9 +136,9 @@ namespace Frida {
ChildWatch.add ((Pid) child_pid, on_child_dead);
if (pipes != null) {
stdin_streams[child_pid] = new UnixOutputStream (pipes.input, false);
process_next_output_from.begin (new UnixInputStream (pipes.output, false), child_pid, 1, pipes);
process_next_output_from.begin (new UnixInputStream (pipes.error, false), child_pid, 2, pipes);
stdin_streams[child_pid] = pipes.input;
process_next_output_from.begin (pipes.output, child_pid, 1, pipes);
process_next_output_from.begin (pipes.error, child_pid, 2, pipes);
}
return child_pid;
@@ -180,10 +180,8 @@ namespace Frida {
var pipes = pending.pipes;
if (pipes != null) {
pipes.clear_retained ();
process_next_output_from.begin (new UnixInputStream (pipes.output, false), pid, 1, pipes);
process_next_output_from.begin (new UnixInputStream (pipes.error, false), pid, 2, pipes);
process_next_output_from.begin (pipes.output, pid, 1, pipes);
process_next_output_from.begin (pipes.error, pid, 2, pipes);
}
}
@@ -439,8 +437,11 @@ namespace Frida {
private bool is_booting (uint task) throws Error {
Gum.Darwin.AllImageInfos infos;
if (!Gum.Darwin.query_all_image_infos (task, out infos))
throw new Error.PROCESS_NOT_FOUND ("Target process died unexpectedly");
try {
Gum.Darwin.query_all_image_infos (task, out infos);
} catch (Gum.Error e) {
throw new Error.PROCESS_NOT_FOUND ("%s", e.message);
}
return !infos.libsystem_initialized;
}
@@ -888,57 +889,4 @@ namespace Frida {
return false;
}
}
public sealed class StdioPipes : Object {
public int input {
get;
construct;
}
public int output {
get;
construct;
}
public int error {
get;
construct;
}
private int[] retained = {};
public StdioPipes (int input, int output, int error) {
Object (input: input, output: output, error: error);
}
construct {
try {
if (input != -1)
Unix.set_fd_nonblocking (input, true);
Unix.set_fd_nonblocking (output, true);
Unix.set_fd_nonblocking (error, true);
} catch (GLib.Error e) {
assert_not_reached ();
}
}
~StdioPipes () {
clear_retained ();
if (input != -1)
Posix.close (input);
Posix.close (output);
Posix.close (error);
}
public void clear_retained () {
foreach (var fd in retained)
Posix.close (fd);
retained = {};
}
public void retain (int fd) {
retained += fd;
}
}
}
+123 -21
View File
@@ -314,6 +314,9 @@ namespace Frida {
SocketService? service = null;
TimeoutSource? timeout_source = null;
bool timed_out = false;
bool child_exited = false;
int child_status = 0;
try {
string tempdir;
@@ -341,7 +344,6 @@ namespace Frida {
return false;
});
idle_source.attach (main_context);
yield;
var incoming_handler = service.incoming.connect ((c) => {
@@ -350,42 +352,63 @@ namespace Frida {
return true;
});
var timer = new Timer ();
timeout_source = new TimeoutSource (10);
timeout_source = new TimeoutSource.seconds (10);
timeout_source.set_callback (() => {
timed_out = true;
launch_helper.callback ();
return Source.CONTINUE;
return Source.REMOVE;
});
timeout_source.attach (main_context);
string[] argv = { helper_path, socket_address };
GLib.SpawnFlags flags = GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN | /* GLib.SpawnFlags.CLOEXEC_PIPES */ 256;
GLib.SpawnFlags flags =
GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN |
GLib.SpawnFlags.DO_NOT_REAP_CHILD |
/* GLib.SpawnFlags.CLOEXEC_PIPES */ 256;
GLib.Process.spawn_async (null, argv, null, flags, null, out pending_pid);
while (pending_stream == null && timer.elapsed () < 10.0 && !process_is_dead (pending_pid))
var exit_monitor = new ChildExitMonitor (pending_pid, main_context);
var exit_handler = exit_monitor.exited.connect ((status) => {
child_exited = true;
child_status = status;
launch_helper.callback ();
});
while (pending_stream == null && !timed_out && !child_exited)
yield;
exit_monitor.disconnect (exit_handler);
exit_monitor.stop ();
timeout_source.destroy ();
timeout_source = null;
service.disconnect (incoming_handler);
service.stop ();
service = null;
timeout_source.destroy ();
timeout_source = null;
if (pending_stream == null)
throw new Error.TIMED_OUT ("Unexpectedly timed out while spawning helper process");
if (pending_stream == null) {
if (child_exited)
throw new Error.PROCESS_NOT_FOUND ("Helper exited during launch (status=%d)", child_status);
if (timed_out)
throw new Error.TIMED_OUT ("Unexpectedly timed out while spawning helper");
}
pending_connection = yield new DBusConnection (pending_stream, ServerGuid.HOST_SESSION_SERVICE,
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
pending_proxy = yield pending_connection.get_proxy (null, ObjectPath.HELPER, DO_NOT_LOAD_PROPERTIES,
cancellable);
if (pending_connection.is_closed ())
throw new Error.NOT_SUPPORTED ("Helper terminated prematurely");
} catch (GLib.Error e) {
bool died_unexpectedly = pending_pid != 0 && process_is_dead (pending_pid);
if (pending_pid != 0 && !died_unexpectedly)
if (pending_pid != 0 && !child_exited) {
Posix.kill ((Posix.pid_t) pending_pid, Posix.Signal.KILL);
reap_later.begin (pending_pid);
}
if (timeout_source != null)
timeout_source.destroy ();
@@ -393,10 +416,6 @@ namespace Frida {
if (service != null)
service.stop ();
if (died_unexpectedly) {
throw new Error.PROCESS_NOT_FOUND ("Peer process (%d) died unexpectedly: %s",
pending_pid, e.message);
}
if (e is Error)
throw (Error) e;
throw new Error.PERMISSION_DENIED ("%s", e.message);
@@ -422,10 +441,6 @@ namespace Frida {
return proxy;
}
private static bool process_is_dead (uint pid) {
return Posix.kill ((Posix.pid_t) pid, 0) == -1 && Posix.errno == Posix.ESRCH;
}
private void on_connection_closed (bool remote_peer_vanished, GLib.Error? error) {
obtain_request = null;
@@ -441,7 +456,10 @@ namespace Frida {
connection.on_closed.disconnect (on_connection_closed);
connection = null;
Pid pid = process_pid;
process_pid = 0;
if (pid != 0)
reap_later.begin (pid);
}
private void on_output (uint pid, int fd, uint8[] data) {
@@ -559,4 +577,88 @@ namespace Frida {
return false;
}
}
private class ChildExitMonitor : Object {
public signal void exited (int status);
private Pid pid;
private MainContext? context;
private TimeoutSource? source;
private uint interval_ms = 10;
private const uint MAX_INTERVAL_MS = 1000;
public ChildExitMonitor (Pid pid, MainContext? ctx = null) {
this.pid = pid;
context = ctx ?? MainContext.get_thread_default ();
arm ();
}
public void stop () {
if (source != null) {
source.destroy ();
source = null;
}
}
private void arm () {
source = new TimeoutSource (interval_ms);
source.set_callback (() => {
int status;
if (try_reap (pid, out status)) {
stop ();
exited (status);
return Source.REMOVE;
}
if (interval_ms < MAX_INTERVAL_MS) {
interval_ms = uint.min (MAX_INTERVAL_MS, 2 * interval_ms);
stop ();
arm ();
return Source.REMOVE;
}
return Source.CONTINUE;
});
source.attach (context);
}
private static bool try_reap (Pid pid, out int status) {
status = 0;
var r = Posix.waitpid ((Posix.pid_t) pid, out status, Posix.WNOHANG);
if (r > 0)
return true;
if (r == 0)
return false;
if (Posix.errno == Posix.EINTR)
return false;
if (Posix.errno == Posix.ECHILD)
return true;
return true;
}
}
private async void reap_later (Pid pid) {
var monitor = new ChildExitMonitor (pid);
bool done = false;
ulong h = monitor.exited.connect ((status) => {
done = true;
reap_later.callback ();
});
try {
while (!done)
yield;
} finally {
monitor.disconnect (h);
monitor.stop ();
}
}
}
-2
View File
@@ -109,8 +109,6 @@ _frida_get_springboard_api (void)
gum_interceptor_replace (interceptor, frida_xpc_look_up_endpoint, frida_replacement_xpc_look_up_endpoint, NULL, NULL);
else
g_error ("Unable to locate _xpc_look_up_endpoint(); please file a bug");
g_object_unref (interceptor);
}
#endif
+1 -1
View File
@@ -489,7 +489,7 @@ static void
frida_add_app_icons (GHashTable * parameters, NSImage * image)
{
GVariantBuilder builder;
const guint sizes[] = { 16, 32 };
const guint sizes[] = { 128 };
guint i;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
File diff suppressed because it is too large. Load diff
-703
View File
@@ -1,703 +0,0 @@
package re.frida;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.os.Looper;
import android.os.Process;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.Base64;
import android.util.Base64OutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.Class;
import java.lang.Exception;
import java.lang.Object;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Helper {
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: frida-helper <instance-id>");
System.exit(1);
return;
}
String instanceId = args[0];
new File("/data/local/tmp/frida-helper-" + instanceId + ".dex").delete();
LocalServerSocket socket;
try {
socket = new LocalServerSocket("/frida-helper-" + instanceId);
} catch (IOException e) {
System.err.println(e);
System.exit(2);
return;
}
Looper.prepare();
Context context;
try {
Class<?> ActivityThread = Class.forName("android.app.ActivityThread");
Object activityThread = ActivityThread.getDeclaredMethod("systemMain").invoke(null);
context = (Context) ActivityThread.getDeclaredMethod("getSystemContext").invoke(activityThread);
} catch (InvocationTargetException e) {
System.err.println(e.getCause());
System.exit(1);
return;
} catch (Exception e) {
System.err.println(e);
System.exit(1);
return;
}
new Helper(socket, context).run();
}
private PackageManager mPackageManager;
private ActivityManager mActivityManager;
private Field mTopActivityField;
private String mLauncherPkgName;
private static Pattern sStatusUidPattern = Pattern.compile("^Uid:\\s+\\d+\\s+(\\d+)\\s+\\d+\\s+\\d+$", Pattern.MULTILINE);
private long mSystemBootTime;
private long mMillisecondsPerJiffy;
private SimpleDateFormat mIso8601;
private Method mGetpwuid;
private Field mPwnameField;
private LocalServerSocket mSocket;
private Thread mWorker;
private final int MAX_REQUEST_SIZE = 128 * 1024;
public Helper(LocalServerSocket socket, Context ctx) {
mPackageManager = ctx.getPackageManager();
mActivityManager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
try {
mTopActivityField = Class.forName("android.app.TaskInfo").getDeclaredField("topActivity");
} catch (Exception e) {
}
mLauncherPkgName = detectLauncherPackageName();
mSystemBootTime = querySystemBootTime();
mMillisecondsPerJiffy = 1000 / Os.sysconf(OsConstants._SC_CLK_TCK);
mIso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
mIso8601.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
mGetpwuid = Class.forName("android.system.Os").getDeclaredMethod("getpwuid", int.class);
mPwnameField = Class.forName("android.system.StructPasswd").getDeclaredField("pw_name");
} catch (Exception e) {
throw new RuntimeException(e);
}
mSocket = socket;
mWorker = new Thread("Connection Listener") {
public void run() {
handleIncomingConnections();
}
};
}
private void run() {
mWorker.start();
Looper.loop();
}
private void handleIncomingConnections() {
System.out.println("READY.");
while (true) {
try {
LocalSocket client = mSocket.accept();
Thread handler = new Thread("Connection Handler") {
public void run() {
handleConnection(client);
}
};
handler.start();
} catch (IOException e) {
break;
}
}
}
protected void handleConnection(LocalSocket client) {
DataInputStream input;
DataOutputStream output;
try {
input = new DataInputStream(new BufferedInputStream(client.getInputStream()));
output = new DataOutputStream(new BufferedOutputStream(client.getOutputStream()));
} catch (IOException e) {
return;
}
while (true) {
try {
int requestSize = input.readInt();
if (requestSize < 1 || requestSize > MAX_REQUEST_SIZE) {
break;
}
byte[] rawRequest = new byte[requestSize];
input.readFully(rawRequest);
JSONArray request = new JSONArray(new String(rawRequest));
JSONArray response;
String type = request.getString(0);
if (type.equals("get-frontmost-application")) {
response = getFrontmostApplication(request);
} else if (type.equals("enumerate-applications")) {
response = enumerateApplications(request);
} else if (type.equals("enumerate-processes")) {
response = enumerateProcesses(request);
} else {
break;
}
byte[] rawResponse = (response != null)
? response.toString().getBytes()
: JSONObject.NULL.toString().getBytes();
output.writeInt(rawResponse.length);
output.write(rawResponse);
output.flush();
} catch (JSONException e) {
break;
} catch (EOFException e) {
break;
} catch (IOException e) {
break;
}
}
try {
client.close();
} catch (IOException e) {
}
}
private JSONArray getFrontmostApplication(JSONArray request) throws JSONException {
Scope scope = Scope.valueOf(request.getString(1).toUpperCase());
String pkgName = getFrontmostPackageName();
if (pkgName == null) {
return null;
}
ApplicationInfo appInfo;
try {
appInfo = mPackageManager.getApplicationInfo(pkgName, 0);
} catch (NameNotFoundException e) {
return null;
}
CharSequence appLabel = appInfo.loadLabel(mPackageManager);
int pid = 0;
List<RunningAppProcessInfo> pkgProcesses = getAppProcesses().get(pkgName);
if (pkgProcesses != null) {
pid = pkgProcesses.get(0).pid;
}
JSONObject parameters = null;
if (scope != Scope.MINIMAL) {
try {
parameters = fetchAppParameters(appInfo, scope);
} catch (NameNotFoundException e) {
return null;
}
if (pid != 0) {
try {
addProcessMetadata(parameters, pid);
} catch (IOException e) {
return null;
}
}
}
JSONArray app = new JSONArray();
app.put(pkgName);
app.put(appLabel);
app.put(pid);
app.put(parameters);
return app;
}
private JSONArray enumerateApplications(JSONArray request) throws JSONException {
JSONArray identifiersValue = request.getJSONArray(1);
Scope scope = Scope.valueOf(request.getString(2).toUpperCase());
List<ApplicationInfo> apps;
int numIdentifiers = identifiersValue.length();
if (numIdentifiers > 0) {
apps = new ArrayList<ApplicationInfo>();
for (int i = 0; i != numIdentifiers; i++) {
String pkgName = identifiersValue.getString(i);
try {
apps.add(mPackageManager.getApplicationInfo(pkgName, 0));
} catch (NameNotFoundException e) {
}
}
} else {
apps = getLauncherApplications();
}
JSONArray result = new JSONArray();
Map<String, List<RunningAppProcessInfo>> processes = getAppProcesses();
String frontmostPkgName = (scope != Scope.MINIMAL) ? getFrontmostPackageName() : null;
for (ApplicationInfo appInfo : apps) {
String pkgName = appInfo.packageName;
CharSequence appLabel = appInfo.loadLabel(mPackageManager);
int pid = 0;
List<RunningAppProcessInfo> pkgProcesses = processes.get(pkgName);
if (pkgProcesses != null) {
pid = pkgProcesses.get(0).pid;
}
JSONObject parameters = null;
if (scope != Scope.MINIMAL) {
try {
parameters = fetchAppParameters(appInfo, scope);
} catch (NameNotFoundException e) {
continue;
}
if (pid != 0) {
try {
addProcessMetadata(parameters, pid);
} catch (IOException e) {
pid = 0;
}
}
if (pid != 0 && pkgName.equals(frontmostPkgName)) {
parameters.put("frontmost", true);
}
}
JSONArray app = new JSONArray();
app.put(pkgName);
app.put(appLabel);
app.put(pid);
app.put(parameters);
result.put(app);
}
return result;
}
private JSONArray enumerateProcesses(JSONArray request) throws JSONException {
JSONArray pidsValue = request.getJSONArray(1);
Scope scope = Scope.valueOf(request.getString(2).toUpperCase());
int numPids = pidsValue.length();
List<Integer> pids = new ArrayList<Integer>(numPids);
if (numPids > 0) {
for (int i = 0; i != numPids; i++) {
pids.add(pidsValue.getInt(i));
}
} else {
int myPid = Process.myPid();
for (File candidate : new File("/proc").listFiles()) {
if (!candidate.isDirectory()) {
continue;
}
int pid;
try {
pid = Integer.parseInt(candidate.getName());
} catch (NumberFormatException e) {
continue;
}
if (pid == myPid) {
continue;
}
pids.add(pid);
}
}
JSONArray result = new JSONArray();
Map<String, List<RunningAppProcessInfo>> appProcessByPkgName = getAppProcesses();
Map<Integer, RunningAppProcessInfo> appProcessByPid = new HashMap<Integer, RunningAppProcessInfo>();
for (List<RunningAppProcessInfo> processes : appProcessByPkgName.values()) {
for (RunningAppProcessInfo process : processes) {
appProcessByPid.put(process.pid, process);
}
}
Map<String, ApplicationInfo> appInfoByPkgName = new HashMap<String, ApplicationInfo>();
for (ApplicationInfo appInfo : getLauncherApplications()) {
appInfoByPkgName.put(appInfo.packageName, appInfo);
}
Map<Integer, ApplicationInfo> appInfoByPid = new HashMap<Integer, ApplicationInfo>();
for (List<RunningAppProcessInfo> processes : appProcessByPkgName.values()) {
RunningAppProcessInfo mostImportantProcess = processes.get(0);
for (String pkgName : mostImportantProcess.pkgList) {
ApplicationInfo appInfo = appInfoByPkgName.get(pkgName);
if (appInfo != null) {
appInfoByPid.put(mostImportantProcess.pid, appInfo);
break;
}
}
}
int frontmostPid = -1;
if (scope != Scope.MINIMAL) {
String frontmostPkgName = getFrontmostPackageName();
if (frontmostPkgName != null) {
List<RunningAppProcessInfo> frontmostProcesses = appProcessByPkgName.get(frontmostPkgName);
if (frontmostProcesses != null) {
frontmostPid = frontmostProcesses.get(0).pid;
}
}
}
for (Integer pid : pids) {
File procDir = new File("/proc", pid.toString());
ApplicationInfo appInfo = appInfoByPid.get(pid);
CharSequence name;
if (appInfo != null) {
name = appInfo.loadLabel(mPackageManager);
} else {
String cmdline;
try {
cmdline = getFileContentsAsString(new File(procDir, "cmdline"));
} catch (IOException e) {
continue;
}
boolean isKernelProcess = cmdline.isEmpty();
if (isKernelProcess) {
continue;
}
name = deriveProcessNameFromCmdline(cmdline);
}
JSONObject parameters = null;
if (scope != Scope.MINIMAL) {
parameters = new JSONObject();
try {
File program = new File(Os.readlink(new File(procDir, "exe").getAbsolutePath()));
parameters.put("path", program.getAbsolutePath());
} catch (ErrnoException e) {
}
try {
addProcessMetadata(parameters, pid);
} catch (IOException e) {
continue;
}
RunningAppProcessInfo appProcess = appProcessByPid.get(pid);
if (appProcess != null) {
JSONArray ids = new JSONArray();
for (String pkgName : appProcess.pkgList) {
ids.put(pkgName);
}
parameters.put("applications", ids);
}
if (scope == Scope.FULL && appInfo != null) {
JSONArray icons = new JSONArray();
icons.put(fetchAppIcon(appInfo));
parameters.put("$icons", icons);
}
if (pid == frontmostPid) {
parameters.put("frontmost", true);
}
}
JSONArray process = new JSONArray();
process.put(pid);
process.put(name);
process.put(parameters);
result.put(process);
}
return result;
}
@SuppressWarnings("deprecation")
private String getFrontmostPackageName() {
if (mTopActivityField == null) {
return null;
}
List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
if (tasks.isEmpty()) {
return null;
}
RunningTaskInfo task = tasks.get(0);
ComponentName name;
try {
name = (ComponentName) mTopActivityField.get(task);
} catch (Exception e) {
throw new RuntimeException(e);
}
String pkgName = name.getPackageName();
if (pkgName.equals(mLauncherPkgName)) {
return null;
}
return pkgName;
}
private List<ApplicationInfo> getLauncherApplications() {
List<ApplicationInfo> apps = new ArrayList<ApplicationInfo>();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
for (ResolveInfo resolveInfo : mPackageManager.queryIntentActivities(intent, 0)) {
apps.add(resolveInfo.activityInfo.applicationInfo);
}
return apps;
}
private JSONObject fetchAppParameters(ApplicationInfo appInfo, Scope scope) throws NameNotFoundException {
JSONObject parameters = new JSONObject();
PackageInfo packageInfo = mPackageManager.getPackageInfo(appInfo.packageName, 0);
try {
parameters.put("version", packageInfo.versionName);
parameters.put("build", Integer.toString(packageInfo.versionCode));
parameters.put("sources", fetchAppSources(appInfo));
parameters.put("data-dir", appInfo.dataDir);
parameters.put("target-sdk", appInfo.targetSdkVersion);
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
parameters.put("debuggable", true);
}
if (scope == Scope.FULL) {
JSONArray icons = new JSONArray();
icons.put(fetchAppIcon(appInfo));
parameters.put("$icons", icons);
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
return parameters;
}
private static JSONArray fetchAppSources(ApplicationInfo appInfo) {
JSONArray sources = new JSONArray();
sources.put(appInfo.publicSourceDir);
String[] splitDirs = appInfo.splitPublicSourceDirs;
if (splitDirs != null) {
for (String splitDir : splitDirs) {
sources.put(splitDir);
}
}
return sources;
}
private String fetchAppIcon(ApplicationInfo appInfo) {
Drawable icon = mPackageManager.getApplicationIcon(appInfo);
int width = icon.getIntrinsicWidth();
int height = icon.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
icon.setBounds(0, 0, width, height);
icon.draw(canvas);
ByteArrayOutputStream output = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, new Base64OutputStream(output, Base64.NO_WRAP));
return output.toString();
}
private Map<String, List<RunningAppProcessInfo>> getAppProcesses() {
Map<String, List<RunningAppProcessInfo>> processes = new HashMap<String, List<RunningAppProcessInfo>>();
for (RunningAppProcessInfo processInfo : mActivityManager.getRunningAppProcesses()) {
for (String pkgName : processInfo.pkgList) {
List<RunningAppProcessInfo> entries = processes.get(pkgName);
if (entries == null) {
entries = new ArrayList<RunningAppProcessInfo>();
processes.put(pkgName, entries);
}
entries.add(processInfo);
if (entries.size() > 1) {
Collections.sort(entries, new Comparator<RunningAppProcessInfo>() {
@Override
public int compare(RunningAppProcessInfo a, RunningAppProcessInfo b) {
return a.importance - b.importance;
}
});
}
}
}
return processes;
}
private static String deriveProcessNameFromCmdline(String cmdline) {
String str = cmdline;
int spaceDashOffset = str.indexOf(" -");
if (spaceDashOffset != -1) {
str = str.substring(0, spaceDashOffset);
}
return new File(str).getName();
}
private void addProcessMetadata(JSONObject parameters, int pid) throws IOException {
File procNode = new File("/proc/" + Integer.toString(pid));
String status = getFileContentsAsString(new File(procNode, "status"));
Matcher m = sStatusUidPattern.matcher(status);
m.find();
int uid = Integer.parseInt(m.group(1));
String stat = getFileContentsAsString(new File(procNode, "stat"));
int commFieldEndOffset = stat.indexOf(')');
int stateFieldStartOffset = commFieldEndOffset + 2;
String[] statFields = stat.substring(stateFieldStartOffset).split(" ");
int manPageFieldIdOffset = 3;
int ppid = Integer.parseInt(statFields[4 - manPageFieldIdOffset]);
long startTimeDeltaInJiffies = Long.parseLong(statFields[22 - manPageFieldIdOffset]);
long startTimeDeltaInMilliseconds = startTimeDeltaInJiffies * mMillisecondsPerJiffy;
Date started = new Date(mSystemBootTime + startTimeDeltaInMilliseconds);
try {
parameters.put("user", resolveUserIdToName(uid));
parameters.put("ppid", ppid);
parameters.put("started", mIso8601.format(started));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
private static long querySystemBootTime() {
String stat;
try {
stat = getFileContentsAsString(new File("/proc/stat"));
} catch (IOException e) {
throw new RuntimeException(e);
}
Matcher m = Pattern.compile("^btime (\\d+)$", Pattern.MULTILINE).matcher(stat);
m.find();
return Long.parseLong(m.group(1)) * 1000;
}
private String resolveUserIdToName(int uid) {
try {
return (String) mPwnameField.get(mGetpwuid.invoke(null, uid));
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
private String detectLauncherPackageName() {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> launchers = mPackageManager.queryIntentActivities(intent, 0);
if (launchers.isEmpty()) {
return null;
}
return launchers.get(0).activityInfo.packageName;
}
private static String getFileContentsAsString(File file) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
FileInputStream input = new FileInputStream(file);
try {
byte[] buffer = new byte[64 * 1024];
while (true) {
int n = input.read(buffer);
if (n == -1) {
break;
}
result.write(buffer, 0, n);
}
} finally {
input.close();
}
return result.toString();
}
}
enum Scope {
MINIMAL,
METADATA,
FULL;
}
+19 -37
View File
@@ -172,8 +172,6 @@ static gboolean frida_exec_instance_try_perform_transition (FridaExecInstance *
static void frida_exec_instance_suspend (FridaExecInstance * self);
static void frida_exec_instance_resume (FridaExecInstance * self);
static void frida_make_pipe (int fds[2]);
static FridaInjectInstance * frida_inject_instance_new (FridaBinjector * binjector, guint id, guint pid, const FridaRemoteApi * api,
const gchar * temp_path);
static void frida_inject_instance_recreate_fifo (FridaInjectInstance * self);
@@ -211,7 +209,10 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
{
FridaSpawnInstance * instance;
gchar ** argv, ** envp;
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
FridaFileDescriptor * in_fd = NULL;
FridaFileDescriptor * out_fd = NULL;
FridaFileDescriptor * err_fd = NULL;
GError * stdio_error = NULL;
gchar * old_cwd = NULL;
gboolean success;
const gchar * failed_operation;
@@ -221,24 +222,9 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
argv = frida_host_spawn_options_compute_argv (options, path, NULL);
envp = frida_host_spawn_options_compute_envp (options, NULL);
switch (options->stdio)
{
case FRIDA_STDIO_INHERIT:
*pipes = NULL;
break;
case FRIDA_STDIO_PIPE:
frida_make_pipe (stdin_pipe);
frida_make_pipe (stdout_pipe);
frida_make_pipe (stderr_pipe);
*pipes = frida_stdio_pipes_new (stdin_pipe[1], stdout_pipe[0], stderr_pipe[0]);
break;
default:
g_assert_not_reached ();
}
*pipes = frida_make_stdio_pipes (options->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &stdio_error);
if (stdio_error != NULL)
goto propagate_stdio_error;
if (strlen (options->cwd) > 0)
{
@@ -254,9 +240,9 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
if (options->stdio == FRIDA_STDIO_PIPE)
{
dup2 (stdin_pipe[0], 0);
dup2 (stdout_pipe[1], 1);
dup2 (stderr_pipe[1], 2);
dup2 (in_fd->handle, 0);
dup2 (out_fd->handle, 1);
dup2 (err_fd->handle, 2);
}
ptrace (PT_TRACE_ME, 0, NULL, 0);
@@ -273,13 +259,6 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
g_warning ("Failed to restore working directory");
}
if (options->stdio == FRIDA_STDIO_PIPE)
{
close (stdin_pipe[0]);
close (stdout_pipe[1]);
close (stderr_pipe[1]);
}
success = frida_wait_for_child_signal (instance->pid, SIGTRAP, NULL);
CHECK_OS_RESULT (success, !=, FALSE, "wait(SIGTRAP)");
@@ -290,6 +269,11 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
goto beach;
propagate_stdio_error:
{
g_propagate_error (error, stdio_error);
goto failure;
}
chdir_failed:
{
g_set_error (error,
@@ -316,6 +300,10 @@ failure:
}
beach:
{
g_clear_object (&in_fd);
g_clear_object (&out_fd);
g_clear_object (&err_fd);
g_free (old_cwd);
g_strfreev (envp);
g_strfreev (argv);
@@ -736,12 +724,6 @@ frida_exec_instance_resume (FridaExecInstance * self)
ptrace (PT_DETACH, self->pid, NULL, 0);
}
static void
frida_make_pipe (int fds[2])
{
g_unix_open_pipe (fds, FD_CLOEXEC, NULL);
}
static FridaInjectInstance *
frida_inject_instance_new (FridaBinjector * binjector, guint id, guint pid, const FridaRemoteApi * api, const gchar * temp_path)
{
+3 -40
View File
@@ -68,9 +68,9 @@ namespace Frida {
monitor_child (child_pid);
if (pipes != null) {
stdin_streams[child_pid] = new UnixOutputStream (pipes.input, false);
process_next_output_from.begin (new UnixInputStream (pipes.output, false), child_pid, 1, pipes);
process_next_output_from.begin (new UnixInputStream (pipes.error, false), child_pid, 2, pipes);
stdin_streams[child_pid] = pipes.input;
process_next_output_from.begin (pipes.output, child_pid, 1, pipes);
process_next_output_from.begin (pipes.error, child_pid, 2, pipes);
}
return child_pid;
@@ -696,41 +696,4 @@ namespace Frida {
protected enum ProgressMessageType {
HELLO = 0xff
}
protected class StdioPipes : Object {
public int input {
get;
construct;
}
public int output {
get;
construct;
}
public int error {
get;
construct;
}
public StdioPipes (int input, int output, int error) {
Object (input: input, output: output, error: error);
}
construct {
try {
Unix.set_fd_nonblocking (input, true);
Unix.set_fd_nonblocking (output, true);
Unix.set_fd_nonblocking (error, true);
} catch (GLib.Error e) {
assert_not_reached ();
}
}
~StdioPipes () {
Posix.close (input);
Posix.close (output);
Posix.close (error);
}
}
}
+8 -2
View File
@@ -24,7 +24,7 @@ namespace Frida {
}
}
public sealed class DeviceManager : Object {
public sealed class DeviceManager : Object, HostSessionHub {
public signal void added (Device device);
public signal void removed (Device device);
public signal void changed ();
@@ -394,6 +394,12 @@ namespace Frida {
changed ();
}
private async HostSessionEntry resolve_host_session (string id, Cancellable? cancellable) throws Error, IOError {
var device = yield get_device_by_id (id, 0, cancellable);
var session = yield device.get_host_session (cancellable);
return new HostSessionEntry (device.provider, session);
}
private void check_open () throws Error {
if (stop_request != null)
throw new Error.INVALID_OPERATION ("Device manager is closed");
@@ -1326,7 +1332,7 @@ namespace Frida {
host_session_request = new Promise<HostSession> ();
try {
var session = yield provider.create (host_session_options, cancellable);
var session = yield provider.create (manager, host_session_options, cancellable);
attach_host_session (session);
current_host_session = session;
+89 -24
View File
@@ -961,7 +961,10 @@ namespace Frida.Fruity {
private LibUSB.Context? usb_context;
private LibUSB.HotCallbackHandle iphone_callback;
private LibUSB.HotCallbackHandle ipad_callback;
private uint pending_usb_device_arrivals = 0;
private Gee.Set<uint32> startup_counted = new Gee.HashSet<uint32> ();
private uint startup_inflight = 0;
private uint startup_barrier_reached = 0;
private uint usb_started_resolved = 0;
private Gee.Map<uint32, LibUSB.Device> polled_usb_devices = new Gee.HashMap<uint32, LibUSB.Device> ();
private Source? polled_usb_timer;
private uint polled_usb_outdated = 0;
@@ -1099,14 +1102,17 @@ namespace Frida.Fruity {
return;
}
AtomicUint.inc (ref pending_usb_device_arrivals);
bool callbacks_registered = true;
bool callbacks_registered = false;
if (LibUSB.has_capability (HAS_HOTPLUG) != 0) {
usb_context.hotplug_register_callback (DEVICE_ARRIVED | DEVICE_LEFT, ENUMERATE, VENDOR_ID_APPLE,
PRODUCT_ID_IPHONE, LibUSB.HotPlugEvent.MATCH_ANY, on_usb_hotplug_event, out iphone_callback);
usb_context.hotplug_register_callback (DEVICE_ARRIVED | DEVICE_LEFT, ENUMERATE, VENDOR_ID_APPLE,
PRODUCT_ID_IPAD, LibUSB.HotPlugEvent.MATCH_ANY, on_usb_hotplug_event, out ipad_callback);
callbacks_registered = true;
usb_context.interrupt_event_handler ();
int completed = 0;
usb_context.handle_events_completed (out completed);
} else {
refresh_polled_usb_devices ();
@@ -1120,21 +1126,17 @@ namespace Frida.Fruity {
polled_usb_timer = source;
}
if (AtomicUint.dec_and_test (ref pending_usb_device_arrivals)) {
schedule_on_frida_thread (() => {
usb_started.resolve (true);
return Source.REMOVE;
});
}
schedule_startup_barrier_close ();
while (state != STOPPING) {
int completed = 0;
usb_context.handle_events_completed (out completed);
while (true) {
State s;
lock (state)
s = state;
if (AtomicUint.compare_and_exchange (ref polled_usb_outdated, 1, 0))
refresh_polled_usb_devices ();
if (s == STOPPING)
break;
if (state == FLUSHING) {
if (s == FLUSHING) {
if (callbacks_registered) {
usb_context.hotplug_deregister_callback (iphone_callback);
usb_context.hotplug_deregister_callback (ipad_callback);
@@ -1147,10 +1149,18 @@ namespace Frida.Fruity {
}
lock (state) {
if (pending_usb_ops.is_empty)
if (pending_usb_ops.is_empty) {
state = STOPPING;
break;
}
}
}
int completed = 0;
usb_context.handle_events_completed (out completed);
if (s != FLUSHING && AtomicUint.compare_and_exchange (ref polled_usb_outdated, 1, 0))
refresh_polled_usb_devices ();
}
schedule_on_frida_thread (() => {
@@ -1168,9 +1178,22 @@ namespace Frida.Fruity {
}
private void on_usb_device_arrived (LibUSB.Device device) {
AtomicUint.inc (ref pending_usb_device_arrivals);
var desc = LibUSB.DeviceDescriptor (device);
uint32 id = make_usb_device_id (device, desc);
bool counted_for_startup = false;
lock (state) {
if (state == STARTING && AtomicUint.get (ref startup_barrier_reached) == 0) {
if (!startup_counted.contains (id)) {
startup_counted.add (id);
AtomicUint.inc (ref startup_inflight);
counted_for_startup = true;
}
}
}
schedule_on_frida_thread (() => {
handle_usb_device_arrival.begin (device);
handle_usb_device_arrival.begin (device, counted_for_startup, id);
return Source.REMOVE;
});
}
@@ -1182,7 +1205,7 @@ namespace Frida.Fruity {
});
}
private async void handle_usb_device_arrival (LibUSB.Device raw_device) {
private async void handle_usb_device_arrival (LibUSB.Device raw_device, bool counted_for_startup, uint32 counted_id) {
try {
UsbDevice usb_device;
try {
@@ -1199,7 +1222,11 @@ namespace Frida.Fruity {
var transport = usb_transports.first_match (t => t.udid == udid);
bool may_need_time_to_settle = state != STARTING && (transport == null || transport.modeswitch_in_progress);
bool starting;
lock (state)
starting = state == STARTING;
bool may_need_time_to_settle = !starting && (transport == null || transport.modeswitch_in_progress);
if (may_need_time_to_settle) {
try {
yield sleep (250, io_cancellable);
@@ -1219,7 +1246,11 @@ namespace Frida.Fruity {
transport = new PortableCoreDeviceUsbTransport (this, usb_device, pairing_store);
usb_transports.add (transport);
if (state != STARTING) {
bool open_now;
lock (state)
open_now = state != STARTING;
if (open_now) {
try {
yield transport.open (io_cancellable);
} catch (GLib.Error e) {
@@ -1229,11 +1260,19 @@ namespace Frida.Fruity {
transport_attached (transport);
}
} finally {
if (AtomicUint.dec_and_test (ref pending_usb_device_arrivals) && state == STARTING)
usb_started.resolve (true);
if (counted_for_startup)
resolve_startup_device (counted_id);
}
}
private void resolve_startup_device (uint32 id) {
lock (state)
startup_counted.remove (id);
if (AtomicUint.dec_and_test (ref startup_inflight))
maybe_resolve_usb_started ();
}
private async void handle_usb_device_departure (LibUSB.Device raw_device) {
var transport = usb_transports.first_match (t => t.usb_device.raw_device == raw_device && !t.modeswitch_in_progress);
if (transport == null)
@@ -1274,6 +1313,32 @@ namespace Frida.Fruity {
polled_usb_devices = current_devices;
}
private void maybe_resolve_usb_started () {
if (AtomicUint.get (ref startup_barrier_reached) == 0)
return;
if (AtomicUint.get (ref startup_inflight) != 0)
return;
if (!AtomicUint.compare_and_exchange (ref usb_started_resolved, 0, 1))
return;
schedule_on_frida_thread (() => {
usb_started.resolve (true);
return Source.REMOVE;
});
}
private void schedule_startup_barrier_close () {
var source = new IdleSource ();
source.set_priority (Priority.LOW);
source.set_callback (() => {
AtomicUint.set (ref startup_barrier_reached, 1);
maybe_resolve_usb_started ();
return Source.REMOVE;
});
source.attach (main_context);
}
private UsbOperation allocate_usb_operation () throws Error {
var op = new PendingUsbOperation (this);
+2 -1
View File
@@ -80,7 +80,8 @@ namespace Frida {
yield Fruity.DTXConnection.close_all (device, cancellable);
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (host_session != null)
throw new Error.INVALID_OPERATION ("Already created");
+6 -2
View File
@@ -88,8 +88,12 @@ namespace Frida.Fruity {
request.set_string ("SystemBUID", system_buid);
var response = yield service.query (request, cancellable);
if (response.has ("Error"))
throw new LockdownError.PROTOCOL ("Unexpected response: %s", response.get_string ("Error"));
if (response.has ("Error")) {
var error = response.get_string ("Error");
if (error == "InvalidHostID")
yield unpair (cancellable);
throw new LockdownError.PROTOCOL ("Unexpected response: %s", error);
}
if (response.get_boolean ("EnableSessionSSL"))
service.stream = yield start_tls (service.stream, cancellable);
+4 -1
View File
@@ -760,8 +760,11 @@ namespace Frida.Fruity {
return OK;
}));
if (n == 0)
if (n == 0) {
if (state == CLOSED)
throw new IOError.CLOSED ("Connection is closed");
throw new IOError.WOULD_BLOCK ("Resource temporarily unavailable");
}
return n;
}
+55 -11
View File
@@ -47,6 +47,9 @@ namespace Frida {
}
private void add_nonlocal_backends () {
#if HAVE_SIMMY_BACKEND
add_backend (new SimmyHostSessionBackend ());
#endif
#if !IOS && !ANDROID && !TVOS
#if HAVE_FRUITY_BACKEND
add_backend (new FruityHostSessionBackend ());
@@ -161,7 +164,7 @@ namespace Frida {
get;
}
public abstract async HostSession create (HostSessionOptions? options = null,
public abstract async HostSession create (HostSessionHub hub, HostSessionOptions? options = null,
Cancellable? cancellable = null) throws Error, IOError;
public abstract async void destroy (HostSession session, Cancellable? cancellable = null) throws Error, IOError;
public signal void host_session_detached (HostSession session);
@@ -180,6 +183,42 @@ namespace Frida {
public abstract void unlink_service_session (HostSession host_session, ServiceSessionId id);
}
public interface HostSessionConnection : Object {
public abstract HostSession host_session {
get;
}
public abstract async AgentSession link_agent_session (AgentSessionId id, AgentMessageSink sink,
Cancellable? cancellable = null) throws Error, IOError;
public abstract void unlink_agent_session (AgentSessionId id);
}
public interface HostSessionHub : Object {
public abstract async HostSessionEntry resolve_host_session (string id, Cancellable? cancellable) throws Error, IOError;
}
public class NullHostSessionHub : Object, HostSessionHub {
public async HostSessionEntry resolve_host_session (string id, Cancellable? cancellable) throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Host session lookup not supported");
}
}
public class HostSessionEntry : Object {
public HostSessionProvider provider {
get;
construct;
}
public HostSession session {
get;
construct;
}
public HostSessionEntry (HostSessionProvider provider, HostSession session) {
Object (provider: provider, session: session);
}
}
public enum HostSessionProviderKind {
LOCAL,
REMOTE,
@@ -277,7 +316,8 @@ namespace Frida {
host_session = null;
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (host_session != null)
throw new Error.INVALID_OPERATION ("Already created");
@@ -332,7 +372,13 @@ namespace Frida {
}
}
public abstract class LocalHostSession : Object, HostSession, AgentController {
public abstract class LocalHostSession : Object, HostSession, HostSessionConnection, AgentController {
public HostSession host_session {
get {
return this;
}
}
private Gee.HashMap<uint, Cancellable> pending_establish_ops = new Gee.HashMap<uint, Cancellable> ();
private Gee.HashMap<uint, Future<AgentEntry>> agent_entries = new Gee.HashMap<uint, Future<AgentEntry>> ();
@@ -1423,7 +1469,7 @@ namespace Frida {
public abstract class InternalAgent : Object, AgentMessageSink, RpcPeer {
public signal void unloaded ();
public weak LocalHostSession host_session {
public weak HostSessionConnection connection {
get;
construct;
}
@@ -1447,11 +1493,7 @@ namespace Frida {
construct {
rpc_client = new RpcClient (this);
host_session.agent_session_detached.connect (on_agent_session_detached);
}
~InternalAgent () {
host_session.agent_session_detached.disconnect (on_agent_session_detached);
connection.host_session.agent_session_detached.connect (on_agent_session_detached);
}
public async void close (Cancellable? cancellable) throws IOError {
@@ -1463,6 +1505,8 @@ namespace Frida {
}
yield ensure_unloaded (cancellable);
connection.host_session.agent_session_detached.disconnect (on_agent_session_detached);
}
protected abstract async uint get_target_pid (Cancellable? cancellable) throws Error, IOError;
@@ -1516,9 +1560,9 @@ namespace Frida {
target_pid = yield get_target_pid (cancellable);
try {
session_id = yield host_session.attach (target_pid, attach_options, cancellable);
session_id = yield connection.host_session.attach (target_pid, attach_options, cancellable);
session = yield host_session.link_agent_session (session_id, (AgentMessageSink) this, cancellable);
session = yield connection.link_agent_session (session_id, (AgentMessageSink) this, cancellable);
string? source = yield load_source (cancellable);
if (source != null) {
-22
View File
@@ -1,22 +0,0 @@
import esbuild from 'esbuild';
import alias from 'esbuild-plugin-alias';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const BUFFER_SHIM = require.resolve('@frida/buffer');
esbuild.build({
entryPoints: ['index.js'],
outfile: 'system-server.js',
bundle: true,
platform: 'node',
target: ['es2022'],
legalComments: 'none',
minify: true,
plugins: [
alias({
'buffer': BUFFER_SHIM,
'node:buffer': BUFFER_SHIM,
}),
],
}).catch(() => process.exit(1));
-492
View File
@@ -1,492 +0,0 @@
import Java from 'frida-java-bridge';
let ApplicationInfo, Base64OutputStream, Bitmap, ByteArrayOutputStream, Canvas, ComponentName, ContextWrapper, Intent, ResolveInfo,
RunningAppProcessInfo, RunningTaskInfo, UserHandle;
let ACTION_MAIN, ARGB_8888, CATEGORY_HOME, CATEGORY_LAUNCHER, GET_ACTIVITIES, FLAG_ACTIVITY_NEW_TASK, FLAG_DEBUGGABLE, NO_WRAP, PNG;
let context, packageManager, activityManager, loadAppLabel, loadResolveInfoLabel;
let multiUserSupported;
let launcherPkgName;
const pendingLaunches = new Map();
function init() {
const ActivityManager = Java.use('android.app.ActivityManager');
const ActivityThread = Java.use('android.app.ActivityThread');
ApplicationInfo = Java.use('android.content.pm.ApplicationInfo');
const Base64 = Java.use('android.util.Base64');
Base64OutputStream = Java.use('android.util.Base64OutputStream');
Bitmap = Java.use('android.graphics.Bitmap');
const BitmapCompressFormat = Java.use('android.graphics.Bitmap$CompressFormat');
const BitmapConfig = Java.use('android.graphics.Bitmap$Config');
ByteArrayOutputStream = Java.use('java.io.ByteArrayOutputStream');
Canvas = Java.use('android.graphics.Canvas');
ComponentName = Java.use('android.content.ComponentName');
ContextWrapper = Java.use('android.content.ContextWrapper');
Intent = Java.use('android.content.Intent');
const Context = Java.use('android.content.Context');
const PackageManager = Java.use('android.content.pm.PackageManager');
ResolveInfo = Java.use('android.content.pm.ResolveInfo');
RunningAppProcessInfo = Java.use('android.app.ActivityManager$RunningAppProcessInfo');
RunningTaskInfo = Java.use('android.app.ActivityManager$RunningTaskInfo');
UserHandle = Java.use('android.os.UserHandle');
const ACTIVITY_SERVICE = Context.ACTIVITY_SERVICE.value;
ACTION_MAIN = Intent.ACTION_MAIN.value;
ARGB_8888 = BitmapConfig.ARGB_8888.value;
CATEGORY_HOME = Intent.CATEGORY_HOME.value;
CATEGORY_LAUNCHER = Intent.CATEGORY_LAUNCHER.value;
GET_ACTIVITIES = PackageManager.GET_ACTIVITIES.value;
FLAG_ACTIVITY_NEW_TASK = Intent.FLAG_ACTIVITY_NEW_TASK.value;
FLAG_DEBUGGABLE = ApplicationInfo.FLAG_DEBUGGABLE.value;
NO_WRAP = Base64.NO_WRAP.value;
PNG = BitmapCompressFormat.PNG.value;
context = ActivityThread.currentApplication();
packageManager = context.getPackageManager();
activityManager = Java.cast(context.getSystemService(ACTIVITY_SERVICE), ActivityManager);
loadAppLabel = ApplicationInfo.loadUnsafeLabel ?? ApplicationInfo.loadLabel;
multiUserSupported = 'getApplicationInfoAsUser' in PackageManager;
launcherPkgName = detectLauncherPackageName();
installLaunchTimeoutRemovalInstrumentation();
}
rpc.exports = {
getFrontmostApplication(scope) {
return performOnJavaVM(() => {
const pkgName = getFrontmostPackageName();
if (pkgName === null)
return null;
const appInfo = packageManager.getApplicationInfo(pkgName, 0);
const appLabel = loadAppLabel.call(appInfo, packageManager).toString();
const pid = computeAppPids(getAppProcesses()).get(pkgName) ?? 0;
const parameters = (scope !== 'minimal') ? fetchAppParameters(pkgName, appInfo, scope) : null;
return [pkgName, appLabel, pid, parameters];
});
},
enumerateApplications(identifiers, scope) {
return performOnJavaVM(() => {
const apps = [];
if (identifiers.length > 0) {
for (const pkgName of identifiers) {
try {
apps.push([pkgName, packageManager.getApplicationInfo(pkgName, 0)]);
} catch (e) {
}
}
} else {
for (const appInfo of getLauncherApplications()) {
apps.push([appInfo.packageName.value, appInfo]);
}
}
const result = [];
const pids = computeAppPids(getAppProcesses());
const includeParameters = scope !== 'minimal';
const frontmostPkgName = includeParameters ? getFrontmostPackageName() : null;
for (const [pkgName, appInfo] of apps) {
const appLabel = loadAppLabel.call(appInfo, packageManager).toString();
const pid = pids.get(pkgName) ?? 0;
let parameters = null;
if (includeParameters) {
parameters = fetchAppParameters(pkgName, appInfo, scope);
if (pkgName === frontmostPkgName)
parameters.frontmost = true;
}
result.push([pkgName, appLabel, pid, parameters]);
}
return result;
});
},
getProcessName(pkgName, uid) {
checkUidOptionSupported(uid);
return performOnJavaVM(() => {
try {
return getAppInfo(pkgName, uid).processName.value;
} catch (e) {
throw new Error(`Unable to find application with identifier '${pkgName}'${(uid !== 0) ? ' belonging to uid ' + uid : ''}`);
}
});
},
getProcessParameters(pids, scope) {
const result = {};
const appProcesses = getAppProcesses();
const appPidByPkgName = computeAppPids(appProcesses);
const appProcessByPid = new Map();
for (const process of appProcesses)
appProcessByPid.set(process.pid, process);
const appInfoByPkgName = new Map();
for (const appInfo of getLauncherApplications())
appInfoByPkgName.set(appInfo.packageName.value, appInfo);
const appInfoByPid = new Map();
for (const [pkgName, appPid] of appPidByPkgName.entries()) {
const appInfo = appInfoByPkgName.get(pkgName);
if (appInfo !== undefined)
appInfoByPid.set(appPid, appInfo);
}
let frontmostPid = -1;
const frontmostPkgName = getFrontmostPackageName();
if (frontmostPkgName !== null) {
frontmostPid = appPidByPkgName.get(frontmostPkgName) ?? -1;
}
const includeParameters = scope !== 'minimal';
const includeIcons = scope === 'full';
for (const pid of pids) {
const parameters = {};
const appInfo = appInfoByPid.get(pid);
if (appInfo !== undefined) {
parameters.$name = loadAppLabel.call(appInfo, packageManager).toString()
if (includeIcons)
parameters.$icon = fetchAppIcon(appInfo);
}
if (includeParameters) {
const appProcess = appProcessByPid.get(pid);
if (appProcess !== undefined) {
parameters.applications = appProcess.pkgList;
}
if (pid === frontmostPid) {
parameters.frontmost = true;
}
}
if (Object.keys(parameters).length !== 0) {
result[pid] = parameters;
}
}
return result;
},
startActivity(pkgName, activity, uid) {
checkUidOptionSupported(uid);
return performOnJavaVM(() => {
let user, ctx, pm;
if (uid !== 0) {
user = UserHandle.of(uid);
ctx = context.createPackageContextAsUser(pkgName, 0, user);
pm = ctx.getPackageManager();
} else {
user = null;
ctx = context;
pm = packageManager;
}
let appInstalled = false;
const apps = (uid !== 0)
? pm.getInstalledApplicationsAsUser(0, uid)
: pm.getInstalledApplications(0);
const numApps = apps.size();
for (let i = 0; i !== numApps; i++) {
const appInfo = Java.cast(apps.get(i), ApplicationInfo);
if (appInfo.packageName.value === pkgName) {
appInstalled = true;
break;
}
}
if (!appInstalled)
throw new Error("Unable to find application with identifier '" + pkgName + "'");
let intent = pm.getLaunchIntentForPackage(pkgName);
if (intent === null && 'getLeanbackLaunchIntentForPackage' in pm)
intent = pm.getLeanbackLaunchIntentForPackage(pkgName);
if (intent === null && activity === null)
throw new Error('Unable to find a front-door activity');
if (intent === null) {
intent = Intent.$new();
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
}
if (activity !== null) {
const pkgInfo = (uid !== 0)
? pm.getPackageInfoAsUser(pkgName, GET_ACTIVITIES, uid)
: pm.getPackageInfo(pkgName, GET_ACTIVITIES);
const activities = pkgInfo.activities.value.map(activityInfo => activityInfo.name.value);
if (!activities.includes(activity))
throw new Error("Unable to find activity with identifier '" + activity + "'");
intent.setClassName(pkgName, activity);
}
performLaunchOperation(pkgName, uid, () => {
if (user !== null)
ContextWrapper.$new(ctx).startActivityAsUser(intent, user);
else
ctx.startActivity(intent);
});
});
},
sendBroadcast(pkgName, receiver, action, uid) {
checkUidOptionSupported(uid);
return performOnJavaVM(() => {
const intent = Intent.$new();
intent.setComponent(ComponentName.$new(pkgName, receiver));
intent.setAction(action);
performLaunchOperation(pkgName, uid, () => {
if (uid !== 0)
ContextWrapper.$new(context).sendBroadcastAsUser(intent, UserHandle.of(uid));
else
context.sendBroadcast(intent);
});
});
},
stopPackage(pkgName, uid) {
checkUidOptionSupported(uid);
return performOnJavaVM(() => {
if (uid !== 0)
activityManager.forceStopPackageAsUser(pkgName, uid);
else
activityManager.forceStopPackage(pkgName);
});
},
tryStopPackageByPid(pid) {
return performOnJavaVM(() => {
const processes = activityManager.getRunningAppProcesses();
const numProcesses = processes.size();
for (let i = 0; i !== numProcesses; i++) {
const process = Java.cast(processes.get(i), RunningAppProcessInfo);
if (process.pid.value === pid) {
for (const pkgName of process.pkgList.value) {
activityManager.forceStopPackage(pkgName);
}
return true;
}
}
return false;
});
},
};
function getFrontmostPackageName() {
const tasks = activityManager.getRunningTasks(1);
if (tasks.isEmpty())
return null;
const task = Java.cast(tasks.get(0), RunningTaskInfo);
if (task.topActivity === undefined)
return null;
const name = task.topActivity.value.getPackageName();
if (name === launcherPkgName)
return null;
return name;
}
function getLauncherApplications() {
const intent = Intent.$new(ACTION_MAIN);
intent.addCategory(CATEGORY_LAUNCHER);
const activities = packageManager.queryIntentActivities(intent, 0);
const result = [];
const n = activities.size();
for (let i = 0; i !== n; i++) {
const resolveInfo = Java.cast(activities.get(i), ResolveInfo);
result.push(resolveInfo.activityInfo.value.applicationInfo.value);
}
return result;
}
function getAppInfo(pkgName, uid) {
return (uid !== 0)
? packageManager.getApplicationInfoAsUser(pkgName, 0, uid)
: packageManager.getApplicationInfo(pkgName, 0);
}
function fetchAppParameters(pkgName, appInfo, scope) {
const pkgInfo = packageManager.getPackageInfo(pkgName, 0);
const parameters = {
'version': pkgInfo.versionName.value,
'build': pkgInfo.versionCode.value.toString(),
'sources': [appInfo.publicSourceDir.value].concat(appInfo.splitPublicSourceDirs?.value ?? []),
'data-dir': appInfo.dataDir.value,
'target-sdk': appInfo.targetSdkVersion.value,
};
if ((appInfo.flags.value & FLAG_DEBUGGABLE) !== 0)
parameters.debuggable = true;
if (scope === 'full')
parameters.$icon = fetchAppIcon(appInfo);
return parameters;
}
function fetchAppIcon(appInfo) {
const icon = packageManager.getApplicationIcon(appInfo);
const width = icon.getIntrinsicWidth();
const height = icon.getIntrinsicHeight();
const bitmap = Bitmap.createBitmap(width, height, ARGB_8888);
const canvas = Canvas.$new(bitmap);
icon.setBounds(0, 0, width, height);
icon.draw(canvas);
const output = ByteArrayOutputStream.$new();
bitmap.compress(PNG, 100, Base64OutputStream.$new(output, NO_WRAP));
return output.toString('US-ASCII');
}
function getAppProcesses() {
const result = [];
const processes = activityManager.getRunningAppProcesses();
const n = processes.size();
for (let i = 0; i !== n; i++) {
const process = Java.cast(processes.get(i), RunningAppProcessInfo);
result.push({
pid: process.pid.value,
importance: process.importance.value,
pkgList: process.pkgList.value
});
}
return result;
}
function computeAppPids(processes) {
const pids = new Map();
for (const { pid, importance, pkgList } of processes) {
for (const pkgName of pkgList) {
let entries = pids.get(pkgName);
if (entries === undefined) {
entries = [];
pids.set(pkgName, entries);
}
entries.push([ pid, importance ]);
if (entries.length > 1) {
entries.sort((a, b) => a[1] - b[1]);
}
}
}
return new Map(Array.from(pids.entries()).map(([k, v]) => [k, v[0][0]]));
}
function detectLauncherPackageName() {
const intent = Intent.$new(ACTION_MAIN);
intent.addCategory(CATEGORY_HOME);
const launchers = packageManager.queryIntentActivities(intent, 0);
if (launchers.isEmpty())
return null;
const launcher = Java.cast(launchers.get(0), ResolveInfo);
return launcher.activityInfo.value.packageName.value;
}
function checkUidOptionSupported(uid) {
if (uid !== 0 && !multiUserSupported)
throw new Error('The “uid” option is not supported on the current Android OS version');
}
function installLaunchTimeoutRemovalInstrumentation() {
const Handler = Java.use('android.os.Handler');
const OSProcess = Java.use('android.os.Process');
const pendingStartRequests = new Set();
const start = OSProcess.start;
start.implementation = function (processClass, niceName) {
const result = start.apply(this, arguments);
if (tryFinishLaunch(niceName)) {
pendingStartRequests.add(Process.getCurrentThreadId());
}
return result;
};
const sendMessageDelayed = Handler.sendMessageDelayed;
sendMessageDelayed.implementation = function (msg, delayMillis) {
const tid = Process.getCurrentThreadId();
if (pendingStartRequests.has(tid)) {
pendingStartRequests.delete(tid);
msg.recycle();
return true;
}
return sendMessageDelayed.call(this, msg, delayMillis);
};
}
function performLaunchOperation(pkgName, uid, operation) {
const processName = getAppInfo(pkgName, uid).processName.value;
tryFinishLaunch(processName);
const timer = setTimeout(() => {
if (pendingLaunches.get(processName) === timer)
tryFinishLaunch(processName);
}, 10000);
pendingLaunches.set(processName, timer);
try {
return operation();
} catch (e) {
tryFinishLaunch(processName);
throw e;
}
}
function tryFinishLaunch(processName) {
const timer = pendingLaunches.get(processName);
if (timer === undefined)
return false;
pendingLaunches.delete(processName);
clearTimeout(timer);
return true;
}
function performOnJavaVM(task) {
return new Promise((resolve, reject) => {
Java.perform(() => {
try {
const result = task();
resolve(result);
} catch (e) {
reject(e);
}
});
});
}
Java.perform(init);
-19
View File
@@ -1,19 +0,0 @@
system_server_js = custom_target('system-server.js',
input: [
'package.json',
'package-lock.json',
'build.js',
'index.js',
],
output: [
'system-server.js',
],
command: [
python,
compile_js_agent,
'@OUTPUT@',
'@PRIVATE_DIR@',
npm,
'@INPUT@',
],
)
-563
View File
@@ -1,563 +0,0 @@
{
"name": "frida-system-server-agent",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "frida-system-server-agent",
"version": "1.0.0",
"dependencies": {
"@frida/buffer": "^7.0.4",
"frida-java-bridge": "^7.0.8"
},
"devDependencies": {
"esbuild": "^0.25.5",
"esbuild-plugin-alias": "^0.2.1"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@frida/buffer": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@frida/buffer/-/buffer-7.0.4.tgz",
"integrity": "sha512-RxQ1lZRRiCJj7nhcCiD8xeJx0NsLpGGnjqsmTg7jShGmbnVFMN5W7+J+3gqdPSQhc/IxNBIWc6zRXVp4+qnYHg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.5.1",
"ieee754": "^1.2.1"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.5",
"@esbuild/android-arm": "0.25.5",
"@esbuild/android-arm64": "0.25.5",
"@esbuild/android-x64": "0.25.5",
"@esbuild/darwin-arm64": "0.25.5",
"@esbuild/darwin-x64": "0.25.5",
"@esbuild/freebsd-arm64": "0.25.5",
"@esbuild/freebsd-x64": "0.25.5",
"@esbuild/linux-arm": "0.25.5",
"@esbuild/linux-arm64": "0.25.5",
"@esbuild/linux-ia32": "0.25.5",
"@esbuild/linux-loong64": "0.25.5",
"@esbuild/linux-mips64el": "0.25.5",
"@esbuild/linux-ppc64": "0.25.5",
"@esbuild/linux-riscv64": "0.25.5",
"@esbuild/linux-s390x": "0.25.5",
"@esbuild/linux-x64": "0.25.5",
"@esbuild/netbsd-arm64": "0.25.5",
"@esbuild/netbsd-x64": "0.25.5",
"@esbuild/openbsd-arm64": "0.25.5",
"@esbuild/openbsd-x64": "0.25.5",
"@esbuild/sunos-x64": "0.25.5",
"@esbuild/win32-arm64": "0.25.5",
"@esbuild/win32-ia32": "0.25.5",
"@esbuild/win32-x64": "0.25.5"
}
},
"node_modules/esbuild-plugin-alias": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz",
"integrity": "sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==",
"dev": true,
"license": "MIT"
},
"node_modules/frida-java-bridge": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/frida-java-bridge/-/frida-java-bridge-7.0.8.tgz",
"integrity": "sha512-EmqoTGzcDuCd+YFcjBJJPf7iVQCSbbj9fD0/TN8YYYwl5ZJtTqaCLTTsaC07o3bJaDVs/f3ks+Vu1bOw0S1PnQ==",
"license": "LGPL-2.0 WITH WxWindows-exception-3.1"
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
}
}
}
@@ -1,17 +0,0 @@
{
"name": "frida-system-server-agent",
"version": "1.0.0",
"description": "Agent used by Frida's Linux backend on Android",
"type": "module",
"scripts": {
"build": "node build.js"
},
"devDependencies": {
"esbuild": "^0.25.5",
"esbuild-plugin-alias": "^0.2.1"
},
"dependencies": {
"@frida/buffer": "^7.0.4",
"frida-java-bridge": "^7.0.8"
}
}
+228 -202
View File
@@ -36,21 +36,8 @@ namespace Frida {
string[] argv = options.compute_argv (path);
string[] envp = options.compute_envp ();
StdioPipes? pipes = null;
FileDescriptor? stdin_read = null, stdin_write = null;
FileDescriptor? stdout_read = null, stdout_write = null;
FileDescriptor? stderr_read = null, stderr_write = null;
switch (options.stdio) {
case INHERIT:
break;
case PIPE: {
make_pty (out stdin_read, out stdin_write);
make_pty (out stdout_read, out stdout_write);
make_pty (out stderr_read, out stderr_write);
pipes = new StdioPipes (stdin_write, stdout_read, stderr_read);
break;
}
}
FileDescriptor? in_fd, out_fd, err_fd;
StdioPipes? pipes = make_stdio_pipes (options.stdio, true, out in_fd, null, out out_fd, null, out err_fd, null);
string? old_cwd = null;
if (options.cwd.length > 0) {
@@ -68,10 +55,10 @@ namespace Frida {
if (pid == 0) {
Posix.setsid ();
if (options.stdio == PIPE) {
Posix.dup2 (stdin_read.handle, 0);
Posix.dup2 (stdout_write.handle, 1);
Posix.dup2 (stderr_write.handle, 2);
if (pipes != null) {
Posix.dup2 (in_fd.handle, 0);
Posix.dup2 (out_fd.handle, 1);
Posix.dup2 (err_fd.handle, 2);
}
if (_ptrace (TRACEME) == -1) {
@@ -637,9 +624,9 @@ namespace Frida {
monitor ();
if (pipes != null) {
stdin_stream = new UnixOutputStream (pipes.input.handle, false);
process_next_output_from.begin (new UnixInputStream (pipes.output.handle, false), 1);
process_next_output_from.begin (new UnixInputStream (pipes.error.handle, false), 2);
stdin_stream = pipes.input;
process_next_output_from.begin (pipes.output, 1);
process_next_output_from.begin (pipes.error, 2);
}
}
@@ -703,37 +690,6 @@ namespace Frida {
}
}
private sealed class StdioPipes : Object {
public FileDescriptor input {
get;
construct;
}
public FileDescriptor output {
get;
construct;
}
public FileDescriptor error {
get;
construct;
}
public StdioPipes (FileDescriptor input, FileDescriptor output, FileDescriptor error) {
Object (input: input, output: output, error: error);
}
construct {
try {
Unix.set_fd_nonblocking (input.handle, true);
Unix.set_fd_nonblocking (output.handle, true);
Unix.set_fd_nonblocking (error.handle, true);
} catch (GLib.Error e) {
assert_not_reached ();
}
}
}
private sealed class ExecTransitionSession : SeizeSession {
private ExecTransitionSession (uint pid) {
Object (pid: pid, on_init: SeizeSession.InitBehavior.CONTINUE);
@@ -785,25 +741,38 @@ namespace Frida {
public async void wait_for_syscall (LinuxSyscall mask, Cancellable? cancellable) throws Error, IOError {
bool on_syscall_entry = true;
int pending_signal = 0;
do {
while (state != SATISFIED) {
ptrace (SYSCALL, tid, null, (void *) pending_signal);
pending_signal = 0;
Posix.Signal sig = yield wait_for_next_signal (cancellable);
if (sig != (TRAP | 0x80)) {
on_syscall_entry = !on_syscall_entry;
pending_signal = sig;
var wr = yield ChildProcess.wait_for_next_stop (tid, cancellable);
wr.check_stopped ();
if (wr.is_ptrace_event_stop) {
if (wr.ptrace_event == PtraceEvent.EXEC)
on_syscall_entry = true;
continue;
}
if (on_syscall_entry) {
get_regs (&saved_regs);
if (_syscall_satisfies (get_syscall_id (saved_regs), mask))
state = SATISFIED;
if (wr.is_syscall_stop) {
if (on_syscall_entry) {
get_regs (&saved_regs);
var id = get_syscall_id (saved_regs);
if (_syscall_satisfies (id, mask))
state = SATISFIED;
}
on_syscall_entry = !on_syscall_entry;
continue;
}
on_syscall_entry = !on_syscall_entry;
} while (state != SATISFIED);
if (wr.should_deliver_to_tracee)
pending_signal = wr.stop_signal;
else
pending_signal = 0;
}
}
public async void interrupt (Cancellable? cancellable) throws Error, IOError {
@@ -2061,6 +2030,10 @@ namespace Frida {
return yield ChildProcess.wait_for_next_signal (tid, cancellable);
}
public async WaitResult wait_for_next_stop (Cancellable? cancellable) throws Error, IOError {
return yield ChildProcess.wait_for_next_stop (tid, cancellable);
}
public void get_regs (GPRegs * regs) throws Error {
#if !MIPS
if (regset_supported) {
@@ -3062,30 +3035,53 @@ namespace Frida {
namespace ChildProcess {
private async void wait_for_early_signal (uint pid, Posix.Signal sig, Cancellable? cancellable) throws Error, IOError {
while (true) {
Posix.Signal next_signal = yield wait_for_next_signal (pid, cancellable);
if (next_signal == sig)
return;
ptrace (CONT, pid);
}
yield wait_for_signals_common (pid, { sig }, suppress_unmatched, cancellable);
}
private async void wait_for_signal (uint pid, Posix.Signal sig, Cancellable? cancellable) throws Error, IOError {
yield wait_for_signals (pid, { sig }, cancellable);
}
private async Posix.Signal wait_for_signals (uint pid, Posix.Signal[] sigs, Cancellable? cancellable) throws Error, IOError {
while (true) {
Posix.Signal next_signal = yield wait_for_next_signal (pid, cancellable);
if (next_signal in sigs)
return next_signal;
private async Posix.Signal wait_for_signals (uint pid, Posix.Signal[] sigs, Cancellable? cancellable)
throws Error, IOError {
return yield wait_for_signals_common (pid, sigs, pass_through_unmatched, cancellable);
}
ptrace (CONT, pid, null, (void *) next_signal);
private async Posix.Signal wait_for_signals_common (uint pid, Posix.Signal[] sigs, UnmatchedStopHandler on_unmatched,
Cancellable? cancellable) throws Error, IOError {
while (true) {
var wr = yield wait_for_next_stop (pid, cancellable);
wr.check_stopped ();
if (wr.stop_signal in sigs)
return wr.stop_signal;
if (!wr.should_deliver_to_tracee) {
ptrace (CONT, pid);
continue;
}
on_unmatched (pid, wr);
}
}
private delegate void UnmatchedStopHandler (uint pid, WaitResult wr) throws Error;
private static void pass_through_unmatched (uint pid, WaitResult wr) throws Error {
ptrace (CONT, pid, null, (void *) wr.stop_signal);
}
private static void suppress_unmatched (uint pid, WaitResult wr) throws Error {
ptrace (CONT, pid);
}
private async Posix.Signal wait_for_next_signal (uint pid, Cancellable? cancellable) throws Error, IOError {
var wr = yield wait_for_next_stop (pid, cancellable);
wr.check_stopped ();
return wr.stop_signal;
}
private async WaitResult wait_for_next_stop (uint pid, Cancellable? cancellable) throws Error, IOError {
var main_context = MainContext.get_thread_default ();
bool timed_out = false;
@@ -3098,21 +3094,23 @@ namespace Frida {
int status = 0;
uint[] delays = { 0, 1, 2, 5, 10, 20, 50, 250 };
try {
for (uint i = 0; !timed_out && !cancellable.set_error_if_cancelled (); i++) {
int res = Posix.waitpid ((Posix.pid_t) pid, out status, Posix.WNOHANG);
if (res == -1)
throw new Error.NOT_SUPPORTED ("Unable to wait for next signal: %s", strerror (errno));
throw new Error.NOT_SUPPORTED ("Unable to wait for next stop: %s", strerror (errno));
if (res != 0)
break;
uint delay_ms = (i < delays.length) ? delays[i] : delays[delays.length - 1];
var delay_source = new TimeoutSource (delay_ms);
delay_source.set_callback (wait_for_next_signal.callback);
delay_source.set_callback (wait_for_next_stop.callback);
delay_source.attach (main_context);
var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (wait_for_next_signal.callback);
cancel_source.set_callback (wait_for_next_stop.callback);
cancel_source.attach (main_context);
yield;
@@ -3125,21 +3123,79 @@ namespace Frida {
}
if (timed_out)
throw new Error.TIMED_OUT ("Unexpectedly timed out while waiting for signal from process with PID %u", pid);
throw new Error.TIMED_OUT ("Unexpectedly timed out while waiting for stop from process with PID %u", pid);
return WaitResult (pid, status);
}
}
public enum WaitKind {
EXITED,
SIGNALED,
STOPPED,
OTHER
}
public struct WaitResult {
public uint pid;
public int status;
public WaitKind kind;
public uint exit_status;
public Posix.Signal term_signal;
public Posix.Signal stop_signal;
public PtraceEvent ptrace_event;
public bool is_ptrace_event_stop;
public bool is_syscall_stop;
public bool should_deliver_to_tracee;
public WaitResult (uint pid, int status) {
this.pid = pid;
this.status = status;
kind = WaitKind.OTHER;
exit_status = 0;
term_signal = 0;
stop_signal = 0;
ptrace_event = PtraceEvent.NONE;
is_ptrace_event_stop = false;
is_syscall_stop = false;
should_deliver_to_tracee = false;
if (PosixStatus.is_exit (status)) {
throw new Error.NOT_SUPPORTED ("Target exited with status %u",
PosixStatus.parse_exit_status (status));
}
kind = WaitKind.EXITED;
exit_status = PosixStatus.parse_exit_status (status);
} else if (PosixStatus.is_signaled (status)) {
kind = WaitKind.SIGNALED;
term_signal = PosixStatus.parse_termination_signal (status);
} else if (PosixStatus.is_stopped (status)) {
kind = WaitKind.STOPPED;
if (PosixStatus.is_signaled (status)) {
throw new Error.NOT_SUPPORTED ("Target terminated with signal %u",
PosixStatus.parse_termination_signal (status));
}
stop_signal = PosixStatus.parse_stop_signal (status);
ptrace_event = PosixStatus.ptrace_event (status);
if (!PosixStatus.is_stopped (status))
throw new Error.NOT_SUPPORTED ("Unexpected status: 0x%08x", status);
return PosixStatus.parse_stop_signal (status);
is_ptrace_event_stop = (stop_signal == Posix.Signal.TRAP) && (ptrace_event != NONE);
is_syscall_stop = (stop_signal == (Posix.Signal.TRAP | 0x80)) && (ptrace_event == NONE);
should_deliver_to_tracee = !is_syscall_stop && !is_ptrace_event_stop && stop_signal != Posix.Signal.TRAP;
}
}
public void check_stopped () throws Error {
switch (kind) {
case WaitKind.EXITED:
throw new Error.NOT_SUPPORTED ("Target exited with status %u", exit_status);
case WaitKind.SIGNALED:
throw new Error.NOT_SUPPORTED ("Target terminated with signal %u", term_signal);
case WaitKind.STOPPED:
return;
default:
throw new Error.NOT_SUPPORTED ("Unexpected status: 0x%08x", status);
}
}
}
@@ -3161,60 +3217,30 @@ namespace Frida {
[CCode (cname = "WSTOPSIG", cheader_filename = "sys/wait.h")]
private extern Posix.Signal parse_stop_signal (int status);
private PtraceEvent ptrace_event (int status) {
return ((uint) status) >> 16;
}
}
public enum PtraceEvent {
NONE,
FORK,
VFORK,
CLONE,
EXEC,
VFORK_DONE,
EXIT,
SECCOMP,
}
private int tgkill (uint tgid, uint tid, Posix.Signal sig) {
return Linux.syscall (SysCall.tgkill, tgid, tid, sig);
}
public extern bool _syscall_satisfies (int syscall_id, LinuxSyscall mask);
private void make_pty (out FileDescriptor read, out FileDescriptor write) throws Error {
#if HAVE_OPENPTY
int rfd = -1, wfd = -1;
char name[Posix.Limits.PATH_MAX];
if (Linux.openpty (out rfd, out wfd, name, null, null) == -1)
throw new Error.NOT_SUPPORTED ("Unable to open PTY: %s", strerror (errno));
enable_close_on_exec (rfd);
enable_close_on_exec (wfd);
configure_terminal_attributes (rfd);
read = new FileDescriptor (rfd);
write = new FileDescriptor (wfd);
#else
try {
int fds[2];
Unix.open_pipe (fds, Posix.FD_CLOEXEC);
read = new FileDescriptor (fds[0]);
write = new FileDescriptor (fds[1]);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to open pipe: %s", e.message);
}
#endif
}
#if HAVE_OPENPTY
private void enable_close_on_exec (int fd) {
Posix.fcntl (fd, Posix.F_SETFD, Posix.fcntl (fd, Posix.F_GETFD) | Posix.FD_CLOEXEC);
}
private void configure_terminal_attributes (int fd) {
var tios = Posix.termios ();
Posix.tcgetattr (fd, out tios);
tios.c_oflag &= ~Posix.ONLCR;
tios.c_cflag = (tios.c_cflag & Posix.CLOCAL) | Posix.CS8 | Posix.CREAD | Posix.HUPCL;
tios.c_lflag &= ~Posix.ECHO;
Posix.tcsetattr (fd, 0, tios);
}
#endif
private sealed class ProcMapsSoEntry {
public class ProcMapsSoEntry {
public uint64 base_address;
public string path;
public string identity;
@@ -3226,7 +3252,7 @@ namespace Frida {
}
public static ProcMapsSoEntry? find_by_address (uint pid, uint64 address) {
var iter = MapsIter.for_pid (pid);
var iter = ProcMapsIter.for_pid (pid);
while (iter.next ()) {
uint64 start = iter.start_address;
uint64 end = iter.end_address;
@@ -3240,7 +3266,7 @@ namespace Frida {
public static ProcMapsSoEntry? find_by_path (uint pid, string path) {
var candidates = new Gee.ArrayList<Candidate> ();
Candidate? latest_candidate = null;
var iter = MapsIter.for_pid (pid);
var iter = ProcMapsIter.for_pid (pid);
#if ANDROID
unowned string libc_path = Gum.Process.get_libc_module ().path;
#endif
@@ -3296,80 +3322,80 @@ namespace Frida {
return result;
}
}
}
private class MapsIter {
private string? contents;
private MatchInfo? info;
private uint offset = 0;
public sealed class ProcMapsIter {
private string? contents;
private MatchInfo? info;
private uint offset = 0;
public uint64 start_address {
get {
return uint64.parse (info.fetch (1), 16);
}
public uint64 start_address {
get {
return uint64.parse (info.fetch (1), 16);
}
}
public uint64 end_address {
get {
return uint64.parse (info.fetch (2), 16);
}
}
public string flags {
owned get {
return info.fetch (3);
}
}
public uint64 file_offset {
get {
return uint64.parse (info.fetch (4), 16);
}
}
public string identity {
owned get {
return info.fetch (5);
}
}
public string path {
owned get {
return info.fetch (6);
}
}
public static ProcMapsIter for_pid (uint pid) {
return new ProcMapsIter (pid);
}
private ProcMapsIter (uint pid) {
try {
FileUtils.get_contents ("/proc/%u/maps".printf (pid), out contents);
} catch (FileError e) {
return;
}
public uint64 end_address {
get {
return uint64.parse (info.fetch (2), 16);
}
if (!/^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,} \d+) +([^\n]+)$/m.match (
contents, 0, out info)) {
assert_not_reached ();
}
}
public string flags {
owned get {
return info.fetch (3);
}
}
public bool next () {
if (info == null)
return false;
public uint64 file_offset {
get {
return uint64.parse (info.fetch (4), 16);
}
}
public string identity {
owned get {
return info.fetch (5);
}
}
public string path {
owned get {
return info.fetch (6);
}
}
public static MapsIter for_pid (uint pid) {
return new MapsIter (pid);
}
private MapsIter (uint pid) {
if (offset > 0) {
try {
FileUtils.get_contents ("/proc/%u/maps".printf (pid), out contents);
} catch (FileError e) {
return;
}
if (!/^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,} \d+) +([^\n]+)$/m.match (
contents, 0, out info)) {
assert_not_reached ();
}
}
public bool next () {
if (info == null)
info.next ();
} catch (RegexError e) {
return false;
if (offset > 0) {
try {
info.next ();
} catch (RegexError e) {
return false;
}
}
offset++;
return info.matches ();
}
offset++;
return info.matches ();
}
}
+3 -2
View File
@@ -17,8 +17,9 @@ build: ext/linux/tools/include/nolibc/nolibc.h
rm -rf build
$(MESON) setup --cross-file $(crossfile) -Db_lto=true build
$(MESON) compile -C build
cp build/bootstrapper.bin bootstrapper-$(host_arch).bin
cp build/loader.bin loader-$(host_arch).bin
install -m644 build/bootstrapper.bin bootstrapper-$(host_arch).bin
install -m644 build/loader.bin loader-$(host_arch).bin
[ -f build/zymbiote.bin ] && install -m644 build/zymbiote.bin zymbiote-$(host_arch).bin || true
ext/linux/tools/include/nolibc/nolibc.h: nolibc-tweaks.patch
if [ -d ext/linux ]; then \
+9
View File
@@ -865,6 +865,15 @@ frida_try_load_libc_and_raise (FridaBootstrapContext * ctx)
"r" (entrypoint)
: "memory"
);
#elif defined (__riscv)
asm volatile (
"mv sp, %0\n\t"
"jr %1\n\t"
:
: "r" (&params),
"r" (entrypoint)
: "memory"
);
#endif
}
}
+1 -1
View File
@@ -9,7 +9,7 @@
#endif
#ifndef __ELF_NATIVE_CLASS
# if defined (__x86_64__) || (defined (__aarch64__) && !defined (__ILP32__)) || defined (__mips64)
# if defined (__x86_64__) || (defined (__aarch64__) && !defined (__ILP32__)) || defined (__mips64) || (defined (__riscv) && __riscv_xlen == 64)
# define __ELF_NATIVE_CLASS 64
# else
# define __ELF_NATIVE_CLASS 32
+13 -24
View File
@@ -1,29 +1,18 @@
SECTIONS {
.text : {
*(.text.entrypoint)
. = 0;
.payload : ALIGN(4096)
{
KEEP(*(.text.entrypoint))
*(.text*)
*(.rodata*)
}
.dynstr : {
*(.dynstr)
}
.dynsym : {
*(.dynsym)
}
.gnu.hash : {
*(.gnu.hash)
}
.hash : {
*(.hash)
}
.note.gnu.build-id : {
*(.note.gnu.build-id)
}
.note.android.ident : {
*(.note.android.ident)
}
.got : ALIGN(64) {
*(.got*)
. = ALIGN(64);
}
.dynstr : { *(.dynstr) }
.dynsym : { *(.dynsym) }
.gnu.hash : { *(.gnu.hash) }
.hash : { *(.hash) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.note.android.ident : { *(.note.android.ident) }
.got : ALIGN(64) { *(.got*) . = ALIGN(64); }
}
+24 -21
View File
@@ -28,27 +28,10 @@ if host_machine.cpu_family() == 'x86'
extra_flags += '-fno-stack-protector'
endif
common_objcopy_flags = [
objcopy_flags = [
'-O', 'binary',
'-S',
'-R', '.plt',
'-R', '.rel.dyn',
'-R', '.rela.dyn',
'-R', '.dynamic',
'-R', '.dynstr',
'-R', '.dynsym',
'-R', '.gnu.version',
'-R', '.gnu.version_r',
'-R', '.gnu.hash',
'-R', '.hash',
'-R', '.note.*',
'-j', '.payload',
]
if not host_machine.cpu_family().startswith('mips')
common_objcopy_flags += [
'-R', '.got',
'-R', '.got.plt',
]
endif
helper_lds = meson.current_source_dir() / 'helper.lds'
@@ -80,7 +63,7 @@ bootstrapper = shared_module('bootstrapper', bootstrapper_sources,
custom_target('bootstrapper-bin',
input: [bootstrapper],
output: ['bootstrapper.bin'],
command: [objcopy, common_objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
command: [objcopy, objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
install_dir: 'share' / 'frida-helpers',
install: true,
)
@@ -99,7 +82,27 @@ loader = shared_module('loader', ['loader.c', 'syscall.c'],
custom_target('loader-bin',
input: [loader],
output: ['loader.bin'],
command: [objcopy, common_objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
command: [objcopy, objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
install_dir: 'share' / 'frida-helpers',
install: true,
)
if host_machine.system() == 'android'
zymbiote_flags = []
if host_machine.cpu_family() == 'aarch64'
zymbiote_flags += '-mbranch-protection=bti'
endif
zymbiote = shared_module('zymbiote', ['zymbiote.c'],
name_prefix: '',
c_args: zymbiote_flags + extra_flags,
link_args: extra_link_args,
link_depends: [helper_lds],
)
custom_target('zymbiote-bin',
input: [zymbiote],
output: ['zymbiote.bin'],
command: [objcopy, objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
install_dir: 'share' / 'frida-helpers',
install: true,
)
endif
+20
View File
@@ -138,6 +138,26 @@ frida_syscall_4 (size_t n, size_t a, size_t b, size_t c, size_t d)
result = (status == 0) ? retval : -retval;
}
#elif defined (__riscv)
{
register ssize_t a7 asm ("a7") = n;
register size_t a0 asm ("a0") = a;
register size_t a1 asm ("a1") = b;
register size_t a2 asm ("a2") = c;
register size_t a3 asm ("a3") = d;
asm volatile (
"ecall\n\t"
: "+r" (a0)
: "r" (a1),
"r" (a2),
"r" (a3),
"r" (a7)
: "memory"
);
result = a0;
}
#endif
return result;
+3 -1
View File
@@ -1,7 +1,9 @@
#ifndef __FRIDA_SYSCALL_H__
#define __FRIDA_SYSCALL_H__
#include <unistd.h>
#ifndef NOLIBC
# include <unistd.h>
#endif
#include <sys/syscall.h>
#define frida_syscall_0(n) frida_syscall_4 (n, 0, 0, 0, 0)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+295
View File
@@ -0,0 +1,295 @@
#include <errno.h>
#include <jni.h>
#include <signal.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/un.h>
typedef struct _FridaApi FridaApi;
struct _FridaApi
{
char name[64];
void ** art_method_slot;
void (* original_set_argv0) (JNIEnv * env, jobject clazz, jstring name);
int (* socket) (int domain, int type, int protocol);
int (* connect) (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
int * (* __errno) (void);
pid_t (* getpid) (void);
pid_t (* getppid) (void);
ssize_t (* sendmsg) (int sockfd, const struct msghdr * msg, int flags);
ssize_t (* recv) (int sockfd, void * buf, size_t len, int flags);
int (* close) (int fd);
int (* raise) (int sig);
};
static volatile const FridaApi frida =
{
.name = "/frida-zymbiote-00000000000000000000000000000000",
};
static int frida_stop_and_return (JNIEnv * env, jobject clazz, jstring name);
static int frida_get_errno (void);
static int frida_connect (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
static ssize_t frida_sendmsg (int sockfd, const struct msghdr * msg, int flags);
static bool frida_sendmsg_all (int sockfd, struct iovec * iov, size_t iovlen, int flags);
static ssize_t frida_recv (int sockfd, void * buf, size_t len, int flags);
__attribute__ ((section (".text.entrypoint")))
__attribute__ ((visibility ("default")))
int
frida_zymbiote_replacement_set_argv0 (JNIEnv * env, jobject clazz, jstring name)
{
bool success = false;
int fd;
struct sockaddr_un addr;
socklen_t addrlen;
unsigned int name_len;
*frida.art_method_slot = frida.original_set_argv0;
frida.original_set_argv0 (env, clazz, name);
fd = frida.socket (AF_UNIX, SOCK_STREAM, 0);
if (fd == -1)
goto beach;
addr.sun_family = AF_UNIX;
addr.sun_path[0] = '\0';
name_len = 0;
for (unsigned int i = 0; i != sizeof (frida.name); i++)
{
if (frida.name[i] == '\0')
break;
if (1u + i >= sizeof (addr.sun_path))
break;
addr.sun_path[1u + i] = frida.name[i];
name_len++;
}
addrlen = (socklen_t) (offsetof (struct sockaddr_un, sun_path) + 1u + name_len);
if (frida_connect (fd, (const struct sockaddr *) &addr, addrlen) == -1)
goto beach;
{
const char * name_utf8;
struct
{
uint32_t pid;
uint32_t ppid;
uint32_t package_name_len;
} header;
struct iovec iov[2];
name_utf8 = (*env)->GetStringUTFChars (env, name, NULL);
header.pid = frida.getpid ();
header.ppid = frida.getppid ();
header.package_name_len = 0;
while (name_utf8[header.package_name_len] != '\0')
header.package_name_len++;
iov[0].iov_base = &header;
iov[0].iov_len = sizeof (header);
iov[1].iov_base = (void *) name_utf8;
iov[1].iov_len = header.package_name_len;
if (!frida_sendmsg_all (fd, iov, 2, MSG_NOSIGNAL))
goto beach;
(*env)->ReleaseStringUTFChars (env, name, name_utf8);
}
{
uint8_t rx;
if (frida_recv (fd, &rx, 1, 0) != 1)
goto beach;
}
success = true;
beach:
if (fd != -1)
frida.close (fd);
if (success)
{
__attribute__ ((musttail))
return frida_stop_and_return (env, clazz, name);
}
return 0;
}
__attribute__ ((naked, noinline))
static int
frida_stop_and_return (JNIEnv * env, jobject clazz, jstring name)
{
#if defined (__i386__)
__asm__ __volatile__ (
"movl $%c[sig], 4(%%esp)\n"
"call 1f\n"
"1: pop %%eax\n"
"addl $(frida-1b), %%eax\n"
"movl %c[raise_off](%%eax), %%eax\n"
"jmp *%%eax\n"
:
: [sig] "i" (SIGSTOP),
[raise_off] "i" (offsetof (FridaApi, raise))
: "eax", "memory");
#elif defined (__x86_64__)
__asm__ __volatile__ (
"mov $%c[sig], %%edi\n"
"leaq frida(%%rip), %%r11\n"
"movq %c[raise_off](%%r11), %%r11\n"
"jmp *%%r11\n"
:
: [sig] "i" (SIGSTOP),
[raise_off] "i" (offsetof (FridaApi, raise))
: "r11", "rdi", "memory");
#elif defined (__arm__)
__asm__ __volatile__ (
"mov r0, %[sig]\n"
"adr r12, frida\n"
"ldr r12, [r12, %[raise_off]]\n"
"bx r12\n"
:
: [sig] "i" (SIGSTOP),
[raise_off] "i" (offsetof (FridaApi, raise))
: "r12", "memory");
#elif defined (__aarch64__)
__asm__ __volatile__ (
"mov w0, #%[sig]\n"
"adrp x16, frida\n"
"add x16, x16, :lo12:frida\n"
"ldr x16, [x16, %[raise_off]]\n"
"br x16\n"
:
: [sig] "i" (SIGSTOP),
[raise_off] "i" (offsetof (FridaApi, raise))
: "x16", "memory");
#else
# error Unsupported architecture
#endif
}
static int
frida_get_errno (void)
{
return *frida.__errno ();
}
static int
frida_connect (int sockfd, const struct sockaddr * addr, socklen_t addrlen)
{
for (;;)
{
if (frida.connect (sockfd, addr, addrlen) == 0)
return 0;
if (frida_get_errno () == EINTR)
continue;
return -1;
}
}
static ssize_t
frida_sendmsg (int sockfd, const struct msghdr * msg, int flags)
{
for (;;)
{
ssize_t n = frida.sendmsg (sockfd, msg, flags);
if (n != -1)
return n;
if (frida_get_errno () == EINTR)
continue;
return -1;
}
}
static bool
frida_sendmsg_all (int sockfd, struct iovec * iov, size_t iovlen, int flags)
{
size_t idx = 0;
size_t off = 0;
while (idx != iovlen)
{
struct msghdr m;
m.msg_name = NULL;
m.msg_namelen = 0;
m.msg_iov = &iov[idx];
m.msg_iovlen = iovlen - idx;
m.msg_control = NULL;
m.msg_controllen = 0;
m.msg_flags = 0;
ssize_t n = frida_sendmsg (sockfd, &m, flags);
if (n == -1)
return false;
size_t remaining = n;
while (remaining != 0)
{
size_t avail = iov[idx].iov_len - off;
if (remaining < avail)
{
iov[idx].iov_base = iov[idx].iov_base + remaining;
iov[idx].iov_len -= remaining;
remaining = 0;
}
else
{
remaining -= avail;
idx++;
off = 0;
if (idx == iovlen)
break;
}
}
}
return true;
}
static ssize_t
frida_recv (int sockfd, void * buf, size_t len, int flags)
{
for (;;)
{
ssize_t n = frida.recv (sockfd, buf, len, flags);
if (n != -1)
return n;
if (frida_get_errno () == EINTR)
continue;
return -1;
}
}
File diff suppressed because it is too large. Load diff
+1
View File
@@ -4,6 +4,7 @@
#include <string.h>
#include <unistd.h>
#include <gio/gunixmounts.h>
#include <gum/gum.h>
typedef struct _FridaEnumerateProcessesOperation FridaEnumerateProcessesOperation;
+60 -22
View File
@@ -29,6 +29,38 @@ backend_vala_args_private = []
backend_libs = []
backend_deps = []
if host_os == 'android' or have_droidy_backend
subdir('android-helper')
android_data = custom_target('frida-data-android',
input: [
'android.resources',
android_helper_dex,
'linux' / 'helpers' / f'zymbiote-x86.bin',
'linux' / 'helpers' / f'zymbiote-x86_64.bin',
'linux' / 'helpers' / f'zymbiote-arm.bin',
'linux' / 'helpers' / f'zymbiote-arm64.bin',
],
output: [
'frida-data-android.vapi',
'frida-data-android.h',
'frida-data-android.c',
'frida-data-android-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-android',
'@INPUT1@',
'@INPUT2@',
'@INPUT3@',
'@INPUT4@',
'@INPUT5@',
],
)
backend_sources += [android_data]
endif
if have_local_backend
post_process_helper = post_process + ['executable', 're.frida.Helper']
@@ -287,31 +319,9 @@ if have_local_backend
]
if host_os == 'android'
subdir('linux' / 'agent' / 'system-server')
backend_vala_args_private += frida_selinux_vala_args
backend_libs += frida_selinux
backend_deps += frida_selinux_dep
android_data = custom_target('frida-data-android',
input: [
'linux' / 'frida-android.resources',
system_server_js,
],
output: [
'frida-data-android.vapi',
'frida-data-android.h',
'frida-data-android.c',
'frida-data-android-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-android',
'@INPUT1@',
],
)
backend_sources += [android_data]
endif
helper_backend_sources = [
@@ -500,6 +510,34 @@ if have_local_backend
endif
endif
if have_simmy_backend
backend_sources += [
'simmy' / 'simmy-host-session.vala',
'simmy' / 'simmy-host-session-glue.m',
'simmy' / 'text-plist.vala',
]
simmy_data = custom_target('frida-data-simmy',
input: [
'simmy' / 'frida-simmy.resources',
'simmy' / 'agent' / 'springboard.js',
],
output: [
'frida-data-simmy.vapi',
'frida-data-simmy.h',
'frida-data-simmy.c',
'frida-data-simmy-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-simmy',
'@INPUT1@',
],
)
backend_sources += [simmy_data]
endif
if have_fruity_backend
backend_sources += [
'fruity' / 'fruity-host-session.vala',
+2 -1
View File
@@ -957,7 +957,8 @@ namespace Frida {
_icon = builder.end ();
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (channel != null)
throw new Error.INVALID_OPERATION ("Already created");
+131
View File
@@ -0,0 +1,131 @@
const SBS = bind({
module: '/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices',
cprefix: 'SBS',
functions: {
copyFrontmostApplicationDisplayIdentifier: ['pointer', [ ]],
copyLocalizedApplicationNameForDisplayIdentifier: ['pointer', ['pointer' ]],
copyIconImagePNGDataForDisplayIdentifier: ['pointer', ['pointer' ]],
processIDForDisplayIdentifier: ['bool', ['pointer', 'pointer']],
}
});
const CF = bind({
module: '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation',
cprefix: 'CF',
functions: {
retain: ['pointer', ['pointer' ]],
release: ['void', ['pointer' ]],
stringCreateWithCString: ['pointer', ['pointer', 'pointer', 'uint' ]],
stringGetCStringPtr: ['pointer', ['pointer', 'uint' ]],
stringGetLength: ['long', ['pointer' ]],
stringGetMaximumSizeForEncoding: ['long', ['long', 'uint' ]],
stringGetCString: ['bool', ['pointer', 'pointer', 'long', 'uint']],
dataGetLength: ['long', ['pointer' ]],
dataGetBytePtr: ['pointer', ['pointer' ]],
}
});
const kCFBooleanTrue = Module.getGlobalExportByName('kCFBooleanTrue').readPointer();
const kCFStringEncodingUTF8 = 0x08000100;
rpc.exports = {
getFrontmostApplication(scope) {
const idObj = SBS.copyFrontmostApplicationDisplayIdentifier();
if (idObj.isNull())
return null;
const id = cfStringToUtf8(idObj);
const nameObj = SBS.copyLocalizedApplicationNameForDisplayIdentifier(idObj);
const name = cfStringToUtf8(nameObj);
const pidBuf = Memory.alloc(4);
SBS.processIDForDisplayIdentifier(idObj, pidBuf);
const pid = pidBuf.readU32();
CF.release(nameObj);
CF.release(idObj);
if (pid === 0)
return null;
return [
id,
name,
pid,
(scope === 'full') ? fetchApplicationIcon(id) : null
];
},
fetchApplicationIcons(appIds) {
return appIds.map(fetchApplicationIcon);
},
};
function fetchApplicationIcon(appId) {
const idObj = CF.stringCreateWithCString(NULL, Memory.allocUtf8String(appId), kCFStringEncodingUTF8);
const pngObj = SBS.copyIconImagePNGDataForDisplayIdentifier(idObj);
CF.release(idObj);
const png = CF.dataGetBytePtr(pngObj).readByteArray(CF.dataGetLength(pngObj));
CF.release(pngObj);
return base64FromBytes(png);
}
Interceptor.attach(Module.getGlobalExportByName('SecTaskCopyValueForEntitlement'), {
onEnter(args) {
this.entitlement = cfStringToUtf8(args[1]);
},
onLeave(retval) {
if (this.entitlement === 'com.apple.springboard.iconState' && retval.isNull())
retval.replace(CF.retain(kCFBooleanTrue));
}
});
function bind({ module, cprefix, functions }) {
const mod = Process.getModuleByName(module);
const nfOpts = { exceptions: 'propagate' };
return Object.fromEntries(Object.entries(functions).map(([name, [retType, argTypes]]) => [
name,
new NativeFunction(mod.getExportByName(cprefix + name.charAt(0).toUpperCase() + name.slice(1)), retType, argTypes, nfOpts)
]));
}
function cfStringToUtf8(cfstr) {
const direct = CF.stringGetCStringPtr(cfstr, kCFStringEncodingUTF8);
if (!direct.isNull())
return direct.readUtf8String();
const len16 = CF.stringGetLength(cfstr);
const maxBytes = CF.stringGetMaximumSizeForEncoding(len16, kCFStringEncodingUTF8).valueOf() + 1;
const buf = Memory.alloc(maxBytes);
CF.stringGetCString(cfstr, buf, maxBytes, kCFStringEncodingUTF8);
return buf.readUtf8String();
}
function base64FromBytes(buf) {
const bytes = new Uint8Array(buf);
const enc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let i = 0;
const len = bytes.length;
let out = '';
while (i < len) {
const c1 = bytes[i++];
const c2 = (i !== len) ? bytes[i++] : 0;
const c3 = (i !== len) ? bytes[i++] : 0;
const o1 = c1 >> 2;
const o2 = ((c1 & 0x03) << 4) | (c2 >> 4);
const o3 = ((c2 & 0x0f) << 2) | (c3 >> 6);
const o4 = c3 & 0x3f;
if (i - 1 > len)
out += enc[o1] + enc[o2] + '==';
else if (i > len)
out += enc[o1] + enc[o2] + enc[o3] + '=';
else
out += enc[o1] + enc[o2] + enc[o3] + enc[o4];
}
return out;
}
+2
View File
@@ -0,0 +1,2 @@
[resource-compiler]
namespace = Frida.Data.Simmy
+528
View File
@@ -0,0 +1,528 @@
#include "frida-core.h"
#include <dlfcn.h>
#import <Foundation/Foundation.h>
typedef struct _FridaSimmyContext FridaSimmyContext;
struct _FridaSimmyContext
{
dispatch_queue_t dispatch_queue;
void * core_simulator;
NSString * SimDeviceLaunchApplicationKeyArguments;
NSString * SimDeviceLaunchApplicationKeyEnvironment;
NSString * SimDeviceLaunchApplicationKeyStandardErrPath;
NSString * SimDeviceLaunchApplicationKeyStandardOutPath;
NSString * SimDeviceLaunchApplicationKeyTerminateRunningProcess;
NSString * SimDeviceLaunchApplicationKeyWaitForDebugger;
NSString * SimDeviceSpawnKeyArguments;
NSString * SimDeviceSpawnKeyEnvironment;
NSString * SimDeviceSpawnKeyStderr;
NSString * SimDeviceSpawnKeyStdin;
NSString * SimDeviceSpawnKeyStdout;
NSString * SimDeviceSpawnKeyWaitForDebugger;
FridaSimmyHostSessionBackendDeviceAddedFunc on_device_added;
gpointer on_device_added_target;
};
static void frida_simmy_context_destroy (FridaSimmyContext * self);
static void frida_simmy_context_emit_devices (FridaSimmyContext * self);
static NSArray * frida_argv_to_arguments_array (gchar * const * argv, gint argv_length, gint start_index);
static NSDictionary * frida_envp_to_environment_dictionary (gchar * const * envp, gint envp_length);
@interface SimRuntime : NSObject
@property (nonatomic, readonly, strong) NSString * identifier;
@property (nonatomic, readonly, strong) NSString * shortName;
@property (nonatomic, readonly, strong) NSString * versionString;
@property (nonatomic, readonly, strong) NSString * root;
@end
@interface SimDevice : NSObject
@property (nonatomic, readonly, strong) NSUUID * UDID;
@property (nonatomic, readonly, strong) NSString * name;
@property (nonatomic, readonly, strong) SimRuntime * runtime;
@property (nonatomic, readonly, strong) NSString * stateString;
- (NSString *)getenv:(NSString *)name
error:(NSError * _Nullable * _Nullable)error;
- (NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)installedAppsWithError:(NSError * _Nullable * _Nullable)error;
- (void)launchApplicationAsyncWithID:(NSString *)identifier
options:(NSDictionary<NSString *, id> * _Nullable)options
completionQueue:(dispatch_queue_t)queue
completionHandler:(void (^) (NSError * error, int pid))handler;
- spawnAsyncWithPath:(NSString *)path
options:(NSDictionary<NSString *, id> * _Nullable)options
terminationQueue:(dispatch_queue_t)tq
terminationHandler:(void (^) (int status))th
completionQueue:(dispatch_queue_t)cq
completionHandler:(void (^) (NSError * error, int pid))ch;
@end
@interface SimDeviceSet : NSObject
@property (nonatomic, readonly, strong) NSArray<SimDevice *> *devices;
@end
@protocol SimServiceContextClass
+ (instancetype)serviceContextForDeveloperDir:(NSString *)dir
error:(NSError * _Nullable * _Nullable)error;
@end
@interface SimServiceContext : NSObject
- (SimDeviceSet *)defaultDeviceSetWithError:(NSError * _Nullable * _Nullable)error;
@end
void *
_frida_simmy_host_session_backend_start (FridaSimmyHostSessionBackendDeviceAddedFunc on_device_added, gpointer on_device_added_target,
FridaSimmyCompleteFunc on_complete, gpointer on_complete_target, GDestroyNotify on_complete_target_destroy_notify)
{
FridaSimmyContext * ctx;
ctx = g_slice_new0 (FridaSimmyContext);
ctx->dispatch_queue = dispatch_queue_create ("re.frida.simmy.queue", DISPATCH_QUEUE_SERIAL);
ctx->on_device_added = on_device_added;
ctx->on_device_added_target = on_device_added_target;
dispatch_async (ctx->dispatch_queue, ^
{
frida_simmy_context_emit_devices (ctx);
on_complete (on_complete_target);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
});
return ctx;
}
void
_frida_simmy_host_session_backend_stop (void * simmy_context, FridaSimmyCompleteFunc on_complete,
gpointer on_complete_target, GDestroyNotify on_complete_target_destroy_notify)
{
FridaSimmyContext * ctx = simmy_context;
dispatch_async (ctx->dispatch_queue, ^
{
frida_simmy_context_destroy (ctx);
g_slice_free (FridaSimmyContext, ctx);
on_complete (on_complete_target);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
});
}
static void
frida_simmy_context_destroy (FridaSimmyContext * self)
{
g_clear_pointer (&self->core_simulator, dlclose);
dispatch_release (self->dispatch_queue);
}
static void
frida_simmy_context_emit_devices (FridaSimmyContext * self)
{
void * xcselect_module;
bool (* xcselect_get_developer_dir_path) (char *, size_t, bool *, bool *, bool *);
char developer_dir[1024] = { 0, };
bool from_override, is_command_line_tools, from_fallback;
void * cs;
NSString ** str;
Class<SimServiceContextClass> SimServiceContextClass;
SimServiceContext * ctx;
SimDeviceSet * set;
xcselect_module = dlopen ("/usr/lib/libxcselect.dylib", RTLD_GLOBAL | RTLD_LAZY);
if (xcselect_module == NULL)
goto beach;
xcselect_get_developer_dir_path = dlsym (xcselect_module, "xcselect_get_developer_dir_path");
if (!xcselect_get_developer_dir_path (developer_dir, sizeof (developer_dir), &from_override, &is_command_line_tools, &from_fallback))
goto beach;
cs = dlopen ("/Library/Developer/PrivateFrameworks/CoreSimulator.framework/CoreSimulator", RTLD_GLOBAL | RTLD_LAZY);
if (cs == NULL)
goto beach;
self->core_simulator = cs;
#define FRIDA_ASSIGN_CS_CONSTANT(N) \
str = dlsym (cs, G_STRINGIFY (N)); \
g_assert (str != NULL); \
self->N = *str
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyArguments);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyEnvironment);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyStandardErrPath);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyStandardOutPath);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyTerminateRunningProcess);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyWaitForDebugger);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyArguments);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyEnvironment);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyStderr);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyStdin);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyStdout);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyWaitForDebugger);
#undef FRIDA_ASSIGN_CS_CONSTANT
SimServiceContextClass = NSClassFromString (@"SimServiceContext");
ctx = [SimServiceContextClass serviceContextForDeveloperDir:[NSString stringWithUTF8String:developer_dir] error:nil];
set = [ctx defaultDeviceSetWithError:nil];
for (SimDevice * device in set.devices)
{
FridaSimmyRuntime * runtime;
FridaSimmyDevice * d;
if (![device.stateString isEqualToString:@"Booted"])
continue;
runtime = frida_simmy_runtime_new (device.runtime);
d = frida_simmy_device_new ([device retain], device.UDID.UUIDString.UTF8String, device.name.UTF8String,
[device getenv:@"SIMULATOR_MODEL_IDENTIFIER" error:nil].UTF8String, runtime, self);
self->on_device_added (d, self->on_device_added_target);
g_object_unref (d);
g_object_unref (runtime);
}
beach:
if (xcselect_module != NULL)
dlclose (xcselect_module);
}
void
_frida_simmy_device_list_applications (FridaSimmyDevice * self, FridaSimmyDeviceListApplicationsCompleteFunc on_complete,
gpointer on_complete_target, GDestroyNotify on_complete_target_destroy_notify)
{
FridaSimmyContext * ctx = frida_simmy_device_get_simmy_context (self);
dispatch_async (ctx->dispatch_queue, ^
{
GeeArrayList * applications;
SimDevice * device;
applications = gee_array_list_new (FRIDA_SIMMY_TYPE_APPLICATION, g_object_ref, g_object_unref, NULL, NULL, NULL);
device = frida_simmy_device_get_handle (self);
NSDictionary<NSString *, NSDictionary<NSString *, id> *> * bundles = [device installedAppsWithError:nil];
for (NSString * identifier in bundles)
{
NSDictionary<NSString *, id> * bundle;
NSString * display_name;
FridaSimmyApplication * app;
bundle = bundles[identifier];
display_name = bundle[@"CFBundleDisplayName"];
app = frida_simmy_application_new (identifier.UTF8String, display_name.UTF8String);
gee_collection_add (GEE_COLLECTION (applications), app);
g_object_unref (app);
}
on_complete (GEE_LIST (applications), on_complete_target);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
g_object_unref (applications);
});
}
void
_frida_simmy_device_launch_application (FridaSimmyDevice * self, const gchar * identifier, FridaHostSpawnOptions * opts,
FridaSimmyDeviceLaunchApplicationCompleteFunc on_complete, gpointer on_complete_target,
GDestroyNotify on_complete_target_destroy_notify)
{
SimDevice * device;
FridaSimmyContext * ctx;
__block FridaStdioPipes * pipes;
__block FridaFileDescriptor * out_fd, * err_fd;
gchar * out_name, * err_name;
GError * error = NULL;
__block GMainContext * main_context;
device = frida_simmy_device_get_handle (self);
ctx = frida_simmy_device_get_simmy_context (self);
if (opts->has_envp)
goto envp_not_supported;
pipes = frida_make_stdio_pipes (opts->stdio, FALSE, NULL, NULL, &out_fd, &out_name, &err_fd, &err_name, &error);
if (error != NULL)
goto propagate_error;
main_context = g_main_context_ref_thread_default ();
@autoreleasepool
{
NSMutableDictionary<NSString *, id> * launch_opts;
launch_opts = [@{
ctx->SimDeviceLaunchApplicationKeyWaitForDebugger: @YES,
ctx->SimDeviceLaunchApplicationKeyTerminateRunningProcess: @YES,
} mutableCopy];
if (opts->has_argv)
launch_opts[ctx->SimDeviceLaunchApplicationKeyArguments] = frida_argv_to_arguments_array (opts->argv, opts->argv_length1, 1);
if (opts->has_env)
launch_opts[ctx->SimDeviceLaunchApplicationKeyEnvironment] = frida_envp_to_environment_dictionary (opts->env, opts->env_length1);
if (opts->stdio == FRIDA_STDIO_PIPE)
{
launch_opts[ctx->SimDeviceLaunchApplicationKeyStandardOutPath] = @(out_name);
launch_opts[ctx->SimDeviceLaunchApplicationKeyStandardErrPath] = @(err_name);
};
[device launchApplicationAsyncWithID:[NSString stringWithUTF8String:identifier]
options:launch_opts
completionQueue:ctx->dispatch_queue
completionHandler:
^(NSError * error, int pid)
{
FridaSimmySpawnedProcess * process = NULL;
if (error == nil)
process = frida_simmy_spawned_process_new (pid, pipes, main_context);
g_main_context_unref (main_context);
main_context = NULL;
g_clear_object (&pipes);
g_clear_object (&out_fd);
g_clear_object (&err_fd);
on_complete (error.localizedDescription.UTF8String, process, on_complete_target);
g_clear_object (&process);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
}];
}
g_free (out_name);
g_free (err_name);
return;
envp_not_supported:
{
error = g_error_new_literal (
FRIDA_ERROR,
FRIDA_ERROR_NOT_SUPPORTED,
"The 'envp' option is not supported when spawning Simmy apps, use the 'env' option instead");
goto propagate_error;
}
propagate_error:
{
dispatch_async (ctx->dispatch_queue, ^
{
on_complete (error->message, NULL, on_complete_target);
g_error_free (error);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
});
return;
}
}
void
_frida_simmy_device_spawn_program (FridaSimmyDevice * self, const gchar * program, FridaHostSpawnOptions * opts,
FridaSimmyDeviceSpawnProgramCompleteFunc on_complete, gpointer on_complete_target,
GDestroyNotify on_complete_target_destroy_notify)
{
const FridaStdio stdio = opts->stdio;
SimDevice * device;
FridaSimmyContext * ctx;
__block FridaStdioPipes * pipes;
__block FridaFileDescriptor * in_fd, * out_fd, * err_fd;
GError * error = NULL;
__block GMainContext * main_context;
device = frida_simmy_device_get_handle (self);
ctx = frida_simmy_device_get_simmy_context (self);
if (opts->has_envp)
goto envp_not_supported;
pipes = frida_make_stdio_pipes (opts->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &error);
if (error != NULL)
goto propagate_error;
main_context = g_main_context_ref_thread_default ();
@autoreleasepool
{
NSMutableDictionary<NSString *, id> * spawn_opts;
__block FridaSimmySpawnedProcess * process = NULL;
spawn_opts = [@{
ctx->SimDeviceSpawnKeyWaitForDebugger: @YES,
} mutableCopy];
if (opts->has_argv)
spawn_opts[ctx->SimDeviceSpawnKeyArguments] = frida_argv_to_arguments_array (opts->argv, opts->argv_length1, 0);
if (opts->has_env)
spawn_opts[ctx->SimDeviceSpawnKeyEnvironment] = frida_envp_to_environment_dictionary (opts->env, opts->env_length1);
if (stdio == FRIDA_STDIO_PIPE)
{
spawn_opts[ctx->SimDeviceSpawnKeyStdin] = @(in_fd->handle);
spawn_opts[ctx->SimDeviceSpawnKeyStdout] = @(out_fd->handle);
spawn_opts[ctx->SimDeviceSpawnKeyStderr] = @(err_fd->handle);
};
[device spawnAsyncWithPath:[NSString stringWithUTF8String:program]
options:spawn_opts
terminationQueue:ctx->dispatch_queue
terminationHandler:
^(int status)
{
_frida_simmy_spawned_process_on_termination (process, status);
g_object_unref (process);
process = NULL;
}
completionQueue:ctx->dispatch_queue
completionHandler:
^(NSError * error, int pid)
{
if (error == nil)
process = frida_simmy_spawned_process_new (pid, pipes, main_context);
g_main_context_unref (main_context);
main_context = NULL;
g_clear_object (&pipes);
g_clear_object (&in_fd);
g_clear_object (&out_fd);
g_clear_object (&err_fd);
on_complete (error.localizedDescription.UTF8String, process, on_complete_target);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
}];
}
return;
envp_not_supported:
{
error = g_error_new_literal (
FRIDA_ERROR,
FRIDA_ERROR_NOT_SUPPORTED,
"The 'envp' option is not supported when spawning Simmy programs, use the 'env' option instead");
goto propagate_error;
}
propagate_error:
{
dispatch_async (ctx->dispatch_queue, ^
{
on_complete (error->message, NULL, on_complete_target);
g_error_free (error);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
});
return;
}
}
const gchar *
_frida_simmy_runtime_get_identifier (FridaSimmyRuntime * self)
{
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).identifier.UTF8String;
}
const gchar *
_frida_simmy_runtime_get_short_name (FridaSimmyRuntime * self)
{
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).shortName.UTF8String;
}
const gchar *
_frida_simmy_runtime_get_version_string (FridaSimmyRuntime * self)
{
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).versionString.UTF8String;
}
const gchar *
_frida_simmy_runtime_get_root (FridaSimmyRuntime * self)
{
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).root.UTF8String;
}
static NSArray *
frida_argv_to_arguments_array (gchar * const * argv, gint argv_length, gint start_index)
{
NSMutableArray * result;
gint i;
result = [NSMutableArray arrayWithCapacity:argv_length];
for (i = start_index; i < argv_length; i++)
[result addObject:[NSString stringWithUTF8String:argv[i]]];
return result;
}
static NSDictionary *
frida_envp_to_environment_dictionary (gchar * const * envp, gint envp_length)
{
NSMutableDictionary * result;
gint i;
result = [NSMutableDictionary dictionaryWithCapacity:envp_length];
for (i = 0; i != envp_length; i++)
{
const gchar * pair, * equals_sign, * name_start, * value_start;
NSUInteger name_size, value_size;
NSString * name, * value;
pair = envp[i];
equals_sign = strchr (pair, '=');
if (equals_sign == NULL)
continue;
name_start = pair;
name_size = equals_sign - name_start;
value_start = equals_sign + 1;
value_size = pair + strlen (pair) - value_start;
name = [[NSString alloc] initWithBytes:name_start
length:name_size
encoding:NSUTF8StringEncoding];
value = [[NSString alloc] initWithBytes:value_start
length:value_size
encoding:NSUTF8StringEncoding];
[result setObject:value forKey:name];
[value release];
[name release];
}
return result;
}
+898
View File
@@ -0,0 +1,898 @@
namespace Frida {
public sealed class SimmyHostSessionBackend : Object, HostSessionBackend {
private Gee.Map<string, SimmyHostSessionProvider> providers = new Gee.HashMap<string, SimmyHostSessionProvider> ();
private void * simmy_context;
private MainContext main_context;
private Cancellable io_cancellable = new Cancellable ();
construct {
main_context = MainContext.ref_thread_default ();
}
public async void start (Cancellable? cancellable) throws IOError {
simmy_context = _start (on_device_added, () => {
schedule_on_frida_thread (start.callback);
});
yield;
}
public async void stop (Cancellable? cancellable) throws IOError {
_stop (simmy_context, () => {
schedule_on_frida_thread (stop.callback);
});
yield;
simmy_context = null;
io_cancellable.cancel ();
foreach (var provider in providers.values) {
provider_unavailable (provider);
yield provider.close (cancellable);
}
providers.clear ();
}
private void on_device_added (Simmy.Device device) {
schedule_on_frida_thread (() => {
var prov = new SimmyHostSessionProvider (device);
providers[device.udid] = prov;
provider_available (prov);
return Source.REMOVE;
});
}
private void schedule_on_frida_thread (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (main_context);
}
public extern static void * _start (DeviceAddedFunc on_device_added, owned Simmy.CompleteFunc on_complete);
public extern static void _stop (void * simmy_context, owned Simmy.CompleteFunc on_complete);
public delegate void DeviceAddedFunc (Simmy.Device device);
}
public sealed class SimmyHostSessionProvider : Object, HostSessionProvider {
public Simmy.Device device {
get;
construct;
}
public string id {
get { return device.udid; }
}
public string name {
get { return device.name; }
}
public Variant? icon {
get { return null; }
}
public HostSessionProviderKind kind {
get {
return HostSessionProviderKind.REMOTE;
}
}
private SimmyHostSession? host_session;
public SimmyHostSessionProvider (Simmy.Device device) {
Object (device: device);
}
public async void close (Cancellable? cancellable) throws IOError {
if (host_session == null)
return;
host_session.agent_session_detached.disconnect (on_agent_session_detached);
yield host_session.close (cancellable);
host_session = null;
}
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (host_session != null)
throw new Error.INVALID_OPERATION ("Already created");
HostSessionEntry local_system = yield hub.resolve_host_session ("local", cancellable);
host_session = new SimmyHostSession (device, local_system);
host_session.agent_session_detached.connect (on_agent_session_detached);
return host_session;
}
public async void destroy (HostSession session, Cancellable? cancellable) throws Error, IOError {
if (session != host_session)
throw new Error.INVALID_ARGUMENT ("Invalid host session");
host_session.agent_session_detached.disconnect (on_agent_session_detached);
yield host_session.close (cancellable);
host_session = null;
}
public async AgentSession link_agent_session (HostSession host_session, AgentSessionId id, AgentMessageSink sink,
Cancellable? cancellable) throws Error, IOError {
if (host_session != this.host_session)
throw new Error.INVALID_ARGUMENT ("Invalid host session");
return yield this.host_session.link_agent_session (id, sink, cancellable);
}
public void unlink_agent_session (HostSession host_session, AgentSessionId id) {
if (host_session != this.host_session)
return;
this.host_session.unlink_agent_session (id);
}
public async IOStream link_channel (HostSession host_session, ChannelId id, Cancellable? cancellable)
throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Channels are not supported by this backend");
}
public void unlink_channel (HostSession host_session, ChannelId id) {
}
public async ServiceSession link_service_session (HostSession host_session, ServiceSessionId id, Cancellable? cancellable)
throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Services are not supported by this backend");
}
public void unlink_service_session (HostSession host_session, ServiceSessionId id) {
}
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
agent_session_detached (id, reason, crash);
}
}
public sealed class SimmyHostSession : Object, HostSession, HostSessionConnection {
public Simmy.Device device {
get;
construct;
}
public HostSessionEntry local_system {
get;
construct;
}
public HostSession host_session {
get {
return this;
}
}
private Gee.Map<uint, Simmy.SpawnedProcess> spawned_processes = new Gee.HashMap<uint, Simmy.SpawnedProcess> ();
private SpringboardAgent springboard_agent;
private Cancellable io_cancellable = new Cancellable ();
public SimmyHostSession (Simmy.Device device, HostSessionEntry local_system) {
Object (device: device, local_system: local_system);
}
construct {
var s = local_system.session;
s.process_crashed.connect (on_process_crashed);
s.agent_session_detached.connect (on_agent_session_detached);
s.uninjected.connect (on_uninjected);
springboard_agent = new SpringboardAgent (this);
springboard_agent.unloaded.connect (on_springboard_agent_unloaded);
}
public async void close (Cancellable? cancellable) throws IOError {
springboard_agent.unloaded.disconnect (on_springboard_agent_unloaded);
yield springboard_agent.close (cancellable);
io_cancellable.cancel ();
}
public async void ping (uint interval_seconds, Cancellable? cancellable) throws Error, IOError {
throw new Error.INVALID_OPERATION ("Only meant to be implemented by services");
}
public async HashTable<string, Variant> query_system_parameters (Cancellable? cancellable) throws Error, IOError {
var parameters = new HashTable<string, Variant> (str_hash, str_equal);
var runtime = device.runtime;
unowned string os_name = runtime.short_name;
var os = new HashTable<string, Variant> (str_hash, str_equal);
os["id"] = os_name.down ();
os["name"] = os_name.replace ("iOS", "iPhone OS");
os["version"] = runtime.version_string;
parameters["os"] = os;
parameters["platform"] = "darwin";
parameters["arch"] = "arm64";
var hardware = new HashTable<string, Variant> (str_hash, str_equal);
hardware["product"] = device.model_identifier;
parameters["hardware"] = hardware;
parameters["access"] = "full";
parameters["name"] = device.name;
parameters["udid"] = device.udid;
return parameters;
}
public async HostApplicationInfo get_frontmost_application (HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
var opts = FrontmostQueryOptions._deserialize (options);
try {
return yield springboard_agent.get_frontmost_application (opts, cancellable);
} catch (Error e) {
if (e is Error.PERMISSION_DENIED)
throw new Error.NOT_SUPPORTED ("Frontmost query is only supported with SIP disabled (for now)");
throw e;
}
}
public async HostApplicationInfo[] enumerate_applications (HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
var opts = ApplicationQueryOptions._deserialize (options);
var scope = opts.scope;
Future<Gee.List<Simmy.Application>> list_apps_request = device.list_applications ();
Future<Gee.List<LaunchdJob>> list_jobs_request = list_launchd_jobs (cancellable);
var selected_ids = new Gee.HashSet<string> ();
opts.enumerate_selected_identifiers (identifier => {
selected_ids.add (identifier);
});
bool include_all_apps = selected_ids.is_empty;
var apps = new Gee.ArrayList<Simmy.Application> ();
foreach (Simmy.Application app in yield list_apps_request.wait_async (cancellable)) {
unowned string id = app.identifier;
if (include_all_apps || id in selected_ids)
apps.add (app);
}
Gee.Map<string, Bytes>? icons = null;
if (scope == FULL) {
var app_ids = new Gee.ArrayList<string> ();
foreach (var app in apps)
app_ids.add (app.identifier);
try {
icons = yield springboard_agent.fetch_application_icons (app_ids, cancellable);
} catch (Error e) {
}
}
var pids = new Gee.HashMap<string, uint> ();
foreach (var job in yield list_jobs_request.wait_async (cancellable)) {
if (job.pid == 0)
continue;
unowned string label = job.label;
if (!label.has_prefix ("UIKitApplication:"))
continue;
string identifier = label[17:].split ("[", 2)[0];
pids[identifier] = job.pid;
}
var result = new HostApplicationInfo[0];
foreach (var app in apps) {
unowned string identifier = app.identifier;
var info = HostApplicationInfo (identifier, app.display_name, pids[identifier], make_parameters_dict ());
if (scope == FULL && icons != null)
add_app_icons (info.parameters, icons[identifier]);
result += info;
}
return result;
}
public async HostProcessInfo[] enumerate_processes (HashTable<string, Variant> options, Cancellable? cancellable)
throws Error, IOError {
var opts = ProcessQueryOptions._deserialize (options);
if (!opts.has_selected_pids ()) {
foreach (var job in yield list_launchd_jobs (cancellable).wait_async (cancellable)) {
if (job.pid != 0)
opts.select_pid (job.pid);
}
}
try {
return yield local_system.session.enumerate_processes (opts._serialize (), cancellable);
} catch (GLib.Error e) {
throw_api_error (e);
}
}
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async HostChildInfo[] enumerate_pending_children (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async uint spawn (string program, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
Future<Simmy.SpawnedProcess> f;
if (program[0] == '/')
f = device.spawn_program (program, options);
else
f = device.launch_application (program, options);
var process = yield f.wait_async (cancellable);
uint pid = process.pid;
if (options.stdio == PIPE) {
spawned_processes[pid] = process;
process.terminated.connect (on_spawned_process_terminated);
process.output.connect (on_spawned_process_output);
}
return pid;
}
private void on_spawned_process_terminated (Simmy.SpawnedProcess process, int status) {
spawned_processes.unset (process.pid);
}
private void on_spawned_process_output (Simmy.SpawnedProcess process, int fd, uint8[] data) {
output (process.pid, fd, data);
if (data.length == 0)
spawned_processes.unset (process.pid);
}
public async void input (uint pid, uint8[] data, Cancellable? cancellable) throws Error, IOError {
var process = spawned_processes[pid];
if (process == null)
throw new Error.INVALID_ARGUMENT ("Invalid PID");
yield process.input (data, cancellable);
}
public async void resume (uint pid, Cancellable? cancellable) throws GLib.Error {
yield local_system.session.resume (pid, cancellable);
}
public async void kill (uint pid, Cancellable? cancellable) throws GLib.Error {
yield local_system.session.kill (pid, cancellable);
}
public async AgentSessionId attach (uint pid, HashTable<string, Variant> options,
Cancellable? cancellable) throws GLib.Error {
return yield local_system.session.attach (pid, options, cancellable);
}
public async void reattach (AgentSessionId id, Cancellable? cancellable) throws Error, IOError {
throw new Error.INVALID_OPERATION ("Only meant to be implemented by services");
}
public async AgentSession link_agent_session (AgentSessionId id, AgentMessageSink sink,
Cancellable? cancellable) throws Error, IOError {
return yield local_system.provider.link_agent_session (local_system.session, id, sink, cancellable);
}
public void unlink_agent_session (AgentSessionId id) {
local_system.provider.unlink_agent_session (local_system.session, id);
}
public async InjectorPayloadId inject_library_file (uint pid, string path, string entrypoint, string data,
Cancellable? cancellable) throws GLib.Error {
return yield local_system.session.inject_library_file (pid, path, entrypoint, data, cancellable);
}
public async InjectorPayloadId inject_library_blob (uint pid, uint8[] blob, string entrypoint, string data,
Cancellable? cancellable) throws GLib.Error {
return yield local_system.session.inject_library_blob (pid, blob, entrypoint, data, cancellable);
}
public async ChannelId open_channel (string address, Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public IOStream link_channel (ChannelId id) throws Error {
throw_not_supported ();
}
public void unlink_channel (ChannelId id) {
assert_not_reached ();
}
public async ServiceSessionId open_service (string address, Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public ServiceSession link_service_session (ServiceSessionId id) throws Error {
throw_not_supported ();
}
public void unlink_service_session (ServiceSessionId id) {
assert_not_reached ();
}
private void on_process_crashed (CrashInfo crash) {
process_crashed (crash);
}
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
agent_session_detached (id, reason, crash);
}
private void on_uninjected (InjectorPayloadId id) {
uninjected (id);
}
internal Future<Gee.List<LaunchdJob>> list_launchd_jobs (Cancellable? cancellable) {
var promise = new Promise<Gee.List<LaunchdJob>> ();
perform_list_launchd_jobs.begin (cancellable, promise);
return promise.future;
}
private async void perform_list_launchd_jobs (Cancellable? cancellable, Promise<Gee.List<LaunchdJob>> request) {
try {
RunResult res = yield run_and_capture_output ({
Path.build_filename (device.runtime.root, "bin", "launchctl"),
"list"
}, cancellable).wait_async (cancellable);
res.check ();
var jobs = new Gee.ArrayList<LaunchdJob> ();
string[] lines = res.standard_output.chomp ().split ("\n");
foreach (string line in lines[1:]) {
string[] tokens = line.split ("\t", 3);
if (tokens.length != 3)
throw new Error.PROTOCOL ("Unexpected launchctl output");
unowned string raw_pid = tokens[0];
unowned string label = tokens[2];
jobs.add (new LaunchdJob () {
pid = uint.parse (raw_pid),
label = label,
});
}
request.resolve (jobs);
} catch (GLib.Error e) {
request.reject (e);
}
}
internal class LaunchdJob {
public uint pid;
public string label;
}
private Future<RunResult> run_and_capture_output (string[] argv, Cancellable? cancellable) {
var promise = new Promise<RunResult> ();
perform_run_and_capture_output.begin (argv, cancellable, promise);
return promise.future;
}
private async void perform_run_and_capture_output (string[] argv, Cancellable? cancellable, Promise<RunResult> request) {
string[] argv_copy = argv;
var opts = HostSpawnOptions ();
opts.has_argv = true;
opts.argv = argv_copy;
opts.stdio = PIPE;
Simmy.SpawnedProcess process;
try {
process = yield device.spawn_program (argv_copy[0], opts).wait_async (cancellable);
} catch (GLib.Error e) {
request.reject (e);
return;
}
int status = -1;
var standard_output = new StringBuilder.sized (1024);
var standard_error = new StringBuilder.sized (1024);
process.terminated.connect ((s) => {
status = s;
perform_run_and_capture_output.callback ();
});
process.output.connect ((fd, data) => {
var buf = new char[data.length + 1];
Memory.copy (buf, data, data.length);
buf[data.length] = '\0';
unowned StringBuilder target = (fd == 1) ? standard_output : standard_error;
target.append ((string) buf);
});
process.resume ();
yield;
request.resolve (new RunResult (argv_copy) {
status = status,
standard_output = standard_output.str,
standard_error = standard_error.str,
});
}
private class RunResult {
public int status;
public string standard_output;
public string standard_error;
private string[] argv;
public RunResult (string[] argv) {
this.argv = argv;
}
public void check () throws Error {
if (status != 0) {
throw new Error.NOT_SUPPORTED ("Failed to run `%s`, it exited with status %d: %s",
string.joinv (" ", argv),
status,
standard_error.chomp ());
}
}
}
private void on_springboard_agent_unloaded (InternalAgent dead_agent) {
dead_agent.unloaded.disconnect (on_springboard_agent_unloaded);
springboard_agent = new SpringboardAgent (this);
springboard_agent.unloaded.connect (on_springboard_agent_unloaded);
}
[NoReturn]
private static void throw_not_supported () throws Error {
throw new Error.NOT_SUPPORTED ("Not yet supported by the Simmy backend");
}
}
private sealed class SpringboardAgent : InternalAgent {
public SpringboardAgent (HostSessionConnection connection) {
Object (connection: connection);
}
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
Cancellable? cancellable) throws Error, IOError {
var scope = options.scope;
var scope_node = new Json.Node.alloc ().init_string (scope.to_nick ());
Json.Node response = yield call ("getFrontmostApplication", new Json.Node[] { scope_node }, null, cancellable);
if (response.get_node_type () == NULL)
return HostApplicationInfo.empty ();
var item = response.get_array ();
var identifier = item.get_string_element (0);
var name = item.get_string_element (1);
var pid = (uint) item.get_int_element (2);
var info = HostApplicationInfo (identifier, name, pid, make_parameters_dict ());
if (scope == FULL)
add_app_icons (info.parameters, new Bytes.take (Base64.decode (item.get_string_element (3))));
return info;
}
public async Gee.Map<string, Bytes> fetch_application_icons (Gee.List<string> app_ids, Cancellable? cancellable)
throws Error, IOError {
var app_id_values = new Json.Array ();
foreach (var id in app_ids)
app_id_values.add_string_element (id);
var args = new Json.Node[] { new Json.Node.alloc ().init_array (app_id_values) };
Json.Node response = yield call ("fetchApplicationIcons", args, null, cancellable);
var icons = new Gee.HashMap<string, Bytes> ();
var reader = new Json.Reader (response);
int n = reader.count_elements ();
for (int i = 0; i != n; i++) {
reader.read_element (i);
if (!reader.get_null_value ())
icons[app_ids[i]] = new Bytes (Base64.decode (reader.get_string_value ()));
reader.end_element ();
}
return icons;
}
protected override async uint get_target_pid (Cancellable? cancellable) throws Error, IOError {
var host_session = (SimmyHostSession) connection.host_session;
foreach (var job in yield host_session.list_launchd_jobs (cancellable).wait_async (cancellable)) {
if (job.label == "com.apple.SpringBoard")
return job.pid;
}
throw new Error.NOT_SUPPORTED ("Unable to locate SpringBoard process");
}
protected override async string? load_source (Cancellable? cancellable) throws Error, IOError {
return (string) Frida.Data.Simmy.get_springboard_js_blob ().data;
}
}
private void add_app_icons (HashTable<string, Variant> parameters, Bytes png) {
var icons = new VariantBuilder (new VariantType.array (VariantType.VARDICT));
icons.open (VariantType.VARDICT);
icons.add ("{sv}", "format", new Variant.string ("png"));
icons.add ("{sv}", "image", Variant.new_from_data (new VariantType ("ay"), png.get_data (), true, png));
icons.close ();
parameters["icons"] = icons.end ();
}
[CCode (gir_namespace = "FridaSimmy", gir_version = "1.0")]
namespace Simmy {
public sealed class Device : Object {
public void * handle {
get;
construct;
}
public string udid {
get;
construct;
}
public string name {
get;
construct;
}
public string model_identifier {
get;
construct;
}
public Runtime runtime {
get;
construct;
}
public void * simmy_context {
get;
construct;
}
public Device (void * handle, string udid, string name, string model_identifier, Runtime runtime,
void * simmy_context) {
Object (
handle: handle,
udid: udid,
name: name,
model_identifier: model_identifier,
runtime: runtime,
simmy_context: simmy_context
);
}
public Future<Gee.List<Application>> list_applications () {
var promise = new Promise<Gee.List<Application>> ();
_list_applications (applications => {
promise.resolve (applications);
});
return promise.future;
}
public Future<SpawnedProcess> launch_application (string identifier, HostSpawnOptions opts) {
var promise = new Promise<SpawnedProcess> ();
_launch_application (identifier, opts, (error_message, process) => {
if (error_message != null)
promise.reject (new Error.NOT_SUPPORTED ("%s", error_message));
else
promise.resolve (process);
});
return promise.future;
}
public Future<SpawnedProcess> spawn_program (string program, HostSpawnOptions opts) {
var promise = new Promise<SpawnedProcess> ();
_spawn_program (program, opts, (error_message, process) => {
if (error_message != null)
promise.reject (new Error.NOT_SUPPORTED ("%s", error_message));
else
promise.resolve (process);
});
return promise.future;
}
public extern void _list_applications (owned ListApplicationsCompleteFunc on_complete);
public extern void _launch_application (string identifier, HostSpawnOptions options,
owned LaunchApplicationCompleteFunc on_complete);
public extern void _spawn_program (string program, HostSpawnOptions options,
owned SpawnProgramCompleteFunc on_complete);
public delegate void ListApplicationsCompleteFunc (Gee.List<Application> device);
public delegate void LaunchApplicationCompleteFunc (string? error_message, SpawnedProcess? process);
public delegate void SpawnProgramCompleteFunc (string? error_message, SpawnedProcess? process);
}
public sealed class Runtime : Object {
public void * handle {
get;
construct;
}
public string identifier {
get {
return _get_identifier ();
}
}
public string short_name {
get {
return _get_short_name ();
}
}
public string version_string {
get {
return _get_version_string ();
}
}
public string root {
get {
return _get_root ();
}
}
public Runtime (void * handle) {
Object (handle: handle);
}
public extern unowned string _get_identifier ();
public extern unowned string _get_short_name ();
public extern unowned string _get_version_string ();
public extern unowned string _get_root ();
}
public sealed class Application : Object {
public string identifier {
get;
construct;
}
public string display_name {
get;
construct;
}
public Application (string identifier, string display_name) {
Object (identifier: identifier, display_name: display_name);
}
}
public delegate void CompleteFunc ();
public sealed class SpawnedProcess : Object {
public signal void terminated (int status);
public signal void output (int fd, uint8[] data);
public uint pid {
get;
construct;
}
public StdioPipes? pipes {
get;
construct;
}
public MainContext main_context {
get;
construct;
}
private OutputStream? stdin_stream;
private Cancellable io_cancellable = new Cancellable ();
public SpawnedProcess (uint pid, StdioPipes? pipes, MainContext main_context) {
Object (
pid: pid,
pipes: pipes,
main_context: main_context
);
}
construct {
if (pipes != null) {
var source = new IdleSource ();
source.set_callback (() => {
start_processing_pipes ();
return Source.REMOVE;
});
source.attach (main_context);
}
}
private void start_processing_pipes () {
stdin_stream = pipes.input;
process_next_output_from.begin (pipes.output, 1);
process_next_output_from.begin (pipes.error, 2);
}
public void close () {
io_cancellable.cancel ();
}
public void resume () {
Posix.kill ((Posix.pid_t) pid, Posix.Signal.CONT);
}
public void _on_termination (int status) {
var source = new IdleSource ();
source.set_callback (() => {
if (stdin_stream != null) {
stdin_stream.close_async.begin ();
stdin_stream = null;
}
close ();
terminated (status);
return Source.REMOVE;
});
source.attach (main_context);
}
public async void input (uint8[] data, Cancellable? cancellable) throws Error, IOError {
if (stdin_stream == null)
throw new Error.NOT_SUPPORTED ("Unable to pass input to process spawned without piped stdio");
try {
yield stdin_stream.write_all_async (data, Priority.DEFAULT, cancellable, null);
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
}
private async void process_next_output_from (InputStream stream, int fd) {
try {
var buf = new uint8[4096];
var n = yield stream.read_async (buf, Priority.DEFAULT, io_cancellable);
var data = buf[0:n];
output (fd, data);
if (n > 0)
process_next_output_from.begin (stream, fd);
} catch (GLib.Error e) {
stream.close_async.begin ();
if (!(e is IOError.CANCELLED))
output (fd, {});
}
}
}
}
}
+296
View File
@@ -0,0 +1,296 @@
[CCode (gir_namespace = "FridaSimmy", gir_version = "1.0")]
namespace Frida.TextPList {
public class Parser {
private Lexer lex;
private Token look;
public Parser (string text) {
lex = new Lexer (text);
}
public Variant parse () throws Error {
look = lex.next_token ();
Variant v = parse_value ();
if (look.type != EOF)
throw new Error.PROTOCOL ("Trailing content after root value");
return v;
}
private Variant parse_value () throws Error {
switch (look.type) {
case LBRACE: return parse_dict ();
case LPAREN: return parse_array ();
case NUMBER: return parse_number ();
case STRING: return parse_string ();
default:
throw new Error.PROTOCOL (@"Unexpected token %s".printf (look.type.to_string ()));
}
}
private Variant parse_dict () throws Error {
eat (LBRACE);
var b = new VariantBuilder (new VariantType ("a{sv}"));
while (look.type != RBRACE) {
if (look.type != STRING)
throw new Error.PROTOCOL ("Dictionary key must be STRING");
string key = look.text;
eat (STRING);
eat (EQUAL);
b.add ("{sv}", key, parse_value ());
if (look.type == SEMI)
eat (SEMI);
if (look.type == RBRACE)
break;
}
eat (RBRACE);
return b.end ();
}
private Variant parse_array () throws Error {
eat (LPAREN);
var b = new VariantBuilder (new VariantType ("av"));
if (look.type != RPAREN) {
b.add_value (parse_value ());
while (look.type == COMMA) {
eat (COMMA);
b.add_value (new Variant.variant (parse_value ()));
}
}
eat (RPAREN);
return b.end ();
}
private Variant parse_number () throws Error {
string raw = look.text;
eat (NUMBER);
if (raw.index_of_char ('.') != -1) {
double d;
if (!double.try_parse (raw, out d))
throw new Error.PROTOCOL (@"Bad double: %s".printf (raw));
return d;
} else {
int64 x;
if (!int64.try_parse (raw, out x))
throw new Error.PROTOCOL (@"Bad int: %s".printf (raw));
return x;
}
}
private Variant parse_string () throws Error {
string s = look.text;
eat (STRING);
return s;
}
private void eat (TokenType t) throws Error {
if (look.type != t)
throw new Error.PROTOCOL (@"Expected %s but found %s".printf (t.to_string (), look.type.to_string ()));
look = lex.next_token ();
}
}
private class Lexer {
private string s;
private int i = 0;
private int n;
public Lexer (string input) {
s = input;
n = s.length;
}
public Token next_token () throws Error {
skip_whitespace_and_comments ();
if (at_end ())
return new Token (EOF);
switch (peek ()) {
case '{': next (); return new Token (LBRACE);
case '}': next (); return new Token (RBRACE);
case '(': next (); return new Token (LPAREN);
case ')': next (); return new Token (RPAREN);
case '=': next (); return new Token (EQUAL);
case ';': next (); return new Token (SEMI);
case ',': next (); return new Token (COMMA);
case '"': return new Token (STRING, read_quoted ());
default:
var word = read_bare ();
if (word.length == 0)
throw new Error.PROTOCOL (@"Unexpected character '%c'".printf (peek ()));
return read_number_or_bareword (word);
}
}
private bool at_end () {
return i >= n;
}
private char peek () {
return s[i];
}
private char next () {
return s[i++];
}
private void skip_whitespace_and_comments () {
while (!at_end ()) {
if (is_whitespace (peek ())) {
next ();
continue;
}
if (peek () == '/' && i + 1 < n) {
if (s[i + 1] == '/') {
i += 2;
while (!at_end () && peek () != '\n')
next ();
continue;
}
if (s[i + 1] == '*') {
i += 2;
while (!at_end ()) {
if (peek () == '*' && i + 1 < n && s[i + 1] == '/') {
i += 2;
break;
}
next ();
}
continue;
}
}
break;
}
}
private static bool is_whitespace (char c) {
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
private string read_quoted () throws Error {
next ();
var sb = new StringBuilder ();
while (!at_end ()) {
char c = next ();
if (c == '\\') {
if (at_end ())
break;
char e = next ();
switch (e) {
case 'n':
sb.append_c ('\n');
break;
case 'r':
sb.append_c ('\r');
break;
case 't':
sb.append_c ('\t');
break;
case '"':
sb.append_c ('"');
break;
case '\\':
sb.append_c ('\\');
break;
default:
sb.append_c (e);
break;
}
} else if (c == '"') {
return sb.str;
} else {
sb.append_c (c);
}
}
throw new Error.PROTOCOL ("Unterminated quoted string");
}
private string read_bare () {
int start = i;
while (!at_end () && is_bare_char (peek ()))
next ();
return s.substring (start, i - start);
}
private static bool is_bare_char (char c) {
if (c.isalnum ())
return true;
const string extra = "._-/:$+@";
return extra.index_of_char (c) != -1;
}
private Token read_number_or_bareword (string word) {
bool numeric = true;
bool has_dot = false;
int len = word.length;
for (int k = 0; k != len; k++) {
char c = word[k];
if (k == 0 && (c == '+' || c == '-'))
continue;
if (c == '.') {
if (has_dot) {
numeric = false;
break;
}
has_dot = true;
continue;
}
if (!c.isdigit ()) {
numeric = false;
break;
}
}
if (numeric && word.length > 0 && !(word == "+" || word == "-"))
return new Token (NUMBER, word);
return new Token (STRING, word);
}
}
private class Token {
public TokenType type;
public string text;
public Token (TokenType t, string s = "") {
type = t;
text = s;
}
}
private enum TokenType {
LBRACE,
RBRACE,
LPAREN,
RPAREN,
EQUAL,
SEMI,
COMMA,
STRING,
NUMBER,
EOF,
}
}
+2 -1
View File
@@ -61,7 +61,8 @@ namespace Frida {
io_cancellable.cancel ();
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
string? raw_address = null;
TlsCertificate? certificate = null;
string? origin = null;
-21
View File
@@ -298,25 +298,4 @@ namespace Frida {
public extern void _do_resume ();
}
public sealed class StdioPipes : Object {
public OutputStream input {
get;
construct;
}
public InputStream output {
get;
construct;
}
public InputStream error {
get;
construct;
}
public StdioPipes (OutputStream input, InputStream output, InputStream error) {
Object (input: input, output: output, error: error);
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.3.2
revision = 17.6.2
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/lwip.git
revision = 4fe7223c2e80dc328266fb2483e789ee5fad7c79
revision = 334ebb20a22023db2f0d5017e96eb0c7798082fd
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/openssl.git
revision = 5029c4562d23547d8e29768e1b0de573c52bf3fc
revision = fa60a1c8c704e4ca0cc0dcb289c3be1fea1b50ff
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/selinux.git
revision = 6d5513fd8069e9ff9b7aa10970d34457b32970c8
revision = b7c8cd70d821bb3fc69500e6273149299b80b923
depth = 1
[provide]
+2 -1
View File
@@ -1,7 +1,8 @@
[wrap-git]
url = https://github.com/frida/termux-elf-cleaner.git
revision = 8e572cb066907cfc8ac1f73d511683844de906ee
revision = ab933ec128c6538067cdf3907ebbbc3bc35f1c55
depth = 1
[provide]
program_names = termux-elf-cleaner
+6
View File
@@ -108,6 +108,12 @@ frida_test_cpu (void)
# else
return FRIDA_TEST_CPU_MIPS;
# endif
#elif defined (HAVE_RISCV)
# if GLIB_SIZEOF_VOID_P == 8
return FRIDA_TEST_CPU_RISCV64;
# else
return FRIDA_TEST_CPU_RISCV32;
# endif
#endif
}
+3 -1
View File
@@ -231,7 +231,9 @@ namespace Frida.Test {
ARM_32,
ARM_64,
MIPS,
MIPSEL
MIPSEL,
RISCV32,
RISCV64
}
public enum Arch {
+15 -14
View File
@@ -470,7 +470,8 @@ namespace Frida.HostSessionTest {
get { return HostSessionProviderKind.LOCAL; }
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Not implemented");
}
@@ -1536,7 +1537,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var applications = yield session.enumerate_applications (make_parameters_dict (), cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
@@ -1577,7 +1578,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -1776,7 +1777,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var applications = yield session.enumerate_applications (make_parameters_dict (), cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
@@ -1814,7 +1815,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -1919,7 +1920,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -2398,7 +2399,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var id = yield host_session.attach (pid, make_parameters_dict (), cancellable);
var session = yield prov.link_agent_session (host_session, id, h, cancellable);
@@ -2549,7 +2550,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
@@ -2576,7 +2577,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -3307,7 +3308,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
@@ -3333,7 +3334,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -3584,7 +3585,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
@@ -3617,7 +3618,7 @@ namespace Frida.HostSessionTest {
Cancellable? cancellable = null;
stdout.printf ("connecting to frida-server\n");
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
stdout.printf ("enumerating processes\n");
var processes = yield host_session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
@@ -4242,7 +4243,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
+14
View File
@@ -77,6 +77,18 @@ namespace Darwin.XNU {
[CCode (cheader_filename = "sys/sysctl.h")]
public int sysctlbyname (string name, void * oldp, size_t * oldlenp, void * newp = null, size_t newlen = 0);
[CCode (cheader_filename = "util.h")]
public int openpty (out int amaster, out int aslave, [CCode (array_length=false, array_null_terminated=true)] char[] name,
Posix.termios? termp, winsize? winp);
[CCode (cname = "struct winsize", has_type_id = false, cheader_filename = "sys/ttycom.h", destroy_function = "")]
public struct winsize {
public ushort ws_row;
public ushort ws_col;
public ushort ws_xpixel;
public ushort ws_ypixel;
}
[CCode (cname = "struct xinpgen", cheader_filename = "netinet/in_pcb.h")]
public struct InetPcbGeneration {
[CCode (cname = "xig_len")]
@@ -104,4 +116,6 @@ namespace Darwin.XNU {
[CCode (cheader_filename = "fcntl.h")]
public const int F_FULLFSYNC;
[CCode (cheader_filename = "fcntl.h")]
public const int F_SETNOSIGPIPE;
}
+14
View File
@@ -0,0 +1,14 @@
[CCode (lower_case_cprefix = "", gir_namespace = "FreeBSD", gir_version = "1.0")]
namespace FreeBSD {
[CCode (cheader_filename = "libutil.h")]
public int openpty (out int amaster, out int aslave, [CCode (array_length=false, array_null_terminated=true)] char[] name,
Posix.termios? termp, winsize? winp);
[CCode (cname = "struct winsize", has_type_id = false, cheader_filename = "sys/ttycom.h", destroy_function = "")]
public struct winsize {
public ushort ws_row;
public ushort ws_col;
public ushort ws_xpixel;
public ushort ws_ypixel;
}
}