mirror of
https://github.com/frida/frida-core.git
synced 2026-11-10 01:51:27 -10:00
Compare commits
No files matched your search
+190
-15
@@ -16,13 +16,13 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { id: windows-x86_64, runner: '"windows-latest"' }
|
||||
- { id: windows-x86, runner: '"windows-latest"' }
|
||||
- { id: macos-x86_64, runner: '"macos-latest"' }
|
||||
- { id: macos-arm64, runner: '"macos-latest"' }
|
||||
- { id: linux-x86_64, runner: '"ubuntu-latest"' }
|
||||
- { id: linux-x86, runner: '"ubuntu-latest"' }
|
||||
- { id: freebsd-arm64, runner: '["self-hosted", "freebsd", "arm64"]' }
|
||||
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang' }
|
||||
- { id: windows-x86_64, runner: '"windows-latest"', sys: MINGW64, pkg: 'mingw-w64-x86_64-gcc' }
|
||||
- { id: windows-x86, runner: '"windows-latest"', sys: MINGW32, pkg: 'mingw-w64-i686-gcc' }
|
||||
- { id: macos-x86_64, runner: '"macos-latest"' }
|
||||
- { id: macos-arm64, runner: '"macos-latest"' }
|
||||
- { id: linux-x86_64, runner: '"ubuntu-latest"' }
|
||||
- { id: linux-x86, runner: '"ubuntu-latest"' }
|
||||
fail-fast: false
|
||||
runs-on: ${{ fromJSON(matrix.runner) }}
|
||||
steps:
|
||||
@@ -30,6 +30,18 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install MinGW toolchain for cgo support
|
||||
if: ${{ startsWith(matrix.id, 'windows-') }}
|
||||
id: msys2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.sys }}
|
||||
install: ${{ matrix.pkg }}
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
- name: Set up Apple certificates
|
||||
if: ${{ startsWith(matrix.id, 'macos-') }}
|
||||
uses: ./.github/actions/setup-apple-certificates
|
||||
@@ -48,18 +60,29 @@ jobs:
|
||||
- name: Build
|
||||
if: ${{ startsWith(matrix.id, 'windows-') }}
|
||||
run: |
|
||||
.\configure ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
.\configure `
|
||||
--host=${{ matrix.id }} `
|
||||
--enable-compiler-backend `
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
.\make
|
||||
env:
|
||||
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
|
||||
- name: Build
|
||||
if: ${{ !startsWith(matrix.id, 'windows-') && matrix.id != 'linux-x86' }}
|
||||
run: |
|
||||
./configure ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
--enable-compiler-backend \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Build
|
||||
if: matrix.id == 'linux-x86'
|
||||
run: |
|
||||
CC="gcc -m32" CXX="g++ -m32" STRIP="strip" \
|
||||
./configure --build=linux-x86 --host=linux-x86 ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
--build=linux-x86 \
|
||||
--host=linux-x86 \
|
||||
--enable-compiler-backend \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
if: ${{ !startsWith(matrix.id, 'linux-') }}
|
||||
@@ -68,6 +91,7 @@ jobs:
|
||||
name: core-devkit-${{ matrix.id }}
|
||||
path: build/src/devkit/
|
||||
- name: Test
|
||||
if: ${{ matrix.id != 'windows-arm64' && !startsWith(matrix.id, 'macos-') }}
|
||||
run: make test
|
||||
|
||||
cross:
|
||||
@@ -91,9 +115,17 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install ${{ matrix.pkg }}
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
- name: Build
|
||||
run: |
|
||||
./configure ${{ matrix.opts }} ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
${{ matrix.opts }} \
|
||||
${{ !contains(matrix.id, 'mips') && '--enable-compiler-backend' || '' }} \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
if: ${{ !startsWith(matrix.id, 'linux-') }}
|
||||
@@ -114,9 +146,17 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
- name: Build
|
||||
run: |
|
||||
./configure --host=$XTOOLS_HOST ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
--host=$XTOOLS_HOST \
|
||||
${{ !startsWith(matrix.arch, 'mips') && '--enable-compiler-backend' || '' }} \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -127,6 +167,131 @@ jobs:
|
||||
if: matrix.arch == 'x86' || matrix.arch == 'x86_64'
|
||||
run: make test
|
||||
|
||||
emulator:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [armbe8, arm64be]
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
./configure --host=$XTOOLS_HOST --without-prebuilds=sdk:host --enable-tests
|
||||
make
|
||||
- name: Run custom container
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: docker:stable
|
||||
options: -v ${{ github.workspace }}:/workspace:delegated
|
||||
run: |
|
||||
cat <<EOF > /workspace/initscript
|
||||
#!/bin/sh
|
||||
dmesg -n1
|
||||
mount -t proc none /proc
|
||||
mount -t sysfs none /sys
|
||||
mount -t tmpfs none /tmp
|
||||
mount -t devtmpfs none /dev
|
||||
mount -t 9p -o rw,sync,dirsync,relatime,trans=virtio,msize=131072,version=9p2000.L host0 /workspace
|
||||
ip link set lo up
|
||||
ip addr add 192.168.76.20/24 dev eth0
|
||||
ip link set eth0 up
|
||||
ip route add default via 192.168.76.2 dev eth0
|
||||
export LD_LIBRARY_PATH=/lib:/lib64:/libilp32
|
||||
/sbin/entropy
|
||||
cat /etc/motd
|
||||
export PS1='\u@frida:\w\$ '
|
||||
/root/frida-tests
|
||||
status=\$?
|
||||
echo "exit status: \$status"
|
||||
if [ \$status -eq 0 ]; then
|
||||
echo EXIT_OK
|
||||
elif [ \$status -gt 128 ]; then
|
||||
signal=\$((\$status - 128))
|
||||
echo "EXIT_FAIL (terminated by signal \$signal)"
|
||||
else
|
||||
echo "EXIT_FAIL (exit code \$status)"
|
||||
fi
|
||||
EOF
|
||||
|
||||
cat <<EOF > /workspace/launch.sh
|
||||
#!/bin/sh
|
||||
LOGFILE=/workspace/qemu.log
|
||||
: > "\$LOGFILE"
|
||||
timeout 120 qemu-system-aarch64 \
|
||||
-M virt \
|
||||
-machine virtualization=true \
|
||||
-machine type=virt \
|
||||
-cpu cortex-a72 \
|
||||
-smp 4 \
|
||||
-kernel /root/Image.gz \
|
||||
-initrd /root/initramfs.img \
|
||||
-append 'earlyprintk=serial,ttyAMA0 console=ttyAMA0 coredump_filter=0x3f' \
|
||||
-m 4096M \
|
||||
-net nic,id=eth \
|
||||
-net user,id=mynet,net=192.168.76.0/24 \
|
||||
-virtfs local,path=/workspace/,mount_tag=host0,security_model=passthrough,id=host0 \
|
||||
-nographic \
|
||||
-no-reboot \
|
||||
>> "\$LOGFILE" 2>&1 &
|
||||
QEMU_PID=\$!
|
||||
echo "QEMU started with PID \$QEMU_PID"
|
||||
tail -n 0 -f "\$LOGFILE" | while read line; do
|
||||
echo "\$line"
|
||||
echo "\$line" | grep "EXIT_OK" >/dev/null 2>&1
|
||||
if [ \$? -eq 0 ]; then
|
||||
echo "Success signal received from guest. Killing QEMU..."
|
||||
kill -9 "\$QEMU_PID"
|
||||
cat -n "\$LOGFILE"
|
||||
exit 0
|
||||
fi
|
||||
echo "\$line" | grep "EXIT_FAIL" >/dev/null 2>&1
|
||||
if [ \$? -eq 0 ]; then
|
||||
echo "Failure signal received from guest. Killing QEMU..."
|
||||
kill -9 "\$QEMU_PID"
|
||||
cat -n "\$LOGFILE"
|
||||
exit 1
|
||||
fi
|
||||
if ! kill -0 "\$QEMU_PID" 2>/dev/null; then
|
||||
echo "QEMU process has exited."
|
||||
cat -n "\$LOGFILE"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
EOF
|
||||
|
||||
cat <<EOF > /workspace/Dockerfile.ci
|
||||
FROM ghcr.io/frida/x-tools-linux-be-target:latest
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
cpio
|
||||
|
||||
RUN mkdir /tmp/initramfs
|
||||
WORKDIR /tmp/initramfs
|
||||
RUN cpio --extract < /root/initramfs.img
|
||||
|
||||
RUN mkdir /tmp/initramfs/workspace
|
||||
|
||||
COPY initscript /tmp/initramfs/init
|
||||
RUN chmod +x /tmp/initramfs/init
|
||||
|
||||
COPY build/tests /tmp/initramfs/root/
|
||||
RUN find . | cpio -o --format=newc -R root:root > /root/initramfs.img
|
||||
|
||||
COPY launch.sh /root/launch.sh
|
||||
RUN chmod +x /root/launch.sh
|
||||
ENTRYPOINT ["/root/launch.sh"]
|
||||
EOF
|
||||
|
||||
docker build -t ci-image -f /workspace/Dockerfile.ci /workspace
|
||||
docker run --rm -v /workspace:/workspace ci-image
|
||||
|
||||
mobile:
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -143,6 +308,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
- name: Set up Apple certificates
|
||||
if: startsWith(matrix.id, 'ios-')
|
||||
uses: ./.github/actions/setup-apple-certificates
|
||||
@@ -160,7 +330,10 @@ jobs:
|
||||
run: echo ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }} >> $GITHUB_ENV
|
||||
- name: Build
|
||||
run: |
|
||||
./configure --host=${{ matrix.id }} ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
./configure \
|
||||
--host=${{ matrix.id }} \
|
||||
--enable-compiler-backend \
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -250,6 +423,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
@@ -263,7 +437,7 @@ jobs:
|
||||
- name: Check for unexpected changes
|
||||
run: |
|
||||
git config --global --add safe.directory "$(realpath .)"
|
||||
status_output="$(git status --porcelain)"
|
||||
status_output=$(git status --porcelain tests/labrats/{resident-agent,simple-agent}-linux-${{ matrix.arch }}.so tests/labrats/{forker,sleeper,spawner}-linux-${{ matrix.arch }})
|
||||
if [ -n "$status_output" ]; then
|
||||
echo "Unexpected changes detected:"
|
||||
echo "$status_output"
|
||||
@@ -282,6 +456,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
@@ -295,7 +470,7 @@ jobs:
|
||||
- name: Check for unexpected changes
|
||||
run: |
|
||||
git config --global --add safe.directory "$(realpath .)"
|
||||
status_output="$(git status --porcelain)"
|
||||
status_output=$(git status --porcelain src/linux/helpers/{bootstrapper,loader}-${{ matrix.arch }}.bin)
|
||||
if [ -n "$status_output" ]; then
|
||||
echo "Unexpected changes detected:"
|
||||
echo "$status_output"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/build/
|
||||
/deps/
|
||||
/src/barebone/agent/target/
|
||||
/src/barebone/agent/xpacks/
|
||||
/src/barebone/helpers/memory-scanner/target/
|
||||
/src/barebone/helpers/target/
|
||||
/src/barebone/node_modules/
|
||||
|
||||
@@ -435,6 +435,7 @@ def compile(privdir: Path, state: State):
|
||||
environ={**build_env, **group.extra_environ},
|
||||
allowed_prebuilds=state.allowed_prebuilds,
|
||||
extra_meson_options=[
|
||||
"-Dcompiler_backend=disabled",
|
||||
"-Dhelper_modern=",
|
||||
"-Dhelper_legacy=",
|
||||
"-Dagent_modern=",
|
||||
@@ -499,6 +500,8 @@ def option_should_be_forwarded(k: "OptionKey",
|
||||
|
||||
if k.is_project():
|
||||
if is_for_us:
|
||||
if k.name == "compiler_backend":
|
||||
return False
|
||||
tokens = k.name.split("_")
|
||||
if tokens[0] in {"helper", "agent"} and tokens[-1] in {"modern", "legacy"}:
|
||||
return False
|
||||
|
||||
@@ -1400,12 +1400,12 @@ namespace Frida.Agent {
|
||||
if (nb_api.unload_library == null)
|
||||
parameters.append ("|eternal|sticky");
|
||||
/*
|
||||
* Disable ExitMonitor to work around a bug in Android's libndk_translation.so on Android 11.
|
||||
* We need to avoid modifying libc.so ranges that the translator potentially depends on, to
|
||||
* avoid blowing up when Interceptor's CPU cache flush results in the translated code being
|
||||
* discarded, which seems like an edge-case the translator doesn't handle.
|
||||
* Disable Exceptor and ExitMonitor to work around a bug in Android's libndk_translation.so
|
||||
* on Android 11. We need to avoid modifying libc.so ranges that the translator potentially
|
||||
* depends on, to avoid blowing up when Interceptor's CPU cache flush results in the translated
|
||||
* code being discarded, which seems like an edge-case the translator doesn't handle.
|
||||
*/
|
||||
parameters.append ("|exit-monitor:off");
|
||||
parameters.append ("|exceptor:off|exit-monitor:off");
|
||||
|
||||
emulated_bridge_state = new BridgeState (parameters.str);
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "frida-base.h"
|
||||
|
||||
GBytes *
|
||||
frida_make_bytes_with_owner (GType t_type,
|
||||
GBoxedCopyFunc t_dup_func,
|
||||
GDestroyNotify t_destroy_func,
|
||||
void * data,
|
||||
gsize size,
|
||||
gpointer owner)
|
||||
{
|
||||
return g_bytes_new_with_free_func (data, size, t_destroy_func, owner);
|
||||
}
|
||||
+107
-32
@@ -356,10 +356,20 @@ namespace Frida {
|
||||
return *((int8 *) get_pointer (offset, sizeof (int8)));
|
||||
}
|
||||
|
||||
public unowned Buffer write_int8 (size_t offset, int8 val) {
|
||||
*((int8 *) get_pointer (offset, sizeof (int8))) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint8 read_uint8 (size_t offset) {
|
||||
return *get_pointer (offset, sizeof (uint8));
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint8 (size_t offset, uint8 val) {
|
||||
*((uint8 *) get_pointer (offset, sizeof (uint8))) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int16 read_int16 (size_t offset) {
|
||||
int16 val = *((int16 *) get_pointer (offset, sizeof (int16)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
@@ -367,6 +377,14 @@ namespace Frida {
|
||||
: int16.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_int16 (size_t offset, int16 val) {
|
||||
int16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int16 *) get_pointer (offset, sizeof (int16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint16 read_uint16 (size_t offset) {
|
||||
uint16 val = *((uint16 *) get_pointer (offset, sizeof (uint16)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
@@ -374,6 +392,14 @@ namespace Frida {
|
||||
: uint16.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint16 (size_t offset, uint16 val) {
|
||||
uint16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint16 *) get_pointer (offset, sizeof (uint16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int32 read_int32 (size_t offset) {
|
||||
int32 val = *((int32 *) get_pointer (offset, sizeof (int32)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
@@ -381,6 +407,14 @@ namespace Frida {
|
||||
: int32.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_int32 (size_t offset, int32 val) {
|
||||
int32 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int32 *) get_pointer (offset, sizeof (int32))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint32 read_uint32 (size_t offset) {
|
||||
uint32 val = *((uint32 *) get_pointer (offset, sizeof (uint32)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
@@ -403,6 +437,14 @@ namespace Frida {
|
||||
: int64.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_int64 (size_t offset, int64 val) {
|
||||
int64 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int64 *) get_pointer (offset, sizeof (int64))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint64 read_uint64 (size_t offset) {
|
||||
uint64 val = *((uint64 *) get_pointer (offset, sizeof (uint64)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
@@ -446,7 +488,14 @@ namespace Frida {
|
||||
|
||||
public string read_fixed_string (size_t offset, size_t size) throws Error {
|
||||
string * start = (string *) get_pointer (offset, size);
|
||||
string val = start->substring (0, (long) size);
|
||||
size_t max_length = size_t.min (size, this.size - offset);
|
||||
string * end = memchr (start, 0, max_length);
|
||||
size_t n;
|
||||
if (end != null)
|
||||
n = end - start;
|
||||
else
|
||||
n = size;
|
||||
string val = start->substring (0, (long) n);
|
||||
if (!val.validate ())
|
||||
throw new Error.PROTOCOL ("Invalid UTF-8 string");
|
||||
return val;
|
||||
@@ -458,6 +507,18 @@ namespace Frida {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t offset, size_t size) {
|
||||
var data = new uint8[size + 1];
|
||||
Memory.copy (data, get_pointer (offset, size), size);
|
||||
return new Bytes.take ((owned) data);
|
||||
}
|
||||
|
||||
public unowned Buffer write_bytes (size_t offset, Bytes bytes) {
|
||||
size_t size = bytes.get_size ();
|
||||
Memory.copy (get_pointer (offset, size), bytes.get_data (), size);
|
||||
return this;
|
||||
}
|
||||
|
||||
private uint8 * get_pointer (size_t offset, size_t n) {
|
||||
size_t minimum_size = offset + n;
|
||||
assert (size >= minimum_size);
|
||||
@@ -467,121 +528,135 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public sealed class BufferReader {
|
||||
public size_t offset {
|
||||
get {
|
||||
return _offset;
|
||||
}
|
||||
}
|
||||
|
||||
public size_t available {
|
||||
get {
|
||||
return buffer.bytes.get_size () - offset;
|
||||
return buffer.bytes.get_size () - _offset;
|
||||
}
|
||||
}
|
||||
|
||||
private Buffer buffer;
|
||||
private size_t offset = 0;
|
||||
private size_t _offset = 0;
|
||||
|
||||
public BufferReader (Buffer buf) {
|
||||
buffer = buf;
|
||||
}
|
||||
|
||||
public uint64 read_pointer (size_t offset) throws Error {
|
||||
public uint64 read_pointer () throws Error {
|
||||
var pointer_size = buffer.pointer_size;
|
||||
check_available (pointer_size);
|
||||
var ptr = buffer.read_pointer (offset);
|
||||
offset += pointer_size;
|
||||
var ptr = buffer.read_pointer (_offset);
|
||||
_offset += pointer_size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public int8 read_int8 () throws Error {
|
||||
check_available (sizeof (int8));
|
||||
var val = buffer.read_int8 (offset);
|
||||
offset += sizeof (int8);
|
||||
var val = buffer.read_int8 (_offset);
|
||||
_offset += sizeof (int8);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint8 read_uint8 () throws Error {
|
||||
check_available (sizeof (uint8));
|
||||
var val = buffer.read_uint8 (offset);
|
||||
offset += sizeof (uint8);
|
||||
var val = buffer.read_uint8 (_offset);
|
||||
_offset += sizeof (uint8);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int16 read_int16 () throws Error {
|
||||
check_available (sizeof (int16));
|
||||
var val = buffer.read_int16 (offset);
|
||||
offset += sizeof (int16);
|
||||
var val = buffer.read_int16 (_offset);
|
||||
_offset += sizeof (int16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint16 read_uint16 () throws Error {
|
||||
check_available (sizeof (uint16));
|
||||
var val = buffer.read_uint16 (offset);
|
||||
offset += sizeof (uint16);
|
||||
var val = buffer.read_uint16 (_offset);
|
||||
_offset += sizeof (uint16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int32 read_int32 () throws Error {
|
||||
check_available (sizeof (int32));
|
||||
var val = buffer.read_int32 (offset);
|
||||
offset += sizeof (int32);
|
||||
var val = buffer.read_int32 (_offset);
|
||||
_offset += sizeof (int32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint32 read_uint32 () throws Error {
|
||||
check_available (sizeof (uint32));
|
||||
var val = buffer.read_uint32 (offset);
|
||||
offset += sizeof (uint32);
|
||||
var val = buffer.read_uint32 (_offset);
|
||||
_offset += sizeof (uint32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int64 read_int64 () throws Error {
|
||||
check_available (sizeof (int64));
|
||||
var val = buffer.read_int64 (offset);
|
||||
offset += sizeof (int64);
|
||||
var val = buffer.read_int64 (_offset);
|
||||
_offset += sizeof (int64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint64 read_uint64 () throws Error {
|
||||
check_available (sizeof (uint64));
|
||||
var val = buffer.read_uint64 (offset);
|
||||
offset += sizeof (uint64);
|
||||
var val = buffer.read_uint64 (_offset);
|
||||
_offset += sizeof (uint64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public float read_float () throws Error {
|
||||
check_available (sizeof (float));
|
||||
var val = buffer.read_float (offset);
|
||||
offset += sizeof (float);
|
||||
var val = buffer.read_float (_offset);
|
||||
_offset += sizeof (float);
|
||||
return val;
|
||||
}
|
||||
|
||||
public double read_double () throws Error {
|
||||
check_available (sizeof (double));
|
||||
var val = buffer.read_double (offset);
|
||||
offset += sizeof (double);
|
||||
var val = buffer.read_double (_offset);
|
||||
_offset += sizeof (double);
|
||||
return val;
|
||||
}
|
||||
|
||||
public string read_string () throws Error {
|
||||
check_available (1);
|
||||
var val = buffer.read_string (offset);
|
||||
offset += val.length + 1;
|
||||
var val = buffer.read_string (_offset);
|
||||
_offset += val.length + 1;
|
||||
return val;
|
||||
}
|
||||
|
||||
public string read_fixed_string (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = buffer.read_fixed_string (offset, size);
|
||||
offset += size;
|
||||
var val = buffer.read_fixed_string (_offset, size);
|
||||
_offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = buffer.bytes[offset:offset + size];
|
||||
offset += size;
|
||||
var val = buffer.bytes[_offset:_offset + size];
|
||||
_offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned BufferReader skip (size_t n) throws Error {
|
||||
check_available (n);
|
||||
_offset += n;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void check_available (size_t n) throws Error {
|
||||
if (available < n)
|
||||
throw new Error.PROTOCOL ("Malformed buffer: truncated");
|
||||
}
|
||||
}
|
||||
|
||||
public extern Bytes make_bytes_with_owner<T> (void * data, size_t size, owned T? owner = null);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
namespace Frida.FS {
|
||||
public void mkdirp (File dir, Cancellable? cancellable = null) throws Error {
|
||||
try {
|
||||
dir.make_directory_with_parents (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (!(e is IOError.EXISTS))
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public void rmtree (File dir, Cancellable? cancellable = null) throws Error {
|
||||
rmtree_worker (dir, 0, cancellable);
|
||||
}
|
||||
|
||||
private void rmtree_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error {
|
||||
FileEnumerator enumerator;
|
||||
try {
|
||||
enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (depth == 0 && e is IOError.NOT_FOUND)
|
||||
return;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
FileInfo? info;
|
||||
File? child;
|
||||
while (enumerator.iterate (out info, out child, cancellable) && info != null) {
|
||||
if (info == null)
|
||||
continue;
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
rmtree (child, cancellable);
|
||||
else
|
||||
child.delete (cancellable);
|
||||
}
|
||||
|
||||
dir.delete (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (!(e is IOError.EXISTS))
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void rmtree_async (File dir, Cancellable? cancellable = null) throws Error, IOError {
|
||||
yield rmtree_async_worker (dir, 0, cancellable);
|
||||
}
|
||||
|
||||
private async void rmtree_async_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error, IOError {
|
||||
int io_priority = Priority.DEFAULT;
|
||||
|
||||
FileEnumerator enumerator;
|
||||
try {
|
||||
enumerator = yield dir.enumerate_children_async (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, io_priority,
|
||||
cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (depth == 0 && e is IOError.NOT_FOUND)
|
||||
return;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var batch = yield enumerator.next_files_async (1000, io_priority, cancellable);
|
||||
if (batch.is_empty ())
|
||||
break;
|
||||
foreach (FileInfo info in batch) {
|
||||
File child = dir.get_child (info.get_name ());
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
yield rmtree_async_worker (child, depth + 1, cancellable);
|
||||
else
|
||||
yield child.delete_async (io_priority, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
yield dir.delete_async (io_priority, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async string read_all_text (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
Bytes b = yield read_all_bytes (f, cancellable);
|
||||
string text = (string) b.get_data ();
|
||||
if (!text.validate ())
|
||||
throw new Error.PROTOCOL ("Unable to read '%s': invalid UTF-8", f.get_parse_name ());
|
||||
return text;
|
||||
}
|
||||
|
||||
public async void write_all_text (File f, string t, Cancellable? cancellable) throws Error, IOError {
|
||||
yield write_all_bytes (f, new Bytes (t.data), cancellable);
|
||||
}
|
||||
|
||||
public async Bytes read_all_bytes (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
return yield f.load_bytes_async (cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
if (e is IOError.NOT_FOUND)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void write_all_bytes (File f, Bytes b, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
yield f.replace_contents_bytes_async (b, null, false, FileCreateFlags.NONE, cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef __FRIDA_SYSCALL_H__
|
||||
#define __FRIDA_SYSCALL_H__
|
||||
#ifndef __FRIDA_LINUX_SYSCALL_H__
|
||||
#define __FRIDA_LINUX_SYSCALL_H__
|
||||
|
||||
#include <linux/unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
@@ -14,7 +14,7 @@ namespace Frida {
|
||||
[CCode (cheader_filename = "sys/mman.h", cname = "MAP_ANONYMOUS")]
|
||||
public const int MAP_ANONYMOUS;
|
||||
|
||||
[CCode (cheader_filename = "linux/frida-syscall.h", cprefix = "SYS_", has_type_id = false)]
|
||||
[CCode (cheader_filename = "linux-syscall.h", cprefix = "SYS_", has_type_id = false)]
|
||||
public enum SysCall {
|
||||
memfd_create,
|
||||
pidfd_getfd,
|
||||
@@ -0,0 +1,107 @@
|
||||
namespace Frida {
|
||||
private uint linux_major = 0;
|
||||
private uint linux_minor = 0;
|
||||
|
||||
public bool check_kernel_version (uint major, uint minor) {
|
||||
if (linux_major == 0) {
|
||||
var name = Posix.utsname ();
|
||||
name.release.scanf ("%u.%u", out linux_major, out linux_minor);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
private PidFileDescriptor (int fd, uint pid) {
|
||||
base (fd);
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
public static bool is_supported () {
|
||||
return check_kernel_version (5, 3);
|
||||
}
|
||||
|
||||
public static bool getfd_is_supported () {
|
||||
return check_kernel_version (5, 6);
|
||||
}
|
||||
|
||||
public static PidFileDescriptor from_pid (uint pid) throws Error {
|
||||
int fd = pidfd_open (pid, 0);
|
||||
if (fd == -1)
|
||||
throw_pidfd_error (pid, errno);
|
||||
return new PidFileDescriptor (fd, pid);
|
||||
}
|
||||
|
||||
public FileDescriptor getfd (int targetfd) throws Error {
|
||||
int fd = pidfd_getfd (handle, targetfd, 0);
|
||||
if (fd == -1)
|
||||
throw_pidfd_error (pid, errno);
|
||||
return new FileDescriptor (fd);
|
||||
}
|
||||
|
||||
private static int pidfd_open (uint pid, uint flags) {
|
||||
return Linux.syscall (SysCall.pidfd_open, pid, flags);
|
||||
}
|
||||
|
||||
private static int pidfd_getfd (int pidfd, int targetfd, uint flags) {
|
||||
return Linux.syscall (SysCall.pidfd_getfd, pidfd, targetfd, flags);
|
||||
}
|
||||
|
||||
[NoReturn]
|
||||
private static void throw_pidfd_error (uint pid, int err) throws Error {
|
||||
switch (err) {
|
||||
case Posix.ESRCH:
|
||||
throw new Error.PROCESS_NOT_FOUND ("Process not found");
|
||||
case Posix.EPERM:
|
||||
throw new Error.PERMISSION_DENIED ("Unable to use pidfd for pid %u: %s", pid, strerror (err));
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unable to use pidfd for pid %u: %s", pid, strerror (err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace MemoryFileDescriptor {
|
||||
public bool is_supported () {
|
||||
return check_kernel_version (3, 17);
|
||||
}
|
||||
|
||||
public static FileDescriptor from_bytes (string name, Bytes bytes) {
|
||||
assert (is_supported ());
|
||||
|
||||
var fd = new FileDescriptor (memfd_create (name, 0));
|
||||
unowned uint8[] data = bytes.get_data ();
|
||||
ssize_t n = Posix.write (fd.handle, data, data.length);
|
||||
assert (n == data.length);
|
||||
return fd;
|
||||
}
|
||||
|
||||
private int memfd_create (string name, uint flags) {
|
||||
return Linux.syscall (SysCall.memfd_create, name, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-1
@@ -9,9 +9,11 @@ base_sources = [
|
||||
'p2p-glue.c',
|
||||
'dbus.vala',
|
||||
'promise.vala',
|
||||
'fs.vala',
|
||||
'socket.vala',
|
||||
'socket-glue.c',
|
||||
'buffer.vala',
|
||||
'buffer-glue.c',
|
||||
'value.vala',
|
||||
]
|
||||
|
||||
@@ -28,6 +30,15 @@ if host_os_family == 'darwin'
|
||||
base_sources += 'corefoundation.vala'
|
||||
endif
|
||||
|
||||
if host_os_family == 'linux'
|
||||
base_sources += 'linux.vala'
|
||||
extra_vala_args += [
|
||||
'--pkg=linux',
|
||||
'--pkg=linux-system',
|
||||
'--pkg=posix',
|
||||
]
|
||||
endif
|
||||
|
||||
extra_deps = []
|
||||
if host_os_family != 'windows'
|
||||
extra_vala_args += '--pkg=gio-unix-2.0'
|
||||
@@ -45,6 +56,12 @@ if host_os_family == 'darwin'
|
||||
'--pkg=darwin-xpc',
|
||||
]
|
||||
endif
|
||||
if host_os_family == 'linux'
|
||||
base_vala_args += [
|
||||
'--vapidir=' + meson.current_source_dir(),
|
||||
'--pkg=gio-unix-2.0',
|
||||
]
|
||||
endif
|
||||
|
||||
base_gir_name = f'FridaBase-@[email protected]'
|
||||
|
||||
@@ -62,7 +79,7 @@ base = static_library('frida-base-' + api_version, base_sources,
|
||||
base_dep = declare_dependency(
|
||||
include_directories: include_directories('.'),
|
||||
link_with: base,
|
||||
dependencies: [nice_dep, json_glib_dep, gum_dep],
|
||||
dependencies: [nice_dep, json_glib_dep, gum_dep] + extra_deps,
|
||||
)
|
||||
|
||||
base_build_dir = meson.current_build_dir()
|
||||
|
||||
+52
-12
@@ -46,7 +46,8 @@ namespace Frida {
|
||||
}
|
||||
private GLib.Error? _error;
|
||||
|
||||
private Gee.ArrayQueue<CompletionFuncEntry> on_complete;
|
||||
private Gee.ArrayQueue<CompletionFuncEntry>? on_complete;
|
||||
private Gee.ArrayQueue<ThenHandlerEntry<T>>? on_then;
|
||||
|
||||
public async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError {
|
||||
if (_ready)
|
||||
@@ -74,7 +75,21 @@ namespace Frida {
|
||||
return get_result ();
|
||||
}
|
||||
|
||||
private T get_result () throws Frida.Error, IOError {
|
||||
public void then (owned FutureCompletionHandler<T> handler) {
|
||||
if (_ready) {
|
||||
schedule (() => {
|
||||
handler (this);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (on_then == null)
|
||||
on_then = new Gee.ArrayQueue<ThenHandlerEntry<T>> ();
|
||||
on_then.offer (new ThenHandlerEntry<T> ((owned) handler));
|
||||
}
|
||||
|
||||
public T get_result () throws Frida.Error, IOError {
|
||||
if (error != null) {
|
||||
if (error is Frida.Error)
|
||||
throw (Frida.Error) error;
|
||||
@@ -111,19 +126,32 @@ namespace Frida {
|
||||
internal void transition_to_ready () {
|
||||
_ready = true;
|
||||
|
||||
if (on_complete != null && !on_complete.is_empty) {
|
||||
var source = new IdleSource ();
|
||||
source.set_priority (Priority.HIGH);
|
||||
source.set_callback (() => {
|
||||
CompletionFuncEntry? entry;
|
||||
while ((entry = on_complete.poll ()) != null)
|
||||
entry.func ();
|
||||
on_complete = null;
|
||||
return false;
|
||||
if (on_complete != null || on_then != null) {
|
||||
schedule (() => {
|
||||
if (on_complete != null) {
|
||||
CompletionFuncEntry? entry;
|
||||
while ((entry = on_complete.poll ()) != null)
|
||||
entry.func ();
|
||||
on_complete = null;
|
||||
}
|
||||
|
||||
if (on_then != null) {
|
||||
ThenHandlerEntry? entry;
|
||||
while ((entry = on_then.poll ()) != null)
|
||||
entry.handler (this);
|
||||
on_then = null;
|
||||
}
|
||||
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
}
|
||||
|
||||
private static void schedule (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
}
|
||||
|
||||
private class CompletionFuncEntry {
|
||||
@@ -133,6 +161,14 @@ namespace Frida {
|
||||
this.func = (owned) func;
|
||||
}
|
||||
}
|
||||
|
||||
private class ThenHandlerEntry<T> {
|
||||
public FutureCompletionHandler<T> handler;
|
||||
|
||||
public ThenHandlerEntry (owned FutureCompletionHandler<T> handler) {
|
||||
this.handler = (owned) handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface Future<T> : Object {
|
||||
@@ -140,5 +176,9 @@ namespace Frida {
|
||||
public abstract T? value { get; }
|
||||
public abstract GLib.Error? error { get; }
|
||||
public abstract async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError;
|
||||
public abstract void then (owned FutureCompletionHandler<T> handler);
|
||||
public abstract T get_result () throws Frida.Error, IOError;
|
||||
}
|
||||
|
||||
public delegate void FutureCompletionHandler<T> (Future<T> future);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ _frida_query_windows_computer_name (void)
|
||||
return g_utf16_to_utf8 (buffer, -1, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
#elif defined (HAVE_IOS) || defined (HAVE_TVOS)
|
||||
#elif defined (HAVE_IOS) || defined (HAVE_TVOS) || defined (HAVE_XROS)
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
@@ -749,6 +749,19 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.GadgetSession17")]
|
||||
public interface GadgetSession : Object {
|
||||
public abstract async void break_and_resume (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void break_and_detach (Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
public enum GadgetBreakpointAction {
|
||||
INVOKE_RETURN,
|
||||
RESUME,
|
||||
DETACH,
|
||||
PAGE_PLAN,
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.Channel17")]
|
||||
public interface Channel : Object {
|
||||
public abstract async void close (Cancellable? cancellable) throws GLib.Error;
|
||||
@@ -1952,6 +1965,8 @@ namespace Frida {
|
||||
id = "watchos";
|
||||
#elif TVOS
|
||||
id = "tvos";
|
||||
#elif XROS
|
||||
id = "xros";
|
||||
#elif ANDROID
|
||||
id = "android";
|
||||
#elif FREEBSD
|
||||
@@ -2119,6 +2134,7 @@ namespace Frida {
|
||||
public const string AGENT_SESSION = "/re/frida/AgentSession";
|
||||
public const string AGENT_CONTROLLER = "/re/frida/AgentController";
|
||||
public const string AGENT_MESSAGE_SINK = "/re/frida/AgentMessageSink";
|
||||
public const string GADGET_SESSION = "/re/frida/GadgetSession";
|
||||
public const string CHANNEL = "/re/frida/Channel";
|
||||
public const string SERVICE = "/re/frida/Service";
|
||||
public const string TRANSPORT_BROKER = "/re/frida/TransportBroker";
|
||||
|
||||
+166
-1
@@ -1,9 +1,25 @@
|
||||
#if defined (HAVE_TVOS) || defined (HAVE_WATCHOS)
|
||||
# include <Availability.h>
|
||||
# undef __TVOS_PROHIBITED
|
||||
# define __TVOS_PROHIBITED
|
||||
# undef __WATCHOS_PROHIBITED
|
||||
# define __WATCHOS_PROHIBITED
|
||||
#endif
|
||||
|
||||
#include "frida-gadget.h"
|
||||
|
||||
#include "frida-base.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <gum/gumdarwin.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <objc/runtime.h>
|
||||
#include <pthread.h>
|
||||
|
||||
static void frida_on_breakpoints_steal_attempt (GumInvocationContext * ic, gpointer user_data);
|
||||
|
||||
static GumInvocationListener * frida_dont_steal_my_breakpoints;
|
||||
|
||||
gchar *
|
||||
frida_gadget_environment_detect_bundle_id (void)
|
||||
{
|
||||
@@ -27,7 +43,7 @@ frida_gadget_environment_detect_bundle_name (void)
|
||||
gchar *
|
||||
frida_gadget_environment_detect_documents_dir (void)
|
||||
{
|
||||
#if defined (HAVE_IOS) || defined (HAVE_TVOS)
|
||||
#if defined (HAVE_IOS) || defined (HAVE_TVOS) || defined (HAVE_XROS)
|
||||
@autoreleasepool
|
||||
{
|
||||
NSArray<NSString *> * paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
@@ -51,3 +67,152 @@ frida_gadget_environment_set_thread_name (const gchar * name)
|
||||
/* For now only implemented on i/macOS as Fruity.Injector relies on it there. */
|
||||
pthread_setname_np (name);
|
||||
}
|
||||
|
||||
void
|
||||
frida_gadget_environment_detect_darwin_location_fields (GumAddress our_address, gchar ** executable_name, gchar ** our_path,
|
||||
GumMemoryRange ** our_range)
|
||||
{
|
||||
mach_port_t task;
|
||||
GumDarwinModuleResolver * resolver;
|
||||
GPtrArray * modules;
|
||||
guint i;
|
||||
|
||||
task = mach_task_self ();
|
||||
|
||||
resolver = gum_darwin_module_resolver_new (task, NULL);
|
||||
if (resolver == NULL)
|
||||
return;
|
||||
|
||||
gum_darwin_module_resolver_fetch_modules (resolver, &modules, NULL);
|
||||
|
||||
for (i = 0; i != modules->len; i++)
|
||||
{
|
||||
GumModule * module;
|
||||
const GumMemoryRange * range;
|
||||
|
||||
module = g_ptr_array_index (modules, i);
|
||||
range = gum_module_get_range (module);
|
||||
|
||||
if (*executable_name == NULL)
|
||||
{
|
||||
gum_mach_header_t * header = GSIZE_TO_POINTER (range->base_address);
|
||||
if (header->filetype == MH_EXECUTE)
|
||||
*executable_name = g_strdup (gum_module_get_name (module));
|
||||
}
|
||||
|
||||
if (our_address >= range->base_address && our_address < range->base_address + range->size)
|
||||
{
|
||||
if (*our_path == NULL)
|
||||
*our_path = g_strdup (gum_module_get_path (module));
|
||||
|
||||
if (*our_range == NULL)
|
||||
*our_range = gum_memory_range_copy (range);
|
||||
}
|
||||
|
||||
if (*executable_name != NULL && *our_path != NULL && *our_range != NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
g_ptr_array_unref (modules);
|
||||
|
||||
g_object_unref (resolver);
|
||||
}
|
||||
|
||||
void
|
||||
frida_gadget_environment_ensure_debugger_breakpoints_only (void)
|
||||
{
|
||||
task_set_exception_ports (
|
||||
mach_task_self (),
|
||||
EXC_MASK_ALL & ~EXC_MASK_BREAKPOINT,
|
||||
MACH_PORT_NULL,
|
||||
EXCEPTION_DEFAULT,
|
||||
THREAD_STATE_NONE
|
||||
);
|
||||
|
||||
if (gum_process_get_code_signing_policy () == GUM_CODE_SIGNING_OPTIONAL &&
|
||||
frida_dont_steal_my_breakpoints == NULL &&
|
||||
gum_darwin_is_debugger_mapping_enforced ())
|
||||
{
|
||||
gpointer exceptions_set, exceptions_swap;
|
||||
GumInterceptor * interceptor;
|
||||
|
||||
exceptions_set = &task_set_exception_ports;
|
||||
exceptions_swap = &task_swap_exception_ports;
|
||||
|
||||
frida_dont_steal_my_breakpoints = gum_make_call_listener (frida_on_breakpoints_steal_attempt,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
interceptor = gum_interceptor_obtain ();
|
||||
|
||||
gum_interceptor_attach (interceptor, exceptions_set, frida_dont_steal_my_breakpoints,
|
||||
NULL, GUM_ATTACH_FLAGS_NONE);
|
||||
gum_interceptor_attach (interceptor, exceptions_swap, frida_dont_steal_my_breakpoints,
|
||||
NULL, GUM_ATTACH_FLAGS_NONE);
|
||||
|
||||
g_object_unref (interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
frida_gadget_environment_allow_stolen_breakpoints (void)
|
||||
{
|
||||
GumInterceptor * interceptor;
|
||||
|
||||
if (frida_dont_steal_my_breakpoints == NULL)
|
||||
return;
|
||||
|
||||
interceptor = gum_interceptor_obtain ();
|
||||
|
||||
gum_interceptor_detach (interceptor, frida_dont_steal_my_breakpoints);
|
||||
|
||||
g_object_unref (frida_dont_steal_my_breakpoints);
|
||||
frida_dont_steal_my_breakpoints = NULL;
|
||||
|
||||
g_object_unref (interceptor);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_on_breakpoints_steal_attempt (GumInvocationContext * ic, gpointer user_data)
|
||||
{
|
||||
exception_mask_t exception_mask;
|
||||
|
||||
exception_mask = GPOINTER_TO_SIZE (gum_invocation_context_get_nth_argument (ic, 1));
|
||||
exception_mask &= ~EXC_MASK_BREAKPOINT;
|
||||
gum_invocation_context_replace_nth_argument (ic, 1, GSIZE_TO_POINTER (exception_mask));
|
||||
}
|
||||
|
||||
void
|
||||
frida_gadget_environment_break_and_resume (void)
|
||||
{
|
||||
#ifdef HAVE_ARM64
|
||||
asm volatile (
|
||||
"mov x1, #1337\n\t"
|
||||
"mov x2, #1337\n\t"
|
||||
"mov x3, %0\n\t"
|
||||
"brk #1337\n\t"
|
||||
:
|
||||
: "r" ((gsize) FRIDA_GADGET_BREAKPOINT_ACTION_RESUME)
|
||||
: "x1", "x2", "x3"
|
||||
);
|
||||
#else
|
||||
g_assert_not_reached ();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
frida_gadget_environment_break_and_detach (void)
|
||||
{
|
||||
#ifdef HAVE_ARM64
|
||||
asm volatile (
|
||||
"mov x1, #1337\n\t"
|
||||
"mov x2, #1337\n\t"
|
||||
"mov x3, %0\n\t"
|
||||
"brk #1337\n\t"
|
||||
:
|
||||
: "r" ((gsize) FRIDA_GADGET_BREAKPOINT_ACTION_DETACH)
|
||||
: "x1", "x2", "x3"
|
||||
);
|
||||
#else
|
||||
g_assert_not_reached ();
|
||||
#endif
|
||||
}
|
||||
+53
-85
@@ -24,12 +24,6 @@ namespace Frida.Gadget {
|
||||
default = Gum.CodeSigningPolicy.OPTIONAL;
|
||||
}
|
||||
|
||||
private ObjectClass klass = (ObjectClass) typeof (Config).class_ref ();
|
||||
|
||||
public Json.Node serialize_property (string property_name, GLib.Value value, GLib.ParamSpec pspec) {
|
||||
return default_serialize_property (property_name, value, pspec);
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "interaction" && property_node.get_node_type () == Json.NodeType.OBJECT) {
|
||||
var interaction_node = property_node.get_object ();
|
||||
@@ -77,20 +71,6 @@ namespace Frida.Gadget {
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
public unowned ParamSpec? find_property (string name) {
|
||||
return klass.find_property (name);
|
||||
}
|
||||
|
||||
public new Value get_property (ParamSpec pspec) {
|
||||
var val = Value (pspec.value_type);
|
||||
base.get_property (pspec.name, ref val);
|
||||
return val;
|
||||
}
|
||||
|
||||
public new void set_property (ParamSpec pspec, Value value) {
|
||||
base.set_property (pspec.name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ScriptInteraction : Object, Json.Serializable {
|
||||
@@ -112,12 +92,6 @@ namespace Frida.Gadget {
|
||||
default = Script.ChangeBehavior.IGNORE;
|
||||
}
|
||||
|
||||
private ObjectClass klass = (ObjectClass) typeof (ScriptInteraction).class_ref ();
|
||||
|
||||
public Json.Node serialize_property (string property_name, GLib.Value value, GLib.ParamSpec pspec) {
|
||||
return default_serialize_property (property_name, value, pspec);
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "parameters" && property_node.get_node_type () == Json.NodeType.OBJECT) {
|
||||
var v = Value (typeof (Json.Node));
|
||||
@@ -129,20 +103,6 @@ namespace Frida.Gadget {
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
public unowned ParamSpec? find_property (string name) {
|
||||
return klass.find_property (name);
|
||||
}
|
||||
|
||||
public new Value get_property (ParamSpec pspec) {
|
||||
var val = Value (pspec.value_type);
|
||||
base.get_property (pspec.name, ref val);
|
||||
return val;
|
||||
}
|
||||
|
||||
public new void set_property (ParamSpec pspec, Value value) {
|
||||
base.set_property (pspec.name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ScriptDirectoryInteraction : Object {
|
||||
@@ -183,12 +143,6 @@ namespace Frida.Gadget {
|
||||
default = Script.ChangeBehavior.IGNORE;
|
||||
}
|
||||
|
||||
private ObjectClass klass = (ObjectClass) typeof (ScriptConfig).class_ref ();
|
||||
|
||||
public Json.Node serialize_property (string property_name, GLib.Value value, GLib.ParamSpec pspec) {
|
||||
return default_serialize_property (property_name, value, pspec);
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "parameters" && property_node.get_node_type () == Json.NodeType.OBJECT) {
|
||||
var v = Value (typeof (Json.Node));
|
||||
@@ -200,20 +154,6 @@ namespace Frida.Gadget {
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
public unowned ParamSpec? find_property (string name) {
|
||||
return klass.find_property (name);
|
||||
}
|
||||
|
||||
public new Value get_property (ParamSpec pspec) {
|
||||
var val = Value (pspec.value_type);
|
||||
base.get_property (pspec.name, ref val);
|
||||
return val;
|
||||
}
|
||||
|
||||
public new void set_property (ParamSpec pspec, Value value) {
|
||||
base.set_property (pspec.name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ProcessFilter : Object {
|
||||
@@ -299,8 +239,6 @@ namespace Frida.Gadget {
|
||||
default = make_empty_json_object ();
|
||||
}
|
||||
|
||||
private ObjectClass klass = (ObjectClass) typeof (ConnectInteraction).class_ref ();
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "parameters" && property_node.get_node_type () == Json.NodeType.OBJECT) {
|
||||
var v = Value (typeof (Json.Node));
|
||||
@@ -312,20 +250,6 @@ namespace Frida.Gadget {
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
public unowned ParamSpec? find_property (string name) {
|
||||
return klass.find_property (name);
|
||||
}
|
||||
|
||||
public new Value get_property (ParamSpec pspec) {
|
||||
var val = Value (pspec.value_type);
|
||||
base.get_property (pspec.name, ref val);
|
||||
return val;
|
||||
}
|
||||
|
||||
public new void set_property (ParamSpec pspec, Value value) {
|
||||
base.set_property (pspec.name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Location : Object {
|
||||
@@ -481,6 +405,12 @@ namespace Frida.Gadget {
|
||||
|
||||
interceptor = Gum.Interceptor.obtain ();
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
#if DARWIN
|
||||
if (mapped_range != null)
|
||||
Environment.ensure_debugger_breakpoints_only ();
|
||||
#endif
|
||||
|
||||
exceptor = Gum.Exceptor.obtain ();
|
||||
|
||||
try {
|
||||
@@ -700,6 +630,11 @@ namespace Frida.Gadget {
|
||||
controller = null;
|
||||
|
||||
exceptor = null;
|
||||
|
||||
#if DARWIN
|
||||
Environment.allow_stolen_breakpoints ();
|
||||
#endif
|
||||
|
||||
interceptor = null;
|
||||
}
|
||||
}
|
||||
@@ -759,7 +694,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
|
||||
try {
|
||||
return Json.gobject_from_data (typeof (Config), config_data) as Config;
|
||||
return (Config) Json.gobject_from_data (typeof (Config), config_data);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid config: %s", e.message);
|
||||
}
|
||||
@@ -767,7 +702,7 @@ namespace Frida.Gadget {
|
||||
|
||||
private Config parse_config (string config_data) throws Error {
|
||||
try {
|
||||
return Json.gobject_from_data (typeof (Config), config_data) as Config;
|
||||
return (Config) Json.gobject_from_data (typeof (Config), config_data);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid config: %s", e.message);
|
||||
}
|
||||
@@ -775,21 +710,20 @@ namespace Frida.Gadget {
|
||||
|
||||
private Location detect_location (Gum.MemoryRange? mapped_range) {
|
||||
string? executable_name = null;
|
||||
string? executable_path = null;
|
||||
Gum.MemoryRange? executable_range = null;
|
||||
string? our_path = null;
|
||||
Gum.MemoryRange? our_range = mapped_range;
|
||||
|
||||
Gum.Address our_address = Gum.Address.from_pointer (Gum.strip_code_pointer ((void *) detect_location));
|
||||
|
||||
#if DARWIN
|
||||
Environment.detect_darwin_location_fields (our_address, ref executable_name, ref our_path, ref our_range);
|
||||
#else
|
||||
var index = 0;
|
||||
Gum.Process.enumerate_modules ((details) => {
|
||||
var range = details.range;
|
||||
|
||||
if (index == 0) {
|
||||
executable_name = details.name;
|
||||
executable_path = details.path;
|
||||
executable_range = details.range;
|
||||
}
|
||||
|
||||
if (mapped_range != null)
|
||||
@@ -805,6 +739,7 @@ namespace Frida.Gadget {
|
||||
|
||||
return true;
|
||||
});
|
||||
#endif
|
||||
|
||||
assert (our_range != null);
|
||||
|
||||
@@ -1234,7 +1169,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
|
||||
try {
|
||||
return Json.gobject_from_data (typeof (ScriptConfig), data) as ScriptConfig;
|
||||
return (ScriptConfig) Json.gobject_from_data (typeof (ScriptConfig), data);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid config: %s", e.message);
|
||||
}
|
||||
@@ -1242,7 +1177,13 @@ namespace Frida.Gadget {
|
||||
}
|
||||
|
||||
private sealed class Script : Object, RpcPeer {
|
||||
private const uint8 QUICKJS_BYTECODE_MAGIC = 0x02;
|
||||
private const uint8 QUICKJS_BYTECODE_MAGIC =
|
||||
#if BIG_ENDIAN
|
||||
0x42
|
||||
#else
|
||||
0x02
|
||||
#endif
|
||||
;
|
||||
|
||||
public enum ChangeBehavior {
|
||||
IGNORE,
|
||||
@@ -1742,7 +1683,7 @@ namespace Frida.Gadget {
|
||||
}
|
||||
}
|
||||
|
||||
private class ControlChannel : Object, Peer, HostSession {
|
||||
private class ControlChannel : Object, Peer, HostSession, GadgetSession {
|
||||
public weak ControlServer parent {
|
||||
get;
|
||||
construct;
|
||||
@@ -1773,6 +1714,9 @@ namespace Frida.Gadget {
|
||||
HostSession host_session = this;
|
||||
registrations.add (connection.register_object (Frida.ObjectPath.HOST_SESSION, host_session));
|
||||
|
||||
GadgetSession gadget_session = this;
|
||||
registrations.add (connection.register_object (Frida.ObjectPath.GADGET_SESSION, gadget_session));
|
||||
|
||||
AuthenticationService null_auth = new NullAuthenticationService ();
|
||||
registrations.add (connection.register_object (Frida.ObjectPath.AUTHENTICATION_SERVICE, null_auth));
|
||||
} catch (IOError e) {
|
||||
@@ -1935,6 +1879,22 @@ namespace Frida.Gadget {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to open services when embedded");
|
||||
}
|
||||
|
||||
public async void break_and_resume (Cancellable? cancellable) throws Error, IOError {
|
||||
#if DARWIN
|
||||
Environment.break_and_resume ();
|
||||
#else
|
||||
throw new Error.NOT_SUPPORTED ("This API is only applicable on Darwin");
|
||||
#endif
|
||||
}
|
||||
|
||||
public async void break_and_detach (Cancellable? cancellable) throws Error, IOError {
|
||||
#if DARWIN
|
||||
Environment.break_and_detach ();
|
||||
#else
|
||||
throw new Error.NOT_SUPPORTED ("This API is only applicable on Darwin");
|
||||
#endif
|
||||
}
|
||||
|
||||
private void validate_pid (uint pid) throws Error {
|
||||
if (pid != this_process.pid)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to act on other processes when embedded");
|
||||
@@ -2170,6 +2130,14 @@ namespace Frida.Gadget {
|
||||
private extern bool has_objc_class (string name);
|
||||
|
||||
private extern void set_thread_name (string name);
|
||||
#if DARWIN
|
||||
private extern void detect_darwin_location_fields (Gum.Address our_address, ref string? executable_name,
|
||||
ref string? our_path, ref Gum.MemoryRange? our_range);
|
||||
private extern void ensure_debugger_breakpoints_only ();
|
||||
private extern void allow_stolen_breakpoints ();
|
||||
private extern void break_and_resume ();
|
||||
private extern void break_and_detach ();
|
||||
#endif
|
||||
}
|
||||
|
||||
private extern void log_info (string message);
|
||||
|
||||
+68
-33
@@ -44,11 +44,17 @@ namespace Frida {
|
||||
|
||||
#if ANDROID
|
||||
public sealed class ThreadCountCloaker : Object {
|
||||
private GetOsThreadStat * get_os_thread_stat_slot;
|
||||
private static GetOsThreadStat old_get_os_thread_stat_impl;
|
||||
|
||||
private ReadFunc * read_slot;
|
||||
private static ReadFunc old_read_impl;
|
||||
|
||||
private static string expected_magic = "%u (".printf (Posix.getpid ());
|
||||
|
||||
[CCode (has_target = false)]
|
||||
private delegate ssize_t GetOsThreadStat (Posix.pid_t tid, void * buf, size_t count);
|
||||
|
||||
[CCode (has_target = false)]
|
||||
private delegate ssize_t ReadFunc (int fd, void * buf, size_t count);
|
||||
|
||||
@@ -56,39 +62,64 @@ namespace Frida {
|
||||
var art = Gum.Process.find_module_by_name ("libart.so");
|
||||
if (art != null) {
|
||||
art.enumerate_imports (imp => {
|
||||
if (imp.name == "read") {
|
||||
if (imp.name == "_ZN3art15GetOsThreadStatEiPcm" || imp.name == "_ZN3art15GetOsThreadStatEiPcj") {
|
||||
get_os_thread_stat_slot = (GetOsThreadStat *) imp.slot;
|
||||
} else if (imp.name == "read") {
|
||||
read_slot = (ReadFunc *) imp.slot;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (read_slot != null)
|
||||
old_read_impl = update_read_slot (on_read);
|
||||
if (get_os_thread_stat_slot != null) {
|
||||
old_get_os_thread_stat_impl = (GetOsThreadStat) update_slot ((void **) get_os_thread_stat_slot,
|
||||
(void *) on_get_os_thread_stat);
|
||||
}
|
||||
if (read_slot != null) {
|
||||
old_read_impl = (ReadFunc) update_slot ((void **) read_slot, (void *) on_read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~ThreadCountCloaker () {
|
||||
if (read_slot != null)
|
||||
update_read_slot (old_read_impl);
|
||||
public override void dispose () {
|
||||
if (get_os_thread_stat_slot != null) {
|
||||
update_slot ((void **) get_os_thread_stat_slot, (void *) old_get_os_thread_stat_impl);
|
||||
}
|
||||
if (read_slot != null) {
|
||||
update_slot ((void **) read_slot, (void *) old_read_impl);
|
||||
}
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
private ReadFunc update_read_slot (ReadFunc new_impl) {
|
||||
private static void * update_slot (void ** orig, void * new_impl) {
|
||||
Gum.PageProtection old_prot = READ;
|
||||
Gum.Memory.query_protection (read_slot, out old_prot);
|
||||
Gum.Memory.query_protection (orig, out old_prot);
|
||||
|
||||
bool is_writable = (old_prot & Gum.PageProtection.WRITE) != 0;
|
||||
if (!is_writable)
|
||||
Gum.mprotect (read_slot, sizeof (void *), old_prot | WRITE);
|
||||
Gum.mprotect (orig, sizeof (void *), old_prot | WRITE);
|
||||
|
||||
ReadFunc old_impl = *read_slot;
|
||||
*read_slot = new_impl;
|
||||
void * old_impl = *orig;
|
||||
*orig = new_impl;
|
||||
|
||||
if (!is_writable)
|
||||
Gum.mprotect (read_slot, sizeof (void *), old_prot);
|
||||
Gum.mprotect (orig, sizeof (void *), old_prot);
|
||||
|
||||
return old_impl;
|
||||
}
|
||||
|
||||
private static ssize_t on_get_os_thread_stat (Posix.pid_t tid, void * buf, size_t count) {
|
||||
ssize_t n = old_get_os_thread_stat_impl (tid, buf, count);
|
||||
|
||||
if (tid == Posix.getpid ()) {
|
||||
try {
|
||||
n = adjust_thread_count (buf, n, count);
|
||||
} catch (FileError e) {
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
private static ssize_t on_read (int fd, void * buf, size_t count) {
|
||||
var n = old_read_impl (fd, buf, count);
|
||||
if (n <= 0)
|
||||
@@ -101,32 +132,36 @@ namespace Frida {
|
||||
if (!file_descriptor_is_proc_self_stat (fd))
|
||||
return n;
|
||||
|
||||
unowned string raw_str = (string) buf;
|
||||
string str = raw_str.substring (0, n);
|
||||
|
||||
MatchInfo info;
|
||||
if (!/^(\d+ \(.+\)(?: [^ ]+){17}) \d+ (.+)/s.match (str, 0, out info))
|
||||
return n;
|
||||
string fields_before = info.fetch (1);
|
||||
string fields_after = info.fetch (2);
|
||||
|
||||
// We cannot simply use the value we got from the kernel and subtract the number of cloaked threads,
|
||||
// as there's a chance the total may have changed in the last moment.
|
||||
uint num_uncloaked_threads = query_num_uncloaked_threads ();
|
||||
|
||||
string adjusted_str = "%s %u %s".printf (fields_before, num_uncloaked_threads, fields_after);
|
||||
|
||||
var adjusted_length = adjusted_str.length;
|
||||
if (adjusted_length > count)
|
||||
return n;
|
||||
Memory.copy (buf, adjusted_str, adjusted_length);
|
||||
n = adjusted_length;
|
||||
n = adjust_thread_count (buf, n, count);
|
||||
} catch (FileError e) {
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
private static ssize_t adjust_thread_count (void * buf, ssize_t n, size_t count) throws FileError {
|
||||
unowned string raw_str = (string) buf;
|
||||
string str = raw_str.substring (0, n);
|
||||
|
||||
MatchInfo info;
|
||||
if (!/^(\d+ \(.+\)(?: [^ ]+){17}) \d+ (.+)/s.match (str, 0, out info))
|
||||
return n;
|
||||
string fields_before = info.fetch (1);
|
||||
string fields_after = info.fetch (2);
|
||||
|
||||
// We cannot simply use the value we got from the kernel and subtract the number of cloaked threads,
|
||||
// as there's a chance the total may have changed in the last moment.
|
||||
uint num_uncloaked_threads = query_num_uncloaked_threads ();
|
||||
|
||||
string adjusted_str = "%s %u %s".printf (fields_before, num_uncloaked_threads, fields_after);
|
||||
|
||||
var adjusted_length = adjusted_str.length;
|
||||
if (adjusted_length > count)
|
||||
return n;
|
||||
Memory.copy (buf, adjusted_str, adjusted_length);
|
||||
return adjusted_length;
|
||||
}
|
||||
|
||||
private static bool file_content_might_be_from_proc_self_stat (void * content, ssize_t size) {
|
||||
if (size < expected_magic.length)
|
||||
return false;
|
||||
|
||||
@@ -55,6 +55,7 @@ struct _FridaUnwindHookState
|
||||
gpointer set_info_original;
|
||||
void (* set_info) (gpointer cursor, gint is_return_address);
|
||||
gpointer (* get_reg) (gpointer cursor, gint reg);
|
||||
GumInterceptor * interceptor;
|
||||
};
|
||||
|
||||
#if __has_feature (ptrauth_calls)
|
||||
@@ -97,7 +98,6 @@ _frida_unwind_sitter_hook_libunwind (void)
|
||||
#if GLIB_SIZEOF_VOID_P == 8
|
||||
gpointer * set_info_slot;
|
||||
gpointer get_reg_impl;
|
||||
GumInterceptor * interceptor;
|
||||
|
||||
if (state != NULL)
|
||||
return;
|
||||
@@ -121,8 +121,9 @@ _frida_unwind_sitter_hook_libunwind (void)
|
||||
state->set_info = FRIDA_RESIGN_PTR (state->set_info_original);
|
||||
state->get_reg = FRIDA_RESIGN_PTR (get_reg_impl);
|
||||
|
||||
interceptor = gum_interceptor_obtain ();
|
||||
if (gum_interceptor_replace (interceptor, state->set_info_original, frida_unwind_cursor_set_info_replacement, NULL, NULL)
|
||||
state->interceptor = gum_interceptor_obtain ();
|
||||
|
||||
if (gum_interceptor_replace (state->interceptor, state->set_info_original, frida_unwind_cursor_set_info_replacement, NULL, NULL)
|
||||
!= GUM_REPLACE_OK)
|
||||
goto unsupported_version;
|
||||
|
||||
@@ -137,13 +138,12 @@ unsupported_version:
|
||||
void
|
||||
_frida_unwind_sitter_unhook_libunwind (void)
|
||||
{
|
||||
GumInterceptor * interceptor;
|
||||
|
||||
if (state == NULL)
|
||||
return;
|
||||
|
||||
interceptor = gum_interceptor_obtain ();
|
||||
gum_interceptor_revert (interceptor, state->set_info_original);
|
||||
gum_interceptor_revert (state->interceptor, state->set_info_original);
|
||||
|
||||
g_object_unref (state->interceptor);
|
||||
|
||||
g_slice_free (FridaUnwindHookState, state);
|
||||
state = NULL;
|
||||
|
||||
+112
-72
@@ -6,13 +6,17 @@ project('frida-core', 'vala', 'c', 'cpp',
|
||||
default_options: ['c_std=gnu99,c99', 'cpp_std=c++17'],
|
||||
)
|
||||
|
||||
fs = import('fs')
|
||||
|
||||
if not meson.get_compiler('vala').version().endswith('-frida')
|
||||
error('''Incompatible Vala compiler detected.
|
||||
|
||||
Frida currently relies on features not yet upstream. Grab the Frida-optimized
|
||||
Vala compiler from:
|
||||
Vala sources from:
|
||||
|
||||
https://github.com/frida/vala''')
|
||||
https://github.com/frida/vala
|
||||
|
||||
and compile them with Meson.''')
|
||||
endif
|
||||
|
||||
c_languages = ['c', 'cpp']
|
||||
@@ -20,7 +24,7 @@ cc = meson.get_compiler('c')
|
||||
cpp = meson.get_compiler('cpp')
|
||||
|
||||
releng = meson.global_source_root() / 'releng'
|
||||
if not import('fs').exists(releng)
|
||||
if not fs.exists(releng)
|
||||
releng = meson.project_source_root() / 'releng'
|
||||
endif
|
||||
|
||||
@@ -98,11 +102,6 @@ else
|
||||
host_cpu_mode = '32'
|
||||
endif
|
||||
host_docks_mobile_devices = host_os in ['windows', 'macos', 'linux'] and host_cpu_mode == '64'
|
||||
gumjs_archs = {
|
||||
'x86': 'ia32',
|
||||
'x86_64': 'x64',
|
||||
}
|
||||
host_arch_gumjs = gumjs_archs.get(host_arch, host_arch)
|
||||
|
||||
if host_os_family == 'darwin'
|
||||
c_languages += ['objc', 'objcpp']
|
||||
@@ -272,12 +271,14 @@ if host_os == 'linux'
|
||||
#endif
|
||||
'''
|
||||
if cc.compiles(glibc_src, name: 'compiling for glibc')
|
||||
cdata.set('HAVE_GLIBC', 1)
|
||||
libc = 'glibc'
|
||||
elif cc.compiles(uclibc_src, name: 'compiling for uClibc')
|
||||
cdata.set('HAVE_UCLIBC', 1)
|
||||
libc = 'uclibc'
|
||||
else
|
||||
cdata.set('HAVE_MUSL', 1)
|
||||
libc = 'musl'
|
||||
endif
|
||||
cdata.set('HAVE_' + libc.to_upper(), 1)
|
||||
vala_flags += '--define=' + libc.to_upper()
|
||||
endif
|
||||
|
||||
if get_option('b_sanitize') == 'address'
|
||||
@@ -319,7 +320,7 @@ gobject_dep = dependency('gobject-2.0')
|
||||
gmodule_dep = dependency('gmodule-2.0')
|
||||
gio_dep = dependency('gio-2.0')
|
||||
gee_dep = dependency('gee-0.8', default_options: gee_options)
|
||||
json_glib_dep = dependency('json-glib-1.0', default_options: [
|
||||
json_glib_dep = dependency('json-glib-1.0', version: '>=1.8', default_options: [
|
||||
'introspection=disabled',
|
||||
'gtk_doc=disabled',
|
||||
'tests=false',
|
||||
@@ -343,6 +344,7 @@ quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), de
|
||||
'atomics=disabled',
|
||||
'stack_check=disabled',
|
||||
])
|
||||
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_gio_dep = dependency('gio-2.0', native: true)
|
||||
@@ -362,6 +364,44 @@ if host_os == 'android'
|
||||
])
|
||||
endif
|
||||
|
||||
if host_toolchain == 'microsoft'
|
||||
lib = find_program('lib')
|
||||
ar = ''
|
||||
nm = ''
|
||||
ranlib = ''
|
||||
strip = ''
|
||||
else
|
||||
lib = ''
|
||||
ar = find_program('ar')
|
||||
nm = find_program('nm')
|
||||
ranlib = find_program('ranlib')
|
||||
strip = find_program('strip')
|
||||
endif
|
||||
if host_os_family != 'windows'
|
||||
if host_os_family == 'darwin'
|
||||
readelf = ''
|
||||
otool = find_program('otool')
|
||||
libtool = find_program('libtool')
|
||||
else
|
||||
readelf = find_program('readelf')
|
||||
otool = ''
|
||||
libtool = ''
|
||||
endif
|
||||
else
|
||||
readelf = ''
|
||||
otool = ''
|
||||
libtool = ''
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
install_name_tool = find_program('install_name_tool')
|
||||
lipo = find_program('lipo')
|
||||
codesign = find_program('codesign')
|
||||
else
|
||||
install_name_tool = ''
|
||||
lipo = ''
|
||||
codesign = ''
|
||||
endif
|
||||
|
||||
have_local_backend = get_option('local_backend').allowed()
|
||||
have_fruity_backend = get_option('fruity_backend') \
|
||||
.disable_auto_if(not host_docks_mobile_devices) \
|
||||
@@ -371,18 +411,57 @@ have_droidy_backend = get_option('droidy_backend') \
|
||||
.allowed()
|
||||
have_socket_backend = get_option('socket_backend').allowed()
|
||||
have_barebone_backend = get_option('barebone_backend') \
|
||||
.disable_if(not quickjs_dep.found()) \
|
||||
.disable_if(not (quickjs_dep.found() and lzfse_dep.found())) \
|
||||
.disable_auto_if(not host_docks_mobile_devices) \
|
||||
.allowed()
|
||||
have_compiler_backend = get_option('compiler_backend') \
|
||||
.disable_if(not have_local_backend, error_message: 'compiler backend requires the local backend for script execution') \
|
||||
.disable_auto_if(host_os == 'watchos') \
|
||||
.allowed()
|
||||
if get_option('compiler_snapshot').auto()
|
||||
# Generating our snapshot with V8's CPU simulator takes a very long time.
|
||||
generate_compiler_snapshot = meson.can_run_host_binaries()
|
||||
else
|
||||
generate_compiler_snapshot = get_option('compiler_snapshot').enabled()
|
||||
compiler_backend_opt = get_option('compiler_backend') \
|
||||
.disable_auto_if(host_os == 'watchos')
|
||||
have_compiler_backend = false
|
||||
if compiler_backend_opt.allowed()
|
||||
go = find_program('go', native: true, required: false)
|
||||
if go.found()
|
||||
message('Detecting cgo availability')
|
||||
detect_output = run_command(
|
||||
[
|
||||
python,
|
||||
files('tools' / 'cgo' / 'detect.py'),
|
||||
go,
|
||||
host_os,
|
||||
host_abi,
|
||||
shlib_suffix,
|
||||
cc.get_id(),
|
||||
'>>>', cc.cmd_array(), '<<<',
|
||||
'>>>', ar, '<<<',
|
||||
'>>>', nm, '<<<',
|
||||
'>>>', ranlib, '<<<',
|
||||
],
|
||||
capture: true,
|
||||
check: true
|
||||
).stdout()
|
||||
lines = detect_output.split('\n')
|
||||
status = lines[0]
|
||||
if status == 'ok'
|
||||
have_compiler_backend = true
|
||||
tokens = lines[1].split(' ')
|
||||
go_mode = tokens[0]
|
||||
go_config = tokens[1]
|
||||
compiler_backend_static_compilation = go_mode == 'c-archive'
|
||||
if compiler_backend_static_compilation
|
||||
vala_flags += '--define=COMPILER_BACKEND_STATIC_COMPILATION'
|
||||
endif
|
||||
else
|
||||
error_message = lines[1]
|
||||
if compiler_backend_opt.enabled()
|
||||
error(error_message)
|
||||
else
|
||||
warning(f'Building without Compiler backend: @error_message@')
|
||||
endif
|
||||
endif
|
||||
elif compiler_backend_opt.enabled()
|
||||
error('Need Go >= 1.24 to build the Compiler backend powered by ESBuild and typescript-go')
|
||||
else
|
||||
warning('Building without Compiler backend: Go >= 1.24 not found')
|
||||
endif
|
||||
endif
|
||||
|
||||
build_gadget = get_option('gadget') \
|
||||
@@ -452,12 +531,6 @@ gumjs_inspector_vala_args = ['--pkg=frida-gumjs-inspector-1.0', gum_vala_args]
|
||||
have_v8 = gumjs_dep.get_variable('gumjs_v8') == 'enabled'
|
||||
if have_v8
|
||||
vala_flags += ['--define=HAVE_V8']
|
||||
v8_mksnapshot = find_program('v8-mksnapshot-@0@-@1@'.format(host_os, host_abi), required: false)
|
||||
if not v8_mksnapshot.found()
|
||||
v8_mksnapshot = ''
|
||||
endif
|
||||
else
|
||||
v8_mksnapshot = ''
|
||||
endif
|
||||
|
||||
backend_deps_private = []
|
||||
@@ -488,9 +561,6 @@ if have_fruity_backend
|
||||
'dns=disabled',
|
||||
'arp=disabled',
|
||||
'ethernet=enabled',
|
||||
'tcp_mss=4036',
|
||||
'tcp_snd_buf=65535',
|
||||
'tcp_wnd=65535',
|
||||
]
|
||||
if optimize_for_prod
|
||||
lwip_options += 'lwip_debug=disabled'
|
||||
@@ -506,6 +576,11 @@ if quickjs_dep.found()
|
||||
backend_reqs_private += 'quickjs'
|
||||
endif
|
||||
|
||||
if lzfse_dep.found()
|
||||
backend_deps_private += lzfse_dep
|
||||
backend_reqs_private += 'lzfse'
|
||||
endif
|
||||
|
||||
if host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>')
|
||||
vala_flags += '--define=HAVE_OPENPTY'
|
||||
endif
|
||||
@@ -579,42 +654,6 @@ if host_os in ['ios', 'tvos']
|
||||
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
|
||||
endif
|
||||
|
||||
if host_toolchain == 'microsoft'
|
||||
lib = find_program('lib')
|
||||
ar = ''
|
||||
nm = ''
|
||||
strip = ''
|
||||
else
|
||||
lib = ''
|
||||
ar = find_program('ar')
|
||||
nm = find_program('nm')
|
||||
strip = find_program('strip')
|
||||
endif
|
||||
if host_os_family != 'windows'
|
||||
if host_os_family == 'darwin'
|
||||
readelf = ''
|
||||
otool = find_program('otool')
|
||||
libtool = find_program('libtool')
|
||||
else
|
||||
readelf = find_program('readelf')
|
||||
otool = ''
|
||||
libtool = ''
|
||||
endif
|
||||
else
|
||||
readelf = ''
|
||||
otool = ''
|
||||
libtool = ''
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
install_name_tool = find_program('install_name_tool')
|
||||
lipo = find_program('lipo')
|
||||
codesign = find_program('codesign')
|
||||
else
|
||||
install_name_tool = ''
|
||||
lipo = ''
|
||||
codesign = ''
|
||||
endif
|
||||
|
||||
if get_option('devkits').length() != 0
|
||||
if get_option('default_library') != 'static'
|
||||
error('Devkits can only be generated from static libraries')
|
||||
@@ -622,6 +661,7 @@ if get_option('devkits').length() != 0
|
||||
mkdevkit = [python, releng / 'mkdevkit.py']
|
||||
uninstalled_dir = meson.global_build_root() / 'meson-uninstalled'
|
||||
devkit_options = [
|
||||
'--dep-symbol-scope', get_option('devkit_symbol_scope'),
|
||||
'--cc', '>>>', cc.cmd_array(), '<<<',
|
||||
'--c-args', '>>>', get_option('c_args'), '<<<',
|
||||
'--pkg-config', '>>>', find_program('pkg-config'), '<<<',
|
||||
@@ -708,14 +748,15 @@ vala_flags += [
|
||||
'--pkg', 'config',
|
||||
]
|
||||
|
||||
vala_flags += ['--define=' + host_os_family.to_upper()]
|
||||
vala_flags += '--define=' + host_os_family.to_upper()
|
||||
if host_os != host_os_family
|
||||
vala_flags += ['--define=' + host_os.to_upper()]
|
||||
vala_flags += '--define=' + host_os.to_upper()
|
||||
endif
|
||||
vala_flags += ['--define=' + host_arch.to_upper()]
|
||||
vala_flags += '--define=' + host_arch.to_upper()
|
||||
if host_abi != host_arch
|
||||
vala_flags += ['--define=' + host_abi.to_upper()]
|
||||
vala_flags += '--define=' + host_abi.to_upper()
|
||||
endif
|
||||
vala_flags += '--define=' + host_machine.endian().to_upper() + '_ENDIAN'
|
||||
add_project_arguments(vala_flags, language: 'vala')
|
||||
add_project_arguments('-I' + vapidir, language: 'c')
|
||||
|
||||
@@ -760,7 +801,6 @@ summary(
|
||||
'assets': get_option('assets'),
|
||||
'mapper': have_mapper,
|
||||
'connectivity': tls_provider_dep.found(),
|
||||
'compiler_snapshot': generate_compiler_snapshot,
|
||||
'gadget': build_gadget,
|
||||
'server': build_server,
|
||||
'portal': build_portal,
|
||||
|
||||
+10
-7
@@ -37,13 +37,7 @@ option('barebone_backend',
|
||||
option('compiler_backend',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Include frida-compile backend powered by GumJS',
|
||||
)
|
||||
|
||||
option('compiler_snapshot',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Speed up compiler startup by using a snapshot'
|
||||
description: 'Include frida-compile backend powered by ESBuild and typescript-go',
|
||||
)
|
||||
|
||||
option('assets',
|
||||
@@ -156,6 +150,15 @@ option('devkits',
|
||||
description: 'Devkits to build'
|
||||
)
|
||||
|
||||
option('devkit_symbol_scope',
|
||||
type: 'combo',
|
||||
choices: ['prefixed', 'original'],
|
||||
value: 'prefixed',
|
||||
description : 'How devkit generation treats third-party symbols:\n'
|
||||
+ ' - prefixed: add the \'_frida_\' prefix\n'
|
||||
+ ' - original: keep symbols exactly as upstream\n'
|
||||
)
|
||||
|
||||
option('tests',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
|
||||
+1
-1
Submodule releng updated: 5d9edf166c...c24becede0.
+1
-1
@@ -6,7 +6,7 @@ server_sources = [
|
||||
if host_os_family == 'darwin'
|
||||
server_sources += ['server-darwin.m']
|
||||
endif
|
||||
if host_os in ['ios', 'tvos']
|
||||
if host_os in ['ios', 'tvos', 'xros']
|
||||
server_sources += [
|
||||
'server-ios-tvos.c',
|
||||
]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "server-glue.h"
|
||||
|
||||
#include "frida-core.h"
|
||||
#if defined (HAVE_IOS) || defined (HAVE_TVOS)
|
||||
#if defined (HAVE_IOS) || defined (HAVE_TVOS) || defined (HAVE_XROS)
|
||||
# include "server-ios-tvos.h"
|
||||
#endif
|
||||
#ifdef HAVE_ANDROID
|
||||
@@ -54,7 +54,7 @@ frida_server_environment_set_verbose_logging_enabled (gboolean enabled)
|
||||
void
|
||||
frida_server_environment_configure (void)
|
||||
{
|
||||
#if defined (HAVE_IOS) || defined (HAVE_TVOS)
|
||||
#if defined (HAVE_IOS) || defined (HAVE_TVOS) || defined (HAVE_XROS)
|
||||
_frida_server_ios_tvos_configure ();
|
||||
#endif
|
||||
|
||||
|
||||
+40
-5
@@ -26,6 +26,11 @@ OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed )?(class|interface)\s+(\w+
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate refined Frida API definitions")
|
||||
parser.add_argument('--output', dest='output_type', choices=['bundle', 'header', 'gir', 'vapi', 'vapi-stamp'], default='bundle')
|
||||
parser.add_argument('frida_version', metavar='frida-version', type=str)
|
||||
parser.add_argument('frida_major_version', metavar='frida-major-version', type=str)
|
||||
parser.add_argument('frida_minor_version', metavar='frida-minor-version', type=str)
|
||||
parser.add_argument('frida_micro_version', metavar='frida-micro-version', type=str)
|
||||
parser.add_argument('frida_nano_version', metavar='frida-nano-version', type=str)
|
||||
parser.add_argument('api_version', metavar='api-version', type=str)
|
||||
parser.add_argument('core_header', metavar='/path/to/frida-core.h', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('core_gir', metavar='/path/to/Frida-x.y.gir', type=argparse.FileType('r', encoding='utf-8'))
|
||||
@@ -38,6 +43,13 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
output_type = args.output_type
|
||||
frida_version = args.frida_version
|
||||
frida_version_components = (
|
||||
args.frida_major_version,
|
||||
args.frida_minor_version,
|
||||
args.frida_micro_version,
|
||||
args.frida_nano_version,
|
||||
)
|
||||
api_version = args.api_version
|
||||
core_header = args.core_header.read()
|
||||
core_gir = args.core_gir.read()
|
||||
@@ -53,6 +65,7 @@ def main():
|
||||
|
||||
toplevel_names = [
|
||||
"frida.vala",
|
||||
"package-manager.vala",
|
||||
"control-service.vala",
|
||||
"portal-service.vala",
|
||||
"file-monitor.vala",
|
||||
@@ -78,7 +91,7 @@ def main():
|
||||
elif output_type == 'vapi':
|
||||
enable_vapi = True
|
||||
|
||||
api = parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi)
|
||||
api = parse_api(frida_version, frida_version_components, api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi)
|
||||
|
||||
if enable_header:
|
||||
emit_header(api, output_dir)
|
||||
@@ -93,7 +106,26 @@ def emit_header(api, output_dir):
|
||||
with OutputFile(output_dir / 'frida-core.h') as output_header_file:
|
||||
output_header_file.write("#ifndef __FRIDA_CORE_H__\n#define __FRIDA_CORE_H__\n\n")
|
||||
|
||||
output_header_file.write("#include <glib.h>\n#include <glib-object.h>\n#include <gio/gio.h>\n#include <json-glib/json-glib.h>\n")
|
||||
output_header_file.write("#include <glib.h>\n#include <glib-object.h>\n#include <gio/gio.h>\n#include <json-glib/json-glib.h>\n\n")
|
||||
|
||||
output_header_file.write(f"#define FRIDA_VERSION \"{api.frida_version}\"\n\n")
|
||||
|
||||
for name, value in zip(['MAJOR', 'MINOR', 'MICRO', 'NANO'], api.frida_version_components):
|
||||
output_header_file.write(f"#define FRIDA_{name}_VERSION {value}\n")
|
||||
|
||||
output_header_file.write("""
|
||||
#define FRIDA_CHECK_VERSION(maj, min, mic) \\
|
||||
(FRIDA_CURRENT_VERSION >= FRIDA_VERSION_ENCODE ((maj), (min), (mic)))
|
||||
|
||||
#define FRIDA_CURRENT_VERSION \\
|
||||
FRIDA_VERSION_ENCODE ( \\
|
||||
FRIDA_MAJOR_VERSION, \\
|
||||
FRIDA_MINOR_VERSION, \\
|
||||
FRIDA_MICRO_VERSION)
|
||||
|
||||
#define FRIDA_VERSION_ENCODE(maj, min, mic) \\
|
||||
(((maj) * 1000000U) + ((min) * 1000U) + (mic))
|
||||
""")
|
||||
|
||||
output_header_file.write("\nG_BEGIN_DECLS\n")
|
||||
|
||||
@@ -271,7 +303,7 @@ def emit_vapi(api, output_dir):
|
||||
output_deps_file.write("gobject-2.0\n")
|
||||
output_deps_file.write("gio-2.0\n")
|
||||
|
||||
def parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi):
|
||||
def parse_api(frida_version, frida_version_components, api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi):
|
||||
all_headers = core_header + "\n" + base_header
|
||||
|
||||
all_enum_names = [m.group(1) for m in re.finditer(r"^\t+public\s+enum\s+(\w+)\s+", toplevel_code + "\n" + base_vapi, re.MULTILINE)]
|
||||
@@ -464,7 +496,7 @@ def parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, b
|
||||
m = re.search(r"^[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
|
||||
f.c_prototype = beautify_cprototype(m.group(0))
|
||||
|
||||
return ApiSpec(api_version, object_types, functions, enum_types, error_types)
|
||||
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types)
|
||||
|
||||
def function_is_public(name):
|
||||
return not name.startswith("_") and \
|
||||
@@ -478,7 +510,8 @@ def function_is_public(name):
|
||||
"parse_control_address",
|
||||
"parse_cluster_address",
|
||||
"parse_socket_address",
|
||||
"negotiate_connection"
|
||||
"negotiate_connection",
|
||||
"check_kernel_version",
|
||||
]
|
||||
|
||||
def parse_vala_object_types(source) -> List[ApiObjectType]:
|
||||
@@ -489,6 +522,8 @@ def parse_vapi_functions(vapi) -> List[ApiFunction]:
|
||||
|
||||
@dataclass
|
||||
class ApiSpec:
|
||||
frida_version: str
|
||||
frida_version_components: List[int]
|
||||
version: str
|
||||
object_types: List[ApiObjectType]
|
||||
functions: List[ApiFunction]
|
||||
|
||||
@@ -3,6 +3,11 @@ fs = import('fs')
|
||||
generate_api = [python, files('generate.py')]
|
||||
|
||||
generate_api_args = [
|
||||
frida_version,
|
||||
frida_major_version.to_string(),
|
||||
frida_minor_version.to_string(),
|
||||
frida_micro_version.to_string(),
|
||||
frida_nano_version.to_string(),
|
||||
api_version,
|
||||
core_build_dir / 'frida-core.h',
|
||||
core_build_dir / core_gir_name,
|
||||
|
||||
@@ -0,0 +1,503 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class AgentConnection : Object, AsyncInitable {
|
||||
public signal void script_message (AgentScriptId id, string json, Bytes? data);
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
private SocketConnection hostlink;
|
||||
private BufferedInputStream input;
|
||||
private OutputStream output;
|
||||
|
||||
private AgentConfig agent_config;
|
||||
private ImageConfig? image_config;
|
||||
private Machine machine;
|
||||
private Allocator allocator;
|
||||
|
||||
private Allocation elf_allocation;
|
||||
private Allocation config_allocation;
|
||||
private Callback mprotect_callback;
|
||||
private Callback remap_writable_pages_callback;
|
||||
|
||||
private Gee.Map<uint16, Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Promise<Variant>> ();
|
||||
private uint16 next_request_id = 1;
|
||||
|
||||
private const int COMMAND_TIMEOUT_MS = 25000;
|
||||
|
||||
public static async AgentConnection open (AgentConfig agent_config, ImageConfig? image_config, Machine machine,
|
||||
Allocator allocator, Cancellable? cancellable) throws Error, IOError {
|
||||
var connection = new AgentConnection () {
|
||||
agent_config = agent_config,
|
||||
image_config = image_config,
|
||||
machine = machine,
|
||||
allocator = allocator,
|
||||
};
|
||||
|
||||
try {
|
||||
yield connection.init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
|
||||
HostlinkTransportConfig? transport_config = agent_config.transport as HostlinkTransportConfig;
|
||||
if (transport_config == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported transport config: only hostlink is supported for now");
|
||||
|
||||
var qmp = yield QmpClient.open (transport_config.qmp, 0, cancellable);
|
||||
var link = yield qmp.open_hostlink (cancellable);
|
||||
hostlink = link.connection;
|
||||
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
|
||||
"base-stream", hostlink.get_input_stream (),
|
||||
"close-base-stream", false,
|
||||
"buffer-size", 128 * 1024);
|
||||
output = hostlink.get_output_stream ();
|
||||
|
||||
var gdb = machine.gdb;
|
||||
ByteOrder byte_order = gdb.byte_order;
|
||||
uint pointer_size = gdb.pointer_size;
|
||||
|
||||
uint64 kernel_base;
|
||||
Layout layout;
|
||||
if (image_config != null) {
|
||||
kernel_base = image_config.base.address;
|
||||
layout = yield Layout.load_from_symbol_source (File.new_for_path (image_config.file), kernel_base,
|
||||
byte_order, pointer_size, cancellable);
|
||||
} else {
|
||||
kernel_base = 0;
|
||||
layout = new Layout.empty ();
|
||||
}
|
||||
if (kernel_base == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
|
||||
|
||||
var symbols = new Gee.HashMap<string, SymbolInfo> ();
|
||||
var hash_builder = new SymbolHashBuilder ();
|
||||
foreach (var s in layout.symbols) {
|
||||
symbols[s.name] = s;
|
||||
hash_builder.add_symbol (s);
|
||||
}
|
||||
if (image_config != null) {
|
||||
foreach (var e in image_config.symbols.entries) {
|
||||
unowned string name = e.key;
|
||||
if (!symbols.has_key (name)) {
|
||||
var s = new SymbolInfo () {
|
||||
name = name,
|
||||
offset = (uint32) e.value,
|
||||
symbol_type = 0xf,
|
||||
section = 0x10, // FIXME
|
||||
};
|
||||
symbols[name] = s;
|
||||
hash_builder.add_symbol (s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SymbolInfo? thread_block = symbols["thread_block"];
|
||||
if (thread_block == null)
|
||||
throw new Error.NOT_SUPPORTED ("Missing symbol for thread_block");
|
||||
|
||||
var config_builder = new VariantBuilder (new VariantType ("(tuta(ssuuuu)ay)"));
|
||||
config_builder.add ("t", link.mmio);
|
||||
config_builder.add ("u", link.irq);
|
||||
config_builder.add ("t", kernel_base);
|
||||
|
||||
config_builder.open (new VariantType ("a(ssuuuu)"));
|
||||
foreach (var m in layout.modules) {
|
||||
config_builder.add ("(ssuuuu)",
|
||||
m.name,
|
||||
m.version,
|
||||
m.offset,
|
||||
m.size,
|
||||
m.start_func_offset,
|
||||
m.stop_func_offset
|
||||
);
|
||||
}
|
||||
config_builder.close ();
|
||||
|
||||
Bytes symbol_data = hash_builder.build (byte_order);
|
||||
config_builder.add_value (Variant.new_from_data (new VariantType ("ay"), symbol_data.get_data (), true,
|
||||
symbol_data));
|
||||
|
||||
Gum.ElfModule elf;
|
||||
try {
|
||||
elf = new Gum.ElfModule.from_file (agent_config.path);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
|
||||
var raw_elf = gdb.make_buffer (new Bytes (elf.get_file_data ()));
|
||||
var missing_symbols = new Gee.ArrayList<string> ();
|
||||
elf.enumerate_symbols (s => {
|
||||
unowned Gum.ElfSectionDetails? sect = s.section;
|
||||
if (sect != null && sect.name == ".kernel_addrs") {
|
||||
string name = s.name[1:];
|
||||
SymbolInfo? info = symbols[name];
|
||||
if (info != null) {
|
||||
size_t file_offset = (size_t) (sect.offset + (s.address - sect.address));
|
||||
raw_elf.write_pointer (file_offset, kernel_base + info.offset);
|
||||
} else {
|
||||
missing_symbols.add (name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!missing_symbols.is_empty) {
|
||||
throw new Error.INVALID_ARGUMENT ("Missing symbols for: %s",
|
||||
string.joinv (", ", missing_symbols.to_array ()));
|
||||
}
|
||||
|
||||
yield machine.enter_exception_level (1, 1000, cancellable);
|
||||
|
||||
var bp = yield gdb.add_breakpoint (SOFT, kernel_base + thread_block.offset, 4, cancellable);
|
||||
GDB.Breakpoint? hit_breakpoint = null;
|
||||
do {
|
||||
var exception = yield gdb.continue_until_exception (cancellable);
|
||||
hit_breakpoint = exception.breakpoint;
|
||||
} while (hit_breakpoint != bp);
|
||||
yield bp.remove (cancellable);
|
||||
|
||||
size_t page_size = yield machine.query_page_size (cancellable);
|
||||
|
||||
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator, cancellable);
|
||||
|
||||
uint64 start_address = 0;
|
||||
uint64 remap_writable_pages_address = 0;
|
||||
uint64 mprotect_address = 0;
|
||||
uint64 base_va = elf_allocation.virtual_address;
|
||||
elf.enumerate_symbols (e => {
|
||||
if (e.name == "_start")
|
||||
start_address = base_va + e.address;
|
||||
else if (e.name == "gum_barebone_try_remap_writable_pages")
|
||||
remap_writable_pages_address = base_va + e.address;
|
||||
else if (e.name == "gum_try_mprotect")
|
||||
mprotect_address = base_va + e.address;
|
||||
else
|
||||
return true;
|
||||
return start_address == 0 || mprotect_address == 0 || remap_writable_pages_address == 0;
|
||||
});
|
||||
if (start_address == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
|
||||
if (remap_writable_pages_address == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid agent: no gum_barebone_try_remap_writable_pages symbol found");
|
||||
if (mprotect_address == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid agent: no gum_try_mprotect symbol found");
|
||||
|
||||
remap_writable_pages_callback = yield new Callback (remap_writable_pages_address,
|
||||
new RemapWritablePagesHandler (machine), machine, cancellable);
|
||||
mprotect_callback = yield new Callback (mprotect_address, new MemoryProtectHandler (machine), machine, cancellable);
|
||||
|
||||
var config_blob = config_builder.end ().get_data_as_bytes ();
|
||||
config_allocation = yield allocator.allocate (config_blob.get_size (), 8, cancellable);
|
||||
|
||||
yield gdb.write_byte_array (config_allocation.virtual_address, config_blob, cancellable);
|
||||
|
||||
yield machine.invoke (start_address, {
|
||||
config_allocation.virtual_address,
|
||||
config_allocation.size
|
||||
},
|
||||
cancellable);
|
||||
|
||||
yield gdb.continue (cancellable);
|
||||
|
||||
process_incoming_messages.begin ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
io_cancellable.cancel ();
|
||||
}
|
||||
|
||||
public async AgentScriptId create_script (string source, Cancellable? cancellable) throws Error, IOError {
|
||||
var payload = new Variant ("s", source);
|
||||
var response = yield execute_command (Command.CREATE_SCRIPT, payload, cancellable);
|
||||
if (!response.check_format_string ("u", false))
|
||||
throw new Error.PROTOCOL ("Invalid create_script response format");
|
||||
uint32 script_handle;
|
||||
response.get ("u", out script_handle);
|
||||
return AgentScriptId (script_handle);
|
||||
}
|
||||
|
||||
public async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
var payload = new Variant ("u", script_id.handle);
|
||||
yield execute_command (Command.LOAD_SCRIPT, payload, cancellable);
|
||||
}
|
||||
|
||||
public async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
var payload = new Variant ("u", script_id.handle);
|
||||
yield execute_command (Command.DESTROY_SCRIPT, payload, cancellable);
|
||||
}
|
||||
|
||||
public async void post_script_message (AgentScriptId script_id, string message, Bytes? data, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var payload = new Variant ("(us)", script_id.handle, message);
|
||||
// TODO: Include data.
|
||||
yield execute_command (Command.POST_SCRIPT_MESSAGE, payload, cancellable);
|
||||
}
|
||||
|
||||
private async Variant execute_command (Command command, Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
uint16 request_id = next_request_id++;
|
||||
|
||||
var command_message = new Variant ("(yqv)", (uint8) command, request_id, payload);
|
||||
if (machine.gdb.byte_order != ByteOrder.HOST)
|
||||
command_message = command_message.byteswap ();
|
||||
var command_bytes = command_message.get_data_as_bytes ();
|
||||
|
||||
var builder = machine.gdb.make_buffer_builder ();
|
||||
Bytes frame = builder
|
||||
.append_uint32 ((uint32) command_bytes.get_size ())
|
||||
.append_bytes (command_bytes)
|
||||
.build ();
|
||||
|
||||
var promise = new Promise<Variant> ();
|
||||
pending_requests[request_id] = promise;
|
||||
|
||||
try {
|
||||
yield output.write_all_async (frame.get_data (), Priority.DEFAULT, cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
pending_requests.unset (request_id);
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
var timeout_source = new TimeoutSource (COMMAND_TIMEOUT_MS);
|
||||
timeout_source.set_callback (() => {
|
||||
Promise<Variant>? p;
|
||||
if (pending_requests.unset (request_id, out p))
|
||||
p.reject (new Error.TIMED_OUT ("Command timed out"));
|
||||
return Source.REMOVE;
|
||||
});
|
||||
timeout_source.attach (MainContext.get_thread_default ());
|
||||
|
||||
try {
|
||||
return yield promise.future.wait_async (cancellable);
|
||||
} finally {
|
||||
timeout_source.destroy ();
|
||||
}
|
||||
}
|
||||
|
||||
private async void process_incoming_messages () {
|
||||
var byte_order = machine.gdb.byte_order;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
size_t header_size = 4;
|
||||
if (input.get_available () < header_size)
|
||||
yield fill_until_n_bytes_available (header_size);
|
||||
|
||||
uint32 body_size = 0;
|
||||
unowned uint8[] size_buf = ((uint8[]) &body_size)[:4];
|
||||
input.peek (size_buf);
|
||||
body_size = uint32.from_little_endian (body_size);
|
||||
|
||||
size_t full_size = header_size + body_size;
|
||||
if (input.get_available () < full_size)
|
||||
yield fill_until_n_bytes_available (full_size);
|
||||
|
||||
var body = new uint8[body_size];
|
||||
input.peek (body, header_size);
|
||||
|
||||
input.skip (full_size, io_cancellable);
|
||||
|
||||
var raw_message = new Bytes.take ((owned) body);
|
||||
|
||||
var message = Variant.new_from_data (new VariantType ("(yqv)"), raw_message.get_data (), false,
|
||||
raw_message);
|
||||
if (byte_order != ByteOrder.HOST)
|
||||
message = message.byteswap ();
|
||||
if (!message.check_format_string ("(yqv)", false))
|
||||
throw new Error.PROTOCOL ("Invalid message format");
|
||||
|
||||
uint8 command_code;
|
||||
uint16 request_id;
|
||||
Variant payload;
|
||||
message.get ("(yqv)", out command_code, out request_id, out payload);
|
||||
|
||||
if (command_code == Command.SCRIPT_MESSAGE) {
|
||||
if (!payload.check_format_string ("(us)", false))
|
||||
throw new Error.PROTOCOL ("Invalid script message payload format");
|
||||
|
||||
uint32 script_handle;
|
||||
unowned string json;
|
||||
payload.get ("(u&s)", out script_handle, out json);
|
||||
|
||||
script_message (AgentScriptId (script_handle), json, null);
|
||||
} else if (command_code == Command.REPLY) {
|
||||
Promise<Variant>? promise;
|
||||
if (pending_requests.unset (request_id, out promise))
|
||||
promise.resolve (payload);
|
||||
}
|
||||
}
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private async void fill_until_n_bytes_available (size_t minimum) throws Error, IOError {
|
||||
size_t available = input.get_available ();
|
||||
while (available < minimum) {
|
||||
if (input.get_buffer_size () < minimum)
|
||||
input.set_buffer_size (minimum);
|
||||
|
||||
ssize_t n;
|
||||
try {
|
||||
n = yield input.fill_async ((ssize_t) (input.get_buffer_size () - available), Priority.DEFAULT,
|
||||
io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("Connection closed");
|
||||
}
|
||||
|
||||
if (n == 0)
|
||||
throw new Error.TRANSPORT ("Connection closed");
|
||||
|
||||
available += n;
|
||||
}
|
||||
}
|
||||
|
||||
private class RemapWritablePagesHandler : Object, CallbackHandler {
|
||||
public signal void output (string message);
|
||||
|
||||
public uint arity {
|
||||
get { return 2; }
|
||||
}
|
||||
|
||||
private Machine machine;
|
||||
|
||||
public RemapWritablePagesHandler (Machine machine) {
|
||||
this.machine = machine;
|
||||
}
|
||||
|
||||
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var pages = args[0];
|
||||
var num_pages = (uint) args[1];
|
||||
|
||||
var physical_addresses = new Gee.ArrayList<uint64?> ();
|
||||
var gdb = machine.gdb;
|
||||
var reader = new BufferReader (yield gdb.read_buffer (pages, num_pages * gdb.pointer_size, cancellable));
|
||||
for (uint i = 0; i != num_pages; i++)
|
||||
physical_addresses.add (reader.read_pointer ());
|
||||
|
||||
Allocation allocation = yield machine.allocate_pages (physical_addresses, cancellable);
|
||||
// TODO: Handle cleanup.
|
||||
|
||||
return allocation.virtual_address;
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryProtectHandler : Object, CallbackHandler {
|
||||
public signal void output (string message);
|
||||
|
||||
public uint arity {
|
||||
get { return 3; }
|
||||
}
|
||||
|
||||
private Machine machine;
|
||||
|
||||
public MemoryProtectHandler (Machine machine) {
|
||||
this.machine = machine;
|
||||
}
|
||||
|
||||
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var address = args[0];
|
||||
var size = (size_t) args[1];
|
||||
var prot = (Gum.PageProtection) args[2];
|
||||
try {
|
||||
yield machine.protect_pages (address, size, prot, cancellable);
|
||||
return 1;
|
||||
} catch (GLib.Error e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum Command {
|
||||
CREATE_SCRIPT = 1,
|
||||
LOAD_SCRIPT = 2,
|
||||
DESTROY_SCRIPT = 3,
|
||||
POST_SCRIPT_MESSAGE = 4,
|
||||
REPLY = 128,
|
||||
SCRIPT_MESSAGE = 129
|
||||
}
|
||||
|
||||
private enum Status {
|
||||
IDLE,
|
||||
BUSY,
|
||||
DATA_READY,
|
||||
ERROR
|
||||
}
|
||||
}
|
||||
|
||||
private class SymbolHashBuilder : Object {
|
||||
private Gee.Map<string, Gee.List<SymbolInfo>> symbol_table = new Gee.TreeMap<string, Gee.List<SymbolInfo>> ();
|
||||
|
||||
public void add_symbol (SymbolInfo symbol) {
|
||||
var symbol_list = symbol_table[symbol.name];
|
||||
if (symbol_list == null) {
|
||||
symbol_list = new Gee.ArrayList<SymbolInfo> ();
|
||||
symbol_table[symbol.name] = symbol_list;
|
||||
}
|
||||
symbol_list.add (symbol);
|
||||
}
|
||||
|
||||
public Bytes build (ByteOrder byte_order) {
|
||||
var builder = new BufferBuilder (byte_order);
|
||||
|
||||
var all_symbols = new Gee.ArrayList<SymbolInfo> ();
|
||||
foreach (var entry in symbol_table.entries) {
|
||||
foreach (var symbol in entry.value)
|
||||
all_symbols.add (symbol);
|
||||
}
|
||||
|
||||
uint total_symbols = all_symbols.size;
|
||||
builder.append_uint32 (total_symbols);
|
||||
|
||||
var name_index_offset = builder.offset;
|
||||
builder.skip (total_symbols * 4);
|
||||
|
||||
var addr_index_offset = builder.offset;
|
||||
builder.skip (total_symbols * 4);
|
||||
|
||||
var symbol_offsets = new uint32[total_symbols];
|
||||
for (uint i = 0; i != total_symbols; i++) {
|
||||
var symbol = all_symbols[(int) i];
|
||||
|
||||
builder.align (4);
|
||||
symbol_offsets[i] = (uint32) builder.offset;
|
||||
|
||||
builder.append_uint32 (symbol.offset);
|
||||
// TODO: Only include details we need.
|
||||
builder.append_uint8 (symbol.symbol_type);
|
||||
builder.append_uint8 (symbol.section);
|
||||
builder.append_uint16 (symbol.description);
|
||||
builder.append_string (symbol.name, StringTerminator.NUL);
|
||||
}
|
||||
|
||||
for (uint i = 0; i != total_symbols; i++)
|
||||
builder.write_uint32 (name_index_offset + (i * 4), symbol_offsets[i]);
|
||||
|
||||
var addr_sorted_symbols = new Gee.ArrayList<int> ();
|
||||
for (uint i = 0; i != total_symbols; i++)
|
||||
addr_sorted_symbols.add ((int) i);
|
||||
addr_sorted_symbols.sort ((a, b) => {
|
||||
var symbol_a = all_symbols[a];
|
||||
var symbol_b = all_symbols[b];
|
||||
if (symbol_a.offset < symbol_b.offset)
|
||||
return -1;
|
||||
if (symbol_a.offset > symbol_b.offset)
|
||||
return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
for (uint i = 0; i != total_symbols; i++) {
|
||||
int original_index = addr_sorted_symbols[(int) i];
|
||||
uint symbol_data_offset = symbol_offsets[original_index];
|
||||
builder.write_uint32 (addr_index_offset + (i * 4), symbol_data_offset);
|
||||
}
|
||||
|
||||
return builder.build ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "aarch64-unknown-none"
|
||||
Generated
+296
@@ -0,0 +1,296 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "frida-barebone-agent"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "frida-barebone-agent"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "z"
|
||||
overflow-checks = false
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.2"
|
||||
bindgen = "0.72.0"
|
||||
@@ -0,0 +1,43 @@
|
||||
## Getting the C toolchain
|
||||
|
||||
npm install -g xpm
|
||||
xpm install
|
||||
export PATH=$PWD/xpacks/.bin:$PATH
|
||||
|
||||
## How to build Gum
|
||||
|
||||
./configure \
|
||||
--host=aarch64-none-elf \
|
||||
--enable-gumjs \
|
||||
--with-devkits=gum,gumjs \
|
||||
--with-devkit-symbol-scope=original
|
||||
make
|
||||
export GUMJS_DEVKIT_DIR=$PWD/build/bindings/gumjs/devkit
|
||||
|
||||
## Building
|
||||
|
||||
export PATH=$PWD/xpacks/.bin:$PATH
|
||||
export CC_aarch64_unknown_none=aarch64-none-elf-gcc
|
||||
export AR_aarch64_unknown_none=aarch64-none-elf-ar
|
||||
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
|
||||
|
||||
## Development loop
|
||||
|
||||
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
|
||||
cargo build --release && make -C ~/src/frida-python && killall -9 qemu-system-aarch64 && sleep 2 && frida -D barebone -p 0
|
||||
|
||||
## Speeding up loop
|
||||
|
||||
./configure \
|
||||
-- \
|
||||
-Dfrida-core:compat=disabled \
|
||||
-Dfrida-core:local_backend=disabled \
|
||||
-Dfrida-core:fruity_backend=disabled \
|
||||
-Dfrida-core:droidy_backend=disabled \
|
||||
-Dfrida-core:socket_backend=disabled \
|
||||
-Dfrida-core:compiler_backend=disabled \
|
||||
-Dfrida-core:gadget=disabled \
|
||||
-Dfrida-core:server=disabled \
|
||||
-Dfrida-core:portal=disabled \
|
||||
-Dfrida-core:inject=disabled \
|
||||
-Dfrida-core:tests=enabled
|
||||
@@ -0,0 +1,51 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(8) {
|
||||
. = ALIGN(8);
|
||||
_panic = .; . += 8;
|
||||
_IOLog = .; . += 8;
|
||||
_kalloc = .; . += 8;
|
||||
_kfree = .; . += 8;
|
||||
_kernel_thread_start = .; . += 8;
|
||||
_assert_wait = .; . += 8;
|
||||
_assert_wait_timeout = .; . += 8;
|
||||
_thread_block = .; . += 8;
|
||||
_thread_wakeup = .; . += 8;
|
||||
_mach_absolute_time = .; . += 8;
|
||||
_absolutetime_to_nanoseconds = .; . += 8;
|
||||
_clock_get_calendar_microtime = .; . += 8;
|
||||
_ml_io_map = .; . += 8;
|
||||
_ml_vtophys = .; . += 8;
|
||||
__ZN9IOService11getPlatformEv = .; . += 8;
|
||||
__ZN9IOServiceC2Ev = .; . += 8;
|
||||
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
|
||||
__ZN6OSData9withBytesEPKvj = .; . += 8;
|
||||
}
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(8);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
let devkit_dir = PathBuf::from(env::var("GUMJS_DEVKIT_DIR").unwrap());
|
||||
|
||||
let cc_str = env::var("CC_aarch64_unknown_none")
|
||||
.or_else(|_| env::var("CC"))
|
||||
.unwrap_or_else(|_| "cc".to_string());
|
||||
let cc = Path::new(&cc_str);
|
||||
let cc_include_paths = detect_gcc_include_paths(cc);
|
||||
let cc_library_paths = detect_gcc_library_paths(cc);
|
||||
|
||||
let cc_clang_args: Vec<String> = cc_include_paths
|
||||
.iter()
|
||||
.flat_map(|path| vec!["-isystem".to_string(), path.to_string_lossy().into_owned()])
|
||||
.collect();
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.use_core()
|
||||
.header(devkit_dir.join("frida-gumjs.h").to_str().unwrap())
|
||||
.clang_arg("-nostdinc")
|
||||
.clang_arg("-target")
|
||||
.clang_arg("aarch64-none-elf")
|
||||
.clang_args(cc_clang_args)
|
||||
.clang_arg(format!("-I{}", devkit_dir.to_str().unwrap()))
|
||||
.merge_extern_blocks(true)
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
bindings
|
||||
.write_to_file(out_dir.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings");
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
devkit_dir.to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=frida-gumjs");
|
||||
for path in cc_library_paths {
|
||||
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
|
||||
}
|
||||
println!("cargo:rustc-link-lib=static=c");
|
||||
println!("cargo:rustc-link-lib=static=m");
|
||||
println!("cargo:rustc-link-arg=--export-dynamic");
|
||||
println!("cargo:rustc-link-arg=--emit-relocs");
|
||||
println!("cargo:rustc-link-arg=--script=agent.lds");
|
||||
println!("cargo:rustc-link-arg=--gc-sections");
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
|
||||
pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc)
|
||||
.args(["-xc", "-E", "-v", "-"])
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.expect("Failed to execute GCC to detect include paths");
|
||||
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
|
||||
let mut grab = false;
|
||||
let mut paths = Vec::<PathBuf>::new();
|
||||
|
||||
for line in stderr.lines() {
|
||||
if line.starts_with("#include <...> search starts here:") {
|
||||
grab = true;
|
||||
continue;
|
||||
}
|
||||
if line.starts_with("End of search list.") {
|
||||
break;
|
||||
}
|
||||
if !grab {
|
||||
continue;
|
||||
}
|
||||
|
||||
let raw = line.trim();
|
||||
if raw.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let p = Path::new(raw);
|
||||
if p.exists() {
|
||||
let canonical = fs::canonicalize(p).expect("Failed to canonicalize include path");
|
||||
if !paths.contains(&canonical) {
|
||||
paths.push(canonical);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
pub fn detect_gcc_library_paths(gcc: &Path) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc).arg("-print-search-dirs").output()
|
||||
.expect("Failed to execute GCC to detect library paths");
|
||||
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
let line = stdout.lines().find(|l| l.starts_with("libraries:"))
|
||||
.expect("Failed to find libraries line in GCC output");
|
||||
|
||||
let raw_dirs = line.trim_start_matches("libraries: =");
|
||||
let sep = if cfg!(windows) { ';' } else { ':' };
|
||||
|
||||
let mut paths = Vec::new();
|
||||
for raw in raw_dirs.split(sep) {
|
||||
if raw.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let p = Path::new(raw);
|
||||
if !p.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let canonical = fs::canonicalize(p).expect("Failed to canonicalize library path");
|
||||
if !paths.contains(&canonical) {
|
||||
paths.push(canonical);
|
||||
}
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
|
||||
export PYTHONPATH=$HOME/src/frida-python
|
||||
|
||||
export PATH=$PWD/xpacks/.bin:$PATH
|
||||
export CC_aarch64_unknown_none=aarch64-none-elf-gcc
|
||||
export AR_aarch64_unknown_none=aarch64-none-elf-ar
|
||||
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
|
||||
export GUMJS_DEVKIT_DIR=/home/oleavr/src/frida-gum/build/bindings/gumjs/devkit
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"connection": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 9000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"connection": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 9000
|
||||
},
|
||||
"allocator": {
|
||||
"mode": "physical",
|
||||
"physical_base": "0x8ec9b4000"
|
||||
},
|
||||
"agent": {
|
||||
"path": "/Users/oleavr/src/frida-core/src/barebone/agent/target/aarch64-unknown-none/debug/frida-barebone-agent",
|
||||
"transport": {
|
||||
"type": "ram",
|
||||
"path": "/Volumes/RAM Disk/ios-dram",
|
||||
"base_address": "0x800000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"connection": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 9000
|
||||
},
|
||||
"allocator": {
|
||||
"mode": "target-functions",
|
||||
"alloc_function": "0xfffffff007a3c278",
|
||||
"free_function": "0xfffffff007a3c338"
|
||||
},
|
||||
"agent": {
|
||||
"path": "/Users/oleavr/src/frida-core/src/barebone/agent/target/aarch64-unknown-none/release/frida-barebone-agent",
|
||||
"transport": {
|
||||
"type": "hostlink",
|
||||
"qmp": "unix:/Users/oleavr/src/frida-barebone-ios/qmp.sock"
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"file": "/Users/oleavr/src/frida-barebone-ios/iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/kernelcache.research.iphone12b",
|
||||
"base": "0xfffffff007004000",
|
||||
"symbols": {
|
||||
"IOLog": "0xfedb68",
|
||||
"_ZN6OSData9withBytesEPKvj": "0xf88588",
|
||||
"_ZN8OSSymbol17withCStringNoCopyEPKc": "0xfc05dc",
|
||||
"_ZN9IOService11getPlatformEv": "0x101ad48",
|
||||
"_ZN9IOServiceC2Ev": "0x1018318",
|
||||
"absolutetime_to_nanoseconds": "0xb5d1e4",
|
||||
"assert_wait": "0xa4d294",
|
||||
"assert_wait_timeout": "0xa4d430",
|
||||
"clock_get_calendar_microtime": "0xa1f32c",
|
||||
"kalloc": "0xa38278",
|
||||
"kernel_thread_start": "0xa70674",
|
||||
"kfree": "0xa38338",
|
||||
"mach_absolute_time": "0xb5ccc0",
|
||||
"ml_io_map": "0xb57a04",
|
||||
"ml_vtophys": "0xb584a0",
|
||||
"panic": "0x27d7944",
|
||||
"thread_block": "0xa51728",
|
||||
"thread_wakeup": "0xa54ea0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
const _copyinstr = new NativeFunction(
|
||||
DebugSymbol.getFunctionByName('copyinstr'),
|
||||
'int',
|
||||
['pointer', 'pointer', 'size_t', 'pointer']);
|
||||
const { pointerSize } = Process;
|
||||
|
||||
Interceptor.attach(DebugSymbol.getFunctionByName('__mac_execve'), {
|
||||
onEnter(args) {
|
||||
const fname = copyinstr(args[1].readPointer());
|
||||
console.log(`>>> __mac_execve() fname=${fname}`);
|
||||
},
|
||||
onLeave(retval) {
|
||||
console.log(`<<< __mac_execve() => ${retval.toInt32()}`);
|
||||
}
|
||||
});
|
||||
|
||||
Interceptor.attach(DebugSymbol.getFunctionByName('posix_spawn'), {
|
||||
onEnter(args) {
|
||||
const path = copyinstr(args[1].add(pointerSize).readPointer());
|
||||
console.log(`>>> posix_spawn() path=${path}`);
|
||||
},
|
||||
onLeave(retval) {
|
||||
console.log(`<<< posix_spawn() => ${retval.toInt32()}`);
|
||||
}
|
||||
});
|
||||
|
||||
function copyinstr(userAddr) {
|
||||
const bufSize = 4096;
|
||||
const buf = Memory.alloc(bufSize);
|
||||
const actual = Memory.alloc(pointerSize);
|
||||
if (_copyinstr(userAddr, buf, bufSize, actual) !== 0)
|
||||
throw new Error('copyinstr failed');
|
||||
return buf.readUtf8String();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
console.log("Agent loading");
|
||||
const cm = new CModule(File.readAllBytes("./target/aarch64-unknown-none/release/frida-barebone-agent"));
|
||||
//const cm = new CModule(File.readAllBytes("./target/aarch64-unknown-none/debug/frida-barebone-agent"));
|
||||
|
||||
const start = new NativeFunction(cm._start, "pointer", []);
|
||||
const bufferPhysicalAddress = start();
|
||||
console.log("Ready! Buffer is at physical address:", bufferPhysicalAddress);
|
||||
$gdb.continue();
|
||||
|
||||
Object.assign(globalThis, { cm, start });
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "frida-barebone-agent",
|
||||
"version": "1.0.0",
|
||||
"description": "xPack configuration for frida-barebone-agent",
|
||||
"xpack": {
|
||||
"minimumXpmRequired": "0.20.8",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@xpack-dev-tools/aarch64-none-elf-gcc": {
|
||||
"specifier": "14.2.1-1.1.1",
|
||||
"local": "link",
|
||||
"platforms": "all"
|
||||
}
|
||||
},
|
||||
"properties": {},
|
||||
"actions": {},
|
||||
"buildConfigurations": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use core::ptr;
|
||||
|
||||
use crate::bindings::{g_wait_is_set, gint64, gpointer};
|
||||
use crate::xnu;
|
||||
|
||||
const G_WAIT_INFINITE: gint64 = -1;
|
||||
|
||||
pub static mut WAKEUP_TOKEN: u64 = 0;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_get_monotonic_time() -> gint64 {
|
||||
let abstime = xnu::mach_absolute_time();
|
||||
let time = xnu::absolutetime_to_nanoseconds(abstime);
|
||||
(time / 1000) as gint64
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
|
||||
let wait_event = ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8;
|
||||
|
||||
let wait_result = if timeout_us == G_WAIT_INFINITE {
|
||||
xnu::assert_wait(wait_event, xnu::THREAD_INTERRUPTIBLE)
|
||||
} else {
|
||||
xnu::assert_wait_timeout(
|
||||
wait_event,
|
||||
xnu::THREAD_INTERRUPTIBLE,
|
||||
(timeout_us * 1000) as u32,
|
||||
1, // TODO
|
||||
)
|
||||
};
|
||||
if wait_result != xnu::THREAD_WAITING {
|
||||
panic!("assert_wait_timeout failed: {}", wait_result);
|
||||
}
|
||||
|
||||
if unsafe { g_wait_is_set(token) != 0 } {
|
||||
xnu::thread_wakeup(wait_event);
|
||||
return;
|
||||
}
|
||||
|
||||
xnu::thread_block(None);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_wait_wake(_token: gpointer) {
|
||||
xnu::thread_wakeup(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
use crate::bindings::GCond;
|
||||
use crate::bindings::GMutex;
|
||||
use crate::bindings::GPrivate;
|
||||
use crate::bindings::GRWLock;
|
||||
use crate::bindings::GRecMutex;
|
||||
use crate::bindings::GSystemThread;
|
||||
use crate::bindings::GThreadFunc;
|
||||
use crate::bindings::{gpointer, gulong};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
use core::arch::asm;
|
||||
use core::ffi::c_void;
|
||||
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||
|
||||
// Our implementation structures that overlay the opaque GLib structures
|
||||
|
||||
#[repr(C, align(8))]
|
||||
struct MutexImpl {
|
||||
lock: AtomicU32,
|
||||
_padding: [u8; core::mem::size_of::<GMutex>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
#[repr(C, align(8))]
|
||||
struct RecMutexImpl {
|
||||
owner: AtomicU64, // Thread ID of the owner (uses the 'p' field)
|
||||
count: AtomicU32, // Recursion count (uses i[0])
|
||||
_unused: u32, // Unused (uses i[1])
|
||||
}
|
||||
|
||||
#[repr(C, align(8))]
|
||||
struct RWLockImpl {
|
||||
state: AtomicU32, // Lock state (readers count + writer bit)
|
||||
_padding: [u8; core::mem::size_of::<GRWLock>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
#[repr(C, align(8))]
|
||||
struct CondImpl {
|
||||
signal: AtomicU32, // Signal state
|
||||
_padding: [u8; core::mem::size_of::<GCond>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
const _: () = assert!(core::mem::size_of::<MutexImpl>() == core::mem::size_of::<GMutex>());
|
||||
const _: () = assert!(core::mem::align_of::<MutexImpl>() == core::mem::align_of::<GMutex>());
|
||||
const _: () = assert!(core::mem::size_of::<RecMutexImpl>() == core::mem::size_of::<GRecMutex>());
|
||||
const _: () = assert!(core::mem::align_of::<RecMutexImpl>() == core::mem::align_of::<GRecMutex>());
|
||||
const _: () = assert!(core::mem::size_of::<RWLockImpl>() == core::mem::size_of::<GRWLock>());
|
||||
const _: () = assert!(core::mem::align_of::<RWLockImpl>() == core::mem::align_of::<GRWLock>());
|
||||
const _: () = assert!(core::mem::size_of::<CondImpl>() == core::mem::size_of::<GCond>());
|
||||
const _: () = assert!(core::mem::align_of::<CondImpl>() == core::mem::align_of::<GCond>());
|
||||
|
||||
// System thread implementation
|
||||
#[repr(C)]
|
||||
struct SystemThreadImpl {
|
||||
mutex: GMutex, // Protects the thread state
|
||||
cond: GCond, // Signals thread completion
|
||||
thread_id: AtomicU64, // XNU thread ID
|
||||
func: GThreadFunc, // Thread function
|
||||
data: gpointer, // User data
|
||||
finished: AtomicU32, // Whether thread has finished (0 = running, 1 = finished)
|
||||
detached: AtomicU32, // Whether thread is detached (1) or joinable (0)
|
||||
}
|
||||
|
||||
// Thread wrapper data passed to XNU kernel thread
|
||||
#[repr(C)]
|
||||
struct ThreadWrapperData {
|
||||
func: GThreadFunc,
|
||||
data: gpointer,
|
||||
system_thread: *mut SystemThreadImpl,
|
||||
}
|
||||
|
||||
unsafe fn mutex_impl<'a>(mutex: *mut GMutex) -> &'a mut MutexImpl {
|
||||
unsafe { &mut *(mutex as *mut MutexImpl) }
|
||||
}
|
||||
|
||||
unsafe fn rec_mutex_impl<'a>(rec_mutex: *mut GRecMutex) -> &'a mut RecMutexImpl {
|
||||
unsafe { &mut *(rec_mutex as *mut RecMutexImpl) }
|
||||
}
|
||||
|
||||
unsafe fn rw_lock_impl<'a>(rw_lock: *mut GRWLock) -> &'a mut RWLockImpl {
|
||||
unsafe { &mut *(rw_lock as *mut RWLockImpl) }
|
||||
}
|
||||
|
||||
unsafe fn cond_impl<'a>(cond: *mut GCond) -> &'a mut CondImpl {
|
||||
unsafe { &mut *(cond as *mut CondImpl) }
|
||||
}
|
||||
|
||||
// TODO: Clean up Thread-Local Storage (TLS) entries when threads exit
|
||||
static mut TLS_STORAGE: Option<Box<BTreeMap<(u64, *mut GPrivate), *mut c_void>>> = None;
|
||||
static TLS_LOCK: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_mutex_init(mutex: *mut GMutex) {
|
||||
unsafe {
|
||||
let impl_ = mutex_impl(mutex);
|
||||
impl_.lock.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_mutex_clear(_mutex: *mut GMutex) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_mutex_lock(mutex: *mut GMutex) {
|
||||
unsafe {
|
||||
let impl_ = mutex_impl(mutex);
|
||||
lock_acquire(&impl_.lock);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_mutex_unlock(mutex: *mut GMutex) {
|
||||
unsafe {
|
||||
let impl_ = mutex_impl(mutex);
|
||||
lock_release(&impl_.lock);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_mutex_trylock(mutex: *mut GMutex) -> u32 {
|
||||
unsafe {
|
||||
let impl_ = mutex_impl(mutex);
|
||||
if lock_try_acquire(&impl_.lock) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rec_mutex_init(rec_mutex: *mut GRecMutex) {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
impl_.owner.store(0, Ordering::Relaxed);
|
||||
impl_.count.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rec_mutex_clear(_rec_mutex: *mut GRecMutex) {
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
let current_thread = get_current_thread_id();
|
||||
|
||||
loop {
|
||||
if rec_mutex_try_acquire(impl_, current_thread) {
|
||||
break;
|
||||
}
|
||||
rec_mutex_wait(impl_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rec_mutex_trylock(rec_mutex: *mut GRecMutex) -> u32 {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
let current_thread = get_current_thread_id();
|
||||
|
||||
if rec_mutex_try_acquire(impl_, current_thread) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
|
||||
loop {
|
||||
let current_count = impl_.count.load(Ordering::Relaxed);
|
||||
|
||||
if current_count == 1 {
|
||||
if impl_.count
|
||||
.compare_exchange_weak(1, 0, Ordering::Release, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
impl_.owner.store(0, Ordering::Relaxed);
|
||||
lock_notify_all();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if impl_.count
|
||||
.compare_exchange_weak(current_count, current_count - 1, Ordering::Release, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -> bool {
|
||||
let current_count = impl_.count.load(Ordering::Relaxed);
|
||||
|
||||
if current_count == 0 {
|
||||
if impl_.count
|
||||
.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
impl_.owner.store(current_thread, Ordering::Relaxed);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
let current_owner = impl_.owner.load(Ordering::Relaxed);
|
||||
if current_owner == current_thread {
|
||||
if impl_.count
|
||||
.compare_exchange_weak(
|
||||
current_count,
|
||||
current_count + 1,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
|
||||
while impl_.count.load(Ordering::Relaxed) != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
// Read-Write Lock implementation
|
||||
// We use a simple scheme where the atomic value represents:
|
||||
// - 0: unlocked
|
||||
// - 1-0x7FFFFFFF: number of readers holding the lock
|
||||
// - 0x80000000: writer lock held
|
||||
const RW_WRITER_LOCK_BIT: u32 = 0x80000000;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rw_lock_init(rw_lock: *mut GRWLock) {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
impl_.state.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rw_lock_clear(_rw_lock: *mut GRWLock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rw_lock_writer_lock(rw_lock: *mut GRWLock) {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
loop {
|
||||
if rw_lock_writer_try_acquire(impl_) {
|
||||
break;
|
||||
}
|
||||
rw_lock_wait_for_all(impl_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rw_lock_writer_trylock(rw_lock: *mut GRWLock) -> u32 {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
if rw_lock_writer_try_acquire(impl_) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rw_lock_writer_unlock(rw_lock: *mut GRWLock) {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
impl_.state.store(0, Ordering::Release);
|
||||
lock_notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rw_lock_reader_lock(rw_lock: *mut GRWLock) {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
loop {
|
||||
let current = impl_.state.load(Ordering::Relaxed);
|
||||
if current & RW_WRITER_LOCK_BIT != 0 {
|
||||
rw_lock_wait_for_writers(impl_);
|
||||
continue;
|
||||
}
|
||||
if impl_.state
|
||||
.compare_exchange_weak(current, current + 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rw_lock_reader_trylock(rw_lock: *mut GRWLock) -> u32 {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
if rw_lock_reader_try_acquire(impl_) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_rw_lock_reader_unlock(rw_lock: *mut GRWLock) {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
loop {
|
||||
let current = impl_.state.load(Ordering::Relaxed);
|
||||
if impl_.state
|
||||
.compare_exchange_weak(current, current - 1, Ordering::Release, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
if current - 1 == 0 {
|
||||
lock_notify_all();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn rw_lock_writer_try_acquire(impl_: &mut RWLockImpl) -> bool {
|
||||
impl_.state
|
||||
.compare_exchange_weak(0, RW_WRITER_LOCK_BIT, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
|
||||
let current = impl_.state.load(Ordering::Relaxed);
|
||||
if current & RW_WRITER_LOCK_BIT != 0 {
|
||||
return false;
|
||||
}
|
||||
impl_.state
|
||||
.compare_exchange_weak(current, current + 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_init(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.signal.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_clear(_cond: *mut GCond) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
|
||||
unsafe {
|
||||
g_mutex_unlock(mutex);
|
||||
|
||||
let impl_ = cond_impl(cond);
|
||||
while impl_.signal.load(Ordering::Acquire) == 0 {
|
||||
lock_wait_primitive();
|
||||
}
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_signal(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.signal.store(1, Ordering::Release);
|
||||
lock_notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_broadcast(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.signal.store(1, Ordering::Release);
|
||||
lock_notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, _end_time: i64) -> u32 {
|
||||
// TODO: Implement proper timeout handling
|
||||
g_cond_wait(cond, mutex);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
|
||||
unsafe {
|
||||
let thread_id = get_current_thread_id();
|
||||
lock_acquire(&TLS_LOCK);
|
||||
|
||||
ensure_tls_storage();
|
||||
let storage_ptr = core::ptr::addr_of_mut!(TLS_STORAGE);
|
||||
let storage = (*storage_ptr).as_ref().unwrap();
|
||||
|
||||
let value = storage
|
||||
.get(&(thread_id, key))
|
||||
.copied()
|
||||
.unwrap_or(core::ptr::null_mut());
|
||||
|
||||
lock_release(&TLS_LOCK);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_private_set(key: *mut GPrivate, value: *mut c_void) {
|
||||
unsafe {
|
||||
let thread_id = get_current_thread_id();
|
||||
lock_acquire(&TLS_LOCK);
|
||||
|
||||
ensure_tls_storage();
|
||||
let storage_ptr = core::ptr::addr_of_mut!(TLS_STORAGE);
|
||||
let storage = (*storage_ptr).as_mut().unwrap();
|
||||
|
||||
storage.insert((thread_id, key), value);
|
||||
|
||||
lock_release(&TLS_LOCK);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn ensure_tls_storage() {
|
||||
let storage_ptr = core::ptr::addr_of_mut!(TLS_STORAGE);
|
||||
unsafe {
|
||||
if (*storage_ptr).is_none() {
|
||||
*storage_ptr = Some(Box::new(BTreeMap::new()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _g_system_thread_create(
|
||||
_stack_size: gulong,
|
||||
_name: *const core::ffi::c_char,
|
||||
func: GThreadFunc,
|
||||
data: gpointer,
|
||||
) -> *mut GSystemThread {
|
||||
unsafe {
|
||||
let system_thread = crate::xnu::kalloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
|
||||
|
||||
g_mutex_init(&mut (*system_thread).mutex);
|
||||
g_cond_init(&mut (*system_thread).cond);
|
||||
(*system_thread).thread_id.store(0, Ordering::Relaxed);
|
||||
(*system_thread).func = func;
|
||||
(*system_thread).data = data;
|
||||
(*system_thread).finished.store(0, Ordering::Relaxed);
|
||||
(*system_thread).detached.store(0, Ordering::Relaxed);
|
||||
|
||||
let wrapper_data = crate::xnu::kalloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
|
||||
(*wrapper_data).func = func;
|
||||
(*wrapper_data).data = data;
|
||||
(*wrapper_data).system_thread = system_thread;
|
||||
|
||||
let _xnu_result = crate::xnu::kernel_thread_start(thread_wrapper, wrapper_data as *mut c_void);
|
||||
|
||||
system_thread as *mut GSystemThread
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn thread_wrapper(parameter: *mut c_void, _wait_result: i32) {
|
||||
unsafe {
|
||||
let wrapper_data = parameter as *mut ThreadWrapperData;
|
||||
let func = (*wrapper_data).func;
|
||||
let data = (*wrapper_data).data;
|
||||
let system_thread = (*wrapper_data).system_thread;
|
||||
|
||||
let current_thread_id = get_current_thread_id();
|
||||
(*system_thread).thread_id.store(current_thread_id, Ordering::Relaxed);
|
||||
|
||||
func.unwrap()(data);
|
||||
|
||||
g_mutex_lock(&mut (*system_thread).mutex);
|
||||
(*system_thread).finished.store(1, Ordering::Relaxed);
|
||||
g_cond_signal(&mut (*system_thread).cond);
|
||||
|
||||
let detached = (*system_thread).detached.load(Ordering::Relaxed);
|
||||
g_mutex_unlock(&mut (*system_thread).mutex);
|
||||
|
||||
if detached != 0 {
|
||||
cleanup_system_thread(system_thread);
|
||||
}
|
||||
|
||||
cleanup_wrapper_data(wrapper_data);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _g_system_thread_detach(thread: *mut GSystemThread) {
|
||||
unsafe {
|
||||
let system_thread = thread as *mut SystemThreadImpl;
|
||||
|
||||
g_mutex_lock(&mut (*system_thread).mutex);
|
||||
(*system_thread).detached.store(1, Ordering::Relaxed);
|
||||
|
||||
let finished = (*system_thread).finished.load(Ordering::Relaxed);
|
||||
g_mutex_unlock(&mut (*system_thread).mutex);
|
||||
if finished != 0 {
|
||||
cleanup_system_thread(system_thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
|
||||
unsafe {
|
||||
let system_thread = thread as *mut SystemThreadImpl;
|
||||
|
||||
loop {
|
||||
let thread_id = (*system_thread).thread_id.load(Ordering::Acquire);
|
||||
if thread_id != 0 {
|
||||
break;
|
||||
}
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
g_mutex_lock(&mut (*system_thread).mutex);
|
||||
while (*system_thread).finished.load(Ordering::Relaxed) == 0 {
|
||||
g_cond_wait(&mut (*system_thread).cond, &mut (*system_thread).mutex);
|
||||
}
|
||||
g_mutex_unlock(&mut (*system_thread).mutex);
|
||||
|
||||
cleanup_system_thread(system_thread);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn cleanup_system_thread(system_thread: *mut SystemThreadImpl) {
|
||||
crate::xnu::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
|
||||
}
|
||||
|
||||
unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
|
||||
crate::xnu::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
|
||||
}
|
||||
|
||||
unsafe fn lock_acquire(lock: &AtomicU32) {
|
||||
loop {
|
||||
if unsafe { lock_try_acquire(lock) } {
|
||||
break;
|
||||
}
|
||||
unsafe { lock_wait(lock) };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn lock_try_acquire(lock: &AtomicU32) -> bool {
|
||||
lock.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
unsafe fn lock_release(lock: &AtomicU32) {
|
||||
lock.store(0, Ordering::Release);
|
||||
unsafe { lock_notify_all() };
|
||||
}
|
||||
|
||||
unsafe fn lock_wait(lock: &AtomicU32) {
|
||||
while lock.load(Ordering::Relaxed) != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn lock_wait_primitive() {
|
||||
unsafe {
|
||||
asm!("wfe", options(nomem, nostack));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn lock_notify_all() {
|
||||
unsafe {
|
||||
asm!("sev", options(nomem, nostack));
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_current_thread_id() -> u64 {
|
||||
let thread_ptr: u64;
|
||||
unsafe {
|
||||
asm!("mrs {}, tpidr_el1", out(reg) thread_ptr, options(nomem, nostack));
|
||||
}
|
||||
thread_ptr
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GInterfaceInfo, _GTypeInfo, _GumPageProtection_GUM_PAGE_EXECUTE,
|
||||
_GumRwxSupport_GUM_RWX_NONE, GArray, GObject, GObjectClass, GPrivate, GType,
|
||||
GumDebugSymbolDetails, GumExportDetails, GumExportType_GUM_EXPORT_FUNCTION,
|
||||
GumFoundExportFunc, GumMemoryRange, GumModule, GumModuleInterface, GumModuleRegistry,
|
||||
GumPageProtection, GumRwxSupport, GumThreadId, GumTlsKey, g_array_append_vals, g_array_new,
|
||||
g_free, g_object_get_type, g_object_new, g_object_unref, g_once_init_enter,
|
||||
g_once_init_leave, g_strdup, g_type_add_interface_static, g_type_class_peek_parent,
|
||||
g_type_register_static, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
|
||||
gum_barebone_register_module,
|
||||
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
|
||||
gum_module_get_type, gum_mprotect, gum_query_page_size,
|
||||
},
|
||||
gthread, libc, xnu,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::ffi::CString;
|
||||
use alloc::format;
|
||||
use core::ptr;
|
||||
use core::{ffi::CStr, ptr::read_volatile};
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_process_get_current_thread_id() -> GumThreadId {
|
||||
unsafe { gthread::get_current_thread_id() }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_page_size() -> guint {
|
||||
unsafe {
|
||||
let tcr_el1: u64;
|
||||
core::arch::asm!("mrs {}, tcr_el1", out(reg) tcr_el1, options(nomem, nostack));
|
||||
|
||||
let tg0 = (tcr_el1 >> 14) & 0x3;
|
||||
|
||||
match tg0 {
|
||||
0b00 => 4096,
|
||||
0b01 => 65536,
|
||||
0b10 => 16384,
|
||||
_ => 4096,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
unsafe {
|
||||
let page_size = gum_query_page_size() as usize;
|
||||
let mut physical_addrs = alloc::vec::Vec::with_capacity(n_pages as usize);
|
||||
|
||||
let mut current_page = first_page as u64;
|
||||
for _ in 0..n_pages {
|
||||
let phys_addr = xnu::ml_vtophys(current_page);
|
||||
physical_addrs.push(phys_addr as gpointer);
|
||||
current_page += page_size as u64;
|
||||
}
|
||||
|
||||
_gum_barebone_try_remap_writable_pages(
|
||||
physical_addrs.as_ptr() as *mut *const core::ffi::c_void,
|
||||
physical_addrs.len() as guint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
#[inline(never)]
|
||||
pub extern "C" fn gum_barebone_try_remap_writable_pages(
|
||||
addrs: *const gpointer,
|
||||
n_addrs: guint,
|
||||
) -> gpointer {
|
||||
unsafe {
|
||||
read_volatile(&n_addrs);
|
||||
read_volatile(addrs)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
_address: gpointer,
|
||||
_size: gsize,
|
||||
_prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
_address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let ptr = crate::xnu::kalloc(size as usize);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
crate::xnu::free(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_clear_cache(address: gpointer, size: gsize) {
|
||||
unsafe {
|
||||
let start = address as *const u8;
|
||||
let end = start.add(size as usize);
|
||||
libc::__clear_cache(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_tls_key_new() -> GumTlsKey {
|
||||
Box::into_raw(Box::new(unsafe { core::mem::zeroed::<GPrivate>() })) as GumTlsKey
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_tls_key_free(key: GumTlsKey) {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(key as *mut GPrivate);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_tls_key_get_value(key: GumTlsKey) -> gpointer {
|
||||
gthread::g_private_get(key as *mut GPrivate)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_tls_key_set_value(key: GumTlsKey, value: gpointer) {
|
||||
gthread::g_private_set(key as *mut GPrivate, value)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
let kernel_base = crate::xnu::get_kernel_base();
|
||||
|
||||
unsafe {
|
||||
let module_infos = core::ptr::addr_of!(crate::MODULE_INFO);
|
||||
let module_infos = &*module_infos;
|
||||
|
||||
let mut i = 0;
|
||||
for module_info in module_infos.iter() {
|
||||
let module_base = kernel_base + module_info.offset as u64;
|
||||
|
||||
let module_path = if i == 0 {
|
||||
"/System/Library/Kernels/kernel"
|
||||
} else {
|
||||
&format!(
|
||||
"/System/Library/Extensions/{}.kext/{}",
|
||||
module_info.name, module_info.name
|
||||
)
|
||||
};
|
||||
let module_range = GumMemoryRange {
|
||||
base_address: module_base,
|
||||
size: module_info.size as u64,
|
||||
};
|
||||
|
||||
let module = gum_native_module_new(&module_path, &module_info.version, &module_range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct GumNativeModule {
|
||||
parent: GObject,
|
||||
name: *mut gchar,
|
||||
version: *mut gchar,
|
||||
path: *mut gchar,
|
||||
range: GumMemoryRange,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(dead_code)]
|
||||
pub struct GumNativeModuleClass {
|
||||
parent_class: GObjectClass,
|
||||
}
|
||||
|
||||
static mut GUM_NATIVE_MODULE_TYPE: gsize = 0;
|
||||
static mut GUM_NATIVE_MODULE_PARENT_CLASS: *mut GObjectClass = core::ptr::null_mut();
|
||||
|
||||
fn gum_native_module_get_type() -> GType {
|
||||
unsafe {
|
||||
if g_once_init_enter(
|
||||
core::ptr::addr_of_mut!(GUM_NATIVE_MODULE_TYPE) as *mut ::core::ffi::c_void
|
||||
) != 0
|
||||
{
|
||||
let type_name = c"GumNativeModule".as_ptr() as *const gchar;
|
||||
|
||||
let type_info = _GTypeInfo {
|
||||
class_size: core::mem::size_of::<GObjectClass>() as u16,
|
||||
base_init: None,
|
||||
base_finalize: None,
|
||||
class_init: Some(gum_native_module_class_init),
|
||||
class_finalize: None,
|
||||
class_data: core::ptr::null(),
|
||||
instance_size: core::mem::size_of::<GumNativeModule>() as u16,
|
||||
n_preallocs: 0,
|
||||
instance_init: None,
|
||||
value_table: core::ptr::null(),
|
||||
};
|
||||
|
||||
let new_type = g_type_register_static(g_object_get_type(), type_name, &type_info, 0);
|
||||
|
||||
let interface_info = _GInterfaceInfo {
|
||||
interface_init: Some(gum_native_module_iface_init),
|
||||
interface_finalize: None,
|
||||
interface_data: core::ptr::null_mut(),
|
||||
};
|
||||
|
||||
g_type_add_interface_static(new_type, gum_module_get_type(), &interface_info);
|
||||
|
||||
g_once_init_leave(
|
||||
core::ptr::addr_of_mut!(GUM_NATIVE_MODULE_TYPE) as *mut ::core::ffi::c_void,
|
||||
new_type as gsize,
|
||||
);
|
||||
}
|
||||
|
||||
GUM_NATIVE_MODULE_TYPE as GType
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn gum_native_module_class_init(klass: gpointer, _class_data: gpointer) {
|
||||
unsafe {
|
||||
let object_class = klass as *mut GObjectClass;
|
||||
|
||||
GUM_NATIVE_MODULE_PARENT_CLASS = g_type_class_peek_parent(klass) as *mut GObjectClass;
|
||||
|
||||
(*object_class).finalize = Some(gum_native_module_finalize);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpointer) {
|
||||
unsafe {
|
||||
let iface = g_iface as *mut GumModuleInterface;
|
||||
(*iface).get_name = Some(gum_native_module_get_name);
|
||||
(*iface).get_version = Some(gum_native_module_get_version);
|
||||
(*iface).get_path = Some(gum_native_module_get_path);
|
||||
(*iface).get_range = Some(gum_native_module_get_range);
|
||||
(*iface).enumerate_exports = Some(gum_native_module_enumerate_exports);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn gum_native_module_finalize(object: *mut GObject) {
|
||||
unsafe {
|
||||
let module = object as *mut GumNativeModule;
|
||||
|
||||
g_free((*module).path as gpointer);
|
||||
|
||||
(*GUM_NATIVE_MODULE_PARENT_CLASS).finalize.unwrap()(object);
|
||||
}
|
||||
}
|
||||
|
||||
fn gum_native_module_new(path: &str, version: &str, range: &GumMemoryRange) -> *mut GumModule {
|
||||
unsafe {
|
||||
let path_cstr = CString::new(path).unwrap();
|
||||
let version_cstr = CString::new(version).unwrap();
|
||||
|
||||
let module =
|
||||
g_object_new(gum_native_module_get_type(), ptr::null()) as *mut GumNativeModule;
|
||||
(*module).path = g_strdup(path_cstr.as_ptr());
|
||||
(*module).name = (*module).path.add(path.rfind('/').unwrap() + 1);
|
||||
(*module).version = g_strdup(version_cstr.as_ptr());
|
||||
(*module).range = *range;
|
||||
|
||||
module as *mut GumModule
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn gum_native_module_get_name(module: *mut GumModule) -> *const gchar {
|
||||
unsafe {
|
||||
let native_module = module as *mut GumNativeModule;
|
||||
(*native_module).name as *const gchar
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn gum_native_module_get_version(module: *mut GumModule) -> *const gchar {
|
||||
unsafe {
|
||||
let native_module = module as *mut GumNativeModule;
|
||||
(*native_module).version as *const gchar
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn gum_native_module_get_path(module: *mut GumModule) -> *const gchar {
|
||||
unsafe {
|
||||
let native_module = module as *mut GumNativeModule;
|
||||
(*native_module).path as *const gchar
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn gum_native_module_get_range(module: *mut GumModule) -> *const GumMemoryRange {
|
||||
unsafe {
|
||||
let native_module = module as *mut GumNativeModule;
|
||||
&(*native_module).range as *const GumMemoryRange
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn gum_native_module_enumerate_exports(
|
||||
self_: *mut GumModule,
|
||||
func: GumFoundExportFunc,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let module = self_ as *mut GumNativeModule;
|
||||
let module_range = &(*module).range;
|
||||
|
||||
let symbol_table = core::ptr::addr_of!(crate::SYMBOL_TABLE);
|
||||
let symbol_table = &*symbol_table;
|
||||
|
||||
const N_EXT: u8 = 0x01; // External symbol flag
|
||||
const N_TYPE: u8 = 0x0e; // Type mask
|
||||
const N_SECT: u8 = 0x0e; // Defined in section
|
||||
|
||||
let start_address = module_range.base_address;
|
||||
let end_address = module_range.base_address + module_range.size;
|
||||
|
||||
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
|
||||
let is_external = (symbol_ref.symbol_type() & N_EXT) != 0;
|
||||
let is_defined = (symbol_ref.symbol_type() & N_TYPE) == N_SECT;
|
||||
if !is_external || !is_defined {
|
||||
continue;
|
||||
}
|
||||
|
||||
let export_type = GumExportType_GUM_EXPORT_FUNCTION;
|
||||
|
||||
let export_details = GumExportDetails {
|
||||
type_: export_type,
|
||||
name: g_strdup(symbol_ref.name_ptr()),
|
||||
address: symbol_ref.address(),
|
||||
};
|
||||
|
||||
let should_continue =
|
||||
func.unwrap()(&export_details as *const GumExportDetails, user_data);
|
||||
if should_continue == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_details_from_address(
|
||||
address: gpointer,
|
||||
details: *mut GumDebugSymbolDetails,
|
||||
) -> gboolean {
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
if let Some(symbol) = table.find_symbol_by_address(address as u64) {
|
||||
let name_bytes = symbol.name().as_bytes();
|
||||
let copy_len = core::cmp::min(name_bytes.len(), 2048);
|
||||
core::ptr::copy_nonoverlapping(
|
||||
name_bytes.as_ptr(),
|
||||
(*details).symbol_name.as_mut_ptr() as *mut u8,
|
||||
copy_len,
|
||||
);
|
||||
(*details).symbol_name[copy_len] = 0;
|
||||
|
||||
(*details).address = symbol.address();
|
||||
|
||||
(*details).module_name[0] = 0;
|
||||
(*details).file_name[0] = 0;
|
||||
(*details).line_number = 0;
|
||||
(*details).column = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
let name_ptr = table.find_symbol_name_ptr_by_address(address as u64);
|
||||
g_strdup(name_ptr as *const gchar)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
|
||||
unsafe {
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
if let Some(symbol) = table.find_symbol_by_name(&target_name) {
|
||||
return symbol.address() as gpointer;
|
||||
}
|
||||
|
||||
core::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_by_name(&target_name);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let glob_pattern = CStr::from_ptr(pattern).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_matching_glob(&glob_pattern);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
use core::cell::UnsafeCell;
|
||||
use core::ffi::c_void;
|
||||
use core::mem::size_of;
|
||||
use core::ptr::{read_volatile, write_volatile};
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use crate::gum::gum_barebone_query_page_size;
|
||||
use crate::xnu;
|
||||
|
||||
const MMIO_SIZE: u64 = 0x200;
|
||||
|
||||
static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
const QSZ: u16 = 64;
|
||||
|
||||
const MAGIC: usize = 0x000;
|
||||
const VERSION: usize = 0x004;
|
||||
const DEVICE: usize = 0x008;
|
||||
const STATUS: usize = 0x070;
|
||||
|
||||
const DEVFEAT: usize = 0x010;
|
||||
const DEVFEAT_SEL: usize = 0x014;
|
||||
const DRVFEAT: usize = 0x020;
|
||||
const DRVFEAT_SEL: usize = 0x024;
|
||||
|
||||
const QSEL: usize = 0x030;
|
||||
const QNUM_MAX: usize = 0x034;
|
||||
const QNUM: usize = 0x038;
|
||||
const QREADY: usize = 0x044;
|
||||
|
||||
const QDESC_LO: usize = 0x080;
|
||||
const QDESC_HI: usize = 0x084;
|
||||
const QAVAIL_LO: usize = 0x090;
|
||||
const QAVAIL_HI: usize = 0x094;
|
||||
const QUSED_LO: usize = 0x0a0;
|
||||
const QUSED_HI: usize = 0x0a4;
|
||||
|
||||
const QNOTIFY: usize = 0x050;
|
||||
const ISR: usize = 0x060;
|
||||
const ISR_ACK: usize = 0x064;
|
||||
|
||||
const ST_ACK: u32 = 1;
|
||||
const ST_DRV: u32 = 2;
|
||||
const ST_DRV_OK: u32 = 4;
|
||||
const ST_FEAT_OK: u32 = 8;
|
||||
const ST_FAILED: u32 = 0x80;
|
||||
|
||||
const DEV_ID_CONSOLE: u32 = 3;
|
||||
const F_VERSION_1: u64 = 1u64 << 32;
|
||||
const F_MULTIPORT: u64 = 1u64 << 1;
|
||||
|
||||
const INT_VRING: u32 = 1;
|
||||
|
||||
const Q_RX0: u16 = 0;
|
||||
const Q_TX0: u16 = 1;
|
||||
const Q_CTRL_RX: u16 = 2;
|
||||
const Q_CTRL_TX: u16 = 3;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct VConsCtrl {
|
||||
id: u32,
|
||||
event: u16,
|
||||
value: u16,
|
||||
}
|
||||
const EV_DEVICE_READY: u16 = 0;
|
||||
const EV_DEVICE_ADD: u16 = 1;
|
||||
const EV_PORT_READY: u16 = 3;
|
||||
const EV_PORT_OPEN: u16 = 6;
|
||||
const EV_CONSOLE_PORT: u16 = 4;
|
||||
|
||||
#[repr(C)]
|
||||
struct Desc {
|
||||
addr: u64,
|
||||
len: u32,
|
||||
flags: u16,
|
||||
next: u16,
|
||||
}
|
||||
const D_NEXT: u16 = 1;
|
||||
const D_WRITE: u16 = 2;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct Avail {
|
||||
flags: u16,
|
||||
idx: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct UsedElem {
|
||||
id: u32,
|
||||
len: u32,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct Used {
|
||||
flags: u16,
|
||||
idx: u16,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct DmaPage {
|
||||
va: *mut u8,
|
||||
pa: u64,
|
||||
}
|
||||
|
||||
fn dma_page_alloc() -> DmaPage {
|
||||
let len = PAGE_SIZE.load(Ordering::Relaxed);
|
||||
let va = xnu::kalloc(len);
|
||||
let pa = xnu::ml_vtophys(va as u64);
|
||||
DmaPage { va, pa }
|
||||
}
|
||||
|
||||
fn dma_page_free(p: DmaPage) {
|
||||
let len = PAGE_SIZE.load(Ordering::Relaxed);
|
||||
xnu::free(p.va, len);
|
||||
}
|
||||
|
||||
struct Vq {
|
||||
sel: u16,
|
||||
size: u16,
|
||||
desc_va: *mut u8,
|
||||
avail_va: *mut u8,
|
||||
used_va: *mut u8,
|
||||
avail_idx: u16,
|
||||
used_idx: u16,
|
||||
free_head: u16,
|
||||
free_cnt: u16,
|
||||
}
|
||||
impl Vq {
|
||||
fn new(mmio: *mut u8, sel: u16, size: u16) -> Self {
|
||||
w32(mmio, QSEL, sel as u32);
|
||||
let max = r32(mmio, QNUM_MAX) as u16;
|
||||
debug_assert!(max >= size && max != 0);
|
||||
w32(mmio, QNUM, size as u32);
|
||||
|
||||
let d = dma_page_alloc();
|
||||
let a = dma_page_alloc();
|
||||
let u = dma_page_alloc();
|
||||
|
||||
let ps = PAGE_SIZE.load(core::sync::atomic::Ordering::Relaxed);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(d.va, 0, ps);
|
||||
core::ptr::write_bytes(a.va, 0, ps);
|
||||
core::ptr::write_bytes(u.va, 0, ps);
|
||||
}
|
||||
|
||||
w64(mmio, QDESC_LO, QDESC_HI, d.pa);
|
||||
w64(mmio, QAVAIL_LO, QAVAIL_HI, a.pa);
|
||||
w64(mmio, QUSED_LO, QUSED_HI, u.pa);
|
||||
|
||||
let dp = d.va as *mut Desc;
|
||||
for i in 0..size {
|
||||
unsafe {
|
||||
(*dp.add(i as usize)).flags = 0;
|
||||
(*dp.add(i as usize)).next = if i + 1 < size { i + 1 } else { 0xFFFF };
|
||||
}
|
||||
}
|
||||
|
||||
w32(mmio, QREADY, 1);
|
||||
|
||||
Self {
|
||||
sel,
|
||||
size,
|
||||
desc_va: d.va,
|
||||
avail_va: a.va,
|
||||
used_va: u.va,
|
||||
avail_idx: 0,
|
||||
used_idx: 0,
|
||||
free_head: 0,
|
||||
free_cnt: size,
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc(&mut self) -> u16 {
|
||||
debug_assert!(self.free_cnt > 0);
|
||||
let h = self.free_head;
|
||||
let dp = self.desc_va as *mut Desc;
|
||||
unsafe {
|
||||
self.free_head = (*dp.add(h as usize)).next;
|
||||
}
|
||||
self.free_cnt -= 1;
|
||||
h
|
||||
}
|
||||
|
||||
fn free_chain(&mut self, mut idx: u16) {
|
||||
let dp = self.desc_va as *mut Desc;
|
||||
loop {
|
||||
self.free_cnt += 1;
|
||||
let (flags, next) = unsafe {
|
||||
let p = dp.add(idx as usize);
|
||||
((*p).flags, (*p).next)
|
||||
};
|
||||
if (flags & D_NEXT) == 0 {
|
||||
break;
|
||||
}
|
||||
idx = next;
|
||||
}
|
||||
unsafe {
|
||||
(*dp.add(idx as usize)).next = self.free_head;
|
||||
}
|
||||
self.free_head = idx;
|
||||
}
|
||||
|
||||
fn push_avail(&mut self, head: u16) {
|
||||
let ap = self.avail_va as *mut Avail;
|
||||
let ring = unsafe { (ap as *mut u8).add(size_of::<Avail>()) as *mut u16 };
|
||||
let slot = (self.avail_idx % self.size) as usize;
|
||||
unsafe {
|
||||
*ring.add(slot) = head;
|
||||
}
|
||||
wmb();
|
||||
self.avail_idx = self.avail_idx.wrapping_add(1);
|
||||
unsafe {
|
||||
(*ap).idx = self.avail_idx;
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_used(&mut self) -> Option<UsedElem> {
|
||||
let up = self.used_va as *mut Used;
|
||||
if unsafe { (*up).idx } == self.used_idx {
|
||||
return None;
|
||||
}
|
||||
let ring = unsafe { (up as *mut u8).add(size_of::<Used>()) as *mut UsedElem };
|
||||
let elem = unsafe { *ring.add((self.used_idx % self.size) as usize) };
|
||||
self.used_idx = self.used_idx.wrapping_add(1);
|
||||
Some(elem)
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
mmio: *mut u8,
|
||||
|
||||
ctrl_rx: Vq,
|
||||
ctrl_tx: Vq,
|
||||
|
||||
port_id: Option<u32>,
|
||||
rx: Option<Vq>,
|
||||
tx: Option<Vq>,
|
||||
|
||||
rx_need: usize,
|
||||
rx_have: usize,
|
||||
rx_lenbuf: [u8; 4],
|
||||
rx_lenhave: usize,
|
||||
rx_buf: Option<&'static mut [u8]>,
|
||||
|
||||
tx_head: *mut TxNode,
|
||||
tx_tail: *mut TxNode,
|
||||
|
||||
ctrl_rx_pages: [Option<DmaPage>; QSZ as usize],
|
||||
data_rx_pages: [Option<DmaPage>; QSZ as usize],
|
||||
tx_pages: [Option<DmaPage>; QSZ as usize],
|
||||
|
||||
wake_token: *const u8,
|
||||
|
||||
on_rx: Option<fn(&[u8])>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct TxNode {
|
||||
next: *mut TxNode,
|
||||
frame: &'static [u8],
|
||||
}
|
||||
|
||||
pub struct Hostlink {
|
||||
state: UnsafeCell<Inner>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Hostlink {}
|
||||
|
||||
impl Hostlink {
|
||||
pub fn init(mmio_base: u64, irq_line: u32, on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
|
||||
let page_size = gum_barebone_query_page_size();
|
||||
PAGE_SIZE.store(page_size as usize, Ordering::Relaxed);
|
||||
|
||||
let mmio = xnu::ml_io_map(mmio_base, MMIO_SIZE) as *mut u8;
|
||||
if mmio.is_null() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
w32(mmio, STATUS, 0);
|
||||
w32(mmio, STATUS, ST_ACK | ST_DRV);
|
||||
|
||||
let magic_ok = r32(mmio, MAGIC) == 0x7472_6976;
|
||||
let version_ok = r32(mmio, VERSION) == 2;
|
||||
let device_ok = r32(mmio, DEVICE) == DEV_ID_CONSOLE;
|
||||
if !(magic_ok && version_ok && device_ok) {
|
||||
w32(mmio, STATUS, ST_FAILED);
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let dev_lo = feat_get(mmio, 0) as u64;
|
||||
let dev_hi = (feat_get(mmio, 1) as u64) << 32;
|
||||
let mut drv: u64 = 0;
|
||||
if (dev_hi & F_VERSION_1) != 0 {
|
||||
drv |= F_VERSION_1;
|
||||
}
|
||||
if (dev_lo & F_MULTIPORT) != 0 {
|
||||
drv |= F_MULTIPORT;
|
||||
}
|
||||
feat_set(mmio, 0, (drv & 0xffff_ffff) as u32);
|
||||
feat_set(mmio, 1, (drv >> 32) as u32);
|
||||
w32(mmio, STATUS, r32(mmio, STATUS) | ST_FEAT_OK);
|
||||
let feats_ok = (r32(mmio, STATUS) & ST_FEAT_OK) != 0;
|
||||
if !feats_ok {
|
||||
w32(mmio, STATUS, ST_FAILED);
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let ctrl_rx = Vq::new(mmio, Q_CTRL_RX, QSZ);
|
||||
let ctrl_tx = Vq::new(mmio, Q_CTRL_TX, QSZ);
|
||||
|
||||
w32(mmio, STATUS, r32(mmio, STATUS) | ST_DRV_OK);
|
||||
|
||||
xnu::install_interrupt_handler(
|
||||
irq_line,
|
||||
wake_token as *mut c_void,
|
||||
isr_wake,
|
||||
core::ptr::null_mut(),
|
||||
);
|
||||
|
||||
let inner = Inner {
|
||||
mmio,
|
||||
ctrl_rx,
|
||||
ctrl_tx,
|
||||
port_id: None,
|
||||
rx: None,
|
||||
tx: None,
|
||||
rx_need: 0,
|
||||
rx_have: 0,
|
||||
rx_lenbuf: [0; 4],
|
||||
rx_lenhave: 0,
|
||||
rx_buf: None,
|
||||
tx_head: core::ptr::null_mut(),
|
||||
tx_tail: core::ptr::null_mut(),
|
||||
ctrl_rx_pages: [None; QSZ as usize],
|
||||
data_rx_pages: [None; QSZ as usize],
|
||||
tx_pages: [None; QSZ as usize],
|
||||
wake_token,
|
||||
on_rx,
|
||||
};
|
||||
|
||||
let hl = Hostlink {
|
||||
state: UnsafeCell::new(inner),
|
||||
};
|
||||
|
||||
hl.ctrl_prime_rx(8);
|
||||
hl.ctrl_send(VConsCtrl {
|
||||
id: 0,
|
||||
event: EV_DEVICE_READY,
|
||||
value: 1,
|
||||
});
|
||||
|
||||
Ok(hl)
|
||||
}
|
||||
|
||||
pub fn send(&self, payload: &[u8]) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
|
||||
let total = 4 + payload.len();
|
||||
let buf = xnu::kalloc(total);
|
||||
unsafe {
|
||||
let len = payload.len() as u32;
|
||||
*buf.add(0) = (len & 0xFF) as u8;
|
||||
*buf.add(1) = ((len >> 8) & 0xFF) as u8;
|
||||
*buf.add(2) = ((len >> 16) & 0xFF) as u8;
|
||||
*buf.add(3) = ((len >> 24) & 0xFF) as u8;
|
||||
core::ptr::copy_nonoverlapping(payload.as_ptr(), buf.add(4), payload.len());
|
||||
}
|
||||
let frame: &'static [u8] = unsafe { core::slice::from_raw_parts(buf, total) };
|
||||
|
||||
let node = xnu::kalloc(size_of::<TxNode>()) as *mut TxNode;
|
||||
unsafe {
|
||||
(*node).next = core::ptr::null_mut();
|
||||
(*node).frame = frame;
|
||||
}
|
||||
|
||||
if s.tx_tail.is_null() {
|
||||
s.tx_head = node;
|
||||
s.tx_tail = node;
|
||||
} else {
|
||||
unsafe {
|
||||
(*s.tx_tail).next = node;
|
||||
}
|
||||
s.tx_tail = node;
|
||||
}
|
||||
|
||||
xnu::thread_wakeup(s.wake_token);
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
|
||||
if (r32(s.mmio, ISR) & INT_VRING) != 0 {
|
||||
w32(s.mmio, ISR_ACK, INT_VRING);
|
||||
}
|
||||
|
||||
self.ctrl_complete();
|
||||
self.ctrl_prime_rx(QSZ as usize);
|
||||
|
||||
self.data_rx_complete();
|
||||
self.data_tx_complete();
|
||||
self.data_tx_push();
|
||||
self.data_rx_refill();
|
||||
}
|
||||
|
||||
fn ctrl_prime_rx(&self, count: usize) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
let mut posted = 0usize;
|
||||
while posted < count {
|
||||
if s.ctrl_rx.free_cnt == 0 {
|
||||
break;
|
||||
}
|
||||
let pg = dma_page_alloc();
|
||||
let h = s.ctrl_rx.alloc();
|
||||
let d = s.ctrl_rx.desc_va as *mut Desc;
|
||||
unsafe {
|
||||
(*d.add(h as usize)).addr = pg.pa;
|
||||
(*d.add(h as usize)).len = PAGE_SIZE.load(Ordering::Relaxed) as u32;
|
||||
(*d.add(h as usize)).flags = D_WRITE;
|
||||
(*d.add(h as usize)).next = 0;
|
||||
}
|
||||
s.ctrl_rx_pages[h as usize] = Some(pg);
|
||||
s.ctrl_rx.push_avail(h);
|
||||
posted += 1;
|
||||
}
|
||||
let sel = s.ctrl_rx.sel;
|
||||
self.kick(sel);
|
||||
}
|
||||
|
||||
fn ctrl_send(&self, msg: VConsCtrl) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
let pg = dma_page_alloc();
|
||||
unsafe {
|
||||
core::ptr::write(pg.va as *mut VConsCtrl, msg);
|
||||
}
|
||||
let h = s.ctrl_tx.alloc();
|
||||
let d = s.ctrl_tx.desc_va as *mut Desc;
|
||||
unsafe {
|
||||
(*d.add(h as usize)).addr = pg.pa;
|
||||
(*d.add(h as usize)).len = size_of::<VConsCtrl>() as u32;
|
||||
(*d.add(h as usize)).flags = 0;
|
||||
(*d.add(h as usize)).next = 0;
|
||||
}
|
||||
s.tx_pages[h as usize] = Some(pg);
|
||||
s.ctrl_tx.push_avail(h);
|
||||
let sel = s.ctrl_tx.sel;
|
||||
self.kick(sel);
|
||||
}
|
||||
|
||||
fn ctrl_complete(&self) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
while let Some(u) = s.ctrl_rx.pop_used() {
|
||||
let h = u.id as u16;
|
||||
if let Some(pg) = s.ctrl_rx_pages[h as usize].take() {
|
||||
let ev = unsafe { *(pg.va as *const VConsCtrl) };
|
||||
dma_page_free(pg);
|
||||
|
||||
match ev.event {
|
||||
EV_DEVICE_ADD | EV_CONSOLE_PORT => {
|
||||
if s.port_id.is_none() {
|
||||
self.setup_data_port(ev.id);
|
||||
self.ctrl_send(VConsCtrl {
|
||||
id: ev.id,
|
||||
event: EV_PORT_READY,
|
||||
value: 1,
|
||||
});
|
||||
self.ctrl_send(VConsCtrl {
|
||||
id: ev.id,
|
||||
event: EV_PORT_OPEN,
|
||||
value: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let pg2 = dma_page_alloc();
|
||||
let d = s.ctrl_rx.desc_va as *mut Desc;
|
||||
unsafe {
|
||||
(*d.add(h as usize)).addr = pg2.pa;
|
||||
(*d.add(h as usize)).len = PAGE_SIZE.load(Ordering::Relaxed) as u32;
|
||||
(*d.add(h as usize)).flags = D_WRITE;
|
||||
(*d.add(h as usize)).next = 0;
|
||||
}
|
||||
s.ctrl_rx_pages[h as usize] = Some(pg2);
|
||||
s.ctrl_rx.push_avail(h);
|
||||
}
|
||||
}
|
||||
let sel = s.ctrl_rx.sel;
|
||||
self.kick(sel);
|
||||
|
||||
while let Some(u) = s.ctrl_tx.pop_used() {
|
||||
let head = u.id as u16;
|
||||
if let Some(pg) = s.tx_pages[head as usize].take() {
|
||||
dma_page_free(pg);
|
||||
}
|
||||
s.ctrl_tx.free_chain(head);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_data_port(&self, id: u32) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
if s.port_id.is_some() {
|
||||
return;
|
||||
}
|
||||
let (rx_i, tx_i) = if id == 0 {
|
||||
(Q_RX0, Q_TX0)
|
||||
} else {
|
||||
let base = 4 + ((id as u16 - 1) * 2);
|
||||
(base, base + 1)
|
||||
};
|
||||
let rx = Vq::new(s.mmio, rx_i, QSZ);
|
||||
let tx = Vq::new(s.mmio, tx_i, QSZ);
|
||||
s.rx = Some(rx);
|
||||
s.tx = Some(tx);
|
||||
s.port_id = Some(id);
|
||||
self.data_rx_refill();
|
||||
}
|
||||
|
||||
fn data_rx_refill(&self) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
let Some(rxq) = s.rx.as_mut() else {
|
||||
return;
|
||||
};
|
||||
while rxq.free_cnt > 0 {
|
||||
let h = rxq.alloc();
|
||||
let pg = dma_page_alloc();
|
||||
let d = rxq.desc_va as *mut Desc;
|
||||
unsafe {
|
||||
(*d.add(h as usize)).addr = pg.pa;
|
||||
(*d.add(h as usize)).len = PAGE_SIZE.load(Ordering::Relaxed) as u32;
|
||||
(*d.add(h as usize)).flags = D_WRITE;
|
||||
(*d.add(h as usize)).next = 0;
|
||||
}
|
||||
s.data_rx_pages[h as usize] = Some(pg);
|
||||
rxq.push_avail(h);
|
||||
}
|
||||
let sel = rxq.sel;
|
||||
self.kick(sel);
|
||||
}
|
||||
|
||||
fn data_rx_complete(&self) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
let Some(rxq) = s.rx.as_mut() else {
|
||||
return;
|
||||
};
|
||||
while let Some(u) = rxq.pop_used() {
|
||||
let h = u.id as u16;
|
||||
let n = u.len as usize;
|
||||
|
||||
if let Some(pg) = s.data_rx_pages[h as usize].take() {
|
||||
{
|
||||
let bytes = unsafe { core::slice::from_raw_parts(pg.va, n) };
|
||||
self.feed_rx_stream(bytes);
|
||||
}
|
||||
dma_page_free(pg);
|
||||
|
||||
let pg2 = dma_page_alloc();
|
||||
let d = rxq.desc_va as *mut Desc;
|
||||
unsafe {
|
||||
(*d.add(h as usize)).addr = pg2.pa;
|
||||
(*d.add(h as usize)).len = PAGE_SIZE.load(Ordering::Relaxed) as u32;
|
||||
(*d.add(h as usize)).flags = D_WRITE;
|
||||
(*d.add(h as usize)).next = 0;
|
||||
}
|
||||
s.data_rx_pages[h as usize] = Some(pg2);
|
||||
rxq.push_avail(h);
|
||||
}
|
||||
}
|
||||
let sel = rxq.sel;
|
||||
self.kick(sel);
|
||||
}
|
||||
|
||||
fn feed_rx_stream(&self, mut chunk: &[u8]) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
while !chunk.is_empty() {
|
||||
if s.rx_lenhave < 4 {
|
||||
let need = 4 - s.rx_lenhave;
|
||||
let take = core::cmp::min(need, chunk.len());
|
||||
s.rx_lenbuf[s.rx_lenhave..s.rx_lenhave + take].copy_from_slice(&chunk[..take]);
|
||||
s.rx_lenhave += take;
|
||||
chunk = &chunk[take..];
|
||||
if s.rx_lenhave < 4 {
|
||||
return;
|
||||
}
|
||||
|
||||
let len = (s.rx_lenbuf[0] as usize)
|
||||
| ((s.rx_lenbuf[1] as usize) << 8)
|
||||
| ((s.rx_lenbuf[2] as usize) << 16)
|
||||
| ((s.rx_lenbuf[3] as usize) << 24);
|
||||
|
||||
if s.rx_buf.is_none() && len > 0 {
|
||||
let buf = xnu::kalloc(len);
|
||||
let slice: &'static mut [u8] =
|
||||
unsafe { core::slice::from_raw_parts_mut(buf, len) };
|
||||
s.rx_buf = Some(slice);
|
||||
s.rx_need = len;
|
||||
s.rx_have = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let need = s.rx_need.saturating_sub(s.rx_have);
|
||||
let take = core::cmp::min(need, chunk.len());
|
||||
if let Some(ref mut buf) = s.rx_buf {
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
chunk.as_ptr(),
|
||||
buf.as_mut_ptr().wrapping_add(s.rx_have),
|
||||
take,
|
||||
);
|
||||
}
|
||||
}
|
||||
s.rx_have += take;
|
||||
chunk = &chunk[take..];
|
||||
|
||||
if s.rx_have == s.rx_need {
|
||||
if let (Some(cb), Some(buf)) = (s.on_rx, &s.rx_buf) {
|
||||
cb(&buf[..]);
|
||||
}
|
||||
s.rx_buf = None;
|
||||
s.rx_need = 0;
|
||||
s.rx_have = 0;
|
||||
s.rx_lenhave = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn data_tx_complete(&self) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
let Some(txq) = s.tx.as_mut() else {
|
||||
return;
|
||||
};
|
||||
while let Some(u) = txq.pop_used() {
|
||||
let mut i = u.id as u16;
|
||||
loop {
|
||||
if let Some(pg) = s.tx_pages[i as usize].take() {
|
||||
dma_page_free(pg);
|
||||
}
|
||||
let d = txq.desc_va as *mut Desc;
|
||||
let (f, n) = unsafe {
|
||||
let p = d.add(i as usize);
|
||||
((*p).flags, (*p).next)
|
||||
};
|
||||
if (f & D_NEXT) == 0 {
|
||||
break;
|
||||
}
|
||||
i = n;
|
||||
}
|
||||
txq.free_chain(u.id as u16);
|
||||
}
|
||||
}
|
||||
|
||||
fn data_tx_push(&self) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
let Some(txq) = s.tx.as_mut() else {
|
||||
return;
|
||||
};
|
||||
loop {
|
||||
let node = if s.tx_head.is_null() {
|
||||
core::ptr::null_mut()
|
||||
} else {
|
||||
let n = s.tx_head;
|
||||
s.tx_head = unsafe { (*n).next };
|
||||
if s.tx_head.is_null() {
|
||||
s.tx_tail = core::ptr::null_mut();
|
||||
}
|
||||
n
|
||||
};
|
||||
if node.is_null() {
|
||||
break;
|
||||
}
|
||||
let frame = unsafe { (*node).frame };
|
||||
|
||||
let mut off = 0usize;
|
||||
let mut head: Option<u16> = None;
|
||||
let mut prev = 0u16;
|
||||
|
||||
while off < frame.len() {
|
||||
if txq.free_cnt == 0 {
|
||||
break;
|
||||
}
|
||||
let page = PAGE_SIZE.load(Ordering::Relaxed);
|
||||
let chunk = core::cmp::min(page, frame.len() - off);
|
||||
let pg = dma_page_alloc();
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(frame.as_ptr().add(off), pg.va, chunk);
|
||||
}
|
||||
|
||||
let i = txq.alloc();
|
||||
let d = txq.desc_va as *mut Desc;
|
||||
unsafe {
|
||||
(*d.add(i as usize)).addr = pg.pa;
|
||||
(*d.add(i as usize)).len = chunk as u32;
|
||||
(*d.add(i as usize)).flags = 0;
|
||||
(*d.add(i as usize)).next = 0;
|
||||
}
|
||||
s.tx_pages[i as usize] = Some(pg);
|
||||
|
||||
if let Some(_h) = head {
|
||||
unsafe {
|
||||
(*d.add(prev as usize)).flags |= D_NEXT;
|
||||
(*d.add(prev as usize)).next = i;
|
||||
}
|
||||
prev = i;
|
||||
} else {
|
||||
head = Some(i);
|
||||
prev = i;
|
||||
}
|
||||
|
||||
off += chunk;
|
||||
}
|
||||
|
||||
if let Some(h) = head {
|
||||
let sel = txq.sel;
|
||||
txq.push_avail(h);
|
||||
self.kick(sel);
|
||||
}
|
||||
|
||||
xnu::free(frame.as_ptr() as *mut u8, frame.len());
|
||||
xnu::free(node as *mut u8, core::mem::size_of::<TxNode>());
|
||||
}
|
||||
}
|
||||
|
||||
fn kick(&self, sel: u16) {
|
||||
let s = unsafe { &*self.state.get() };
|
||||
wmb();
|
||||
w32(s.mmio, QSEL, sel as u32);
|
||||
w32(s.mmio, QNOTIFY, sel as u32);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn isr_wake(token: *mut c_void, _refcon: *mut c_void, _nub: *mut c_void, _src: i32) {
|
||||
xnu::thread_wakeup(token as *const u8);
|
||||
}
|
||||
|
||||
fn r32(mmio: *mut u8, off: usize) -> u32 {
|
||||
unsafe { read_volatile(mmio.add(off) as *const u32) }
|
||||
}
|
||||
|
||||
fn w32(mmio: *mut u8, off: usize, val: u32) {
|
||||
unsafe { write_volatile(mmio.add(off) as *mut u32, val) }
|
||||
}
|
||||
|
||||
fn w64(mmio: *mut u8, lo: usize, hi: usize, v: u64) {
|
||||
w32(mmio, lo, (v & 0xffff_ffff) as u32);
|
||||
w32(mmio, hi, (v >> 32) as u32);
|
||||
}
|
||||
|
||||
fn feat_get(mmio: *mut u8, sel: u32) -> u32 {
|
||||
w32(mmio, DEVFEAT_SEL, sel);
|
||||
r32(mmio, DEVFEAT)
|
||||
}
|
||||
|
||||
fn feat_set(mmio: *mut u8, sel: u32, v: u32) {
|
||||
w32(mmio, DRVFEAT_SEL, sel);
|
||||
w32(mmio, DRVFEAT, v)
|
||||
}
|
||||
|
||||
fn wmb() {
|
||||
unsafe { core::arch::asm!("dmb ishst", options(nostack, preserves_flags)) }
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use core::ptr;
|
||||
use crate::bindings::{GMutex, GRecMutex};
|
||||
use crate::gthread;
|
||||
|
||||
static mut MALLOC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __malloc_lock(_reent: *mut core::ffi::c_void) {
|
||||
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(MALLOC_LOCK));
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __malloc_unlock(_reent: *mut core::ffi::c_void) {
|
||||
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(MALLOC_LOCK));
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _fini() {
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _exit(_status: i32) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _kill(_pid: i32, _sig: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
|
||||
const HEAP_SIZE: usize = 32 * 1024 * 1024; // 32 MB
|
||||
|
||||
static mut HEAP_START: *mut u8 = ptr::null_mut();
|
||||
static mut HEAP_CURRENT: *mut u8 = ptr::null_mut();
|
||||
static mut HEAP_MUTEX: GMutex = unsafe { core::mem::zeroed() };
|
||||
|
||||
unsafe {
|
||||
gthread::g_mutex_lock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
|
||||
if HEAP_START.is_null() {
|
||||
HEAP_START = crate::xnu::kalloc(HEAP_SIZE);
|
||||
if HEAP_START.is_null() {
|
||||
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
panic!("Failed to allocate 32 MB heap");
|
||||
}
|
||||
HEAP_CURRENT = HEAP_START;
|
||||
}
|
||||
|
||||
let prev_heap_current = HEAP_CURRENT;
|
||||
let new_heap_current = HEAP_CURRENT.add(incr as usize);
|
||||
|
||||
if new_heap_current > HEAP_START.add(HEAP_SIZE) {
|
||||
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
panic!("Heap exhausted: requested {} bytes, {} bytes remaining",
|
||||
incr,
|
||||
HEAP_START.add(HEAP_SIZE).offset_from(HEAP_CURRENT));
|
||||
}
|
||||
|
||||
HEAP_CURRENT = new_heap_current;
|
||||
|
||||
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
prev_heap_current
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _getpid() -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Timeval {
|
||||
tv_sec: i64,
|
||||
tv_usec: i64,
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _gettimeofday(tp: *mut core::ffi::c_void, _tzp: *mut core::ffi::c_void) -> i32 {
|
||||
let (secs, microsecs) = crate::xnu::clock_get_calendar_microtime();
|
||||
let timeval = Timeval {
|
||||
tv_sec: secs as i64,
|
||||
tv_usec: microsecs as i64,
|
||||
};
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(
|
||||
&timeval as *const Timeval as *const u8,
|
||||
tp as *mut u8,
|
||||
core::mem::size_of::<Timeval>(),
|
||||
);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _isatty(_fd: i32) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _close(_fd: i32) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _write(_fd: i32, _buf: *const u8, count: usize) -> isize {
|
||||
count as isize
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _link(_old: *const u8, _new: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _unlink(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
|
||||
// On AArch64, we need to flush the instruction cache
|
||||
// This is a simplified implementation for the kernel environment
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ic iallu", // Invalidate all instruction caches
|
||||
"dsb sy", // Data synchronization barrier
|
||||
"isb", // Instruction synchronization barrier
|
||||
options(nomem, nostack),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::alloc::{GlobalAlloc, Layout};
|
||||
use core::ffi::{CStr, c_void};
|
||||
use core::ptr;
|
||||
use core::ptr::null_mut;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use bindings::{
|
||||
GBytes, GCancellable, GError, GVariant, GVariantIter, GumScript, g_error_free, g_free,
|
||||
g_main_context_default, g_main_context_iteration, g_memdup2, g_object_unref,
|
||||
g_variant_check_format_string, g_variant_get, g_variant_get_child_value, g_variant_get_data,
|
||||
g_variant_get_size, g_variant_get_string, g_variant_get_uint32, g_variant_get_uint64,
|
||||
g_variant_iter_init, g_variant_iter_next, g_variant_new, g_variant_new_from_data,
|
||||
g_variant_new_string, g_variant_new_tuple, g_variant_new_uint32, g_variant_type_free,
|
||||
g_variant_type_new, g_variant_unref, gchar, gpointer, gsize, gum_script_backend_create_sync,
|
||||
gum_script_backend_obtain_qjs, gum_script_load_sync, gum_script_post,
|
||||
gum_script_set_message_handler, gum_script_unload_sync,
|
||||
};
|
||||
use hostlink_virtio::Hostlink;
|
||||
use symbols::SymbolTable;
|
||||
|
||||
mod glib;
|
||||
mod gthread;
|
||||
mod gum;
|
||||
mod hostlink_virtio;
|
||||
|
||||
mod libc;
|
||||
mod pac;
|
||||
mod symbols;
|
||||
mod xnu;
|
||||
|
||||
mod bindings {
|
||||
#![allow(
|
||||
dead_code,
|
||||
improper_ctypes,
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
non_upper_case_globals,
|
||||
unused_imports
|
||||
)]
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum FridaCommand {
|
||||
CreateScript = 1,
|
||||
LoadScript = 2,
|
||||
DestroyScript = 3,
|
||||
PostScriptMessage = 4,
|
||||
|
||||
Reply = 128,
|
||||
ScriptMessage = 129,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for FridaCommand {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
FridaCommand::CreateScript => write!(f, "CreateScript"),
|
||||
FridaCommand::LoadScript => write!(f, "LoadScript"),
|
||||
FridaCommand::DestroyScript => write!(f, "DestroyScript"),
|
||||
FridaCommand::PostScriptMessage => write!(f, "PostScriptMessage"),
|
||||
FridaCommand::Reply => write!(f, "Reply"),
|
||||
FridaCommand::ScriptMessage => write!(f, "ScriptMessage"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HandlerResponse {
|
||||
variant: *mut GVariant,
|
||||
}
|
||||
|
||||
impl HandlerResponse {
|
||||
fn success(variant: *mut GVariant) -> Self {
|
||||
Self { variant }
|
||||
}
|
||||
|
||||
fn success_empty() -> Self {
|
||||
let variant = unsafe { g_variant_new_tuple(ptr::null(), 0) };
|
||||
Self { variant }
|
||||
}
|
||||
|
||||
fn error(message: &str) -> Self {
|
||||
let mut c_message = String::from(message);
|
||||
c_message.push('\0');
|
||||
let error_variant = unsafe { g_variant_new_string(c_message.as_ptr()) };
|
||||
|
||||
Self {
|
||||
variant: error_variant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut CONFIG_DATA: &'static [u8] = &[];
|
||||
pub static mut MODULE_INFO: Vec<ModuleInfo> = Vec::new();
|
||||
pub static mut SYMBOL_TABLE: SymbolTable = SymbolTable::empty();
|
||||
|
||||
static mut TRANSPORT_DRIVER: *mut Hostlink = core::ptr::null_mut();
|
||||
|
||||
#[inline(always)]
|
||||
fn transport_set(driver: Hostlink) {
|
||||
unsafe {
|
||||
let boxed = Box::into_raw(Box::new(driver));
|
||||
TRANSPORT_DRIVER = boxed;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn transport_get_unchecked() -> &'static Hostlink {
|
||||
unsafe {
|
||||
debug_assert!(!TRANSPORT_DRIVER.is_null());
|
||||
&*TRANSPORT_DRIVER
|
||||
}
|
||||
}
|
||||
|
||||
static mut SCRIPTS: BTreeMap<u32, *mut GumScript> = BTreeMap::new();
|
||||
static NEXT_SCRIPT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModuleInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub offset: u32,
|
||||
pub size: u32,
|
||||
pub start_func_offset: u32,
|
||||
pub stop_func_offset: u32,
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn _start(config_data: *const u8, config_size: usize) {
|
||||
unsafe {
|
||||
CONFIG_DATA = core::slice::from_raw_parts(config_data, config_size);
|
||||
|
||||
xnu::kernel_thread_start(frida_agent_worker, 12345usize as *mut core::ffi::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_agent_worker(_parameter: *mut core::ffi::c_void, _wait_result: i32) {
|
||||
unsafe {
|
||||
bindings::g_set_panic_handler(Some(frida_panic_handler), ptr::null_mut());
|
||||
bindings::gum_init_embedded();
|
||||
bindings::g_log_set_default_handler(Some(frida_log_handler), ptr::null_mut());
|
||||
|
||||
let (mmio, irq, kernel_base, module_info, symbol_table) =
|
||||
parse_config(core::ptr::addr_of!(CONFIG_DATA).read());
|
||||
xnu::set_kernel_base(kernel_base);
|
||||
MODULE_INFO = module_info;
|
||||
SYMBOL_TABLE = symbol_table;
|
||||
|
||||
transport_set(
|
||||
Hostlink::init(
|
||||
mmio,
|
||||
irq,
|
||||
Some(on_frame_from_host),
|
||||
ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let main_context = g_main_context_default();
|
||||
|
||||
loop {
|
||||
transport_get_unchecked().process();
|
||||
g_main_context_iteration(main_context, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_frame_from_host(frame: &[u8]) {
|
||||
if let Some(variant) = deserialize_message(&frame) {
|
||||
process_incoming_message(variant);
|
||||
unsafe {
|
||||
g_variant_unref(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn parse_config(config: &[u8]) -> (u64, u32, u64, Vec<ModuleInfo>, SymbolTable) {
|
||||
unsafe {
|
||||
let type_string = c"(tuta(ssuuuu)ay)".as_ptr() as *const gchar;
|
||||
let variant_type = g_variant_type_new(type_string);
|
||||
|
||||
let root_variant = g_variant_new_from_data(
|
||||
variant_type,
|
||||
config.as_ptr() as *const core::ffi::c_void,
|
||||
config.len() as gsize,
|
||||
1,
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
let hostlink_mmio_variant = g_variant_get_child_value(root_variant, 0);
|
||||
let hostlink_mmio = g_variant_get_uint64(hostlink_mmio_variant);
|
||||
|
||||
let hostlink_irq_variant = g_variant_get_child_value(root_variant, 1);
|
||||
let hostlink_irq = g_variant_get_uint32(hostlink_irq_variant);
|
||||
|
||||
let kernel_base_variant = g_variant_get_child_value(root_variant, 2);
|
||||
let kernel_base = g_variant_get_uint64(kernel_base_variant);
|
||||
xnu::set_kernel_base(kernel_base);
|
||||
|
||||
let module_info_variant = g_variant_get_child_value(root_variant, 3);
|
||||
let mut iter: GVariantIter = core::mem::zeroed();
|
||||
g_variant_iter_init(&mut iter as *mut GVariantIter, module_info_variant);
|
||||
|
||||
let mut module_info: Vec<ModuleInfo> = Vec::new();
|
||||
let mut raw_name: *mut gchar = null_mut();
|
||||
let mut raw_version: *mut gchar = null_mut();
|
||||
let mut offset: u32 = 0;
|
||||
let mut size: u32 = 0;
|
||||
let mut start_func_offset: u32 = 0;
|
||||
let mut stop_func_offset: u32 = 0;
|
||||
while g_variant_iter_next(
|
||||
&mut iter as *mut GVariantIter,
|
||||
c"(&s&suuuu)".as_ptr(),
|
||||
&mut raw_name,
|
||||
&mut raw_version,
|
||||
&mut offset,
|
||||
&mut size,
|
||||
&mut start_func_offset,
|
||||
&mut stop_func_offset,
|
||||
) != 0
|
||||
{
|
||||
module_info.push(ModuleInfo {
|
||||
name: String::from(CStr::from_ptr(raw_name).to_str().unwrap()),
|
||||
version: String::from(CStr::from_ptr(raw_version).to_str().unwrap()),
|
||||
offset,
|
||||
size,
|
||||
start_func_offset,
|
||||
stop_func_offset,
|
||||
});
|
||||
}
|
||||
|
||||
let symbol_array_variant = g_variant_get_child_value(root_variant, 4);
|
||||
let symbol_data_ptr = g_variant_get_data(symbol_array_variant) as *const u8;
|
||||
let symbol_data_size = g_variant_get_size(symbol_array_variant) as usize;
|
||||
let symbol_table = SymbolTable::new(core::slice::from_raw_parts(
|
||||
symbol_data_ptr,
|
||||
symbol_data_size,
|
||||
));
|
||||
|
||||
g_variant_unref(symbol_array_variant);
|
||||
g_variant_unref(module_info_variant);
|
||||
g_variant_unref(kernel_base_variant);
|
||||
g_variant_unref(hostlink_irq_variant);
|
||||
g_variant_unref(hostlink_mmio_variant);
|
||||
g_variant_unref(root_variant);
|
||||
g_variant_type_free(variant_type);
|
||||
|
||||
(
|
||||
hostlink_mmio,
|
||||
hostlink_irq,
|
||||
kernel_base,
|
||||
module_info,
|
||||
symbol_table,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn serialize_message(variant: *mut GVariant) -> Option<Vec<u8>> {
|
||||
unsafe {
|
||||
let size = g_variant_get_size(variant) as usize;
|
||||
if size == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let data_ptr = g_variant_get_data(variant) as *const u8;
|
||||
let mut result = Vec::with_capacity(size);
|
||||
result.resize(size, 0);
|
||||
core::ptr::copy_nonoverlapping(data_ptr, result.as_mut_ptr(), size);
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_message(data: &[u8]) -> Option<*mut GVariant> {
|
||||
unsafe {
|
||||
if data.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let variant_type = g_variant_type_new(c"(yqv)".as_ptr());
|
||||
let data_copy = g_memdup2(data.as_ptr() as *const c_void, data.len() as u64);
|
||||
let variant = g_variant_new_from_data(
|
||||
variant_type,
|
||||
data_copy,
|
||||
data.len() as u64,
|
||||
0,
|
||||
Some(g_free),
|
||||
data_copy,
|
||||
);
|
||||
g_variant_type_free(variant_type);
|
||||
|
||||
if variant.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(variant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_incoming_message(variant: *mut GVariant) {
|
||||
{
|
||||
let mut cmd_value: u8 = 0;
|
||||
let mut request_id: u16 = 0;
|
||||
let mut payload_variant: *mut GVariant = ptr::null_mut();
|
||||
|
||||
let cmd = unsafe {
|
||||
g_variant_get(
|
||||
variant,
|
||||
c"(yqv)".as_ptr(),
|
||||
&mut cmd_value,
|
||||
&mut request_id,
|
||||
&mut payload_variant,
|
||||
);
|
||||
|
||||
core::mem::transmute::<u8, FridaCommand>(cmd_value)
|
||||
};
|
||||
|
||||
let response = match cmd {
|
||||
FridaCommand::CreateScript => handle_create_script(payload_variant),
|
||||
FridaCommand::LoadScript => handle_load_script(payload_variant),
|
||||
FridaCommand::DestroyScript => handle_destroy_script(payload_variant),
|
||||
FridaCommand::PostScriptMessage => handle_post_script_message(payload_variant),
|
||||
_ => HandlerResponse::error("Unknown command"),
|
||||
};
|
||||
|
||||
send_command_reply(request_id, response);
|
||||
|
||||
unsafe { g_variant_unref(payload_variant) };
|
||||
}
|
||||
}
|
||||
|
||||
fn send_command_reply(request_id: u16, response: HandlerResponse) {
|
||||
unsafe {
|
||||
let message = g_variant_new(
|
||||
c"(yqv)".as_ptr(),
|
||||
FridaCommand::Reply as u8 as u32,
|
||||
request_id as u32,
|
||||
response.variant,
|
||||
);
|
||||
|
||||
if let Some(serialized) = serialize_message(message) {
|
||||
transport_get_unchecked().send(&serialized);
|
||||
}
|
||||
|
||||
g_variant_unref(message);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_create_script(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"s".as_ptr(), 0) == 0 {
|
||||
return HandlerResponse::error("Invalid payload format: expected string");
|
||||
}
|
||||
let source = g_variant_get_string(payload_variant, core::ptr::null_mut());
|
||||
|
||||
let backend = gum_script_backend_obtain_qjs();
|
||||
let cancellable: *mut GCancellable = ptr::null_mut();
|
||||
let mut error: *mut GError = ptr::null_mut();
|
||||
|
||||
let script = gum_script_backend_create_sync(
|
||||
backend,
|
||||
c"agent.js".as_ptr(),
|
||||
source,
|
||||
ptr::null_mut(),
|
||||
cancellable,
|
||||
&mut error,
|
||||
);
|
||||
|
||||
if !error.is_null() {
|
||||
let error_msg = CStr::from_ptr((*error).message).to_str().unwrap();
|
||||
let error_string = String::from(error_msg);
|
||||
g_error_free(error);
|
||||
return HandlerResponse::error(&error_string);
|
||||
}
|
||||
|
||||
let script_id = NEXT_SCRIPT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let script_id_ptr = Box::into_raw(Box::new(script_id));
|
||||
gum_script_set_message_handler(
|
||||
script,
|
||||
Some(frida_message_handler),
|
||||
script_id_ptr as *mut c_void,
|
||||
None,
|
||||
);
|
||||
|
||||
core::ptr::addr_of_mut!(SCRIPTS)
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(script_id, script);
|
||||
|
||||
HandlerResponse::success(g_variant_new_uint32(script_id))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_message_handler(
|
||||
message: *const gchar,
|
||||
_data: *mut GBytes,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let script_id = *(user_data as *const u32);
|
||||
|
||||
let message_variant = g_variant_new(
|
||||
c"(yqv)".as_ptr(),
|
||||
FridaCommand::ScriptMessage as u8 as u32,
|
||||
0u32,
|
||||
g_variant_new(c"(us)".as_ptr(), script_id, message),
|
||||
);
|
||||
|
||||
if let Some(serialized) = serialize_message(message_variant) {
|
||||
transport_get_unchecked().send(&serialized);
|
||||
}
|
||||
|
||||
g_variant_unref(message_variant);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_load_script(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
|
||||
return HandlerResponse::error("Invalid payload format: expected uint32");
|
||||
}
|
||||
let script_id = g_variant_get_uint32(payload_variant);
|
||||
|
||||
let Some(script) = get_script_by_id(script_id) else {
|
||||
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
|
||||
};
|
||||
|
||||
gum_script_load_sync(script, ptr::null_mut());
|
||||
|
||||
HandlerResponse::success_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_destroy_script(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
|
||||
return HandlerResponse::error("Invalid payload format: expected uint32");
|
||||
}
|
||||
let script_id = g_variant_get_uint32(payload_variant);
|
||||
|
||||
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
|
||||
let Some(script) = scripts.remove(&script_id) else {
|
||||
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
|
||||
};
|
||||
|
||||
gum_script_unload_sync(script, ptr::null_mut());
|
||||
g_object_unref(script as *mut c_void);
|
||||
|
||||
HandlerResponse::success_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_post_script_message(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"(us)".as_ptr(), 0) == 0 {
|
||||
return HandlerResponse::error("Invalid payload format: expected (uint32, string)");
|
||||
}
|
||||
let mut script_id: u32 = 0;
|
||||
let mut message: *const gchar = ptr::null();
|
||||
g_variant_get(
|
||||
payload_variant,
|
||||
c"(u&s)".as_ptr(),
|
||||
&mut script_id,
|
||||
&mut message,
|
||||
);
|
||||
|
||||
let Some(script) = get_script_by_id(script_id) else {
|
||||
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
|
||||
};
|
||||
|
||||
gum_script_post(script, message, ptr::null_mut());
|
||||
|
||||
HandlerResponse::success_empty()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_script_by_id(script_id: u32) -> Option<*mut GumScript> {
|
||||
unsafe {
|
||||
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
|
||||
scripts.get(&script_id).copied()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_panic_handler(message: *const u8, _user_data: *mut core::ffi::c_void) {
|
||||
let msg = unsafe { core::ffi::CStr::from_ptr(message).to_str().unwrap() };
|
||||
panic!("[Frida] {}", msg);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_log_handler(
|
||||
_log_domain: *const core::ffi::c_char,
|
||||
_log_level: i32,
|
||||
message: *const core::ffi::c_char,
|
||||
_user_data: *mut core::ffi::c_void,
|
||||
) {
|
||||
let msg = unsafe { core::ffi::CStr::from_ptr(message).to_str().unwrap() };
|
||||
kprintln!("[Frida] {}", msg);
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
|
||||
let mut s = format!("{}", info);
|
||||
s.push('\0');
|
||||
xnu::panic(s.as_str());
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub struct XnuAllocator;
|
||||
|
||||
unsafe impl GlobalAlloc for XnuAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
xnu::kalloc(layout.size())
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
xnu::free(ptr, layout.size());
|
||||
}
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: XnuAllocator = XnuAllocator;
|
||||
extern crate alloc;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! kprintln {
|
||||
($($arg:tt)*) => {{
|
||||
use core::fmt::Write;
|
||||
let mut buf = alloc::string::String::new();
|
||||
write!(&mut buf, $($arg)*).unwrap();
|
||||
buf.push('\n');
|
||||
buf.push('\0');
|
||||
crate::xnu::io_log(&buf)
|
||||
}};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use core::arch::asm;
|
||||
|
||||
pub unsafe fn ptrauth_sign(ptr: *const u8, discriminator: usize) -> *const u8 {
|
||||
let signed: usize;
|
||||
unsafe {
|
||||
asm!(
|
||||
".inst 0xdac10020", // pacia x0, x1
|
||||
in("x0") ptr as usize,
|
||||
in("x1") discriminator,
|
||||
lateout("x0") signed,
|
||||
options(nomem, nostack),
|
||||
);
|
||||
}
|
||||
signed as *const u8
|
||||
}
|
||||
|
||||
pub unsafe fn ptrauth_strip_data(ptr: *const u8) -> *const u8 {
|
||||
let stripped: usize;
|
||||
unsafe {
|
||||
asm!(
|
||||
".inst 0xdac143e0", // xpaci x0
|
||||
in("x0") ptr as usize,
|
||||
lateout("x0") stripped,
|
||||
options(nomem, nostack),
|
||||
);
|
||||
}
|
||||
stripped as *const u8
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
use crate::bindings::{
|
||||
GPatternSpec, g_pattern_spec_free, g_pattern_spec_match_string, g_pattern_spec_new,
|
||||
};
|
||||
use crate::xnu::get_kernel_base;
|
||||
use alloc::ffi::CString;
|
||||
use core::cmp::Ordering;
|
||||
use core::ffi::{CStr, c_char};
|
||||
use core::mem::size_of;
|
||||
use core::ptr;
|
||||
|
||||
pub struct SymbolTable {
|
||||
data: &'static [u8],
|
||||
symbol_count: usize,
|
||||
}
|
||||
|
||||
impl SymbolTable {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
data: &[],
|
||||
symbol_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(data: &'static [u8]) -> Self {
|
||||
if data.len() < 4 {
|
||||
return Self::empty();
|
||||
}
|
||||
let symbol_count = unsafe { *(data.as_ptr() as *const u32) as usize };
|
||||
Self { data, symbol_count }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.symbol_count == 0
|
||||
}
|
||||
|
||||
pub fn symbol_count(&self) -> usize {
|
||||
self.symbol_count
|
||||
}
|
||||
|
||||
pub fn find_symbol_by_name(&self, name: &str) -> Option<SymbolRef> {
|
||||
let (_, entry) = self.binary_search_by_name(name)?;
|
||||
Some(SymbolRef {
|
||||
symbol_table: self,
|
||||
entry,
|
||||
kernel_base: get_kernel_base(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_symbols_by_name(&self, name: &str) -> SymbolsByNameIterator {
|
||||
let (found_index, _) = match self.binary_search_by_name(name) {
|
||||
Some(result) => result,
|
||||
None => return SymbolsByNameIterator::empty(self),
|
||||
};
|
||||
|
||||
let mut start = found_index;
|
||||
let mut end = found_index;
|
||||
|
||||
while start > 0 {
|
||||
let entry = self.get_symbol_entry_by_name_index(start - 1);
|
||||
let symbol_name = entry.name(self);
|
||||
if symbol_name == name {
|
||||
start -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while end + 1 < self.symbol_count {
|
||||
let entry = self.get_symbol_entry_by_name_index(end + 1);
|
||||
let symbol_name = entry.name(self);
|
||||
if symbol_name == name {
|
||||
end += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SymbolsByNameIterator {
|
||||
symbol_table: self,
|
||||
current_index: start,
|
||||
end_index: end + 1,
|
||||
kernel_base: get_kernel_base(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_symbols_matching_glob(&self, pattern: &str) -> SymbolsMatchingIterator {
|
||||
if self.is_empty() {
|
||||
return SymbolsMatchingIterator::empty(self);
|
||||
}
|
||||
|
||||
let pattern_cstr = CString::new(pattern).unwrap();
|
||||
let pspec = unsafe { g_pattern_spec_new(pattern_cstr.as_ptr()) };
|
||||
|
||||
SymbolsMatchingIterator {
|
||||
symbol_table: self,
|
||||
current_index: 0,
|
||||
end_index: self.symbol_count,
|
||||
pspec,
|
||||
kernel_base: get_kernel_base(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_symbol_by_address(&self, address: u64) -> Option<SymbolRef> {
|
||||
let target_offset = (address - get_kernel_base()) as u32;
|
||||
let (_, entry) = self.binary_search_by_address(target_offset)?;
|
||||
Some(SymbolRef {
|
||||
symbol_table: self,
|
||||
entry,
|
||||
kernel_base: get_kernel_base(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_symbol_name_ptr_by_address(&self, address: u64) -> *const c_char {
|
||||
let target_offset = (address - get_kernel_base()) as u32;
|
||||
if let Some((_, entry)) = self.binary_search_by_address(target_offset) {
|
||||
entry.name_ptr(self)
|
||||
} else {
|
||||
ptr::null()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_closest_symbol_by_address(&self, address: u64) -> Option<SymbolRef> {
|
||||
let kernel_base = get_kernel_base();
|
||||
let target_offset = (address - kernel_base) as u32;
|
||||
let (_, entry) = self.binary_search_closest_by_address(target_offset)?;
|
||||
Some(SymbolRef {
|
||||
symbol_table: self,
|
||||
entry,
|
||||
kernel_base,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter_symbols_in_range(&self, start_address: u64, end_address: u64) -> SymbolsInRangeIterator {
|
||||
if self.is_empty() {
|
||||
return SymbolsInRangeIterator {
|
||||
symbol_table: self,
|
||||
current_index: 0,
|
||||
end_index: 0,
|
||||
end_offset: 0,
|
||||
kernel_base: get_kernel_base(),
|
||||
};
|
||||
}
|
||||
|
||||
let kernel_base = get_kernel_base();
|
||||
let start_offset = (start_address - kernel_base) as u32;
|
||||
let end_offset = (end_address - kernel_base) as u32;
|
||||
|
||||
let mut left = 0;
|
||||
let mut right = self.symbol_count;
|
||||
|
||||
while left < right {
|
||||
let mid = left + (right - left) / 2;
|
||||
let entry = self.get_symbol_entry_by_address_index(mid);
|
||||
|
||||
if entry.address_offset < start_offset {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
while left > 0 {
|
||||
let prev_entry = self.get_symbol_entry_by_address_index(left - 1);
|
||||
if prev_entry.address_offset >= start_offset {
|
||||
left -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SymbolsInRangeIterator {
|
||||
symbol_table: self,
|
||||
current_index: left,
|
||||
end_index: self.symbol_count,
|
||||
end_offset,
|
||||
kernel_base,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_symbol_entry_by_name_index(&self, index: usize) -> &SymbolEntry {
|
||||
let offset = self.name_index_start() + index * 4;
|
||||
let symbol_offset = unsafe { *(self.data.as_ptr().add(offset) as *const u32) as usize };
|
||||
self.symbol_entry_at(symbol_offset)
|
||||
}
|
||||
|
||||
fn get_symbol_entry_by_address_index(&self, index: usize) -> &SymbolEntry {
|
||||
let offset = self.address_index_start() + index * 4;
|
||||
let symbol_offset = unsafe { *(self.data.as_ptr().add(offset) as *const u32) as usize };
|
||||
self.symbol_entry_at(symbol_offset)
|
||||
}
|
||||
|
||||
fn symbol_entry_at(&self, offset: usize) -> &SymbolEntry {
|
||||
unsafe { &*(self.data.as_ptr().add(offset) as *const SymbolEntry) }
|
||||
}
|
||||
|
||||
fn name_index_start(&self) -> usize {
|
||||
4
|
||||
}
|
||||
|
||||
fn address_index_start(&self) -> usize {
|
||||
4 + self.symbol_count * 4
|
||||
}
|
||||
|
||||
fn binary_search_by_name(&self, name: &str) -> Option<(usize, &SymbolEntry)> {
|
||||
self.binary_search(
|
||||
|table, mid| table.get_symbol_entry_by_name_index(mid),
|
||||
|entry| entry.name(self).cmp(name),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn binary_search_by_address(&self, target_offset: u32) -> Option<(usize, &SymbolEntry)> {
|
||||
self.binary_search(
|
||||
|table, mid| table.get_symbol_entry_by_address_index(mid),
|
||||
|entry| entry.address_offset.cmp(&target_offset),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn binary_search_closest_by_address(
|
||||
&self,
|
||||
target_offset: u32,
|
||||
) -> Option<(usize, &SymbolEntry)> {
|
||||
self.binary_search(
|
||||
|table, mid| table.get_symbol_entry_by_address_index(mid),
|
||||
|entry| entry.address_offset.cmp(&target_offset),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
fn binary_search<F, C>(
|
||||
&self,
|
||||
get_entry_fn: F,
|
||||
compare_fn: C,
|
||||
find_closest: bool,
|
||||
) -> Option<(usize, &SymbolEntry)>
|
||||
where
|
||||
F: Fn(&Self, usize) -> &SymbolEntry,
|
||||
C: Fn(&SymbolEntry) -> Ordering,
|
||||
{
|
||||
if self.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut left = 0;
|
||||
let mut right = self.symbol_count;
|
||||
let mut best_match: Option<(usize, &SymbolEntry)> = None;
|
||||
|
||||
while left < right {
|
||||
let mid = left + (right - left) / 2;
|
||||
let entry = get_entry_fn(self, mid);
|
||||
|
||||
match compare_fn(entry) {
|
||||
Ordering::Equal => return Some((mid, entry)),
|
||||
Ordering::Less => {
|
||||
if find_closest {
|
||||
best_match = Some((mid, entry));
|
||||
}
|
||||
left = mid + 1;
|
||||
}
|
||||
Ordering::Greater => right = mid,
|
||||
}
|
||||
}
|
||||
|
||||
best_match
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct SymbolEntry {
|
||||
address_offset: u32,
|
||||
symbol_type: u8,
|
||||
section: u8,
|
||||
description: u16,
|
||||
}
|
||||
|
||||
impl SymbolEntry {
|
||||
fn name<'a>(&self, symbol_table: &'a SymbolTable) -> &'a str {
|
||||
unsafe {
|
||||
CStr::from_ptr(self.name_ptr(symbol_table))
|
||||
.to_str()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn name_ptr(&self, symbol_table: &SymbolTable) -> *const c_char {
|
||||
let entry_ptr = self as *const SymbolEntry as *const u8;
|
||||
let data_start = symbol_table.data.as_ptr();
|
||||
let entry_offset = unsafe { entry_ptr.offset_from(data_start) as usize };
|
||||
let name_start = entry_offset + size_of::<SymbolEntry>();
|
||||
|
||||
unsafe { symbol_table.data.as_ptr().add(name_start) as *const c_char }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SymbolRef<'a> {
|
||||
symbol_table: &'a SymbolTable,
|
||||
entry: &'a SymbolEntry,
|
||||
kernel_base: u64,
|
||||
}
|
||||
|
||||
impl<'a> SymbolRef<'a> {
|
||||
pub fn name(&self) -> &'a str {
|
||||
self.entry.name(self.symbol_table)
|
||||
}
|
||||
|
||||
pub fn name_ptr(&self) -> *const c_char {
|
||||
self.entry.name_ptr(self.symbol_table)
|
||||
}
|
||||
|
||||
pub fn address(&self) -> u64 {
|
||||
self.kernel_base + self.entry.address_offset as u64
|
||||
}
|
||||
|
||||
pub fn symbol_type(&self) -> u8 {
|
||||
self.entry.symbol_type
|
||||
}
|
||||
|
||||
pub fn section(&self) -> u8 {
|
||||
self.entry.section
|
||||
}
|
||||
|
||||
pub fn description(&self) -> u16 {
|
||||
self.entry.description
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SymbolsByNameIterator<'a> {
|
||||
symbol_table: &'a SymbolTable,
|
||||
current_index: usize,
|
||||
end_index: usize,
|
||||
kernel_base: u64,
|
||||
}
|
||||
|
||||
impl<'a> SymbolsByNameIterator<'a> {
|
||||
fn empty(symbol_table: &'a SymbolTable) -> Self {
|
||||
Self {
|
||||
symbol_table,
|
||||
current_index: 0,
|
||||
end_index: 0,
|
||||
kernel_base: get_kernel_base(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SymbolsByNameIterator<'a> {
|
||||
type Item = SymbolRef<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.current_index == self.end_index {
|
||||
return None;
|
||||
}
|
||||
|
||||
let entry = self.symbol_table.get_symbol_entry_by_name_index(self.current_index);
|
||||
self.current_index += 1;
|
||||
|
||||
Some(SymbolRef {
|
||||
symbol_table: self.symbol_table,
|
||||
entry,
|
||||
kernel_base: self.kernel_base,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SymbolsMatchingIterator<'a> {
|
||||
symbol_table: &'a SymbolTable,
|
||||
current_index: usize,
|
||||
end_index: usize,
|
||||
pspec: *mut GPatternSpec,
|
||||
kernel_base: u64,
|
||||
}
|
||||
|
||||
impl<'a> SymbolsMatchingIterator<'a> {
|
||||
fn empty(symbol_table: &'a SymbolTable) -> Self {
|
||||
Self {
|
||||
symbol_table,
|
||||
current_index: 0,
|
||||
end_index: 0,
|
||||
pspec: ptr::null_mut(),
|
||||
kernel_base: get_kernel_base(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SymbolsMatchingIterator<'a> {
|
||||
type Item = SymbolRef<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.current_index != self.end_index {
|
||||
let entry = self.symbol_table.get_symbol_entry_by_name_index(self.current_index);
|
||||
self.current_index += 1;
|
||||
|
||||
if unsafe { g_pattern_spec_match_string(self.pspec, entry.name_ptr(self.symbol_table)) } != 0 {
|
||||
return Some(SymbolRef {
|
||||
symbol_table: self.symbol_table,
|
||||
entry,
|
||||
kernel_base: self.kernel_base,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for SymbolsMatchingIterator<'a> {
|
||||
fn drop(&mut self) {
|
||||
if !self.pspec.is_null() {
|
||||
unsafe { g_pattern_spec_free(self.pspec) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SymbolsInRangeIterator<'a> {
|
||||
symbol_table: &'a SymbolTable,
|
||||
current_index: usize,
|
||||
end_index: usize,
|
||||
end_offset: u32,
|
||||
kernel_base: u64,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SymbolsInRangeIterator<'a> {
|
||||
type Item = SymbolRef<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.current_index != self.end_index {
|
||||
let entry = self.symbol_table.get_symbol_entry_by_address_index(self.current_index);
|
||||
self.current_index += 1;
|
||||
|
||||
if entry.address_offset >= self.end_offset {
|
||||
break;
|
||||
}
|
||||
|
||||
return Some(SymbolRef {
|
||||
symbol_table: self.symbol_table,
|
||||
entry,
|
||||
kernel_base: self.kernel_base,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
use core::ffi::c_void;
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
static KERNEL_BASE: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
pub fn get_kernel_base() -> u64 {
|
||||
KERNEL_BASE.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn set_kernel_base(base: u64) {
|
||||
KERNEL_BASE.store(base, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
type ContinuationFn = unsafe extern "C" fn(_parameter: *mut c_void, _wait_result: i32);
|
||||
|
||||
unsafe extern "C" {
|
||||
static _panic: unsafe extern "C" fn(*const u8);
|
||||
static _IOLog: unsafe extern "C" fn(*const u8, ...);
|
||||
static _kalloc: unsafe extern "C" fn(usize) -> *mut u8;
|
||||
static _kfree: unsafe extern "C" fn(*mut u8, usize) -> *mut u8;
|
||||
static _kernel_thread_start:
|
||||
unsafe extern "C" fn(*const (), *mut c_void, *mut *mut c_void) -> isize;
|
||||
static _assert_wait: unsafe extern "C" fn(*const u8, u32) -> i32;
|
||||
static _assert_wait_timeout: unsafe extern "C" fn(*const u8, u32, u32, u32) -> i32;
|
||||
static _thread_block: unsafe extern "C" fn(Option<ContinuationFn>) -> i32;
|
||||
static _thread_wakeup: unsafe extern "C" fn(*const u8) -> i32;
|
||||
static _mach_absolute_time: unsafe extern "C" fn() -> u64;
|
||||
static _absolutetime_to_nanoseconds: unsafe extern "C" fn(u64, *mut u64);
|
||||
static _clock_get_calendar_microtime: unsafe extern "C" fn(*mut u32, *mut u32);
|
||||
static _ml_io_map: unsafe extern "C" fn(u64, u64) -> *mut c_void;
|
||||
static _ml_vtophys: unsafe extern "C" fn(u64) -> u64;
|
||||
static __ZN9IOService11getPlatformEv: unsafe extern "C" fn() -> *mut c_void;
|
||||
static __ZN9IOServiceC2Ev: unsafe extern "C" fn(*mut core::ffi::c_void);
|
||||
static __ZN8OSSymbol17withCStringNoCopyEPKc:
|
||||
unsafe extern "C" fn(*const core::ffi::c_char) -> *const OSSymbol;
|
||||
static __ZN6OSData9withBytesEPKvj:
|
||||
unsafe extern "C" fn(*const core::ffi::c_void, u32) -> *mut OSData;
|
||||
}
|
||||
|
||||
const IO_SERVICE_VTABLE_LENGTH: isize = 168;
|
||||
|
||||
const VT_LOOKUP_IC: isize = IO_SERVICE_VTABLE_LENGTH + 25; // IOPlatformExpert
|
||||
const VT_REGISTER_INT: isize = IO_SERVICE_VTABLE_LENGTH + 0; // IOInterruptController
|
||||
const VT_ENABLE_INT: isize = IO_SERVICE_VTABLE_LENGTH + 3; // IOInterruptController
|
||||
|
||||
pub fn panic(msg: &str) {
|
||||
unsafe { _panic(msg.as_ptr()) };
|
||||
}
|
||||
|
||||
pub fn io_log(msg: &str) {
|
||||
unsafe {
|
||||
_IOLog(msg.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kalloc(size: usize) -> *mut u8 {
|
||||
unsafe { _kalloc(size) }
|
||||
}
|
||||
|
||||
pub fn free(ptr: *mut u8, size: usize) {
|
||||
unsafe {
|
||||
_kfree(ptr, size);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kernel_thread_start(continuation: ContinuationFn, thread_parameter: *mut c_void) -> isize {
|
||||
let mut new_thread: *mut c_void = core::ptr::null_mut();
|
||||
return unsafe {
|
||||
let ptr = crate::pac::ptrauth_sign(continuation as *const u8, 0xd507);
|
||||
_kernel_thread_start(
|
||||
core::mem::transmute(ptr),
|
||||
thread_parameter,
|
||||
&mut new_thread as *mut *mut c_void,
|
||||
)
|
||||
// TODO: Call thread_deallocate()
|
||||
};
|
||||
}
|
||||
|
||||
pub const THREAD_INTERRUPTIBLE: u32 = 1;
|
||||
pub const THREAD_WAITING: i32 = -1;
|
||||
|
||||
pub fn assert_wait(event: *const u8, interruptible: u32) -> i32 {
|
||||
unsafe { _assert_wait(event, interruptible) }
|
||||
}
|
||||
|
||||
pub fn assert_wait_timeout(
|
||||
event: *const u8,
|
||||
interruptible: u32,
|
||||
interval: u32,
|
||||
scale_factor: u32,
|
||||
) -> i32 {
|
||||
unsafe { _assert_wait_timeout(event, interruptible, interval, scale_factor) }
|
||||
}
|
||||
|
||||
pub fn thread_block(continuation: Option<ContinuationFn>) -> i32 {
|
||||
unsafe { _thread_block(continuation) }
|
||||
}
|
||||
|
||||
pub fn thread_wakeup(event: *const u8) -> i32 {
|
||||
unsafe { _thread_wakeup(event) }
|
||||
}
|
||||
|
||||
pub fn mach_absolute_time() -> u64 {
|
||||
unsafe { _mach_absolute_time() }
|
||||
}
|
||||
|
||||
pub fn absolutetime_to_nanoseconds(abstime: u64) -> u64 {
|
||||
let mut nanoseconds: u64 = 0;
|
||||
unsafe {
|
||||
_absolutetime_to_nanoseconds(abstime, &mut nanoseconds);
|
||||
}
|
||||
nanoseconds
|
||||
}
|
||||
|
||||
pub fn clock_get_calendar_microtime() -> (u32, u32) {
|
||||
let mut secs: u32 = 0;
|
||||
let mut microsecs: u32 = 0;
|
||||
unsafe {
|
||||
_clock_get_calendar_microtime(&mut secs, &mut microsecs);
|
||||
}
|
||||
(secs, microsecs)
|
||||
}
|
||||
|
||||
pub fn ml_io_map(phys_addr: u64, size: u64) -> *mut c_void {
|
||||
unsafe { _ml_io_map(phys_addr, size) }
|
||||
}
|
||||
|
||||
pub fn ml_vtophys(vaddr: u64) -> u64 {
|
||||
unsafe { _ml_vtophys(vaddr) }
|
||||
}
|
||||
|
||||
pub type IOInterruptHandler =
|
||||
extern "C" fn(target: *mut c_void, refcon: *mut c_void, nub: *mut c_void, source: i32);
|
||||
|
||||
pub fn install_interrupt_handler(
|
||||
irq: u32,
|
||||
target: *mut c_void,
|
||||
handler: IOInterruptHandler,
|
||||
refcon: *mut c_void,
|
||||
) -> i32 {
|
||||
let pe = unsafe { __ZN9IOService11getPlatformEv() };
|
||||
|
||||
let name =
|
||||
unsafe { __ZN8OSSymbol17withCStringNoCopyEPKc(c"IOInterruptController0000001A".as_ptr()) };
|
||||
|
||||
let lookup: extern "C" fn(*mut IOPlatformExpert, *mut OSSymbol) -> *mut IOInterruptController =
|
||||
vf(pe as _, VT_LOOKUP_IC);
|
||||
let ic = lookup(pe as *mut IOPlatformExpert, name as *mut OSSymbol);
|
||||
if ic.is_null() {
|
||||
panic!("Failed to lookup IOInterruptController");
|
||||
}
|
||||
|
||||
let nub = kalloc(0x88);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(nub, 0, 0x88);
|
||||
__ZN9IOServiceC2Ev(nub as *mut core::ffi::c_void);
|
||||
}
|
||||
|
||||
type IOServiceInitFn = unsafe extern "C" fn(*mut c_void, *mut c_void) -> bool;
|
||||
let init_fn: IOServiceInitFn = vf(nub as *mut c_void, 21);
|
||||
unsafe { init_fn(nub as *mut c_void, core::ptr::null_mut()) };
|
||||
|
||||
let interrupt_sources =
|
||||
kalloc(core::mem::size_of::<IOInterruptSource>()) as *mut IOInterruptSource;
|
||||
|
||||
let source_bytes = irq.to_ne_bytes();
|
||||
let vector_data =
|
||||
unsafe { __ZN6OSData9withBytesEPKvj(source_bytes.as_ptr() as *const core::ffi::c_void, 4) };
|
||||
|
||||
unsafe {
|
||||
(*interrupt_sources).interrupt_controller = ic;
|
||||
(*interrupt_sources).vector_data = vector_data;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let interrupt_sources_ptr = (nub as *mut u8).offset(0x80) as *mut *mut IOInterruptSource;
|
||||
*interrupt_sources_ptr = interrupt_sources;
|
||||
}
|
||||
|
||||
let reg: extern "C" fn(
|
||||
*mut _,
|
||||
*mut c_void,
|
||||
i32,
|
||||
*mut c_void,
|
||||
IOInterruptHandler,
|
||||
*mut c_void,
|
||||
) -> i32 = vf(ic as _, VT_REGISTER_INT);
|
||||
|
||||
let signed_handler = unsafe {
|
||||
let handler_ptr = crate::pac::ptrauth_sign(handler as *const u8, 0xd36);
|
||||
core::mem::transmute::<*const u8, IOInterruptHandler>(handler_ptr)
|
||||
};
|
||||
|
||||
let kr = reg(ic, nub as *mut c_void, 0, target, signed_handler, refcon);
|
||||
if kr != 0 {
|
||||
return kr;
|
||||
}
|
||||
|
||||
let en: extern "C" fn(*mut _, *mut c_void, i32) -> i32 = vf(ic as _, VT_ENABLE_INT);
|
||||
en(ic, nub as *mut c_void, 0)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct IOPlatformExpert {
|
||||
_p: [u8; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct IOInterruptController {
|
||||
_p: [u8; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct OSSymbol {
|
||||
_p: [u8; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct OSData {
|
||||
_p: [u8; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct IOInterruptSource {
|
||||
interrupt_controller: *mut IOInterruptController,
|
||||
vector_data: *mut OSData,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn vf<T>(obj: *mut c_void, slot: isize) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
let vtable_ptr = unsafe { *(obj as *const *const usize) };
|
||||
let vtable = unsafe { crate::pac::ptrauth_strip_data(vtable_ptr as *const u8) as *const usize };
|
||||
let entry = unsafe { *vtable.offset(slot) };
|
||||
let entry_ptr = unsafe { crate::pac::ptrauth_strip_data(entry as *const u8) };
|
||||
unsafe { core::mem::transmute_copy::<*const u8, T>(&entry_ptr) }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
QEMUAppleSilicon/build/qemu-system-aarch64 \
|
||||
-M t8030,memory-backend=ram,trustcache=iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/Firmware/038-44135-124.dmg.trustcache,ticket=root_ticket.der,sep-fw=sep-firmware.n104.RELEASE.new.img4,sep-rom=AppleSEPROM-Cebu-B1,kaslr-off=true,usb-conn-type=unix,usb-conn-addr=/Users/oleavr/src/frida-barebone-ios/usb.socket \
|
||||
-object memory-backend-file,id=ram,size=4G,mem-path="/Volumes/RAM Disk/ios-dram",share=on \
|
||||
-kernel iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/kernelcache.research.iphone12b \
|
||||
-dtb iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/Firmware/all_flash/DeviceTree.n104ap.im4p \
|
||||
-append "tlto_us=-1 mtxspin=-1 agm-genuine=1 agm-authentic=1 agm-trusted=1 serial=3 launchd_unsecure_cache=1 wdt=-1" \
|
||||
-smp 7 \
|
||||
-drive file=sep_nvram,if=pflash,format=qcow2 \
|
||||
-drive file=sep_ssc,if=pflash,format=qcow2 \
|
||||
-drive file=nvme.1,format=qcow2,if=none,id=drive.1 -device nvme-ns,drive=drive.1,bus=nvme-bus.0,nsid=1,nstype=1,logical_block_size=4096,physical_block_size=4096 \
|
||||
-drive file=nvme.2,format=qcow2,if=none,id=drive.2 -device nvme-ns,drive=drive.2,bus=nvme-bus.0,nsid=2,nstype=2,logical_block_size=4096,physical_block_size=4096 \
|
||||
-drive file=nvme.3,format=qcow2,if=none,id=drive.3 -device nvme-ns,drive=drive.3,bus=nvme-bus.0,nsid=3,nstype=3,logical_block_size=4096,physical_block_size=4096 \
|
||||
-drive file=nvme.4,format=qcow2,if=none,id=drive.4 -device nvme-ns,drive=drive.4,bus=nvme-bus.0,nsid=4,nstype=4,logical_block_size=4096,physical_block_size=4096 \
|
||||
-drive file=nvram,if=none,format=qcow2,id=nvram -device apple-nvram,drive=nvram,bus=nvme-bus.0,nsid=5,nstype=5,id=nvram,logical_block_size=4096,physical_block_size=4096 \
|
||||
-drive file=nvme.6,format=qcow2,if=none,id=drive.6 -device nvme-ns,drive=drive.6,bus=nvme-bus.0,nsid=6,nstype=6,logical_block_size=4096,physical_block_size=4096 \
|
||||
-drive file=nvme.7,format=qcow2,if=none,id=drive.7 -device nvme-ns,drive=drive.7,bus=nvme-bus.0,nsid=7,nstype=8,logical_block_size=4096,physical_block_size=4096 \
|
||||
-initrd iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/038-44135-124.dmg \
|
||||
-serial mon:stdio \
|
||||
-monitor tcp:127.0.0.1:9001,server,nowait \
|
||||
-gdb tcp::9000
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,40 @@
|
||||
<dict ID="0">
|
||||
<key>IOPlatformInterruptController</key>
|
||||
<string ID="1">IOCPUInterruptController is not serializable</string>
|
||||
|
||||
<key>IOInterruptController0000001A</key>
|
||||
<string ID="2">AppleInterruptController is not serializable</string>
|
||||
|
||||
<key>IOInterruptController00000048</key>
|
||||
<string ID="3">AppleS5L8960XGPIOIC is not serializable</string>
|
||||
|
||||
<key>IOInterruptController00000047</key>
|
||||
<string ID="4">AppleS5L8960XGPIOIC is not serializable</string>
|
||||
|
||||
<key>IOInterruptController00000023</key>
|
||||
<string ID="5">AppleS5L8960XGPIOIC is not serializable</string>
|
||||
|
||||
<key>IOInterruptController00000049</key>
|
||||
<string ID="6">AppleS5L8960XGPIOIC is not serializable</string>
|
||||
|
||||
<key>IOInterruptController0000008B</key>
|
||||
<string ID="7">AppleT8015SPMIController is not serializable</string>
|
||||
|
||||
<key>IOInterruptController00000084</key>
|
||||
<string ID="8">AppleT8015SPMIController is not serializable</string>
|
||||
|
||||
<key>ApplePCIEMSIController-apcie</key>
|
||||
<string ID="9">ApplePCIEMSIController is not serializable</string>
|
||||
|
||||
<key>AppleEmbeddedPCIEPort2</key>
|
||||
<string ID="10">AppleT8030PCIePort is not serializable</string>
|
||||
|
||||
<key>AppleEmbeddedPCIEPort3</key>
|
||||
<string ID="11">AppleT8030PCIePort is not serializable</string>
|
||||
|
||||
<key>IOInterruptController00000087</key>
|
||||
<string ID="12">AppleT8015SPMIController is not serializable</string>
|
||||
|
||||
<key>IOInterruptController0000005F</key>
|
||||
<string ID="13">AppleSMCPMU is not serializable</string>
|
||||
</dict>
|
||||
@@ -0,0 +1,33 @@
|
||||
const osSerialize_withCapacity = new NativeFunction(ptr('0xfffffff007fbf074'), 'pointer', ['uint']);
|
||||
|
||||
const UNPAC_MASK = ptr('0xfffffff000000000');
|
||||
|
||||
const serializer = osSerialize_withCapacity(4096);
|
||||
|
||||
const gIOInterruptControllers = ptr('0xfffffff009a3f090').readPointer();
|
||||
|
||||
const serialize = new NativeFunction(implFor(6, vtableOf(gIOInterruptControllers)), 'uint' /* 'bool' */, ['pointer', 'pointer']);
|
||||
console.log('serialize() is at:', serialize.handle);
|
||||
const result = serialize(gIOInterruptControllers, serializer);
|
||||
console.log('result returned:', result);
|
||||
|
||||
const getText = new NativeFunction(implFor(15, vtableOf(serializer)), 'pointer', ['pointer']);
|
||||
const text = getText(serializer);
|
||||
console.log('Got text:', text.readUtf8String(text));
|
||||
|
||||
const release = new NativeFunction(implFor(5, vtableOf(serializer)), 'void', ['pointer']);
|
||||
release(serializer);
|
||||
|
||||
Object.assign(globalThis, { osSerialize_withCapacity });
|
||||
|
||||
function implFor(n, vtable) {
|
||||
return unpac(vtable.add(n * Process.pointerSize).readPointer());
|
||||
}
|
||||
|
||||
function vtableOf(instance) {
|
||||
return unpac(instance.readPointer());
|
||||
}
|
||||
|
||||
function unpac(p) {
|
||||
return p.or(UNPAC_MASK);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
import r2pipe
|
||||
|
||||
def scan_files_for_pattern(directory, hex_pattern, file_extension=None):
|
||||
search_command = "/xj " + hex_pattern.strip()
|
||||
|
||||
for root, _, files in os.walk(directory):
|
||||
for filename in files:
|
||||
if file_extension and not filename.endswith(file_extension):
|
||||
continue
|
||||
|
||||
file_path = os.path.join(root, filename)
|
||||
try:
|
||||
r2 = r2pipe.open(file_path, flags=["-n"])
|
||||
r2.cmd("e io.cache=true")
|
||||
|
||||
result = r2.cmdj(search_command)
|
||||
|
||||
if result:
|
||||
print(f"Found in {file_path}:")
|
||||
for match in result:
|
||||
print(f" - Offset: 0x{match['offset']:x}")
|
||||
|
||||
r2.quit()
|
||||
except Exception as e:
|
||||
print(f" Error processing {file_path}: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Scan dump files for a hex pattern using r2pipe.")
|
||||
parser.add_argument("directory", help="Directory containing dump files")
|
||||
parser.add_argument("pattern", help="Hex pattern to search (e.g. 'ff ff ff ff')")
|
||||
parser.add_argument("--ext", help="Only scan files with this extension", default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
scan_files_for_pattern(args.directory, args.pattern, args.ext)
|
||||
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Symbolicate panic backtrace from kernel log")
|
||||
parser.add_argument("--log-file", type=argparse.FileType("r"), required=True,
|
||||
help="Path to the kernel log file")
|
||||
parser.add_argument("--elf-file", type=str, required=True,
|
||||
help="Path to the ELF file for symbolication")
|
||||
parser.add_argument("--base-address", type=lambda x: int(x, 16), required=True,
|
||||
help="Base address where the ELF was loaded (e.g., 0xffffffe8195f0000)")
|
||||
parser.add_argument("--verbose", "-v", action="store_true",
|
||||
help="Show all backtrace frames, not just symbolicated ones")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
log_content = args.log_file.read()
|
||||
args.log_file.close()
|
||||
|
||||
frames = find_last_panic(log_content)
|
||||
if not frames:
|
||||
print("No panic found in the log file", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Found last panic with {len(frames)} backtrace frames")
|
||||
print(f"ELF base address: 0x{args.base_address:x}")
|
||||
print(f"ELF file: {args.elf_file}")
|
||||
print()
|
||||
|
||||
symbolicated_count = 0
|
||||
|
||||
for i, (lr_str, fp_str) in enumerate(frames):
|
||||
lr_addr = int(lr_str, 16)
|
||||
#fp_addr = int(fp_str, 16)
|
||||
|
||||
if lr_addr >= args.base_address:
|
||||
function, location = symbolicate_address(args.elf_file, lr_addr, args.base_address)
|
||||
offset = lr_addr - args.base_address
|
||||
|
||||
print(f"Frame {i:2d}: lr: {lr_str} (offset: 0x{offset:x}) fp: {fp_str}")
|
||||
|
||||
# Color the function name based on resolution status
|
||||
if function == "<unknown>":
|
||||
colored_function = Colors.bold_yellow(function)
|
||||
elif function == "<error>":
|
||||
colored_function = Colors.bold_red(function)
|
||||
else:
|
||||
colored_function = Colors.bold_green(function)
|
||||
|
||||
print(f" -> {colored_function}")
|
||||
print(f" {location}")
|
||||
print()
|
||||
symbolicated_count += 1
|
||||
elif args.verbose:
|
||||
print(f"Frame {i:2d}: lr: {lr_str} fp: {fp_str} (outside ELF range)")
|
||||
|
||||
if symbolicated_count == 0:
|
||||
print("No addresses found within the ELF address range")
|
||||
if not args.verbose:
|
||||
print("Use --verbose to see all frames")
|
||||
else:
|
||||
print(f"Symbolicated {symbolicated_count} frame(s)")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def find_last_panic(log_content: str):
|
||||
panic_pattern = re.compile(
|
||||
r"panic\(.*?\n.*?Panicked thread:.*?\n((?:\s*lr:\s*0x[0-9a-fA-F]+\s*fp:\s*0x[0-9a-fA-F]+\n)*)",
|
||||
re.DOTALL
|
||||
)
|
||||
|
||||
panics = list(panic_pattern.finditer(log_content))
|
||||
|
||||
if not panics:
|
||||
return None
|
||||
|
||||
last_panic = panics[-1]
|
||||
backtrace_section = last_panic.group(1)
|
||||
|
||||
lr_fp_pattern = re.compile(r"lr:\s*(0x[0-9a-fA-F]+)\s*fp:\s*(0x[0-9a-fA-F]+)")
|
||||
frames = lr_fp_pattern.findall(backtrace_section)
|
||||
|
||||
return frames
|
||||
|
||||
|
||||
def symbolicate_address(elf_path: str, address: int, base_address: int):
|
||||
try:
|
||||
offset = address - base_address
|
||||
|
||||
result = subprocess.run([
|
||||
"aarch64-none-elf-addr2line", "-e", elf_path, "-f", "-C", f"0x{offset:x}"
|
||||
], capture_output=True, text=True, check=True)
|
||||
|
||||
lines = result.stdout.strip().split("\n")
|
||||
if len(lines) >= 2:
|
||||
function = lines[0]
|
||||
location = lines[1]
|
||||
|
||||
if function == "??":
|
||||
function = "<unknown>"
|
||||
if location == "??:0" or location == "??:?":
|
||||
location = "<unknown>"
|
||||
|
||||
return function, location
|
||||
else:
|
||||
return "<unknown>", "<unknown>"
|
||||
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
return "<error>", "<error>"
|
||||
|
||||
|
||||
class Colors:
|
||||
BOLD = "\033[1m"
|
||||
GREEN = "\033[32m"
|
||||
YELLOW = "\033[33m"
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
|
||||
@staticmethod
|
||||
def bold_green(text: str):
|
||||
return f"{Colors.BOLD}{Colors.GREEN}{text}{Colors.RESET}"
|
||||
|
||||
@staticmethod
|
||||
def bold_yellow(text: str):
|
||||
return f"{Colors.BOLD}{Colors.YELLOW}{text}{Colors.RESET}"
|
||||
|
||||
@staticmethod
|
||||
def bold_red(text: str):
|
||||
return f"{Colors.BOLD}{Colors.RED}{text}{Colors.RESET}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+104
-24
@@ -14,59 +14,83 @@ namespace Frida.Barebone {
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract size_t size {
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract async void deallocate (Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
|
||||
public sealed class SimpleAllocator : Object, Allocator {
|
||||
public sealed class NullAllocator : Object, Allocator {
|
||||
public size_t page_size {
|
||||
get { return _page_size; }
|
||||
get {
|
||||
return _page_size;
|
||||
}
|
||||
}
|
||||
|
||||
private size_t _page_size;
|
||||
|
||||
public NullAllocator (size_t page_size) {
|
||||
this._page_size = page_size;
|
||||
}
|
||||
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("To enable this feature, specify an allocator in your FRIDA_BAREBONE_CONFIG");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PhysicalAllocator : Object, Allocator {
|
||||
public size_t page_size {
|
||||
get {
|
||||
return _page_size;
|
||||
}
|
||||
}
|
||||
|
||||
private Machine machine;
|
||||
private size_t _page_size;
|
||||
private uint64 base_pa;
|
||||
|
||||
private uint64 cursor;
|
||||
|
||||
public SimpleAllocator (Machine machine, size_t page_size, uint64 base_pa) {
|
||||
public PhysicalAllocator (Machine machine, size_t page_size, PhysicalAllocatorConfig config) {
|
||||
this.machine = machine;
|
||||
this._page_size = page_size;
|
||||
this.base_pa = base_pa;
|
||||
this.cursor = base_pa;
|
||||
this.cursor = config.physical_base.address;
|
||||
}
|
||||
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Error, IOError {
|
||||
if (base_pa == 0) {
|
||||
uint64 example_base;
|
||||
if ("corellium" in machine.gdb.features)
|
||||
example_base = 0x0800000000 + ((2048 - 3) * 1024 * 1024);
|
||||
else
|
||||
example_base = 0x0040000000 + (128 * 1024 * 1024);
|
||||
throw new Error.NOT_SUPPORTED ("To enable this feature, set FRIDA_BAREBONE_HEAP_BASE to the physical " +
|
||||
"base address to use, e.g. 0x%" + uint64.FORMAT_MODIFIER + "x", example_base);
|
||||
}
|
||||
|
||||
uint64 address_pa = cursor;
|
||||
|
||||
size_t vm_size = round_size_up (size, page_size);
|
||||
size_t vm_size = round_size_up (size, _page_size);
|
||||
cursor += vm_size;
|
||||
|
||||
uint num_pages = (uint) (vm_size / page_size);
|
||||
uint num_pages = (uint) (vm_size / _page_size);
|
||||
|
||||
Allocation page_allocation = yield machine.allocate_pages (address_pa, num_pages, cancellable);
|
||||
var physical_addresses = new Gee.ArrayList<uint64?> ();
|
||||
for (uint i = 0; i != num_pages; i++)
|
||||
physical_addresses.add (address_pa + (i * _page_size));
|
||||
|
||||
return new SimpleAllocation (page_allocation);
|
||||
Allocation page_allocation = yield machine.allocate_pages (physical_addresses, cancellable);
|
||||
|
||||
return new PhysicalAllocation (page_allocation);
|
||||
}
|
||||
|
||||
private class SimpleAllocation : Object, Allocation {
|
||||
private class PhysicalAllocation : Object, Allocation {
|
||||
public uint64 virtual_address {
|
||||
get { return page_allocation.virtual_address; }
|
||||
get {
|
||||
return page_allocation.virtual_address;
|
||||
}
|
||||
}
|
||||
|
||||
public size_t size {
|
||||
get {
|
||||
return page_allocation.size;
|
||||
}
|
||||
}
|
||||
|
||||
private Allocation page_allocation;
|
||||
|
||||
public SimpleAllocation (Allocation page_allocation) {
|
||||
this.page_allocation = page_allocation;
|
||||
public PhysicalAllocation (Allocation allocation) {
|
||||
page_allocation = allocation;
|
||||
}
|
||||
|
||||
public async void deallocate (Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -75,4 +99,60 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TargetFunctionsAllocator : Object, Allocator {
|
||||
public size_t page_size {
|
||||
get {
|
||||
return _page_size;
|
||||
}
|
||||
}
|
||||
|
||||
private Machine machine;
|
||||
private size_t _page_size;
|
||||
private TargetFunctionsAllocatorConfig config;
|
||||
|
||||
public TargetFunctionsAllocator (Machine machine, size_t page_size, TargetFunctionsAllocatorConfig config) {
|
||||
this.machine = machine;
|
||||
this._page_size = page_size;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Error, IOError {
|
||||
uint64 address = yield machine.invoke (config.alloc_function.address, { size }, cancellable);
|
||||
|
||||
// TODO: Handle alignment.
|
||||
|
||||
return new TargetAllocation (address, size, machine, config);
|
||||
}
|
||||
|
||||
private class TargetAllocation : Object, Allocation {
|
||||
public uint64 virtual_address {
|
||||
get {
|
||||
return _virtual_address;
|
||||
}
|
||||
}
|
||||
|
||||
public size_t size {
|
||||
get {
|
||||
return _size;
|
||||
}
|
||||
}
|
||||
|
||||
private uint64 _virtual_address;
|
||||
public size_t _size;
|
||||
private Machine machine;
|
||||
private TargetFunctionsAllocatorConfig config;
|
||||
|
||||
public TargetAllocation (uint64 address, size_t size, Machine m, TargetFunctionsAllocatorConfig c) {
|
||||
_virtual_address = address;
|
||||
_size = size;
|
||||
machine = m;
|
||||
config = c;
|
||||
}
|
||||
|
||||
public async void deallocate (Cancellable? cancellable) throws Error, IOError {
|
||||
yield machine.invoke (config.free_function.address, { _virtual_address, size }, cancellable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,16 +26,25 @@ namespace Frida.Barebone {
|
||||
return 4096;
|
||||
}
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern, uint max_matches,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
@@ -45,8 +54,7 @@ namespace Frida.Barebone {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, uint64 landing_zone, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ namespace Frida.Barebone {
|
||||
private const uint64 INT6_MASK = 0x3fULL;
|
||||
private const uint64 INT48_MASK = 0xffffffffffffULL;
|
||||
|
||||
private const uint64 TABLE_PXN_BIT = 1ULL << 59;
|
||||
|
||||
private const uint64 UXN_BIT = 1ULL << 54;
|
||||
private const uint64 PXN_BIT = 1ULL << 53;
|
||||
private const uint64 AP1_BIT = 1ULL << 7;
|
||||
private const uint64 AP0_BIT = 1ULL << 6;
|
||||
|
||||
public Arm64Machine (GDB.Client gdb) {
|
||||
Object (gdb: gdb);
|
||||
}
|
||||
@@ -35,6 +42,17 @@ namespace Frida.Barebone {
|
||||
return p.granule;
|
||||
}
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
GDB.Exception? exception = gdb.exception;
|
||||
if (exception == null)
|
||||
throw new Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
GDB.Thread thread = exception.thread;
|
||||
|
||||
var cpsr = yield thread.read_register ("cpsr", cancellable);
|
||||
|
||||
return (uint) ((cpsr >> 2) & 3);
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
Gee.List<RangeDetails> ranges = ("corellium" in gdb.features)
|
||||
@@ -55,7 +73,7 @@ namespace Frida.Barebone {
|
||||
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
try {
|
||||
yield collect_ranges_in_table (p.tt1, p.first_level, p.upper_bits, p.granule, result, cancellable);
|
||||
yield collect_ranges_in_table (p.tt1, p.first_level, p.upper_bits, p, result, cancellable);
|
||||
} finally {
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
}
|
||||
@@ -63,30 +81,30 @@ namespace Frida.Barebone {
|
||||
return result;
|
||||
}
|
||||
|
||||
private async void collect_ranges_in_table (uint64 table_address, uint level, uint64 upper_bits, Granule granule,
|
||||
private async void collect_ranges_in_table (uint64 table_address, uint level, uint64 upper_bits, MMUParameters p,
|
||||
Gee.List<RangeDetails> ranges, Cancellable? cancellable) throws Error, IOError {
|
||||
uint max_entries = compute_max_entries (level, granule);
|
||||
uint max_entries = compute_max_entries (level, p);
|
||||
Buffer entries = yield gdb.read_buffer (table_address, max_entries * Descriptor.SIZE, cancellable);
|
||||
uint shift = address_shift_at_level (level, granule);
|
||||
uint shift = address_shift_at_level (level, p.granule);
|
||||
for (uint i = 0; i != max_entries; i++) {
|
||||
uint64 raw_descriptor = entries.read_uint64 (i * Descriptor.SIZE);
|
||||
|
||||
Descriptor desc = Descriptor.parse (raw_descriptor, level, granule);
|
||||
Descriptor desc = Descriptor.parse (raw_descriptor, level, p.granule);
|
||||
if (desc.kind == INVALID)
|
||||
continue;
|
||||
|
||||
uint64 address = upper_bits | ((uint64) i << shift);
|
||||
|
||||
if (desc.kind == BLOCK) {
|
||||
size_t size = 1 << num_block_bits_at_level (level, granule);
|
||||
Gum.PageProtection prot = protection_from_flags (desc.flags);
|
||||
size_t size = 1 << num_block_bits_at_level (level, p.granule);
|
||||
Gum.PageProtection prot = protection_from_flags (desc.flags, p);
|
||||
|
||||
ranges.add (new RangeDetails (address, desc.target_address, size, prot, MappingType.UNKNOWN));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
yield collect_ranges_in_table (desc.target_address, level + 1, address, granule, ranges, cancellable);
|
||||
yield collect_ranges_in_table (desc.target_address, level + 1, address, p, ranges, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,33 +163,35 @@ namespace Frida.Barebone {
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
MMUParameters p = yield MMUParameters.load (gdb, cancellable);
|
||||
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
try {
|
||||
Allocation? allocation = yield maybe_insert_descriptor_in_table (physical_address, num_pages, p.tt1,
|
||||
p.first_level, p.upper_bits, p.granule, cancellable);
|
||||
Allocation? allocation = yield maybe_insert_descriptor_in_table (physical_addresses, p.tt1, p.first_level,
|
||||
p.upper_bits, p, cancellable);
|
||||
if (allocation == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to insert page table mapping; please file a bug");
|
||||
|
||||
return allocation;
|
||||
} finally {
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
}
|
||||
}
|
||||
|
||||
private async Allocation? maybe_insert_descriptor_in_table (uint64 physical_address, uint num_pages,
|
||||
uint64 table_address, uint level, uint64 upper_bits, Granule granule, Cancellable? cancellable)
|
||||
private async Allocation? maybe_insert_descriptor_in_table (Gee.List<uint64?> physical_addresses,
|
||||
uint64 table_address, uint level, uint64 upper_bits, MMUParameters p, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
uint max_entries = compute_max_entries (level, granule);
|
||||
uint shift = address_shift_at_level (level, granule);
|
||||
uint max_entries = compute_max_entries (level, p);
|
||||
uint shift = address_shift_at_level (level, p.granule);
|
||||
|
||||
uint64 first_available_va = 0;
|
||||
uint64 first_available_slot = 0;
|
||||
uint num_available_slots = 0;
|
||||
uint chunk_max_size = (level < 3) ? 4 : 64;
|
||||
uint chunk_offset = 0;
|
||||
uint num_pages = physical_addresses.size;
|
||||
while (chunk_offset != max_entries && num_available_slots != num_pages) {
|
||||
uint64 chunk_base_address = table_address + (chunk_offset * Descriptor.SIZE);
|
||||
uint chunk_size = uint.min (chunk_max_size, max_entries - chunk_offset);
|
||||
@@ -184,13 +204,15 @@ namespace Frida.Barebone {
|
||||
uint table_index = chunk_offset + i;
|
||||
uint64 address = upper_bits | ((uint64) table_index << shift);
|
||||
|
||||
Descriptor desc = Descriptor.parse (raw_descriptor, level, granule);
|
||||
Descriptor desc = Descriptor.parse (raw_descriptor, level, p.granule);
|
||||
|
||||
if (level < 3) {
|
||||
if (desc.kind != TABLE)
|
||||
continue;
|
||||
Allocation? allocation = yield maybe_insert_descriptor_in_table (physical_address,
|
||||
num_pages, desc.target_address, level + 1, address, granule, cancellable);
|
||||
if ((desc.flags & DescriptorFlags.PXNTABLE) != 0)
|
||||
continue;
|
||||
Allocation? allocation = yield maybe_insert_descriptor_in_table (physical_addresses,
|
||||
desc.target_address, level + 1, address, p, cancellable);
|
||||
if (allocation != null)
|
||||
return allocation;
|
||||
continue;
|
||||
@@ -215,26 +237,205 @@ namespace Frida.Barebone {
|
||||
if (num_available_slots != num_pages)
|
||||
return null;
|
||||
|
||||
Gum.PageProtection page_prot = Gum.PageProtection.READ | Gum.PageProtection.WRITE;
|
||||
if (!p.sprr_enabled)
|
||||
page_prot |= Gum.PageProtection.EXECUTE;
|
||||
|
||||
var builder = gdb.make_buffer_builder ();
|
||||
for (uint i = 0; i != num_available_slots; i++) {
|
||||
uint64 cur_physical_address = physical_address + (i * granule);
|
||||
uint64 new_descriptor = cur_physical_address | 0x40000000000403ULL;
|
||||
uint64 base_descriptor = physical_addresses[(int) i] | 0x403ULL;
|
||||
uint64 new_descriptor = apply_protection_bits (base_descriptor, page_prot, p);
|
||||
builder.append_uint64 (new_descriptor);
|
||||
}
|
||||
Bytes new_descriptors = builder.build ();
|
||||
Bytes old_descriptors = yield gdb.read_byte_array (first_available_slot, new_descriptors.get_size (), cancellable);
|
||||
yield gdb.write_byte_array (first_available_slot, new_descriptors, cancellable);
|
||||
|
||||
return new DescriptorAllocation (first_available_va, first_available_slot, old_descriptors, gdb);
|
||||
size_t size = num_pages * p.granule;
|
||||
return new DescriptorAllocation (first_available_va, size, first_available_slot, old_descriptors, gdb);
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
MMUParameters p = yield MMUParameters.load (gdb, cancellable);
|
||||
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
try {
|
||||
uint64 page_mask = p.granule - 1;
|
||||
uint64 aligned_va = virtual_address & ~page_mask;
|
||||
uint64 aligned_end = (virtual_address + size + page_mask) & ~page_mask;
|
||||
uint num_pages = (uint) ((aligned_end - aligned_va) / p.granule);
|
||||
|
||||
yield perform_protect_pages (aligned_va, num_pages, prot, p, cancellable);
|
||||
} finally {
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
}
|
||||
}
|
||||
|
||||
private async void perform_protect_pages (uint64 start_va, uint num_pages, Gum.PageProtection prot, MMUParameters p,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var table_cache = new TableWalkCache ();
|
||||
uint pages_processed = 0;
|
||||
|
||||
var processed_tables = new Gee.HashSet<uint64?> (Numeric.uint64_hash, Numeric.uint64_equal);
|
||||
bool need_exec = (prot & Gum.PageProtection.EXECUTE) != 0;
|
||||
|
||||
while (pages_processed != num_pages) {
|
||||
uint64 current_va = start_va + (uint64) pages_processed * p.granule;
|
||||
|
||||
uint64 table_pa = yield find_level3_table (current_va, p, table_cache, cancellable);
|
||||
|
||||
if (need_exec && !processed_tables.contains (table_pa)) {
|
||||
yield clear_pxn_table_bits_for_va (current_va, p, table_cache, cancellable);
|
||||
processed_tables.add (table_pa);
|
||||
}
|
||||
|
||||
uint level3_shift = address_shift_at_level (3, p.granule);
|
||||
uint64 level3_mask = (1ULL << level3_shift) - 1;
|
||||
uint64 table_base_va = current_va & ~level3_mask;
|
||||
uint64 table_end_va = table_base_va + (1ULL << level3_shift);
|
||||
uint64 remaining_va = start_va + num_pages * p.granule;
|
||||
uint64 batch_end_va = uint64.min (remaining_va, table_end_va);
|
||||
uint batch_size = (uint) ((batch_end_va - current_va) / p.granule);
|
||||
|
||||
yield protect_pages_in_table (current_va, batch_size, prot, table_pa, p, cancellable);
|
||||
|
||||
pages_processed += batch_size;
|
||||
}
|
||||
}
|
||||
|
||||
private async void protect_pages_in_table (uint64 current_va, uint batch_size, Gum.PageProtection prot,
|
||||
uint64 table_pa, MMUParameters p, Cancellable? cancellable) throws Error, IOError {
|
||||
uint index_bits = num_address_bits_at_level (3, p);
|
||||
uint index_shift = inpage_bits_for_granule (p.granule);
|
||||
uint index_mask = (1U << index_bits) - 1;
|
||||
|
||||
uint64 first_index = (current_va >> index_shift) & index_mask;
|
||||
uint64 first_slot_pa = table_pa + (first_index * Descriptor.SIZE);
|
||||
|
||||
Buffer descriptors = yield gdb.read_buffer (first_slot_pa, batch_size * Descriptor.SIZE, cancellable);
|
||||
|
||||
bool any_changed = false;
|
||||
for (uint i = 0; i != batch_size; i++) {
|
||||
uint64 raw_desc = descriptors.read_uint64 (i * Descriptor.SIZE);
|
||||
uint64 new_desc = apply_protection_bits (raw_desc, prot, p);
|
||||
|
||||
if (new_desc != raw_desc) {
|
||||
descriptors.write_uint64 (i * Descriptor.SIZE, new_desc);
|
||||
any_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (any_changed)
|
||||
yield gdb.write_byte_array (first_slot_pa, descriptors.bytes, cancellable);
|
||||
}
|
||||
|
||||
private async uint64 find_level3_table (uint64 va, MMUParameters p, TableWalkCache cache, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
TableWalkResult? cached = cache.lookup (va, p);
|
||||
if (cached != null)
|
||||
return yield walk_to_level3 (va, cached.table_pa, cached.level, p, cache, cancellable);
|
||||
|
||||
return yield walk_to_level3 (va, p.tt1, p.first_level, p, cache, cancellable);
|
||||
}
|
||||
|
||||
private async uint64 walk_to_level3 (uint64 va, uint64 start_table_pa, uint start_level, MMUParameters p,
|
||||
TableWalkCache cache, Cancellable? cancellable) throws Error, IOError {
|
||||
uint64 table_pa = start_table_pa;
|
||||
uint level = start_level;
|
||||
|
||||
while (level != 3) {
|
||||
cache.store (va, level, table_pa, p);
|
||||
|
||||
uint shift = address_shift_at_level (level, p.granule);
|
||||
uint entries = compute_max_entries (level, p);
|
||||
uint index = (uint) ((va >> shift) & (entries - 1));
|
||||
uint64 slot_pa = table_pa + ((uint64) index * Descriptor.SIZE);
|
||||
|
||||
Buffer d_buf = yield gdb.read_buffer (slot_pa, Descriptor.SIZE, cancellable);
|
||||
uint64 raw = d_buf.read_uint64 (0);
|
||||
Descriptor desc = Descriptor.parse (raw, level, p.granule);
|
||||
|
||||
if (desc.kind != TABLE)
|
||||
throw new Error.NOT_SUPPORTED ("Walk failed at level %u", level);
|
||||
|
||||
table_pa = desc.target_address;
|
||||
level++;
|
||||
}
|
||||
|
||||
return table_pa;
|
||||
}
|
||||
|
||||
private async void clear_pxn_table_bits_for_va (uint64 va, MMUParameters p, TableWalkCache cache, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
uint64 table_pa = p.tt1;
|
||||
for (uint level = p.first_level; level != 3; level++) {
|
||||
uint entries = compute_max_entries (level, p);
|
||||
uint shift = address_shift_at_level (level, p.granule);
|
||||
uint index = (uint) ((va >> shift) & (entries - 1));
|
||||
uint64 slot_pa = table_pa + (index * Descriptor.SIZE);
|
||||
|
||||
Buffer d_buf = yield gdb.read_buffer (slot_pa, Descriptor.SIZE, cancellable);
|
||||
uint64 raw_desc = d_buf.read_uint64 (0);
|
||||
Descriptor desc = Descriptor.parse (raw_desc, level, p.granule);
|
||||
if (desc.kind != TABLE)
|
||||
break;
|
||||
|
||||
if ((raw_desc & TABLE_PXN_BIT) != 0) {
|
||||
uint64 new_desc = raw_desc & ~TABLE_PXN_BIT;
|
||||
d_buf.write_uint64 (0, new_desc);
|
||||
yield gdb.write_byte_array (slot_pa, d_buf.bytes, cancellable);
|
||||
}
|
||||
|
||||
table_pa = desc.target_address;
|
||||
}
|
||||
}
|
||||
|
||||
private class TableWalkCache {
|
||||
private Gee.Map<uint64?, TableWalkResult> cache =
|
||||
new Gee.HashMap<uint64?, TableWalkResult> (Numeric.uint64_hash, Numeric.uint64_equal);
|
||||
|
||||
public TableWalkResult? lookup (uint64 va, MMUParameters p) {
|
||||
for (int level = 2; level >= (int) p.first_level; level--) {
|
||||
uint64 cache_key = compute_cache_key (va, (uint) level, p);
|
||||
TableWalkResult? result = cache[cache_key];
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void store (uint64 va, uint level, uint64 table_pa, MMUParameters p) {
|
||||
if (level >= 3)
|
||||
return;
|
||||
|
||||
uint64 cache_key = compute_cache_key (va, level, p);
|
||||
cache[cache_key] = new TableWalkResult () {
|
||||
table_pa = table_pa,
|
||||
level = level
|
||||
};
|
||||
}
|
||||
|
||||
private uint64 compute_cache_key (uint64 va, uint level, MMUParameters p) {
|
||||
uint shift = address_shift_at_level (level + 1, p.granule);
|
||||
return (va >> shift) << shift;
|
||||
}
|
||||
}
|
||||
|
||||
private class TableWalkResult {
|
||||
public uint64 table_pa;
|
||||
public uint level;
|
||||
}
|
||||
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern, uint max_matches,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
unowned uint8[] scanner_blob = Data.Barebone.get_memory_scanner_arm64_elf_blob ().data;
|
||||
|
||||
var raw_scanner = new Bytes.static (scanner_blob);
|
||||
|
||||
Gum.ElfModule scanner;
|
||||
try {
|
||||
scanner = new Gum.ElfModule.from_blob (new Bytes.static (scanner_blob));
|
||||
scanner = new Gum.ElfModule.from_blob (raw_scanner);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
@@ -293,13 +494,17 @@ namespace Frida.Barebone {
|
||||
|
||||
uint64 module_page_pa = page_start (module_pa, page_size);
|
||||
|
||||
Allocation module_allocation = yield allocate_pages (module_page_pa, num_pages, cancellable);
|
||||
var module_pas = new Gee.ArrayList<uint64?> ();
|
||||
for (uint i = 0; i != num_pages; i++)
|
||||
module_pas.add (module_page_pa + (i * page_size)); // FIXME: This assumes a contiguous range.
|
||||
|
||||
Allocation module_allocation = yield allocate_pages (module_pas, cancellable);
|
||||
|
||||
size_t page_offset = (size_t) (module_pa - module_page_pa);
|
||||
uint64 module_va = module_allocation.virtual_address + page_offset;
|
||||
uint64 data_va = module_va + data_offset;
|
||||
|
||||
Bytes scanner_module = relocate (scanner, module_va);
|
||||
Bytes scanner_module = relocate (scanner, raw_scanner, module_va);
|
||||
Bytes original_memory = yield gdb.read_byte_array (module_va, vm_size, cancellable);
|
||||
yield gdb.write_byte_array (module_va, scanner_module, cancellable);
|
||||
|
||||
@@ -354,14 +559,17 @@ namespace Frida.Barebone {
|
||||
case ABS64:
|
||||
relocated.write_uint64 ((size_t) r.address, base_va + r.symbol.address + r.addend);
|
||||
break;
|
||||
case PREL32:
|
||||
var diff = (int64) (base_va + r.symbol.address + r.addend) - (int64) (base_va + r.address);
|
||||
relocated.write_int32 ((size_t) r.address, (int32) diff);
|
||||
break;
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported relocation type: %s",
|
||||
Marshal.enum_to_nick<Gum.ElfArm64Relocation> (type));
|
||||
}
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, uint64 landing_zone, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
if (args.length > 8)
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported number of arguments; please open a PR");
|
||||
|
||||
@@ -377,6 +585,7 @@ namespace Frida.Barebone {
|
||||
|
||||
regs["pc"] = impl;
|
||||
|
||||
uint64 landing_zone = saved_regs["pc"].get_uint64 ();
|
||||
regs["x30"] = landing_zone;
|
||||
|
||||
uint64 sp = saved_regs["sp"].get_uint64 ();
|
||||
@@ -406,6 +615,35 @@ namespace Frida.Barebone {
|
||||
return retval;
|
||||
}
|
||||
|
||||
public async void execute (uint64 start, uint64 end, Cancellable? cancellable) throws Error, IOError {
|
||||
bool was_running = gdb.state != STOPPED;
|
||||
if (was_running)
|
||||
yield gdb.stop (cancellable);
|
||||
|
||||
GDB.Thread thread = gdb.exception.thread;
|
||||
Gee.Map<string, Variant> saved_regs = yield thread.read_registers (cancellable);
|
||||
|
||||
var regs = new Gee.HashMap<string, Variant> ();
|
||||
regs.set_all (saved_regs);
|
||||
|
||||
regs["pc"] = start;
|
||||
|
||||
yield thread.write_registers (regs, cancellable);
|
||||
|
||||
GDB.Breakpoint bp = yield gdb.add_breakpoint (SOFT, end, 4, cancellable);
|
||||
GDB.Exception ex = null;
|
||||
do {
|
||||
ex = yield gdb.continue_until_exception (cancellable);
|
||||
} while (ex.breakpoint != bp || ex.thread.id != thread.id);
|
||||
// TODO: Improve GDB.Client to guarantee a single GDB.Thread instance per ID.
|
||||
yield bp.remove (cancellable);
|
||||
|
||||
yield thread.write_registers (saved_regs, cancellable);
|
||||
|
||||
if (was_running)
|
||||
yield gdb.continue (cancellable);
|
||||
}
|
||||
|
||||
public async CallFrame load_call_frame (GDB.Thread thread, uint arity, Cancellable? cancellable) throws Error, IOError {
|
||||
var regs = yield thread.read_registers (cancellable);
|
||||
|
||||
@@ -689,6 +927,10 @@ namespace Frida.Barebone {
|
||||
public uint first_level;
|
||||
public uint64 upper_bits;
|
||||
public uint64 tt1;
|
||||
public uint t1sz;
|
||||
|
||||
public bool sprr_enabled;
|
||||
public uint64 sprr_perm;
|
||||
|
||||
public static async MMUParameters load (GDB.Client gdb, Cancellable? cancellable) throws Error, IOError {
|
||||
GDB.Exception? exception = gdb.exception;
|
||||
@@ -716,6 +958,10 @@ namespace Frida.Barebone {
|
||||
parameters.upper_bits |= 1ULL << (63 - i);
|
||||
|
||||
parameters.tt1 = regs.ttbr1 & INT48_MASK;
|
||||
parameters.t1sz = t1sz;
|
||||
|
||||
parameters.sprr_enabled = (regs.sprr_config & 1) != 0;
|
||||
parameters.sprr_perm = regs.sprr_perm;
|
||||
|
||||
return parameters;
|
||||
}
|
||||
@@ -724,6 +970,8 @@ namespace Frida.Barebone {
|
||||
private class MMURegisters {
|
||||
public uint64 tcr;
|
||||
public uint64 ttbr1;
|
||||
public uint64 sprr_config;
|
||||
public uint64 sprr_perm;
|
||||
|
||||
public static async MMURegisters read (GDB.Thread thread, Cancellable? cancellable) throws Error, IOError {
|
||||
var regs = new MMURegisters ();
|
||||
@@ -741,10 +989,19 @@ namespace Frida.Barebone {
|
||||
regs.tcr = val;
|
||||
else if (name == "ttbr1_el1")
|
||||
regs.ttbr1 = val;
|
||||
else if (name == "sprr_config_el1")
|
||||
regs.sprr_config = val;
|
||||
else if (name == "sprr_el1br1_el1")
|
||||
regs.sprr_perm = val;
|
||||
}
|
||||
} else {
|
||||
regs.tcr = yield thread.read_register ("tcr_el1", cancellable);
|
||||
regs.ttbr1 = yield thread.read_register ("ttbr1_el1", cancellable);
|
||||
try {
|
||||
regs.sprr_config = yield thread.read_register ("sprr_config_el1", cancellable);
|
||||
regs.sprr_perm = yield thread.read_register ("sprr_el1br1_el1", cancellable);
|
||||
} catch (Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
return regs;
|
||||
@@ -850,16 +1107,27 @@ namespace Frida.Barebone {
|
||||
|
||||
private class DescriptorAllocation : Object, Allocation {
|
||||
public uint64 virtual_address {
|
||||
get { return base_va; }
|
||||
get {
|
||||
return base_va;
|
||||
}
|
||||
}
|
||||
|
||||
public size_t size {
|
||||
get {
|
||||
return _size;
|
||||
}
|
||||
}
|
||||
|
||||
private uint64 base_va;
|
||||
private size_t _size;
|
||||
private uint64 first_slot;
|
||||
private Bytes? old_descriptors;
|
||||
private GDB.Client gdb;
|
||||
|
||||
public DescriptorAllocation (uint64 base_va, uint64 first_slot, Bytes old_descriptors, GDB.Client gdb) {
|
||||
public DescriptorAllocation (uint64 base_va, size_t size, uint64 first_slot, Bytes old_descriptors,
|
||||
GDB.Client gdb) {
|
||||
this.base_va = base_va;
|
||||
this._size = size;
|
||||
this.first_slot = first_slot;
|
||||
this.old_descriptors = old_descriptors;
|
||||
this.gdb = gdb;
|
||||
@@ -908,11 +1176,23 @@ namespace Frida.Barebone {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
private static uint compute_max_entries (uint level, Granule granule) {
|
||||
return 1 << num_address_bits_at_level (level, granule);
|
||||
private static uint compute_max_entries (uint level, MMUParameters p) {
|
||||
return 1 << num_address_bits_at_level (level, p);
|
||||
}
|
||||
|
||||
private static uint num_address_bits_at_level (uint level, Granule granule) {
|
||||
private static uint num_address_bits_at_level (uint level, MMUParameters p) {
|
||||
if (level == p.first_level) {
|
||||
uint available_address_bits = 64 - p.t1sz - inpage_bits_for_granule (p.granule);
|
||||
uint consumed_by_lower_levels = 0;
|
||||
for (uint l = level + 1; l <= 3; l++)
|
||||
consumed_by_lower_levels += unclipped_address_bits_at_level (l, p.granule);
|
||||
return available_address_bits - consumed_by_lower_levels;
|
||||
} else {
|
||||
return unclipped_address_bits_at_level (level, p.granule);
|
||||
}
|
||||
}
|
||||
|
||||
private static uint unclipped_address_bits_at_level (uint level, Granule granule) {
|
||||
switch (granule) {
|
||||
case 4K:
|
||||
return 9;
|
||||
@@ -932,7 +1212,7 @@ namespace Frida.Barebone {
|
||||
private static uint address_shift_at_level (uint level, Granule granule) {
|
||||
uint shift = inpage_bits_for_granule (granule);
|
||||
for (int l = 2; l != (int) level - 1; l--)
|
||||
shift += num_address_bits_at_level (l, granule);
|
||||
shift += unclipped_address_bits_at_level (l, granule);
|
||||
return shift;
|
||||
}
|
||||
|
||||
@@ -949,7 +1229,45 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private static Gum.PageProtection protection_from_flags (DescriptorFlags flags) {
|
||||
private static Gum.PageProtection protection_from_flags (DescriptorFlags flags, MMUParameters p) {
|
||||
if (p.sprr_enabled) {
|
||||
uint idx = 0;
|
||||
if ((flags & DescriptorFlags.AP_READ_ONLY) != 0)
|
||||
idx |= 1 << 3;
|
||||
if ((flags & DescriptorFlags.AP_ALLOW_APPLICATION) != 0)
|
||||
idx |= 1 << 2;
|
||||
if ((flags & DescriptorFlags.UXN) != 0)
|
||||
idx |= 1 << 1;
|
||||
if ((flags & DescriptorFlags.PXN) != 0)
|
||||
idx |= 1 << 0;
|
||||
|
||||
uint perm = (uint) ((p.sprr_perm >> (idx * 4)) & 0xf);
|
||||
|
||||
Gum.PageProtection prot;
|
||||
switch (perm & 3) {
|
||||
case 0:
|
||||
prot = 0;
|
||||
break;
|
||||
case 1:
|
||||
prot = READ | EXECUTE;
|
||||
if ((perm >> 2) == 2)
|
||||
prot = EXECUTE;
|
||||
break;
|
||||
case 2:
|
||||
prot = READ;
|
||||
break;
|
||||
case 3:
|
||||
prot = READ | WRITE;
|
||||
if ((perm >> 2) == 1)
|
||||
prot = 0;
|
||||
break;
|
||||
default:
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
return prot;
|
||||
}
|
||||
|
||||
Gum.PageProtection prot = READ;
|
||||
if ((flags & DescriptorFlags.PXN) == 0)
|
||||
prot |= EXECUTE;
|
||||
@@ -958,6 +1276,93 @@ namespace Frida.Barebone {
|
||||
return prot;
|
||||
}
|
||||
|
||||
private static uint64 apply_protection_bits (uint64 descriptor, Gum.PageProtection prot, MMUParameters p) throws Error {
|
||||
uint64 result = descriptor;
|
||||
|
||||
if (p.sprr_enabled) {
|
||||
uint target_sprr_index = find_sprr_index_for_permissions (prot, p);
|
||||
result = apply_sprr_index_to_descriptor (result, target_sprr_index);
|
||||
} else {
|
||||
result = apply_non_sprr_protection_bits (result, prot);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static uint64 apply_non_sprr_protection_bits (uint64 descriptor, Gum.PageProtection prot) {
|
||||
uint64 result = descriptor;
|
||||
|
||||
bool want_exec = (prot & Gum.PageProtection.EXECUTE) != 0;
|
||||
bool want_write = (prot & Gum.PageProtection.WRITE) != 0;
|
||||
|
||||
if (want_exec)
|
||||
result &= ~(UXN_BIT | PXN_BIT);
|
||||
else
|
||||
result |= (UXN_BIT | PXN_BIT);
|
||||
|
||||
if (want_write) {
|
||||
result &= ~(AP1_BIT | AP0_BIT);
|
||||
} else {
|
||||
result |= AP1_BIT;
|
||||
result &= ~AP0_BIT;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static uint find_sprr_index_for_permissions (Gum.PageProtection prot, MMUParameters p) throws Error {
|
||||
bool want_read = (prot & Gum.PageProtection.READ) != 0;
|
||||
bool want_write = (prot & Gum.PageProtection.WRITE) != 0;
|
||||
bool want_exec = (prot & Gum.PageProtection.EXECUTE) != 0;
|
||||
|
||||
for (uint i = 0; i != 16; i++) {
|
||||
uint perm = (uint) ((p.sprr_perm >> (i * 4)) & 0xf);
|
||||
|
||||
bool slot_allows_read = false;
|
||||
bool slot_allows_write = false;
|
||||
bool slot_allows_exec = false;
|
||||
|
||||
switch (perm & 3) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
slot_allows_exec = true;
|
||||
if ((perm >> 2) != 2)
|
||||
slot_allows_read = true;
|
||||
break;
|
||||
case 2:
|
||||
slot_allows_read = true;
|
||||
break;
|
||||
case 3:
|
||||
slot_allows_read = true;
|
||||
slot_allows_write = ((perm >> 2) != 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (slot_allows_read == want_read && slot_allows_write == want_write && slot_allows_exec == want_exec)
|
||||
return i;
|
||||
}
|
||||
|
||||
throw new Error.INVALID_ARGUMENT ("No suitable SPRR slot found for protection 0x%x", prot);
|
||||
}
|
||||
|
||||
private static uint64 apply_sprr_index_to_descriptor (uint64 descriptor, uint sprr_index) {
|
||||
uint64 result = descriptor;
|
||||
|
||||
result &= ~(AP1_BIT | AP0_BIT | UXN_BIT | PXN_BIT);
|
||||
|
||||
if ((sprr_index & 0x8) != 0)
|
||||
result |= AP1_BIT;
|
||||
if ((sprr_index & 0x4) != 0)
|
||||
result |= AP0_BIT;
|
||||
if ((sprr_index & 0x2) != 0)
|
||||
result |= UXN_BIT;
|
||||
if ((sprr_index & 0x1) != 0)
|
||||
result |= PXN_BIT;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Gum.PageProtection protection_from_corellium_pt_details (string details) {
|
||||
Gum.PageProtection prot = READ;
|
||||
if (!("pXN" in details))
|
||||
|
||||
@@ -22,15 +22,24 @@ namespace Frida.Barebone {
|
||||
return 4096;
|
||||
}
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern,
|
||||
uint max_matches, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
@@ -40,8 +49,7 @@ namespace Frida.Barebone {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, uint64 landing_zone, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,16 +22,25 @@ namespace Frida.Barebone {
|
||||
return 4096;
|
||||
}
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern, uint max_matches,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
@@ -41,8 +50,7 @@ namespace Frida.Barebone {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, uint64 landing_zone, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,16 +24,25 @@ namespace Frida.Barebone {
|
||||
return 4096;
|
||||
}
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern, uint max_matches,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
@@ -43,8 +52,7 @@ namespace Frida.Barebone {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, uint64 landing_zone, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,26 +2,8 @@ namespace Frida {
|
||||
public sealed class BareboneHostSessionBackend : Object, HostSessionBackend {
|
||||
private BareboneHostSessionProvider? provider;
|
||||
|
||||
private const uint16 DEFAULT_PORT = 3333;
|
||||
|
||||
public async void start (Cancellable? cancellable) throws IOError {
|
||||
SocketConnectable? connectable = null;
|
||||
unowned string? address = Environment.get_variable ("FRIDA_BAREBONE_ADDRESS");
|
||||
if (address != null) {
|
||||
try {
|
||||
connectable = NetworkAddress.parse (address, DEFAULT_PORT);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
if (connectable == null)
|
||||
connectable = new InetSocketAddress (new InetAddress.loopback (SocketFamily.IPV4), DEFAULT_PORT);
|
||||
|
||||
uint64 heap_base_pa = 0;
|
||||
unowned string? heap_base_preference = Environment.get_variable ("FRIDA_BAREBONE_HEAP_BASE");
|
||||
if (heap_base_preference != null)
|
||||
heap_base_pa = uint64.parse (heap_base_preference, 16);
|
||||
|
||||
provider = new BareboneHostSessionProvider (connectable, heap_base_pa);
|
||||
provider = new BareboneHostSessionProvider ();
|
||||
provider_available (provider);
|
||||
}
|
||||
|
||||
@@ -49,22 +31,8 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public SocketConnectable connectable {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public uint64 heap_base_pa {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private BareboneHostSession? host_session;
|
||||
|
||||
public BareboneHostSessionProvider (SocketConnectable connectable, uint64 heap_base_pa) {
|
||||
Object (connectable: connectable, heap_base_pa: heap_base_pa);
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
if (host_session != null) {
|
||||
yield host_session.close (cancellable);
|
||||
@@ -76,6 +44,29 @@ namespace Frida {
|
||||
if (host_session != null)
|
||||
throw new Error.INVALID_OPERATION ("Already created");
|
||||
|
||||
Barebone.Config config;
|
||||
unowned string? config_path = Environment.get_variable ("FRIDA_BAREBONE_CONFIG");
|
||||
if (config_path != null) {
|
||||
try {
|
||||
var config_data = yield FS.read_all_text (File.new_for_path (config_path), cancellable);
|
||||
var cfg = (Barebone.Config) Json.gobject_from_data (typeof (Barebone.Config), config_data);
|
||||
cfg.check ();
|
||||
config = cfg;
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("Unable to load %s: %s", config_path, e.message);
|
||||
}
|
||||
} else {
|
||||
config = new Barebone.Config ();
|
||||
}
|
||||
|
||||
SocketConnectable connectable;
|
||||
try {
|
||||
Barebone.ConnectionConfig c = config.connection;
|
||||
connectable = NetworkAddress.parse (c.host, c.port);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("Unable to load %s: %s", config_path, e.message);
|
||||
}
|
||||
|
||||
IOStream stream;
|
||||
try {
|
||||
var client = new SocketClient ();
|
||||
@@ -109,16 +100,39 @@ namespace Frida {
|
||||
break;
|
||||
}
|
||||
|
||||
var page_size = yield machine.query_page_size (cancellable);
|
||||
size_t page_size;
|
||||
try {
|
||||
page_size = yield machine.query_page_size (cancellable);
|
||||
} catch (Error e) {
|
||||
page_size = 0;
|
||||
}
|
||||
|
||||
// TODO: Locate and use kernel's allocator when possible.
|
||||
Barebone.Allocator allocator = new Barebone.SimpleAllocator (machine, page_size, heap_base_pa);
|
||||
Barebone.Allocator allocator;
|
||||
Barebone.AllocatorConfig? ac = config.allocator;
|
||||
if (ac == null) {
|
||||
allocator = new Barebone.NullAllocator (page_size);
|
||||
} else if (ac is Barebone.PhysicalAllocatorConfig) {
|
||||
allocator = new Barebone.PhysicalAllocator (machine, page_size,
|
||||
(Barebone.PhysicalAllocatorConfig) ac);
|
||||
} else if (ac is Barebone.TargetFunctionsAllocatorConfig) {
|
||||
allocator = new Barebone.TargetFunctionsAllocator (machine, page_size,
|
||||
(Barebone.TargetFunctionsAllocatorConfig) ac);
|
||||
} else {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
Barebone.AgentConnection? agent_connection = null;
|
||||
Barebone.AgentConfig? agent_config = config.agent;
|
||||
if (agent_config != null) {
|
||||
agent_connection = yield Barebone.AgentConnection.open (agent_config, config.image, machine, allocator,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
var interceptor = new Barebone.Interceptor (machine, allocator);
|
||||
|
||||
var services = new Barebone.Services (machine, allocator, interceptor);
|
||||
|
||||
host_session = new BareboneHostSession (services);
|
||||
host_session = new BareboneHostSession (agent_connection, services);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
return host_session;
|
||||
@@ -170,6 +184,11 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public sealed class BareboneHostSession : Object, HostSession {
|
||||
public Barebone.AgentConnection? connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Barebone.Services services {
|
||||
get;
|
||||
construct;
|
||||
@@ -178,8 +197,8 @@ namespace Frida {
|
||||
private Gee.Map<AgentSessionId?, BareboneAgentSession> agent_sessions =
|
||||
new Gee.HashMap<AgentSessionId?, BareboneAgentSession> (AgentSessionId.hash, AgentSessionId.equal);
|
||||
|
||||
public BareboneHostSession (Barebone.Services services) {
|
||||
Object (services: services);
|
||||
public BareboneHostSession (Barebone.AgentConnection? connection, Barebone.Services services) {
|
||||
Object (connection: connection, services: services);
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
@@ -255,13 +274,17 @@ namespace Frida {
|
||||
|
||||
var opts = SessionOptions._deserialize (options);
|
||||
if (opts.realm == EMULATED)
|
||||
throw new Error.NOT_SUPPORTED ("Emulated realm is not supported on barebone targets");
|
||||
throw new Error.NOT_SUPPORTED ("Emulated realm is not supported on Barebone targets");
|
||||
|
||||
var session_id = AgentSessionId.generate ();
|
||||
|
||||
MainContext dbus_context = yield get_dbus_context ();
|
||||
|
||||
var session = new BareboneAgentSession (session_id, opts.persist_timeout, dbus_context, services);
|
||||
BareboneAgentSession session;
|
||||
if (connection != null)
|
||||
session = new RemoteBareboneAgentSession (connection, session_id, opts.persist_timeout, dbus_context);
|
||||
else
|
||||
session = new LocalBareboneAgentSession (services, session_id, opts.persist_timeout, dbus_context);
|
||||
agent_sessions[session_id] = session;
|
||||
session.closed.connect (on_agent_session_closed);
|
||||
|
||||
@@ -321,7 +344,158 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BareboneAgentSession : Object, AgentSession {
|
||||
private sealed class LocalBareboneAgentSession : BareboneAgentSession {
|
||||
public Barebone.Services services {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Gee.Map<AgentScriptId?, BareboneScript> scripts =
|
||||
new Gee.HashMap<AgentScriptId?, BareboneScript> (AgentScriptId.hash, AgentScriptId.equal);
|
||||
private uint next_script_id = 1;
|
||||
|
||||
public LocalBareboneAgentSession (Barebone.Services services, AgentSessionId id, uint persist_timeout,
|
||||
MainContext dbus_context) {
|
||||
Object (
|
||||
services: services,
|
||||
id: id,
|
||||
persist_timeout: persist_timeout,
|
||||
frida_context: MainContext.ref_thread_default (),
|
||||
dbus_context: dbus_context
|
||||
);
|
||||
}
|
||||
|
||||
public override async AgentScriptId create_script (string source, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
var opts = ScriptOptions._deserialize (options);
|
||||
if (opts.runtime == V8)
|
||||
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the Barebone backend");
|
||||
|
||||
var id = AgentScriptId (next_script_id++);
|
||||
|
||||
var script = BareboneScript.create (id, source, services);
|
||||
scripts[id] = script;
|
||||
script.message.connect (on_message_from_script);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public override async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
BareboneScript script = get_script (script_id);
|
||||
yield script.destroy (cancellable);
|
||||
script.message.disconnect (on_message_from_script);
|
||||
|
||||
scripts.unset (script_id);
|
||||
}
|
||||
|
||||
public override async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
var script = get_script (script_id);
|
||||
yield script.load (cancellable);
|
||||
}
|
||||
|
||||
public override async void post_messages (AgentMessage[] messages, uint batch_id, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
transmitter.check_okay_to_receive ();
|
||||
|
||||
foreach (var m in messages) {
|
||||
switch (m.kind) {
|
||||
case SCRIPT: {
|
||||
BareboneScript? script = scripts[m.script_id];
|
||||
if (script != null)
|
||||
script.post (m.text, m.has_data ? new Bytes (m.data) : null);
|
||||
break;
|
||||
}
|
||||
case DEBUGGER:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
transmitter.notify_rx_batch_id (batch_id);
|
||||
}
|
||||
|
||||
private void on_message_from_script (BareboneScript script, string json, Bytes? data) {
|
||||
transmitter.post_message_from_script (script.id, json, data);
|
||||
}
|
||||
|
||||
private BareboneScript get_script (AgentScriptId script_id) throws Error {
|
||||
var script = scripts[script_id];
|
||||
if (script == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid script ID");
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RemoteBareboneAgentSession : BareboneAgentSession {
|
||||
public Barebone.AgentConnection connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public RemoteBareboneAgentSession (Barebone.AgentConnection connection, AgentSessionId id, uint persist_timeout,
|
||||
MainContext dbus_context) {
|
||||
Object (
|
||||
connection: connection,
|
||||
id: id,
|
||||
persist_timeout: persist_timeout,
|
||||
frida_context: MainContext.ref_thread_default (),
|
||||
dbus_context: dbus_context
|
||||
);
|
||||
}
|
||||
|
||||
construct {
|
||||
connection.script_message.connect (on_message_from_script);
|
||||
}
|
||||
|
||||
public override async AgentScriptId create_script (string source, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
var opts = ScriptOptions._deserialize (options);
|
||||
if (opts.runtime == V8)
|
||||
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the Barebone backend");
|
||||
|
||||
return yield connection.create_script (source, cancellable);
|
||||
}
|
||||
|
||||
public override async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
yield connection.destroy_script (script_id, cancellable);
|
||||
}
|
||||
|
||||
public override async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
yield connection.load_script (script_id, cancellable);
|
||||
}
|
||||
|
||||
public override async void post_messages (AgentMessage[] messages, uint batch_id, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
transmitter.check_okay_to_receive ();
|
||||
|
||||
foreach (var m in messages) {
|
||||
switch (m.kind) {
|
||||
case SCRIPT:
|
||||
yield connection.post_script_message (m.script_id, m.text,
|
||||
m.has_data ? new Bytes (m.data) : null, cancellable);
|
||||
break;
|
||||
case DEBUGGER:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
transmitter.notify_rx_batch_id (batch_id);
|
||||
}
|
||||
|
||||
private void on_message_from_script (AgentScriptId id, string json, Bytes? data) {
|
||||
transmitter.post_message_from_script (id, json, data);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class BareboneAgentSession : Object, AgentSession {
|
||||
public signal void closed ();
|
||||
|
||||
public AgentSessionId id {
|
||||
@@ -335,8 +509,12 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public AgentMessageSink? message_sink {
|
||||
get { return transmitter.message_sink; }
|
||||
set { transmitter.message_sink = value; }
|
||||
get {
|
||||
return transmitter.message_sink;
|
||||
}
|
||||
set {
|
||||
transmitter.message_sink = value;
|
||||
}
|
||||
}
|
||||
|
||||
public MainContext frida_context {
|
||||
@@ -349,34 +527,9 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public Barebone.Services services {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Barebone.Allocator allocator {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Promise<bool>? close_request;
|
||||
|
||||
private Gee.Map<AgentScriptId?, BareboneScript> scripts =
|
||||
new Gee.HashMap<AgentScriptId?, BareboneScript> (AgentScriptId.hash, AgentScriptId.equal);
|
||||
private uint next_script_id = 1;
|
||||
|
||||
private AgentMessageTransmitter transmitter;
|
||||
|
||||
public BareboneAgentSession (AgentSessionId id, uint persist_timeout, MainContext dbus_context,
|
||||
Barebone.Services services) {
|
||||
Object (
|
||||
id: id,
|
||||
persist_timeout: persist_timeout,
|
||||
frida_context: MainContext.ref_thread_default (),
|
||||
dbus_context: dbus_context,
|
||||
services: services
|
||||
);
|
||||
}
|
||||
protected AgentMessageTransmitter transmitter;
|
||||
|
||||
construct {
|
||||
assert (frida_context != null);
|
||||
@@ -421,22 +574,8 @@ namespace Frida {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async AgentScriptId create_script (string source, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
var opts = ScriptOptions._deserialize (options);
|
||||
if (opts.runtime == V8)
|
||||
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the barebone backend");
|
||||
|
||||
var id = AgentScriptId (next_script_id++);
|
||||
|
||||
var script = BareboneScript.create (id, source, services);
|
||||
scripts[id] = script;
|
||||
script.message.connect (on_message_from_script);
|
||||
|
||||
return id;
|
||||
}
|
||||
public abstract async AgentScriptId create_script (string source, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public async AgentScriptId create_script_from_bytes (uint8[] bytes, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -453,32 +592,14 @@ namespace Frida {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
public abstract async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
BareboneScript script = get_script (script_id);
|
||||
yield script.destroy (cancellable);
|
||||
script.message.disconnect (on_message_from_script);
|
||||
|
||||
scripts.unset (script_id);
|
||||
}
|
||||
|
||||
public async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
get_script (script_id).load ();
|
||||
}
|
||||
public abstract async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
private BareboneScript get_script (AgentScriptId script_id) throws Error {
|
||||
var script = scripts[script_id];
|
||||
if (script == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid script ID");
|
||||
return script;
|
||||
}
|
||||
|
||||
public async void enable_debugger (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
@@ -487,25 +608,8 @@ namespace Frida {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void post_messages (AgentMessage[] messages, uint batch_id,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
transmitter.check_okay_to_receive ();
|
||||
|
||||
foreach (var m in messages) {
|
||||
switch (m.kind) {
|
||||
case SCRIPT: {
|
||||
BareboneScript? script = scripts[m.script_id];
|
||||
if (script != null)
|
||||
script.post (m.text, m.has_data ? new Bytes (m.data) : null);
|
||||
break;
|
||||
}
|
||||
case DEBUGGER:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
transmitter.notify_rx_batch_id (batch_id);
|
||||
}
|
||||
public abstract async void post_messages (AgentMessage[] messages, uint batch_id, Cancellable? cancellable)
|
||||
throws Error, IOError;
|
||||
|
||||
public async PortalMembershipId join_portal (string address, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -537,15 +641,11 @@ namespace Frida {
|
||||
transmitter.commit_migration ();
|
||||
}
|
||||
|
||||
private void check_open () throws Error {
|
||||
protected void check_open () throws Error {
|
||||
if (close_request != null)
|
||||
throw new Error.INVALID_OPERATION ("Session is closing");
|
||||
}
|
||||
|
||||
private void on_message_from_script (BareboneScript script, string json, Bytes? data) {
|
||||
transmitter.post_message_from_script (script.id, json, data);
|
||||
}
|
||||
|
||||
private void on_transmitter_closed () {
|
||||
transmitter.closed.disconnect (on_transmitter_closed);
|
||||
transmitter.new_candidates.disconnect (on_transmitter_new_candidates);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
private sealed class CModule : Object {
|
||||
public signal void console_output (string message);
|
||||
|
||||
public Gee.List<Export> exports {
|
||||
get;
|
||||
default = new Gee.ArrayList<Export> ();
|
||||
}
|
||||
|
||||
public class Export {
|
||||
public string name;
|
||||
public uint64 address;
|
||||
|
||||
internal Export (string name, uint64 address) {
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
}
|
||||
}
|
||||
|
||||
private Gum.ElfModule elf;
|
||||
private Allocation allocation;
|
||||
private Callback console_log_callback;
|
||||
|
||||
public async CModule.from_blob (Bytes blob, Machine machine, Allocator allocator, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
try {
|
||||
elf = new Gum.ElfModule.from_blob (blob);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
|
||||
var raw_elf = new Bytes.static (elf.get_file_data ());
|
||||
|
||||
size_t page_size = yield machine.query_page_size (cancellable);
|
||||
|
||||
allocation = yield inject_elf (elf, raw_elf, page_size, machine, allocator, cancellable);
|
||||
|
||||
uint64 base_va = allocation.virtual_address;
|
||||
uint64 console_log_trap = 0;
|
||||
elf.enumerate_symbols (e => {
|
||||
if (e.name == "")
|
||||
return true;
|
||||
|
||||
if (e.name == "_console_log") {
|
||||
console_log_trap = base_va + e.address;
|
||||
return true;
|
||||
}
|
||||
|
||||
exports.add (new Export (e.name, base_va + e.address));
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (console_log_trap != 0) {
|
||||
var handler = new ConsoleLogHandler (machine.gdb);
|
||||
handler.output.connect (on_console_output);
|
||||
console_log_callback = yield new Callback (console_log_trap, handler, machine, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_console_output (string message) {
|
||||
console_output (message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
/**
|
||||
* Barebone backend configuration. This is specified via the FRIDA_BAREBONE_CONFIG environment
|
||||
* variable, which should point to the filesystem path of a JSON-encoded configuration file.
|
||||
*
|
||||
* Example JSON configurations:
|
||||
*
|
||||
* 1. Using all defaults:
|
||||
* {
|
||||
* "connection": {
|
||||
* "host": "127.0.0.1",
|
||||
* "port": 3333
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 2. Using a physical memory allocator:
|
||||
* {
|
||||
* "connection": {
|
||||
* "host": "127.0.0.1",
|
||||
* "port": 9000
|
||||
* },
|
||||
* "allocator": {
|
||||
* "mode": "physical",
|
||||
* "physical_base": "0x8ec9b4000"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 3. Using target-specific allocation functions:
|
||||
* {
|
||||
* "connection": {
|
||||
* "host": "127.0.0.1",
|
||||
* "port": 9000
|
||||
* },
|
||||
* "allocator": {
|
||||
* "mode": "target-functions",
|
||||
* "alloc_function": "0xfffffff007a3c278",
|
||||
* "free_function": "0xfffffff007a3c338"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* 4. Injecting a remote agent:
|
||||
* {
|
||||
* "agent": {
|
||||
* "path": "/path/to/target/aarch64-unknown-none/release/frida-barebone-agent",
|
||||
* "transport": {
|
||||
* "type": "hostlink",
|
||||
* "qmp": "unix:/path/to/qmp.sock"
|
||||
* }
|
||||
* },
|
||||
* "image": {
|
||||
* "file": "/path/to/kernelcache.research.iphone12b",
|
||||
* "base": "0xfffffff007004000"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public sealed class Config : Object, Json.Serializable {
|
||||
public ConnectionConfig connection {
|
||||
get;
|
||||
set;
|
||||
default = new ConnectionConfig ();
|
||||
}
|
||||
|
||||
public AllocatorConfig? allocator {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public AgentConfig? agent {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ImageConfig? image {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void check () throws Error {
|
||||
if (allocator != null)
|
||||
allocator.check ();
|
||||
if (agent != null)
|
||||
agent.check ();
|
||||
if (image != null)
|
||||
image.check ();
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "allocator") {
|
||||
AllocatorConfig? allocator = null;
|
||||
Type t = typeof (InvalidAllocatorConfig);
|
||||
if (property_node.get_node_type () == Json.NodeType.OBJECT) {
|
||||
switch (property_node.get_object ().get_string_member_with_default ("mode", "invalid")) {
|
||||
case "physical":
|
||||
t = typeof (PhysicalAllocatorConfig);
|
||||
break;
|
||||
case "target-functions":
|
||||
t = typeof (TargetFunctionsAllocatorConfig);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
allocator = (AllocatorConfig) Json.gobject_deserialize (t, property_node);
|
||||
} else {
|
||||
allocator = new InvalidAllocatorConfig ();
|
||||
}
|
||||
|
||||
var v = Value (t);
|
||||
v.set_object (allocator);
|
||||
value = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConnectionConfig : Object {
|
||||
public string host {
|
||||
get;
|
||||
set;
|
||||
default = "127.0.0.1";
|
||||
}
|
||||
|
||||
public uint16 port {
|
||||
get;
|
||||
set;
|
||||
default = 3333;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AllocatorConfig : Object {
|
||||
public abstract void check () throws Error;
|
||||
}
|
||||
|
||||
public sealed class InvalidAllocatorConfig : AllocatorConfig {
|
||||
public override void check () throws Error {
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'allocator' is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PhysicalAllocatorConfig : AllocatorConfig, Json.Serializable {
|
||||
public MemoryAddress physical_base {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override void check () throws Error {
|
||||
if (physical_base == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'allocator.physical_base' is missing");
|
||||
physical_base.check ();
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "physical_base") {
|
||||
value = deserialize_address ("allocator.physical_base", property_node);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TargetFunctionsAllocatorConfig : AllocatorConfig, Json.Serializable {
|
||||
public MemoryAddress alloc_function {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public MemoryAddress free_function {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override void check () throws Error {
|
||||
if (alloc_function == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'allocator.alloc_function' is missing");
|
||||
alloc_function.check ();
|
||||
|
||||
if (free_function == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'allocator.free_function' is missing");
|
||||
free_function.check ();
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "alloc-function") {
|
||||
value = deserialize_address ("allocator.alloc_function", property_node);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property_name == "free-function") {
|
||||
value = deserialize_address ("allocator.free_function", property_node);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AgentConfig : Object, Json.Serializable {
|
||||
public string path {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public TransportConfig transport {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void check () throws Error {
|
||||
if (path == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.path' is missing");
|
||||
|
||||
if (transport == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport' is missing");
|
||||
transport.check ();
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "transport") {
|
||||
TransportConfig transport;
|
||||
Type t = typeof (InvalidTransportConfig);
|
||||
if (property_node.get_node_type () == Json.NodeType.OBJECT) {
|
||||
switch (property_node.get_object ().get_string_member_with_default ("type", "invalid")) {
|
||||
case "hostlink":
|
||||
t = typeof (HostlinkTransportConfig);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
transport = (TransportConfig) Json.gobject_deserialize (t, property_node);
|
||||
} else {
|
||||
transport = new InvalidTransportConfig ();
|
||||
}
|
||||
|
||||
var v = Value (t);
|
||||
v.set_object (transport);
|
||||
value = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TransportConfig : Object {
|
||||
public abstract void check () throws Error;
|
||||
}
|
||||
|
||||
public sealed class InvalidTransportConfig : TransportConfig {
|
||||
public override void check () throws Error {
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport' is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HostlinkTransportConfig : TransportConfig {
|
||||
public string qmp {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override void check () throws Error {
|
||||
if (qmp == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport.qmp' is missing");
|
||||
if (!qmp.has_prefix ("unix:"))
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport.qmp' must be a UNIX socket for now");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ImageConfig : Object, Json.Serializable {
|
||||
public string file {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public MemoryAddress base {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Gee.Map<string, uint64?> symbols {
|
||||
get;
|
||||
set;
|
||||
default = new Gee.HashMap<string, uint64?> ();
|
||||
}
|
||||
|
||||
public void check () throws Error {
|
||||
if (file == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'image.file' is missing");
|
||||
|
||||
if (@base == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'image.base' is missing");
|
||||
@base.check ();
|
||||
|
||||
if (symbols == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'image.symbols' is invalid");
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "base") {
|
||||
value = deserialize_address ("image.base", property_node);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property_name == "symbols") {
|
||||
Gee.Map<string, uint64?> syms = null;
|
||||
|
||||
if (property_node.get_node_type () == Json.NodeType.OBJECT) {
|
||||
syms = new Gee.HashMap<string, uint64?> ();
|
||||
|
||||
property_node.get_object ().foreach_member ((obj, name, node) => {
|
||||
if (syms == null)
|
||||
return;
|
||||
|
||||
uint64 addr;
|
||||
if (!try_deserialize_address (node, out addr)) {
|
||||
syms = null;
|
||||
return;
|
||||
}
|
||||
|
||||
syms[name] = addr;
|
||||
});
|
||||
}
|
||||
|
||||
value = syms;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class MemoryAddress : Object {
|
||||
public string label {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public uint64 address {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public abstract void check () throws Error;
|
||||
}
|
||||
|
||||
public sealed class InvalidMemoryAddress : MemoryAddress {
|
||||
public InvalidMemoryAddress (string label) {
|
||||
Object (label: label);
|
||||
}
|
||||
|
||||
public override void check () throws Error {
|
||||
throw new Error.NOT_SUPPORTED ("Config for '%s' is invalid", label);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NonNullMemoryAddress : MemoryAddress {
|
||||
public NonNullMemoryAddress (string label, uint64 address) {
|
||||
Object (label: label, address: address);
|
||||
}
|
||||
|
||||
public override void check () throws Error {
|
||||
if (address == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Config for '%s' cannot be NULL", label);
|
||||
}
|
||||
}
|
||||
|
||||
private MemoryAddress deserialize_address (string label, Json.Node node) {
|
||||
uint64 addr;
|
||||
if (!try_deserialize_address (node, out addr))
|
||||
return new InvalidMemoryAddress (label);
|
||||
return new NonNullMemoryAddress (label, addr);
|
||||
}
|
||||
|
||||
private bool try_deserialize_address (Json.Node node, out uint64 address) {
|
||||
Type t = node.get_value_type ();
|
||||
|
||||
if (t == typeof (string))
|
||||
return uint64.try_parse (node.get_string (), out address, null, 16);
|
||||
|
||||
if (t == typeof (int64)) {
|
||||
address = node.get_int ();
|
||||
return true;
|
||||
}
|
||||
|
||||
address = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
private class ConsoleLogHandler : Object, CallbackHandler {
|
||||
public signal void output (string message);
|
||||
|
||||
public uint arity {
|
||||
get { return 2; }
|
||||
}
|
||||
|
||||
private GDB.Client gdb;
|
||||
|
||||
public ConsoleLogHandler (GDB.Client gdb) {
|
||||
this.gdb = gdb;
|
||||
}
|
||||
|
||||
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var message = args[0];
|
||||
var len = (long) args[1];
|
||||
|
||||
Bytes str_bytes = yield gdb.read_byte_array (message, len, cancellable);
|
||||
unowned uint8[] str_data = str_bytes.get_data ();
|
||||
unowned string str_raw = (string) str_data;
|
||||
string str = str_raw.substring (0, len);
|
||||
|
||||
output (str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public async Allocation inject_elf (Gum.ElfModule elf, Bytes raw_elf, size_t page_size, Machine machine, Allocator allocator,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
size_t vm_size = (size_t) elf.mapped_size;
|
||||
|
||||
uint num_pages = (uint) (vm_size / page_size);
|
||||
if (vm_size % page_size != 0)
|
||||
num_pages++;
|
||||
|
||||
uint64 text_base = 0;
|
||||
size_t text_size = 0;
|
||||
elf.enumerate_segments (s => {
|
||||
if ((s.protection & Gum.PageProtection.EXECUTE) != 0) {
|
||||
text_base = s.vm_address;
|
||||
text_size = (size_t) s.vm_size;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (text_size == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to detect text segment");
|
||||
|
||||
var allocation = yield allocator.allocate (num_pages * page_size, page_size, cancellable);
|
||||
try {
|
||||
uint64 base_va = allocation.virtual_address;
|
||||
|
||||
Bytes relocated_image = machine.relocate (elf, raw_elf, base_va);
|
||||
yield machine.gdb.write_byte_array (base_va, relocated_image, cancellable);
|
||||
|
||||
yield machine.protect_pages (base_va + text_base, text_size, READ | EXECUTE, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
yield allocation.deallocate (cancellable);
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
return allocation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone.Img4 {
|
||||
public async Payload parse_file (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
Bytes blob = yield FS.read_all_bytes (f, cancellable);
|
||||
return parse (blob);
|
||||
}
|
||||
|
||||
public Payload parse (Bytes blob) throws Error {
|
||||
BufferReader r = new BufferReader (new Buffer (blob, BIG_ENDIAN));
|
||||
|
||||
if (r.read_uint8 () != Der.SEQUENCE)
|
||||
throw new Error.PROTOCOL ("IMG4/IM4P must start with DER SEQUENCE");
|
||||
size_t seq_len = Der.read_length (r);
|
||||
size_t seq_end = r.offset + seq_len;
|
||||
|
||||
string first_magic = Der.read_string (r);
|
||||
|
||||
if (first_magic == "IMG4") {
|
||||
if (r.read_uint8 () != Der.SEQUENCE)
|
||||
throw new Error.PROTOCOL ("Expected SEQUENCE for IM4P");
|
||||
size_t im4p_len = Der.read_length (r);
|
||||
seq_end = r.offset + im4p_len;
|
||||
if (Der.read_string (r) != "IM4P")
|
||||
throw new Error.PROTOCOL ("Missing IM4P magic");
|
||||
} else if (first_magic != "IM4P") {
|
||||
throw new Error.PROTOCOL ("Not an IMG4/IM4P container (magic='%s')", first_magic);
|
||||
}
|
||||
|
||||
string kind = Der.read_string (r);
|
||||
string description = Der.read_string (r);
|
||||
Bytes payload_raw = Der.read_octet_string (r);
|
||||
Bytes final_data = maybe_decompress_lzfse (payload_raw);
|
||||
|
||||
return new Payload (kind, description, final_data);
|
||||
}
|
||||
|
||||
public class Payload : Object {
|
||||
public string kind {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public string description {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Bytes data {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Payload (string kind, string description, Bytes data) {
|
||||
Object (
|
||||
kind: kind,
|
||||
description: description,
|
||||
data: data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Der {
|
||||
private const uint8 OCTET_STRING = 0x04;
|
||||
private const uint8 UTF8_STRING = 0x0C;
|
||||
private const uint8 IA5_STRING = 0x16;
|
||||
private const uint8 SEQUENCE = 0x30;
|
||||
|
||||
private size_t read_length (BufferReader r) throws Error {
|
||||
uint8 b = r.read_uint8 ();
|
||||
if ((b & 0x80) == 0)
|
||||
return b;
|
||||
|
||||
uint8 nlen = (uint8) (b & 0x7f);
|
||||
if (nlen == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Indefinite length not supported in IMG4");
|
||||
|
||||
size_t len = 0;
|
||||
for (uint8 i = 0; i != nlen; i++)
|
||||
len = (len << 8) | r.read_uint8 ();
|
||||
return len;
|
||||
}
|
||||
|
||||
private Bytes read_octet_string (BufferReader r) throws Error {
|
||||
uint8 tag = r.read_uint8 ();
|
||||
if (tag != Der.OCTET_STRING)
|
||||
throw new Error.PROTOCOL ("Expected OCTET STRING, got tag 0x%02x".printf (tag));
|
||||
size_t len = read_length (r);
|
||||
return r.read_bytes (len);
|
||||
}
|
||||
|
||||
private string read_string (BufferReader r) throws Error {
|
||||
uint8 tag = r.read_uint8 ();
|
||||
if (tag != Der.IA5_STRING && tag != Der.UTF8_STRING)
|
||||
throw new Error.PROTOCOL ("Unexpected string tag 0x%02x", tag);
|
||||
size_t len = read_length (r);
|
||||
return r.read_fixed_string (len);
|
||||
}
|
||||
}
|
||||
|
||||
private Bytes maybe_decompress_lzfse (Bytes src) throws Error {
|
||||
if (LZFSE.is_lzfse (src))
|
||||
return LZFSE.decode (src);
|
||||
return src;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class Layout : Object {
|
||||
public Gee.List<ModuleInfo> modules {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Gee.List<SymbolInfo> symbols {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private const uint64 CHAINED_PTR_TARGET_MASK = (1 << 30) - 1;
|
||||
|
||||
private Layout (Gee.List<ModuleInfo> modules, Gee.List<SymbolInfo> symbols) {
|
||||
Object (modules: modules, symbols: symbols);
|
||||
}
|
||||
|
||||
public Layout.empty () {
|
||||
Object (modules: new Gee.ArrayList<ModuleInfo> (), symbols: new Gee.ArrayList<SymbolInfo> ());
|
||||
}
|
||||
|
||||
public static async Layout load_from_symbol_source (File symbol_source, uint64 kernel_base, ByteOrder byte_order,
|
||||
uint pointer_size, Cancellable? cancellable) throws Error, IOError {
|
||||
var payload = yield Img4.parse_file (symbol_source, cancellable);
|
||||
|
||||
Bytes kerncache = payload.data;
|
||||
|
||||
Gum.DarwinModule mod;
|
||||
try {
|
||||
mod = new Gum.DarwinModule.from_blob (kerncache, ARM64, Gum.PtrauthSupport.SUPPORTED);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
|
||||
var blob = new Blob (kerncache, byte_order, pointer_size);
|
||||
|
||||
var modules = compute_module_list (mod, blob, kernel_base);
|
||||
var symbols = compute_symbol_list (mod);
|
||||
|
||||
return new Layout (modules, symbols);
|
||||
}
|
||||
|
||||
private static Gee.List<ModuleInfo> compute_module_list (Gum.DarwinModule mod, Blob blob, uint64 kernel_base) throws Error {
|
||||
Buffer? kmod_info = null;
|
||||
Buffer? kmod_start = null;
|
||||
Error? pending_error = null;
|
||||
mod.enumerate_sections (s => {
|
||||
if (s.segment_name == "__PRELINK_INFO") {
|
||||
try {
|
||||
if (s.section_name == "__kmod_info")
|
||||
kmod_info = blob.slice (s.file_offset, (size_t) s.size, "__kmod_info");
|
||||
else if (s.section_name == "__kmod_start")
|
||||
kmod_start = blob.slice (s.file_offset, (size_t) s.size, "__kmod_start");
|
||||
} catch (Error e) {
|
||||
pending_error = e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (pending_error != null)
|
||||
throw pending_error;
|
||||
if (kmod_info == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to find __kmod_info");
|
||||
if (kmod_start == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to find __kmod_start");
|
||||
|
||||
var kexts = new Gee.ArrayList<ModuleInfo> ();
|
||||
var info_pointers = new BufferReader (kmod_info);
|
||||
while (info_pointers.available != 0) {
|
||||
var kmodinfo_start = chained_pointer_to_vm_offset (info_pointers.read_pointer (), kernel_base);
|
||||
var kmodinfo = new BufferReader (blob.slice (kmodinfo_start, 196, "__kmod_info entry"));
|
||||
|
||||
kmodinfo.skip (blob.pointer_size + 4 + 4);
|
||||
|
||||
string name = kmodinfo.read_fixed_string (64);
|
||||
string version = kmodinfo.read_fixed_string (64);
|
||||
|
||||
kmodinfo.skip (4 + (2 * blob.pointer_size) + 8 + 8);
|
||||
|
||||
size_t start = chained_pointer_to_vm_offset (kmodinfo.read_pointer (), kernel_base);
|
||||
size_t stop = chained_pointer_to_vm_offset (kmodinfo.read_pointer (), kernel_base);
|
||||
|
||||
kexts.add (new ModuleInfo () {
|
||||
name = name,
|
||||
version = version,
|
||||
start_func_offset = (uint32) start,
|
||||
stop_func_offset = (uint32) stop,
|
||||
});
|
||||
}
|
||||
|
||||
var start_offsets = new Gee.ArrayList<uint> ();
|
||||
var start_pointers = new BufferReader (kmod_start);
|
||||
while (start_pointers.available != 0)
|
||||
start_offsets.add ((uint) chained_pointer_to_vm_offset (start_pointers.read_pointer (), kernel_base));
|
||||
if (start_offsets.size != kexts.size + 1)
|
||||
throw new Error.PROTOCOL ("Unexpected __kmod_start length");
|
||||
|
||||
var n = kexts.size;
|
||||
for (var i = 0; i != n; i++) {
|
||||
var kext = kexts[i];
|
||||
kext.offset = start_offsets[i];
|
||||
kext.size = start_offsets[i + 1] - kext.offset;
|
||||
}
|
||||
|
||||
var result = new Gee.ArrayList<ModuleInfo> ();
|
||||
|
||||
result.add (new ModuleInfo () {
|
||||
name = "mach_kernel",
|
||||
version = mod.source_version,
|
||||
offset = 0,
|
||||
size = kexts[0].offset,
|
||||
});
|
||||
result.add_all (kexts);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static size_t chained_pointer_to_vm_offset (uint64 val, uint64 kernel_base) {
|
||||
var target = (size_t) (val & CHAINED_PTR_TARGET_MASK);
|
||||
|
||||
bool is_auth = (val >> 63) != 0;
|
||||
if (is_auth)
|
||||
return target;
|
||||
|
||||
return target - (size_t) (kernel_base & CHAINED_PTR_TARGET_MASK);
|
||||
}
|
||||
|
||||
private static Gee.List<SymbolInfo> compute_symbol_list (Gum.DarwinModule mod) {
|
||||
var symbols = new Gee.ArrayList<SymbolInfo> ();
|
||||
mod.enumerate_symbols (s => {
|
||||
symbols.add (new SymbolInfo () {
|
||||
name = (s.name[0] == '_') ? s.name[1:] : s.name,
|
||||
offset = (uint32) s.address,
|
||||
symbol_type = s.type,
|
||||
section = s.section,
|
||||
description = s.description
|
||||
});
|
||||
return true;
|
||||
});
|
||||
return symbols;
|
||||
}
|
||||
}
|
||||
|
||||
public class ModuleInfo {
|
||||
public string name;
|
||||
public string version;
|
||||
public uint32 offset;
|
||||
public uint32 size;
|
||||
public uint32 start_func_offset;
|
||||
public uint32 stop_func_offset;
|
||||
}
|
||||
|
||||
public class SymbolInfo {
|
||||
public string name;
|
||||
public uint32 offset;
|
||||
public uint8 symbol_type;
|
||||
public uint8 section;
|
||||
public uint16 description;
|
||||
}
|
||||
|
||||
private class Blob {
|
||||
private Bytes bytes;
|
||||
private size_t size;
|
||||
public ByteOrder byte_order;
|
||||
public uint pointer_size;
|
||||
|
||||
public Blob (Bytes b, ByteOrder o, uint ptr_size) {
|
||||
bytes = b;
|
||||
size = b.get_size ();
|
||||
byte_order = o;
|
||||
pointer_size = ptr_size;
|
||||
}
|
||||
|
||||
public Buffer slice (size_t offset, size_t n, string label) throws Error {
|
||||
if (offset >= size)
|
||||
throw new Error.PROTOCOL ("Offset out of bounds while parsing %s", label);
|
||||
|
||||
size_t end = offset + n;
|
||||
if (end > size)
|
||||
throw new Error.PROTOCOL ("Size out of bounds while parsing %s", label);
|
||||
|
||||
return new Buffer (bytes[offset:end], byte_order, pointer_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone.LZFSE {
|
||||
public bool is_lzfse (Bytes buf) {
|
||||
if (buf.get_size () < 4)
|
||||
return false;
|
||||
unowned uint8[] d = buf.get_data ();
|
||||
return d[0] == 'b' && d[1] == 'v' && d[2] == 'x';
|
||||
}
|
||||
|
||||
public Bytes decode (Bytes compressed) throws Error {
|
||||
size_t est_max = compressed.get_size () * 15;
|
||||
uint8[] dst_buf = new uint8[est_max];
|
||||
|
||||
size_t written = decode_buffer (dst_buf, compressed.get_data ());
|
||||
if (written == 0)
|
||||
throw new Error.PROTOCOL ("liblzfse returned 0 bytes");
|
||||
|
||||
return new Bytes (dst_buf[:written]);
|
||||
}
|
||||
|
||||
[CCode (cheader_filename = "lzfse.h", cname = "lzfse_decode_buffer")]
|
||||
private extern size_t decode_buffer ([CCode (array_length_type = "size_t")] uint8[] dst, [CCode (array_length_type = "size_t")] uint8[] src, void * scratch = null);
|
||||
}
|
||||
@@ -16,19 +16,45 @@ namespace Frida.Barebone {
|
||||
|
||||
public abstract async size_t query_page_size (Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public abstract async uint query_exception_level (Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public async void enter_exception_level (uint level, uint timeout, Cancellable? cancellable) throws Error, IOError {
|
||||
var timer = new Timer ();
|
||||
|
||||
do {
|
||||
var el = yield query_exception_level (cancellable);
|
||||
if (el == level)
|
||||
return;
|
||||
|
||||
yield gdb.continue (cancellable);
|
||||
|
||||
var source = new TimeoutSource (10);
|
||||
source.set_callback (enter_exception_level.callback);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
yield;
|
||||
|
||||
yield gdb.stop (cancellable);
|
||||
} while ((uint) (timer.elapsed () * 1000.0) < timeout);
|
||||
|
||||
throw new Error.TIMED_OUT ("Timed out while trying to get target to exception level %u", level);
|
||||
}
|
||||
|
||||
public abstract async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError;
|
||||
|
||||
public abstract async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
|
||||
public abstract async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError;
|
||||
|
||||
public abstract async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public abstract async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern,
|
||||
uint max_matches, Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public Bytes relocate (Gum.ElfModule module, uint64 base_va) throws Error {
|
||||
public Bytes relocate (Gum.ElfModule elf, Bytes raw_elf, uint64 base_va) throws Error {
|
||||
uint64 file_start = uint64.MAX;
|
||||
uint64 file_end = 0;
|
||||
module.enumerate_segments (s => {
|
||||
elf.enumerate_segments (s => {
|
||||
if (s.file_size != 0) {
|
||||
file_start = uint64.min (s.file_offset, file_start);
|
||||
file_end = uint64.max (s.file_offset + s.file_size, file_end);
|
||||
@@ -36,9 +62,9 @@ namespace Frida.Barebone {
|
||||
return true;
|
||||
});
|
||||
|
||||
var relocated_buf = gdb.make_buffer (new Bytes (module.get_file_data ()[file_start:file_end]));
|
||||
var relocated_buf = gdb.make_buffer (new Bytes (raw_elf[(size_t) file_start:(size_t) file_end].get_data ()));
|
||||
Error? pending_error = null;
|
||||
module.enumerate_relocations (r => {
|
||||
elf.enumerate_relocations (r => {
|
||||
unowned string parent_section = (r.parent != null) ? r.parent.name : "";
|
||||
if (parent_section == ".rela.text" || parent_section.has_prefix (".rela.debug_"))
|
||||
return true;
|
||||
@@ -58,15 +84,14 @@ namespace Frida.Barebone {
|
||||
Bytes relocated_bytes = relocated_buf.bytes;
|
||||
Bytes relocated_image = gdb.make_buffer_builder ()
|
||||
.append_bytes (relocated_bytes)
|
||||
.skip ((size_t) (module.mapped_size - relocated_bytes.get_size ()))
|
||||
.skip ((size_t) (elf.mapped_size - relocated_bytes.get_size ()))
|
||||
.build ();
|
||||
return relocated_image;
|
||||
}
|
||||
|
||||
public abstract void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Error;
|
||||
|
||||
public abstract async uint64 invoke (uint64 impl, uint64[] args, uint64 landing_zone, Cancellable? cancellable)
|
||||
throws Error, IOError;
|
||||
public abstract async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public abstract async CallFrame load_call_frame (GDB.Thread thread, uint arity, Cancellable? cancellable)
|
||||
throws Error, IOError;
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
backend_sources += files(
|
||||
'barebone-host-session.vala',
|
||||
'config.vala',
|
||||
'agent-connection.vala',
|
||||
'script.vala',
|
||||
'machine.vala',
|
||||
'allocator.vala',
|
||||
'callback.vala',
|
||||
'memory-scanner.vala',
|
||||
'interceptor.vala',
|
||||
'c-module.vala',
|
||||
'rust-module.vala',
|
||||
'console.vala',
|
||||
'layout.vala',
|
||||
'elf.vala',
|
||||
'img4.vala',
|
||||
'lzfse.vala',
|
||||
'qmp-client.vala',
|
||||
'arch-x86' / 'machine.vala',
|
||||
'arch-x86_64' / 'machine.vala',
|
||||
'arch-arm' / 'machine.vala',
|
||||
@@ -59,4 +68,3 @@ backend_sources += custom_target('frida-data-barebone',
|
||||
|
||||
backend_vala_args += gum_vala_args
|
||||
backend_vala_args_private += '--pkg=quickjs'
|
||||
backend_deps += quickjs_dep
|
||||
Generated
+473
-475
@@ -8,498 +8,496 @@
|
||||
"name": "frida-barebone-script-runtime",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@types/frida-gum": "^19.0.0",
|
||||
"frida-compile": "^17.0.0"
|
||||
"@types/frida-gum": "^19.0.1",
|
||||
"esbuild": "^0.25.5",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/assert": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/assert/-/assert-3.0.2.tgz",
|
||||
"integrity": "sha512-JXJq5SbXGrM5EkjrZKfRmB29zOoEOix02NC6A5TSJ+C1GE/X051EinJJsuOO2pEOx7KZwpvAHvS0WXW0+levKg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/base64-js": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@frida/base64-js/-/base64-js-2.0.3.tgz",
|
||||
"integrity": "sha512-2w0F+1TynOTCZ/v7du9LdHPWwq0lJhazjo2fF9upMyQmA1zHetT14fLuQ1v/6T0qPgyeEGkiSrybstU8EsgeUA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
"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"
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/crosspath": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/crosspath/-/crosspath-3.0.0.tgz",
|
||||
"integrity": "sha512-bNdO1spIPD2P40XtK89N49oZpJhstdlnkJZcD4yJ17jrdkm9Ctu0sd9MIEX6Z8Tm8ydhVJBAOMEKl9/R27onAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "^17.0.36"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/crypto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@frida/crypto/-/crypto-1.0.1.tgz",
|
||||
"integrity": "sha512-WXTkVjESvX8TIVXIynJJv9BKSIdQP0Iis04StIXknkDi1ULdMlrypA9p5C4KW1H6GCkNsVnKbybpzsulYCTvag==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@frida/diagnostics_channel": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/diagnostics_channel/-/diagnostics_channel-1.0.0.tgz",
|
||||
"integrity": "sha512-mYX1jp/5Bpk24tHArJNx65iCk7qSuV8YJkdU0gFNVtJUXxfV8BG5WuPa4mL+ynxsbWWpsg/cwKZbLAepYKTdQQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/events": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@frida/events/-/events-4.0.4.tgz",
|
||||
"integrity": "sha512-qJVQ6VWHf9sjUKuiJzoCAC00frbpcwxeYfvQ+PP9LU/d70j+QvjWgYe98Qa3ekLaBU6r/AvWm8ThKCDUCLWrQQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/http": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/http/-/http-4.0.2.tgz",
|
||||
"integrity": "sha512-cvkc7ex7GmVXVOWqtjXKBWUUbYEBgpNRKZbEEoMeI8KiIs8zejKwg+N7rx7296Ao+EP3+xcUr4wBVr3xLaUVfQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"http-parser-js": "^0.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/http-parser-js": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/http-parser-js/-/http-parser-js-1.0.0.tgz",
|
||||
"integrity": "sha512-2nMrNXt/OeTlWbqnE8AH4Sfz4I2+BGoN206dzKEyC/g2svtn83Xu+zuv/V3TkwrA27s26Mcy84ZwsXeNlqNxUQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/https": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/https/-/https-1.0.0.tgz",
|
||||
"integrity": "sha512-OiqQ6qsALcWOktRLq07oJ0i6sH8eX6MXb/MdZS1qVKDRf6wchH4Pjn6fiLB+pt/OlYbggk+DOfpHwSdjTwuHMQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/ieee754": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/ieee754/-/ieee754-2.0.2.tgz",
|
||||
"integrity": "sha512-wlcUebnne4ENN7GDr5pTH598ZDLMVOsh0FjenxeVOe6u7ewZkz9gGRnLnZKJAm9kl5G6XhdxhI0cSXVQK/rQUw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/@frida/net": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/net/-/net-4.0.2.tgz",
|
||||
"integrity": "sha512-qQRe7hQ+ZfCcG/SE3P1TRqQ9bmuK/T7wPCYaT4z56rBPWAxsaQbQHpX4fR6OrFaSDr7X0xJLsTbdIp9hGhhLZg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/os": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/os/-/os-1.0.2.tgz",
|
||||
"integrity": "sha512-3ISAiGNiyIya3QN2EHBCz1wqP0enTdSxP99wUeroeh8+AQRmgoOr/5TRnrVry8pe378anay3fmV/tdUMMSkehQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/path": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@frida/path/-/path-2.0.3.tgz",
|
||||
"integrity": "sha512-2RQy36QatoC846fzBhBhV8sXMsSOBGoYvwTHeaE1zUdz7F4RNScP4QEekTTooBYWYX/XjiF36KQpYAzc9OYFtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/process": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@frida/process/-/process-1.2.1.tgz",
|
||||
"integrity": "sha512-nvCu22DstFW2ttGFtOKekHM7vnjbZm+XgtvavOt427GNT6uV7k0JYK9tnMbcLMRWv57DG6udAmuJlWs8Paq1ag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/punycode": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/punycode/-/punycode-3.0.0.tgz",
|
||||
"integrity": "sha512-XVSDY2KamDs1D5/fTVgHcOSNxdU4kTboxzqJMBbTjcQC7XScIT9c0EfbwKCq7Kci6gWQdsHSCr7lU+9Oc4KAdg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/querystring": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/querystring/-/querystring-1.0.0.tgz",
|
||||
"integrity": "sha512-15m1fOZPmoO/vWlgPJrG/J9/BJDz6a2/JpVGpS8ynNzo+fBhTznaStX5nHxUs24mVTqh/OqLo0EiYJM5WWHXxg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/readable-stream": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@frida/readable-stream/-/readable-stream-4.1.3.tgz",
|
||||
"integrity": "sha512-ntGUFmi+CryRGRJIK13a/VST2Ad19uivbln8Xd92vKPAARq+6vMIASDyZIqyl5BLRccfiyCHdYgrgQ6RI5rUig==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/reserved-words": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/reserved-words/-/reserved-words-1.0.0.tgz",
|
||||
"integrity": "sha512-2yG/XxJlsGlk/mm6eZTb4OAaQEhkTI2qaFfZFtAsrA/XuCpuMWkS4y/guyBlsRu4hAuhK2HPmNM8+OLLK1zM9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/stream": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/stream/-/stream-1.0.2.tgz",
|
||||
"integrity": "sha512-4OuaC1ztmEKgTq3WeBhsy8Oq+AwW9n9cYnvLklcC9jwD93AEwgbWpecLlxJCVuALvTMdhKPg0nQVfyGYP/i9Bw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@frida/readable-stream": "^4.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/string_decoder": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/string_decoder/-/string_decoder-2.0.0.tgz",
|
||||
"integrity": "sha512-in371tYZMHQiW9HF5MS3JDw6Ao6tyBoq34UWy2rzOswYyMG1rpizh85ofi/yVkxDiaqybEZefxzkVittpPGT6g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/terser": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/terser/-/terser-1.0.0.tgz",
|
||||
"integrity": "sha512-59h9WuNzD1Rx/zwoWqQ/FW/4Y/Q3R91Eng2hEwdHapqiTDvtKbZ08F6CynCR7ZVinrh4tLYsF46AtVPTz1ys9g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
"acorn": "^8.5.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/terser/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/timers": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/timers/-/timers-3.0.0.tgz",
|
||||
"integrity": "sha512-3b+0igv10aT8TMxefrTAd06rActqbxJLY2Xkkq9vYcPBffB/yHszl0NYIp/5ko8WC3ecDYPU6bQiY6fjs72zTA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/tty": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/tty/-/tty-1.0.0.tgz",
|
||||
"integrity": "sha512-p/kjLnKYxEAB1MdYP8+5rKv9CsHzyA+0jg9BcGETzjQVKHHcroHDULRxDYUh+DC7qs6cpX8QdDQh9E+a6ydgsQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/url": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@frida/url/-/url-1.0.2.tgz",
|
||||
"integrity": "sha512-ZKunbKJHMr8w2Eb/5K1avy0MzK1B998S17wYXNv3RmzBGxMm8S5T0F3qEpRxkU7/72P8m4izyQU87fWl+FjQsQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@frida/punycode": "^3.0.0",
|
||||
"@frida/querystring": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@frida/util": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@frida/util/-/util-1.0.3.tgz",
|
||||
"integrity": "sha512-htcG3uDiRXv89ERVNNYhfase39kJ2X75ZARfrYcYEtJLFEsSk0nemM1YnEIR4CjrHvdvkWHrwgKkS+acOyoNEg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@frida/vm": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@frida/vm/-/vm-2.0.0.tgz",
|
||||
"integrity": "sha512-7fsjLWscZT5odNIBtg6qbLNI+vAk1xmii6H5W2kaYkMYt0vRohQEcDSUWacA+eaWlu5SvMjZI82Yibj/3G9pJw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
|
||||
"integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
|
||||
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/frida-gum": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.0.0.tgz",
|
||||
"integrity": "sha512-qViK1KwvPEAkajwSUv0wpZvZLiX8xO4JFFCh8dfYZfzSyOEfoE0KliCP/nJSKIKG6N3hXnPowrvPufjs5fLKAA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "17.0.45",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
|
||||
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/frida-compile": {
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/frida-compile/-/frida-compile-17.0.0.tgz",
|
||||
"integrity": "sha512-ixJRj14qLH/Z0ckFWd5fKeDMm0W2+2WDpSGnLRgpn0nuCQAbDHRCqVCWtPnV8uzPJmA6XOX0pujsMpYe9q9RRw==",
|
||||
"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,
|
||||
"dependencies": {
|
||||
"@frida/assert": "^3.0.1",
|
||||
"@frida/base64-js": "^2.0.3",
|
||||
"@frida/buffer": "^7.0.4",
|
||||
"@frida/crosspath": "^3.0.0",
|
||||
"@frida/crypto": "^1.0.1",
|
||||
"@frida/diagnostics_channel": "^1.0.0",
|
||||
"@frida/events": "^4.0.4",
|
||||
"@frida/http": "^4.0.2",
|
||||
"@frida/http-parser-js": "^1.0.0",
|
||||
"@frida/https": "^1.0.0",
|
||||
"@frida/ieee754": "^2.0.2",
|
||||
"@frida/net": "^4.0.1",
|
||||
"@frida/os": "^1.0.0",
|
||||
"@frida/path": "^2.0.3",
|
||||
"@frida/process": "^1.2.1",
|
||||
"@frida/punycode": "^3.0.0",
|
||||
"@frida/querystring": "^1.0.0",
|
||||
"@frida/readable-stream": "^4.1.3",
|
||||
"@frida/reserved-words": "^1.0.0",
|
||||
"@frida/stream": "^1.0.2",
|
||||
"@frida/string_decoder": "^2.0.0",
|
||||
"@frida/terser": "^1.0.0",
|
||||
"@frida/timers": "^3.0.0",
|
||||
"@frida/tty": "^1.0.0",
|
||||
"@frida/url": "^1.0.2",
|
||||
"@frida/util": "^1.0.3",
|
||||
"@frida/vm": "^2.0.0",
|
||||
"commander": "^11.1.0",
|
||||
"frida-fs": "^6.0.0",
|
||||
"typed-emitter": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"frida-compile": "dist/cli.js"
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/frida-fs": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/frida-fs/-/frida-fs-6.0.0.tgz",
|
||||
"integrity": "sha512-I0fnKupZh7fU2ixwCIjJnEqNdhfm/SC1ZYBMy58EmeqOWSbjw1Dpy99mJ7BJdbk2KNXJeaBZGxDYLtn0J9S+UA==",
|
||||
"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/@types/frida-gum": {
|
||||
"version": "19.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.0.1.tgz",
|
||||
"integrity": "sha512-mp8BpWWkQFJH86ZN5uXZuDiA7KY/+jDknrw0a06x0/+F5ucPwctHrCuV6iT+LLCp/GyICc36ssedF33B1WoLsw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/http-parser-js": {
|
||||
"version": "0.5.8",
|
||||
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
|
||||
"integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"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": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
|
||||
"integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/typed-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
|
||||
"dev": true,
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"rxjs": "*"
|
||||
"@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/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
"name": "frida-barebone-script-runtime",
|
||||
"version": "1.0.0",
|
||||
"description": "Script runtime used by Frida's Barebone backend",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"prepare": "npm run build",
|
||||
"build": "frida-compile script-runtime/entrypoint.ts -o script-runtime.js -c"
|
||||
"build": "esbuild script-runtime/entrypoint.ts --outfile=script-runtime.js --bundle --platform=node --target=es2022 --minify",
|
||||
"check": "tsc",
|
||||
"watch": "tsc -w"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/frida-gum": "^19.0.0",
|
||||
"frida-compile": "^17.0.0"
|
||||
"@types/frida-gum": "^19.0.1",
|
||||
"esbuild": "^0.25.5",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class QmpClient : Object, AsyncInitable {
|
||||
public signal void event (string name, Json.Node? data);
|
||||
|
||||
public string address {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public uint16 port {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private SocketConnection connection;
|
||||
private DataInputStream input;
|
||||
private OutputStream output;
|
||||
|
||||
private bool is_connected = false;
|
||||
private Promise<bool> close_request = new Promise<bool> ();
|
||||
|
||||
private Gee.Map<uint, Promise<Json.Node>> pending_requests = new Gee.HashMap<uint, Promise<Json.Node>> ();
|
||||
private uint next_request_id = 1;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
private const int REQUEST_TIMEOUT_MS = 30000;
|
||||
|
||||
public QmpClient (string? address = null, uint16 port = 0) {
|
||||
Object (
|
||||
address: address ?? "localhost",
|
||||
port: port != 0 ? port : 4444
|
||||
);
|
||||
}
|
||||
|
||||
public static async QmpClient open (string? address = null, uint16 port = 0, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
var client = new QmpClient (address, port);
|
||||
|
||||
try {
|
||||
yield client.init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
|
||||
var connectable = parse_socket_address (address, port, "localhost", 4444);
|
||||
|
||||
try {
|
||||
var client = new SocketClient ();
|
||||
connection = yield client.connect_async (connectable, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("Unable to connect to QMP server: %s", e.message);
|
||||
}
|
||||
|
||||
var socket = connection.socket;
|
||||
if (socket.get_family () != UNIX)
|
||||
Tcp.enable_nodelay (socket);
|
||||
|
||||
input = new DataInputStream (connection.get_input_stream ());
|
||||
input.set_newline_type (DataStreamNewlineType.LF);
|
||||
output = connection.get_output_stream ();
|
||||
|
||||
bool started_message_processing = false;
|
||||
try {
|
||||
is_connected = true;
|
||||
|
||||
string? greeting_line = yield input.read_line_async (Priority.DEFAULT, cancellable);
|
||||
if (greeting_line == null)
|
||||
throw new Error.TRANSPORT ("Connection closed during QMP greeting");
|
||||
|
||||
process_incoming_messages.begin ();
|
||||
started_message_processing = true;
|
||||
|
||||
yield execute_command ("qmp_capabilities", null, cancellable);
|
||||
|
||||
return true;
|
||||
} catch (GLib.Error e) {
|
||||
is_connected = false;
|
||||
if (!started_message_processing)
|
||||
close_request.resolve (true);
|
||||
|
||||
if (e is Error)
|
||||
throw_api_error (e);
|
||||
else
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable = null) throws IOError {
|
||||
io_cancellable.cancel ();
|
||||
|
||||
try {
|
||||
yield close_request.future.wait_async (cancellable);
|
||||
} catch (Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
public async Hostlink open_hostlink (Cancellable? cancellable = null) throws Error, IOError {
|
||||
#if WINDOWS
|
||||
throw new Error.NOT_SUPPORTED ("Missing open_hostlink() for Windows");
|
||||
#else
|
||||
uint64 mmio = (uint64) yield get_qom_property_int ("/machine", "hostlink-mmio", cancellable);
|
||||
uint irq = (uint) yield get_qom_property_int ("/machine", "hostlink-irq", cancellable);
|
||||
string bus = yield get_qom_property_string ("/machine", "hostlink-bus", cancellable);
|
||||
|
||||
int fds[2];
|
||||
if (Posix.socketpair (Posix.AF_UNIX, Posix.SOCK_STREAM, 0, fds) != 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to allocate socketpair");
|
||||
|
||||
Socket local_sock, remote_sock;
|
||||
try {
|
||||
local_sock = new Socket.from_fd (fds[0]);
|
||||
remote_sock = new Socket.from_fd (fds[1]);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
string fd_name = "appfd";
|
||||
yield getfd (fd_name, fds[1], cancellable);
|
||||
|
||||
string chardev = "vserial0";
|
||||
yield add_chardev_from_fd (chardev, fd_name, cancellable);
|
||||
|
||||
yield add_serial_port (chardev, bus, "re.frida.hostlink", "hostlink.port", 1, cancellable);
|
||||
|
||||
return new Hostlink () {
|
||||
connection = SocketConnection.factory_create_connection (local_sock),
|
||||
mmio = mmio,
|
||||
irq = irq,
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
public class Hostlink {
|
||||
public SocketConnection connection;
|
||||
public uint64 mmio;
|
||||
public uint irq;
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
private async int64 get_qom_property_int (string path, string property, Cancellable? cancellable) throws Error, IOError {
|
||||
var val = yield get_qom_property (path, property, cancellable);
|
||||
if (val.get_value_type () != typeof (int64))
|
||||
throw new Error.PROTOCOL ("Expected '%s' property on %s to be an integer", property, path);
|
||||
return val.get_int ();
|
||||
}
|
||||
|
||||
private async string get_qom_property_string (string path, string property, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var val = yield get_qom_property (path, property, cancellable);
|
||||
if (val.get_value_type () != typeof (string))
|
||||
throw new Error.PROTOCOL ("Expected '%s' property on %s to be a string", property, path);
|
||||
return val.get_string ();
|
||||
}
|
||||
|
||||
private async Json.Node get_qom_property (string path, string property, Cancellable? cancellable) throws Error, IOError {
|
||||
var args = new Json.Builder ();
|
||||
args
|
||||
.begin_object ()
|
||||
.set_member_name ("path")
|
||||
.add_string_value (path)
|
||||
.set_member_name ("property")
|
||||
.add_string_value (property)
|
||||
.end_object ();
|
||||
return yield execute_command ("qom-get", args.get_root (), cancellable);
|
||||
}
|
||||
|
||||
private async void getfd (string name, int fd, Cancellable? cancellable) throws Error, IOError {
|
||||
var args = new Json.Builder ();
|
||||
args
|
||||
.begin_object ()
|
||||
.set_member_name ("fdname")
|
||||
.add_string_value (name)
|
||||
.end_object ();
|
||||
yield execute_command_with_file_descriptor ("getfd", args.get_root (), fd, cancellable);
|
||||
}
|
||||
|
||||
private async void add_chardev_from_fd (string id, string fd_name, Cancellable? cancellable) throws Error, IOError {
|
||||
var args = new Json.Builder ();
|
||||
args
|
||||
.begin_object ()
|
||||
.set_member_name ("id")
|
||||
.add_string_value (id)
|
||||
.set_member_name ("backend")
|
||||
.begin_object ()
|
||||
.set_member_name ("type")
|
||||
.add_string_value ("socket")
|
||||
.set_member_name ("data")
|
||||
.begin_object ()
|
||||
.set_member_name ("server")
|
||||
.add_boolean_value (false)
|
||||
.set_member_name ("addr")
|
||||
.begin_object ()
|
||||
.set_member_name ("type")
|
||||
.add_string_value ("fd")
|
||||
.set_member_name ("data")
|
||||
.begin_object ()
|
||||
.set_member_name ("str")
|
||||
.add_string_value (fd_name)
|
||||
.end_object ()
|
||||
.end_object ()
|
||||
.end_object ()
|
||||
.end_object ()
|
||||
.end_object ();
|
||||
yield execute_command ("chardev-add", args.get_root (), cancellable);
|
||||
}
|
||||
|
||||
private async void add_serial_port (string chardev, string bus, string name, string id, uint nr, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var args = new Json.Builder ();
|
||||
args
|
||||
.begin_object ()
|
||||
.set_member_name ("driver")
|
||||
.add_string_value ("virtserialport")
|
||||
.set_member_name ("chardev")
|
||||
.add_string_value (chardev)
|
||||
.set_member_name ("bus")
|
||||
.add_string_value (bus)
|
||||
.set_member_name ("name")
|
||||
.add_string_value (name)
|
||||
.set_member_name ("id")
|
||||
.add_string_value (id)
|
||||
.set_member_name ("nr")
|
||||
.add_int_value ((int64) nr)
|
||||
.end_object ();
|
||||
yield execute_command ("device_add", args.get_root (), cancellable);
|
||||
}
|
||||
#endif
|
||||
|
||||
public async Json.Node execute_command (string command, Json.Node? arguments = null, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
check_connected ();
|
||||
|
||||
Request request = begin_request (command, arguments);
|
||||
|
||||
try {
|
||||
yield output.write_all_async (request.json.data, Priority.DEFAULT, cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
cancel_request (request);
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
return yield join_request (request, cancellable);
|
||||
}
|
||||
|
||||
public async Json.Node execute_command_with_file_descriptor (string command, Json.Node? arguments = null, int fd,
|
||||
Cancellable? cancellable = null) throws Error, IOError {
|
||||
check_connected ();
|
||||
|
||||
#if WINDOWS
|
||||
throw new Error.NOT_SUPPORTED ("Executing command with SocketControlMessage is not supported on Windows");
|
||||
#else
|
||||
Request request = begin_request (command, arguments);
|
||||
|
||||
var scm = new UnixFDMessage ();
|
||||
try {
|
||||
scm.append_fd (fd);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
|
||||
Socket s = connection.get_socket ();
|
||||
|
||||
unowned uint8[] raw_command = request.json.data;
|
||||
|
||||
OutputVector vectors[1] = {
|
||||
OutputVector () {
|
||||
buffer = raw_command,
|
||||
size = raw_command.length,
|
||||
},
|
||||
};
|
||||
SocketControlMessage scms[1] = { scm };
|
||||
|
||||
ssize_t n;
|
||||
try {
|
||||
n = s.send_message (null, vectors, scms, 0, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
cancel_request (request);
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
if (n != raw_command.length) {
|
||||
try {
|
||||
yield output.write_all_async (raw_command[n:], Priority.DEFAULT, cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
cancel_request (request);
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
return yield join_request (request, cancellable);
|
||||
#endif
|
||||
}
|
||||
|
||||
private Request begin_request (string command, Json.Node? arguments) {
|
||||
uint id = next_request_id++;
|
||||
|
||||
var promise = new Promise<Json.Node> ();
|
||||
pending_requests[id] = promise;
|
||||
|
||||
string json = build_request (command, id, arguments);
|
||||
|
||||
return new Request () {
|
||||
promise = promise,
|
||||
json = json,
|
||||
id = id,
|
||||
};
|
||||
}
|
||||
|
||||
private static string build_request (string command, uint id, Json.Node? arguments) {
|
||||
var b = new Json.Builder ();
|
||||
b
|
||||
.begin_object ()
|
||||
.set_member_name ("execute")
|
||||
.add_string_value (command);
|
||||
|
||||
if (arguments != null) {
|
||||
b
|
||||
.set_member_name ("arguments")
|
||||
.add_value (arguments);
|
||||
}
|
||||
|
||||
b
|
||||
.set_member_name ("id")
|
||||
.add_int_value ((int64) id);
|
||||
|
||||
Json.Node message = b.end_object ().get_root ();
|
||||
|
||||
return Json.to_string (message, false) + "\n";
|
||||
}
|
||||
|
||||
private async Json.Node join_request (Request request, Cancellable? cancellable) throws Error, IOError {
|
||||
var timeout_source = new TimeoutSource (REQUEST_TIMEOUT_MS);
|
||||
timeout_source.set_callback (() => {
|
||||
Promise<Json.Node>? p;
|
||||
if (pending_requests.unset (request.id, out p))
|
||||
p.reject (new Error.TIMED_OUT ("QMP command timed out"));
|
||||
return Source.REMOVE;
|
||||
});
|
||||
timeout_source.attach (MainContext.get_thread_default ());
|
||||
|
||||
try {
|
||||
return yield request.promise.future.wait_async (cancellable);
|
||||
} finally {
|
||||
timeout_source.destroy ();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancel_request (Request r) {
|
||||
pending_requests.unset (r.id);
|
||||
}
|
||||
|
||||
private class Request {
|
||||
public Promise<Json.Node> promise;
|
||||
public string json;
|
||||
public uint id;
|
||||
}
|
||||
|
||||
private void check_connected () throws Error {
|
||||
if (!is_connected)
|
||||
throw new Error.INVALID_OPERATION ("QMP client is not connected");
|
||||
}
|
||||
|
||||
private async void process_incoming_messages () {
|
||||
try {
|
||||
while (is_connected) {
|
||||
string? line = yield input.read_line_async (Priority.DEFAULT, io_cancellable);
|
||||
if (line == null)
|
||||
break;
|
||||
|
||||
handle_message (Json.from_string (line));
|
||||
}
|
||||
} catch (GLib.Error e) {
|
||||
} finally {
|
||||
is_connected = false;
|
||||
|
||||
foreach (var promise in pending_requests.values)
|
||||
promise.reject (new Error.TRANSPORT ("QMP connection closed"));
|
||||
pending_requests.clear ();
|
||||
}
|
||||
|
||||
io_cancellable.cancel ();
|
||||
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (process_incoming_messages.callback);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
yield;
|
||||
|
||||
try {
|
||||
yield connection.close_async ();
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
|
||||
close_request.resolve (true);
|
||||
}
|
||||
|
||||
private void handle_message (Json.Node message) throws Error {
|
||||
var r = new Json.Reader (message);
|
||||
|
||||
bool is_response = r.read_member ("id");
|
||||
uint id = 0;
|
||||
if (is_response) {
|
||||
id = (uint) r.get_int_value ();
|
||||
GLib.Error? e = r.get_error ();
|
||||
if (e != null)
|
||||
throw new Error.PROTOCOL ("Malformed message: %s", e.message);
|
||||
}
|
||||
r.end_member ();
|
||||
|
||||
if (is_response)
|
||||
handle_response (id, r);
|
||||
else
|
||||
handle_event (r);
|
||||
}
|
||||
|
||||
private void handle_response (uint request_id, Json.Reader r) throws Error {
|
||||
Promise<Json.Node>? promise;
|
||||
if (!pending_requests.unset (request_id, out promise))
|
||||
return;
|
||||
|
||||
if (r.read_member ("error")) {
|
||||
r.read_member ("desc");
|
||||
string? description = r.get_string_value ();
|
||||
if (description == null)
|
||||
throw new Error.PROTOCOL ("Malformed message: %s", r.get_error ().message);
|
||||
r.end_member ();
|
||||
|
||||
promise.reject (new Error.NOT_SUPPORTED ("%s", description));
|
||||
return;
|
||||
}
|
||||
r.end_member ();
|
||||
|
||||
r.read_member ("return");
|
||||
promise.resolve (r.get_current_node ());
|
||||
}
|
||||
|
||||
private void handle_event (Json.Reader r) throws Error {
|
||||
r.read_member ("event");
|
||||
string? name = r.get_string_value ();
|
||||
if (name == null)
|
||||
throw new Error.PROTOCOL ("Malformed event message: missing 'event' property");
|
||||
r.end_member ();
|
||||
|
||||
Json.Node? data = null;
|
||||
if (r.read_member ("data")) {
|
||||
if (!r.is_object ())
|
||||
throw new Error.PROTOCOL ("Malformed event message: 'data' must be an object");
|
||||
data = r.get_current_node ();
|
||||
}
|
||||
r.end_member ();
|
||||
|
||||
event (name, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,23 +84,15 @@ namespace Frida.Barebone {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
|
||||
size_t vm_size = (size_t) elf.mapped_size;
|
||||
var raw_elf = new Bytes.static (elf.get_file_data ());
|
||||
|
||||
size_t page_size = yield machine.query_page_size (cancellable);
|
||||
uint num_pages = (uint) (vm_size / page_size);
|
||||
if (vm_size % page_size != 0)
|
||||
num_pages++;
|
||||
|
||||
var gdb = machine.gdb;
|
||||
allocation = yield inject_elf (elf, raw_elf, page_size, machine, allocator, cancellable);
|
||||
|
||||
allocation = yield allocator.allocate (num_pages * page_size, page_size, cancellable);
|
||||
uint64 base_va = allocation.virtual_address;
|
||||
|
||||
Bytes relocated_image = machine.relocate (elf, base_va);
|
||||
yield gdb.write_byte_array (base_va, relocated_image, cancellable);
|
||||
|
||||
uint64 console_log_trap = 0;
|
||||
elf.enumerate_dynamic_symbols (e => {
|
||||
elf.enumerate_symbols (e => {
|
||||
if (e.name == "")
|
||||
return true;
|
||||
|
||||
@@ -116,38 +108,14 @@ namespace Frida.Barebone {
|
||||
});
|
||||
|
||||
if (console_log_trap != 0) {
|
||||
console_log_callback = yield new Callback (console_log_trap, new ConsoleLogHandler (this, gdb),
|
||||
machine, cancellable);
|
||||
var handler = new ConsoleLogHandler (machine.gdb);
|
||||
handler.output.connect (on_console_output);
|
||||
console_log_callback = yield new Callback (console_log_trap, handler, machine, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
private class ConsoleLogHandler : Object, CallbackHandler {
|
||||
public uint arity {
|
||||
get { return 2; }
|
||||
}
|
||||
|
||||
private weak RustModule parent;
|
||||
private GDB.Client gdb;
|
||||
|
||||
public ConsoleLogHandler (RustModule parent, GDB.Client gdb) {
|
||||
this.parent = parent;
|
||||
this.gdb = gdb;
|
||||
}
|
||||
|
||||
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var message = args[0];
|
||||
var len = (long) args[1];
|
||||
|
||||
Bytes str_bytes = yield gdb.read_byte_array (message, len, cancellable);
|
||||
unowned uint8[] str_data = str_bytes.get_data ();
|
||||
unowned string str_raw = (string) str_data;
|
||||
string str = str_raw.substring (0, len);
|
||||
|
||||
parent.console_output (str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
private void on_console_output (string message) {
|
||||
console_output (message);
|
||||
}
|
||||
|
||||
private const string CRATE_NAME = "rustmodule";
|
||||
@@ -201,14 +169,17 @@ namespace Frida.Barebone {
|
||||
.resolve_relative_path (CRATE_NAME);
|
||||
}
|
||||
|
||||
yield write_text_file (workdir, "module.lds", make_linker_script (symbols), cancellable);
|
||||
yield write_text_file (workdir, "module.lds", make_linker_script (code, symbols), cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
~CompilationAssets () {
|
||||
rmtree (workdir);
|
||||
try {
|
||||
FS.rmtree (workdir);
|
||||
} catch (Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private static string make_main_rs (string code, Machine machine) {
|
||||
@@ -267,8 +238,8 @@ namespace Frida.Barebone {
|
||||
return rs.str;
|
||||
}
|
||||
|
||||
private static string make_linker_script (Gee.Map<string, uint64?> symbols) {
|
||||
var script = new StringBuilder.sized (256);
|
||||
private static string make_linker_script (string code, Gee.Map<string, uint64?> symbols) {
|
||||
var script = new StringBuilder.sized (512);
|
||||
|
||||
foreach (var e in symbols.entries) {
|
||||
unowned string name = e.key;
|
||||
@@ -279,15 +250,16 @@ namespace Frida.Barebone {
|
||||
.append_printf ("0x%" + uint64.FORMAT_MODIFIER + "x;\n", address);
|
||||
}
|
||||
|
||||
script.append (prettify_text_asset (BASE_LINKER_SCRIPT));
|
||||
|
||||
return script.str;
|
||||
}
|
||||
|
||||
private const string BASE_LINKER_SCRIPT = """
|
||||
script.append ("""
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text*);
|
||||
""");
|
||||
|
||||
foreach (string name in extract_extern_c_symbols (code))
|
||||
script.append_printf (" KEEP(*(.text.%s))\n", name);
|
||||
|
||||
script.append ("""
|
||||
_console_log = .;
|
||||
. += 8;
|
||||
}
|
||||
@@ -304,7 +276,25 @@ namespace Frida.Barebone {
|
||||
*(.bss*)
|
||||
}
|
||||
}
|
||||
""";
|
||||
""");
|
||||
|
||||
return prettify_text_asset (script.str);
|
||||
}
|
||||
|
||||
private static Gee.List<string> extract_extern_c_symbols (string code) {
|
||||
var names = new Gee.ArrayList<string> ();
|
||||
|
||||
var export_pattern = /\#\[\s*no_mangle\s*\]\s*pub\s+(?:unsafe\s+)?extern\s*"C"\s+fn\s+([A-Za-z_]\w*)\s*\(/;
|
||||
|
||||
try {
|
||||
MatchInfo info;
|
||||
for (export_pattern.match (code, 0, out info); info.matches (); info.next ())
|
||||
names.add (info.fetch (1));
|
||||
} catch (RegexError e) {
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private const string BUILTINS = """
|
||||
#![no_main]
|
||||
@@ -428,23 +418,4 @@ namespace Frida.Barebone {
|
||||
yield file.replace_contents_async (content.data, null, false, FileCreateFlags.NONE, cancellable, null);
|
||||
return file;
|
||||
}
|
||||
|
||||
private void rmtree (File dir) {
|
||||
try {
|
||||
var enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS);
|
||||
FileInfo? info;
|
||||
File? child;
|
||||
while (enumerator.iterate (out info, out child) && info != null) {
|
||||
if (info == null)
|
||||
continue;
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
rmtree (child);
|
||||
else
|
||||
child.delete ();
|
||||
}
|
||||
|
||||
dir.delete ();
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,24 +210,6 @@ Object.defineProperties(globalThis, {
|
||||
}
|
||||
}
|
||||
},
|
||||
ObjC: {
|
||||
enumerable: true,
|
||||
value: {
|
||||
available: false
|
||||
}
|
||||
},
|
||||
Swift: {
|
||||
enumerable: true,
|
||||
value: {
|
||||
available: false
|
||||
}
|
||||
},
|
||||
Java: {
|
||||
enumerable: true,
|
||||
value: {
|
||||
available: false
|
||||
}
|
||||
},
|
||||
console: {
|
||||
enumerable: true,
|
||||
value: new Console()
|
||||
@@ -322,6 +304,13 @@ Object.defineProperties(Process, {
|
||||
});
|
||||
|
||||
const marshalers: { [type: string]: Marshaler } = {
|
||||
void: {
|
||||
fromNative(v) {
|
||||
},
|
||||
toNative(v) {
|
||||
throw new Error("invalid operation");
|
||||
}
|
||||
},
|
||||
pointer: {
|
||||
fromNative(v) {
|
||||
return new BNativePointer(v);
|
||||
@@ -356,6 +345,28 @@ const marshalers: { [type: string]: Marshaler } = {
|
||||
return BigInt(v);
|
||||
}
|
||||
},
|
||||
uint: {
|
||||
fromNative(v) {
|
||||
return Number(v);
|
||||
},
|
||||
toNative(v) {
|
||||
if (typeof v !== "number") {
|
||||
throw new Error("expected an integer");
|
||||
}
|
||||
return BigInt(v);
|
||||
}
|
||||
},
|
||||
size_t: {
|
||||
fromNative(v) {
|
||||
return new BUInt64(v);
|
||||
},
|
||||
toNative(v) {
|
||||
if (typeof v === "number") {
|
||||
return BigInt(v);
|
||||
}
|
||||
return v.$v;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function getMarshalerFor(t: NativeFunctionReturnType | NativeFunctionArgumentType): Marshaler {
|
||||
|
||||
+276
-47
@@ -42,8 +42,6 @@ namespace Frida {
|
||||
|
||||
private Gee.Queue<QuickJS.Value?> tick_callbacks = new Gee.ArrayQueue<QuickJS.Value?> ();
|
||||
|
||||
private Barebone.Allocation? cached_landing_zone; // TODO: Deallocate on teardown.
|
||||
|
||||
private Gee.Set<Barebone.Callback> native_callbacks = new Gee.HashSet<Barebone.Callback> ();
|
||||
|
||||
private static QuickJS.ClassID cpu_context_class;
|
||||
@@ -56,6 +54,9 @@ namespace Frida {
|
||||
private static QuickJS.ClassExoticMethods invocation_args_exotic_methods;
|
||||
private static QuickJS.ClassID invocation_retval_class;
|
||||
|
||||
private static QuickJS.ClassID c_module_class;
|
||||
private Gee.Set<Barebone.CModule> c_modules = new Gee.HashSet<Barebone.CModule> ();
|
||||
|
||||
private static QuickJS.ClassID rust_module_class;
|
||||
private Gee.Set<Barebone.RustModule> rust_modules = new Gee.HashSet<Barebone.RustModule> ();
|
||||
|
||||
@@ -72,7 +73,7 @@ namespace Frida {
|
||||
private QuickJS.Value int64_func = QuickJS.Undefined;
|
||||
private QuickJS.Value uint64_func = QuickJS.Undefined;
|
||||
|
||||
private Gee.ArrayList<QuickJS.Value?> entrypoints = new Gee.ArrayList<QuickJS.Value?> ();
|
||||
private Gee.Queue<Entrypoint> entrypoints = new Gee.ArrayQueue<Entrypoint> ();
|
||||
private Gee.Map<string, Asset> assets = new Gee.HashMap<string, Asset> ();
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
@@ -155,6 +156,7 @@ namespace Frida {
|
||||
|
||||
var memory_obj = ctx.make_object ();
|
||||
add_cfunc (memory_obj, "alloc", on_memory_alloc, 1);
|
||||
add_cfunc (memory_obj, "protect", on_memory_protect, 3);
|
||||
add_cfunc (memory_obj, "scan", on_memory_scan, 4);
|
||||
add_cfunc (memory_obj, "scanSync", on_memory_scan_sync, 3);
|
||||
global.set_property_str (ctx, "Memory", memory_obj);
|
||||
@@ -206,6 +208,17 @@ namespace Frida {
|
||||
ir.class_name = "InvocationReturnValue";
|
||||
rt.make_class (QuickJS.make_class_id (ref invocation_retval_class), ir);
|
||||
|
||||
QuickJS.ClassDef cm;
|
||||
cm.class_name = "CModule";
|
||||
cm.finalizer = on_c_module_finalize;
|
||||
rt.make_class (QuickJS.make_class_id (ref c_module_class), cm);
|
||||
var cm_proto = ctx.make_object ();
|
||||
add_cfunc (cm_proto, "dispose", on_c_module_dispose, 0);
|
||||
ctx.set_class_proto (c_module_class, cm_proto);
|
||||
var cm_ctor = ctx.make_cfunction2 (on_c_module_construct, cm.class_name, 1, constructor, 0);
|
||||
cm_ctor.set_constructor (ctx, cm_proto);
|
||||
global.set_property_str (ctx, "CModule", cm_ctor);
|
||||
|
||||
QuickJS.ClassDef rm;
|
||||
rm.class_name = "RustModule";
|
||||
rm.finalizer = on_rust_module_finalize;
|
||||
@@ -328,6 +341,8 @@ namespace Frida {
|
||||
foreach (var val in values)
|
||||
ctx.free_value (val);
|
||||
|
||||
entrypoints.clear ();
|
||||
|
||||
QuickJS.Atom atoms[] = {
|
||||
address_key,
|
||||
base_key,
|
||||
@@ -376,48 +391,65 @@ namespace Frida {
|
||||
yield;
|
||||
}
|
||||
|
||||
public void load () {
|
||||
foreach (QuickJS.Value? entrypoint in entrypoints) {
|
||||
var result = ctx.eval_function (entrypoint);
|
||||
if (result.is_exception ())
|
||||
public async void load (Cancellable? cancellable) throws IOError {
|
||||
Entrypoint? entrypoint;
|
||||
while ((entrypoint = entrypoints.poll ()) != null) {
|
||||
var result = ctx.eval_function (ctx.dup_value (entrypoint.callable));
|
||||
if (result.is_exception ()) {
|
||||
catch_and_emit ();
|
||||
ctx.free_value (result);
|
||||
|
||||
if (runtime_obj.is_undefined ()) {
|
||||
runtime_obj = global.get_property_str (ctx, "$rt");
|
||||
if (!runtime_obj.is_undefined ()) {
|
||||
dispatch_exception_func = runtime_obj.get_property_str (ctx, "dispatchException");
|
||||
assert (!dispatch_exception_func.is_undefined ());
|
||||
|
||||
dispatch_message_func = runtime_obj.get_property_str (ctx, "dispatchMessage");
|
||||
assert (!dispatch_message_func.is_undefined ());
|
||||
|
||||
var native_pointer_instance = global.get_property_str (ctx, "NULL");
|
||||
assert (!native_pointer_instance.is_undefined ());
|
||||
var native_pointer_proto = native_pointer_instance.get_prototype (ctx);
|
||||
|
||||
var ir_proto = ctx.make_object_proto (native_pointer_proto);
|
||||
add_cfunc (ir_proto, "replace", on_invocation_retval_replace, 1);
|
||||
ctx.set_class_proto (invocation_retval_class, ir_proto);
|
||||
|
||||
ctx.free_value (native_pointer_proto);
|
||||
ctx.free_value (native_pointer_instance);
|
||||
|
||||
ptr_func = global.get_property_str (ctx, "ptr");
|
||||
assert (!ptr_func.is_undefined ());
|
||||
|
||||
int64_func = global.get_property_str (ctx, "int64");
|
||||
assert (!int64_func.is_undefined ());
|
||||
|
||||
uint64_func = global.get_property_str (ctx, "uint64");
|
||||
assert (!uint64_func.is_undefined ());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entrypoint.kind == ESM) {
|
||||
var op = new PromiseWaitOperation (this, result);
|
||||
var r = yield op.perform (cancellable);
|
||||
if (!r.error.is_null ())
|
||||
on_unhandled_exception (r.error);
|
||||
} else {
|
||||
ctx.free_value (result);
|
||||
}
|
||||
|
||||
maybe_bind_runtime ();
|
||||
}
|
||||
|
||||
perform_pending_io ();
|
||||
}
|
||||
|
||||
private void maybe_bind_runtime () {
|
||||
if (!runtime_obj.is_undefined ())
|
||||
return;
|
||||
|
||||
runtime_obj = global.get_property_str (ctx, "$rt");
|
||||
if (runtime_obj.is_undefined ())
|
||||
return;
|
||||
|
||||
dispatch_exception_func = runtime_obj.get_property_str (ctx, "dispatchException");
|
||||
assert (!dispatch_exception_func.is_undefined ());
|
||||
|
||||
dispatch_message_func = runtime_obj.get_property_str (ctx, "dispatchMessage");
|
||||
assert (!dispatch_message_func.is_undefined ());
|
||||
|
||||
var native_pointer_instance = global.get_property_str (ctx, "NULL");
|
||||
assert (!native_pointer_instance.is_undefined ());
|
||||
var native_pointer_proto = native_pointer_instance.get_prototype (ctx);
|
||||
|
||||
var ir_proto = ctx.make_object_proto (native_pointer_proto);
|
||||
add_cfunc (ir_proto, "replace", on_invocation_retval_replace, 1);
|
||||
ctx.set_class_proto (invocation_retval_class, ir_proto);
|
||||
|
||||
ctx.free_value (native_pointer_proto);
|
||||
ctx.free_value (native_pointer_instance);
|
||||
|
||||
ptr_func = global.get_property_str (ctx, "ptr");
|
||||
assert (!ptr_func.is_undefined ());
|
||||
|
||||
int64_func = global.get_property_str (ctx, "int64");
|
||||
assert (!int64_func.is_undefined ());
|
||||
|
||||
uint64_func = global.get_property_str (ctx, "uint64");
|
||||
assert (!uint64_func.is_undefined ());
|
||||
}
|
||||
|
||||
public void post (string json, Bytes? data) {
|
||||
var json_val = ctx.make_string (json);
|
||||
var data_val = (data != null) ? ctx.make_array_buffer (data.get_data ()) : QuickJS.Null;
|
||||
@@ -495,7 +527,7 @@ namespace Frida {
|
||||
throw_malformed_package ();
|
||||
|
||||
var val = compile_module (entrypoint);
|
||||
entrypoints.add (val);
|
||||
entrypoints.offer (new Entrypoint (this, val, ESM));
|
||||
|
||||
string rest = raw_assets[assets_offset:];
|
||||
if (rest.has_prefix (delimiter_marker))
|
||||
@@ -507,7 +539,7 @@ namespace Frida {
|
||||
}
|
||||
} else {
|
||||
var val = compile_script (source, name);
|
||||
entrypoints.add (val);
|
||||
entrypoints.offer (new Entrypoint (this, val, PLAIN));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,11 +685,7 @@ namespace Frida {
|
||||
|
||||
private async void do_invoke (uint64 impl, uint64[] args, Promise<uint64?> promise) {
|
||||
try {
|
||||
if (cached_landing_zone == null)
|
||||
cached_landing_zone = yield services.allocator.allocate (4, 1, io_cancellable);
|
||||
|
||||
uint64 retval = yield services.machine.invoke (impl, args, cached_landing_zone.virtual_address,
|
||||
io_cancellable);
|
||||
uint64 retval = yield services.machine.invoke (impl, args, io_cancellable);
|
||||
|
||||
promise.resolve (retval);
|
||||
} catch (GLib.Error e) {
|
||||
@@ -708,6 +736,111 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private class Entrypoint {
|
||||
private weak BareboneScript script;
|
||||
public QuickJS.Value callable;
|
||||
public Kind kind;
|
||||
|
||||
public enum Kind {
|
||||
PLAIN,
|
||||
ESM
|
||||
}
|
||||
|
||||
public Entrypoint (BareboneScript script, QuickJS.Value callable, Kind kind) {
|
||||
this.script = script;
|
||||
this.callable = callable;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
~Entrypoint () {
|
||||
script.ctx.free_value (callable);
|
||||
}
|
||||
}
|
||||
|
||||
private class PromiseWaitOperation {
|
||||
private BareboneScript script;
|
||||
private QuickJS.Value promise;
|
||||
|
||||
private enum Magic {
|
||||
ON_SUCCESS,
|
||||
ON_FAILURE,
|
||||
}
|
||||
|
||||
public PromiseWaitOperation (BareboneScript script, QuickJS.Value promise) {
|
||||
this.script = script;
|
||||
this.promise = promise;
|
||||
}
|
||||
|
||||
~PromiseWaitOperation () {
|
||||
script.ctx.free_value (promise);
|
||||
}
|
||||
|
||||
public async Result perform (Cancellable? cancellable) throws IOError {
|
||||
var res = new Result (script, perform.callback);
|
||||
|
||||
unowned QuickJS.Context ctx = script.ctx;
|
||||
|
||||
var data = ctx.make_object ();
|
||||
data.set_opaque (res);
|
||||
|
||||
var on_success = ctx.make_cfunction_data (on_settled, 1, Magic.ON_SUCCESS, { data });
|
||||
var on_failure = ctx.make_cfunction_data (on_settled, 1, Magic.ON_FAILURE, { data });
|
||||
|
||||
var then_func = promise.get_property_str (ctx, "then");
|
||||
var catch_func = promise.get_property_str (ctx, "catch");
|
||||
|
||||
ctx.free_value (then_func.call (ctx, promise, { on_success }));
|
||||
ctx.free_value (catch_func.call (ctx, promise, { on_failure }));
|
||||
|
||||
ctx.free_value (catch_func);
|
||||
ctx.free_value (then_func);
|
||||
|
||||
ctx.free_value (on_failure);
|
||||
ctx.free_value (on_success);
|
||||
|
||||
script.perform_pending_io ();
|
||||
yield;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static QuickJS.Value on_settled (QuickJS.Context ctx, QuickJS.Value this_val, QuickJS.Value[] argv, int magic,
|
||||
QuickJS.Value[] data) {
|
||||
QuickJS.ClassID cid;
|
||||
unowned Result res = (Result) data[0].get_any_opaque (out cid);
|
||||
|
||||
if (magic == Magic.ON_SUCCESS)
|
||||
res.val = ctx.dup_value (argv[0]);
|
||||
else
|
||||
res.error = ctx.dup_value (argv[0]);
|
||||
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) res.on_complete);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
|
||||
return QuickJS.Undefined;
|
||||
}
|
||||
|
||||
public class Result {
|
||||
public BareboneScript script;
|
||||
public SourceFunc on_complete;
|
||||
|
||||
public QuickJS.Value val = QuickJS.Null;
|
||||
public QuickJS.Value error = QuickJS.Null;
|
||||
|
||||
public Result (BareboneScript script, owned SourceFunc on_complete) {
|
||||
this.script = script;
|
||||
this.on_complete = (owned) on_complete;
|
||||
}
|
||||
|
||||
~Result () {
|
||||
unowned QuickJS.Context ctx = script.ctx;
|
||||
ctx.free_value (val);
|
||||
ctx.free_value (error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NativeCallbackHandler : Object, Barebone.CallbackHandler {
|
||||
public uint arity {
|
||||
get { return _arity; }
|
||||
@@ -1000,6 +1133,41 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private static QuickJS.Value on_memory_protect (QuickJS.Context ctx, QuickJS.Value this_val, QuickJS.Value[] argv) {
|
||||
BareboneScript * script = ctx.get_opaque ();
|
||||
|
||||
uint64 address;
|
||||
if (!script->unparse_native_pointer (argv[0], out address))
|
||||
return QuickJS.Exception;
|
||||
|
||||
uint size;
|
||||
if (!script->unparse_uint (argv[1], out size))
|
||||
return QuickJS.Exception;
|
||||
|
||||
Gum.PageProtection prot;
|
||||
if (!script->unparse_page_protection (argv[2], out prot))
|
||||
return QuickJS.Exception;
|
||||
|
||||
var promise = new Promise<bool?> ();
|
||||
script->do_memory_protect.begin (address, size, prot, promise);
|
||||
|
||||
bool? result = script->process_events_until_ready (promise);
|
||||
if (result == null)
|
||||
return QuickJS.Exception;
|
||||
|
||||
return ctx.make_bool (result);
|
||||
}
|
||||
|
||||
private async void do_memory_protect (uint64 address, size_t size, Gum.PageProtection prot, Promise<bool?> promise) {
|
||||
try {
|
||||
yield services.machine.protect_pages (address, size, prot, io_cancellable);
|
||||
|
||||
promise.resolve (true);
|
||||
} catch (GLib.Error e) {
|
||||
promise.resolve (false);
|
||||
}
|
||||
}
|
||||
|
||||
private static QuickJS.Value on_memory_scan (QuickJS.Context ctx, QuickJS.Value this_val, QuickJS.Value[] argv) {
|
||||
BareboneScript * script = ctx.get_opaque ();
|
||||
|
||||
@@ -1667,6 +1835,67 @@ namespace Frida {
|
||||
return QuickJS.Undefined;
|
||||
}
|
||||
|
||||
private static QuickJS.Value on_c_module_construct (QuickJS.Context ctx, QuickJS.Value new_target,
|
||||
QuickJS.Value[] argv) {
|
||||
BareboneScript * script = ctx.get_opaque ();
|
||||
|
||||
Bytes blob;
|
||||
if (!script->unparse_bytes (argv[0], out blob))
|
||||
return QuickJS.Exception;
|
||||
|
||||
var promise = new Promise<Barebone.CModule> ();
|
||||
script->load_c_module.begin (blob, promise);
|
||||
|
||||
Barebone.CModule? mod = script->process_events_until_ready (promise);
|
||||
if (mod == null)
|
||||
return QuickJS.Exception;
|
||||
|
||||
var proto = new_target.get_property (ctx, script->prototype_key);
|
||||
var wrapper = ctx.make_object_with_proto_and_class (proto, c_module_class);
|
||||
ctx.free_value (proto);
|
||||
|
||||
wrapper.set_opaque (mod);
|
||||
script->c_modules.add (mod);
|
||||
|
||||
foreach (var e in mod.exports)
|
||||
wrapper.set_property_str (ctx, e.name, script->make_native_pointer (e.address));
|
||||
|
||||
mod.console_output.connect (script->on_console_output);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private async void load_c_module (Bytes blob, Promise<Barebone.CModule> promise) {
|
||||
try {
|
||||
var mod = yield new Barebone.CModule.from_blob (blob, services.machine, services.allocator, io_cancellable);
|
||||
|
||||
promise.resolve (mod);
|
||||
} catch (GLib.Error e) {
|
||||
promise.reject (e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void on_c_module_finalize (QuickJS.Runtime rt, QuickJS.Value val) {
|
||||
Barebone.CModule * mod = val.get_opaque (c_module_class);
|
||||
if (mod == null)
|
||||
return;
|
||||
|
||||
BareboneScript * script = rt.get_opaque ();
|
||||
script->c_modules.remove (mod);
|
||||
}
|
||||
|
||||
private static QuickJS.Value on_c_module_dispose (QuickJS.Context ctx, QuickJS.Value this_val, QuickJS.Value[] argv) {
|
||||
Barebone.CModule * mod = this_val.get_opaque (c_module_class);
|
||||
|
||||
if (mod != null) {
|
||||
this_val.set_opaque (null);
|
||||
BareboneScript * script = ctx.get_opaque ();
|
||||
script->c_modules.remove (mod);
|
||||
}
|
||||
|
||||
return QuickJS.Undefined;
|
||||
}
|
||||
|
||||
private static QuickJS.Value on_rust_module_construct (QuickJS.Context ctx, QuickJS.Value new_target,
|
||||
QuickJS.Value[] argv) {
|
||||
BareboneScript * script = ctx.get_opaque ();
|
||||
@@ -1736,7 +1965,7 @@ namespace Frida {
|
||||
foreach (var e in mod.exports)
|
||||
wrapper.set_property_str (ctx, e.name, script->make_native_pointer (e.address));
|
||||
|
||||
mod.console_output.connect (script->on_rust_module_console_output);
|
||||
mod.console_output.connect (script->on_console_output);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
@@ -1774,7 +2003,7 @@ namespace Frida {
|
||||
return QuickJS.Undefined;
|
||||
}
|
||||
|
||||
private void on_rust_module_console_output (string message) {
|
||||
private void on_console_output (string message) {
|
||||
var builder = new Json.Builder ();
|
||||
builder
|
||||
.begin_object ()
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"module": "Node16",
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["script-runtime/**/*.ts"]
|
||||
}
|
||||
@@ -1,469 +0,0 @@
|
||||
import * as crosspath from "@frida/crosspath";
|
||||
import type * as backend from "frida-compile";
|
||||
import {
|
||||
makeDefaultCompilerOptions,
|
||||
build as _build,
|
||||
watch as _watch,
|
||||
queryDefaultAssets as _queryDefaultAssets,
|
||||
} from "frida-compile";
|
||||
import ts from "frida-compile/ext/typescript.js";
|
||||
import fridaFs from "frida-fs";
|
||||
import fs from "fs";
|
||||
|
||||
const { DiagnosticCategory, FileWatcherEventKind } = ts;
|
||||
|
||||
const { S_IFDIR } = fs.constants;
|
||||
|
||||
export const compilerRoot = "/frida-compile";
|
||||
export const compilerNodeModules = compilerRoot + "/node_modules";
|
||||
|
||||
export const agentDirectories = new Set<string>(__agentDirectories__);
|
||||
export const agentFiles = new Map<string, string>(__agentFiles__);
|
||||
const cachedSourceFiles = new Map<string, ts.SourceFile>();
|
||||
|
||||
declare const __agentDirectories__: string[];
|
||||
declare const __agentFiles__: [string, string][];
|
||||
|
||||
const pendingDiagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
const fileWatchers = new Map<WatcherId, WatcherCallback>();
|
||||
let nextWatcherId: WatcherId = 1;
|
||||
|
||||
populateSourceFileCache();
|
||||
|
||||
function populateSourceFileCache() {
|
||||
const config = makeDefaultCompilerOptions();
|
||||
for (const [name, contents] of agentFiles) {
|
||||
if (name.endsWith(".d.ts")) {
|
||||
const sf = ts.createSourceFile(name, contents, config.target!);
|
||||
ts.prebindSourceFile(sf, config);
|
||||
cachedSourceFiles.set(name, sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function init(): void {
|
||||
function onChange(message: WatchChangeMessage): void {
|
||||
const callback = fileWatchers.get(message.id);
|
||||
callback?.(fileWatcherEventKindFromFileState(message.state));
|
||||
|
||||
recv("watch:change", onChange);
|
||||
}
|
||||
recv("watch:change", onChange);
|
||||
|
||||
const origCreateCompilerHostFromProgramHost = (ts as any).createCompilerHostFromProgramHost;
|
||||
(ts as any).createCompilerHostFromProgramHost = (...args: any[]): ts.CompilerHost => {
|
||||
const host = origCreateCompilerHostFromProgramHost(...args);
|
||||
patchCompilerHost(host);
|
||||
return host;
|
||||
};
|
||||
}
|
||||
|
||||
export function build(projectRoot: string, entrypoint: string, sourceMaps: backend.SourceMaps, compression: backend.Compression): string {
|
||||
const system = new FridaSystem(projectRoot);
|
||||
const assets = _queryDefaultAssets(projectRoot, system);
|
||||
|
||||
try {
|
||||
return _build({
|
||||
projectRoot,
|
||||
entrypoint,
|
||||
assets,
|
||||
system,
|
||||
sourceMaps,
|
||||
compression,
|
||||
onDiagnostic,
|
||||
onCompilerHostCreated: patchCompilerHost,
|
||||
});
|
||||
} finally {
|
||||
flushDiagnostics();
|
||||
}
|
||||
}
|
||||
|
||||
export function watch(projectRoot: string, entrypoint: string, sourceMaps: backend.SourceMaps, compression: backend.Compression): void {
|
||||
const system = new FridaSystem(projectRoot);
|
||||
const assets = _queryDefaultAssets(projectRoot, system);
|
||||
_watch({
|
||||
projectRoot,
|
||||
entrypoint,
|
||||
assets,
|
||||
system,
|
||||
sourceMaps,
|
||||
compression,
|
||||
onDiagnostic,
|
||||
})
|
||||
.on("compilationStarting", () => {
|
||||
send(["watch:compilation-starting"]);
|
||||
})
|
||||
.on("compilationFinished", () => {
|
||||
send(["watch:compilation-finished"]);
|
||||
})
|
||||
.on("bundleUpdated", bundle => {
|
||||
send(["watch:bundle-updated", bundle]);
|
||||
});
|
||||
}
|
||||
|
||||
function patchCompilerHost(host: ts.CompilerHost) {
|
||||
const origGetSourceFile = host.getSourceFile;
|
||||
host.getSourceFile = (fileName, ...args) => {
|
||||
const compilerPath = nativePathToCompilerPath(fileName);
|
||||
if (compilerPath !== null) {
|
||||
const sf = cachedSourceFiles.get(compilerPath);
|
||||
if (sf !== undefined) {
|
||||
return sf;
|
||||
}
|
||||
} else {
|
||||
const startPos = fileName.indexOf("/node_modules/@types/");
|
||||
if (startPos !== -1) {
|
||||
const builtinsPath = fileName.substring(startPos);
|
||||
const sf = cachedSourceFiles.get(builtinsPath);
|
||||
if (sf !== undefined) {
|
||||
let allowCache = true;
|
||||
|
||||
if (builtinsPath.startsWith("/node_modules/@types/frida-gum/")) {
|
||||
try {
|
||||
const hashOnDisk = Checksum.compute("sha256", File.readAllText(fileName));
|
||||
const hashInCache = Checksum.compute("sha256", agentFiles.get(sf.fileName)!);
|
||||
allowCache = hashOnDisk === hashInCache;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (allowCache) {
|
||||
return sf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return origGetSourceFile(fileName, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
export { _build, _watch, _queryDefaultAssets };
|
||||
|
||||
type DiagnosticFile = [path: string, line: number, character: number];
|
||||
|
||||
function onDiagnostic(diagnostic: ts.Diagnostic): void {
|
||||
pendingDiagnostics.push(diagnostic);
|
||||
if (pendingDiagnostics.length === 1) {
|
||||
Script.nextTick(flushDiagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
function flushDiagnostics(): void {
|
||||
const pending = pendingDiagnostics.splice(0, pendingDiagnostics.length);
|
||||
if (pending.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
send([
|
||||
"diagnostics",
|
||||
pending.map(({
|
||||
category,
|
||||
code,
|
||||
file: tsFile,
|
||||
start,
|
||||
messageText
|
||||
}) => {
|
||||
let file: DiagnosticFile | null = null;
|
||||
if (tsFile !== undefined) {
|
||||
const { line, character } = ts.getLineAndCharacterOfPosition(tsFile, start!);
|
||||
file = [tsFile.fileName, line, character];
|
||||
}
|
||||
return [
|
||||
diagnosticCategoryToString(category),
|
||||
code,
|
||||
file,
|
||||
ts.flattenDiagnosticMessageText(messageText, "\n")
|
||||
];
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
function diagnosticCategoryToString(category: ts.DiagnosticCategory): string {
|
||||
switch (category) {
|
||||
case DiagnosticCategory.Warning:
|
||||
return "warning";
|
||||
case DiagnosticCategory.Error:
|
||||
return "error";
|
||||
case DiagnosticCategory.Suggestion:
|
||||
return "suggestion";
|
||||
case DiagnosticCategory.Message:
|
||||
return "message";
|
||||
}
|
||||
}
|
||||
|
||||
class FridaSystem implements ts.System {
|
||||
args = [];
|
||||
newLine = "\n";
|
||||
useCaseSensitiveFileNames = true;
|
||||
|
||||
#projectRoot: string;
|
||||
#projectNodeModules: string;
|
||||
|
||||
constructor(projectRoot: string) {
|
||||
this.#projectRoot = crosspath.ensurePosix(projectRoot);
|
||||
this.#projectNodeModules = crosspath.join(this.#projectRoot, "node_modules");
|
||||
}
|
||||
|
||||
write(s: string): void {
|
||||
}
|
||||
|
||||
writeOutputIsTTY(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
readFile(path: string, encoding?: string): string | undefined {
|
||||
let result: string | undefined;
|
||||
if (path.startsWith(this.#projectRoot)) {
|
||||
try {
|
||||
result = File.readAllText(path);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (result === undefined) {
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null) {
|
||||
result = agentFiles.get(agentZipPath);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getFileSize(path: string): number {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void {
|
||||
File.writeAllText(path, data);
|
||||
}
|
||||
|
||||
watchFile(path: string, callback: ts.FileWatcherCallback, pollingInterval?: number, options?: ts.WatchOptions): ts.FileWatcher {
|
||||
if (path.startsWith(compilerRoot)) {
|
||||
return makeDummyWatcher();
|
||||
}
|
||||
|
||||
return makeFileWatcher(path, "file", pollingInterval ?? 0, kind => {
|
||||
callback(path, kind);
|
||||
});
|
||||
}
|
||||
|
||||
watchDirectory(path: string, callback: ts.DirectoryWatcherCallback, recursive?: boolean, options?: ts.WatchOptions): ts.FileWatcher {
|
||||
if (path.startsWith(compilerRoot)) {
|
||||
return makeDummyWatcher();
|
||||
}
|
||||
|
||||
return makeFileWatcher(path, "directory", 0, kind => {
|
||||
callback(path);
|
||||
});
|
||||
}
|
||||
|
||||
resolvePath(path: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
fileExists(path: string): boolean {
|
||||
if (path.startsWith(this.#projectRoot)) {
|
||||
try {
|
||||
const st = fs.statSync(path);
|
||||
return !st.isDirectory();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath === null) {
|
||||
return false;
|
||||
}
|
||||
return agentFiles.has(agentZipPath);
|
||||
}
|
||||
|
||||
directoryExists(path: string): boolean {
|
||||
if (path === compilerNodeModules) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (path.startsWith(this.#projectRoot)) {
|
||||
try {
|
||||
const st = fs.statSync(path);
|
||||
return st.isDirectory();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath === null) {
|
||||
return false;
|
||||
}
|
||||
return agentDirectories.has(agentZipPath);
|
||||
}
|
||||
|
||||
createDirectory(path: string): void {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
getExecutingFilePath(): string {
|
||||
return [compilerRoot, "ext", "typescript.js"].join("/");
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string {
|
||||
return this.#projectRoot;
|
||||
}
|
||||
|
||||
getDirectories(path: string): string[] {
|
||||
const result = new Set<string>();
|
||||
|
||||
if (path.startsWith(this.#projectRoot)) {
|
||||
try {
|
||||
for (const entry of fridaFs.list(path).filter(entry => entry.type === S_IFDIR)) {
|
||||
result.add(entry.name);
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null) {
|
||||
for (const dir of agentDirectories) {
|
||||
const slashIndex = dir.lastIndexOf("/");
|
||||
const parent = dir.substring(0, slashIndex);
|
||||
if (parent === agentZipPath) {
|
||||
const basename = dir.substring(slashIndex + 1);
|
||||
result.add(basename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(result);
|
||||
}
|
||||
|
||||
readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getModifiedTime(path: string): Date | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setModifiedTime(path: string, time: Date): void {
|
||||
}
|
||||
|
||||
deleteFile(path: string): void {
|
||||
}
|
||||
|
||||
createHash(data: string): string {
|
||||
return this.createSHA256Hash(data);
|
||||
}
|
||||
|
||||
createSHA256Hash(data: string): string {
|
||||
return Checksum.compute("sha256", data);
|
||||
}
|
||||
|
||||
getMemoryUsage(): number {
|
||||
return Frida.heapSize;
|
||||
}
|
||||
|
||||
exit(exitCode?: number): void {
|
||||
}
|
||||
|
||||
realpath(path: string): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
getEnvironmentVariable(name: string): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any {
|
||||
return setTimeout(callback);
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId: any): void {
|
||||
return clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
clearScreen(): void {
|
||||
}
|
||||
|
||||
base64decode(input: string): string {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
base64encode(input: string): string {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
#nativePathToAgentZipPath(path: string): string | null {
|
||||
const compilerPath = nativePathToCompilerPath(path);
|
||||
if (compilerPath !== null) {
|
||||
return compilerPath;
|
||||
}
|
||||
|
||||
if (path.startsWith(this.#projectNodeModules)) {
|
||||
return "/node_modules" + path.substring(this.#projectNodeModules.length);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function nativePathToCompilerPath(path: string): string | null {
|
||||
if (!path.startsWith(compilerRoot)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const subPath = path.substring(compilerRoot.length);
|
||||
if (subPath.startsWith("/node_modules")) {
|
||||
return subPath;
|
||||
}
|
||||
|
||||
return "/node_modules/frida-compile" + subPath;
|
||||
}
|
||||
|
||||
type WatcherId = number;
|
||||
type WatcherCallback = (eventKind: ts.FileWatcherEventKind) => void;
|
||||
type FileState = "pristine" | "modified" | "deleted";
|
||||
|
||||
interface WatchChangeMessage {
|
||||
id: WatcherId;
|
||||
state: FileState;
|
||||
}
|
||||
|
||||
function makeFileWatcher(path: string, type: "file" | "directory", pollingInterval: number, callback: WatcherCallback): ts.FileWatcher {
|
||||
if (nextWatcherId === 0xffffffff) {
|
||||
nextWatcherId = 1;
|
||||
}
|
||||
let id: WatcherId = nextWatcherId++;
|
||||
while (fileWatchers.has(id)) {
|
||||
id = nextWatcherId++;
|
||||
}
|
||||
|
||||
fileWatchers.set(id, callback);
|
||||
|
||||
send(["watch:add", id, path, type, pollingInterval ?? 0]);
|
||||
|
||||
return {
|
||||
close() {
|
||||
send(["watch:remove", id]);
|
||||
|
||||
fileWatchers.delete(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function makeDummyWatcher(): ts.FileWatcher {
|
||||
return {
|
||||
close() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function fileWatcherEventKindFromFileState(state: FileState): ts.FileWatcherEventKind {
|
||||
switch (state) {
|
||||
case "pristine": return FileWatcherEventKind.Created;
|
||||
case "modified": return FileWatcherEventKind.Changed;
|
||||
case "deleted": return FileWatcherEventKind.Deleted;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
const { init, build, watch } = FridaCompilerAgentCore;
|
||||
|
||||
init();
|
||||
|
||||
rpc.exports = { build, watch };
|
||||
@@ -1,222 +0,0 @@
|
||||
const {
|
||||
compilerRoot,
|
||||
compilerNodeModules,
|
||||
agentDirectories,
|
||||
agentFiles,
|
||||
_build,
|
||||
_queryDefaultAssets,
|
||||
} = FridaCompilerAgentCore;
|
||||
|
||||
const projectRoot = '/agent';
|
||||
const projectNodeModules = '/agent/node_modules';
|
||||
const entrypoint = 'index.ts';
|
||||
const sourceMaps = 'included';
|
||||
const compression = 'none';
|
||||
|
||||
const hashes = new Map();
|
||||
let nextHashId = 1;
|
||||
|
||||
function main() {
|
||||
const system = new StubSystem();
|
||||
const assets = _queryDefaultAssets(projectRoot, system);
|
||||
_build({
|
||||
projectRoot,
|
||||
entrypoint,
|
||||
assets,
|
||||
system,
|
||||
sourceMaps,
|
||||
compression,
|
||||
onDiagnostic,
|
||||
});
|
||||
}
|
||||
|
||||
function onDiagnostic(diagnostic) {
|
||||
throwNotImplemented('diagnostic', ts.flattenDiagnosticMessageText(diagnostic, '\n'));
|
||||
}
|
||||
|
||||
class StubSystem {
|
||||
args = [];
|
||||
newLine = '\n';
|
||||
useCaseSensitiveFileNames = true;
|
||||
|
||||
write(s) {
|
||||
}
|
||||
|
||||
writeOutputIsTTY() {
|
||||
return true;
|
||||
}
|
||||
|
||||
readFile(path, encoding) {
|
||||
if (path === '/agent/tsconfig.json')
|
||||
return '{}';
|
||||
|
||||
if (path === '/agent/index.ts')
|
||||
return 'Interceptor.attach(ptr(1234), { onEnter(args) {} });'
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null) {
|
||||
return agentFiles.get(agentZipPath);
|
||||
}
|
||||
|
||||
throwNotImplemented('readFile', path);
|
||||
}
|
||||
|
||||
getFileSize(path) {
|
||||
throwNotImplemented('getFileSize');
|
||||
}
|
||||
|
||||
writeFile(path, data, writeByteOrderMark) {
|
||||
throwNotImplemented('writeFile');
|
||||
}
|
||||
|
||||
watchFile(path, callback, pollingInterval, options) {
|
||||
throwNotImplemented('watchFile');
|
||||
}
|
||||
|
||||
watchDirectory(path, callback, recursive, options) {
|
||||
throwNotImplemented('watchDirectory');
|
||||
}
|
||||
|
||||
resolvePath(path) {
|
||||
throwNotImplemented('resolvePath');
|
||||
}
|
||||
|
||||
fileExists(path) {
|
||||
if (path === '/package.json' || path === '/agent/package.json')
|
||||
return false;
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null)
|
||||
return agentFiles.has(agentZipPath);
|
||||
|
||||
throwNotImplemented('fileExists', path);
|
||||
}
|
||||
|
||||
directoryExists(path) {
|
||||
if (path === '/' || path === projectRoot || path === compilerNodeModules)
|
||||
return true;
|
||||
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null)
|
||||
return agentDirectories.has(agentZipPath);
|
||||
|
||||
if (path === '/node_modules/@types' || path === '/node_modules')
|
||||
return false;
|
||||
|
||||
throwNotImplemented('directoryExists', path);
|
||||
}
|
||||
|
||||
createDirectory(path) {
|
||||
throwNotImplemented('createDirectory');
|
||||
}
|
||||
|
||||
getExecutingFilePath() {
|
||||
return [compilerRoot, 'ext', 'typescript.js'].join('/');
|
||||
}
|
||||
|
||||
getCurrentDirectory() {
|
||||
return '/';
|
||||
}
|
||||
|
||||
getDirectories(path) {
|
||||
const agentZipPath = this.#nativePathToAgentZipPath(path);
|
||||
if (agentZipPath !== null) {
|
||||
const result = [];
|
||||
for (const dir of agentDirectories) {
|
||||
const slashIndex = dir.lastIndexOf('/');
|
||||
const parent = dir.substring(0, slashIndex);
|
||||
if (parent === agentZipPath) {
|
||||
const basename = dir.substring(slashIndex + 1);
|
||||
result.push(basename);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
throwNotImplemented('getDirectories', path);
|
||||
}
|
||||
|
||||
readDirectory(path, extensions, exclude, include, depth) {
|
||||
if (!extensions.includes('.ts'))
|
||||
throwNotImplemented('readDirectory', path, extensions);
|
||||
return ['index.ts'];
|
||||
}
|
||||
|
||||
getModifiedTime(path) {
|
||||
}
|
||||
|
||||
setModifiedTime(path, time) {
|
||||
}
|
||||
|
||||
deleteFile(path) {
|
||||
}
|
||||
|
||||
createHash(data) {
|
||||
let hash = hashes.get(data);
|
||||
if (hash === undefined) {
|
||||
hash = 'hash' + nextHashId++;
|
||||
hashes.set(data, hash);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
createSHA256Hash(data) {
|
||||
throwNotImplemented('createSHA256Hash');
|
||||
}
|
||||
|
||||
getMemoryUsage() {
|
||||
throwNotImplemented('getMemoryUsage');
|
||||
}
|
||||
|
||||
exit(exitCode) {
|
||||
}
|
||||
|
||||
realpath(path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
getEnvironmentVariable(name) {
|
||||
return '';
|
||||
}
|
||||
|
||||
setTimeout(callback, ms, ...args) {
|
||||
throwNotImplemented('setTimeout');
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId) {
|
||||
throwNotImplemented('clearTimeout');
|
||||
}
|
||||
|
||||
clearScreen() {
|
||||
}
|
||||
|
||||
base64decode(input) {
|
||||
throwNotImplemented('base64decode');
|
||||
}
|
||||
|
||||
base64encode(input) {
|
||||
throwNotImplemented('base64encode');
|
||||
}
|
||||
|
||||
#nativePathToAgentZipPath(path) {
|
||||
if (path.startsWith(compilerRoot)) {
|
||||
const subPath = path.substring(compilerRoot.length);
|
||||
if (subPath.startsWith('/node_modules')) {
|
||||
return subPath;
|
||||
}
|
||||
return '/node_modules/frida-compile' + subPath;
|
||||
}
|
||||
|
||||
if (path.startsWith(projectNodeModules)) {
|
||||
return '/node_modules' + path.substring(projectNodeModules.length);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function throwNotImplemented(operation, ...details) {
|
||||
throw new Error('not implemented: ' + operation + ((details.length > 0) ? ` (${details.join(' ')})` : ''));
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#ifdef HAVE_ARM64
|
||||
# define FRIDA_CGO_INIT_FUNC _st0_arm64_windows_lib
|
||||
#elif GLIB_SIZEOF_VOID_P == 8
|
||||
# define FRIDA_CGO_INIT_FUNC _st0_amd64_windows_lib
|
||||
#else
|
||||
# define FRIDA_CGO_INIT_FUNC st0_386_windows_lib
|
||||
#endif
|
||||
|
||||
extern void FRIDA_CGO_INIT_FUNC ();
|
||||
|
||||
void
|
||||
_frida_compiler_backend_init_go_runtime (void)
|
||||
{
|
||||
FRIDA_CGO_INIT_FUNC ();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void
|
||||
_frida_compiler_backend_init_go_runtime (void)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,816 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
FRIDA_OUTPUT_UNESCAPED,
|
||||
FRIDA_OUTPUT_HEX_BYTES,
|
||||
FRIDA_OUTPUT_C_STRING,
|
||||
} FridaOutputFormat;
|
||||
|
||||
typedef enum {
|
||||
FRIDA_BUNDLE_ESM,
|
||||
FRIDA_BUNDLE_IIFE,
|
||||
} FridaBundleFormat;
|
||||
|
||||
typedef void (* FridaBuildCompleteFunc) (char * bundle, char * error_message, void * user_data);
|
||||
typedef void (* FridaWatchReadyFunc) (uintptr_t session_handle, char * error_message, void * user_data);
|
||||
typedef void (* FridaStartingFunc) (void * user_data);
|
||||
typedef void (* FridaFinishedFunc) (void * user_data);
|
||||
typedef void (* FridaOutputFunc) (char * bundle, void * user_data);
|
||||
typedef void (* FridaDiagnosticFunc) (char * category, int code, char * path, int line, int character, char * text,
|
||||
void * user_data);
|
||||
typedef void (* FridaDestroyFunc) (void * user_data);
|
||||
|
||||
static inline void
|
||||
invoke_build_complete_func (FridaBuildCompleteFunc fn,
|
||||
char * bundle,
|
||||
char * error_message,
|
||||
void * user_data)
|
||||
{
|
||||
fn (bundle, error_message, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_watch_ready_func (FridaWatchReadyFunc fn,
|
||||
uintptr_t session_handle,
|
||||
char * error_message,
|
||||
void * user_data)
|
||||
{
|
||||
fn (session_handle, error_message, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_starting_func (FridaStartingFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_finished_func (FridaFinishedFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_output_func (FridaOutputFunc fn,
|
||||
char * bundle,
|
||||
void * user_data)
|
||||
{
|
||||
fn (bundle, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_diagnostic_func (FridaDiagnosticFunc fn,
|
||||
char * category,
|
||||
int code,
|
||||
char * path,
|
||||
int line,
|
||||
int character,
|
||||
char * text,
|
||||
void * user_data)
|
||||
{
|
||||
fn (category, code, path, line, character, text, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_destroy_func (FridaDestroyFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/cgo"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
tsscanner "github.com/frida/typescript-go/pkg/scanner"
|
||||
)
|
||||
|
||||
type BuildOptions struct {
|
||||
ProjectRoot string
|
||||
Entrypoint string
|
||||
OutputFormat OutputFormat
|
||||
BundleFormat BundleFormat
|
||||
DisableTypeCheck bool
|
||||
SourceMap bool
|
||||
Compress bool
|
||||
}
|
||||
|
||||
type OutputFormat C.FridaOutputFormat
|
||||
|
||||
const (
|
||||
OutputFormatUnescaped OutputFormat = OutputFormat(C.FRIDA_OUTPUT_UNESCAPED)
|
||||
OutputFormatHexBytes OutputFormat = OutputFormat(C.FRIDA_OUTPUT_HEX_BYTES)
|
||||
OutputFormatCString OutputFormat = OutputFormat(C.FRIDA_OUTPUT_C_STRING)
|
||||
)
|
||||
|
||||
type BundleFormat C.FridaBundleFormat
|
||||
|
||||
const (
|
||||
BundleFormatESM BundleFormat = BundleFormat(C.FRIDA_BUNDLE_ESM)
|
||||
BundleFormatIIFE BundleFormat = BundleFormat(C.FRIDA_BUNDLE_IIFE)
|
||||
)
|
||||
|
||||
type Diagnostic struct {
|
||||
category string
|
||||
code int
|
||||
path string
|
||||
line, character int
|
||||
text string
|
||||
}
|
||||
|
||||
type BuildEventCallbacks struct {
|
||||
OnStart BuildStartCallback
|
||||
OnEnd BuildEndCallback
|
||||
OnOutput BuildOutputCallback
|
||||
OnDiagnostic BuildDiagnosticCallback
|
||||
OnConfigChange ConfigChangeCallback
|
||||
}
|
||||
|
||||
type BuildStartCallback func()
|
||||
type BuildEndCallback func()
|
||||
type BuildOutputCallback func(bundle string)
|
||||
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,
|
||||
onCompleteFn C.FridaBuildCompleteFunc, onCompleteData unsafe.Pointer, onCompleteDataDestroy C.FridaDestroyFunc,
|
||||
onDiagnosticFn C.FridaDiagnosticFunc, onDiagnosticData unsafe.Pointer) {
|
||||
options := BuildOptions{
|
||||
ProjectRoot: C.GoString(cProjectRoot),
|
||||
Entrypoint: C.GoString(cEntrypoint),
|
||||
OutputFormat: OutputFormat(outputFormat),
|
||||
BundleFormat: BundleFormat(bundleFormat),
|
||||
DisableTypeCheck: disableTypeCheck != 0,
|
||||
SourceMap: sourceMap != 0,
|
||||
Compress: compress != 0,
|
||||
}
|
||||
onComplete := NewCDelegate(onCompleteFn, onCompleteData, onCompleteDataDestroy)
|
||||
onDiagnostic := NewCDelegate(onDiagnosticFn, onDiagnosticData, nil)
|
||||
|
||||
go func() {
|
||||
defer onComplete.Dispose()
|
||||
defer onDiagnostic.Dispose()
|
||||
|
||||
onDiagnostic := makeCBuildDiagnosticCallback(onDiagnostic)
|
||||
|
||||
bundle, err := build(options, onDiagnostic)
|
||||
|
||||
var cBundle, cErrorMessage *C.char
|
||||
if err == nil {
|
||||
cBundle = C.CString(bundle)
|
||||
} else {
|
||||
cErrorMessage = C.CString(err.Error())
|
||||
}
|
||||
C.invoke_build_complete_func(onComplete.Func, cBundle, cErrorMessage, onComplete.Data)
|
||||
}()
|
||||
}
|
||||
|
||||
//export _frida_compiler_backend_watch
|
||||
func _frida_compiler_backend_watch(cProjectRoot, cEntrypoint *C.char, outputFormat C.FridaOutputFormat, bundleFormat C.FridaBundleFormat,
|
||||
disableTypeCheck, sourceMap, compress uintptr,
|
||||
onReadyFn C.FridaWatchReadyFunc, onReadyData unsafe.Pointer, onReadyDataDestroy C.FridaDestroyFunc,
|
||||
onStartingFn C.FridaStartingFunc, onStartingData unsafe.Pointer,
|
||||
onFinishedFn C.FridaFinishedFunc, onFinishedData unsafe.Pointer,
|
||||
onOutputFn C.FridaOutputFunc, onOutputData unsafe.Pointer,
|
||||
onDiagnosticFn C.FridaDiagnosticFunc, onDiagnosticData unsafe.Pointer) {
|
||||
options := BuildOptions{
|
||||
ProjectRoot: C.GoString(cProjectRoot),
|
||||
Entrypoint: C.GoString(cEntrypoint),
|
||||
OutputFormat: OutputFormat(outputFormat),
|
||||
BundleFormat: BundleFormat(bundleFormat),
|
||||
DisableTypeCheck: disableTypeCheck != 0,
|
||||
SourceMap: sourceMap != 0,
|
||||
Compress: compress != 0,
|
||||
}
|
||||
onReady := NewCDelegate(onReadyFn, onReadyData, onReadyDataDestroy)
|
||||
onStarting := NewCDelegate(onStartingFn, onStartingData, nil)
|
||||
onFinished := NewCDelegate(onFinishedFn, onFinishedData, nil)
|
||||
onOutput := NewCDelegate(onOutputFn, onOutputData, nil)
|
||||
onDiagnostic := NewCDelegate(onDiagnosticFn, onDiagnosticData, nil)
|
||||
|
||||
go func() {
|
||||
defer onReady.Dispose()
|
||||
|
||||
onDispose := func() {
|
||||
onStarting.Dispose()
|
||||
onFinished.Dispose()
|
||||
onOutput.Dispose()
|
||||
onDiagnostic.Dispose()
|
||||
}
|
||||
|
||||
callbacks := BuildEventCallbacks{
|
||||
OnStart: func() {
|
||||
C.invoke_starting_func(onStarting.Func, onStarting.Data)
|
||||
},
|
||||
OnEnd: func() {
|
||||
C.invoke_starting_func(onFinished.Func, onFinished.Data)
|
||||
},
|
||||
OnOutput: func(bundle string) {
|
||||
C.invoke_output_func(onOutput.Func, C.CString(bundle), onOutput.Data)
|
||||
},
|
||||
OnDiagnostic: makeCBuildDiagnosticCallback(onDiagnostic),
|
||||
}
|
||||
|
||||
session, err := NewWatchSession(options, onDispose, callbacks)
|
||||
|
||||
var cSession C.uintptr_t
|
||||
var cErrorMessage *C.char
|
||||
if err == nil {
|
||||
cSession = C.uintptr_t(cgo.NewHandle(session))
|
||||
} else {
|
||||
cErrorMessage = C.CString(err.Error())
|
||||
}
|
||||
C.invoke_watch_ready_func(onReady.Func, cSession, cErrorMessage, onReady.Data)
|
||||
}()
|
||||
}
|
||||
|
||||
//export _frida_compiler_backend_watch_session_dispose
|
||||
func _frida_compiler_backend_watch_session_dispose(h uintptr) {
|
||||
handle := cgo.Handle(h)
|
||||
session := handle.Value().(*WatchSession)
|
||||
session.Dispose()
|
||||
handle.Delete()
|
||||
}
|
||||
|
||||
func makeCBuildDiagnosticCallback(onDiagnostic *CDelegate[C.FridaDiagnosticFunc]) BuildDiagnosticCallback {
|
||||
return func(d Diagnostic) {
|
||||
var cPath *C.char
|
||||
if d.path != "" {
|
||||
cPath = C.CString(d.path)
|
||||
}
|
||||
|
||||
C.invoke_diagnostic_func(onDiagnostic.Func, C.CString(d.category), C.int(d.code), cPath, C.int(d.line),
|
||||
C.int(d.character), C.CString(d.text), onDiagnostic.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func build(options BuildOptions, onDiagnostic BuildDiagnosticCallback) (bundle string, err error) {
|
||||
callbacks := BuildEventCallbacks{
|
||||
OnOutput: func(b string) {
|
||||
bundle = b
|
||||
},
|
||||
OnDiagnostic: onDiagnostic,
|
||||
}
|
||||
|
||||
ctx, err := makeContext(options, callbacks)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer ctx.Dispose()
|
||||
|
||||
result := ctx.Rebuild()
|
||||
|
||||
if len(result.Errors) != 0 {
|
||||
err = fmt.Errorf("Compilation failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type WatchSession struct {
|
||||
mu sync.Mutex
|
||||
options BuildOptions
|
||||
callbacks BuildEventCallbacks
|
||||
ctx esbuild.BuildContext
|
||||
onDispose SessionDisposeHandler
|
||||
}
|
||||
|
||||
type SessionDisposeHandler func()
|
||||
|
||||
func NewWatchSession(opts BuildOptions, onDispose SessionDisposeHandler, callbacks BuildEventCallbacks) (session *WatchSession, err error) {
|
||||
cbs := BuildEventCallbacks{
|
||||
OnStart: callbacks.OnStart,
|
||||
OnEnd: callbacks.OnEnd,
|
||||
OnOutput: callbacks.OnOutput,
|
||||
OnDiagnostic: callbacks.OnDiagnostic,
|
||||
OnConfigChange: func() {
|
||||
if callbacks.OnConfigChange != nil {
|
||||
callbacks.OnConfigChange()
|
||||
}
|
||||
session.onConfigChange()
|
||||
},
|
||||
}
|
||||
|
||||
ctx, err := makeContext(opts, cbs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Watch(esbuild.WatchOptions{})
|
||||
|
||||
session = &WatchSession{
|
||||
options: opts,
|
||||
callbacks: cbs,
|
||||
ctx: ctx,
|
||||
onDispose: onDispose,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *WatchSession) Dispose() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.ctx != nil {
|
||||
s.ctx.Dispose()
|
||||
s.ctx = nil
|
||||
}
|
||||
|
||||
s.onDispose()
|
||||
}
|
||||
|
||||
func (s *WatchSession) onConfigChange() {
|
||||
go func() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.ctx != nil {
|
||||
s.ctx.Dispose()
|
||||
s.ctx = nil
|
||||
}
|
||||
|
||||
ctx, err := makeContext(s.options, s.callbacks)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.ctx = ctx
|
||||
|
||||
ctx.Watch(esbuild.WatchOptions{})
|
||||
}()
|
||||
}
|
||||
|
||||
func makeContext(options BuildOptions, callbacks BuildEventCallbacks) (ctx esbuild.BuildContext, err error) {
|
||||
var e error
|
||||
|
||||
var projectRoot string
|
||||
if projectRoot, e = filepath.EvalSymlinks(options.ProjectRoot); e != nil {
|
||||
err = fmt.Errorf("Failed to resolve project root: %w", e)
|
||||
return
|
||||
}
|
||||
|
||||
var entrypoint string
|
||||
if filepath.IsAbs(options.Entrypoint) {
|
||||
entrypoint = options.Entrypoint
|
||||
} else {
|
||||
entrypoint = filepath.Join(projectRoot, options.Entrypoint)
|
||||
}
|
||||
if entrypoint, e = filepath.EvalSymlinks(entrypoint); e != nil {
|
||||
err = fmt.Errorf("Failed to resolve entrypoint: %w", e)
|
||||
return
|
||||
}
|
||||
rel, e := filepath.Rel(projectRoot, entrypoint)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Could not compute entrypoint path relative to project root: %w", e)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(rel, "..") {
|
||||
err = fmt.Errorf("Entrypoint must be inside the project root")
|
||||
return
|
||||
}
|
||||
|
||||
isTS := strings.HasSuffix(entrypoint, ".ts")
|
||||
|
||||
var tsconfigCache *TSConfigCache
|
||||
var tsconfigText string
|
||||
var tsCompiler *TSCompiler
|
||||
|
||||
if isTS {
|
||||
tsconfigCache = NewTSConfigCache(projectRoot, options.SourceMap, callbacks.OnConfigChange)
|
||||
|
||||
tsCompiler = NewTSCompiler(projectRoot, entrypoint, tsconfigCache.GetCompilerOptions)
|
||||
|
||||
_, tsconfigText, err = tsconfigCache.GetCompilerOptions(tsCompiler)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to load tsconfig options: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sourcemapOption := esbuild.SourceMapNone
|
||||
if options.SourceMap {
|
||||
if options.BundleFormat == BundleFormatESM {
|
||||
sourcemapOption = esbuild.SourceMapExternal
|
||||
} else {
|
||||
sourcemapOption = esbuild.SourceMapInline
|
||||
}
|
||||
}
|
||||
|
||||
minifyWhitespace := false
|
||||
minifyIdentifiers := false
|
||||
minifySyntax := false
|
||||
if options.Compress {
|
||||
minifyWhitespace = true
|
||||
minifyIdentifiers = true
|
||||
minifySyntax = true
|
||||
}
|
||||
|
||||
var format esbuild.Format
|
||||
if options.BundleFormat == BundleFormatESM {
|
||||
format = esbuild.FormatESModule
|
||||
} else {
|
||||
format = esbuild.FormatIIFE
|
||||
}
|
||||
|
||||
plugins := []esbuild.Plugin{
|
||||
makeBuildObserverPlugin(projectRoot, entrypoint, options, callbacks),
|
||||
}
|
||||
|
||||
if isTS && !options.DisableTypeCheck {
|
||||
plugins = append(plugins, makeTypeScriptPlugin(tsCompiler))
|
||||
}
|
||||
|
||||
plugins = append(plugins, makeFridaShimsPlugin())
|
||||
|
||||
buildOpts := esbuild.BuildOptions{
|
||||
Sourcemap: sourcemapOption,
|
||||
SourcesContent: esbuild.SourcesContentExclude,
|
||||
Target: esbuild.ES2022,
|
||||
MinifyWhitespace: minifyWhitespace,
|
||||
MinifyIdentifiers: minifyIdentifiers,
|
||||
MinifySyntax: minifySyntax,
|
||||
LegalComments: esbuild.LegalCommentsNone,
|
||||
Bundle: true,
|
||||
Outdir: projectRoot,
|
||||
AbsWorkingDir: projectRoot,
|
||||
Platform: esbuild.PlatformNode,
|
||||
Format: format,
|
||||
Inject: []string{"frida-builtins:///node-globals.js"},
|
||||
EntryPoints: []string{entrypoint},
|
||||
Write: false,
|
||||
Plugins: plugins,
|
||||
}
|
||||
|
||||
if isTS {
|
||||
buildOpts.TsconfigRaw = tsconfigText
|
||||
}
|
||||
|
||||
if buildCtx, ctxErr := esbuild.Context(buildOpts); ctxErr == nil {
|
||||
ctx = buildCtx
|
||||
} else {
|
||||
for _, e := range ctxErr.Errors {
|
||||
emitDiagnostic("error", e, callbacks.OnDiagnostic)
|
||||
}
|
||||
err = fmt.Errorf("Failed to create ESBuild context")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func emitDiagnostic(category string, message esbuild.Message, onDiagnostic BuildDiagnosticCallback) {
|
||||
d := Diagnostic{
|
||||
category: category,
|
||||
code: -1,
|
||||
text: message.Text,
|
||||
}
|
||||
|
||||
l := message.Location
|
||||
if l != nil {
|
||||
d.path = l.File
|
||||
d.line = l.Line
|
||||
d.character = l.Column
|
||||
}
|
||||
|
||||
if message.PluginName == "frida-custom-ts" && len(message.Notes) == 2 {
|
||||
d.category = message.Notes[0].Text
|
||||
fmt.Sscan(message.Notes[1].Text, &d.code)
|
||||
}
|
||||
|
||||
onDiagnostic(d)
|
||||
}
|
||||
|
||||
func changeToJS(p string) string {
|
||||
ext := path.Ext(p)
|
||||
base := p[:len(p)-len(ext)]
|
||||
return base + ".js"
|
||||
}
|
||||
|
||||
func makeBuildObserverPlugin(projectRoot, entrypoint string, options BuildOptions, callbacks BuildEventCallbacks) esbuild.Plugin {
|
||||
return esbuild.Plugin{
|
||||
Name: "frida-build-observer",
|
||||
Setup: func(build esbuild.PluginBuild) {
|
||||
if callbacks.OnStart != nil {
|
||||
build.OnStart(func() (esbuild.OnStartResult, error) {
|
||||
callbacks.OnStart()
|
||||
return esbuild.OnStartResult{}, nil
|
||||
})
|
||||
}
|
||||
|
||||
build.OnEnd(func(result *esbuild.BuildResult) (esbuild.OnEndResult, error) {
|
||||
if len(result.Errors) == 0 {
|
||||
var output string
|
||||
if options.BundleFormat == BundleFormatESM {
|
||||
output = makeESMBundle(result.OutputFiles, projectRoot, entrypoint)
|
||||
} else {
|
||||
output = string(result.OutputFiles[0].Contents)
|
||||
}
|
||||
|
||||
switch options.OutputFormat {
|
||||
case OutputFormatHexBytes:
|
||||
output = encodeStringToHexBytes(output)
|
||||
case OutputFormatCString:
|
||||
output = encodeStringToCString(output)
|
||||
}
|
||||
|
||||
callbacks.OnOutput(output)
|
||||
} else {
|
||||
for _, e := range result.Errors {
|
||||
emitDiagnostic("error", e, callbacks.OnDiagnostic)
|
||||
}
|
||||
for _, e := range result.Warnings {
|
||||
emitDiagnostic("warning", e, callbacks.OnDiagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
if callbacks.OnEnd != nil {
|
||||
callbacks.OnEnd()
|
||||
}
|
||||
|
||||
return esbuild.OnEndResult{}, nil
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeESMBundle(files []esbuild.OutputFile, projectRoot, entrypoint string) string {
|
||||
entrypointJS, entrypointMap := outputForEntrypoint(projectRoot, entrypoint)
|
||||
relEntryp, err := filepath.Rel(projectRoot, entrypoint)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Cannot compute entrypoint relative path: %v", err))
|
||||
}
|
||||
entrySubDir := filepath.Dir(relEntryp)
|
||||
|
||||
entryIndexJS := -1
|
||||
entryIndexMap := -1
|
||||
for i, of := range files {
|
||||
if of.Path == entrypointJS {
|
||||
entryIndexJS = i
|
||||
}
|
||||
if of.Path == entrypointMap {
|
||||
entryIndexMap = i
|
||||
}
|
||||
}
|
||||
if entryIndexJS < 0 {
|
||||
panic(fmt.Sprintf("Entrypoint JS %q not found in files", entrypointJS))
|
||||
}
|
||||
|
||||
orderedFiles := make([]esbuild.OutputFile, 0, len(files))
|
||||
orderedFiles = append(orderedFiles, files[entryIndexJS])
|
||||
if entryIndexMap >= 0 {
|
||||
orderedFiles = append(orderedFiles, files[entryIndexMap])
|
||||
}
|
||||
|
||||
for i, of := range files {
|
||||
if i == entryIndexJS || i == entryIndexMap {
|
||||
continue
|
||||
}
|
||||
orderedFiles = append(orderedFiles, of)
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("📦\n")
|
||||
for _, of := range orderedFiles {
|
||||
size := len(of.Contents)
|
||||
relPath, _ := filepath.Rel(projectRoot, of.Path)
|
||||
withSub := filepath.Join(entrySubDir, relPath)
|
||||
sb.WriteString(fmt.Sprintf("%d /%s\n", size, filepath.ToSlash(withSub)))
|
||||
}
|
||||
|
||||
sb.WriteString("✄\n")
|
||||
for i, of := range orderedFiles {
|
||||
sb.Write(of.Contents)
|
||||
if i < len(orderedFiles)-1 {
|
||||
sb.WriteString("\n✄\n")
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func outputForEntrypoint(projectRoot, entrypoint string) (jsOut, mapOut string) {
|
||||
jsOut = filepath.Join(projectRoot, changeToJS(filepath.Base(entrypoint)))
|
||||
mapOut = jsOut + ".map"
|
||||
return
|
||||
}
|
||||
|
||||
func encodeStringToHexBytes(s string) string {
|
||||
n := len(s)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
totalLen := n*4 + (n-1)*2 + 1
|
||||
buf := make([]byte, 0, totalLen)
|
||||
|
||||
hexDigits := "0123456789abcdef"
|
||||
for i := 0; i < n; i++ {
|
||||
b := s[i]
|
||||
buf = append(buf, '0', 'x')
|
||||
buf = append(buf,
|
||||
hexDigits[b>>4],
|
||||
hexDigits[b&0xF],
|
||||
)
|
||||
|
||||
if i < n-1 {
|
||||
if (i+1)%12 == 0 {
|
||||
buf = append(buf, ',', '\n')
|
||||
} else {
|
||||
buf = append(buf, ',', ' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf = append(buf, '\n')
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func encodeStringToCString(s string) string {
|
||||
n := len(s)
|
||||
if n == 0 {
|
||||
return `""`
|
||||
}
|
||||
|
||||
hexDigits := "0123456789abcdef"
|
||||
var bldr strings.Builder
|
||||
|
||||
openLiteral := func() {
|
||||
bldr.WriteByte('"')
|
||||
}
|
||||
closeLiteral := func() {
|
||||
bldr.WriteByte('"')
|
||||
}
|
||||
|
||||
inLiteral := false
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
c := s[i]
|
||||
|
||||
if !inLiteral {
|
||||
openLiteral()
|
||||
inLiteral = true
|
||||
}
|
||||
|
||||
if c == '\n' {
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('n')
|
||||
closeLiteral()
|
||||
inLiteral = false
|
||||
|
||||
if i < n-1 {
|
||||
bldr.WriteByte('\n')
|
||||
openLiteral()
|
||||
inLiteral = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '"':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('"')
|
||||
case '\\':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('\\')
|
||||
|
||||
case '\a':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('a')
|
||||
case '\b':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('b')
|
||||
case '\t':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('t')
|
||||
case '\v':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('v')
|
||||
case '\f':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('f')
|
||||
case '\r':
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('r')
|
||||
|
||||
default:
|
||||
isPrintableExceptQuoteAndBackslash := c >= 0x20 && c <= 0x7E
|
||||
if isPrintableExceptQuoteAndBackslash {
|
||||
bldr.WriteByte(c)
|
||||
} else {
|
||||
bldr.WriteByte('\\')
|
||||
bldr.WriteByte('x')
|
||||
bldr.WriteByte(hexDigits[c>>4])
|
||||
bldr.WriteByte(hexDigits[c&0xF])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inLiteral {
|
||||
closeLiteral()
|
||||
}
|
||||
|
||||
bldr.WriteByte('\n')
|
||||
|
||||
return bldr.String()
|
||||
}
|
||||
|
||||
func makeTypeScriptPlugin(compiler *TSCompiler) esbuild.Plugin {
|
||||
return esbuild.Plugin{
|
||||
Name: "frida-custom-ts",
|
||||
Setup: func(build esbuild.PluginBuild) {
|
||||
build.OnStart(func() (esbuild.OnStartResult, error) {
|
||||
compiler.EnsureProgramUpToDate()
|
||||
return esbuild.OnStartResult{}, nil
|
||||
})
|
||||
|
||||
build.OnEnd(func(result *esbuild.BuildResult) (esbuild.OnEndResult, error) {
|
||||
if len(result.Errors) == 0 {
|
||||
compiler.CommitPendingInputs()
|
||||
} else {
|
||||
compiler.DropPendingInputs()
|
||||
}
|
||||
return esbuild.OnEndResult{}, nil
|
||||
})
|
||||
|
||||
build.OnLoad(esbuild.OnLoadOptions{Filter: "\\.ts$"}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
||||
compiledJS, tsDiagnostics, err := compiler.Compile(args.Path)
|
||||
|
||||
result := esbuild.OnLoadResult{
|
||||
WatchFiles: compiler.WatchFiles(),
|
||||
WatchDirs: compiler.WatchDirs(),
|
||||
}
|
||||
|
||||
var esbuildMessages []esbuild.Message
|
||||
for _, d := range tsDiagnostics {
|
||||
f := d.File()
|
||||
pos := d.Pos()
|
||||
line, column := tsscanner.GetLineAndCharacterOfPosition(f, pos)
|
||||
|
||||
esbuildMessages = append(esbuildMessages, esbuild.Message{
|
||||
Text: d.Message(),
|
||||
Location: &esbuild.Location{
|
||||
File: f.FileName(),
|
||||
Line: line,
|
||||
Column: column,
|
||||
Length: d.Len(),
|
||||
LineText: f.Text()[pos:d.End()],
|
||||
},
|
||||
Notes: []esbuild.Note{
|
||||
{Text: d.Category().Name()},
|
||||
{Text: fmt.Sprintf("%d", d.Code())},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
mainErr := esbuild.Message{Text: err.Error()}
|
||||
result.Errors = append([]esbuild.Message{mainErr}, esbuildMessages...)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if len(esbuildMessages) > 0 {
|
||||
result.Errors = esbuildMessages
|
||||
return result, nil
|
||||
}
|
||||
|
||||
result.Contents = &compiledJS
|
||||
result.Loader = esbuild.LoaderJS
|
||||
return result, nil
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type CDelegate[F any] struct {
|
||||
Func F
|
||||
null F
|
||||
Data unsafe.Pointer
|
||||
dataDestroy C.FridaDestroyFunc
|
||||
}
|
||||
|
||||
func NewCDelegate[F any](function F, data unsafe.Pointer, dataDestroy C.FridaDestroyFunc) *CDelegate[F] {
|
||||
return &CDelegate[F]{Func: function, Data: data, dataDestroy: dataDestroy}
|
||||
}
|
||||
|
||||
func (d *CDelegate[F]) Dispose() {
|
||||
if d.dataDestroy != nil {
|
||||
C.invoke_destroy_func(d.dataDestroy, d.Data)
|
||||
}
|
||||
d.Func = d.null
|
||||
d.Data = nil
|
||||
d.dataDestroy = nil
|
||||
}
|
||||
|
||||
func main() {}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
global:
|
||||
_frida_compiler_backend_*;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
@@ -0,0 +1,375 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
|
||||
def main(argv):
|
||||
go_config = json.loads(base64.b64decode(argv[1]).decode("utf-8"))
|
||||
output_dir, priv_dir, go, npm, *inputs = [Path(d).resolve() for d in argv[2:]]
|
||||
|
||||
try:
|
||||
build_backend(go_config, inputs, output_dir, priv_dir, go, npm)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e, file=sys.stderr)
|
||||
print(
|
||||
"Output:\n\t| " + "\n\t| ".join(e.output.strip().split("\n")),
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(2)
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def build_backend(
|
||||
config: dict,
|
||||
inputs: List[Path],
|
||||
output_dir: Path,
|
||||
priv_dir: Path,
|
||||
go: Path,
|
||||
npm: Path,
|
||||
):
|
||||
base_dir = min(inputs, key=lambda p: len(p.parts)).parent
|
||||
for f in inputs:
|
||||
dest = priv_dir / f.relative_to(base_dir)
|
||||
dest.parent.mkdir(exist_ok=True)
|
||||
shutil.copy(f, dest)
|
||||
|
||||
go_sources = [str(f.relative_to(base_dir)) for f in inputs if f.suffix == ".go"]
|
||||
|
||||
def run(*args):
|
||||
return subprocess.run(
|
||||
args,
|
||||
cwd=priv_dir,
|
||||
env=config["env"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
encoding="utf-8",
|
||||
check=True,
|
||||
)
|
||||
|
||||
run(npm, "install")
|
||||
# run(npm, "link", "/home/oleavr/src/frida-fs")
|
||||
|
||||
mode = config["mode"]
|
||||
|
||||
backend_stem = "frida-compiler-backend"
|
||||
if mode == "c-shared":
|
||||
backend_stem += "-raw"
|
||||
|
||||
backend_a = priv_dir / f"{backend_stem}.a"
|
||||
backend_shlib = priv_dir / (f"{backend_stem}" + config["shlib_suffix"])
|
||||
backend_h = priv_dir / f"{backend_stem}.h"
|
||||
|
||||
extra_go_args = config["extra_go_args"].copy()
|
||||
|
||||
if mode == "c-shared":
|
||||
version_script = priv_dir / "backend.version"
|
||||
extra_go_args.append(f"-ldflags=-linkmode=external -extldflags=-Wl,--version-script={version_script}")
|
||||
|
||||
run(
|
||||
go,
|
||||
"build",
|
||||
f"-buildmode={mode}",
|
||||
"-o",
|
||||
backend_a.name if mode == "c-archive" else backend_shlib.name,
|
||||
"-buildvcs=false",
|
||||
*extra_go_args,
|
||||
*go_sources,
|
||||
)
|
||||
|
||||
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"])
|
||||
|
||||
if (mingw := config.get("mingw")) is not None and (abi := config["abi"]) in {
|
||||
"x86",
|
||||
"x86_64",
|
||||
}:
|
||||
prefix = Path(mingw["prefix"])
|
||||
ar = config["ar"]
|
||||
|
||||
libmingwex_a = prefix / "lib" / "libmingwex.a"
|
||||
if not libmingwex_a.exists():
|
||||
libmingwex_a = prefix / mingw["triplet"] / "lib" / "libmingwex.a"
|
||||
|
||||
libgcc_objects = [
|
||||
"_chkstk_ms.o",
|
||||
]
|
||||
run(*ar, "x", mingw["libcc"], *libgcc_objects)
|
||||
|
||||
gwex_objflavor = "64" if abi == "x86_64" else "32"
|
||||
gwex_objects = [
|
||||
f"lib{gwex_objflavor}_libmingwex_a-{name}.o"
|
||||
for name in [
|
||||
"dmisc",
|
||||
"gdtoa",
|
||||
"gmisc",
|
||||
"mingw_fprintf",
|
||||
"mingw_pformat",
|
||||
"misc",
|
||||
]
|
||||
]
|
||||
run(*ar, "x", libmingwex_a, *gwex_objects)
|
||||
|
||||
extra_objects = []
|
||||
if abi == "x86_64":
|
||||
run(*ar, "x", backend_a.name, "go.o")
|
||||
sort_pdata_in_object(Path(priv_dir) / "go.o")
|
||||
extra_objects.append("go.o")
|
||||
|
||||
run(*ar, "rs", backend_a.name, *libgcc_objects, *gwex_objects, *extra_objects)
|
||||
|
||||
shutil.copy(priv_dir / backend_a.name, output_dir / backend_a.name)
|
||||
else:
|
||||
shutil.copy(priv_dir / backend_shlib.name, output_dir / backend_shlib.name)
|
||||
|
||||
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
|
||||
def sort_pdata_in_object(object_file: Path):
|
||||
data = bytearray(object_file.read_bytes())
|
||||
|
||||
# --- Parse COFF File Header (IMAGE_FILE_HEADER) ---
|
||||
# Format: WORD Machine; WORD NumberOfSections;
|
||||
# DWORD TimeDateStamp; DWORD PointerToSymbolTable;
|
||||
# DWORD NumberOfSymbols; WORD SizeOfOptionalHeader;
|
||||
# WORD Characteristics;
|
||||
IMAGE_FILE_HEADER_FMT = "<HHLLLHH"
|
||||
IMAGE_FILE_HEADER_SIZE = struct.calcsize(IMAGE_FILE_HEADER_FMT)
|
||||
|
||||
if len(data) < IMAGE_FILE_HEADER_SIZE:
|
||||
print("Error: File too small to be a valid COFF object", file=sys.stderr)
|
||||
return
|
||||
|
||||
(
|
||||
machine,
|
||||
number_of_sections,
|
||||
time_date_stamp,
|
||||
pointer_to_symbol_table,
|
||||
number_of_symbols,
|
||||
size_of_optional_header,
|
||||
characteristics,
|
||||
) = struct.unpack(IMAGE_FILE_HEADER_FMT, data[0:IMAGE_FILE_HEADER_SIZE])
|
||||
|
||||
# --- Parse Section Headers to find .pdata ---
|
||||
# Each IMAGE_SECTION_HEADER is 40 bytes: Name[8], Misc(VirtualSize), VirtualAddress,
|
||||
# SizeOfRawData, PointerToRawData, PointerToRelocations, PointerToLinenumbers,
|
||||
# NumberOfRelocations, NumberOfLinenumbers, Characteristics
|
||||
SECTION_HEADER_FMT = "<8sLLLLLLHHI"
|
||||
SECTION_HEADER_SIZE = struct.calcsize(SECTION_HEADER_FMT)
|
||||
section_table_offset = IMAGE_FILE_HEADER_SIZE + size_of_optional_header
|
||||
|
||||
pdata_section_index = None
|
||||
pdata_pointer_to_raw = None
|
||||
pdata_size_of_raw = None
|
||||
pdata_pointer_to_relocs = None
|
||||
pdata_number_of_relocs = None
|
||||
|
||||
# Keep all section headers for rewriting relocation entries later if needed
|
||||
section_headers = []
|
||||
|
||||
for i in range(number_of_sections):
|
||||
offset = section_table_offset + i * SECTION_HEADER_SIZE
|
||||
section_data = data[offset : offset + SECTION_HEADER_SIZE]
|
||||
(
|
||||
name,
|
||||
virtual_size,
|
||||
virtual_address,
|
||||
size_of_raw_data,
|
||||
pointer_to_raw_data,
|
||||
pointer_to_relocations,
|
||||
pointer_to_linenumbers,
|
||||
number_of_relocations,
|
||||
number_of_linenumbers,
|
||||
sec_characteristics,
|
||||
) = struct.unpack(SECTION_HEADER_FMT, section_data)
|
||||
|
||||
section_name = name.rstrip(b"\x00").decode("utf-8")
|
||||
section_headers.append(
|
||||
{
|
||||
"name": section_name,
|
||||
"offset": offset,
|
||||
"virtual_size": virtual_size,
|
||||
"virtual_address": virtual_address,
|
||||
"size_of_raw_data": size_of_raw_data,
|
||||
"pointer_to_raw_data": pointer_to_raw_data,
|
||||
"pointer_to_relocations": pointer_to_relocations,
|
||||
"number_of_relocations": number_of_relocations,
|
||||
}
|
||||
)
|
||||
|
||||
if section_name == ".pdata":
|
||||
pdata_section_index = i
|
||||
pdata_pointer_to_raw = pointer_to_raw_data
|
||||
pdata_size_of_raw = size_of_raw_data
|
||||
pdata_pointer_to_relocs = pointer_to_relocations
|
||||
pdata_number_of_relocs = number_of_relocations
|
||||
|
||||
if pdata_section_index is None:
|
||||
print("Error: No .pdata section found in the object file", file=sys.stderr)
|
||||
return
|
||||
|
||||
# --- Parse Symbol Table ---
|
||||
# Each IMAGE_SYMBOL is 18 bytes: Name[8], Value (4 bytes), SectionNumber (2 bytes),
|
||||
# Type (2 bytes), StorageClass (1 byte), NumberOfAuxSymbols (1 byte)
|
||||
SYMBOL_TABLE_ENTRY_SIZE = 18
|
||||
symbols = []
|
||||
for sym_index in range(number_of_symbols):
|
||||
sym_offset = pointer_to_symbol_table + sym_index * SYMBOL_TABLE_ENTRY_SIZE
|
||||
entry = data[sym_offset : sym_offset + SYMBOL_TABLE_ENTRY_SIZE]
|
||||
if len(entry) < SYMBOL_TABLE_ENTRY_SIZE:
|
||||
print("Error: Truncated symbol table entry", file=sys.stderr)
|
||||
return
|
||||
# Unpack: Name (8s), Value (L), SectionNumber (h), Type (H), StorageClass (B), NumberOfAuxSymbols (B)
|
||||
name_bytes, value, section_number, sym_type, storage_class, num_aux = (
|
||||
struct.unpack("<8sLhHBB", entry)
|
||||
)
|
||||
# Decode name: if name_bytes[0:4] are zeros, use string from offset in string table
|
||||
if name_bytes[:4] == b"\x00\x00\x00\x00":
|
||||
str_table_offset = struct.unpack("<L", name_bytes[4:])[0]
|
||||
# String table starts immediately after symbol table: size (4 bytes) + NUL-terminated strings
|
||||
str_tab_start = (
|
||||
pointer_to_symbol_table + number_of_symbols * SYMBOL_TABLE_ENTRY_SIZE
|
||||
)
|
||||
# Fetch the name from string table
|
||||
str_offset = str_tab_start + str_table_offset
|
||||
name_end = data.find(b"\x00", str_offset)
|
||||
symbol_name = data[str_offset:name_end].decode("utf-8")
|
||||
else:
|
||||
symbol_name = name_bytes.partition(b"\x00")[0].decode("utf-8")
|
||||
|
||||
symbols.append(
|
||||
{"name": symbol_name, "value": value, "section_number": section_number}
|
||||
)
|
||||
|
||||
# --- Read original .pdata section raw data ---
|
||||
pdata_data = data[pdata_pointer_to_raw : pdata_pointer_to_raw + pdata_size_of_raw]
|
||||
if len(pdata_data) % 12 != 0:
|
||||
print("Error: .pdata size is not a multiple of 12 bytes", file=sys.stderr)
|
||||
return
|
||||
num_entries = len(pdata_data) // 12
|
||||
|
||||
# --- Read relocation entries for .pdata ---
|
||||
# Each IMAGE_RELOCATION is 10 bytes: VirtualAddress (4), SymbolTableIndex (4), Type (2)
|
||||
RELOC_ENTRY_SIZE = 10
|
||||
reloc_entries = []
|
||||
reloc_table_start = pdata_pointer_to_relocs
|
||||
for rindex in range(pdata_number_of_relocs):
|
||||
reloff = reloc_table_start + rindex * RELOC_ENTRY_SIZE
|
||||
entry = data[reloff : reloff + RELOC_ENTRY_SIZE]
|
||||
if len(entry) < RELOC_ENTRY_SIZE:
|
||||
print("Error: Truncated relocation entry", file=sys.stderr)
|
||||
return
|
||||
va, sym_index, rtype = struct.unpack("<LLH", entry)
|
||||
reloc_entries.append(
|
||||
{"virtual_address": va, "symbol_index": sym_index, "type": rtype}
|
||||
)
|
||||
|
||||
# There should be 3 relocations per .pdata entry
|
||||
if len(reloc_entries) != num_entries * 3:
|
||||
print(
|
||||
"Warning: Expected {} relocations but found {}".format(
|
||||
num_entries * 3, len(reloc_entries)
|
||||
),
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# --- Associate each entry with its relocations and sorting key ---
|
||||
entries = []
|
||||
for i in range(num_entries):
|
||||
entry_bytes = pdata_data[i * 12 : (i + 1) * 12]
|
||||
entry_relocs = reloc_entries[i * 3 : i * 3 + 3]
|
||||
|
||||
# Sorting key: use symbol.Value of first relocation"s symbol index
|
||||
sym_idx = entry_relocs[0]["symbol_index"]
|
||||
if sym_idx < 0 or sym_idx >= number_of_symbols:
|
||||
key = 0
|
||||
else:
|
||||
key = symbols[sym_idx]["value"]
|
||||
|
||||
entries.append({"entry_bytes": entry_bytes, "relocs": entry_relocs, "key": key})
|
||||
|
||||
# --- Sort entries by computed key ---
|
||||
entries.sort(key=lambda x: x["key"])
|
||||
|
||||
# --- Build new .pdata data buffer ---
|
||||
sorted_pdata = bytearray()
|
||||
for e in entries:
|
||||
sorted_pdata.extend(e["entry_bytes"])
|
||||
|
||||
# --- Build new relocation table buffer ---
|
||||
new_reloc_buf = bytearray()
|
||||
for new_i, e in enumerate(entries):
|
||||
for reloc in e["relocs"]:
|
||||
field_offset = reloc["virtual_address"] % 12
|
||||
new_va = new_i * 12 + field_offset
|
||||
packed = struct.pack("<LLH", new_va, reloc["symbol_index"], reloc["type"])
|
||||
new_reloc_buf.extend(packed)
|
||||
|
||||
# --- Write back sorted .pdata and new relocations into data buffer ---
|
||||
# Replace .pdata raw data
|
||||
data[pdata_pointer_to_raw : pdata_pointer_to_raw + pdata_size_of_raw] = sorted_pdata
|
||||
# Replace relocation entries
|
||||
data[pdata_pointer_to_relocs : pdata_pointer_to_relocs + len(new_reloc_buf)] = (
|
||||
new_reloc_buf
|
||||
)
|
||||
|
||||
# --- Emit the fixed object file ---
|
||||
object_file.write_bytes(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -1,3 +1,2 @@
|
||||
[resource-compiler]
|
||||
namespace = Frida.Data.Compiler
|
||||
compress = *.js:text:7; *.bin:generic:7
|
||||
@@ -1,3 +0,0 @@
|
||||
[resource-compiler]
|
||||
namespace = Frida.Data.Compiler
|
||||
compress = *.js:text:11; *.bin:generic:11
|
||||
+288
-338
@@ -5,59 +5,52 @@ namespace Frida {
|
||||
public signal void output (string bundle);
|
||||
public signal void diagnostics (Variant diagnostics);
|
||||
|
||||
public DeviceManager manager {
|
||||
get;
|
||||
construct;
|
||||
private size_t watch_session_handle = 0;
|
||||
private Gee.Queue<Diagnostic> pending_diagnostics = new Gee.ArrayQueue<Diagnostic> ();
|
||||
|
||||
private MainContext main_context;
|
||||
|
||||
// TODO: Remove the DeviceManager parameter.
|
||||
public Compiler (DeviceManager? manager = null) {
|
||||
Object ();
|
||||
}
|
||||
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
private Promise<Agent>? load_request;
|
||||
private Gee.Map<uint, MonitorEntry> monitors = new Gee.HashMap<uint, MonitorEntry> ();
|
||||
private Gee.Set<MonitorEntry> dirty_monitors = new Gee.HashSet<MonitorEntry> ();
|
||||
private TimeoutSource? monitor_flush_timer;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
#endif
|
||||
|
||||
public Compiler (DeviceManager manager) {
|
||||
Object (manager: manager);
|
||||
static construct {
|
||||
CompilerBackend.init ();
|
||||
}
|
||||
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
public override void dispose () {
|
||||
if (load_request != null)
|
||||
close.begin ();
|
||||
|
||||
base.dispose ();
|
||||
construct {
|
||||
main_context = Frida.get_main_context ();
|
||||
}
|
||||
|
||||
private async void close () {
|
||||
try {
|
||||
var agent = yield get_agent (null);
|
||||
yield agent.close (null);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
load_request = null;
|
||||
|
||||
if (monitor_flush_timer != null) {
|
||||
monitor_flush_timer.destroy ();
|
||||
monitor_flush_timer = null;
|
||||
}
|
||||
|
||||
foreach (var entry in monitors.values)
|
||||
detach_monitor_entry (entry);
|
||||
monitors.clear ();
|
||||
~Compiler () {
|
||||
cancel_watch ();
|
||||
}
|
||||
#endif
|
||||
|
||||
public async string build (string entrypoint, BuildOptions? options = null, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
var agent = yield get_agent (cancellable);
|
||||
CompilerBackend.check_available ();
|
||||
|
||||
BuildOptions opts = (options != null) ? options : new BuildOptions ();
|
||||
string project_root = compute_project_root (entrypoint, opts);
|
||||
|
||||
starting ();
|
||||
try {
|
||||
string bundle = yield agent.build (entrypoint, options, cancellable);
|
||||
string? bundle = null;
|
||||
string? error_message = null;
|
||||
CompilerBackend.BuildCompleteFunc on_complete = (b, e) => {
|
||||
bundle = b;
|
||||
error_message = e;
|
||||
schedule_on_frida_thread (build.callback);
|
||||
};
|
||||
|
||||
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);
|
||||
yield;
|
||||
|
||||
if (error_message != null)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", error_message);
|
||||
|
||||
output (bundle);
|
||||
|
||||
@@ -65,9 +58,6 @@ namespace Frida {
|
||||
} finally {
|
||||
finished ();
|
||||
}
|
||||
#else
|
||||
throw_not_supported ();
|
||||
#endif
|
||||
}
|
||||
|
||||
public string build_sync (string entrypoint, BuildOptions? options = null, Cancellable? cancellable = null)
|
||||
@@ -89,13 +79,38 @@ namespace Frida {
|
||||
|
||||
public async void watch (string entrypoint, WatchOptions? options = null, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
var agent = yield get_agent (cancellable);
|
||||
CompilerBackend.check_available ();
|
||||
|
||||
yield agent.watch (entrypoint, options, cancellable);
|
||||
#else
|
||||
throw_not_supported ();
|
||||
#endif
|
||||
WatchOptions opts = (options != null) ? options : new WatchOptions ();
|
||||
string project_root = compute_project_root (entrypoint, opts);
|
||||
|
||||
cancel_watch ();
|
||||
|
||||
size_t session_handle = 0;
|
||||
string? error_message = null;
|
||||
CompilerBackend.WatchReadyFunc on_ready = (h, e) => {
|
||||
session_handle = h;
|
||||
error_message = e;
|
||||
schedule_on_frida_thread (watch.callback);
|
||||
};
|
||||
|
||||
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,
|
||||
on_output, on_diagnostic);
|
||||
yield;
|
||||
|
||||
if (error_message != null)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", error_message);
|
||||
|
||||
watch_session_handle = session_handle;
|
||||
}
|
||||
|
||||
private void cancel_watch () {
|
||||
if (watch_session_handle != 0) {
|
||||
CompilerBackend.WatchSession.dispose (watch_session_handle);
|
||||
watch_session_handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void watch_sync (string entrypoint, WatchOptions? options = null, Cancellable? cancellable = null)
|
||||
@@ -115,343 +130,238 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
private void on_watch_add (uint id, string path, string type, int64 polling_interval) {
|
||||
var file = File.parse_name (path);
|
||||
private void on_starting () {
|
||||
schedule_on_frida_thread (() => {
|
||||
starting ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
GLib.FileMonitor monitor;
|
||||
try {
|
||||
if (type == "file")
|
||||
monitor = file.monitor_file (FileMonitorFlags.SEND_MOVED, io_cancellable);
|
||||
else if (type == "directory")
|
||||
monitor = file.monitor_directory (FileMonitorFlags.NONE, io_cancellable);
|
||||
else
|
||||
assert_not_reached ();
|
||||
} catch (GLib.Error e) {
|
||||
return;
|
||||
private void on_finished () {
|
||||
schedule_on_frida_thread (() => {
|
||||
finished ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void on_output (owned string bundle) {
|
||||
schedule_on_frida_thread (() => {
|
||||
output (bundle);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void on_diagnostic (owned string category, int code, owned string? path, int line, int character,
|
||||
owned string text) {
|
||||
var diag = new Diagnostic () {
|
||||
category = category,
|
||||
code = code,
|
||||
path = path,
|
||||
line = line,
|
||||
character = character,
|
||||
text = text,
|
||||
};
|
||||
|
||||
bool schedule_emit = false;
|
||||
lock (pending_diagnostics) {
|
||||
schedule_emit = pending_diagnostics.is_empty;
|
||||
pending_diagnostics.add (diag);
|
||||
}
|
||||
|
||||
if (polling_interval != 0)
|
||||
monitor.rate_limit = (int) polling_interval;
|
||||
|
||||
var entry = new MonitorEntry (id, monitor);
|
||||
entry.file_changed.connect (on_file_changed);
|
||||
monitors[id] = entry;
|
||||
}
|
||||
|
||||
private void on_watch_remove (uint id) {
|
||||
MonitorEntry? entry;
|
||||
if (monitors.unset (id, out entry))
|
||||
detach_monitor_entry (entry);
|
||||
}
|
||||
|
||||
private void detach_monitor_entry (MonitorEntry entry) {
|
||||
dirty_monitors.remove (entry);
|
||||
|
||||
entry.file_changed.disconnect (on_file_changed);
|
||||
entry.destroy ();
|
||||
}
|
||||
|
||||
private void on_file_changed (MonitorEntry entry) {
|
||||
dirty_monitors.add (entry);
|
||||
|
||||
if (monitor_flush_timer == null) {
|
||||
var source = new TimeoutSource (50);
|
||||
source.set_callback (on_monitor_flush_tick);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
monitor_flush_timer = source;
|
||||
if (schedule_emit) {
|
||||
schedule_on_frida_thread (() => {
|
||||
emit_pending_diagnostics ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool on_monitor_flush_tick () {
|
||||
var settled = new Gee.ArrayList<MonitorEntry> ();
|
||||
var now = get_monotonic_time ();
|
||||
settled.add_all_iterator (dirty_monitors.filter (m => now - m.last_change >= 50000));
|
||||
|
||||
Agent agent = load_request.future.value;
|
||||
foreach (var entry in settled) {
|
||||
agent.notify_file_changed (entry.id, entry.state, io_cancellable);
|
||||
dirty_monitors.remove (entry);
|
||||
private void emit_pending_diagnostics () {
|
||||
var batch = new Gee.ArrayList<Diagnostic> ();
|
||||
lock (pending_diagnostics) {
|
||||
batch.add_all (pending_diagnostics);
|
||||
pending_diagnostics.clear ();
|
||||
}
|
||||
|
||||
if (!dirty_monitors.is_empty)
|
||||
return Source.CONTINUE;
|
||||
var builder = new VariantBuilder (new VariantType.array (VariantType.VARDICT));
|
||||
|
||||
monitor_flush_timer = null;
|
||||
return Source.REMOVE;
|
||||
}
|
||||
|
||||
private async Agent get_agent (Cancellable? cancellable) throws Error, IOError {
|
||||
Future<Agent> future;
|
||||
if (load_request != null) {
|
||||
future = load_request.future;
|
||||
} else {
|
||||
load_request = new Promise<Agent> ();
|
||||
future = load_request.future;
|
||||
|
||||
load_agent.begin ();
|
||||
foreach (var d in batch) {
|
||||
builder.open (VariantType.VARDICT);
|
||||
builder.add ("{sv}", "category", new Variant.string (d.category));
|
||||
builder.add ("{sv}", "code", new Variant.int64 (d.code));
|
||||
if (d.path != null) {
|
||||
var b = new VariantBuilder (VariantType.VARDICT);
|
||||
b.add ("{sv}", "path", new Variant.string (d.path));
|
||||
b.add ("{sv}", "line", new Variant.int64 (d.line));
|
||||
b.add ("{sv}", "character", new Variant.int64 (d.character));
|
||||
builder.add ("{sv}", "file", b.end ());
|
||||
}
|
||||
builder.add ("{sv}", "text", new Variant.string (d.text));
|
||||
builder.close ();
|
||||
}
|
||||
|
||||
return yield future.wait_async (cancellable);
|
||||
diagnostics (builder.end ());
|
||||
}
|
||||
|
||||
private async void load_agent () {
|
||||
try {
|
||||
var device = yield manager.get_device_by_type (LOCAL, 0, io_cancellable);
|
||||
var session = yield device.get_host_session (io_cancellable);
|
||||
var agent = new Agent (this, (LocalHostSession) session);
|
||||
|
||||
load_request.resolve (agent);
|
||||
} catch (GLib.Error e) {
|
||||
if ((e is Error || e is IOError.CANCELLED))
|
||||
e = new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
|
||||
load_request.reject (e);
|
||||
load_request = null;
|
||||
}
|
||||
}
|
||||
#else
|
||||
[NoReturn]
|
||||
private void throw_not_supported () throws Error {
|
||||
throw new Error.NOT_SUPPORTED ("Compiler backend disabled at build-time");
|
||||
}
|
||||
#endif
|
||||
|
||||
private T create<T> () {
|
||||
return Object.new (typeof (T), parent: this);
|
||||
}
|
||||
|
||||
protected void schedule_on_frida_thread (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
private class Diagnostic {
|
||||
public string category;
|
||||
public int code;
|
||||
public string? path;
|
||||
public int line;
|
||||
public int character;
|
||||
public string text;
|
||||
}
|
||||
|
||||
private abstract class CompilerTask<T> : AsyncTask<T> {
|
||||
public weak Compiler parent {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace CompilerBackend {
|
||||
private void init () {
|
||||
#if HAVE_COMPILER_BACKEND
|
||||
private class Agent : InternalAgent {
|
||||
public weak Compiler parent {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Promise<string> source_request = new Promise<string> ();
|
||||
private Promise<Bytes?> snapshot_request = new Promise<Bytes?> ();
|
||||
|
||||
public Agent (Compiler parent, LocalHostSession host_session) {
|
||||
Object (
|
||||
parent: parent,
|
||||
host_session: host_session,
|
||||
#if HAVE_V8
|
||||
script_runtime: ScriptRuntime.V8
|
||||
#if COMPILER_BACKEND_STATIC_COMPILATION
|
||||
_init_go_runtime ();
|
||||
build = (BuildFunc) _build;
|
||||
watch = (WatchFunc) _watch;
|
||||
WatchSession.dispose = (WatchSession.DisposeFunc) WatchSession._dispose;
|
||||
#else
|
||||
script_runtime: ScriptRuntime.DEFAULT
|
||||
#endif
|
||||
);
|
||||
}
|
||||
unowned uint8[] backend_so = Frida.Data.Compiler.get_frida_compiler_backend_so_blob ().data;
|
||||
|
||||
construct {
|
||||
load_resources ();
|
||||
}
|
||||
FileDescriptor fd;
|
||||
if (MemoryFileDescriptor.is_supported ()) {
|
||||
fd = MemoryFileDescriptor.from_bytes ("frida-compiler-backend.so", new Bytes.static (backend_so));
|
||||
} else {
|
||||
string name_used;
|
||||
try {
|
||||
fd = new FileDescriptor (FileUtils.open_tmp ("frida-compiler-backend-XXXXXX.so", out name_used));
|
||||
FileUtils.unlink (name_used);
|
||||
|
||||
private void load_resources () {
|
||||
new Thread<void> ("compiler-agent-loader", () => {
|
||||
var agent_js_blob = Data.Compiler.get_agent_js_blob ();
|
||||
Bytes agent_js_bytes = decompress (agent_js_blob.data, agent_js_blob.uncompressed_size);
|
||||
source_request.resolve ((string) agent_js_bytes.get_data ());
|
||||
var output = new UnixOutputStream (fd.handle, false);
|
||||
output.write_all (backend_so, null);
|
||||
|
||||
var snapshot_blob = Data.Compiler.get_snapshot_bin_blob ();
|
||||
Bytes snapshot = decompress (snapshot_blob.data, snapshot_blob.uncompressed_size);
|
||||
snapshot_request.resolve ((snapshot.get_size () != 0) ? snapshot : null);
|
||||
});
|
||||
}
|
||||
|
||||
public async string build (string entrypoint, BuildOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
BuildOptions opts = (options != null) ? options : new BuildOptions ();
|
||||
|
||||
Json.Node bundle = yield call ("build", new Json.Node[] {
|
||||
new Json.Node.alloc ().init_string (compute_project_root (entrypoint, opts)),
|
||||
new Json.Node.alloc ().init_string (entrypoint),
|
||||
new Json.Node.alloc ().init_string (opts.source_maps.to_nick ()),
|
||||
new Json.Node.alloc ().init_string (opts.compression.to_nick ()),
|
||||
}, null, cancellable);
|
||||
return bundle.get_string ();
|
||||
}
|
||||
|
||||
public async void watch (string entrypoint, WatchOptions? options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
WatchOptions opts = (options != null) ? options : new WatchOptions ();
|
||||
|
||||
yield call ("watch", new Json.Node[] {
|
||||
new Json.Node.alloc ().init_string (compute_project_root (entrypoint, opts)),
|
||||
new Json.Node.alloc ().init_string (entrypoint),
|
||||
new Json.Node.alloc ().init_string (opts.source_maps.to_nick ()),
|
||||
new Json.Node.alloc ().init_string (opts.compression.to_nick ()),
|
||||
}, null, cancellable);
|
||||
}
|
||||
|
||||
private static string compute_project_root (string entrypoint, CompilerOptions options) {
|
||||
string? project_root = options.project_root;
|
||||
|
||||
if (project_root != null)
|
||||
return project_root;
|
||||
|
||||
if (Path.is_absolute (entrypoint))
|
||||
return Path.get_dirname (entrypoint);
|
||||
|
||||
return Environment.get_current_dir ();
|
||||
}
|
||||
|
||||
public void notify_file_changed (uint id, FileState state, Cancellable? cancellable) {
|
||||
var notification = new Json.Builder ();
|
||||
notification
|
||||
.begin_object ()
|
||||
.set_member_name ("type")
|
||||
.add_string_value ("watch:change")
|
||||
.set_member_name ("id")
|
||||
.add_int_value (id)
|
||||
.set_member_name ("state")
|
||||
.add_string_value (Marshal.enum_to_nick<FileState> (state))
|
||||
.end_object ();
|
||||
post.begin (notification.get_root (), cancellable);
|
||||
}
|
||||
|
||||
protected override async uint get_target_pid (Cancellable? cancellable) throws Error, IOError {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override async string? load_source (Cancellable? cancellable) throws Error, IOError {
|
||||
return yield source_request.future.wait_async (cancellable);
|
||||
}
|
||||
|
||||
protected override async Bytes? load_snapshot (Cancellable? cancellable, out SnapshotTransport transport)
|
||||
throws Error, IOError {
|
||||
transport = SHARED_MEMORY;
|
||||
return yield snapshot_request.future.wait_async (cancellable);
|
||||
}
|
||||
|
||||
protected override void on_event (string type, Json.Array event) {
|
||||
if (type == "diagnostics") {
|
||||
on_build_diagnostics (event.get_array_element (1));
|
||||
} else if (type == "watch:compilation-starting") {
|
||||
parent.starting ();
|
||||
} else if (type == "watch:compilation-finished") {
|
||||
parent.finished ();
|
||||
} else if (type == "watch:bundle-updated") {
|
||||
on_watch_bundle_updated (event.get_string_element (1));
|
||||
} else if (type == "watch:add") {
|
||||
parent.on_watch_add ((uint) event.get_int_element (1), event.get_string_element (2),
|
||||
event.get_string_element (3), event.get_int_element (4));
|
||||
} else if (type == "watch:remove") {
|
||||
parent.on_watch_remove ((uint) event.get_int_element (1));
|
||||
} else {
|
||||
Posix.lseek (fd.handle, 0, Posix.SEEK_SET);
|
||||
} catch (GLib.Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
private void on_build_diagnostics (Json.Array diagnostics) {
|
||||
uint n = diagnostics.get_length ();
|
||||
|
||||
var builder = new VariantBuilder (new VariantType.array (VariantType.VARDICT));
|
||||
for (uint i = 0; i != n; i++) {
|
||||
var diag = diagnostics.get_array_element (i);
|
||||
builder.open (VariantType.VARDICT);
|
||||
builder.add ("{sv}", "category", new Variant.string (diag.get_string_element (0)));
|
||||
builder.add ("{sv}", "code", new Variant.int64 (diag.get_int_element (1)));
|
||||
if (!diag.get_null_element (2)) {
|
||||
var file = diag.get_array_element (2);
|
||||
var b = new VariantBuilder (VariantType.VARDICT);
|
||||
b.add ("{sv}", "path", new Variant.string (file.get_string_element (0)));
|
||||
b.add ("{sv}", "line", new Variant.int64 (file.get_int_element (1)));
|
||||
b.add ("{sv}", "character", new Variant.int64 (file.get_int_element (2)));
|
||||
builder.add ("{sv}", "file", b.end ());
|
||||
}
|
||||
builder.add ("{sv}", "text", new Variant.string (diag.get_string_element (3)));
|
||||
builder.close ();
|
||||
}
|
||||
|
||||
parent.diagnostics (builder.end ());
|
||||
}
|
||||
|
||||
private void on_watch_bundle_updated (string bundle) {
|
||||
parent.output (bundle);
|
||||
}
|
||||
|
||||
private static Bytes decompress (uint8[] data, uint uncompressed_size) {
|
||||
var decoder = new Brotli.Decoder ();
|
||||
decoder.set_parameter (LARGE_WINDOW, 1);
|
||||
|
||||
uint8[] uncompressed_data = new uint8[uncompressed_size + 1];
|
||||
size_t available_in = data.length;
|
||||
size_t available_out = uncompressed_size;
|
||||
uint8 * next_in = data;
|
||||
uint8 * next_out = uncompressed_data;
|
||||
decoder.decompress_stream (&available_in, &next_in, &available_out, &next_out);
|
||||
uncompressed_data.length = (int) (next_out - (uint8 *) uncompressed_data);
|
||||
|
||||
return new Bytes.take ((owned) uncompressed_data);
|
||||
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");
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
private class MonitorEntry : Object {
|
||||
public signal void file_changed ();
|
||||
|
||||
public uint id;
|
||||
public GLib.FileMonitor monitor;
|
||||
|
||||
public FileState state = PRISTINE;
|
||||
public int64 last_change = -1;
|
||||
|
||||
public MonitorEntry (uint id, GLib.FileMonitor monitor) {
|
||||
this.id = id;
|
||||
this.monitor = monitor;
|
||||
|
||||
monitor.changed.connect (on_file_changed);
|
||||
}
|
||||
|
||||
public void destroy () {
|
||||
monitor.changed.disconnect (on_file_changed);
|
||||
monitor.cancel ();
|
||||
}
|
||||
|
||||
private void on_file_changed (File file, File? other_file, FileMonitorEvent event) {
|
||||
switch (event) {
|
||||
case CREATED:
|
||||
state = PRISTINE;
|
||||
break;
|
||||
case CHANGED:
|
||||
if (state != PRISTINE)
|
||||
state = MODIFIED;
|
||||
break;
|
||||
case DELETED:
|
||||
case MOVED:
|
||||
state = DELETED;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
last_change = get_monotonic_time ();
|
||||
|
||||
file_changed ();
|
||||
}
|
||||
private void check_available () throws Error {
|
||||
if (build == null)
|
||||
throw new Error.NOT_SUPPORTED ("Compiler backend disabled at build-time");
|
||||
}
|
||||
|
||||
private enum FileState {
|
||||
PRISTINE,
|
||||
MODIFIED,
|
||||
DELETED,
|
||||
private BuildFunc? build;
|
||||
private WatchFunc? watch;
|
||||
|
||||
[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);
|
||||
|
||||
[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);
|
||||
|
||||
#if COMPILER_BACKEND_STATIC_COMPILATION
|
||||
private extern void _init_go_runtime ();
|
||||
private extern void _build ();
|
||||
private extern void _watch ();
|
||||
#endif
|
||||
|
||||
namespace WatchSession {
|
||||
[CCode (has_target = false)]
|
||||
private delegate void DisposeFunc (size_t handle);
|
||||
|
||||
private DisposeFunc? dispose;
|
||||
|
||||
#if COMPILER_BACKEND_STATIC_COMPILATION
|
||||
private extern void _dispose ();
|
||||
#endif
|
||||
}
|
||||
|
||||
private delegate void BuildCompleteFunc (owned string? bundle, owned string? error_message);
|
||||
private delegate void WatchReadyFunc (size_t session_handle, owned string? error_message);
|
||||
private delegate void StartingFunc ();
|
||||
private delegate void FinishedFunc ();
|
||||
private delegate void OutputFunc (owned string bundle);
|
||||
private delegate void DiagnosticFunc (owned string category, int code, owned string? path, int line, int character,
|
||||
owned string text);
|
||||
|
||||
#if HAVE_COMPILER_BACKEND && !COMPILER_BACKEND_STATIC_COMPILATION
|
||||
private static T resolve_symbol<T> (Module m, string name) {
|
||||
void * address;
|
||||
if (!m.symbol (name, out address))
|
||||
assert_not_reached ();
|
||||
return (T) address;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private string compute_project_root (string entrypoint, CompilerOptions options) {
|
||||
string? project_root = options.project_root;
|
||||
|
||||
if (project_root != null)
|
||||
return project_root;
|
||||
|
||||
if (Path.is_absolute (entrypoint))
|
||||
return Path.get_dirname (entrypoint);
|
||||
|
||||
return Environment.get_current_dir ();
|
||||
}
|
||||
|
||||
public class CompilerOptions : Object {
|
||||
public string? project_root {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public OutputFormat output_format {
|
||||
get;
|
||||
set;
|
||||
default = UNESCAPED;
|
||||
}
|
||||
|
||||
public BundleFormat bundle_format {
|
||||
get;
|
||||
set;
|
||||
default = ESM;
|
||||
}
|
||||
|
||||
public TypeCheckMode type_check {
|
||||
get;
|
||||
set;
|
||||
default = FULL;
|
||||
}
|
||||
|
||||
public SourceMaps source_maps {
|
||||
get;
|
||||
set;
|
||||
@@ -471,6 +381,46 @@ namespace Frida {
|
||||
public sealed class WatchOptions : CompilerOptions {
|
||||
}
|
||||
|
||||
public enum OutputFormat {
|
||||
UNESCAPED,
|
||||
HEX_BYTES,
|
||||
C_STRING;
|
||||
|
||||
public static OutputFormat from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<OutputFormat> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<OutputFormat> (this);
|
||||
}
|
||||
}
|
||||
|
||||
public enum BundleFormat {
|
||||
ESM,
|
||||
IIFE;
|
||||
|
||||
public static BundleFormat from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<BundleFormat> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<BundleFormat> (this);
|
||||
}
|
||||
}
|
||||
|
||||
public enum TypeCheckMode {
|
||||
FULL,
|
||||
NONE;
|
||||
|
||||
public static TypeCheckMode from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<TypeCheckMode> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<TypeCheckMode> (this);
|
||||
}
|
||||
}
|
||||
|
||||
public enum SourceMaps {
|
||||
INCLUDED,
|
||||
OMITTED;
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
INPUTS = [
|
||||
"agent-entrypoint.js",
|
||||
"agent-core.ts",
|
||||
"agent-warmup.js",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"tsconfig.json",
|
||||
"rollup.config.agent-core.ts",
|
||||
"rollup.config.typescript.ts",
|
||||
]
|
||||
|
||||
|
||||
def main(argv):
|
||||
output_dir, priv_dir, input_dir, npm, v8_mksnapshot = [Path(d).resolve() if d else None for d in argv[1:6]]
|
||||
host_os_family, host_arch, host_cpu_mode = argv[6:9]
|
||||
|
||||
try:
|
||||
generate_agent(output_dir, priv_dir, input_dir, npm, v8_mksnapshot, host_os_family, host_arch, host_cpu_mode)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e, file=sys.stderr)
|
||||
print("Output:\n\t| " + "\n\t| ".join(e.output.strip().split("\n")), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def generate_agent(output_dir, priv_dir, input_dir, npm, v8_mksnapshot, host_os_family, host_arch, host_cpu_mode):
|
||||
entrypoint = input_dir / "agent-entrypoint.js"
|
||||
priv_dir.mkdir(exist_ok=True)
|
||||
for name in INPUTS:
|
||||
if name == "agent-entrypoint.js":
|
||||
continue
|
||||
shutil.copy(input_dir / name, priv_dir / name)
|
||||
|
||||
run_kwargs = {
|
||||
"cwd": priv_dir,
|
||||
"stdout": subprocess.PIPE,
|
||||
"stderr": subprocess.STDOUT,
|
||||
"encoding": "utf-8",
|
||||
"check": True,
|
||||
}
|
||||
|
||||
subprocess.run([npm, "install"], **run_kwargs)
|
||||
#subprocess.run([npm, "link", "/Users/oleavr/src/frida-compile"], **run_kwargs)
|
||||
|
||||
components = ["typescript", "agent-core"]
|
||||
for component in components:
|
||||
subprocess.run([
|
||||
npm, "run", "build:" + component,
|
||||
"--",
|
||||
"--environment", f"FRIDA_HOST_OS_FAMILY:{host_os_family},FRIDA_HOST_ARCH:{host_arch},FRIDA_HOST_CPU_MODE:{host_cpu_mode}",
|
||||
"--silent",
|
||||
],
|
||||
**run_kwargs)
|
||||
chunks = []
|
||||
for component in components:
|
||||
script = (priv_dir / f"{component}.js").read_text(encoding="utf-8")
|
||||
chunks.append(script)
|
||||
components_source = "\n".join(chunks)
|
||||
|
||||
agent = output_dir / "agent.js"
|
||||
snapshot = output_dir / "snapshot.bin"
|
||||
|
||||
if v8_mksnapshot is not None:
|
||||
shutil.copy(entrypoint, agent)
|
||||
(priv_dir / "embed.js").write_text(components_source, encoding="utf-8")
|
||||
subprocess.run([
|
||||
v8_mksnapshot,
|
||||
"--turbo-instruction-scheduling",
|
||||
f"--startup-blob={snapshot}",
|
||||
"embed.js",
|
||||
input_dir / "agent-warmup.js",
|
||||
],
|
||||
**run_kwargs)
|
||||
else:
|
||||
agent.write_text("\n".join([
|
||||
components_source,
|
||||
entrypoint.read_text(encoding="utf-8"),
|
||||
]), encoding="utf-8")
|
||||
snapshot.write_bytes(b"")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -0,0 +1,14 @@
|
||||
module github.com/frida/frida-core-compiler
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/evanw/esbuild v0.25.5
|
||||
github.com/frida/typescript-go v0.0.0-20250601212135-a4721f45f19c
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/evanw/esbuild v0.25.5 h1:E+JpeY5S/1LFmnX1vtuZqUKT7qDVcfXdhzMhM3uIKFs=
|
||||
github.com/evanw/esbuild v0.25.5/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/frida/typescript-go v0.0.0-20250601212135-a4721f45f19c h1:IWBI9AwSFLWymI4enx1jrAw0E4H2E+qHwsihv+AxSoE=
|
||||
github.com/frida/typescript-go v0.0.0-20250601212135-a4721f45f19c/go.mod h1:L4RMzD/pFBl7CqIf9XTrX41RO2xcok3OOVVcowv0H4k=
|
||||
github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8 h1:o8UqXPI6SVwQt04RGsqKp3qqmbOfTNMqDrWsc4O47kk=
|
||||
github.com/go-json-experiment/json v0.0.0-20250517221953-25912455fbc8/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/peter-evans/patience v0.3.0 h1:rX0JdJeepqdQl1Sk9c9uvorjYYzL2TfgLX1adqYm9cA=
|
||||
github.com/peter-evans/patience v0.3.0/go.mod h1:Kmxu5sY1NmBLFSStvXjX1wS9mIv7wMcP/ubucyMOAu0=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
+45
-41
@@ -1,61 +1,65 @@
|
||||
compiler_sources = files(
|
||||
'compiler.vala',
|
||||
'backend-glue.c',
|
||||
)
|
||||
|
||||
if have_compiler_backend
|
||||
compiler_mksnapshot = generate_compiler_snapshot ? v8_mksnapshot : ''
|
||||
|
||||
compiler_agent = custom_target('frida-compiler-agent',
|
||||
compiler_backend = custom_target('frida-compiler-backend',
|
||||
input: [
|
||||
'agent-entrypoint.js',
|
||||
'agent-core.ts',
|
||||
'agent-warmup.js',
|
||||
'go.mod',
|
||||
'go.sum',
|
||||
'backend.go',
|
||||
'backend.version',
|
||||
'tscompiler.go',
|
||||
'tsconfig.go',
|
||||
'shims.go',
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'tsconfig.json',
|
||||
'rollup.config.agent-core.ts',
|
||||
'rollup.config.typescript.ts',
|
||||
],
|
||||
output: [
|
||||
'agent.js',
|
||||
'snapshot.bin',
|
||||
'frida-compiler-backend' + (compiler_backend_static_compilation ? '.a' : ('-raw' + shlib_suffix)),
|
||||
'frida-compiler-backend.h',
|
||||
],
|
||||
command: [
|
||||
python,
|
||||
files('generate-agent.py'),
|
||||
files('build-backend.py'),
|
||||
go_config,
|
||||
meson.current_build_dir(),
|
||||
'@PRIVATE_DIR@',
|
||||
meson.current_source_dir(),
|
||||
go,
|
||||
npm,
|
||||
compiler_mksnapshot,
|
||||
host_os_family,
|
||||
host_arch_gumjs,
|
||||
host_cpu_mode,
|
||||
'@INPUT@',
|
||||
],
|
||||
)
|
||||
compiler_data = custom_target('frida-data-compiler',
|
||||
input: [
|
||||
'compiler.resources.' + frida_profile,
|
||||
compiler_agent,
|
||||
],
|
||||
output: [
|
||||
'frida-data-compiler.vapi',
|
||||
'frida-data-compiler.h',
|
||||
'frida-data-compiler.c',
|
||||
'frida-data-compiler-blob' + resource_blob_extension,
|
||||
'frida-data-compiler-agent.js.br',
|
||||
'frida-data-compiler-snapshot.bin.br',
|
||||
],
|
||||
command: [
|
||||
resource_compiler_cmd_array,
|
||||
'-c', '@INPUT0@',
|
||||
'-o', meson.current_build_dir() / 'frida-data-compiler',
|
||||
'@INPUT1@',
|
||||
'@INPUT2@',
|
||||
],
|
||||
)
|
||||
compiler_sources += compiler_data
|
||||
if compiler_backend_static_compilation
|
||||
backend_libs += compiler_backend[0]
|
||||
else
|
||||
filename = 'frida-compiler-backend' + shlib_suffix
|
||||
identity = (host_os_family == 'darwin') ? 'FridaCompilerBackend' : filename
|
||||
compiler_backend_shlib = custom_target('frida-compiler-backend-shlib',
|
||||
input: compiler_backend[0],
|
||||
output: filename,
|
||||
command: post_process + ['shared-library', identity, 'elf-cleaner:off'],
|
||||
)
|
||||
|
||||
backend_vala_args_private += '--pkg=libbrotlidec'
|
||||
backend_deps_private += brotlidec_dep
|
||||
compiler_data = custom_target('frida-data-compiler',
|
||||
input: [
|
||||
'compiler.resources',
|
||||
compiler_backend_shlib,
|
||||
],
|
||||
output: [
|
||||
'frida-data-compiler.vapi',
|
||||
'frida-data-compiler.h',
|
||||
'frida-data-compiler.c',
|
||||
'frida-data-compiler-blob' + resource_blob_extension,
|
||||
],
|
||||
command: [
|
||||
resource_compiler_cmd_array,
|
||||
'-c', '@INPUT0@',
|
||||
'-o', meson.current_build_dir() / 'frida-data-compiler',
|
||||
'@INPUT1@',
|
||||
],
|
||||
)
|
||||
backend_sources += compiler_data
|
||||
endif
|
||||
endif
|
||||
Generated
+104
-1761
File diff suppressed because it is too large.
Load diff
+31
-22
@@ -1,27 +1,36 @@
|
||||
{
|
||||
"name": "frida-compiler-agent",
|
||||
"name": "frida-compiler-shims",
|
||||
"version": "1.0.0",
|
||||
"description": "Agent used by Frida's compiler",
|
||||
"main": "agent.ts",
|
||||
"scripts": {
|
||||
"build:typescript": "rollup --config rollup.config.typescript.ts --configPlugin typescript",
|
||||
"build:agent-core": "rollup --config rollup.config.agent-core.ts --configPlugin typescript"
|
||||
},
|
||||
"description": "Shims used by Frida's compiler",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@frida/crosspath": "3.0.0",
|
||||
"frida-compile": "17.0.0",
|
||||
"frida-fs": "6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@frida/rollup-plugin-node-polyfills": "^1.0.3",
|
||||
"@rollup/plugin-node-resolve": "^14.1.0",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.4",
|
||||
"@types/frida-gum": "19.0.0",
|
||||
"@types/glob": "^8.0.0",
|
||||
"@types/node": "18.7.23",
|
||||
"glob": "^8.0.3",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
"@frida/assert": "^3.0.2",
|
||||
"@frida/base64-js": "^2.0.3",
|
||||
"@frida/buffer": "^7.0.4",
|
||||
"@frida/crypto": "^1.0.1",
|
||||
"@frida/diagnostics_channel": "^1.0.0",
|
||||
"@frida/events": "^4.0.4",
|
||||
"@frida/http": "^4.0.2",
|
||||
"@frida/http-parser-js": "^1.0.0",
|
||||
"@frida/https": "^1.0.0",
|
||||
"@frida/ieee754": "^2.0.2",
|
||||
"@frida/net": "^5.0.0",
|
||||
"@frida/os": "^1.0.2",
|
||||
"@frida/path": "^2.0.3",
|
||||
"@frida/process": "^1.2.1",
|
||||
"@frida/punycode": "^3.0.0",
|
||||
"@frida/querystring": "^1.0.0",
|
||||
"@frida/readable-stream": "^4.1.3",
|
||||
"@frida/reserved-words": "^1.0.0",
|
||||
"@frida/stream": "^1.0.2",
|
||||
"@frida/string_decoder": "^2.0.0",
|
||||
"@frida/timers": "^3.0.0",
|
||||
"@frida/tty": "^1.0.0",
|
||||
"@frida/url": "^1.0.2",
|
||||
"@frida/util": "^1.0.3",
|
||||
"@frida/vm": "^2.0.0",
|
||||
"@types/frida-gum": "19.0.1",
|
||||
"@types/node": "18.19.103",
|
||||
"frida-fs": "7.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
import crosspath from "@frida/crosspath";
|
||||
import fs from "fs";
|
||||
import { sync as glob } from "glob";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import polyfills from "@frida/rollup-plugin-node-polyfills";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import { defineConfig } from "rollup";
|
||||
import type rollup from "rollup";
|
||||
|
||||
export default defineConfig({
|
||||
input: "agent-core.ts",
|
||||
output: {
|
||||
file: "agent-core.js",
|
||||
format: "umd",
|
||||
name: "FridaCompilerAgentCore",
|
||||
generatedCode: {
|
||||
preset: "es2015",
|
||||
},
|
||||
strict: false,
|
||||
interop: "default",
|
||||
globals: {
|
||||
"typescript": "ts",
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
typescript(),
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
values: computeSubstitutionValues(),
|
||||
}),
|
||||
stubTsImportPlugin(),
|
||||
polyfills(),
|
||||
resolve(),
|
||||
/*
|
||||
terser({
|
||||
ecma: 2020,
|
||||
compress: {
|
||||
module: true,
|
||||
global_defs: {
|
||||
"process.env.FRIDA_COMPILE": true
|
||||
},
|
||||
},
|
||||
mangle: {
|
||||
module: true,
|
||||
},
|
||||
}),
|
||||
*/
|
||||
],
|
||||
});
|
||||
|
||||
function computeSubstitutionValues() {
|
||||
const hostOsFamily = process.env.FRIDA_HOST_OS_FAMILY;
|
||||
if (hostOsFamily === undefined) {
|
||||
throw new Error("missing FRIDA_HOST_OS_FAMILY");
|
||||
}
|
||||
|
||||
const hostArch = process.env.FRIDA_HOST_ARCH;
|
||||
if (hostArch === undefined) {
|
||||
throw new Error("missing FRIDA_HOST_ARCH");
|
||||
}
|
||||
|
||||
const hostCpuMode = process.env.FRIDA_HOST_CPU_MODE;
|
||||
if (hostCpuMode === undefined) {
|
||||
throw new Error("missing FRIDA_HOST_CPU_MODE");
|
||||
}
|
||||
|
||||
const outputDir = __dirname;
|
||||
|
||||
const compilerDir = crosspath.join(__dirname, "node_modules", "frida-compile");
|
||||
let usingLinkedCompiler = false;
|
||||
try {
|
||||
usingLinkedCompiler = fs.statSync(crosspath.join(compilerDir, "node_modules")).isDirectory();
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
const assetParentDir = usingLinkedCompiler ? compilerDir : outputDir;
|
||||
const assetModulesDir = crosspath.join(assetParentDir, "node_modules");
|
||||
|
||||
const shimDirs = [
|
||||
["@frida", "assert"],
|
||||
["@frida", "base64-js"],
|
||||
["@frida", "buffer"],
|
||||
["@frida", "crypto"],
|
||||
["@frida", "diagnostics_channel"],
|
||||
["@frida", "events"],
|
||||
["@frida", "http"],
|
||||
["@frida", "http-parser-js"],
|
||||
["@frida", "https"],
|
||||
["@frida", "ieee754"],
|
||||
["@frida", "net"],
|
||||
["@frida", "os"],
|
||||
["@frida", "path"],
|
||||
["@frida", "process"],
|
||||
["@frida", "punycode"],
|
||||
["@frida", "querystring"],
|
||||
["@frida", "readable-stream"],
|
||||
["@frida", "stream"],
|
||||
["@frida", "string_decoder"],
|
||||
["@frida", "timers"],
|
||||
["@frida", "tty"],
|
||||
["@frida", "url"],
|
||||
["@frida", "util"],
|
||||
["@frida", "vm"],
|
||||
["frida-fs"],
|
||||
];
|
||||
const typeDirs = [
|
||||
["@types", "node"],
|
||||
["@types", "frida-gum"],
|
||||
];
|
||||
|
||||
const assets: string[] = [];
|
||||
for (const shimDir of shimDirs) {
|
||||
assets.push(...glob(crosspath.join(assetModulesDir, ...shimDir, "package.json")));
|
||||
assets.push(...glob(crosspath.join(assetModulesDir, ...shimDir, "**", "*.js")));
|
||||
}
|
||||
for (const typeDir of typeDirs) {
|
||||
assets.push(...glob(crosspath.join(assetModulesDir, ...typeDir, "package.json")));
|
||||
assets.push(...glob(crosspath.join(assetModulesDir, ...typeDir, "**", "*.d.ts")));
|
||||
}
|
||||
assets.push(...glob(crosspath.join(compilerDir, "ext", "lib.es*.d.ts")));
|
||||
assets.push(...glob(crosspath.join(compilerDir, "ext", "lib.decorators*.d.ts")));
|
||||
|
||||
const ignoredAssetFiles = new Set([
|
||||
"@frida/process/browser.js",
|
||||
]);
|
||||
|
||||
const agentDirectories = new Set<string>();
|
||||
const agentFiles = new Map<string, string>();
|
||||
for (const assetPath of assets) {
|
||||
let assetRelpath = assetPath.substring(assetParentDir.length);
|
||||
if (usingLinkedCompiler && assetRelpath.startsWith("/ext/")) {
|
||||
assetRelpath = crosspath.join("/", "node_modules", "frida-compile", assetRelpath.substring(1));
|
||||
}
|
||||
|
||||
const identifier = assetRelpath.split("/").slice(2).join("/");
|
||||
if (ignoredAssetFiles.has(identifier)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let currentDir = crosspath.dirname(assetRelpath);
|
||||
do {
|
||||
agentDirectories.add(currentDir);
|
||||
currentDir = crosspath.dirname(currentDir);
|
||||
} while (currentDir !== "/");
|
||||
agentFiles.set(assetRelpath, fs.readFileSync(assetPath, "utf-8"));
|
||||
}
|
||||
|
||||
const orderedAgentDirectories = Array.from(agentDirectories);
|
||||
orderedAgentDirectories.sort();
|
||||
|
||||
const orderedAgentFiles = Array.from(agentFiles);
|
||||
orderedAgentFiles.sort((a, b) => a[0].localeCompare(b[0]));
|
||||
|
||||
return {
|
||||
"Frida.version": "'0.0.0'",
|
||||
"Process.id": "1",
|
||||
"Process.platform": `'${hostOsFamily}'`,
|
||||
"Process.arch": `'${hostArch}'`,
|
||||
"Process.pointerSize": (hostCpuMode === "64") ? "8" : "4",
|
||||
"__agentDirectories__": JSON.stringify(orderedAgentDirectories, null, 4),
|
||||
"__agentFiles__": JSON.stringify(orderedAgentFiles, null, 4),
|
||||
};
|
||||
}
|
||||
|
||||
function stubTsImportPlugin(): rollup.Plugin {
|
||||
return {
|
||||
name: "stub-ts-import",
|
||||
async resolveId(source, importer, options) {
|
||||
if (crosspath.basename(source) === "typescript.js") {
|
||||
return {
|
||||
id: "typescript",
|
||||
external: true,
|
||||
meta: {},
|
||||
moduleSideEffects: false,
|
||||
syntheticNamedExports: false
|
||||
};
|
||||
}
|
||||
|
||||
return this.resolve(source, importer, { skipSelf: true, ...options });
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import path from "path";
|
||||
import { defineConfig } from "rollup";
|
||||
|
||||
export default defineConfig({
|
||||
input: path.join("node_modules", "frida-compile", "ext", "typescript.js"),
|
||||
output: {
|
||||
file: "typescript.js",
|
||||
format: "umd",
|
||||
name: "ts",
|
||||
generatedCode: {
|
||||
preset: "es2015",
|
||||
},
|
||||
strict: false,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
)
|
||||
|
||||
var nodeGlobals = `
|
||||
export { Buffer } from 'node:buffer';
|
||||
export { default as process } from 'node:process';
|
||||
`
|
||||
|
||||
//go:embed node_modules/@frida/*/package.json
|
||||
//go:embed node_modules/@frida/*/*.js
|
||||
//go:embed node_modules/@frida/*/*/*.js
|
||||
//go:embed node_modules/@frida/*/*/*/*.js
|
||||
//go:embed node_modules/frida-fs/package.json
|
||||
//go:embed node_modules/frida-fs/*/*.js
|
||||
var embeddedShims embed.FS
|
||||
|
||||
func makeFridaShimsPlugin() esbuild.Plugin {
|
||||
const (
|
||||
nsBuiltins = "frida-builtins"
|
||||
nsShim = "frida-shim"
|
||||
)
|
||||
|
||||
const (
|
||||
builtinFilter = `^frida-builtins://(.+)$`
|
||||
shimFilter = `^(assert|base64-js|buffer|crypto|diagnostics_channel|events|fs|http|https|http-parser-js|ieee754|net|os|path|process|punycode|querystring|readable-stream|stream|string_decoder|timers|tty|url|util|vm)$`
|
||||
shimNodePrefixFilter = `^node:(assert|buffer|crypto|diagnostics_channel|events|fs|http|https|net|os|path|process|punycode|querystring|stream|string_decoder|timers|tty|url|util|vm)$`
|
||||
)
|
||||
|
||||
return esbuild.Plugin{
|
||||
Name: "frida-custom-shims",
|
||||
Setup: func(build esbuild.PluginBuild) {
|
||||
build.OnResolve(esbuild.OnResolveOptions{Filter: builtinFilter},
|
||||
func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
|
||||
return esbuild.OnResolveResult{
|
||||
Path: strings.TrimPrefix(args.Path, "frida-builtins://"),
|
||||
Namespace: nsBuiltins,
|
||||
SideEffects: esbuild.SideEffectsFalse,
|
||||
}, nil
|
||||
})
|
||||
|
||||
build.OnLoad(esbuild.OnLoadOptions{Filter: ".*", Namespace: nsBuiltins},
|
||||
func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
||||
if args.Path != "/node-globals.js" {
|
||||
return esbuild.OnLoadResult{
|
||||
Errors: []esbuild.Message{{Text: "Unexpected path: " + args.Path}},
|
||||
}, nil
|
||||
}
|
||||
return esbuild.OnLoadResult{
|
||||
Contents: &nodeGlobals,
|
||||
Loader: esbuild.LoaderJS,
|
||||
}, nil
|
||||
})
|
||||
|
||||
registerShimResolve := func(pattern string) {
|
||||
build.OnResolve(esbuild.OnResolveOptions{Filter: pattern},
|
||||
func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
|
||||
return makeResolveResult(nsShim, args.Path)
|
||||
})
|
||||
}
|
||||
registerShimResolve(shimFilter)
|
||||
registerShimResolve(shimNodePrefixFilter)
|
||||
|
||||
build.OnResolve(esbuild.OnResolveOptions{Filter: ".*", Namespace: nsShim},
|
||||
func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
|
||||
if strings.HasPrefix(args.Path, ".") {
|
||||
abs := filepath.ToSlash(filepath.Join(filepath.Dir(args.Importer), args.Path))
|
||||
return esbuild.OnResolveResult{
|
||||
Path: abs,
|
||||
Namespace: nsShim,
|
||||
SideEffects: esbuild.SideEffectsFalse,
|
||||
}, nil
|
||||
}
|
||||
return makeResolveResult(nsShim, args.Path)
|
||||
})
|
||||
|
||||
build.OnLoad(esbuild.OnLoadOptions{Filter: ".*", Namespace: nsShim},
|
||||
func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
|
||||
data, err := embeddedShims.ReadFile(filepath.ToSlash(args.Path))
|
||||
if err != nil {
|
||||
return esbuild.OnLoadResult{
|
||||
Errors: []esbuild.Message{{
|
||||
Text: fmt.Sprintf("Error reading shim file %q: %v", args.Path, err),
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
content := string(data)
|
||||
return esbuild.OnLoadResult{
|
||||
Contents: &content,
|
||||
Loader: loaderFor(args.Path),
|
||||
}, nil
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeResolveResult(ns string, module string) (esbuild.OnResolveResult, error) {
|
||||
path, errs := resolveShim(module)
|
||||
if len(errs) > 0 {
|
||||
return esbuild.OnResolveResult{Errors: errs}, nil
|
||||
}
|
||||
return esbuild.OnResolveResult{
|
||||
Path: path,
|
||||
Namespace: ns,
|
||||
SideEffects: esbuild.SideEffectsFalse,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func loaderFor(path string) esbuild.Loader {
|
||||
switch ext := filepath.Ext(path); ext {
|
||||
case ".js", ".mjs", ".cjs":
|
||||
return esbuild.LoaderJS
|
||||
case ".json":
|
||||
return esbuild.LoaderJSON
|
||||
default:
|
||||
panic(fmt.Sprintf("frida-shims: unsupported file type %q", ext))
|
||||
}
|
||||
}
|
||||
|
||||
var shimMap = map[string]string{
|
||||
"assert": "@frida/assert",
|
||||
"base64-js": "@frida/base64-js",
|
||||
"buffer": "@frida/buffer",
|
||||
"crypto": "@frida/crypto",
|
||||
"diagnostics_channel": "@frida/diagnostics_channel",
|
||||
"events": "@frida/events",
|
||||
"fs": "frida-fs",
|
||||
"http": "@frida/http",
|
||||
"https": "@frida/https",
|
||||
"http-parser-js": "@frida/http-parser-js",
|
||||
"ieee754": "@frida/ieee754",
|
||||
"net": "@frida/net",
|
||||
"os": "@frida/os",
|
||||
"path": "@frida/path",
|
||||
"process": "@frida/process",
|
||||
"punycode": "@frida/punycode",
|
||||
"querystring": "@frida/querystring",
|
||||
"readable-stream": "@frida/readable-stream",
|
||||
"stream": "@frida/stream",
|
||||
"string_decoder": "@frida/string_decoder",
|
||||
"timers": "@frida/timers",
|
||||
"tty": "@frida/tty",
|
||||
"url": "@frida/url",
|
||||
"util": "@frida/util",
|
||||
"vm": "@frida/vm",
|
||||
}
|
||||
|
||||
type PackageJSON struct {
|
||||
Main string `json:"main"`
|
||||
Module string `json:"module"`
|
||||
}
|
||||
|
||||
func resolveShim(shimName string) (string, []esbuild.Message) {
|
||||
var subDir string
|
||||
if strings.HasPrefix(shimName, "@frida/") {
|
||||
subDir = shimName
|
||||
} else {
|
||||
actualShimName := strings.TrimPrefix(shimName, "node:")
|
||||
|
||||
var ok bool
|
||||
subDir, ok = shimMap[actualShimName]
|
||||
if !ok {
|
||||
return "", []esbuild.Message{{Text: "Unknown shim: " + actualShimName}}
|
||||
}
|
||||
}
|
||||
|
||||
shimPackageDir := filepath.Join("node_modules", subDir)
|
||||
packageJSONPath := filepath.Join(shimPackageDir, "package.json")
|
||||
|
||||
pkgJSONBytes, _ := embeddedShims.ReadFile(filepath.ToSlash(packageJSONPath))
|
||||
|
||||
var pkg PackageJSON
|
||||
json.Unmarshal(pkgJSONBytes, &pkg)
|
||||
|
||||
entryFile := ""
|
||||
if pkg.Module != "" {
|
||||
entryFile = pkg.Module
|
||||
} else if pkg.Main != "" {
|
||||
entryFile = pkg.Main
|
||||
} else {
|
||||
entryFile = "index.js"
|
||||
}
|
||||
|
||||
resolvedEntryPath := filepath.Join(shimPackageDir, entryFile)
|
||||
|
||||
return filepath.ToSlash(resolvedEntryPath), nil
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/frida/typescript-go/pkg/ast"
|
||||
"github.com/frida/typescript-go/pkg/bundled"
|
||||
"github.com/frida/typescript-go/pkg/compiler"
|
||||
"github.com/frida/typescript-go/pkg/core"
|
||||
"github.com/frida/typescript-go/pkg/tsoptions"
|
||||
"github.com/frida/typescript-go/pkg/tspath"
|
||||
"github.com/frida/typescript-go/pkg/vfs"
|
||||
"github.com/frida/typescript-go/pkg/vfs/iovfs"
|
||||
"github.com/frida/typescript-go/pkg/vfs/osvfs"
|
||||
)
|
||||
|
||||
//go:embed node_modules/@types/*/package.json
|
||||
//go:embed node_modules/@types/*/*.d.ts
|
||||
//go:embed node_modules/@types/*/*/*.d.ts
|
||||
var embeddedTypes embed.FS
|
||||
|
||||
type TSCompiler struct {
|
||||
projectRoot string
|
||||
entrypoint string
|
||||
loadCompilerOptions LoadCompilerOptionsHandler
|
||||
fs vfs.FS
|
||||
captureFs *captureFS
|
||||
|
||||
mu sync.Mutex
|
||||
options *core.CompilerOptions
|
||||
program *compiler.Program
|
||||
programErr error
|
||||
forceFreshProgram bool
|
||||
mtimes map[tspath.Path]time.Time
|
||||
inputDirs, inputFiles []string
|
||||
pendingDirs, pendingFiles []string
|
||||
}
|
||||
|
||||
type LoadCompilerOptionsHandler func(host tsoptions.ParseConfigHost) (*core.CompilerOptions, string, error)
|
||||
|
||||
func NewTSCompiler(projectRoot, entrypoint string, loadCompilerOptions LoadCompilerOptionsHandler) *TSCompiler {
|
||||
captureFs := newCaptureFS(osvfs.FS())
|
||||
fs := newTypesFS(bundled.WrapFS(captureFs), projectRoot)
|
||||
|
||||
return &TSCompiler{
|
||||
projectRoot: projectRoot,
|
||||
entrypoint: tspath.NormalizePath(entrypoint),
|
||||
loadCompilerOptions: loadCompilerOptions,
|
||||
fs: fs,
|
||||
captureFs: captureFs,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TSCompiler) resetProgramState() {
|
||||
c.options = nil
|
||||
c.program = nil
|
||||
c.programErr = nil
|
||||
c.forceFreshProgram = false
|
||||
c.mtimes = nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) EnsureProgramUpToDate() error {
|
||||
opts, _, err := c.loadCompilerOptions(c)
|
||||
if err != nil {
|
||||
c.resetProgramState()
|
||||
c.recomputeInputs()
|
||||
return err
|
||||
}
|
||||
|
||||
var prog *compiler.Program
|
||||
var progErr error
|
||||
|
||||
if c.forceFreshProgram || c.program == nil || opts != c.options {
|
||||
c.forceFreshProgram = false
|
||||
prog, progErr = c.createProgram(opts)
|
||||
} else {
|
||||
prog, progErr = c.updateProgram(c.program, opts)
|
||||
}
|
||||
|
||||
c.program = prog
|
||||
c.programErr = progErr
|
||||
c.options = opts
|
||||
|
||||
c.recomputeInputs()
|
||||
|
||||
return progErr
|
||||
}
|
||||
|
||||
func (c *TSCompiler) createProgram(options *core.CompilerOptions) (*compiler.Program, error) {
|
||||
host := compiler.NewCompilerHost(options, c.projectRoot, c.fs, bundled.LibPath())
|
||||
|
||||
program := compiler.NewProgram(compiler.ProgramOptions{
|
||||
RootFiles: []string{c.entrypoint},
|
||||
Host: host,
|
||||
Options: options,
|
||||
})
|
||||
|
||||
c.updateMtimes(program)
|
||||
|
||||
return program, nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) updateProgram(old *compiler.Program, options *core.CompilerOptions) (*compiler.Program, error) {
|
||||
newMtimes, err := c.collectMtimes(old)
|
||||
if err != nil {
|
||||
return c.createProgram(options)
|
||||
}
|
||||
|
||||
updated := []tspath.Path{}
|
||||
newProg := old
|
||||
for path, mtime := range newMtimes {
|
||||
if !mtime.Equal(c.mtimes[path]) {
|
||||
var reused bool
|
||||
newProg, reused = newProg.UpdateProgram(path)
|
||||
updated = append(updated, path)
|
||||
if !reused {
|
||||
c.updateMtimes(newProg)
|
||||
return newProg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
c.mtimes = newMtimes
|
||||
return newProg, nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) updateMtimes(p *compiler.Program) {
|
||||
mt, _ := c.collectMtimes(p)
|
||||
c.mtimes = mt
|
||||
}
|
||||
|
||||
func (c *TSCompiler) collectMtimes(p *compiler.Program) (map[tspath.Path]time.Time, error) {
|
||||
mt := make(map[tspath.Path]time.Time, len(p.SourceFiles()))
|
||||
|
||||
for _, sf := range p.SourceFiles() {
|
||||
name := sf.FileName()
|
||||
if isBundled(name) {
|
||||
continue
|
||||
}
|
||||
info := c.fs.Stat(name)
|
||||
if info == nil {
|
||||
return nil, fmt.Errorf("File %q disappeared", name)
|
||||
}
|
||||
mt[sf.Path()] = info.ModTime()
|
||||
}
|
||||
|
||||
return mt, nil
|
||||
}
|
||||
|
||||
func isBundled(name string) bool {
|
||||
return strings.HasPrefix(name, "bundled://")
|
||||
}
|
||||
|
||||
func (c *TSCompiler) WatchDirs() []string {
|
||||
if c.inputDirs != nil {
|
||||
return c.inputDirs
|
||||
}
|
||||
return c.pendingDirs
|
||||
}
|
||||
|
||||
func (c *TSCompiler) WatchFiles() []string {
|
||||
if c.inputFiles != nil {
|
||||
return c.inputFiles
|
||||
}
|
||||
return c.pendingFiles
|
||||
}
|
||||
|
||||
func (c *TSCompiler) CommitPendingInputs() {
|
||||
c.inputDirs, c.pendingDirs = c.pendingDirs, nil
|
||||
c.inputFiles, c.pendingFiles = c.pendingFiles, nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) DropPendingInputs() {
|
||||
c.pendingDirs, c.pendingFiles = nil, nil
|
||||
}
|
||||
|
||||
func (c *TSCompiler) recomputeInputs() {
|
||||
var capHint int
|
||||
if c.program != nil {
|
||||
capHint = 1 + len(c.program.GetSourceFiles())
|
||||
} else {
|
||||
capHint = 1
|
||||
}
|
||||
|
||||
files := make([]string, 0, capHint)
|
||||
files = append(files, filepath.Join(c.projectRoot, "tsconfig.json"))
|
||||
if c.program != nil {
|
||||
for _, sf := range c.program.GetSourceFiles() {
|
||||
name := sf.FileName()
|
||||
if !isBundled(name) {
|
||||
files = append(files, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(files)
|
||||
|
||||
c.pendingDirs = uniqueDirs(files)
|
||||
c.pendingFiles = files
|
||||
}
|
||||
|
||||
func (c *TSCompiler) Compile(filePathToCompile string) (string, []*ast.Diagnostic, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.programErr != nil {
|
||||
return "", nil, c.programErr
|
||||
}
|
||||
program := c.program
|
||||
|
||||
var targetSourceFile *ast.SourceFile
|
||||
normalizedFilePathToCompile := tspath.NormalizePath(filePathToCompile)
|
||||
for _, sf := range program.GetSourceFiles() {
|
||||
if sf.FileName() == normalizedFilePathToCompile {
|
||||
targetSourceFile = sf
|
||||
break
|
||||
}
|
||||
}
|
||||
if targetSourceFile == nil {
|
||||
return "", nil, fmt.Errorf("TypeScript source file not found in program: %s", filePathToCompile)
|
||||
}
|
||||
|
||||
c.captureFs.ClearOutputs()
|
||||
|
||||
res := program.Emit(compiler.EmitOptions{
|
||||
TargetSourceFile: targetSourceFile,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
diagnostics := program.GetSyntacticDiagnostics(ctx, targetSourceFile)
|
||||
if len(diagnostics) == 0 {
|
||||
diagnostics = append(diagnostics, program.GetBindDiagnostics(ctx, targetSourceFile)...)
|
||||
}
|
||||
if len(diagnostics) == 0 {
|
||||
diagnostics = append(diagnostics, program.GetOptionsDiagnostics(ctx)...)
|
||||
}
|
||||
if len(diagnostics) == 0 {
|
||||
diagnostics = append(diagnostics, program.GetGlobalDiagnostics(ctx)...)
|
||||
}
|
||||
if len(diagnostics) == 0 {
|
||||
semanticDiags := program.GetSemanticDiagnostics(ctx, targetSourceFile)
|
||||
for _, d := range semanticDiags {
|
||||
switch d.Code() {
|
||||
case
|
||||
2305, // Module '{0}' has no exported member '{1}'.
|
||||
2306, // File '{0}' is not a module.
|
||||
2307: // Cannot find module '{0}' or its corresponding type declarations.
|
||||
c.forceFreshProgram = true
|
||||
}
|
||||
}
|
||||
diagnostics = append(diagnostics, semanticDiags...)
|
||||
}
|
||||
|
||||
if res.EmitSkipped {
|
||||
errMsg := "TypeScript compilation failed and was skipped"
|
||||
if len(diagnostics) == 0 {
|
||||
errMsg = fmt.Sprintf("TypeScript compilation failed and was skipped for %s, but no diagnostics reported", filePathToCompile)
|
||||
}
|
||||
return "", diagnostics, fmt.Errorf(errMsg)
|
||||
}
|
||||
|
||||
var compiledJS string
|
||||
var foundJSOutput bool
|
||||
for path, content := range c.captureFs.GetOutputs() {
|
||||
ext := filepath.Ext(path)
|
||||
if ext == ".js" {
|
||||
compiledJS = content
|
||||
foundJSOutput = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundJSOutput {
|
||||
if len(diagnostics) == 0 {
|
||||
return "", nil, fmt.Errorf("TypeScript compilation for %s seemed to succeed (emit not skipped, no diagnostics) but no .js output file was captured. Captured outputs: %v", filePathToCompile, c.captureFs.GetOutputs())
|
||||
}
|
||||
return "", diagnostics, fmt.Errorf("No .js output file was captured for %s. Captured outputs: %v", filePathToCompile, c.captureFs.GetOutputs())
|
||||
}
|
||||
|
||||
return compiledJS, diagnostics, nil
|
||||
}
|
||||
|
||||
func uniqueDirs(files []string) []string {
|
||||
m := make(map[string]struct{}, len(files))
|
||||
for _, f := range files {
|
||||
m[filepath.Dir(f)] = struct{}{}
|
||||
}
|
||||
|
||||
out := make([]string, 0, len(m))
|
||||
for d := range m {
|
||||
out = append(out, d)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// FS implements ParseConfigHost.
|
||||
func (c *TSCompiler) FS() vfs.FS {
|
||||
return c.fs
|
||||
}
|
||||
|
||||
// GetCurrentDirectory implements ParseConfigHost.
|
||||
func (c *TSCompiler) GetCurrentDirectory() string {
|
||||
return c.projectRoot
|
||||
}
|
||||
|
||||
type captureFS struct {
|
||||
vfs.FS
|
||||
outputs map[string]string
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
var _ vfs.FS = (*captureFS)(nil)
|
||||
|
||||
func newCaptureFS(inner vfs.FS) *captureFS {
|
||||
return &captureFS{
|
||||
FS: inner,
|
||||
outputs: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *captureFS) ClearOutputs() {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.outputs = make(map[string]string)
|
||||
}
|
||||
|
||||
func (c *captureFS) GetOutputs() map[string]string {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
snap := make(map[string]string, len(c.outputs))
|
||||
for k, v := range c.outputs {
|
||||
snap[k] = v
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
func (c *captureFS) WriteFile(path string, data string, writeByteOrderMark bool) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.outputs[path] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *captureFS) Remove(path string) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
delete(c.outputs, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
type typesFS struct {
|
||||
vfs.FS
|
||||
projectRoot string
|
||||
types vfs.FS
|
||||
}
|
||||
|
||||
var _ vfs.FS = (*typesFS)(nil)
|
||||
|
||||
func newTypesFS(inner vfs.FS, projectRoot string) *typesFS {
|
||||
return &typesFS{
|
||||
FS: inner,
|
||||
projectRoot: projectRoot,
|
||||
types: iovfs.From(embeddedTypes, true),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *typesFS) DirectoryExists(path string) bool {
|
||||
embeddedPath := t.resolveEmbeddedPath(path)
|
||||
if embeddedPath != "" {
|
||||
if t.types.DirectoryExists(embeddedPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return t.FS.DirectoryExists(path)
|
||||
}
|
||||
|
||||
func (t *typesFS) GetAccessibleEntries(path string) vfs.Entries {
|
||||
embeddedPath := t.resolveEmbeddedPath(path)
|
||||
|
||||
var embeddedEntries vfs.Entries
|
||||
if embeddedPath != "" {
|
||||
embeddedEntries = t.types.GetAccessibleEntries(embeddedPath)
|
||||
}
|
||||
|
||||
otherEntries := t.FS.GetAccessibleEntries(path)
|
||||
|
||||
return vfs.Entries{
|
||||
Files: mergeAndSort(embeddedEntries.Files, otherEntries.Files),
|
||||
Directories: mergeAndSort(embeddedEntries.Directories, otherEntries.Directories),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *typesFS) FileExists(path string) bool {
|
||||
embeddedPath := t.resolveEmbeddedPath(path)
|
||||
if embeddedPath != "" {
|
||||
if t.types.FileExists(embeddedPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return t.FS.FileExists(path)
|
||||
}
|
||||
|
||||
func (t *typesFS) ReadFile(path string) (contents string, ok bool) {
|
||||
contents, ok = t.FS.ReadFile(path)
|
||||
if ok {
|
||||
return contents, ok
|
||||
}
|
||||
|
||||
embeddedPath := t.resolveEmbeddedPath(path)
|
||||
if embeddedPath != "" {
|
||||
contents, ok = t.types.ReadFile(embeddedPath)
|
||||
}
|
||||
|
||||
return contents, ok
|
||||
}
|
||||
|
||||
func (c *typesFS) resolveEmbeddedPath(path string) string {
|
||||
rel, err := filepath.Rel(c.projectRoot, path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return "/" + filepath.ToSlash(rel)
|
||||
}
|
||||
|
||||
func mergeAndSort(a, b []string) []string {
|
||||
m := make(map[string]struct{}, len(a)+len(b))
|
||||
for _, v := range append(a, b...) {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return slices.Sorted(maps.Keys(m))
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
tscore "github.com/frida/typescript-go/pkg/core"
|
||||
"github.com/frida/typescript-go/pkg/tsoptions"
|
||||
"github.com/frida/typescript-go/pkg/tspath"
|
||||
)
|
||||
|
||||
type TSConfigCache struct {
|
||||
projectRoot string
|
||||
sourceMap bool
|
||||
tsconfigPath string
|
||||
onChange ConfigChangeCallback
|
||||
|
||||
mu sync.Mutex
|
||||
lastModTime time.Time
|
||||
cachedOpts *tscore.CompilerOptions
|
||||
lastText string
|
||||
}
|
||||
|
||||
type ConfigChangeCallback func()
|
||||
|
||||
func NewTSConfigCache(
|
||||
projectRoot string,
|
||||
sourceMap bool,
|
||||
callback ConfigChangeCallback,
|
||||
) *TSConfigCache {
|
||||
return &TSConfigCache{
|
||||
projectRoot: projectRoot,
|
||||
sourceMap: sourceMap,
|
||||
tsconfigPath: filepath.Join(projectRoot, "tsconfig.json"),
|
||||
lastModTime: time.Time{},
|
||||
cachedOpts: nil,
|
||||
lastText: "",
|
||||
onChange: callback,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TSConfigCache) GetCompilerOptions(
|
||||
host tsoptions.ParseConfigHost,
|
||||
) (*tscore.CompilerOptions, string, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
fs := host.FS()
|
||||
|
||||
info := fs.Stat(c.tsconfigPath)
|
||||
var currentModTime time.Time
|
||||
if info != nil {
|
||||
currentModTime = info.ModTime()
|
||||
} else {
|
||||
currentModTime = time.Time{}
|
||||
}
|
||||
|
||||
if c.cachedOpts != nil && currentModTime.Equal(c.lastModTime) {
|
||||
return c.cachedOpts, c.lastText, nil
|
||||
}
|
||||
|
||||
var tsconfigText string
|
||||
if info != nil {
|
||||
data, ok := fs.ReadFile(c.tsconfigPath)
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("Unable to read %q", c.tsconfigPath)
|
||||
}
|
||||
tsconfigText = string(data)
|
||||
} else {
|
||||
tsconfigText = "{}"
|
||||
}
|
||||
|
||||
parsedSource := tsoptions.NewTsconfigSourceFileFromFilePath(
|
||||
tspath.NormalizePath(c.tsconfigPath),
|
||||
tspath.ToPath(c.tsconfigPath, "", fs.UseCaseSensitiveFileNames()),
|
||||
tsconfigText,
|
||||
)
|
||||
parsedCommandLine := tsoptions.ParseJsonSourceFileConfigFileContent(
|
||||
parsedSource,
|
||||
host,
|
||||
c.projectRoot,
|
||||
nil,
|
||||
c.tsconfigPath,
|
||||
nil, nil, nil,
|
||||
)
|
||||
|
||||
if len(parsedCommandLine.Errors) > 0 {
|
||||
var msgs []string
|
||||
for _, diag := range parsedCommandLine.Errors {
|
||||
msgs = append(msgs, diag.Message())
|
||||
}
|
||||
return nil, "", fmt.Errorf(
|
||||
"Failed to parse %s: %s",
|
||||
c.tsconfigPath,
|
||||
strings.Join(msgs, "; "),
|
||||
)
|
||||
}
|
||||
|
||||
newOpts := parsedCommandLine.CompilerOptions()
|
||||
newOpts.Module = tscore.ModuleKindNode16
|
||||
newOpts.ModuleResolution = tscore.ModuleResolutionKindNode16
|
||||
newOpts.NoEmit = tscore.TSFalse
|
||||
|
||||
sourceMapOptVal := boolToTristate(c.sourceMap)
|
||||
newOpts.SourceMap = sourceMapOptVal
|
||||
newOpts.InlineSourceMap = sourceMapOptVal
|
||||
|
||||
if newOpts.Target == tscore.ScriptTargetNone {
|
||||
newOpts.Target = tscore.ScriptTargetES2022
|
||||
}
|
||||
if newOpts.Lib == nil {
|
||||
newOpts.Lib = []string{"lib.es2022.d.ts"}
|
||||
}
|
||||
if newOpts.SkipLibCheck == tscore.TSUnknown {
|
||||
newOpts.SkipLibCheck = tscore.TSTrue
|
||||
}
|
||||
if newOpts.Strict == tscore.TSUnknown {
|
||||
newOpts.Strict = tscore.TSTrue
|
||||
}
|
||||
|
||||
tsconfigData, _ := json.Marshal(parsedCommandLine.ParsedConfig)
|
||||
tsconfigText = string(tsconfigData)
|
||||
|
||||
hadPreviousOpts := c.cachedOpts != nil
|
||||
optsChanged := true
|
||||
if c.cachedOpts != nil && reflect.DeepEqual(newOpts, c.cachedOpts) {
|
||||
optsChanged = false
|
||||
}
|
||||
|
||||
if optsChanged {
|
||||
c.cachedOpts = newOpts
|
||||
c.lastText = tsconfigText
|
||||
}
|
||||
c.lastModTime = currentModTime
|
||||
|
||||
c.mu.Unlock()
|
||||
defer c.mu.Lock()
|
||||
|
||||
if hadPreviousOpts && optsChanged {
|
||||
if c.onChange != nil {
|
||||
c.onChange()
|
||||
}
|
||||
}
|
||||
|
||||
return newOpts, tsconfigText, nil
|
||||
}
|
||||
|
||||
func boolToTristate(val bool) tscore.Tristate {
|
||||
if val {
|
||||
return tscore.TSTrue
|
||||
}
|
||||
return tscore.TSFalse
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"lib": ["es2020"],
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
@@ -192,7 +192,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private void on_server_connection (IOStream connection, SocketAddress remote_address, DynamicInterface? dynamic_iface) {
|
||||
#if IOS || TVOS
|
||||
#if HAVE_LOCAL_BACKEND && (IOS || TVOS)
|
||||
/*
|
||||
* We defer the launchd injection until the first connection is established in order
|
||||
* to avoid bootloops on unsupported jailbreaks.
|
||||
|
||||
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user