mirror of
https://github.com/frida/frida-core.git
synced 2026-11-09 10:09:38 -10:00
Compare commits
No files matched your search
+145
-6
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,29 +11,6 @@ namespace Frida {
|
||||
return (linux_major == major && linux_minor >= minor) || linux_major > major;
|
||||
}
|
||||
|
||||
public class FileDescriptor : Object, FileDescriptorBased {
|
||||
public int handle;
|
||||
|
||||
public FileDescriptor (int handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
~FileDescriptor () {
|
||||
if (handle != -1)
|
||||
Posix.close (handle);
|
||||
}
|
||||
|
||||
public int steal () {
|
||||
int result = handle;
|
||||
handle = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
public int get_fd () {
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PidFileDescriptor : FileDescriptor {
|
||||
private uint pid;
|
||||
|
||||
|
||||
+17
-2
@@ -19,6 +19,13 @@ base_sources = [
|
||||
|
||||
extra_vala_args = []
|
||||
|
||||
if host_os == 'windows'
|
||||
base_sources += 'windows.vala'
|
||||
else
|
||||
base_sources += 'posix.vala'
|
||||
extra_vala_args += '--pkg=posix'
|
||||
endif
|
||||
|
||||
if host_os == 'macos'
|
||||
base_sources += [
|
||||
'xpc.vala',
|
||||
@@ -28,6 +35,7 @@ endif
|
||||
|
||||
if host_os_family == 'darwin'
|
||||
base_sources += 'corefoundation.vala'
|
||||
extra_vala_args += '--pkg=darwin-xnu'
|
||||
endif
|
||||
|
||||
if host_os_family == 'linux'
|
||||
@@ -35,13 +43,15 @@ if host_os_family == 'linux'
|
||||
extra_vala_args += [
|
||||
'--pkg=linux',
|
||||
'--pkg=linux-system',
|
||||
'--pkg=posix',
|
||||
]
|
||||
endif
|
||||
|
||||
if host_os_family == 'freebsd'
|
||||
extra_vala_args += '--pkg=freebsd'
|
||||
endif
|
||||
|
||||
extra_deps = []
|
||||
if host_os_family != 'windows'
|
||||
extra_vala_args += '--pkg=gio-unix-2.0'
|
||||
extra_deps += gio_unix_dep
|
||||
endif
|
||||
|
||||
@@ -49,6 +59,11 @@ base_vala_args = [gum_vala_args, '--pkg=gio-2.0', '--pkg=json-glib-1.0']
|
||||
if nice_dep.found()
|
||||
base_vala_args += '--pkg=nice'
|
||||
endif
|
||||
if host_os_family != 'windows'
|
||||
base_vala_args += [
|
||||
'--pkg=gio-unix-2.0',
|
||||
]
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
base_vala_args += [
|
||||
'--pkg=corefoundation',
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
namespace Frida {
|
||||
public sealed class StdioPipes : Object {
|
||||
public OutputStream? input {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public InputStream output {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public InputStream error {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public StdioPipes (FileDescriptor? input, FileDescriptor output, FileDescriptor error) {
|
||||
Object (
|
||||
input: (input != null) ? new UnixOutputStream (ensure_nonblocking (input.steal ()), true) : null,
|
||||
output: new UnixInputStream (ensure_nonblocking (output.steal ()), true),
|
||||
error: new UnixInputStream (ensure_nonblocking (error.steal ()), true)
|
||||
);
|
||||
}
|
||||
|
||||
private static int ensure_nonblocking (int fd) {
|
||||
try {
|
||||
Unix.set_fd_nonblocking (fd, true);
|
||||
return fd;
|
||||
} catch (GLib.Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FileDescriptor : Object, FileDescriptorBased {
|
||||
public int handle;
|
||||
|
||||
public FileDescriptor (int handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
~FileDescriptor () {
|
||||
if (handle != -1)
|
||||
Posix.close (handle);
|
||||
}
|
||||
|
||||
public int steal () {
|
||||
int result = handle;
|
||||
handle = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
public int get_fd () {
|
||||
return handle;
|
||||
}
|
||||
|
||||
public size_t pread (uint8[] buf, uint64 offset) throws Error {
|
||||
while (true) {
|
||||
ssize_t res = Posix.pread (handle, buf, buf.length, (Posix.off_t) offset);
|
||||
if (res == -1) {
|
||||
if (errno == Posix.EINTR)
|
||||
continue;
|
||||
throw new Error.TRANSPORT ("%s", strerror (errno));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public void pread_all (uint8[] buf, uint64 offset) throws Error {
|
||||
size_t total = 0;
|
||||
|
||||
while (total != buf.length) {
|
||||
void * dst = (uint8 *) buf + total;
|
||||
size_t remaining = buf.length - total;
|
||||
|
||||
while (true) {
|
||||
ssize_t res = Posix.pread (handle, dst, remaining, (Posix.off_t) (offset + total));
|
||||
if (res == -1) {
|
||||
if (errno == Posix.EINTR)
|
||||
continue;
|
||||
throw new Error.TRANSPORT ("%s", strerror (errno));
|
||||
}
|
||||
|
||||
if (res == 0)
|
||||
throw new Error.TRANSPORT ("Unexpected EOF: read %zu of %zu bytes", total, buf.length);
|
||||
|
||||
total += (size_t) res;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public size_t pwrite (uint8[] buf, uint64 offset) throws Error {
|
||||
while (true) {
|
||||
ssize_t res = Posix.pwrite (handle, buf, buf.length, (Posix.off_t) offset);
|
||||
if (res == -1) {
|
||||
if (errno == Posix.EINTR)
|
||||
continue;
|
||||
throw new Error.TRANSPORT ("%s", strerror (errno));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public void pwrite_all (uint8[] buf, uint64 offset) throws Error {
|
||||
size_t total = 0;
|
||||
|
||||
while (total != buf.length) {
|
||||
uint8[] chunk = buf[total : buf.length];
|
||||
|
||||
while (true) {
|
||||
ssize_t res = Posix.pwrite (handle, chunk, chunk.length, (Posix.off_t) (offset + total));
|
||||
if (res == -1) {
|
||||
if (errno == Posix.EINTR)
|
||||
continue;
|
||||
throw new Error.TRANSPORT ("%s", strerror (errno));
|
||||
}
|
||||
|
||||
total += (size_t) res;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public StdioPipes? make_stdio_pipes (Stdio stdio, bool in_supported, out FileDescriptor? in_fd, out string? in_name,
|
||||
out FileDescriptor? out_fd, out string? out_name, out FileDescriptor? err_fd, out string? err_name) throws Error {
|
||||
if (stdio == PIPE) {
|
||||
FileDescriptor? in_write = null;
|
||||
FileDescriptor? out_read, err_read;
|
||||
|
||||
if (in_supported) {
|
||||
make_stdio_pipe (out in_fd, out in_write, out in_name);
|
||||
} else {
|
||||
in_fd = null;
|
||||
in_name = null;
|
||||
}
|
||||
make_stdio_pipe (out out_read, out out_fd, out out_name);
|
||||
make_stdio_pipe (out err_read, out err_fd, out err_name);
|
||||
|
||||
return new StdioPipes (in_write, out_read, err_read);
|
||||
} else {
|
||||
in_fd = null;
|
||||
in_name = null;
|
||||
|
||||
out_fd = null;
|
||||
out_name = null;
|
||||
|
||||
err_fd = null;
|
||||
err_name = null;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void make_stdio_pipe (out FileDescriptor read, out FileDescriptor write, out string? name = null) throws Error {
|
||||
#if HAVE_OPENPTY
|
||||
int rfd = -1, wfd = -1;
|
||||
char buf[Posix.Limits.PATH_MAX];
|
||||
var res =
|
||||
#if DARWIN
|
||||
Darwin.XNU.openpty (out rfd, out wfd, buf, null, null)
|
||||
#elif LINUX
|
||||
Linux.openpty (out rfd, out wfd, buf, null, null)
|
||||
#elif FREEBSD
|
||||
FreeBSD.openpty (out rfd, out wfd, buf, null, null)
|
||||
#endif
|
||||
;
|
||||
if (res == -1)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to open PTY: %s", strerror (errno));
|
||||
name = (string) buf;
|
||||
|
||||
enable_close_on_exec (rfd);
|
||||
enable_close_on_exec (wfd);
|
||||
|
||||
disable_sigpipe (rfd);
|
||||
disable_sigpipe (wfd);
|
||||
|
||||
configure_terminal_attributes (rfd);
|
||||
|
||||
read = new FileDescriptor (rfd);
|
||||
write = new FileDescriptor (wfd);
|
||||
#else
|
||||
name = null;
|
||||
|
||||
try {
|
||||
int fds[2];
|
||||
Unix.open_pipe (fds, Posix.FD_CLOEXEC);
|
||||
|
||||
read = new FileDescriptor (fds[0]);
|
||||
write = new FileDescriptor (fds[1]);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to open pipe: %s", e.message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAVE_OPENPTY
|
||||
private void enable_close_on_exec (int fd) {
|
||||
Posix.fcntl (fd, Posix.F_SETFD, Posix.fcntl (fd, Posix.F_GETFD) | Posix.FD_CLOEXEC);
|
||||
}
|
||||
|
||||
private void disable_sigpipe (int fd) {
|
||||
#if DARWIN
|
||||
Posix.fcntl (fd, Darwin.XNU.F_SETNOSIGPIPE, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void configure_terminal_attributes (int fd) {
|
||||
var tios = Posix.termios ();
|
||||
Posix.tcgetattr (fd, out tios);
|
||||
|
||||
tios.c_oflag &= ~Posix.ONLCR;
|
||||
tios.c_cflag = (tios.c_cflag & Posix.CLOCAL) | Posix.CS8 | Posix.CREAD | Posix.HUPCL;
|
||||
tios.c_lflag &= ~Posix.ECHO;
|
||||
|
||||
Posix.tcsetattr (fd, 0, tios);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -57,6 +57,22 @@ namespace Frida {
|
||||
push_scope (v);
|
||||
}
|
||||
|
||||
public string[] list_members () throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
|
||||
|
||||
var members = new Gee.ArrayList<string> ();
|
||||
foreach (var item in scope.val) {
|
||||
string k;
|
||||
Variant v;
|
||||
item.get ("{sv}", out k, out v);
|
||||
|
||||
members.add (k);
|
||||
}
|
||||
return members.to_array ();
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Frida {
|
||||
public sealed class StdioPipes : Object {
|
||||
public OutputStream input {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public InputStream output {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public InputStream error {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public StdioPipes (OutputStream input, InputStream output, InputStream error) {
|
||||
Object (input: input, output: output, error: error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,7 @@
|
||||
namespace Frida {
|
||||
#if DARWIN
|
||||
public extern void libc_shim_deinit ();
|
||||
#endif
|
||||
public extern void run_atexit_handlers ();
|
||||
|
||||
public extern uint get_process_id ();
|
||||
|
||||
+45
-12
@@ -32,18 +32,34 @@ frida_version = get_option('frida_version')
|
||||
if frida_version == ''
|
||||
frida_version = run_command(releng / 'frida_version.py', meson.project_source_root(), check: true).stdout().strip()
|
||||
endif
|
||||
tokens = frida_version.split('.')
|
||||
frida_major_version = tokens[0].to_int()
|
||||
frida_minor_version = tokens[1].to_int()
|
||||
if tokens.length() == 4
|
||||
frida_micro_version = tokens[2].split('-')[0].to_int() - 1
|
||||
frida_nano_version = tokens[3].to_int() + 1
|
||||
|
||||
is_dev = frida_version.contains('-dev.')
|
||||
if is_dev
|
||||
dev_parts = frida_version.split('-dev.')
|
||||
assert(dev_parts.length() == 2)
|
||||
prefix = dev_parts[0]
|
||||
frida_nano_version = dev_parts[1].to_int() + 1
|
||||
else
|
||||
assert(tokens.length() == 3)
|
||||
frida_micro_version = tokens[2].to_int()
|
||||
prefix = frida_version
|
||||
frida_nano_version = 0
|
||||
endif
|
||||
frida_profile = (frida_version != '0.0.0' and frida_nano_version == 0) ? 'prod' : 'dev'
|
||||
|
||||
parts = prefix.split('-')
|
||||
core = parts[0]
|
||||
has_suffix = parts.length() > 1
|
||||
|
||||
core_tokens = core.split('.')
|
||||
assert(core_tokens.length() == 3)
|
||||
|
||||
frida_major_version = core_tokens[0].to_int()
|
||||
frida_minor_version = core_tokens[1].to_int()
|
||||
micro_encoded = core_tokens[2].to_int()
|
||||
|
||||
if is_dev and not has_suffix
|
||||
frida_micro_version = micro_encoded - 1
|
||||
else
|
||||
frida_micro_version = micro_encoded
|
||||
endif
|
||||
|
||||
api_version = '1.0'
|
||||
|
||||
@@ -92,6 +108,12 @@ elif host_machine.cpu_family() == 'mips64'
|
||||
else
|
||||
host_abi = 'mips64'
|
||||
endif
|
||||
elif host_machine.cpu_family() == 'riscv32'
|
||||
host_arch = 'riscv'
|
||||
host_abi = 'riscv32'
|
||||
elif host_machine.cpu_family() == 'riscv64'
|
||||
host_arch = 'riscv'
|
||||
host_abi = 'riscv64'
|
||||
else
|
||||
host_arch = host_machine.cpu_family()
|
||||
host_abi = host_arch
|
||||
@@ -236,6 +258,7 @@ cpu_defines = [
|
||||
['arm', 'HAVE_ARM'],
|
||||
['arm64', 'HAVE_ARM64'],
|
||||
['mips', 'HAVE_MIPS'],
|
||||
['riscv', 'HAVE_RISCV'],
|
||||
]
|
||||
foreach d : cpu_defines
|
||||
if d.get(0) == host_arch
|
||||
@@ -292,6 +315,7 @@ gum_dep = dependency('frida-gum-1.0', default_options: [
|
||||
gumjs_dep = dependency('frida-gumjs-1.0')
|
||||
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0')
|
||||
|
||||
glib_version_req = '>=2.74'
|
||||
glib_options = [
|
||||
'cocoa=disabled',
|
||||
'selinux=disabled',
|
||||
@@ -315,7 +339,7 @@ if optimize_for_prod
|
||||
gee_options += 'disable-internal-asserts=true'
|
||||
endif
|
||||
|
||||
glib_dep = dependency('glib-2.0', version: '>=2.72', default_options: glib_options)
|
||||
glib_dep = dependency('glib-2.0', version: glib_version_req, default_options: glib_options)
|
||||
gobject_dep = dependency('gobject-2.0')
|
||||
gmodule_dep = dependency('gmodule-2.0')
|
||||
gio_dep = dependency('gio-2.0')
|
||||
@@ -346,7 +370,7 @@ quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), de
|
||||
])
|
||||
lzfse_dep = dependency('lzfse', required: get_option('barebone_backend'))
|
||||
|
||||
native_glib_dep = dependency('glib-2.0', version: '>=2.74', native: true, default_options: glib_options)
|
||||
native_glib_dep = dependency('glib-2.0', version: glib_version_req, native: true, default_options: glib_options)
|
||||
native_gio_dep = dependency('gio-2.0', native: true)
|
||||
native_gee_dep = dependency('gee-0.8', native: true, default_options: gee_options)
|
||||
native_brotlienc_dep = dependency('libbrotlienc', native: true)
|
||||
@@ -403,6 +427,10 @@ else
|
||||
endif
|
||||
|
||||
have_local_backend = get_option('local_backend').allowed()
|
||||
have_simmy_backend = get_option('simmy_backend') \
|
||||
.disable_auto_if(host_os != 'macos') \
|
||||
.require(host_os == 'macos', error_message: 'Simmy backend is only available on macOS') \
|
||||
.allowed()
|
||||
have_fruity_backend = get_option('fruity_backend') \
|
||||
.disable_auto_if(not host_docks_mobile_devices) \
|
||||
.allowed()
|
||||
@@ -491,6 +519,7 @@ build_tests = get_option('tests') \
|
||||
.allowed()
|
||||
|
||||
foreach b : [['local', have_local_backend],
|
||||
['simmy', have_simmy_backend],
|
||||
['fruity', have_fruity_backend],
|
||||
['droidy', have_droidy_backend],
|
||||
['socket', have_socket_backend],
|
||||
@@ -581,7 +610,7 @@ if lzfse_dep.found()
|
||||
backend_reqs_private += 'lzfse'
|
||||
endif
|
||||
|
||||
if host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>')
|
||||
if host_os_family in ['darwin', 'freebsd'] or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
|
||||
vala_flags += '--define=HAVE_OPENPTY'
|
||||
endif
|
||||
|
||||
@@ -653,6 +682,9 @@ endif
|
||||
if host_os in ['ios', 'tvos']
|
||||
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
|
||||
endif
|
||||
if host_os_family == 'freebsd'
|
||||
backend_libs_private += ['-lutil']
|
||||
endif
|
||||
|
||||
if get_option('devkits').length() != 0
|
||||
if get_option('default_library') != 'static'
|
||||
@@ -786,6 +818,7 @@ endif
|
||||
summary(
|
||||
{
|
||||
'local': have_local_backend,
|
||||
'simmy': have_simmy_backend,
|
||||
'fruity': have_fruity_backend,
|
||||
'droidy': have_droidy_backend,
|
||||
'socket': have_socket_backend,
|
||||
|
||||
@@ -10,6 +10,12 @@ option('local_backend',
|
||||
description: 'Include backend for the local system',
|
||||
)
|
||||
|
||||
option('simmy_backend',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Include the iDevice Simulator backend',
|
||||
)
|
||||
|
||||
option('fruity_backend',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
|
||||
+1
-1
Submodule releng updated: c24becede0...60585cf5cf.
@@ -1,11 +1,15 @@
|
||||
ANDROID_API_LEVEL ?= 21
|
||||
ANDROID_SDK_ROOT ?= $(shell echo ~/Library/Android/sdk)
|
||||
ANDROID_API_LEVEL ?= 19
|
||||
ANDROID_SDK_ROOT ?= $(shell echo ~/Android/Sdk)
|
||||
|
||||
android_platform := android-$(ANDROID_API_LEVEL)
|
||||
android_jar := $(ANDROID_SDK_ROOT)/platforms/$(android_platform)/android.jar
|
||||
|
||||
build/frida-helper.dex: build/frida-helper.jar
|
||||
dx --dex --output=$@ $<
|
||||
helper.dex: build/frida-helper.jar
|
||||
d8 \
|
||||
--classpath $(android_jar) \
|
||||
--output build/ \
|
||||
$<
|
||||
mv build/classes.dex $@
|
||||
|
||||
build/frida-helper.jar: re/frida/Helper.java
|
||||
javac \
|
||||
@@ -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.
@@ -0,0 +1,2 @@
|
||||
backend_sources += files('client.vala')
|
||||
android_helper_dex = files('helper.dex')
|
||||
File diff suppressed because it is too large.
Load diff
File renamed without changes.
+3
-1
@@ -238,7 +238,7 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
|
||||
for elem in core_elements + base_elements:
|
||||
if tag_name == "class":
|
||||
for child in list(elem):
|
||||
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") == "FridaBase.AgentMessageSink") \
|
||||
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in {"Frida.HostSessionHub", "FridaBase.AgentMessageSink"}) \
|
||||
or child.tag == CORE_TAG_FIELD \
|
||||
or child.get("name").startswith("_"):
|
||||
elem.remove(child)
|
||||
@@ -512,6 +512,8 @@ def function_is_public(name):
|
||||
"parse_socket_address",
|
||||
"negotiate_connection",
|
||||
"check_kernel_version",
|
||||
"make_stdio_pipes",
|
||||
"make_stdio_pipe",
|
||||
]
|
||||
|
||||
def parse_vala_object_types(source) -> List[ApiObjectType]:
|
||||
|
||||
@@ -40,7 +40,8 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
if (host_session != null)
|
||||
throw new Error.INVALID_OPERATION ("Already created");
|
||||
|
||||
|
||||
+39
-3
@@ -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,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module github.com/frida/frida-symbol-replacer
|
||||
|
||||
go 1.24.4
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -845,8 +845,8 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public LaunchdAgent (DarwinHostSession host_session, Cancellable io_cancellable) {
|
||||
Object (host_session: host_session, io_cancellable: io_cancellable);
|
||||
public LaunchdAgent (HostSessionConnection connection, Cancellable io_cancellable) {
|
||||
Object (connection: connection, io_cancellable: io_cancellable);
|
||||
}
|
||||
|
||||
construct {
|
||||
@@ -905,7 +905,7 @@ namespace Frida {
|
||||
|
||||
try {
|
||||
if (path == XPC_PROXY_PATH) {
|
||||
var agent = new XpcProxyAgent ((DarwinHostSession) host_session, identifier, pid);
|
||||
var agent = new XpcProxyAgent (connection, identifier, pid);
|
||||
yield agent.run_until_exec (io_cancellable);
|
||||
}
|
||||
|
||||
@@ -921,7 +921,7 @@ namespace Frida {
|
||||
|
||||
try {
|
||||
if (path == XPC_PROXY_PATH) {
|
||||
var agent = new XpcProxyAgent ((DarwinHostSession) host_session, identifier, pid);
|
||||
var agent = new XpcProxyAgent (connection, identifier, pid);
|
||||
yield agent.run_until_exec (io_cancellable);
|
||||
}
|
||||
|
||||
@@ -937,7 +937,8 @@ namespace Frida {
|
||||
|
||||
protected override async string? load_source (Cancellable? cancellable) throws Error, IOError {
|
||||
string * raw_source = Frida.Data.Darwin.get_launchd_js_blob ().data;
|
||||
return raw_source->replace ("@REPORT_CRASHES@", ((DarwinHostSession) host_session).report_crashes.to_string ());
|
||||
return raw_source->replace ("@REPORT_CRASHES@",
|
||||
((DarwinHostSession) connection.host_session).report_crashes.to_string ());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -952,8 +953,8 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public XpcProxyAgent (DarwinHostSession host_session, string identifier, uint pid) {
|
||||
Object (host_session: host_session, identifier: identifier, pid: pid);
|
||||
public XpcProxyAgent (HostSessionConnection connection, string identifier, uint pid) {
|
||||
Object (connection: connection, identifier: identifier, pid: pid);
|
||||
}
|
||||
|
||||
construct {
|
||||
@@ -965,7 +966,7 @@ namespace Frida {
|
||||
public async void run_until_exec (Cancellable? cancellable) throws Error, IOError {
|
||||
yield ensure_loaded (cancellable);
|
||||
|
||||
var helper = ((DarwinHostSession) host_session).helper;
|
||||
var helper = ((DarwinHostSession) connection.host_session).helper;
|
||||
yield helper.resume (pid, cancellable);
|
||||
|
||||
yield wait_for_unload (cancellable);
|
||||
@@ -1002,10 +1003,10 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public ReportCrashAgent (DarwinHostSession host_session, uint pid, MappedAgentContainer mapped_agent_container,
|
||||
public ReportCrashAgent (HostSessionConnection connection, uint pid, MappedAgentContainer mapped_agent_container,
|
||||
Cancellable io_cancellable) {
|
||||
Object (
|
||||
host_session: host_session,
|
||||
connection: connection,
|
||||
pid: pid,
|
||||
mapped_agent_container: mapped_agent_container,
|
||||
io_cancellable: io_cancellable
|
||||
@@ -1226,9 +1227,9 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public OSAnalyticsAgent (DarwinHostSession host_session, uint pid, Cancellable io_cancellable) {
|
||||
public OSAnalyticsAgent (HostSessionConnection connection, uint pid, Cancellable io_cancellable) {
|
||||
Object (
|
||||
host_session: host_session,
|
||||
connection: connection,
|
||||
pid: pid,
|
||||
io_cancellable: io_cancellable
|
||||
);
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <unistd.h>
|
||||
#include <util.h>
|
||||
|
||||
#ifndef _POSIX_SPAWN_DISABLE_ASLR
|
||||
# define _POSIX_SPAWN_DISABLE_ASLR 0x0100
|
||||
@@ -181,6 +180,7 @@ struct _FridaSpawnInstance
|
||||
mach_vm_address_t dyld_data;
|
||||
|
||||
GumAddress modern_entry_address;
|
||||
GumAddress sim_notify_address;
|
||||
|
||||
/* V4+ */
|
||||
GumAddress notify_objc_init;
|
||||
@@ -405,9 +405,6 @@ static void frida_spawn_instance_disable_nth_breakpoint (FridaSpawnInstance * se
|
||||
static guint32 frida_spawn_instance_put_software_breakpoint (FridaSpawnInstance * self, GumAddress where, guint index);
|
||||
static guint32 frida_spawn_instance_overwrite_arm64_instruction (FridaSpawnInstance * self, GumAddress address, guint32 new_instruction);
|
||||
|
||||
static void frida_make_pty (int fds[2]);
|
||||
static void frida_configure_terminal_attributes (gint fd);
|
||||
|
||||
static FridaInjectInstance * frida_inject_instance_new (FridaDarwinHelperBackend * backend, guint id, guint pid);
|
||||
static FridaInjectInstance * frida_inject_instance_clone (const FridaInjectInstance * instance, guint id);
|
||||
static void frida_inject_instance_close (FridaInjectInstance * instance);
|
||||
@@ -804,12 +801,17 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
|
||||
posix_spawnattr_t attributes;
|
||||
sigset_t signal_mask_set;
|
||||
short flags;
|
||||
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
|
||||
FridaFileDescriptor * in_fd = NULL;
|
||||
FridaFileDescriptor * out_fd = NULL;
|
||||
FridaFileDescriptor * err_fd = NULL;
|
||||
GError * stdio_error = NULL;
|
||||
FridaAslr aslr = FRIDA_ASLR_AUTO;
|
||||
GVariant * aslr_value;
|
||||
gchar * old_cwd = NULL;
|
||||
int result, spawn_errno;
|
||||
|
||||
*pipes = NULL;
|
||||
|
||||
instance = frida_spawn_instance_new (self);
|
||||
|
||||
argv = frida_host_spawn_options_compute_argv (options, path, NULL);
|
||||
@@ -823,33 +825,13 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
|
||||
|
||||
flags = POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_START_SUSPENDED;
|
||||
|
||||
switch (options->stdio)
|
||||
{
|
||||
case FRIDA_STDIO_INHERIT:
|
||||
*pipes = NULL;
|
||||
*pipes = frida_make_stdio_pipes (options->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &stdio_error);
|
||||
if (stdio_error != NULL)
|
||||
goto propagate_stdio_error;
|
||||
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, 0, 0);
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, 1, 1);
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, 2, 2);
|
||||
|
||||
break;
|
||||
|
||||
case FRIDA_STDIO_PIPE:
|
||||
frida_make_pty (stdin_pipe);
|
||||
frida_make_pty (stdout_pipe);
|
||||
frida_make_pty (stderr_pipe);
|
||||
|
||||
*pipes = frida_stdio_pipes_new (stdin_pipe[1], stdout_pipe[0], stderr_pipe[0]);
|
||||
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, stdin_pipe[0], 0);
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, stdout_pipe[1], 1);
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, stderr_pipe[1], 2);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, (in_fd != NULL) ? in_fd->handle : 0, 0);
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, (out_fd != NULL) ? out_fd->handle : 1, 1);
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, (err_fd != NULL) ? err_fd->handle : 2, 2);
|
||||
|
||||
aslr_value = g_hash_table_lookup (options->aux, "aslr");
|
||||
if (aslr_value != NULL && !frida_parse_aslr_option (aslr_value, &aslr, error))
|
||||
@@ -872,13 +854,6 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
|
||||
if (old_cwd != NULL)
|
||||
chdir (old_cwd);
|
||||
|
||||
if (options->stdio == FRIDA_STDIO_PIPE)
|
||||
{
|
||||
close (stdin_pipe[0]);
|
||||
close (stdout_pipe[1]);
|
||||
close (stderr_pipe[1]);
|
||||
}
|
||||
|
||||
posix_spawnattr_destroy (&attributes);
|
||||
|
||||
posix_spawn_file_actions_destroy (&file_actions);
|
||||
@@ -892,6 +867,11 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
|
||||
|
||||
goto beach;
|
||||
|
||||
propagate_stdio_error:
|
||||
{
|
||||
g_propagate_error (error, stdio_error);
|
||||
goto early_failure;
|
||||
}
|
||||
chdir_failed:
|
||||
{
|
||||
g_set_error (error,
|
||||
@@ -936,12 +916,18 @@ any_failure:
|
||||
kill (instance->pid, SIGKILL);
|
||||
frida_spawn_instance_close (instance);
|
||||
|
||||
g_clear_object (pipes);
|
||||
|
||||
pid = 0;
|
||||
|
||||
goto beach;
|
||||
}
|
||||
beach:
|
||||
{
|
||||
g_clear_object (&in_fd);
|
||||
g_clear_object (&out_fd);
|
||||
g_clear_object (&err_fd);
|
||||
|
||||
g_free (old_cwd);
|
||||
g_strfreev (envp);
|
||||
g_strfreev (argv);
|
||||
@@ -1057,6 +1043,8 @@ frida_darwin_helper_backend_launch_using_fbs (NSString * identifier, NSURL * url
|
||||
FridaSpringboardApi * api;
|
||||
NSMutableDictionary * debug_options, * open_options;
|
||||
FridaStdioPipes * pipes = NULL;
|
||||
gchar * stdout_name = NULL;
|
||||
gchar * stderr_name = NULL;
|
||||
GError * error = NULL;
|
||||
FridaAslr aslr = FRIDA_ASLR_AUTO;
|
||||
GVariant * aslr_value;
|
||||
@@ -1092,22 +1080,12 @@ frida_darwin_helper_backend_launch_using_fbs (NSString * identifier, NSURL * url
|
||||
if (strlen (spawn_options->cwd) > 0)
|
||||
goto cwd_not_supported;
|
||||
|
||||
pipes = frida_make_stdio_pipes (spawn_options->stdio, FALSE, NULL, NULL, NULL, &stdout_name, NULL, &stderr_name, &error);
|
||||
if (error != NULL)
|
||||
goto failure;
|
||||
|
||||
if (spawn_options->stdio == FRIDA_STDIO_PIPE)
|
||||
{
|
||||
gint stdout_master, stdout_slave, stderr_master, stderr_slave;
|
||||
gchar stdout_name[PATH_MAX], stderr_name[PATH_MAX];
|
||||
|
||||
openpty (&stdout_master, &stdout_slave, stdout_name, NULL, NULL);
|
||||
openpty (&stderr_master, &stderr_slave, stderr_name, NULL, NULL);
|
||||
|
||||
pipes = frida_stdio_pipes_new (-1, stdout_master, stderr_master);
|
||||
|
||||
frida_configure_terminal_attributes (stdout_master);
|
||||
frida_configure_terminal_attributes (stderr_master);
|
||||
|
||||
frida_stdio_pipes_retain (pipes, stdout_slave);
|
||||
frida_stdio_pipes_retain (pipes, stderr_slave);
|
||||
|
||||
chmod (stdout_name, 0666);
|
||||
chmod (stderr_name, 0666);
|
||||
|
||||
@@ -1174,7 +1152,7 @@ frida_darwin_helper_backend_launch_using_fbs (NSString * identifier, NSURL * url
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
goto beach;
|
||||
|
||||
envp_not_supported:
|
||||
{
|
||||
@@ -1195,6 +1173,16 @@ cwd_not_supported:
|
||||
failure:
|
||||
{
|
||||
on_complete (NULL, error, on_complete_target);
|
||||
|
||||
g_clear_object (&pipes);
|
||||
|
||||
goto beach;
|
||||
}
|
||||
beach:
|
||||
{
|
||||
g_free (stdout_name);
|
||||
g_free (stderr_name);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1881,7 +1869,7 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
|
||||
mach_msg_type_number_t state_count = GUM_DARWIN_THREAD_STATE_COUNT;
|
||||
thread_state_flavor_t state_flavor = GUM_DARWIN_THREAD_STATE_FLAVOR;
|
||||
GumAddress dyld_start, dyld_granularity, dyld_chunk, dyld_header;
|
||||
GumAddress modern_entry_address, legacy_entry_address;
|
||||
GumAddress modern_entry_address, legacy_entry_address, sim_notify_address;
|
||||
const gchar * launch_with_closure_names[] = {
|
||||
"__ZN4dyldL17launchWithClosureEPKN5dyld37closure13LaunchClosureEPK15DyldSharedCachePKNS0_11MachOLoadedEmiPPKcSD_SD_R11DiagnosticsPmSG_PbSH_",
|
||||
"__ZN4dyldL17launchWithClosureEPKN5dyld312launch_cache13binary_format7ClosureEPK15DyldSharedCachePK11mach_headermiPPKcSE_SE_PmSF_",
|
||||
@@ -2101,6 +2089,9 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
|
||||
instance->ret_gadget |= 1;
|
||||
}
|
||||
|
||||
sim_notify_address = gum_darwin_module_resolve_symbol_address (dyld, "__ZN5dyld4L29sim_notifyMonitorOfMainCalledEv");
|
||||
instance->sim_notify_address = sim_notify_address;
|
||||
|
||||
instance->ret_state = FRIDA_RET_FROM_HELPER;
|
||||
|
||||
kr = frida_get_debug_state (child_thread, &instance->previous_debug_state, instance->cpu_type);
|
||||
@@ -2112,6 +2103,8 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
|
||||
frida_spawn_instance_set_nth_breakpoint (instance, i++, legacy_entry_address, FRIDA_BREAKPOINT_REPEAT_ALWAYS);
|
||||
if (modern_entry_address != 0)
|
||||
frida_spawn_instance_set_nth_breakpoint (instance, i++, modern_entry_address, FRIDA_BREAKPOINT_REPEAT_ALWAYS);
|
||||
if (sim_notify_address != 0)
|
||||
frida_spawn_instance_set_nth_breakpoint (instance, i++, sim_notify_address, FRIDA_BREAKPOINT_REPEAT_ALWAYS);
|
||||
if (instance->dyld_flavor == FRIDA_DYLD_V4_PLUS)
|
||||
{
|
||||
GumAddress restart_with_dyld_in_cache = gum_darwin_module_resolve_symbol_address (dyld,
|
||||
@@ -3104,7 +3097,11 @@ frida_spawn_instance_handle_breakpoint (FridaSpawnInstance * self, FridaBreakpoi
|
||||
|
||||
if (self->breakpoint_phase == FRIDA_BREAKPOINT_DETECT_FLAVOR)
|
||||
{
|
||||
if (self->dyld_flavor == FRIDA_DYLD_V4_PLUS)
|
||||
if (pc == self->sim_notify_address)
|
||||
{
|
||||
self->breakpoint_phase = FRIDA_BREAKPOINT_LIBSYSTEM_INITIALIZED;
|
||||
}
|
||||
else if (self->dyld_flavor == FRIDA_DYLD_V4_PLUS)
|
||||
{
|
||||
if (pc == self->modern_entry_address)
|
||||
{
|
||||
@@ -4065,44 +4062,6 @@ frida_spawn_instance_overwrite_arm64_instruction (FridaSpawnInstance * self, Gum
|
||||
return original_instruction;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_make_pty (int fds[2])
|
||||
{
|
||||
gboolean pipe_opened;
|
||||
int i;
|
||||
|
||||
pipe_opened = openpty (&fds[0], &fds[1], NULL, NULL, NULL) != -1;
|
||||
g_assert (pipe_opened);
|
||||
|
||||
for (i = 0; i != 2; i++)
|
||||
{
|
||||
const int fd = fds[i];
|
||||
int res;
|
||||
|
||||
res = fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC);
|
||||
g_assert (res == 0);
|
||||
|
||||
res = fcntl (fd, F_SETNOSIGPIPE, TRUE);
|
||||
g_assert (res == 0);
|
||||
}
|
||||
|
||||
frida_configure_terminal_attributes (fds[0]);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_configure_terminal_attributes (gint fd)
|
||||
{
|
||||
struct termios tios;
|
||||
|
||||
tcgetattr (fd, &tios);
|
||||
|
||||
tios.c_oflag &= ~ONLCR;
|
||||
tios.c_cflag = (tios.c_cflag & CLOCAL) | CS8 | CREAD | HUPCL;
|
||||
tios.c_lflag &= ~ECHO;
|
||||
|
||||
tcsetattr (fd, 0, &tios);
|
||||
}
|
||||
|
||||
static FridaInjectInstance *
|
||||
frida_inject_instance_new (FridaDarwinHelperBackend * backend, guint id, guint pid)
|
||||
{
|
||||
|
||||
@@ -136,9 +136,9 @@ namespace Frida {
|
||||
ChildWatch.add ((Pid) child_pid, on_child_dead);
|
||||
|
||||
if (pipes != null) {
|
||||
stdin_streams[child_pid] = new UnixOutputStream (pipes.input, false);
|
||||
process_next_output_from.begin (new UnixInputStream (pipes.output, false), child_pid, 1, pipes);
|
||||
process_next_output_from.begin (new UnixInputStream (pipes.error, false), child_pid, 2, pipes);
|
||||
stdin_streams[child_pid] = pipes.input;
|
||||
process_next_output_from.begin (pipes.output, child_pid, 1, pipes);
|
||||
process_next_output_from.begin (pipes.error, child_pid, 2, pipes);
|
||||
}
|
||||
|
||||
return child_pid;
|
||||
@@ -180,10 +180,8 @@ namespace Frida {
|
||||
|
||||
var pipes = pending.pipes;
|
||||
if (pipes != null) {
|
||||
pipes.clear_retained ();
|
||||
|
||||
process_next_output_from.begin (new UnixInputStream (pipes.output, false), pid, 1, pipes);
|
||||
process_next_output_from.begin (new UnixInputStream (pipes.error, false), pid, 2, pipes);
|
||||
process_next_output_from.begin (pipes.output, pid, 1, pipes);
|
||||
process_next_output_from.begin (pipes.error, pid, 2, pipes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,8 +437,11 @@ namespace Frida {
|
||||
|
||||
private bool is_booting (uint task) throws Error {
|
||||
Gum.Darwin.AllImageInfos infos;
|
||||
if (!Gum.Darwin.query_all_image_infos (task, out infos))
|
||||
throw new Error.PROCESS_NOT_FOUND ("Target process died unexpectedly");
|
||||
try {
|
||||
Gum.Darwin.query_all_image_infos (task, out infos);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.PROCESS_NOT_FOUND ("%s", e.message);
|
||||
}
|
||||
|
||||
return !infos.libsystem_initialized;
|
||||
}
|
||||
@@ -888,57 +889,4 @@ namespace Frida {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StdioPipes : Object {
|
||||
public int input {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public int output {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public int error {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private int[] retained = {};
|
||||
|
||||
public StdioPipes (int input, int output, int error) {
|
||||
Object (input: input, output: output, error: error);
|
||||
}
|
||||
|
||||
construct {
|
||||
try {
|
||||
if (input != -1)
|
||||
Unix.set_fd_nonblocking (input, true);
|
||||
Unix.set_fd_nonblocking (output, true);
|
||||
Unix.set_fd_nonblocking (error, true);
|
||||
} catch (GLib.Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
~StdioPipes () {
|
||||
clear_retained ();
|
||||
|
||||
if (input != -1)
|
||||
Posix.close (input);
|
||||
Posix.close (output);
|
||||
Posix.close (error);
|
||||
}
|
||||
|
||||
public void clear_retained () {
|
||||
foreach (var fd in retained)
|
||||
Posix.close (fd);
|
||||
retained = {};
|
||||
}
|
||||
|
||||
public void retain (int fd) {
|
||||
retained += fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}"));
|
||||
|
||||
+23
-1394
File diff suppressed because it is too large.
Load diff
@@ -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;
|
||||
}
|
||||
@@ -172,8 +172,6 @@ static gboolean frida_exec_instance_try_perform_transition (FridaExecInstance *
|
||||
static void frida_exec_instance_suspend (FridaExecInstance * self);
|
||||
static void frida_exec_instance_resume (FridaExecInstance * self);
|
||||
|
||||
static void frida_make_pipe (int fds[2]);
|
||||
|
||||
static FridaInjectInstance * frida_inject_instance_new (FridaBinjector * binjector, guint id, guint pid, const FridaRemoteApi * api,
|
||||
const gchar * temp_path);
|
||||
static void frida_inject_instance_recreate_fifo (FridaInjectInstance * self);
|
||||
@@ -211,7 +209,10 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
|
||||
{
|
||||
FridaSpawnInstance * instance;
|
||||
gchar ** argv, ** envp;
|
||||
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
|
||||
FridaFileDescriptor * in_fd = NULL;
|
||||
FridaFileDescriptor * out_fd = NULL;
|
||||
FridaFileDescriptor * err_fd = NULL;
|
||||
GError * stdio_error = NULL;
|
||||
gchar * old_cwd = NULL;
|
||||
gboolean success;
|
||||
const gchar * failed_operation;
|
||||
@@ -221,24 +222,9 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
|
||||
argv = frida_host_spawn_options_compute_argv (options, path, NULL);
|
||||
envp = frida_host_spawn_options_compute_envp (options, NULL);
|
||||
|
||||
switch (options->stdio)
|
||||
{
|
||||
case FRIDA_STDIO_INHERIT:
|
||||
*pipes = NULL;
|
||||
break;
|
||||
|
||||
case FRIDA_STDIO_PIPE:
|
||||
frida_make_pipe (stdin_pipe);
|
||||
frida_make_pipe (stdout_pipe);
|
||||
frida_make_pipe (stderr_pipe);
|
||||
|
||||
*pipes = frida_stdio_pipes_new (stdin_pipe[1], stdout_pipe[0], stderr_pipe[0]);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
*pipes = frida_make_stdio_pipes (options->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &stdio_error);
|
||||
if (stdio_error != NULL)
|
||||
goto propagate_stdio_error;
|
||||
|
||||
if (strlen (options->cwd) > 0)
|
||||
{
|
||||
@@ -254,9 +240,9 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
|
||||
|
||||
if (options->stdio == FRIDA_STDIO_PIPE)
|
||||
{
|
||||
dup2 (stdin_pipe[0], 0);
|
||||
dup2 (stdout_pipe[1], 1);
|
||||
dup2 (stderr_pipe[1], 2);
|
||||
dup2 (in_fd->handle, 0);
|
||||
dup2 (out_fd->handle, 1);
|
||||
dup2 (err_fd->handle, 2);
|
||||
}
|
||||
|
||||
ptrace (PT_TRACE_ME, 0, NULL, 0);
|
||||
@@ -273,13 +259,6 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
|
||||
g_warning ("Failed to restore working directory");
|
||||
}
|
||||
|
||||
if (options->stdio == FRIDA_STDIO_PIPE)
|
||||
{
|
||||
close (stdin_pipe[0]);
|
||||
close (stdout_pipe[1]);
|
||||
close (stderr_pipe[1]);
|
||||
}
|
||||
|
||||
success = frida_wait_for_child_signal (instance->pid, SIGTRAP, NULL);
|
||||
CHECK_OS_RESULT (success, !=, FALSE, "wait(SIGTRAP)");
|
||||
|
||||
@@ -290,6 +269,11 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
|
||||
|
||||
goto beach;
|
||||
|
||||
propagate_stdio_error:
|
||||
{
|
||||
g_propagate_error (error, stdio_error);
|
||||
goto failure;
|
||||
}
|
||||
chdir_failed:
|
||||
{
|
||||
g_set_error (error,
|
||||
@@ -316,6 +300,10 @@ failure:
|
||||
}
|
||||
beach:
|
||||
{
|
||||
g_clear_object (&in_fd);
|
||||
g_clear_object (&out_fd);
|
||||
g_clear_object (&err_fd);
|
||||
|
||||
g_free (old_cwd);
|
||||
g_strfreev (envp);
|
||||
g_strfreev (argv);
|
||||
@@ -736,12 +724,6 @@ frida_exec_instance_resume (FridaExecInstance * self)
|
||||
ptrace (PT_DETACH, self->pid, NULL, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_make_pipe (int fds[2])
|
||||
{
|
||||
g_unix_open_pipe (fds, FD_CLOEXEC, NULL);
|
||||
}
|
||||
|
||||
static FridaInjectInstance *
|
||||
frida_inject_instance_new (FridaBinjector * binjector, guint id, guint pid, const FridaRemoteApi * api, const gchar * temp_path)
|
||||
{
|
||||
|
||||
@@ -68,9 +68,9 @@ namespace Frida {
|
||||
monitor_child (child_pid);
|
||||
|
||||
if (pipes != null) {
|
||||
stdin_streams[child_pid] = new UnixOutputStream (pipes.input, false);
|
||||
process_next_output_from.begin (new UnixInputStream (pipes.output, false), child_pid, 1, pipes);
|
||||
process_next_output_from.begin (new UnixInputStream (pipes.error, false), child_pid, 2, pipes);
|
||||
stdin_streams[child_pid] = pipes.input;
|
||||
process_next_output_from.begin (pipes.output, child_pid, 1, pipes);
|
||||
process_next_output_from.begin (pipes.error, child_pid, 2, pipes);
|
||||
}
|
||||
|
||||
return child_pid;
|
||||
@@ -696,41 +696,4 @@ namespace Frida {
|
||||
protected enum ProgressMessageType {
|
||||
HELLO = 0xff
|
||||
}
|
||||
|
||||
protected class StdioPipes : Object {
|
||||
public int input {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public int output {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public int error {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public StdioPipes (int input, int output, int error) {
|
||||
Object (input: input, output: output, error: error);
|
||||
}
|
||||
|
||||
construct {
|
||||
try {
|
||||
Unix.set_fd_nonblocking (input, true);
|
||||
Unix.set_fd_nonblocking (output, true);
|
||||
Unix.set_fd_nonblocking (error, true);
|
||||
} catch (GLib.Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
~StdioPipes () {
|
||||
Posix.close (input);
|
||||
Posix.close (output);
|
||||
Posix.close (error);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -24,7 +24,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DeviceManager : Object {
|
||||
public sealed class DeviceManager : Object, HostSessionHub {
|
||||
public signal void added (Device device);
|
||||
public signal void removed (Device device);
|
||||
public signal void changed ();
|
||||
@@ -394,6 +394,12 @@ namespace Frida {
|
||||
changed ();
|
||||
}
|
||||
|
||||
private async HostSessionEntry resolve_host_session (string id, Cancellable? cancellable) throws Error, IOError {
|
||||
var device = yield get_device_by_id (id, 0, cancellable);
|
||||
var session = yield device.get_host_session (cancellable);
|
||||
return new HostSessionEntry (device.provider, session);
|
||||
}
|
||||
|
||||
private void check_open () throws Error {
|
||||
if (stop_request != null)
|
||||
throw new Error.INVALID_OPERATION ("Device manager is closed");
|
||||
@@ -1326,7 +1332,7 @@ namespace Frida {
|
||||
host_session_request = new Promise<HostSession> ();
|
||||
|
||||
try {
|
||||
var session = yield provider.create (host_session_options, cancellable);
|
||||
var session = yield provider.create (manager, host_session_options, cancellable);
|
||||
attach_host_session (session);
|
||||
|
||||
current_host_session = session;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ namespace Frida {
|
||||
yield Fruity.DTXConnection.close_all (device, cancellable);
|
||||
}
|
||||
|
||||
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
if (host_session != null)
|
||||
throw new Error.INVALID_OPERATION ("Already created");
|
||||
|
||||
|
||||
@@ -88,8 +88,12 @@ namespace Frida.Fruity {
|
||||
request.set_string ("SystemBUID", system_buid);
|
||||
|
||||
var response = yield service.query (request, cancellable);
|
||||
if (response.has ("Error"))
|
||||
throw new LockdownError.PROTOCOL ("Unexpected response: %s", response.get_string ("Error"));
|
||||
if (response.has ("Error")) {
|
||||
var error = response.get_string ("Error");
|
||||
if (error == "InvalidHostID")
|
||||
yield unpair (cancellable);
|
||||
throw new LockdownError.PROTOCOL ("Unexpected response: %s", error);
|
||||
}
|
||||
|
||||
if (response.get_boolean ("EnableSessionSSL"))
|
||||
service.stream = yield start_tls (service.stream, cancellable);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private void add_nonlocal_backends () {
|
||||
#if HAVE_SIMMY_BACKEND
|
||||
add_backend (new SimmyHostSessionBackend ());
|
||||
#endif
|
||||
#if !IOS && !ANDROID && !TVOS
|
||||
#if HAVE_FRUITY_BACKEND
|
||||
add_backend (new FruityHostSessionBackend ());
|
||||
@@ -161,7 +164,7 @@ namespace Frida {
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract async HostSession create (HostSessionOptions? options = null,
|
||||
public abstract async HostSession create (HostSessionHub hub, HostSessionOptions? options = null,
|
||||
Cancellable? cancellable = null) throws Error, IOError;
|
||||
public abstract async void destroy (HostSession session, Cancellable? cancellable = null) throws Error, IOError;
|
||||
public signal void host_session_detached (HostSession session);
|
||||
@@ -180,6 +183,42 @@ namespace Frida {
|
||||
public abstract void unlink_service_session (HostSession host_session, ServiceSessionId id);
|
||||
}
|
||||
|
||||
public interface HostSessionConnection : Object {
|
||||
public abstract HostSession host_session {
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract async AgentSession link_agent_session (AgentSessionId id, AgentMessageSink sink,
|
||||
Cancellable? cancellable = null) throws Error, IOError;
|
||||
public abstract void unlink_agent_session (AgentSessionId id);
|
||||
}
|
||||
|
||||
public interface HostSessionHub : Object {
|
||||
public abstract async HostSessionEntry resolve_host_session (string id, Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
|
||||
public class NullHostSessionHub : Object, HostSessionHub {
|
||||
public async HostSessionEntry resolve_host_session (string id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Host session lookup not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public class HostSessionEntry : Object {
|
||||
public HostSessionProvider provider {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public HostSession session {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public HostSessionEntry (HostSessionProvider provider, HostSession session) {
|
||||
Object (provider: provider, session: session);
|
||||
}
|
||||
}
|
||||
|
||||
public enum HostSessionProviderKind {
|
||||
LOCAL,
|
||||
REMOTE,
|
||||
@@ -277,7 +316,8 @@ namespace Frida {
|
||||
host_session = null;
|
||||
}
|
||||
|
||||
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
if (host_session != null)
|
||||
throw new Error.INVALID_OPERATION ("Already created");
|
||||
|
||||
@@ -332,7 +372,13 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class LocalHostSession : Object, HostSession, AgentController {
|
||||
public abstract class LocalHostSession : Object, HostSession, HostSessionConnection, AgentController {
|
||||
public HostSession host_session {
|
||||
get {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private Gee.HashMap<uint, Cancellable> pending_establish_ops = new Gee.HashMap<uint, Cancellable> ();
|
||||
|
||||
private Gee.HashMap<uint, Future<AgentEntry>> agent_entries = new Gee.HashMap<uint, Future<AgentEntry>> ();
|
||||
@@ -1423,7 +1469,7 @@ namespace Frida {
|
||||
public abstract class InternalAgent : Object, AgentMessageSink, RpcPeer {
|
||||
public signal void unloaded ();
|
||||
|
||||
public weak LocalHostSession host_session {
|
||||
public weak HostSessionConnection connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
@@ -1447,11 +1493,7 @@ namespace Frida {
|
||||
construct {
|
||||
rpc_client = new RpcClient (this);
|
||||
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
}
|
||||
|
||||
~InternalAgent () {
|
||||
host_session.agent_session_detached.disconnect (on_agent_session_detached);
|
||||
connection.host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
@@ -1463,6 +1505,8 @@ namespace Frida {
|
||||
}
|
||||
|
||||
yield ensure_unloaded (cancellable);
|
||||
|
||||
connection.host_session.agent_session_detached.disconnect (on_agent_session_detached);
|
||||
}
|
||||
|
||||
protected abstract async uint get_target_pid (Cancellable? cancellable) throws Error, IOError;
|
||||
@@ -1516,9 +1560,9 @@ namespace Frida {
|
||||
target_pid = yield get_target_pid (cancellable);
|
||||
|
||||
try {
|
||||
session_id = yield host_session.attach (target_pid, attach_options, cancellable);
|
||||
session_id = yield connection.host_session.attach (target_pid, attach_options, cancellable);
|
||||
|
||||
session = yield host_session.link_agent_session (session_id, (AgentMessageSink) this, cancellable);
|
||||
session = yield connection.link_agent_session (session_id, (AgentMessageSink) this, cancellable);
|
||||
|
||||
string? source = yield load_source (cancellable);
|
||||
if (source != null) {
|
||||
|
||||
@@ -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));
|
||||
@@ -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);
|
||||
@@ -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
@@ -1,563 +0,0 @@
|
||||
{
|
||||
"name": "frida-system-server-agent",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frida-system-server-agent",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@frida/buffer": "^7.0.4",
|
||||
"frida-java-bridge": "^7.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.5",
|
||||
"esbuild-plugin-alias": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
|
||||
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
|
||||
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
|
||||
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
|
||||
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
|
||||
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
|
||||
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
|
||||
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
|
||||
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
|
||||
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/buffer": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@frida/buffer/-/buffer-7.0.4.tgz",
|
||||
"integrity": "sha512-RxQ1lZRRiCJj7nhcCiD8xeJx0NsLpGGnjqsmTg7jShGmbnVFMN5W7+J+3gqdPSQhc/IxNBIWc6zRXVp4+qnYHg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
|
||||
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.5",
|
||||
"@esbuild/android-arm": "0.25.5",
|
||||
"@esbuild/android-arm64": "0.25.5",
|
||||
"@esbuild/android-x64": "0.25.5",
|
||||
"@esbuild/darwin-arm64": "0.25.5",
|
||||
"@esbuild/darwin-x64": "0.25.5",
|
||||
"@esbuild/freebsd-arm64": "0.25.5",
|
||||
"@esbuild/freebsd-x64": "0.25.5",
|
||||
"@esbuild/linux-arm": "0.25.5",
|
||||
"@esbuild/linux-arm64": "0.25.5",
|
||||
"@esbuild/linux-ia32": "0.25.5",
|
||||
"@esbuild/linux-loong64": "0.25.5",
|
||||
"@esbuild/linux-mips64el": "0.25.5",
|
||||
"@esbuild/linux-ppc64": "0.25.5",
|
||||
"@esbuild/linux-riscv64": "0.25.5",
|
||||
"@esbuild/linux-s390x": "0.25.5",
|
||||
"@esbuild/linux-x64": "0.25.5",
|
||||
"@esbuild/netbsd-arm64": "0.25.5",
|
||||
"@esbuild/netbsd-x64": "0.25.5",
|
||||
"@esbuild/openbsd-arm64": "0.25.5",
|
||||
"@esbuild/openbsd-x64": "0.25.5",
|
||||
"@esbuild/sunos-x64": "0.25.5",
|
||||
"@esbuild/win32-arm64": "0.25.5",
|
||||
"@esbuild/win32-ia32": "0.25.5",
|
||||
"@esbuild/win32-x64": "0.25.5"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-plugin-alias": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz",
|
||||
"integrity": "sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/frida-java-bridge": {
|
||||
"version": "7.0.8",
|
||||
"resolved": "https://registry.npmjs.org/frida-java-bridge/-/frida-java-bridge-7.0.8.tgz",
|
||||
"integrity": "sha512-EmqoTGzcDuCd+YFcjBJJPf7iVQCSbbj9fD0/TN8YYYwl5ZJtTqaCLTTsaC07o3bJaDVs/f3ks+Vu1bOw0S1PnQ==",
|
||||
"license": "LGPL-2.0 WITH WxWindows-exception-3.1"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "frida-system-server-agent",
|
||||
"version": "1.0.0",
|
||||
"description": "Agent used by Frida's Linux backend on Android",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node build.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.5",
|
||||
"esbuild-plugin-alias": "^0.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@frida/buffer": "^7.0.4",
|
||||
"frida-java-bridge": "^7.0.8"
|
||||
}
|
||||
}
|
||||
+228
-202
@@ -36,21 +36,8 @@ namespace Frida {
|
||||
string[] argv = options.compute_argv (path);
|
||||
string[] envp = options.compute_envp ();
|
||||
|
||||
StdioPipes? pipes = null;
|
||||
FileDescriptor? stdin_read = null, stdin_write = null;
|
||||
FileDescriptor? stdout_read = null, stdout_write = null;
|
||||
FileDescriptor? stderr_read = null, stderr_write = null;
|
||||
switch (options.stdio) {
|
||||
case INHERIT:
|
||||
break;
|
||||
case PIPE: {
|
||||
make_pty (out stdin_read, out stdin_write);
|
||||
make_pty (out stdout_read, out stdout_write);
|
||||
make_pty (out stderr_read, out stderr_write);
|
||||
pipes = new StdioPipes (stdin_write, stdout_read, stderr_read);
|
||||
break;
|
||||
}
|
||||
}
|
||||
FileDescriptor? in_fd, out_fd, err_fd;
|
||||
StdioPipes? pipes = make_stdio_pipes (options.stdio, true, out in_fd, null, out out_fd, null, out err_fd, null);
|
||||
|
||||
string? old_cwd = null;
|
||||
if (options.cwd.length > 0) {
|
||||
@@ -68,10 +55,10 @@ namespace Frida {
|
||||
if (pid == 0) {
|
||||
Posix.setsid ();
|
||||
|
||||
if (options.stdio == PIPE) {
|
||||
Posix.dup2 (stdin_read.handle, 0);
|
||||
Posix.dup2 (stdout_write.handle, 1);
|
||||
Posix.dup2 (stderr_write.handle, 2);
|
||||
if (pipes != null) {
|
||||
Posix.dup2 (in_fd.handle, 0);
|
||||
Posix.dup2 (out_fd.handle, 1);
|
||||
Posix.dup2 (err_fd.handle, 2);
|
||||
}
|
||||
|
||||
if (_ptrace (TRACEME) == -1) {
|
||||
@@ -637,9 +624,9 @@ namespace Frida {
|
||||
monitor ();
|
||||
|
||||
if (pipes != null) {
|
||||
stdin_stream = new UnixOutputStream (pipes.input.handle, false);
|
||||
process_next_output_from.begin (new UnixInputStream (pipes.output.handle, false), 1);
|
||||
process_next_output_from.begin (new UnixInputStream (pipes.error.handle, false), 2);
|
||||
stdin_stream = pipes.input;
|
||||
process_next_output_from.begin (pipes.output, 1);
|
||||
process_next_output_from.begin (pipes.error, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,37 +690,6 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StdioPipes : Object {
|
||||
public FileDescriptor input {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public FileDescriptor output {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public FileDescriptor error {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public StdioPipes (FileDescriptor input, FileDescriptor output, FileDescriptor error) {
|
||||
Object (input: input, output: output, error: error);
|
||||
}
|
||||
|
||||
construct {
|
||||
try {
|
||||
Unix.set_fd_nonblocking (input.handle, true);
|
||||
Unix.set_fd_nonblocking (output.handle, true);
|
||||
Unix.set_fd_nonblocking (error.handle, true);
|
||||
} catch (GLib.Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ExecTransitionSession : SeizeSession {
|
||||
private ExecTransitionSession (uint pid) {
|
||||
Object (pid: pid, on_init: SeizeSession.InitBehavior.CONTINUE);
|
||||
@@ -785,25 +741,38 @@ namespace Frida {
|
||||
public async void wait_for_syscall (LinuxSyscall mask, Cancellable? cancellable) throws Error, IOError {
|
||||
bool on_syscall_entry = true;
|
||||
int pending_signal = 0;
|
||||
do {
|
||||
while (state != SATISFIED) {
|
||||
ptrace (SYSCALL, tid, null, (void *) pending_signal);
|
||||
pending_signal = 0;
|
||||
|
||||
Posix.Signal sig = yield wait_for_next_signal (cancellable);
|
||||
if (sig != (TRAP | 0x80)) {
|
||||
on_syscall_entry = !on_syscall_entry;
|
||||
pending_signal = sig;
|
||||
var wr = yield ChildProcess.wait_for_next_stop (tid, cancellable);
|
||||
wr.check_stopped ();
|
||||
|
||||
if (wr.is_ptrace_event_stop) {
|
||||
if (wr.ptrace_event == PtraceEvent.EXEC)
|
||||
on_syscall_entry = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (on_syscall_entry) {
|
||||
get_regs (&saved_regs);
|
||||
if (_syscall_satisfies (get_syscall_id (saved_regs), mask))
|
||||
state = SATISFIED;
|
||||
if (wr.is_syscall_stop) {
|
||||
if (on_syscall_entry) {
|
||||
get_regs (&saved_regs);
|
||||
var id = get_syscall_id (saved_regs);
|
||||
|
||||
if (_syscall_satisfies (id, mask))
|
||||
state = SATISFIED;
|
||||
}
|
||||
|
||||
on_syscall_entry = !on_syscall_entry;
|
||||
continue;
|
||||
}
|
||||
|
||||
on_syscall_entry = !on_syscall_entry;
|
||||
} while (state != SATISFIED);
|
||||
if (wr.should_deliver_to_tracee)
|
||||
pending_signal = wr.stop_signal;
|
||||
else
|
||||
pending_signal = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async void interrupt (Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -2061,6 +2030,10 @@ namespace Frida {
|
||||
return yield ChildProcess.wait_for_next_signal (tid, cancellable);
|
||||
}
|
||||
|
||||
public async WaitResult wait_for_next_stop (Cancellable? cancellable) throws Error, IOError {
|
||||
return yield ChildProcess.wait_for_next_stop (tid, cancellable);
|
||||
}
|
||||
|
||||
public void get_regs (GPRegs * regs) throws Error {
|
||||
#if !MIPS
|
||||
if (regset_supported) {
|
||||
@@ -3062,30 +3035,53 @@ namespace Frida {
|
||||
|
||||
namespace ChildProcess {
|
||||
private async void wait_for_early_signal (uint pid, Posix.Signal sig, Cancellable? cancellable) throws Error, IOError {
|
||||
while (true) {
|
||||
Posix.Signal next_signal = yield wait_for_next_signal (pid, cancellable);
|
||||
if (next_signal == sig)
|
||||
return;
|
||||
|
||||
ptrace (CONT, pid);
|
||||
}
|
||||
yield wait_for_signals_common (pid, { sig }, suppress_unmatched, cancellable);
|
||||
}
|
||||
|
||||
private async void wait_for_signal (uint pid, Posix.Signal sig, Cancellable? cancellable) throws Error, IOError {
|
||||
yield wait_for_signals (pid, { sig }, cancellable);
|
||||
}
|
||||
|
||||
private async Posix.Signal wait_for_signals (uint pid, Posix.Signal[] sigs, Cancellable? cancellable) throws Error, IOError {
|
||||
while (true) {
|
||||
Posix.Signal next_signal = yield wait_for_next_signal (pid, cancellable);
|
||||
if (next_signal in sigs)
|
||||
return next_signal;
|
||||
private async Posix.Signal wait_for_signals (uint pid, Posix.Signal[] sigs, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
return yield wait_for_signals_common (pid, sigs, pass_through_unmatched, cancellable);
|
||||
}
|
||||
|
||||
ptrace (CONT, pid, null, (void *) next_signal);
|
||||
private async Posix.Signal wait_for_signals_common (uint pid, Posix.Signal[] sigs, UnmatchedStopHandler on_unmatched,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
while (true) {
|
||||
var wr = yield wait_for_next_stop (pid, cancellable);
|
||||
wr.check_stopped ();
|
||||
|
||||
if (wr.stop_signal in sigs)
|
||||
return wr.stop_signal;
|
||||
|
||||
if (!wr.should_deliver_to_tracee) {
|
||||
ptrace (CONT, pid);
|
||||
continue;
|
||||
}
|
||||
|
||||
on_unmatched (pid, wr);
|
||||
}
|
||||
}
|
||||
|
||||
private delegate void UnmatchedStopHandler (uint pid, WaitResult wr) throws Error;
|
||||
|
||||
private static void pass_through_unmatched (uint pid, WaitResult wr) throws Error {
|
||||
ptrace (CONT, pid, null, (void *) wr.stop_signal);
|
||||
}
|
||||
|
||||
private static void suppress_unmatched (uint pid, WaitResult wr) throws Error {
|
||||
ptrace (CONT, pid);
|
||||
}
|
||||
|
||||
private async Posix.Signal wait_for_next_signal (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
var wr = yield wait_for_next_stop (pid, cancellable);
|
||||
wr.check_stopped ();
|
||||
return wr.stop_signal;
|
||||
}
|
||||
|
||||
private async WaitResult wait_for_next_stop (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
var main_context = MainContext.get_thread_default ();
|
||||
|
||||
bool timed_out = false;
|
||||
@@ -3098,21 +3094,23 @@ namespace Frida {
|
||||
|
||||
int status = 0;
|
||||
uint[] delays = { 0, 1, 2, 5, 10, 20, 50, 250 };
|
||||
|
||||
try {
|
||||
for (uint i = 0; !timed_out && !cancellable.set_error_if_cancelled (); i++) {
|
||||
int res = Posix.waitpid ((Posix.pid_t) pid, out status, Posix.WNOHANG);
|
||||
if (res == -1)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to wait for next signal: %s", strerror (errno));
|
||||
throw new Error.NOT_SUPPORTED ("Unable to wait for next stop: %s", strerror (errno));
|
||||
if (res != 0)
|
||||
break;
|
||||
|
||||
uint delay_ms = (i < delays.length) ? delays[i] : delays[delays.length - 1];
|
||||
|
||||
var delay_source = new TimeoutSource (delay_ms);
|
||||
delay_source.set_callback (wait_for_next_signal.callback);
|
||||
delay_source.set_callback (wait_for_next_stop.callback);
|
||||
delay_source.attach (main_context);
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (wait_for_next_signal.callback);
|
||||
cancel_source.set_callback (wait_for_next_stop.callback);
|
||||
cancel_source.attach (main_context);
|
||||
|
||||
yield;
|
||||
@@ -3125,21 +3123,79 @@ namespace Frida {
|
||||
}
|
||||
|
||||
if (timed_out)
|
||||
throw new Error.TIMED_OUT ("Unexpectedly timed out while waiting for signal from process with PID %u", pid);
|
||||
throw new Error.TIMED_OUT ("Unexpectedly timed out while waiting for stop from process with PID %u", pid);
|
||||
|
||||
return WaitResult (pid, status);
|
||||
}
|
||||
}
|
||||
|
||||
public enum WaitKind {
|
||||
EXITED,
|
||||
SIGNALED,
|
||||
STOPPED,
|
||||
OTHER
|
||||
}
|
||||
|
||||
public struct WaitResult {
|
||||
public uint pid;
|
||||
public int status;
|
||||
|
||||
public WaitKind kind;
|
||||
|
||||
public uint exit_status;
|
||||
|
||||
public Posix.Signal term_signal;
|
||||
|
||||
public Posix.Signal stop_signal;
|
||||
public PtraceEvent ptrace_event;
|
||||
public bool is_ptrace_event_stop;
|
||||
public bool is_syscall_stop;
|
||||
|
||||
public bool should_deliver_to_tracee;
|
||||
|
||||
public WaitResult (uint pid, int status) {
|
||||
this.pid = pid;
|
||||
this.status = status;
|
||||
|
||||
kind = WaitKind.OTHER;
|
||||
exit_status = 0;
|
||||
term_signal = 0;
|
||||
stop_signal = 0;
|
||||
ptrace_event = PtraceEvent.NONE;
|
||||
is_ptrace_event_stop = false;
|
||||
is_syscall_stop = false;
|
||||
should_deliver_to_tracee = false;
|
||||
|
||||
if (PosixStatus.is_exit (status)) {
|
||||
throw new Error.NOT_SUPPORTED ("Target exited with status %u",
|
||||
PosixStatus.parse_exit_status (status));
|
||||
}
|
||||
kind = WaitKind.EXITED;
|
||||
exit_status = PosixStatus.parse_exit_status (status);
|
||||
} else if (PosixStatus.is_signaled (status)) {
|
||||
kind = WaitKind.SIGNALED;
|
||||
term_signal = PosixStatus.parse_termination_signal (status);
|
||||
} else if (PosixStatus.is_stopped (status)) {
|
||||
kind = WaitKind.STOPPED;
|
||||
|
||||
if (PosixStatus.is_signaled (status)) {
|
||||
throw new Error.NOT_SUPPORTED ("Target terminated with signal %u",
|
||||
PosixStatus.parse_termination_signal (status));
|
||||
}
|
||||
stop_signal = PosixStatus.parse_stop_signal (status);
|
||||
ptrace_event = PosixStatus.ptrace_event (status);
|
||||
|
||||
if (!PosixStatus.is_stopped (status))
|
||||
throw new Error.NOT_SUPPORTED ("Unexpected status: 0x%08x", status);
|
||||
return PosixStatus.parse_stop_signal (status);
|
||||
is_ptrace_event_stop = (stop_signal == Posix.Signal.TRAP) && (ptrace_event != NONE);
|
||||
is_syscall_stop = (stop_signal == (Posix.Signal.TRAP | 0x80)) && (ptrace_event == NONE);
|
||||
|
||||
should_deliver_to_tracee = !is_syscall_stop && !is_ptrace_event_stop && stop_signal != Posix.Signal.TRAP;
|
||||
}
|
||||
}
|
||||
|
||||
public void check_stopped () throws Error {
|
||||
switch (kind) {
|
||||
case WaitKind.EXITED:
|
||||
throw new Error.NOT_SUPPORTED ("Target exited with status %u", exit_status);
|
||||
case WaitKind.SIGNALED:
|
||||
throw new Error.NOT_SUPPORTED ("Target terminated with signal %u", term_signal);
|
||||
case WaitKind.STOPPED:
|
||||
return;
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unexpected status: 0x%08x", status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3161,60 +3217,30 @@ namespace Frida {
|
||||
|
||||
[CCode (cname = "WSTOPSIG", cheader_filename = "sys/wait.h")]
|
||||
private extern Posix.Signal parse_stop_signal (int status);
|
||||
|
||||
private PtraceEvent ptrace_event (int status) {
|
||||
return ((uint) status) >> 16;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PtraceEvent {
|
||||
NONE,
|
||||
FORK,
|
||||
VFORK,
|
||||
CLONE,
|
||||
EXEC,
|
||||
VFORK_DONE,
|
||||
EXIT,
|
||||
SECCOMP,
|
||||
}
|
||||
|
||||
private int tgkill (uint tgid, uint tid, Posix.Signal sig) {
|
||||
return Linux.syscall (SysCall.tgkill, tgid, tid, sig);
|
||||
}
|
||||
|
||||
|
||||
public extern bool _syscall_satisfies (int syscall_id, LinuxSyscall mask);
|
||||
|
||||
private void make_pty (out FileDescriptor read, out FileDescriptor write) throws Error {
|
||||
#if HAVE_OPENPTY
|
||||
int rfd = -1, wfd = -1;
|
||||
char name[Posix.Limits.PATH_MAX];
|
||||
if (Linux.openpty (out rfd, out wfd, name, null, null) == -1)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to open PTY: %s", strerror (errno));
|
||||
|
||||
enable_close_on_exec (rfd);
|
||||
enable_close_on_exec (wfd);
|
||||
|
||||
configure_terminal_attributes (rfd);
|
||||
|
||||
read = new FileDescriptor (rfd);
|
||||
write = new FileDescriptor (wfd);
|
||||
#else
|
||||
try {
|
||||
int fds[2];
|
||||
Unix.open_pipe (fds, Posix.FD_CLOEXEC);
|
||||
|
||||
read = new FileDescriptor (fds[0]);
|
||||
write = new FileDescriptor (fds[1]);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to open pipe: %s", e.message);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAVE_OPENPTY
|
||||
private void enable_close_on_exec (int fd) {
|
||||
Posix.fcntl (fd, Posix.F_SETFD, Posix.fcntl (fd, Posix.F_GETFD) | Posix.FD_CLOEXEC);
|
||||
}
|
||||
|
||||
private void configure_terminal_attributes (int fd) {
|
||||
var tios = Posix.termios ();
|
||||
Posix.tcgetattr (fd, out tios);
|
||||
|
||||
tios.c_oflag &= ~Posix.ONLCR;
|
||||
tios.c_cflag = (tios.c_cflag & Posix.CLOCAL) | Posix.CS8 | Posix.CREAD | Posix.HUPCL;
|
||||
tios.c_lflag &= ~Posix.ECHO;
|
||||
|
||||
Posix.tcsetattr (fd, 0, tios);
|
||||
}
|
||||
#endif
|
||||
|
||||
private sealed class ProcMapsSoEntry {
|
||||
public class ProcMapsSoEntry {
|
||||
public uint64 base_address;
|
||||
public string path;
|
||||
public string identity;
|
||||
@@ -3226,7 +3252,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public static ProcMapsSoEntry? find_by_address (uint pid, uint64 address) {
|
||||
var iter = MapsIter.for_pid (pid);
|
||||
var iter = ProcMapsIter.for_pid (pid);
|
||||
while (iter.next ()) {
|
||||
uint64 start = iter.start_address;
|
||||
uint64 end = iter.end_address;
|
||||
@@ -3240,7 +3266,7 @@ namespace Frida {
|
||||
public static ProcMapsSoEntry? find_by_path (uint pid, string path) {
|
||||
var candidates = new Gee.ArrayList<Candidate> ();
|
||||
Candidate? latest_candidate = null;
|
||||
var iter = MapsIter.for_pid (pid);
|
||||
var iter = ProcMapsIter.for_pid (pid);
|
||||
#if ANDROID
|
||||
unowned string libc_path = Gum.Process.get_libc_module ().path;
|
||||
#endif
|
||||
@@ -3296,80 +3322,80 @@ namespace Frida {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MapsIter {
|
||||
private string? contents;
|
||||
private MatchInfo? info;
|
||||
private uint offset = 0;
|
||||
public sealed class ProcMapsIter {
|
||||
private string? contents;
|
||||
private MatchInfo? info;
|
||||
private uint offset = 0;
|
||||
|
||||
public uint64 start_address {
|
||||
get {
|
||||
return uint64.parse (info.fetch (1), 16);
|
||||
}
|
||||
public uint64 start_address {
|
||||
get {
|
||||
return uint64.parse (info.fetch (1), 16);
|
||||
}
|
||||
}
|
||||
|
||||
public uint64 end_address {
|
||||
get {
|
||||
return uint64.parse (info.fetch (2), 16);
|
||||
}
|
||||
}
|
||||
|
||||
public string flags {
|
||||
owned get {
|
||||
return info.fetch (3);
|
||||
}
|
||||
}
|
||||
|
||||
public uint64 file_offset {
|
||||
get {
|
||||
return uint64.parse (info.fetch (4), 16);
|
||||
}
|
||||
}
|
||||
|
||||
public string identity {
|
||||
owned get {
|
||||
return info.fetch (5);
|
||||
}
|
||||
}
|
||||
|
||||
public string path {
|
||||
owned get {
|
||||
return info.fetch (6);
|
||||
}
|
||||
}
|
||||
|
||||
public static ProcMapsIter for_pid (uint pid) {
|
||||
return new ProcMapsIter (pid);
|
||||
}
|
||||
|
||||
private ProcMapsIter (uint pid) {
|
||||
try {
|
||||
FileUtils.get_contents ("/proc/%u/maps".printf (pid), out contents);
|
||||
} catch (FileError e) {
|
||||
return;
|
||||
}
|
||||
|
||||
public uint64 end_address {
|
||||
get {
|
||||
return uint64.parse (info.fetch (2), 16);
|
||||
}
|
||||
if (!/^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,} \d+) +([^\n]+)$/m.match (
|
||||
contents, 0, out info)) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
public string flags {
|
||||
owned get {
|
||||
return info.fetch (3);
|
||||
}
|
||||
}
|
||||
public bool next () {
|
||||
if (info == null)
|
||||
return false;
|
||||
|
||||
public uint64 file_offset {
|
||||
get {
|
||||
return uint64.parse (info.fetch (4), 16);
|
||||
}
|
||||
}
|
||||
|
||||
public string identity {
|
||||
owned get {
|
||||
return info.fetch (5);
|
||||
}
|
||||
}
|
||||
|
||||
public string path {
|
||||
owned get {
|
||||
return info.fetch (6);
|
||||
}
|
||||
}
|
||||
|
||||
public static MapsIter for_pid (uint pid) {
|
||||
return new MapsIter (pid);
|
||||
}
|
||||
|
||||
private MapsIter (uint pid) {
|
||||
if (offset > 0) {
|
||||
try {
|
||||
FileUtils.get_contents ("/proc/%u/maps".printf (pid), out contents);
|
||||
} catch (FileError e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,} \d+) +([^\n]+)$/m.match (
|
||||
contents, 0, out info)) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
public bool next () {
|
||||
if (info == null)
|
||||
info.next ();
|
||||
} catch (RegexError e) {
|
||||
return false;
|
||||
|
||||
if (offset > 0) {
|
||||
try {
|
||||
info.next ();
|
||||
} catch (RegexError e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
offset++;
|
||||
|
||||
return info.matches ();
|
||||
}
|
||||
offset++;
|
||||
|
||||
return info.matches ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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" (¶ms),
|
||||
"r" (entrypoint)
|
||||
: "memory"
|
||||
);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
@@ -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;
|
||||
}
|
||||
}
|
||||
+710
-598
File diff suppressed because it is too large.
Load diff
@@ -4,6 +4,7 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <gio/gunixmounts.h>
|
||||
#include <gum/gum.h>
|
||||
|
||||
typedef struct _FridaEnumerateProcessesOperation FridaEnumerateProcessesOperation;
|
||||
|
||||
|
||||
+60
-22
@@ -29,6 +29,38 @@ backend_vala_args_private = []
|
||||
backend_libs = []
|
||||
backend_deps = []
|
||||
|
||||
if host_os == 'android' or have_droidy_backend
|
||||
subdir('android-helper')
|
||||
|
||||
android_data = custom_target('frida-data-android',
|
||||
input: [
|
||||
'android.resources',
|
||||
android_helper_dex,
|
||||
'linux' / 'helpers' / f'zymbiote-x86.bin',
|
||||
'linux' / 'helpers' / f'zymbiote-x86_64.bin',
|
||||
'linux' / 'helpers' / f'zymbiote-arm.bin',
|
||||
'linux' / 'helpers' / f'zymbiote-arm64.bin',
|
||||
],
|
||||
output: [
|
||||
'frida-data-android.vapi',
|
||||
'frida-data-android.h',
|
||||
'frida-data-android.c',
|
||||
'frida-data-android-blob' + resource_blob_extension,
|
||||
],
|
||||
command: [
|
||||
resource_compiler_cmd_array,
|
||||
'-c', '@INPUT0@',
|
||||
'-o', meson.current_build_dir() / 'frida-data-android',
|
||||
'@INPUT1@',
|
||||
'@INPUT2@',
|
||||
'@INPUT3@',
|
||||
'@INPUT4@',
|
||||
'@INPUT5@',
|
||||
],
|
||||
)
|
||||
backend_sources += [android_data]
|
||||
endif
|
||||
|
||||
if have_local_backend
|
||||
post_process_helper = post_process + ['executable', 're.frida.Helper']
|
||||
|
||||
@@ -287,31 +319,9 @@ if have_local_backend
|
||||
]
|
||||
|
||||
if host_os == 'android'
|
||||
subdir('linux' / 'agent' / 'system-server')
|
||||
|
||||
backend_vala_args_private += frida_selinux_vala_args
|
||||
backend_libs += frida_selinux
|
||||
backend_deps += frida_selinux_dep
|
||||
|
||||
android_data = custom_target('frida-data-android',
|
||||
input: [
|
||||
'linux' / 'frida-android.resources',
|
||||
system_server_js,
|
||||
],
|
||||
output: [
|
||||
'frida-data-android.vapi',
|
||||
'frida-data-android.h',
|
||||
'frida-data-android.c',
|
||||
'frida-data-android-blob' + resource_blob_extension,
|
||||
],
|
||||
command: [
|
||||
resource_compiler_cmd_array,
|
||||
'-c', '@INPUT0@',
|
||||
'-o', meson.current_build_dir() / 'frida-data-android',
|
||||
'@INPUT1@',
|
||||
],
|
||||
)
|
||||
backend_sources += [android_data]
|
||||
endif
|
||||
|
||||
helper_backend_sources = [
|
||||
@@ -500,6 +510,34 @@ if have_local_backend
|
||||
endif
|
||||
endif
|
||||
|
||||
if have_simmy_backend
|
||||
backend_sources += [
|
||||
'simmy' / 'simmy-host-session.vala',
|
||||
'simmy' / 'simmy-host-session-glue.m',
|
||||
'simmy' / 'text-plist.vala',
|
||||
]
|
||||
|
||||
simmy_data = custom_target('frida-data-simmy',
|
||||
input: [
|
||||
'simmy' / 'frida-simmy.resources',
|
||||
'simmy' / 'agent' / 'springboard.js',
|
||||
],
|
||||
output: [
|
||||
'frida-data-simmy.vapi',
|
||||
'frida-data-simmy.h',
|
||||
'frida-data-simmy.c',
|
||||
'frida-data-simmy-blob' + resource_blob_extension,
|
||||
],
|
||||
command: [
|
||||
resource_compiler_cmd_array,
|
||||
'-c', '@INPUT0@',
|
||||
'-o', meson.current_build_dir() / 'frida-data-simmy',
|
||||
'@INPUT1@',
|
||||
],
|
||||
)
|
||||
backend_sources += [simmy_data]
|
||||
endif
|
||||
|
||||
if have_fruity_backend
|
||||
backend_sources += [
|
||||
'fruity' / 'fruity-host-session.vala',
|
||||
|
||||
@@ -957,7 +957,8 @@ namespace Frida {
|
||||
_icon = builder.end ();
|
||||
}
|
||||
|
||||
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
if (channel != null)
|
||||
throw new Error.INVALID_OPERATION ("Already created");
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
const SBS = bind({
|
||||
module: '/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices',
|
||||
cprefix: 'SBS',
|
||||
functions: {
|
||||
copyFrontmostApplicationDisplayIdentifier: ['pointer', [ ]],
|
||||
copyLocalizedApplicationNameForDisplayIdentifier: ['pointer', ['pointer' ]],
|
||||
copyIconImagePNGDataForDisplayIdentifier: ['pointer', ['pointer' ]],
|
||||
processIDForDisplayIdentifier: ['bool', ['pointer', 'pointer']],
|
||||
}
|
||||
});
|
||||
|
||||
const CF = bind({
|
||||
module: '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation',
|
||||
cprefix: 'CF',
|
||||
functions: {
|
||||
retain: ['pointer', ['pointer' ]],
|
||||
release: ['void', ['pointer' ]],
|
||||
stringCreateWithCString: ['pointer', ['pointer', 'pointer', 'uint' ]],
|
||||
stringGetCStringPtr: ['pointer', ['pointer', 'uint' ]],
|
||||
stringGetLength: ['long', ['pointer' ]],
|
||||
stringGetMaximumSizeForEncoding: ['long', ['long', 'uint' ]],
|
||||
stringGetCString: ['bool', ['pointer', 'pointer', 'long', 'uint']],
|
||||
dataGetLength: ['long', ['pointer' ]],
|
||||
dataGetBytePtr: ['pointer', ['pointer' ]],
|
||||
}
|
||||
});
|
||||
|
||||
const kCFBooleanTrue = Module.getGlobalExportByName('kCFBooleanTrue').readPointer();
|
||||
const kCFStringEncodingUTF8 = 0x08000100;
|
||||
|
||||
rpc.exports = {
|
||||
getFrontmostApplication(scope) {
|
||||
const idObj = SBS.copyFrontmostApplicationDisplayIdentifier();
|
||||
if (idObj.isNull())
|
||||
return null;
|
||||
|
||||
const id = cfStringToUtf8(idObj);
|
||||
|
||||
const nameObj = SBS.copyLocalizedApplicationNameForDisplayIdentifier(idObj);
|
||||
const name = cfStringToUtf8(nameObj);
|
||||
|
||||
const pidBuf = Memory.alloc(4);
|
||||
SBS.processIDForDisplayIdentifier(idObj, pidBuf);
|
||||
const pid = pidBuf.readU32();
|
||||
|
||||
CF.release(nameObj);
|
||||
CF.release(idObj);
|
||||
|
||||
if (pid === 0)
|
||||
return null;
|
||||
|
||||
return [
|
||||
id,
|
||||
name,
|
||||
pid,
|
||||
(scope === 'full') ? fetchApplicationIcon(id) : null
|
||||
];
|
||||
},
|
||||
fetchApplicationIcons(appIds) {
|
||||
return appIds.map(fetchApplicationIcon);
|
||||
},
|
||||
};
|
||||
|
||||
function fetchApplicationIcon(appId) {
|
||||
const idObj = CF.stringCreateWithCString(NULL, Memory.allocUtf8String(appId), kCFStringEncodingUTF8);
|
||||
const pngObj = SBS.copyIconImagePNGDataForDisplayIdentifier(idObj);
|
||||
CF.release(idObj);
|
||||
|
||||
const png = CF.dataGetBytePtr(pngObj).readByteArray(CF.dataGetLength(pngObj));
|
||||
CF.release(pngObj);
|
||||
return base64FromBytes(png);
|
||||
}
|
||||
|
||||
Interceptor.attach(Module.getGlobalExportByName('SecTaskCopyValueForEntitlement'), {
|
||||
onEnter(args) {
|
||||
this.entitlement = cfStringToUtf8(args[1]);
|
||||
},
|
||||
onLeave(retval) {
|
||||
if (this.entitlement === 'com.apple.springboard.iconState' && retval.isNull())
|
||||
retval.replace(CF.retain(kCFBooleanTrue));
|
||||
}
|
||||
});
|
||||
|
||||
function bind({ module, cprefix, functions }) {
|
||||
const mod = Process.getModuleByName(module);
|
||||
const nfOpts = { exceptions: 'propagate' };
|
||||
return Object.fromEntries(Object.entries(functions).map(([name, [retType, argTypes]]) => [
|
||||
name,
|
||||
new NativeFunction(mod.getExportByName(cprefix + name.charAt(0).toUpperCase() + name.slice(1)), retType, argTypes, nfOpts)
|
||||
]));
|
||||
}
|
||||
|
||||
function cfStringToUtf8(cfstr) {
|
||||
const direct = CF.stringGetCStringPtr(cfstr, kCFStringEncodingUTF8);
|
||||
if (!direct.isNull())
|
||||
return direct.readUtf8String();
|
||||
|
||||
const len16 = CF.stringGetLength(cfstr);
|
||||
const maxBytes = CF.stringGetMaximumSizeForEncoding(len16, kCFStringEncodingUTF8).valueOf() + 1;
|
||||
const buf = Memory.alloc(maxBytes);
|
||||
CF.stringGetCString(cfstr, buf, maxBytes, kCFStringEncodingUTF8);
|
||||
return buf.readUtf8String();
|
||||
}
|
||||
|
||||
function base64FromBytes(buf) {
|
||||
const bytes = new Uint8Array(buf);
|
||||
|
||||
const enc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
let i = 0;
|
||||
const len = bytes.length;
|
||||
|
||||
let out = '';
|
||||
while (i < len) {
|
||||
const c1 = bytes[i++];
|
||||
const c2 = (i !== len) ? bytes[i++] : 0;
|
||||
const c3 = (i !== len) ? bytes[i++] : 0;
|
||||
|
||||
const o1 = c1 >> 2;
|
||||
const o2 = ((c1 & 0x03) << 4) | (c2 >> 4);
|
||||
const o3 = ((c2 & 0x0f) << 2) | (c3 >> 6);
|
||||
const o4 = c3 & 0x3f;
|
||||
|
||||
if (i - 1 > len)
|
||||
out += enc[o1] + enc[o2] + '==';
|
||||
else if (i > len)
|
||||
out += enc[o1] + enc[o2] + enc[o3] + '=';
|
||||
else
|
||||
out += enc[o1] + enc[o2] + enc[o3] + enc[o4];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[resource-compiler]
|
||||
namespace = Frida.Data.Simmy
|
||||
@@ -0,0 +1,528 @@
|
||||
#include "frida-core.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef struct _FridaSimmyContext FridaSimmyContext;
|
||||
|
||||
struct _FridaSimmyContext
|
||||
{
|
||||
dispatch_queue_t dispatch_queue;
|
||||
|
||||
void * core_simulator;
|
||||
|
||||
NSString * SimDeviceLaunchApplicationKeyArguments;
|
||||
NSString * SimDeviceLaunchApplicationKeyEnvironment;
|
||||
NSString * SimDeviceLaunchApplicationKeyStandardErrPath;
|
||||
NSString * SimDeviceLaunchApplicationKeyStandardOutPath;
|
||||
NSString * SimDeviceLaunchApplicationKeyTerminateRunningProcess;
|
||||
NSString * SimDeviceLaunchApplicationKeyWaitForDebugger;
|
||||
|
||||
NSString * SimDeviceSpawnKeyArguments;
|
||||
NSString * SimDeviceSpawnKeyEnvironment;
|
||||
NSString * SimDeviceSpawnKeyStderr;
|
||||
NSString * SimDeviceSpawnKeyStdin;
|
||||
NSString * SimDeviceSpawnKeyStdout;
|
||||
NSString * SimDeviceSpawnKeyWaitForDebugger;
|
||||
|
||||
FridaSimmyHostSessionBackendDeviceAddedFunc on_device_added;
|
||||
gpointer on_device_added_target;
|
||||
};
|
||||
|
||||
static void frida_simmy_context_destroy (FridaSimmyContext * self);
|
||||
static void frida_simmy_context_emit_devices (FridaSimmyContext * self);
|
||||
|
||||
static NSArray * frida_argv_to_arguments_array (gchar * const * argv, gint argv_length, gint start_index);
|
||||
static NSDictionary * frida_envp_to_environment_dictionary (gchar * const * envp, gint envp_length);
|
||||
|
||||
@interface SimRuntime : NSObject
|
||||
@property (nonatomic, readonly, strong) NSString * identifier;
|
||||
@property (nonatomic, readonly, strong) NSString * shortName;
|
||||
@property (nonatomic, readonly, strong) NSString * versionString;
|
||||
@property (nonatomic, readonly, strong) NSString * root;
|
||||
@end
|
||||
|
||||
@interface SimDevice : NSObject
|
||||
@property (nonatomic, readonly, strong) NSUUID * UDID;
|
||||
@property (nonatomic, readonly, strong) NSString * name;
|
||||
@property (nonatomic, readonly, strong) SimRuntime * runtime;
|
||||
@property (nonatomic, readonly, strong) NSString * stateString;
|
||||
- (NSString *)getenv:(NSString *)name
|
||||
error:(NSError * _Nullable * _Nullable)error;
|
||||
- (NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)installedAppsWithError:(NSError * _Nullable * _Nullable)error;
|
||||
- (void)launchApplicationAsyncWithID:(NSString *)identifier
|
||||
options:(NSDictionary<NSString *, id> * _Nullable)options
|
||||
completionQueue:(dispatch_queue_t)queue
|
||||
completionHandler:(void (^) (NSError * error, int pid))handler;
|
||||
- spawnAsyncWithPath:(NSString *)path
|
||||
options:(NSDictionary<NSString *, id> * _Nullable)options
|
||||
terminationQueue:(dispatch_queue_t)tq
|
||||
terminationHandler:(void (^) (int status))th
|
||||
completionQueue:(dispatch_queue_t)cq
|
||||
completionHandler:(void (^) (NSError * error, int pid))ch;
|
||||
@end
|
||||
|
||||
@interface SimDeviceSet : NSObject
|
||||
@property (nonatomic, readonly, strong) NSArray<SimDevice *> *devices;
|
||||
@end
|
||||
|
||||
@protocol SimServiceContextClass
|
||||
+ (instancetype)serviceContextForDeveloperDir:(NSString *)dir
|
||||
error:(NSError * _Nullable * _Nullable)error;
|
||||
@end
|
||||
|
||||
@interface SimServiceContext : NSObject
|
||||
- (SimDeviceSet *)defaultDeviceSetWithError:(NSError * _Nullable * _Nullable)error;
|
||||
@end
|
||||
|
||||
void *
|
||||
_frida_simmy_host_session_backend_start (FridaSimmyHostSessionBackendDeviceAddedFunc on_device_added, gpointer on_device_added_target,
|
||||
FridaSimmyCompleteFunc on_complete, gpointer on_complete_target, GDestroyNotify on_complete_target_destroy_notify)
|
||||
{
|
||||
FridaSimmyContext * ctx;
|
||||
|
||||
ctx = g_slice_new0 (FridaSimmyContext);
|
||||
ctx->dispatch_queue = dispatch_queue_create ("re.frida.simmy.queue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
ctx->on_device_added = on_device_added;
|
||||
ctx->on_device_added_target = on_device_added_target;
|
||||
|
||||
dispatch_async (ctx->dispatch_queue, ^
|
||||
{
|
||||
frida_simmy_context_emit_devices (ctx);
|
||||
|
||||
on_complete (on_complete_target);
|
||||
|
||||
if (on_complete_target_destroy_notify != NULL)
|
||||
on_complete_target_destroy_notify (on_complete_target);
|
||||
});
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void
|
||||
_frida_simmy_host_session_backend_stop (void * simmy_context, FridaSimmyCompleteFunc on_complete,
|
||||
gpointer on_complete_target, GDestroyNotify on_complete_target_destroy_notify)
|
||||
{
|
||||
FridaSimmyContext * ctx = simmy_context;
|
||||
|
||||
dispatch_async (ctx->dispatch_queue, ^
|
||||
{
|
||||
frida_simmy_context_destroy (ctx);
|
||||
g_slice_free (FridaSimmyContext, ctx);
|
||||
|
||||
on_complete (on_complete_target);
|
||||
|
||||
if (on_complete_target_destroy_notify != NULL)
|
||||
on_complete_target_destroy_notify (on_complete_target);
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
frida_simmy_context_destroy (FridaSimmyContext * self)
|
||||
{
|
||||
g_clear_pointer (&self->core_simulator, dlclose);
|
||||
|
||||
dispatch_release (self->dispatch_queue);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_simmy_context_emit_devices (FridaSimmyContext * self)
|
||||
{
|
||||
void * xcselect_module;
|
||||
bool (* xcselect_get_developer_dir_path) (char *, size_t, bool *, bool *, bool *);
|
||||
char developer_dir[1024] = { 0, };
|
||||
bool from_override, is_command_line_tools, from_fallback;
|
||||
void * cs;
|
||||
NSString ** str;
|
||||
Class<SimServiceContextClass> SimServiceContextClass;
|
||||
SimServiceContext * ctx;
|
||||
SimDeviceSet * set;
|
||||
|
||||
xcselect_module = dlopen ("/usr/lib/libxcselect.dylib", RTLD_GLOBAL | RTLD_LAZY);
|
||||
if (xcselect_module == NULL)
|
||||
goto beach;
|
||||
|
||||
xcselect_get_developer_dir_path = dlsym (xcselect_module, "xcselect_get_developer_dir_path");
|
||||
|
||||
if (!xcselect_get_developer_dir_path (developer_dir, sizeof (developer_dir), &from_override, &is_command_line_tools, &from_fallback))
|
||||
goto beach;
|
||||
|
||||
cs = dlopen ("/Library/Developer/PrivateFrameworks/CoreSimulator.framework/CoreSimulator", RTLD_GLOBAL | RTLD_LAZY);
|
||||
if (cs == NULL)
|
||||
goto beach;
|
||||
self->core_simulator = cs;
|
||||
|
||||
#define FRIDA_ASSIGN_CS_CONSTANT(N) \
|
||||
str = dlsym (cs, G_STRINGIFY (N)); \
|
||||
g_assert (str != NULL); \
|
||||
self->N = *str
|
||||
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyArguments);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyEnvironment);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyStandardErrPath);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyStandardOutPath);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyTerminateRunningProcess);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyWaitForDebugger);
|
||||
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyArguments);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyEnvironment);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyStderr);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyStdin);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyStdout);
|
||||
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyWaitForDebugger);
|
||||
|
||||
#undef FRIDA_ASSIGN_CS_CONSTANT
|
||||
|
||||
SimServiceContextClass = NSClassFromString (@"SimServiceContext");
|
||||
|
||||
ctx = [SimServiceContextClass serviceContextForDeveloperDir:[NSString stringWithUTF8String:developer_dir] error:nil];
|
||||
|
||||
set = [ctx defaultDeviceSetWithError:nil];
|
||||
|
||||
for (SimDevice * device in set.devices)
|
||||
{
|
||||
FridaSimmyRuntime * runtime;
|
||||
FridaSimmyDevice * d;
|
||||
|
||||
if (![device.stateString isEqualToString:@"Booted"])
|
||||
continue;
|
||||
|
||||
runtime = frida_simmy_runtime_new (device.runtime);
|
||||
|
||||
d = frida_simmy_device_new ([device retain], device.UDID.UUIDString.UTF8String, device.name.UTF8String,
|
||||
[device getenv:@"SIMULATOR_MODEL_IDENTIFIER" error:nil].UTF8String, runtime, self);
|
||||
self->on_device_added (d, self->on_device_added_target);
|
||||
g_object_unref (d);
|
||||
|
||||
g_object_unref (runtime);
|
||||
}
|
||||
|
||||
beach:
|
||||
if (xcselect_module != NULL)
|
||||
dlclose (xcselect_module);
|
||||
}
|
||||
|
||||
void
|
||||
_frida_simmy_device_list_applications (FridaSimmyDevice * self, FridaSimmyDeviceListApplicationsCompleteFunc on_complete,
|
||||
gpointer on_complete_target, GDestroyNotify on_complete_target_destroy_notify)
|
||||
{
|
||||
FridaSimmyContext * ctx = frida_simmy_device_get_simmy_context (self);
|
||||
|
||||
dispatch_async (ctx->dispatch_queue, ^
|
||||
{
|
||||
GeeArrayList * applications;
|
||||
SimDevice * device;
|
||||
|
||||
applications = gee_array_list_new (FRIDA_SIMMY_TYPE_APPLICATION, g_object_ref, g_object_unref, NULL, NULL, NULL);
|
||||
|
||||
device = frida_simmy_device_get_handle (self);
|
||||
NSDictionary<NSString *, NSDictionary<NSString *, id> *> * bundles = [device installedAppsWithError:nil];
|
||||
for (NSString * identifier in bundles)
|
||||
{
|
||||
NSDictionary<NSString *, id> * bundle;
|
||||
NSString * display_name;
|
||||
FridaSimmyApplication * app;
|
||||
|
||||
bundle = bundles[identifier];
|
||||
display_name = bundle[@"CFBundleDisplayName"];
|
||||
|
||||
app = frida_simmy_application_new (identifier.UTF8String, display_name.UTF8String);
|
||||
gee_collection_add (GEE_COLLECTION (applications), app);
|
||||
g_object_unref (app);
|
||||
}
|
||||
|
||||
on_complete (GEE_LIST (applications), on_complete_target);
|
||||
|
||||
if (on_complete_target_destroy_notify != NULL)
|
||||
on_complete_target_destroy_notify (on_complete_target);
|
||||
|
||||
g_object_unref (applications);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
_frida_simmy_device_launch_application (FridaSimmyDevice * self, const gchar * identifier, FridaHostSpawnOptions * opts,
|
||||
FridaSimmyDeviceLaunchApplicationCompleteFunc on_complete, gpointer on_complete_target,
|
||||
GDestroyNotify on_complete_target_destroy_notify)
|
||||
{
|
||||
SimDevice * device;
|
||||
FridaSimmyContext * ctx;
|
||||
__block FridaStdioPipes * pipes;
|
||||
__block FridaFileDescriptor * out_fd, * err_fd;
|
||||
gchar * out_name, * err_name;
|
||||
GError * error = NULL;
|
||||
__block GMainContext * main_context;
|
||||
|
||||
device = frida_simmy_device_get_handle (self);
|
||||
ctx = frida_simmy_device_get_simmy_context (self);
|
||||
|
||||
if (opts->has_envp)
|
||||
goto envp_not_supported;
|
||||
|
||||
pipes = frida_make_stdio_pipes (opts->stdio, FALSE, NULL, NULL, &out_fd, &out_name, &err_fd, &err_name, &error);
|
||||
if (error != NULL)
|
||||
goto propagate_error;
|
||||
|
||||
main_context = g_main_context_ref_thread_default ();
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
NSMutableDictionary<NSString *, id> * launch_opts;
|
||||
|
||||
launch_opts = [@{
|
||||
ctx->SimDeviceLaunchApplicationKeyWaitForDebugger: @YES,
|
||||
ctx->SimDeviceLaunchApplicationKeyTerminateRunningProcess: @YES,
|
||||
} mutableCopy];
|
||||
|
||||
if (opts->has_argv)
|
||||
launch_opts[ctx->SimDeviceLaunchApplicationKeyArguments] = frida_argv_to_arguments_array (opts->argv, opts->argv_length1, 1);
|
||||
|
||||
if (opts->has_env)
|
||||
launch_opts[ctx->SimDeviceLaunchApplicationKeyEnvironment] = frida_envp_to_environment_dictionary (opts->env, opts->env_length1);
|
||||
|
||||
if (opts->stdio == FRIDA_STDIO_PIPE)
|
||||
{
|
||||
launch_opts[ctx->SimDeviceLaunchApplicationKeyStandardOutPath] = @(out_name);
|
||||
launch_opts[ctx->SimDeviceLaunchApplicationKeyStandardErrPath] = @(err_name);
|
||||
};
|
||||
|
||||
[device launchApplicationAsyncWithID:[NSString stringWithUTF8String:identifier]
|
||||
options:launch_opts
|
||||
completionQueue:ctx->dispatch_queue
|
||||
completionHandler:
|
||||
^(NSError * error, int pid)
|
||||
{
|
||||
FridaSimmySpawnedProcess * process = NULL;
|
||||
|
||||
if (error == nil)
|
||||
process = frida_simmy_spawned_process_new (pid, pipes, main_context);
|
||||
|
||||
g_main_context_unref (main_context);
|
||||
main_context = NULL;
|
||||
|
||||
g_clear_object (&pipes);
|
||||
g_clear_object (&out_fd);
|
||||
g_clear_object (&err_fd);
|
||||
|
||||
on_complete (error.localizedDescription.UTF8String, process, on_complete_target);
|
||||
|
||||
g_clear_object (&process);
|
||||
|
||||
if (on_complete_target_destroy_notify != NULL)
|
||||
on_complete_target_destroy_notify (on_complete_target);
|
||||
}];
|
||||
}
|
||||
|
||||
g_free (out_name);
|
||||
g_free (err_name);
|
||||
|
||||
return;
|
||||
|
||||
envp_not_supported:
|
||||
{
|
||||
error = g_error_new_literal (
|
||||
FRIDA_ERROR,
|
||||
FRIDA_ERROR_NOT_SUPPORTED,
|
||||
"The 'envp' option is not supported when spawning Simmy apps, use the 'env' option instead");
|
||||
goto propagate_error;
|
||||
}
|
||||
propagate_error:
|
||||
{
|
||||
dispatch_async (ctx->dispatch_queue, ^
|
||||
{
|
||||
on_complete (error->message, NULL, on_complete_target);
|
||||
|
||||
g_error_free (error);
|
||||
|
||||
if (on_complete_target_destroy_notify != NULL)
|
||||
on_complete_target_destroy_notify (on_complete_target);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
_frida_simmy_device_spawn_program (FridaSimmyDevice * self, const gchar * program, FridaHostSpawnOptions * opts,
|
||||
FridaSimmyDeviceSpawnProgramCompleteFunc on_complete, gpointer on_complete_target,
|
||||
GDestroyNotify on_complete_target_destroy_notify)
|
||||
{
|
||||
const FridaStdio stdio = opts->stdio;
|
||||
SimDevice * device;
|
||||
FridaSimmyContext * ctx;
|
||||
__block FridaStdioPipes * pipes;
|
||||
__block FridaFileDescriptor * in_fd, * out_fd, * err_fd;
|
||||
GError * error = NULL;
|
||||
__block GMainContext * main_context;
|
||||
|
||||
device = frida_simmy_device_get_handle (self);
|
||||
ctx = frida_simmy_device_get_simmy_context (self);
|
||||
|
||||
if (opts->has_envp)
|
||||
goto envp_not_supported;
|
||||
|
||||
pipes = frida_make_stdio_pipes (opts->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &error);
|
||||
if (error != NULL)
|
||||
goto propagate_error;
|
||||
|
||||
main_context = g_main_context_ref_thread_default ();
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
NSMutableDictionary<NSString *, id> * spawn_opts;
|
||||
__block FridaSimmySpawnedProcess * process = NULL;
|
||||
|
||||
spawn_opts = [@{
|
||||
ctx->SimDeviceSpawnKeyWaitForDebugger: @YES,
|
||||
} mutableCopy];
|
||||
|
||||
if (opts->has_argv)
|
||||
spawn_opts[ctx->SimDeviceSpawnKeyArguments] = frida_argv_to_arguments_array (opts->argv, opts->argv_length1, 0);
|
||||
|
||||
if (opts->has_env)
|
||||
spawn_opts[ctx->SimDeviceSpawnKeyEnvironment] = frida_envp_to_environment_dictionary (opts->env, opts->env_length1);
|
||||
|
||||
if (stdio == FRIDA_STDIO_PIPE)
|
||||
{
|
||||
spawn_opts[ctx->SimDeviceSpawnKeyStdin] = @(in_fd->handle);
|
||||
spawn_opts[ctx->SimDeviceSpawnKeyStdout] = @(out_fd->handle);
|
||||
spawn_opts[ctx->SimDeviceSpawnKeyStderr] = @(err_fd->handle);
|
||||
};
|
||||
|
||||
[device spawnAsyncWithPath:[NSString stringWithUTF8String:program]
|
||||
options:spawn_opts
|
||||
terminationQueue:ctx->dispatch_queue
|
||||
terminationHandler:
|
||||
^(int status)
|
||||
{
|
||||
_frida_simmy_spawned_process_on_termination (process, status);
|
||||
|
||||
g_object_unref (process);
|
||||
process = NULL;
|
||||
}
|
||||
completionQueue:ctx->dispatch_queue
|
||||
completionHandler:
|
||||
^(NSError * error, int pid)
|
||||
{
|
||||
if (error == nil)
|
||||
process = frida_simmy_spawned_process_new (pid, pipes, main_context);
|
||||
|
||||
g_main_context_unref (main_context);
|
||||
main_context = NULL;
|
||||
|
||||
g_clear_object (&pipes);
|
||||
g_clear_object (&in_fd);
|
||||
g_clear_object (&out_fd);
|
||||
g_clear_object (&err_fd);
|
||||
|
||||
on_complete (error.localizedDescription.UTF8String, process, on_complete_target);
|
||||
|
||||
if (on_complete_target_destroy_notify != NULL)
|
||||
on_complete_target_destroy_notify (on_complete_target);
|
||||
}];
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
envp_not_supported:
|
||||
{
|
||||
error = g_error_new_literal (
|
||||
FRIDA_ERROR,
|
||||
FRIDA_ERROR_NOT_SUPPORTED,
|
||||
"The 'envp' option is not supported when spawning Simmy programs, use the 'env' option instead");
|
||||
goto propagate_error;
|
||||
}
|
||||
propagate_error:
|
||||
{
|
||||
dispatch_async (ctx->dispatch_queue, ^
|
||||
{
|
||||
on_complete (error->message, NULL, on_complete_target);
|
||||
|
||||
g_error_free (error);
|
||||
|
||||
if (on_complete_target_destroy_notify != NULL)
|
||||
on_complete_target_destroy_notify (on_complete_target);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const gchar *
|
||||
_frida_simmy_runtime_get_identifier (FridaSimmyRuntime * self)
|
||||
{
|
||||
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).identifier.UTF8String;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
_frida_simmy_runtime_get_short_name (FridaSimmyRuntime * self)
|
||||
{
|
||||
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).shortName.UTF8String;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
_frida_simmy_runtime_get_version_string (FridaSimmyRuntime * self)
|
||||
{
|
||||
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).versionString.UTF8String;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
_frida_simmy_runtime_get_root (FridaSimmyRuntime * self)
|
||||
{
|
||||
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).root.UTF8String;
|
||||
}
|
||||
|
||||
static NSArray *
|
||||
frida_argv_to_arguments_array (gchar * const * argv, gint argv_length, gint start_index)
|
||||
{
|
||||
NSMutableArray * result;
|
||||
gint i;
|
||||
|
||||
result = [NSMutableArray arrayWithCapacity:argv_length];
|
||||
for (i = start_index; i < argv_length; i++)
|
||||
[result addObject:[NSString stringWithUTF8String:argv[i]]];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static NSDictionary *
|
||||
frida_envp_to_environment_dictionary (gchar * const * envp, gint envp_length)
|
||||
{
|
||||
NSMutableDictionary * result;
|
||||
gint i;
|
||||
|
||||
result = [NSMutableDictionary dictionaryWithCapacity:envp_length];
|
||||
for (i = 0; i != envp_length; i++)
|
||||
{
|
||||
const gchar * pair, * equals_sign, * name_start, * value_start;
|
||||
NSUInteger name_size, value_size;
|
||||
NSString * name, * value;
|
||||
|
||||
pair = envp[i];
|
||||
|
||||
equals_sign = strchr (pair, '=');
|
||||
if (equals_sign == NULL)
|
||||
continue;
|
||||
|
||||
name_start = pair;
|
||||
name_size = equals_sign - name_start;
|
||||
|
||||
value_start = equals_sign + 1;
|
||||
value_size = pair + strlen (pair) - value_start;
|
||||
|
||||
name = [[NSString alloc] initWithBytes:name_start
|
||||
length:name_size
|
||||
encoding:NSUTF8StringEncoding];
|
||||
value = [[NSString alloc] initWithBytes:value_start
|
||||
length:value_size
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
[result setObject:value forKey:name];
|
||||
|
||||
[value release];
|
||||
[name release];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,898 @@
|
||||
namespace Frida {
|
||||
public sealed class SimmyHostSessionBackend : Object, HostSessionBackend {
|
||||
private Gee.Map<string, SimmyHostSessionProvider> providers = new Gee.HashMap<string, SimmyHostSessionProvider> ();
|
||||
|
||||
private void * simmy_context;
|
||||
private MainContext main_context;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
construct {
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
}
|
||||
|
||||
public async void start (Cancellable? cancellable) throws IOError {
|
||||
simmy_context = _start (on_device_added, () => {
|
||||
schedule_on_frida_thread (start.callback);
|
||||
});
|
||||
yield;
|
||||
}
|
||||
|
||||
public async void stop (Cancellable? cancellable) throws IOError {
|
||||
_stop (simmy_context, () => {
|
||||
schedule_on_frida_thread (stop.callback);
|
||||
});
|
||||
yield;
|
||||
simmy_context = null;
|
||||
|
||||
io_cancellable.cancel ();
|
||||
|
||||
foreach (var provider in providers.values) {
|
||||
provider_unavailable (provider);
|
||||
yield provider.close (cancellable);
|
||||
}
|
||||
providers.clear ();
|
||||
}
|
||||
|
||||
private void on_device_added (Simmy.Device device) {
|
||||
schedule_on_frida_thread (() => {
|
||||
var prov = new SimmyHostSessionProvider (device);
|
||||
providers[device.udid] = prov;
|
||||
provider_available (prov);
|
||||
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void schedule_on_frida_thread (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
public extern static void * _start (DeviceAddedFunc on_device_added, owned Simmy.CompleteFunc on_complete);
|
||||
public extern static void _stop (void * simmy_context, owned Simmy.CompleteFunc on_complete);
|
||||
|
||||
public delegate void DeviceAddedFunc (Simmy.Device device);
|
||||
}
|
||||
|
||||
public sealed class SimmyHostSessionProvider : Object, HostSessionProvider {
|
||||
public Simmy.Device device {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public string id {
|
||||
get { return device.udid; }
|
||||
}
|
||||
|
||||
public string name {
|
||||
get { return device.name; }
|
||||
}
|
||||
|
||||
public Variant? icon {
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public HostSessionProviderKind kind {
|
||||
get {
|
||||
return HostSessionProviderKind.REMOTE;
|
||||
}
|
||||
}
|
||||
|
||||
private SimmyHostSession? host_session;
|
||||
|
||||
public SimmyHostSessionProvider (Simmy.Device device) {
|
||||
Object (device: device);
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
if (host_session == null)
|
||||
return;
|
||||
|
||||
host_session.agent_session_detached.disconnect (on_agent_session_detached);
|
||||
|
||||
yield host_session.close (cancellable);
|
||||
host_session = null;
|
||||
}
|
||||
|
||||
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
if (host_session != null)
|
||||
throw new Error.INVALID_OPERATION ("Already created");
|
||||
|
||||
HostSessionEntry local_system = yield hub.resolve_host_session ("local", cancellable);
|
||||
|
||||
host_session = new SimmyHostSession (device, local_system);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
return host_session;
|
||||
}
|
||||
|
||||
public async void destroy (HostSession session, Cancellable? cancellable) throws Error, IOError {
|
||||
if (session != host_session)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid host session");
|
||||
|
||||
host_session.agent_session_detached.disconnect (on_agent_session_detached);
|
||||
|
||||
yield host_session.close (cancellable);
|
||||
host_session = null;
|
||||
}
|
||||
|
||||
public async AgentSession link_agent_session (HostSession host_session, AgentSessionId id, AgentMessageSink sink,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
if (host_session != this.host_session)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid host session");
|
||||
|
||||
return yield this.host_session.link_agent_session (id, sink, cancellable);
|
||||
}
|
||||
|
||||
public void unlink_agent_session (HostSession host_session, AgentSessionId id) {
|
||||
if (host_session != this.host_session)
|
||||
return;
|
||||
|
||||
this.host_session.unlink_agent_session (id);
|
||||
}
|
||||
|
||||
public async IOStream link_channel (HostSession host_session, ChannelId id, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Channels are not supported by this backend");
|
||||
}
|
||||
|
||||
public void unlink_channel (HostSession host_session, ChannelId id) {
|
||||
}
|
||||
|
||||
public async ServiceSession link_service_session (HostSession host_session, ServiceSessionId id, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Services are not supported by this backend");
|
||||
}
|
||||
|
||||
public void unlink_service_session (HostSession host_session, ServiceSessionId id) {
|
||||
}
|
||||
|
||||
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
|
||||
agent_session_detached (id, reason, crash);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SimmyHostSession : Object, HostSession, HostSessionConnection {
|
||||
public Simmy.Device device {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public HostSessionEntry local_system {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public HostSession host_session {
|
||||
get {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private Gee.Map<uint, Simmy.SpawnedProcess> spawned_processes = new Gee.HashMap<uint, Simmy.SpawnedProcess> ();
|
||||
|
||||
private SpringboardAgent springboard_agent;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public SimmyHostSession (Simmy.Device device, HostSessionEntry local_system) {
|
||||
Object (device: device, local_system: local_system);
|
||||
}
|
||||
|
||||
construct {
|
||||
var s = local_system.session;
|
||||
s.process_crashed.connect (on_process_crashed);
|
||||
s.agent_session_detached.connect (on_agent_session_detached);
|
||||
s.uninjected.connect (on_uninjected);
|
||||
|
||||
springboard_agent = new SpringboardAgent (this);
|
||||
springboard_agent.unloaded.connect (on_springboard_agent_unloaded);
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
springboard_agent.unloaded.disconnect (on_springboard_agent_unloaded);
|
||||
yield springboard_agent.close (cancellable);
|
||||
|
||||
io_cancellable.cancel ();
|
||||
}
|
||||
|
||||
public async void ping (uint interval_seconds, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.INVALID_OPERATION ("Only meant to be implemented by services");
|
||||
}
|
||||
|
||||
public async HashTable<string, Variant> query_system_parameters (Cancellable? cancellable) throws Error, IOError {
|
||||
var parameters = new HashTable<string, Variant> (str_hash, str_equal);
|
||||
|
||||
var runtime = device.runtime;
|
||||
unowned string os_name = runtime.short_name;
|
||||
|
||||
var os = new HashTable<string, Variant> (str_hash, str_equal);
|
||||
os["id"] = os_name.down ();
|
||||
os["name"] = os_name.replace ("iOS", "iPhone OS");
|
||||
os["version"] = runtime.version_string;
|
||||
parameters["os"] = os;
|
||||
|
||||
parameters["platform"] = "darwin";
|
||||
|
||||
parameters["arch"] = "arm64";
|
||||
|
||||
var hardware = new HashTable<string, Variant> (str_hash, str_equal);
|
||||
hardware["product"] = device.model_identifier;
|
||||
parameters["hardware"] = hardware;
|
||||
|
||||
parameters["access"] = "full";
|
||||
|
||||
parameters["name"] = device.name;
|
||||
parameters["udid"] = device.udid;
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public async HostApplicationInfo get_frontmost_application (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var opts = FrontmostQueryOptions._deserialize (options);
|
||||
|
||||
try {
|
||||
return yield springboard_agent.get_frontmost_application (opts, cancellable);
|
||||
} catch (Error e) {
|
||||
if (e is Error.PERMISSION_DENIED)
|
||||
throw new Error.NOT_SUPPORTED ("Frontmost query is only supported with SIP disabled (for now)");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async HostApplicationInfo[] enumerate_applications (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var opts = ApplicationQueryOptions._deserialize (options);
|
||||
var scope = opts.scope;
|
||||
|
||||
Future<Gee.List<Simmy.Application>> list_apps_request = device.list_applications ();
|
||||
Future<Gee.List<LaunchdJob>> list_jobs_request = list_launchd_jobs (cancellable);
|
||||
|
||||
var selected_ids = new Gee.HashSet<string> ();
|
||||
opts.enumerate_selected_identifiers (identifier => {
|
||||
selected_ids.add (identifier);
|
||||
});
|
||||
bool include_all_apps = selected_ids.is_empty;
|
||||
|
||||
var apps = new Gee.ArrayList<Simmy.Application> ();
|
||||
foreach (Simmy.Application app in yield list_apps_request.wait_async (cancellable)) {
|
||||
unowned string id = app.identifier;
|
||||
if (include_all_apps || id in selected_ids)
|
||||
apps.add (app);
|
||||
}
|
||||
|
||||
Gee.Map<string, Bytes>? icons = null;
|
||||
if (scope == FULL) {
|
||||
var app_ids = new Gee.ArrayList<string> ();
|
||||
foreach (var app in apps)
|
||||
app_ids.add (app.identifier);
|
||||
try {
|
||||
icons = yield springboard_agent.fetch_application_icons (app_ids, cancellable);
|
||||
} catch (Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
var pids = new Gee.HashMap<string, uint> ();
|
||||
foreach (var job in yield list_jobs_request.wait_async (cancellable)) {
|
||||
if (job.pid == 0)
|
||||
continue;
|
||||
|
||||
unowned string label = job.label;
|
||||
if (!label.has_prefix ("UIKitApplication:"))
|
||||
continue;
|
||||
string identifier = label[17:].split ("[", 2)[0];
|
||||
|
||||
pids[identifier] = job.pid;
|
||||
}
|
||||
|
||||
var result = new HostApplicationInfo[0];
|
||||
foreach (var app in apps) {
|
||||
unowned string identifier = app.identifier;
|
||||
|
||||
var info = HostApplicationInfo (identifier, app.display_name, pids[identifier], make_parameters_dict ());
|
||||
if (scope == FULL && icons != null)
|
||||
add_app_icons (info.parameters, icons[identifier]);
|
||||
result += info;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async HostProcessInfo[] enumerate_processes (HashTable<string, Variant> options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var opts = ProcessQueryOptions._deserialize (options);
|
||||
|
||||
if (!opts.has_selected_pids ()) {
|
||||
foreach (var job in yield list_launchd_jobs (cancellable).wait_async (cancellable)) {
|
||||
if (job.pid != 0)
|
||||
opts.select_pid (job.pid);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return yield local_system.session.enumerate_processes (opts._serialize (), cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async HostChildInfo[] enumerate_pending_children (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async uint spawn (string program, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
Future<Simmy.SpawnedProcess> f;
|
||||
if (program[0] == '/')
|
||||
f = device.spawn_program (program, options);
|
||||
else
|
||||
f = device.launch_application (program, options);
|
||||
var process = yield f.wait_async (cancellable);
|
||||
uint pid = process.pid;
|
||||
|
||||
if (options.stdio == PIPE) {
|
||||
spawned_processes[pid] = process;
|
||||
process.terminated.connect (on_spawned_process_terminated);
|
||||
process.output.connect (on_spawned_process_output);
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
private void on_spawned_process_terminated (Simmy.SpawnedProcess process, int status) {
|
||||
spawned_processes.unset (process.pid);
|
||||
}
|
||||
|
||||
private void on_spawned_process_output (Simmy.SpawnedProcess process, int fd, uint8[] data) {
|
||||
output (process.pid, fd, data);
|
||||
|
||||
if (data.length == 0)
|
||||
spawned_processes.unset (process.pid);
|
||||
}
|
||||
|
||||
public async void input (uint pid, uint8[] data, Cancellable? cancellable) throws Error, IOError {
|
||||
var process = spawned_processes[pid];
|
||||
if (process == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid PID");
|
||||
yield process.input (data, cancellable);
|
||||
}
|
||||
|
||||
public async void resume (uint pid, Cancellable? cancellable) throws GLib.Error {
|
||||
yield local_system.session.resume (pid, cancellable);
|
||||
}
|
||||
|
||||
public async void kill (uint pid, Cancellable? cancellable) throws GLib.Error {
|
||||
yield local_system.session.kill (pid, cancellable);
|
||||
}
|
||||
|
||||
public async AgentSessionId attach (uint pid, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
return yield local_system.session.attach (pid, options, cancellable);
|
||||
}
|
||||
|
||||
public async void reattach (AgentSessionId id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.INVALID_OPERATION ("Only meant to be implemented by services");
|
||||
}
|
||||
|
||||
public async AgentSession link_agent_session (AgentSessionId id, AgentMessageSink sink,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
return yield local_system.provider.link_agent_session (local_system.session, id, sink, cancellable);
|
||||
}
|
||||
|
||||
public void unlink_agent_session (AgentSessionId id) {
|
||||
local_system.provider.unlink_agent_session (local_system.session, id);
|
||||
}
|
||||
|
||||
public async InjectorPayloadId inject_library_file (uint pid, string path, string entrypoint, string data,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
return yield local_system.session.inject_library_file (pid, path, entrypoint, data, cancellable);
|
||||
}
|
||||
|
||||
public async InjectorPayloadId inject_library_blob (uint pid, uint8[] blob, string entrypoint, string data,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
return yield local_system.session.inject_library_blob (pid, blob, entrypoint, data, cancellable);
|
||||
}
|
||||
|
||||
public async ChannelId open_channel (string address, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public IOStream link_channel (ChannelId id) throws Error {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public void unlink_channel (ChannelId id) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
public async ServiceSessionId open_service (string address, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public ServiceSession link_service_session (ServiceSessionId id) throws Error {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public void unlink_service_session (ServiceSessionId id) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
private void on_process_crashed (CrashInfo crash) {
|
||||
process_crashed (crash);
|
||||
}
|
||||
|
||||
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
|
||||
agent_session_detached (id, reason, crash);
|
||||
}
|
||||
|
||||
private void on_uninjected (InjectorPayloadId id) {
|
||||
uninjected (id);
|
||||
}
|
||||
|
||||
internal Future<Gee.List<LaunchdJob>> list_launchd_jobs (Cancellable? cancellable) {
|
||||
var promise = new Promise<Gee.List<LaunchdJob>> ();
|
||||
perform_list_launchd_jobs.begin (cancellable, promise);
|
||||
return promise.future;
|
||||
}
|
||||
|
||||
private async void perform_list_launchd_jobs (Cancellable? cancellable, Promise<Gee.List<LaunchdJob>> request) {
|
||||
try {
|
||||
RunResult res = yield run_and_capture_output ({
|
||||
Path.build_filename (device.runtime.root, "bin", "launchctl"),
|
||||
"list"
|
||||
}, cancellable).wait_async (cancellable);
|
||||
|
||||
res.check ();
|
||||
|
||||
var jobs = new Gee.ArrayList<LaunchdJob> ();
|
||||
string[] lines = res.standard_output.chomp ().split ("\n");
|
||||
foreach (string line in lines[1:]) {
|
||||
string[] tokens = line.split ("\t", 3);
|
||||
if (tokens.length != 3)
|
||||
throw new Error.PROTOCOL ("Unexpected launchctl output");
|
||||
|
||||
unowned string raw_pid = tokens[0];
|
||||
unowned string label = tokens[2];
|
||||
|
||||
jobs.add (new LaunchdJob () {
|
||||
pid = uint.parse (raw_pid),
|
||||
label = label,
|
||||
});
|
||||
}
|
||||
|
||||
request.resolve (jobs);
|
||||
} catch (GLib.Error e) {
|
||||
request.reject (e);
|
||||
}
|
||||
}
|
||||
|
||||
internal class LaunchdJob {
|
||||
public uint pid;
|
||||
public string label;
|
||||
}
|
||||
|
||||
private Future<RunResult> run_and_capture_output (string[] argv, Cancellable? cancellable) {
|
||||
var promise = new Promise<RunResult> ();
|
||||
perform_run_and_capture_output.begin (argv, cancellable, promise);
|
||||
return promise.future;
|
||||
}
|
||||
|
||||
private async void perform_run_and_capture_output (string[] argv, Cancellable? cancellable, Promise<RunResult> request) {
|
||||
string[] argv_copy = argv;
|
||||
|
||||
var opts = HostSpawnOptions ();
|
||||
opts.has_argv = true;
|
||||
opts.argv = argv_copy;
|
||||
opts.stdio = PIPE;
|
||||
|
||||
Simmy.SpawnedProcess process;
|
||||
try {
|
||||
process = yield device.spawn_program (argv_copy[0], opts).wait_async (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
request.reject (e);
|
||||
return;
|
||||
}
|
||||
|
||||
int status = -1;
|
||||
var standard_output = new StringBuilder.sized (1024);
|
||||
var standard_error = new StringBuilder.sized (1024);
|
||||
|
||||
process.terminated.connect ((s) => {
|
||||
status = s;
|
||||
perform_run_and_capture_output.callback ();
|
||||
});
|
||||
|
||||
process.output.connect ((fd, data) => {
|
||||
var buf = new char[data.length + 1];
|
||||
Memory.copy (buf, data, data.length);
|
||||
buf[data.length] = '\0';
|
||||
unowned StringBuilder target = (fd == 1) ? standard_output : standard_error;
|
||||
target.append ((string) buf);
|
||||
});
|
||||
|
||||
process.resume ();
|
||||
yield;
|
||||
|
||||
request.resolve (new RunResult (argv_copy) {
|
||||
status = status,
|
||||
standard_output = standard_output.str,
|
||||
standard_error = standard_error.str,
|
||||
});
|
||||
}
|
||||
|
||||
private class RunResult {
|
||||
public int status;
|
||||
public string standard_output;
|
||||
public string standard_error;
|
||||
|
||||
private string[] argv;
|
||||
|
||||
public RunResult (string[] argv) {
|
||||
this.argv = argv;
|
||||
}
|
||||
|
||||
public void check () throws Error {
|
||||
if (status != 0) {
|
||||
throw new Error.NOT_SUPPORTED ("Failed to run `%s`, it exited with status %d: %s",
|
||||
string.joinv (" ", argv),
|
||||
status,
|
||||
standard_error.chomp ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void on_springboard_agent_unloaded (InternalAgent dead_agent) {
|
||||
dead_agent.unloaded.disconnect (on_springboard_agent_unloaded);
|
||||
|
||||
springboard_agent = new SpringboardAgent (this);
|
||||
springboard_agent.unloaded.connect (on_springboard_agent_unloaded);
|
||||
}
|
||||
|
||||
[NoReturn]
|
||||
private static void throw_not_supported () throws Error {
|
||||
throw new Error.NOT_SUPPORTED ("Not yet supported by the Simmy backend");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SpringboardAgent : InternalAgent {
|
||||
public SpringboardAgent (HostSessionConnection connection) {
|
||||
Object (connection: connection);
|
||||
}
|
||||
|
||||
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var scope = options.scope;
|
||||
var scope_node = new Json.Node.alloc ().init_string (scope.to_nick ());
|
||||
|
||||
Json.Node response = yield call ("getFrontmostApplication", new Json.Node[] { scope_node }, null, cancellable);
|
||||
|
||||
if (response.get_node_type () == NULL)
|
||||
return HostApplicationInfo.empty ();
|
||||
|
||||
var item = response.get_array ();
|
||||
var identifier = item.get_string_element (0);
|
||||
var name = item.get_string_element (1);
|
||||
var pid = (uint) item.get_int_element (2);
|
||||
var info = HostApplicationInfo (identifier, name, pid, make_parameters_dict ());
|
||||
if (scope == FULL)
|
||||
add_app_icons (info.parameters, new Bytes.take (Base64.decode (item.get_string_element (3))));
|
||||
return info;
|
||||
}
|
||||
|
||||
public async Gee.Map<string, Bytes> fetch_application_icons (Gee.List<string> app_ids, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var app_id_values = new Json.Array ();
|
||||
foreach (var id in app_ids)
|
||||
app_id_values.add_string_element (id);
|
||||
|
||||
var args = new Json.Node[] { new Json.Node.alloc ().init_array (app_id_values) };
|
||||
|
||||
Json.Node response = yield call ("fetchApplicationIcons", args, null, cancellable);
|
||||
|
||||
var icons = new Gee.HashMap<string, Bytes> ();
|
||||
var reader = new Json.Reader (response);
|
||||
int n = reader.count_elements ();
|
||||
for (int i = 0; i != n; i++) {
|
||||
reader.read_element (i);
|
||||
if (!reader.get_null_value ())
|
||||
icons[app_ids[i]] = new Bytes (Base64.decode (reader.get_string_value ()));
|
||||
reader.end_element ();
|
||||
}
|
||||
return icons;
|
||||
}
|
||||
|
||||
protected override async uint get_target_pid (Cancellable? cancellable) throws Error, IOError {
|
||||
var host_session = (SimmyHostSession) connection.host_session;
|
||||
|
||||
foreach (var job in yield host_session.list_launchd_jobs (cancellable).wait_async (cancellable)) {
|
||||
if (job.label == "com.apple.SpringBoard")
|
||||
return job.pid;
|
||||
}
|
||||
|
||||
throw new Error.NOT_SUPPORTED ("Unable to locate SpringBoard process");
|
||||
}
|
||||
|
||||
protected override async string? load_source (Cancellable? cancellable) throws Error, IOError {
|
||||
return (string) Frida.Data.Simmy.get_springboard_js_blob ().data;
|
||||
}
|
||||
}
|
||||
|
||||
private void add_app_icons (HashTable<string, Variant> parameters, Bytes png) {
|
||||
var icons = new VariantBuilder (new VariantType.array (VariantType.VARDICT));
|
||||
|
||||
icons.open (VariantType.VARDICT);
|
||||
icons.add ("{sv}", "format", new Variant.string ("png"));
|
||||
icons.add ("{sv}", "image", Variant.new_from_data (new VariantType ("ay"), png.get_data (), true, png));
|
||||
icons.close ();
|
||||
|
||||
parameters["icons"] = icons.end ();
|
||||
}
|
||||
|
||||
[CCode (gir_namespace = "FridaSimmy", gir_version = "1.0")]
|
||||
namespace Simmy {
|
||||
public sealed class Device : Object {
|
||||
public void * handle {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public string udid {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public string name {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public string model_identifier {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Runtime runtime {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public void * simmy_context {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Device (void * handle, string udid, string name, string model_identifier, Runtime runtime,
|
||||
void * simmy_context) {
|
||||
Object (
|
||||
handle: handle,
|
||||
udid: udid,
|
||||
name: name,
|
||||
model_identifier: model_identifier,
|
||||
runtime: runtime,
|
||||
simmy_context: simmy_context
|
||||
);
|
||||
}
|
||||
|
||||
public Future<Gee.List<Application>> list_applications () {
|
||||
var promise = new Promise<Gee.List<Application>> ();
|
||||
|
||||
_list_applications (applications => {
|
||||
promise.resolve (applications);
|
||||
});
|
||||
|
||||
return promise.future;
|
||||
}
|
||||
|
||||
public Future<SpawnedProcess> launch_application (string identifier, HostSpawnOptions opts) {
|
||||
var promise = new Promise<SpawnedProcess> ();
|
||||
|
||||
_launch_application (identifier, opts, (error_message, process) => {
|
||||
if (error_message != null)
|
||||
promise.reject (new Error.NOT_SUPPORTED ("%s", error_message));
|
||||
else
|
||||
promise.resolve (process);
|
||||
});
|
||||
|
||||
return promise.future;
|
||||
}
|
||||
|
||||
public Future<SpawnedProcess> spawn_program (string program, HostSpawnOptions opts) {
|
||||
var promise = new Promise<SpawnedProcess> ();
|
||||
|
||||
_spawn_program (program, opts, (error_message, process) => {
|
||||
if (error_message != null)
|
||||
promise.reject (new Error.NOT_SUPPORTED ("%s", error_message));
|
||||
else
|
||||
promise.resolve (process);
|
||||
});
|
||||
|
||||
return promise.future;
|
||||
}
|
||||
|
||||
public extern void _list_applications (owned ListApplicationsCompleteFunc on_complete);
|
||||
public extern void _launch_application (string identifier, HostSpawnOptions options,
|
||||
owned LaunchApplicationCompleteFunc on_complete);
|
||||
public extern void _spawn_program (string program, HostSpawnOptions options,
|
||||
owned SpawnProgramCompleteFunc on_complete);
|
||||
|
||||
public delegate void ListApplicationsCompleteFunc (Gee.List<Application> device);
|
||||
public delegate void LaunchApplicationCompleteFunc (string? error_message, SpawnedProcess? process);
|
||||
public delegate void SpawnProgramCompleteFunc (string? error_message, SpawnedProcess? process);
|
||||
}
|
||||
|
||||
public sealed class Runtime : Object {
|
||||
public void * handle {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public string identifier {
|
||||
get {
|
||||
return _get_identifier ();
|
||||
}
|
||||
}
|
||||
|
||||
public string short_name {
|
||||
get {
|
||||
return _get_short_name ();
|
||||
}
|
||||
}
|
||||
|
||||
public string version_string {
|
||||
get {
|
||||
return _get_version_string ();
|
||||
}
|
||||
}
|
||||
|
||||
public string root {
|
||||
get {
|
||||
return _get_root ();
|
||||
}
|
||||
}
|
||||
|
||||
public Runtime (void * handle) {
|
||||
Object (handle: handle);
|
||||
}
|
||||
|
||||
public extern unowned string _get_identifier ();
|
||||
public extern unowned string _get_short_name ();
|
||||
public extern unowned string _get_version_string ();
|
||||
public extern unowned string _get_root ();
|
||||
}
|
||||
|
||||
public sealed class Application : Object {
|
||||
public string identifier {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public string display_name {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Application (string identifier, string display_name) {
|
||||
Object (identifier: identifier, display_name: display_name);
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void CompleteFunc ();
|
||||
|
||||
public sealed class SpawnedProcess : Object {
|
||||
public signal void terminated (int status);
|
||||
public signal void output (int fd, uint8[] data);
|
||||
|
||||
public uint pid {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public StdioPipes? pipes {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public MainContext main_context {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private OutputStream? stdin_stream;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public SpawnedProcess (uint pid, StdioPipes? pipes, MainContext main_context) {
|
||||
Object (
|
||||
pid: pid,
|
||||
pipes: pipes,
|
||||
main_context: main_context
|
||||
);
|
||||
}
|
||||
|
||||
construct {
|
||||
if (pipes != null) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
start_processing_pipes ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
|
||||
private void start_processing_pipes () {
|
||||
stdin_stream = pipes.input;
|
||||
process_next_output_from.begin (pipes.output, 1);
|
||||
process_next_output_from.begin (pipes.error, 2);
|
||||
}
|
||||
|
||||
public void close () {
|
||||
io_cancellable.cancel ();
|
||||
}
|
||||
|
||||
public void resume () {
|
||||
Posix.kill ((Posix.pid_t) pid, Posix.Signal.CONT);
|
||||
}
|
||||
|
||||
public void _on_termination (int status) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
if (stdin_stream != null) {
|
||||
stdin_stream.close_async.begin ();
|
||||
stdin_stream = null;
|
||||
}
|
||||
|
||||
close ();
|
||||
|
||||
terminated (status);
|
||||
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
public async void input (uint8[] data, Cancellable? cancellable) throws Error, IOError {
|
||||
if (stdin_stream == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to pass input to process spawned without piped stdio");
|
||||
|
||||
try {
|
||||
yield stdin_stream.write_all_async (data, Priority.DEFAULT, cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private async void process_next_output_from (InputStream stream, int fd) {
|
||||
try {
|
||||
var buf = new uint8[4096];
|
||||
var n = yield stream.read_async (buf, Priority.DEFAULT, io_cancellable);
|
||||
|
||||
var data = buf[0:n];
|
||||
output (fd, data);
|
||||
|
||||
if (n > 0)
|
||||
process_next_output_from.begin (stream, fd);
|
||||
} catch (GLib.Error e) {
|
||||
stream.close_async.begin ();
|
||||
|
||||
if (!(e is IOError.CANCELLED))
|
||||
output (fd, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
[CCode (gir_namespace = "FridaSimmy", gir_version = "1.0")]
|
||||
namespace Frida.TextPList {
|
||||
public class Parser {
|
||||
private Lexer lex;
|
||||
private Token look;
|
||||
|
||||
public Parser (string text) {
|
||||
lex = new Lexer (text);
|
||||
}
|
||||
|
||||
public Variant parse () throws Error {
|
||||
look = lex.next_token ();
|
||||
|
||||
Variant v = parse_value ();
|
||||
if (look.type != EOF)
|
||||
throw new Error.PROTOCOL ("Trailing content after root value");
|
||||
return v;
|
||||
}
|
||||
|
||||
private Variant parse_value () throws Error {
|
||||
switch (look.type) {
|
||||
case LBRACE: return parse_dict ();
|
||||
case LPAREN: return parse_array ();
|
||||
case NUMBER: return parse_number ();
|
||||
case STRING: return parse_string ();
|
||||
default:
|
||||
throw new Error.PROTOCOL (@"Unexpected token %s".printf (look.type.to_string ()));
|
||||
}
|
||||
}
|
||||
|
||||
private Variant parse_dict () throws Error {
|
||||
eat (LBRACE);
|
||||
|
||||
var b = new VariantBuilder (new VariantType ("a{sv}"));
|
||||
while (look.type != RBRACE) {
|
||||
if (look.type != STRING)
|
||||
throw new Error.PROTOCOL ("Dictionary key must be STRING");
|
||||
|
||||
string key = look.text;
|
||||
eat (STRING);
|
||||
|
||||
eat (EQUAL);
|
||||
|
||||
b.add ("{sv}", key, parse_value ());
|
||||
|
||||
if (look.type == SEMI)
|
||||
eat (SEMI);
|
||||
|
||||
if (look.type == RBRACE)
|
||||
break;
|
||||
}
|
||||
|
||||
eat (RBRACE);
|
||||
|
||||
return b.end ();
|
||||
}
|
||||
|
||||
private Variant parse_array () throws Error {
|
||||
eat (LPAREN);
|
||||
|
||||
var b = new VariantBuilder (new VariantType ("av"));
|
||||
if (look.type != RPAREN) {
|
||||
b.add_value (parse_value ());
|
||||
while (look.type == COMMA) {
|
||||
eat (COMMA);
|
||||
b.add_value (new Variant.variant (parse_value ()));
|
||||
}
|
||||
}
|
||||
|
||||
eat (RPAREN);
|
||||
|
||||
return b.end ();
|
||||
}
|
||||
|
||||
private Variant parse_number () throws Error {
|
||||
string raw = look.text;
|
||||
eat (NUMBER);
|
||||
|
||||
if (raw.index_of_char ('.') != -1) {
|
||||
double d;
|
||||
if (!double.try_parse (raw, out d))
|
||||
throw new Error.PROTOCOL (@"Bad double: %s".printf (raw));
|
||||
return d;
|
||||
} else {
|
||||
int64 x;
|
||||
if (!int64.try_parse (raw, out x))
|
||||
throw new Error.PROTOCOL (@"Bad int: %s".printf (raw));
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
private Variant parse_string () throws Error {
|
||||
string s = look.text;
|
||||
eat (STRING);
|
||||
return s;
|
||||
}
|
||||
|
||||
private void eat (TokenType t) throws Error {
|
||||
if (look.type != t)
|
||||
throw new Error.PROTOCOL (@"Expected %s but found %s".printf (t.to_string (), look.type.to_string ()));
|
||||
look = lex.next_token ();
|
||||
}
|
||||
}
|
||||
|
||||
private class Lexer {
|
||||
private string s;
|
||||
private int i = 0;
|
||||
private int n;
|
||||
|
||||
public Lexer (string input) {
|
||||
s = input;
|
||||
n = s.length;
|
||||
}
|
||||
|
||||
public Token next_token () throws Error {
|
||||
skip_whitespace_and_comments ();
|
||||
|
||||
if (at_end ())
|
||||
return new Token (EOF);
|
||||
|
||||
switch (peek ()) {
|
||||
case '{': next (); return new Token (LBRACE);
|
||||
case '}': next (); return new Token (RBRACE);
|
||||
case '(': next (); return new Token (LPAREN);
|
||||
case ')': next (); return new Token (RPAREN);
|
||||
case '=': next (); return new Token (EQUAL);
|
||||
case ';': next (); return new Token (SEMI);
|
||||
case ',': next (); return new Token (COMMA);
|
||||
case '"': return new Token (STRING, read_quoted ());
|
||||
default:
|
||||
var word = read_bare ();
|
||||
if (word.length == 0)
|
||||
throw new Error.PROTOCOL (@"Unexpected character '%c'".printf (peek ()));
|
||||
return read_number_or_bareword (word);
|
||||
}
|
||||
}
|
||||
|
||||
private bool at_end () {
|
||||
return i >= n;
|
||||
}
|
||||
|
||||
private char peek () {
|
||||
return s[i];
|
||||
}
|
||||
|
||||
private char next () {
|
||||
return s[i++];
|
||||
}
|
||||
|
||||
private void skip_whitespace_and_comments () {
|
||||
while (!at_end ()) {
|
||||
if (is_whitespace (peek ())) {
|
||||
next ();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (peek () == '/' && i + 1 < n) {
|
||||
if (s[i + 1] == '/') {
|
||||
i += 2;
|
||||
while (!at_end () && peek () != '\n')
|
||||
next ();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s[i + 1] == '*') {
|
||||
i += 2;
|
||||
while (!at_end ()) {
|
||||
if (peek () == '*' && i + 1 < n && s[i + 1] == '/') {
|
||||
i += 2;
|
||||
break;
|
||||
}
|
||||
next ();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool is_whitespace (char c) {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
private string read_quoted () throws Error {
|
||||
next ();
|
||||
|
||||
var sb = new StringBuilder ();
|
||||
while (!at_end ()) {
|
||||
char c = next ();
|
||||
if (c == '\\') {
|
||||
if (at_end ())
|
||||
break;
|
||||
|
||||
char e = next ();
|
||||
switch (e) {
|
||||
case 'n':
|
||||
sb.append_c ('\n');
|
||||
break;
|
||||
case 'r':
|
||||
sb.append_c ('\r');
|
||||
break;
|
||||
case 't':
|
||||
sb.append_c ('\t');
|
||||
break;
|
||||
case '"':
|
||||
sb.append_c ('"');
|
||||
break;
|
||||
case '\\':
|
||||
sb.append_c ('\\');
|
||||
break;
|
||||
default:
|
||||
sb.append_c (e);
|
||||
break;
|
||||
}
|
||||
} else if (c == '"') {
|
||||
return sb.str;
|
||||
} else {
|
||||
sb.append_c (c);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error.PROTOCOL ("Unterminated quoted string");
|
||||
}
|
||||
|
||||
private string read_bare () {
|
||||
int start = i;
|
||||
while (!at_end () && is_bare_char (peek ()))
|
||||
next ();
|
||||
return s.substring (start, i - start);
|
||||
}
|
||||
|
||||
private static bool is_bare_char (char c) {
|
||||
if (c.isalnum ())
|
||||
return true;
|
||||
const string extra = "._-/:$+@";
|
||||
return extra.index_of_char (c) != -1;
|
||||
}
|
||||
|
||||
private Token read_number_or_bareword (string word) {
|
||||
bool numeric = true;
|
||||
bool has_dot = false;
|
||||
|
||||
int len = word.length;
|
||||
for (int k = 0; k != len; k++) {
|
||||
char c = word[k];
|
||||
|
||||
if (k == 0 && (c == '+' || c == '-'))
|
||||
continue;
|
||||
|
||||
if (c == '.') {
|
||||
if (has_dot) {
|
||||
numeric = false;
|
||||
break;
|
||||
}
|
||||
has_dot = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!c.isdigit ()) {
|
||||
numeric = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (numeric && word.length > 0 && !(word == "+" || word == "-"))
|
||||
return new Token (NUMBER, word);
|
||||
|
||||
return new Token (STRING, word);
|
||||
}
|
||||
}
|
||||
|
||||
private class Token {
|
||||
public TokenType type;
|
||||
public string text;
|
||||
|
||||
public Token (TokenType t, string s = "") {
|
||||
type = t;
|
||||
text = s;
|
||||
}
|
||||
}
|
||||
|
||||
private enum TokenType {
|
||||
LBRACE,
|
||||
RBRACE,
|
||||
LPAREN,
|
||||
RPAREN,
|
||||
EQUAL,
|
||||
SEMI,
|
||||
COMMA,
|
||||
STRING,
|
||||
NUMBER,
|
||||
EOF,
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,8 @@ namespace Frida {
|
||||
io_cancellable.cancel ();
|
||||
}
|
||||
|
||||
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
string? raw_address = null;
|
||||
TlsCertificate? certificate = null;
|
||||
string? origin = null;
|
||||
|
||||
@@ -298,25 +298,4 @@ namespace Frida {
|
||||
|
||||
public extern void _do_resume ();
|
||||
}
|
||||
|
||||
public sealed class StdioPipes : Object {
|
||||
public OutputStream input {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public InputStream output {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public InputStream error {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public StdioPipes (OutputStream input, InputStream output, InputStream error) {
|
||||
Object (input: input, output: output, error: error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/frida-gum.git
|
||||
revision = 17.3.2
|
||||
revision = 17.6.2
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/lwip.git
|
||||
revision = 4fe7223c2e80dc328266fb2483e789ee5fad7c79
|
||||
revision = 334ebb20a22023db2f0d5017e96eb0c7798082fd
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/openssl.git
|
||||
revision = 5029c4562d23547d8e29768e1b0de573c52bf3fc
|
||||
revision = fa60a1c8c704e4ca0cc0dcb289c3be1fea1b50ff
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/selinux.git
|
||||
revision = 6d5513fd8069e9ff9b7aa10970d34457b32970c8
|
||||
revision = b7c8cd70d821bb3fc69500e6273149299b80b923
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -231,7 +231,9 @@ namespace Frida.Test {
|
||||
ARM_32,
|
||||
ARM_64,
|
||||
MIPS,
|
||||
MIPSEL
|
||||
MIPSEL,
|
||||
RISCV32,
|
||||
RISCV64
|
||||
}
|
||||
|
||||
public enum Arch {
|
||||
|
||||
@@ -470,7 +470,8 @@ namespace Frida.HostSessionTest {
|
||||
get { return HostSessionProviderKind.LOCAL; }
|
||||
}
|
||||
|
||||
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Not implemented");
|
||||
}
|
||||
|
||||
@@ -1536,7 +1537,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var session = yield prov.create (null, cancellable);
|
||||
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
var applications = yield session.enumerate_applications (make_parameters_dict (), cancellable);
|
||||
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
|
||||
@@ -1577,7 +1578,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var host_session = yield prov.create (null, cancellable);
|
||||
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
uint pid = 0;
|
||||
bool waiting = false;
|
||||
@@ -1776,7 +1777,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var session = yield prov.create (null, cancellable);
|
||||
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
var applications = yield session.enumerate_applications (make_parameters_dict (), cancellable);
|
||||
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
|
||||
@@ -1814,7 +1815,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var host_session = yield prov.create (null, cancellable);
|
||||
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
uint pid = 0;
|
||||
bool waiting = false;
|
||||
@@ -1919,7 +1920,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var host_session = yield prov.create (null, cancellable);
|
||||
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
uint pid = 0;
|
||||
bool waiting = false;
|
||||
@@ -2398,7 +2399,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var host_session = yield prov.create (null, cancellable);
|
||||
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
var id = yield host_session.attach (pid, make_parameters_dict (), cancellable);
|
||||
var session = yield prov.link_agent_session (host_session, id, h, cancellable);
|
||||
@@ -2549,7 +2550,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var session = yield prov.create (null, cancellable);
|
||||
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
|
||||
assert_true (processes.length > 0);
|
||||
@@ -2576,7 +2577,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var host_session = yield prov.create (null, cancellable);
|
||||
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
uint pid = 0;
|
||||
bool waiting = false;
|
||||
@@ -3307,7 +3308,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var session = yield prov.create (null, cancellable);
|
||||
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
|
||||
assert_true (processes.length > 0);
|
||||
@@ -3333,7 +3334,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var host_session = yield prov.create (null, cancellable);
|
||||
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
|
||||
uint pid = 0;
|
||||
bool waiting = false;
|
||||
@@ -3584,7 +3585,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var session = yield prov.create (null, cancellable);
|
||||
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
|
||||
assert_true (processes.length > 0);
|
||||
|
||||
@@ -3617,7 +3618,7 @@ namespace Frida.HostSessionTest {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
stdout.printf ("connecting to frida-server\n");
|
||||
var host_session = yield prov.create (null, cancellable);
|
||||
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
stdout.printf ("enumerating processes\n");
|
||||
var processes = yield host_session.enumerate_processes (make_parameters_dict (), cancellable);
|
||||
assert_true (processes.length > 0);
|
||||
@@ -4242,7 +4243,7 @@ namespace Frida.HostSessionTest {
|
||||
try {
|
||||
Cancellable? cancellable = null;
|
||||
|
||||
var session = yield prov.create (null, cancellable);
|
||||
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
|
||||
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
|
||||
assert_true (processes.length > 0);
|
||||
|
||||
|
||||
@@ -77,6 +77,18 @@ namespace Darwin.XNU {
|
||||
[CCode (cheader_filename = "sys/sysctl.h")]
|
||||
public int sysctlbyname (string name, void * oldp, size_t * oldlenp, void * newp = null, size_t newlen = 0);
|
||||
|
||||
[CCode (cheader_filename = "util.h")]
|
||||
public int openpty (out int amaster, out int aslave, [CCode (array_length=false, array_null_terminated=true)] char[] name,
|
||||
Posix.termios? termp, winsize? winp);
|
||||
|
||||
[CCode (cname = "struct winsize", has_type_id = false, cheader_filename = "sys/ttycom.h", destroy_function = "")]
|
||||
public struct winsize {
|
||||
public ushort ws_row;
|
||||
public ushort ws_col;
|
||||
public ushort ws_xpixel;
|
||||
public ushort ws_ypixel;
|
||||
}
|
||||
|
||||
[CCode (cname = "struct xinpgen", cheader_filename = "netinet/in_pcb.h")]
|
||||
public struct InetPcbGeneration {
|
||||
[CCode (cname = "xig_len")]
|
||||
@@ -104,4 +116,6 @@ namespace Darwin.XNU {
|
||||
|
||||
[CCode (cheader_filename = "fcntl.h")]
|
||||
public const int F_FULLFSYNC;
|
||||
[CCode (cheader_filename = "fcntl.h")]
|
||||
public const int F_SETNOSIGPIPE;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[CCode (lower_case_cprefix = "", gir_namespace = "FreeBSD", gir_version = "1.0")]
|
||||
namespace FreeBSD {
|
||||
[CCode (cheader_filename = "libutil.h")]
|
||||
public int openpty (out int amaster, out int aslave, [CCode (array_length=false, array_null_terminated=true)] char[] name,
|
||||
Posix.termios? termp, winsize? winp);
|
||||
|
||||
[CCode (cname = "struct winsize", has_type_id = false, cheader_filename = "sys/ttycom.h", destroy_function = "")]
|
||||
public struct winsize {
|
||||
public ushort ws_row;
|
||||
public ushort ws_col;
|
||||
public ushort ws_xpixel;
|
||||
public ushort ws_ypixel;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user