Compare commits

...
84 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
63 changed files with 6079 additions and 4266 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
+5 -1
View File
@@ -52,7 +52,6 @@ endif
extra_deps = []
if host_os_family != 'windows'
extra_vala_args += '--pkg=gio-unix-2.0'
extra_deps += gio_unix_dep
endif
@@ -60,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',
+70
View File
@@ -54,6 +54,76 @@ namespace Frida {
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,
+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 ();
+35 -11
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)
+1 -1
Submodule releng updated: 800f2385c3...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.
+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
}
+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;
}
+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);
+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;
}
-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.10"
},
"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.10",
"resolved": "https://registry.npmjs.org/frida-java-bridge/-/frida-java-bridge-7.0.10.tgz",
"integrity": "sha512-AtjgAHE7a2wV6M/8tZvjVqda0T8YGmSoXqkbD0r1Eq6XTFDKYbClKbZqMYHpI9OjxtkIx+xPqkHWWE3+0fDH7w==",
"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.10"
}
}
+219 -104
View File
@@ -741,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 {
@@ -2017,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) {
@@ -3018,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;
@@ -3054,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;
@@ -3081,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);
}
}
}
@@ -3117,6 +3217,21 @@ 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) {
@@ -3125,7 +3240,7 @@ namespace Frida {
public extern bool _syscall_satisfies (int syscall_id, LinuxSyscall mask);
private sealed class ProcMapsSoEntry {
public class ProcMapsSoEntry {
public uint64 base_address;
public string path;
public string identity;
@@ -3137,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;
@@ -3151,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
@@ -3207,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;
+32 -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 = [
+140 -43
View File
@@ -12,12 +12,15 @@ struct _FridaSimmyContext
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;
@@ -30,6 +33,9 @@ struct _FridaSimmyContext
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;
@@ -153,12 +159,15 @@ frida_simmy_context_emit_devices (FridaSimmyContext * self)
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);
@@ -234,7 +243,7 @@ _frida_simmy_device_list_applications (FridaSimmyDevice * self, FridaSimmyDevice
}
void
_frida_simmy_device_launch_application (FridaSimmyDevice * self, const gchar * identifier, FridaHostSpawnOptions * options,
_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)
{
@@ -249,20 +258,12 @@ _frida_simmy_device_launch_application (FridaSimmyDevice * self, const gchar * i
device = frida_simmy_device_get_handle (self);
ctx = frida_simmy_device_get_simmy_context (self);
pipes = frida_make_stdio_pipes (options->stdio, FALSE, NULL, NULL, &out_fd, &out_name, &err_fd, &err_name, &error);
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)
{
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;
}
goto propagate_error;
main_context = g_main_context_ref_thread_default ();
@@ -275,7 +276,13 @@ _frida_simmy_device_launch_application (FridaSimmyDevice * self, const gchar * i
ctx->SimDeviceLaunchApplicationKeyTerminateRunningProcess: @YES,
} mutableCopy];
if (options->stdio == FRIDA_STDIO_PIPE)
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);
@@ -310,26 +317,18 @@ _frida_simmy_device_launch_application (FridaSimmyDevice * self, const gchar * i
g_free (out_name);
g_free (err_name);
}
void
_frida_simmy_device_spawn_program (FridaSimmyDevice * self, const gchar * program, FridaHostSpawnOptions * options,
FridaSimmyDeviceSpawnProgramCompleteFunc on_complete, gpointer on_complete_target,
GDestroyNotify on_complete_target_destroy_notify)
{
const FridaStdio stdio = options->stdio;
SimDevice * device;
FridaSimmyContext * ctx;
__block FridaStdioPipes * pipes;
__block FridaFileDescriptor * in_fd, * out_fd, * err_fd;
GError * error = NULL;
__block GMainContext * main_context;
return;
device = frida_simmy_device_get_handle (self);
ctx = frida_simmy_device_get_simmy_context (self);
pipes = frida_make_stdio_pipes (options->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &error);
if (error != NULL)
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, ^
{
@@ -340,8 +339,34 @@ _frida_simmy_device_spawn_program (FridaSimmyDevice * self, const gchar * progra
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
@@ -353,17 +378,11 @@ _frida_simmy_device_spawn_program (FridaSimmyDevice * self, const gchar * progra
ctx->SimDeviceSpawnKeyWaitForDebugger: @YES,
} mutableCopy];
if (options->has_argv)
{
NSMutableArray<NSString *> * args;
int i;
if (opts->has_argv)
spawn_opts[ctx->SimDeviceSpawnKeyArguments] = frida_argv_to_arguments_array (opts->argv, opts->argv_length1, 0);
args = [NSMutableArray arrayWithCapacity:options->argv_length1];
for (i = 0; i != options->argv_length1; i++)
[args addObject:[NSString stringWithUTF8String:options->argv[i]]];
spawn_opts[ctx->SimDeviceSpawnKeyArguments] = args;
}
if (opts->has_env)
spawn_opts[ctx->SimDeviceSpawnKeyEnvironment] = frida_envp_to_environment_dictionary (opts->env, opts->env_length1);
if (stdio == FRIDA_STDIO_PIPE)
{
@@ -404,6 +423,30 @@ _frida_simmy_device_spawn_program (FridaSimmyDevice * self, const gchar * progra
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 *
@@ -429,3 +472,57 @@ _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;
}
+31 -9
View File
@@ -235,7 +235,13 @@ namespace Frida {
Cancellable? cancellable) throws Error, IOError {
var opts = FrontmostQueryOptions._deserialize (options);
return yield springboard_agent.get_frontmost_application (opts, cancellable);
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,
@@ -246,12 +252,28 @@ namespace Frida {
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 (Simmy.Application app in yield list_apps_request.wait_async (cancellable))
foreach (var app in apps)
app_ids.add (app.identifier);
icons = yield springboard_agent.fetch_application_icons (app_ids, cancellable);
try {
icons = yield springboard_agent.fetch_application_icons (app_ids, cancellable);
} catch (Error e) {
}
}
var pids = new Gee.HashMap<string, uint> ();
@@ -268,11 +290,11 @@ namespace Frida {
}
var result = new HostApplicationInfo[0];
foreach (Simmy.Application app in yield list_apps_request.wait_async (cancellable)) {
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)
if (scope == FULL && icons != null)
add_app_icons (info.parameters, icons[identifier]);
result += info;
}
@@ -675,10 +697,10 @@ namespace Frida {
return promise.future;
}
public Future<SpawnedProcess> launch_application (string identifier, HostSpawnOptions options) {
public Future<SpawnedProcess> launch_application (string identifier, HostSpawnOptions opts) {
var promise = new Promise<SpawnedProcess> ();
_launch_application (identifier, options, (error_message, process) => {
_launch_application (identifier, opts, (error_message, process) => {
if (error_message != null)
promise.reject (new Error.NOT_SUPPORTED ("%s", error_message));
else
@@ -688,10 +710,10 @@ namespace Frida {
return promise.future;
}
public Future<SpawnedProcess> spawn_program (string program, HostSpawnOptions options) {
public Future<SpawnedProcess> spawn_program (string program, HostSpawnOptions opts) {
var promise = new Promise<SpawnedProcess> ();
_spawn_program (program, options, (error_message, process) => {
_spawn_program (program, opts, (error_message, process) => {
if (error_message != null)
promise.reject (new Error.NOT_SUPPORTED ("%s", error_message));
else
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.4.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 {