mirror of
https://github.com/frida/frida-core.git
synced 2026-11-08 17:12:49 -10:00
Compare commits
No files matched your search
@@ -0,0 +1 @@
|
||||
github: frida
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
include:
|
||||
- { id: windows-x86_64, runner: '"windows-latest"' }
|
||||
- { id: windows-x86, runner: '"windows-latest"' }
|
||||
- { id: macos-x86_64, runner: '"macos-11"' }
|
||||
- { 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"' }
|
||||
@@ -42,7 +42,9 @@ jobs:
|
||||
run: sudo security authorizationdb write system.privilege.taskport allow
|
||||
- name: Install gcc-multilib
|
||||
if: matrix.id == 'linux-x86'
|
||||
run: sudo apt-get install gcc-multilib lib32stdc++-11-dev
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-multilib lib32stdc++-13-dev
|
||||
- name: Build
|
||||
if: ${{ startsWith(matrix.id, 'windows-') }}
|
||||
run: |
|
||||
@@ -66,7 +68,6 @@ jobs:
|
||||
name: core-devkit-${{ matrix.id }}
|
||||
path: build/src/devkit/
|
||||
- name: Test
|
||||
if: matrix.id != 'macos-arm64'
|
||||
run: make test
|
||||
|
||||
cross:
|
||||
@@ -87,7 +88,9 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install toolchain
|
||||
run: sudo apt-get install ${{ matrix.pkg }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install ${{ matrix.pkg }}
|
||||
- name: Build
|
||||
run: |
|
||||
./configure ${{ matrix.opts }} ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
@@ -147,6 +150,14 @@ jobs:
|
||||
certificates-p12: ${{ secrets.APPLE_CERTIFICATES_P12 }}
|
||||
certificates-password: ${{ secrets.APPLE_CERTIFICATES_PASSWORD }}
|
||||
keychain-password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
||||
- uses: nttld/setup-ndk@v1
|
||||
if: startsWith(matrix.id, 'android-')
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r25c
|
||||
- name: Add NDK to env
|
||||
if: startsWith(matrix.id, 'android-')
|
||||
run: echo ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }} >> $GITHUB_ENV
|
||||
- name: Build
|
||||
run: |
|
||||
./configure --host=${{ matrix.id }} ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
@@ -177,6 +188,7 @@ jobs:
|
||||
tar -C /tmp/pkg -czf /tmp/runner.tar.gz .
|
||||
- name: Test on Corellium iOS device
|
||||
if: matrix.id == 'ios-arm64'
|
||||
continue-on-error: true
|
||||
uses: frida/corellium-action@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -192,6 +204,7 @@ jobs:
|
||||
./frida-tests
|
||||
- name: Test on Corellium Android device
|
||||
if: matrix.id == 'android-arm64'
|
||||
continue-on-error: true
|
||||
uses: frida/corellium-action@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -228,3 +241,35 @@ jobs:
|
||||
cp -a tests/frida-tests tests/labrats lib/agent/frida-agent.so /tmp/pkg/
|
||||
tar -C /tmp/pkg -cf /tmp/runner.tar .
|
||||
/opt/sabrelite/run.sh /tmp/runner.tar /opt/frida/frida-tests
|
||||
|
||||
# This job is used to check that the Linux helpers are built correctly for all
|
||||
# supported architectures. We do this because the binary artifacts are checked
|
||||
# in, and we want to ensure that they are up-to-date with respect to the source
|
||||
# for all architectures.
|
||||
check-linux-helpers:
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
./src/linux/helpers/rebuild.sh ${{ matrix.arch }}
|
||||
- name: Check for unexpected changes
|
||||
run: |
|
||||
git config --global --add safe.directory "$(realpath .)"
|
||||
status_output="$(git status --porcelain)"
|
||||
if [ -n "$status_output" ]; then
|
||||
echo "Unexpected changes detected:"
|
||||
echo "$status_output"
|
||||
echo "Diff:"
|
||||
git diff
|
||||
exit 1
|
||||
else
|
||||
echo "No unexpected changes detected."
|
||||
fi
|
||||
+59
-12
@@ -4,6 +4,7 @@ import base64
|
||||
from collections import OrderedDict
|
||||
from dataclasses import dataclass, field
|
||||
import itertools
|
||||
import locale
|
||||
import os
|
||||
from pathlib import Path
|
||||
import pickle
|
||||
@@ -14,7 +15,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
from typing import Any, Literal, Mapping, Optional, Sequence
|
||||
from typing import Any, Literal, Mapping, Optional, Sequence, Tuple
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
@@ -140,16 +141,19 @@ def setup(role: Role,
|
||||
compat = set()
|
||||
else:
|
||||
summary = "enabled by user"
|
||||
missing: list[MissingFeature] = []
|
||||
|
||||
if "native" in compat:
|
||||
have_toolchain = True
|
||||
other_label: Optional[str] = None
|
||||
other_triplet: Optional[str] = None
|
||||
extra_environ: dict[str, str] = {}
|
||||
|
||||
if host_os == "windows" and host_arch == "x86_64" and host_config == "mingw":
|
||||
other_triplet = "i686-w64-mingw32"
|
||||
have_toolchain = shutil.which(other_triplet + "-gcc") is not None
|
||||
other_label = "x86"
|
||||
have_toolchain, other_triplet = detect_mingw_toolchain_for("x86")
|
||||
elif host_os == "linux" and host_arch == "x86_64" and host_config is None:
|
||||
other_label = "x86"
|
||||
other_triplet = "i686-linux-gnu"
|
||||
have_toolchain = shutil.which(other_triplet + "-gcc") is not None
|
||||
if not have_toolchain:
|
||||
@@ -173,7 +177,7 @@ def setup(role: Role,
|
||||
if not have_toolchain:
|
||||
if not auto_detect:
|
||||
raise ToolchainNotFoundError(f"unable to locate toolchain for {other_triplet}")
|
||||
summary = "disabled due to missing toolchain for {other_triplet}"
|
||||
missing.append(MissingFeature(other_label, other_triplet))
|
||||
|
||||
if host_os == "windows" and host_arch == "x86_64" and have_toolchain:
|
||||
group = OutputGroup("x86", other_triplet, extra_environ)
|
||||
@@ -269,6 +273,28 @@ def setup(role: Role,
|
||||
]
|
||||
|
||||
if "emulated" in compat:
|
||||
if host_os == "windows" and host_arch == "arm64":
|
||||
for kind, emulated_arch in [("modern", "x86_64"), ("legacy", "x86")]:
|
||||
if host_config == "mingw":
|
||||
found, emulated_triplet = detect_mingw_toolchain_for(emulated_arch)
|
||||
if not found:
|
||||
if not auto_detect:
|
||||
raise ToolchainNotFoundError(f"unable to locate toolchain for {emulated_triplet}")
|
||||
missing.append(MissingFeature(emulated_arch, emulated_triplet))
|
||||
continue
|
||||
else:
|
||||
emulated_triplet = None
|
||||
outputs[OutputGroup(emulated_arch, emulated_triplet, extra_environ)] = [
|
||||
Output(identifier=f"helper_emulated_{kind}",
|
||||
name=HELPER_FILE_WINDOWS.name.replace(".exe", f"-{emulated_arch}.exe"),
|
||||
file=HELPER_FILE_WINDOWS,
|
||||
target=HELPER_TARGET),
|
||||
Output(identifier=f"agent_emulated_{kind}",
|
||||
name=AGENT_FILE_WINDOWS.name.replace(".dll", f"-{emulated_arch}.dll"),
|
||||
file=AGENT_FILE_WINDOWS,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
|
||||
if host_os == "android" and host_arch in {"x86_64", "x86"}:
|
||||
outputs[OutputGroup("arm")] = [
|
||||
Output(identifier="agent_emulated_legacy",
|
||||
@@ -290,6 +316,8 @@ def setup(role: Role,
|
||||
state = State(role, builddir, top_builddir, frida_version, host_os, host_config, allowed_prebuilds, outputs)
|
||||
serialized_state = base64.b64encode(pickle.dumps(state)).decode('ascii')
|
||||
|
||||
if missing:
|
||||
summary = ", ".join([f"{m.label} disabled due to missing toolchain for {m.triplet}" for m in missing])
|
||||
variable_names, output_names = zip(*[(output.identifier, output.name) \
|
||||
for output in itertools.chain.from_iterable(outputs.values())])
|
||||
print("ok")
|
||||
@@ -349,6 +377,12 @@ class Output:
|
||||
target: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class MissingFeature:
|
||||
label: str
|
||||
triplet: str
|
||||
|
||||
|
||||
def compile(privdir: Path, state: State):
|
||||
releng_location = query_releng_location(state.role)
|
||||
subprojects = detect_relevant_subprojects(releng_location)
|
||||
@@ -367,7 +401,7 @@ def compile(privdir: Path, state: State):
|
||||
**kwargs,
|
||||
"stdout": subprocess.PIPE,
|
||||
"stderr": subprocess.STDOUT,
|
||||
"encoding": "utf-8",
|
||||
"encoding": locale.getpreferredencoding(),
|
||||
}
|
||||
else:
|
||||
silenced_kwargs = kwargs
|
||||
@@ -432,7 +466,7 @@ def compile(privdir: Path, state: State):
|
||||
path = Path(raw_path)
|
||||
if not path.is_absolute():
|
||||
path = Path(os.path.relpath(workdir / path, top_builddir))
|
||||
input_entries.append(quote(path.as_posix()))
|
||||
input_entries.append(escape_depfile_path(path.as_posix()))
|
||||
depfile_lines.append(f"{output_relpath}: {' '.join(input_entries)}")
|
||||
|
||||
(state.builddir / DEPFILE_FILENAME).write_text("\n".join(depfile_lines), encoding="utf-8")
|
||||
@@ -480,6 +514,10 @@ def option_should_be_forwarded(k: "OptionKey",
|
||||
return False
|
||||
if not str(v.value):
|
||||
return False
|
||||
if not is_for_child and role == "subproject" and k.subproject in {"", our_project_id}:
|
||||
if not coredata.BUILTIN_OPTIONS[k.as_root()].yielding:
|
||||
return k.subproject == our_project_id
|
||||
return True
|
||||
|
||||
if k.module == "python":
|
||||
if k.name == "install_env" and v.value == "prefix":
|
||||
@@ -519,10 +557,8 @@ def scrub_windows_devenv_dirs_from_path(raw_path: str, env: Mapping[str, str]) -
|
||||
return ";".join(clean_entries)
|
||||
|
||||
|
||||
def quote(path: str) -> str:
|
||||
if " " not in path:
|
||||
return path
|
||||
return "\"" + path.replace ("\"", "\\\"") + "\""
|
||||
def escape_depfile_path(path: str) -> str:
|
||||
return path.replace(" ", "\\ ")
|
||||
|
||||
|
||||
def query_releng_location(role: Role) -> Path:
|
||||
@@ -568,10 +604,10 @@ def grab_subprojects_from_parent(subprojects: dict[str, Path], releng_location:
|
||||
continue
|
||||
|
||||
try:
|
||||
subp_here.symlink_to(location, target_is_directory=True)
|
||||
subp_here.symlink_to(Path("..") / ".." / name, target_is_directory=True)
|
||||
continue
|
||||
except OSError as e:
|
||||
if not getattr(e, "winerror") == 1314:
|
||||
if not getattr(e, "winerror", None) == 1314:
|
||||
raise e
|
||||
|
||||
subprocess.run(["git", "worktree", "add", subp_here],
|
||||
@@ -587,6 +623,12 @@ def configure_import_path(releng_location: Path):
|
||||
sys.path.insert(0, str(releng_location.parent))
|
||||
|
||||
|
||||
def detect_mingw_toolchain_for(arch: str) -> Tuple[bool, Optional[str]]:
|
||||
triplet = MINGW_ARCHS.get(arch, arch) + "-w64-mingw32"
|
||||
found = shutil.which(triplet + "-gcc") is not None
|
||||
return (found, triplet)
|
||||
|
||||
|
||||
STATE_FILENAME = "state.dat"
|
||||
DEPFILE_FILENAME = "compat.deps"
|
||||
|
||||
@@ -614,6 +656,11 @@ MSVS_ENVVARS = {
|
||||
"LIB",
|
||||
}
|
||||
|
||||
MINGW_ARCHS = {
|
||||
"x86": "i686",
|
||||
"arm64": "aarch64",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
@@ -1,6 +1,8 @@
|
||||
helper_modern = get_option('helper_modern')
|
||||
helper_legacy = get_option('helper_legacy')
|
||||
helper_compat = ''
|
||||
helper_emulated_modern = get_option('helper_emulated_modern')
|
||||
helper_emulated_legacy = get_option('helper_emulated_legacy')
|
||||
helper_depends = []
|
||||
|
||||
agent_modern = get_option('agent_modern')
|
||||
@@ -18,6 +20,7 @@ server_depends = []
|
||||
|
||||
no_overridden_compat_bits = \
|
||||
helper_modern == 'auto' and helper_legacy == 'auto' \
|
||||
and helper_emulated_modern == 'auto' and helper_emulated_legacy == 'auto' \
|
||||
and agent_modern == 'auto' and agent_legacy == 'auto' \
|
||||
and agent_emulated_modern == 'auto' and agent_emulated_legacy == 'auto'
|
||||
|
||||
|
||||
+61
-32
@@ -388,7 +388,7 @@ namespace Frida.Inject {
|
||||
return false;
|
||||
}
|
||||
|
||||
script_runner.on_stdin (line);
|
||||
script_runner.on_stdin (line, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -414,7 +414,12 @@ namespace Frida.Inject {
|
||||
|
||||
buf[n] = 0;
|
||||
|
||||
script_runner.on_stdin ((string) buf);
|
||||
if (script_runner.terminal_mode == TerminalMode.BINARY) {
|
||||
var bytes = new Bytes (buf[:n]);
|
||||
script_runner.on_stdin ("", bytes);
|
||||
} else {
|
||||
script_runner.on_stdin ((string) buf, null);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -549,7 +554,7 @@ namespace Frida.Inject {
|
||||
var stage = new Json.Node.alloc ().init_string ("early");
|
||||
|
||||
try {
|
||||
yield rpc_client.call ("init", new Json.Node[] { stage, parameters }, io_cancellable);
|
||||
yield rpc_client.call ("init", new Json.Node[] { stage, parameters }, null, io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
@@ -591,7 +596,7 @@ namespace Frida.Inject {
|
||||
private async TerminalMode query_terminal_mode () {
|
||||
Json.Node mode_value;
|
||||
try {
|
||||
mode_value = yield rpc_client.call ("getFridaTerminalMode", new Json.Node[] {}, io_cancellable);
|
||||
mode_value = yield rpc_client.call ("getFridaTerminalMode", new Json.Node[] {}, null, io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
return COOKED;
|
||||
}
|
||||
@@ -599,7 +604,14 @@ namespace Frida.Inject {
|
||||
if (mode_value.get_value_type () != typeof (string))
|
||||
return COOKED;
|
||||
|
||||
return (mode_value.get_string () == "raw") ? TerminalMode.RAW : TerminalMode.COOKED;
|
||||
switch (mode_value.get_string ()) {
|
||||
case "raw":
|
||||
return TerminalMode.RAW;
|
||||
case "binary":
|
||||
return TerminalMode.BINARY;
|
||||
default:
|
||||
return TerminalMode.COOKED;
|
||||
}
|
||||
}
|
||||
|
||||
private void apply_terminal_mode (TerminalMode mode) throws Error {
|
||||
@@ -632,10 +644,10 @@ namespace Frida.Inject {
|
||||
}
|
||||
#endif
|
||||
|
||||
public void on_stdin (string data) {
|
||||
var data_value = new Json.Node.alloc ().init_string (data);
|
||||
public void on_stdin (string str, Bytes? data) {
|
||||
var str_value = new Json.Node.alloc ().init_string (str);
|
||||
|
||||
rpc_client.call.begin ("onFridaStdin", new Json.Node[] { data_value }, io_cancellable);
|
||||
rpc_client.call.begin ("onFridaStdin", new Json.Node[] { str_value }, data, io_cancellable);
|
||||
}
|
||||
|
||||
private void on_script_file_changed (File file, File? other_file, FileMonitorEvent event_type) {
|
||||
@@ -675,7 +687,7 @@ namespace Frida.Inject {
|
||||
handled = try_handle_log_message (message);
|
||||
break;
|
||||
case "send":
|
||||
handled = try_handle_stdout_message (message);
|
||||
handled = try_handle_stdout_message (message, data);
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
@@ -722,20 +734,19 @@ namespace Frida.Inject {
|
||||
*
|
||||
* {"type":"send","payload":["frida:stdout","DATA"]}
|
||||
*/
|
||||
private bool try_handle_stdout_message (Json.Object message) {
|
||||
private bool try_handle_stdout_message (Json.Object message, Bytes? data) {
|
||||
var payload = message.get_member ("payload");
|
||||
if (payload.get_node_type () != Json.NodeType.ARRAY)
|
||||
return false;
|
||||
|
||||
var tuple = payload.get_array ();
|
||||
if (tuple.get_length () != 2)
|
||||
var tuple_len = tuple.get_length ();
|
||||
if (tuple_len == 0)
|
||||
return false;
|
||||
|
||||
var first_element = tuple.get_element (0);
|
||||
if (first_element.get_value_type () != typeof (string))
|
||||
var type = tuple.get_element (0).get_string ();
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
var type = first_element.get_string ();
|
||||
switch (type) {
|
||||
case "frida:stdout":
|
||||
case "frida:stderr":
|
||||
@@ -744,31 +755,49 @@ namespace Frida.Inject {
|
||||
return false;
|
||||
}
|
||||
|
||||
var second_element = tuple.get_element (1);
|
||||
if (second_element.get_value_type () != typeof (string))
|
||||
return false;
|
||||
var str = second_element.get_string ();
|
||||
|
||||
switch (type) {
|
||||
case "frida:stdout":
|
||||
stdout.write (str.data);
|
||||
stdout.flush ();
|
||||
return true;
|
||||
case "frida:stderr":
|
||||
stderr.write (str.data);
|
||||
return true;
|
||||
default:
|
||||
if (tuple_len >= 2) {
|
||||
var str = tuple.get_element (1).get_string ();
|
||||
if (str == null)
|
||||
return false;
|
||||
|
||||
switch (type) {
|
||||
case "frida:stdout":
|
||||
stdout.write (str.data);
|
||||
stdout.flush ();
|
||||
break;
|
||||
case "frida:stderr":
|
||||
stderr.write (str.data);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
switch (type) {
|
||||
case "frida:stdout":
|
||||
stdout.write (data.get_data ());
|
||||
stdout.flush ();
|
||||
break;
|
||||
case "frida:stderr":
|
||||
stderr.write (data.get_data ());
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void post_rpc_message (string json, Cancellable? cancellable) throws Error, IOError {
|
||||
script.post (json, null);
|
||||
private async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError {
|
||||
script.post (json, data);
|
||||
}
|
||||
}
|
||||
|
||||
private enum TerminalMode {
|
||||
COOKED,
|
||||
RAW
|
||||
RAW,
|
||||
BINARY
|
||||
}
|
||||
}
|
||||
+15
-16
@@ -1509,13 +1509,14 @@ namespace Frida.Agent {
|
||||
}
|
||||
|
||||
public static NativeBridgeApi open () throws Error {
|
||||
string? nb_mod = null;
|
||||
string? vm_mod = null;
|
||||
Gum.Process.enumerate_modules ((details) => {
|
||||
if (/\/lib(64)?\/libnativebridge.so$/.match (details.path))
|
||||
nb_mod = details.path;
|
||||
else if (/^lib(art|dvm).so$/.match (details.name) && !/\/system\/fake-libs/.match (details.path))
|
||||
vm_mod = details.path;
|
||||
Gum.Module? nb_mod = null;
|
||||
Gum.Module? vm_mod = null;
|
||||
Gum.Process.enumerate_modules (module => {
|
||||
unowned string path = module.path;
|
||||
if (/\/lib(64)?\/libnativebridge.so$/.match (path))
|
||||
nb_mod = module;
|
||||
else if (/^lib(art|dvm).so$/.match (module.name) && !/\/system\/fake-libs/.match (path))
|
||||
vm_mod = module;
|
||||
bool carry_on = nb_mod == null || vm_mod == null;
|
||||
return carry_on;
|
||||
});
|
||||
@@ -1530,29 +1531,27 @@ namespace Frida.Agent {
|
||||
NBUnloadLibraryFunc? unload;
|
||||
NBGetTrampolineFunc get_trampoline;
|
||||
|
||||
load = (NBLoadLibraryFunc) Gum.Module.find_export_by_name (nb_mod, "NativeBridgeLoadLibrary");;
|
||||
load = (NBLoadLibraryFunc) nb_mod.find_export_by_name ("NativeBridgeLoadLibrary");;
|
||||
if (load != null) {
|
||||
flavor = MODERN;
|
||||
load_ext = (NBLoadLibraryExtFunc) Gum.Module.find_export_by_name (nb_mod, "NativeBridgeLoadLibraryExt");
|
||||
load_ext = (NBLoadLibraryExtFunc) nb_mod.find_export_by_name ("NativeBridgeLoadLibraryExt");
|
||||
// XXX: NativeBridgeUnloadLibrary() is only a stub as of Android 11 w/ libndk_translation.so
|
||||
unload = null;
|
||||
get_trampoline = (NBGetTrampolineFunc) Gum.Module.find_export_by_name (nb_mod,
|
||||
"NativeBridgeGetTrampoline");
|
||||
get_trampoline = (NBGetTrampolineFunc) nb_mod.find_export_by_name ("NativeBridgeGetTrampoline");
|
||||
} else {
|
||||
flavor = LEGACY;
|
||||
load = (NBLoadLibraryFunc) Gum.Module.find_export_by_name (nb_mod,
|
||||
"_ZN7android23NativeBridgeLoadLibraryEPKci");
|
||||
load_ext = (NBLoadLibraryExtFunc) Gum.Module.find_export_by_name (nb_mod,
|
||||
load = (NBLoadLibraryFunc) nb_mod.find_export_by_name ("_ZN7android23NativeBridgeLoadLibraryEPKci");
|
||||
load_ext = (NBLoadLibraryExtFunc) nb_mod.find_export_by_name (
|
||||
"_ZN7android26NativeBridgeLoadLibraryExtEPKciPNS_25native_bridge_namespace_tE");
|
||||
// XXX: Unload implementation seems to be unreliable.
|
||||
unload = null;
|
||||
get_trampoline = (NBGetTrampolineFunc) Gum.Module.find_export_by_name (nb_mod,
|
||||
get_trampoline = (NBGetTrampolineFunc) nb_mod.find_export_by_name (
|
||||
"_ZN7android25NativeBridgeGetTrampolineEPvPKcS2_j");
|
||||
}
|
||||
if (load == null || get_trampoline == null)
|
||||
throw new Error.NOT_SUPPORTED ("NativeBridge API is not available on this system");
|
||||
|
||||
var get_vms = (JNIGetCreatedJavaVMsFunc) Gum.Module.find_export_by_name (vm_mod, "JNI_GetCreatedJavaVMs");
|
||||
var get_vms = (JNIGetCreatedJavaVMsFunc) vm_mod.find_export_by_name ("JNI_GetCreatedJavaVMs");
|
||||
if (get_vms == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to locate Java VM");
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
global:
|
||||
frida_agent_main;
|
||||
JNI_OnLoad;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
global:
|
||||
frida_agent_main;
|
||||
JNI_OnLoad;
|
||||
|
||||
local:
|
||||
*;
|
||||
|
||||
@@ -28,7 +28,7 @@ if host_os_family == 'windows'
|
||||
elif host_os_family == 'darwin'
|
||||
extra_link_args += '-Wl,-exported_symbol,_frida_agent_main'
|
||||
else
|
||||
symscript = 'frida-agent.version'
|
||||
symscript = (host_os == 'android') ? 'frida-agent-android.version' : 'frida-agent.version'
|
||||
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
|
||||
extra_link_depends += symscript
|
||||
endif
|
||||
|
||||
+135
-9
@@ -428,21 +428,28 @@ namespace Frida {
|
||||
return *((double *) &bits);
|
||||
}
|
||||
|
||||
public string read_string (size_t offset) {
|
||||
public string read_string (size_t offset) throws Error {
|
||||
string * start = (string *) get_pointer (offset, sizeof (char));
|
||||
size_t max_length = size - offset;
|
||||
string * val = (string *) get_pointer (offset, sizeof (char));
|
||||
string * end = memchr (val, 0, max_length);
|
||||
assert (end != null);
|
||||
size_t size = end - val;
|
||||
return val->substring (0, (long) size);
|
||||
string * end = memchr (start, 0, max_length);
|
||||
if (end == null)
|
||||
throw new Error.PROTOCOL ("Missing null character");
|
||||
size_t size = end - start;
|
||||
string val = start->substring (0, (long) size);
|
||||
if (!val.validate ())
|
||||
throw new Error.PROTOCOL ("Invalid UTF-8 string");
|
||||
return val;
|
||||
}
|
||||
|
||||
[CCode (cname = "memchr", cheader_filename = "string.h")]
|
||||
private extern static string * memchr (string * s, int c, size_t n);
|
||||
|
||||
public string read_fixed_string (size_t offset, size_t size) {
|
||||
string * val = (string *) get_pointer (offset, size);
|
||||
return val->substring (0, (long) size);
|
||||
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);
|
||||
if (!val.validate ())
|
||||
throw new Error.PROTOCOL ("Invalid UTF-8 string");
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned Buffer write_string (size_t offset, string val) {
|
||||
@@ -458,4 +465,123 @@ namespace Frida {
|
||||
return data + offset;
|
||||
}
|
||||
}
|
||||
|
||||
public class BufferReader {
|
||||
public size_t available {
|
||||
get {
|
||||
return buffer.bytes.get_size () - offset;
|
||||
}
|
||||
}
|
||||
|
||||
private Buffer buffer;
|
||||
private size_t offset = 0;
|
||||
|
||||
public BufferReader (Buffer buf) {
|
||||
buffer = buf;
|
||||
}
|
||||
|
||||
public uint64 read_pointer (size_t offset) throws Error {
|
||||
var pointer_size = buffer.pointer_size;
|
||||
check_available (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);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint8 read_uint8 () throws Error {
|
||||
check_available (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);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint16 read_uint16 () throws Error {
|
||||
check_available (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);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint32 read_uint32 () throws Error {
|
||||
check_available (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);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint64 read_uint64 () throws Error {
|
||||
check_available (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);
|
||||
return val;
|
||||
}
|
||||
|
||||
public double read_double () throws Error {
|
||||
check_available (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;
|
||||
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;
|
||||
return val;
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = buffer.bytes[offset:offset + size];
|
||||
offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
private void check_available (size_t n) throws Error {
|
||||
if (available < n)
|
||||
throw new Error.PROTOCOL ("Malformed buffer: truncated");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace Frida {
|
||||
public class CFArray<T> {
|
||||
private CoreFoundation.Array handle;
|
||||
|
||||
public static CFArray<T> wrap<T> (void * handle) {
|
||||
return new CFArray<T> ((CoreFoundation.Array) handle);
|
||||
}
|
||||
|
||||
private CFArray (CoreFoundation.Array handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
public Iterator<T> iterator () {
|
||||
return new Iterator<T> (handle);
|
||||
}
|
||||
|
||||
public class Iterator<T> {
|
||||
public CoreFoundation.Array handle;
|
||||
private CoreFoundation.Index i = 0;
|
||||
private CoreFoundation.Index length;
|
||||
|
||||
internal Iterator (CoreFoundation.Array arr) {
|
||||
handle = arr;
|
||||
length = arr.length;
|
||||
}
|
||||
|
||||
public unowned T? next_value () {
|
||||
if (i == length)
|
||||
return null;
|
||||
return handle[i++];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "frida-darwin.h"
|
||||
|
||||
gpointer
|
||||
_frida_dispatch_retain (gpointer object)
|
||||
{
|
||||
dispatch_retain (object);
|
||||
return object;
|
||||
}
|
||||
|
||||
void
|
||||
_frida_xpc_connection_set_event_handler (xpc_connection_t connection, FridaXpcHandler handler, gpointer user_data)
|
||||
{
|
||||
xpc_connection_set_event_handler (connection, ^(xpc_object_t object)
|
||||
{
|
||||
handler (object, user_data);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
_frida_xpc_connection_send_message_with_reply (xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq,
|
||||
FridaXpcHandler handler, gpointer user_data, GDestroyNotify notify)
|
||||
{
|
||||
xpc_connection_send_message_with_reply (connection, message, replyq, ^(xpc_object_t object)
|
||||
{
|
||||
handler (object, user_data);
|
||||
if (notify != NULL)
|
||||
notify (user_data);
|
||||
});
|
||||
}
|
||||
|
||||
gchar *
|
||||
_frida_xpc_object_to_string (xpc_object_t object)
|
||||
{
|
||||
gchar * result;
|
||||
char * str;
|
||||
|
||||
str = xpc_copy_description (object);
|
||||
result = g_strdup (str);
|
||||
free (str);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_frida_xpc_dictionary_apply (xpc_object_t dict, FridaXpcDictionaryApplier applier, gpointer user_data)
|
||||
{
|
||||
return xpc_dictionary_apply (dict, ^bool (const char * key, xpc_object_t val)
|
||||
{
|
||||
return applier (key, val, user_data);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef __FRIDA_DARWIN_H__
|
||||
#define __FRIDA_DARWIN_H__
|
||||
|
||||
#ifdef HAVE_MACOS
|
||||
|
||||
#include <glib.h>
|
||||
#include <xpc/xpc.h>
|
||||
|
||||
typedef void (* FridaXpcHandler) (xpc_object_t object, gpointer user_data);
|
||||
typedef gboolean (* FridaXpcDictionaryApplier) (const gchar * key, xpc_object_t val, gpointer user_data);
|
||||
|
||||
gpointer _frida_dispatch_retain (gpointer object);
|
||||
|
||||
void _frida_xpc_connection_set_event_handler (xpc_connection_t connection, FridaXpcHandler handler, gpointer user_data);
|
||||
void _frida_xpc_connection_send_message_with_reply (xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq,
|
||||
FridaXpcHandler handler, gpointer user_data, GDestroyNotify notify);
|
||||
gchar * _frida_xpc_object_to_string (xpc_object_t object);
|
||||
gboolean _frida_xpc_dictionary_apply (xpc_object_t dict, FridaXpcDictionaryApplier applier, gpointer user_data);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+24
-1
@@ -9,9 +9,22 @@ base_sources = [
|
||||
'socket.vala',
|
||||
'socket-glue.c',
|
||||
'buffer.vala',
|
||||
'value.vala',
|
||||
]
|
||||
|
||||
extra_vala_args = []
|
||||
|
||||
if host_os == 'macos'
|
||||
base_sources += [
|
||||
'xpc.vala',
|
||||
'frida-darwin.c',
|
||||
]
|
||||
endif
|
||||
|
||||
if host_os_family == 'darwin'
|
||||
base_sources += 'corefoundation.vala'
|
||||
endif
|
||||
|
||||
extra_deps = []
|
||||
if host_os_family != 'windows'
|
||||
extra_vala_args += '--pkg=gio-unix-2.0'
|
||||
@@ -22,15 +35,25 @@ base_vala_args = [gum_vala_args, '--pkg=gio-2.0', '--pkg=json-glib-1.0']
|
||||
if nice_dep.found()
|
||||
base_vala_args += '--pkg=nice'
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
base_vala_args += [
|
||||
'--pkg=corefoundation',
|
||||
'--pkg=darwin-gcd',
|
||||
'--pkg=darwin-xpc',
|
||||
]
|
||||
endif
|
||||
|
||||
base_gir_name = f'FridaBase-@[email protected]'
|
||||
|
||||
base = static_library('frida-base-' + api_version, base_sources,
|
||||
c_args: frida_component_cflags,
|
||||
vala_args: [base_vala_args, '--pkg=libsoup-3.0'] + extra_vala_args,
|
||||
vala_header: 'frida-base.h',
|
||||
vala_gir: base_gir_name,
|
||||
vala_vapi: f'frida-base-@[email protected]',
|
||||
dependencies: [glib_dep, gobject_dep, gio_dep, libsoup_dep, nice_dep, usrsctp_dep, gee_dep, json_glib_dep, gum_dep, openssl_dep] + extra_deps,
|
||||
install: true,
|
||||
install_dir: [true, header_install_dir, true],
|
||||
install_dir: [true, header_install_dir, true, false],
|
||||
)
|
||||
|
||||
base_dep = declare_dependency(
|
||||
|
||||
+2
-15
@@ -774,25 +774,12 @@ namespace Frida {
|
||||
protected extern void _handle_transport_packet (uint8[] data);
|
||||
|
||||
protected int _emit_transport_packet (uint8[] data) {
|
||||
var v = OutputVector ();
|
||||
v.buffer = data;
|
||||
v.size = data.length;
|
||||
|
||||
OutputVector[] vectors = { v };
|
||||
|
||||
var m = OutputMessage ();
|
||||
m.vectors = vectors;
|
||||
m.num_vectors = vectors.length;
|
||||
|
||||
OutputMessage[] messages = { m };
|
||||
|
||||
try {
|
||||
transport_socket.send_messages (messages, 0, 0, io_cancellable);
|
||||
Udp.send (data, transport_socket, io_cancellable);
|
||||
return 0;
|
||||
} catch (GLib.Error e) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void _on_sctp_socket_events_changed () {
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ namespace Frida {
|
||||
Object (peer: peer);
|
||||
}
|
||||
|
||||
public async Json.Node call (string method, Json.Node[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
public async Json.Node call (string method, Json.Node[] args, Bytes? data, Cancellable? cancellable) throws Error, IOError {
|
||||
string request_id = Uuid.string_random ();
|
||||
|
||||
var request = new Json.Builder ();
|
||||
@@ -39,7 +39,7 @@ namespace Frida {
|
||||
pending_responses[request_id] = pending;
|
||||
|
||||
try {
|
||||
yield peer.post_rpc_message (raw_request, cancellable);
|
||||
yield peer.post_rpc_message (raw_request, data, cancellable);
|
||||
} catch (Error e) {
|
||||
if (pending_responses.unset (request_id))
|
||||
pending.complete_with_error (e);
|
||||
@@ -158,6 +158,6 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public interface RpcPeer : Object {
|
||||
public abstract async void post_rpc_message (string json, Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
}
|
||||
@@ -2079,6 +2079,17 @@ namespace Frida {
|
||||
}
|
||||
|
||||
namespace Numeric {
|
||||
public uint int64_hash (int64? val) {
|
||||
uint64 v = (uint64) val.abs ();
|
||||
return (uint) ((v >> 32) ^ (v & 0xffffffffU));
|
||||
}
|
||||
|
||||
public bool int64_equal (int64? val_a, int64? val_b) {
|
||||
int64 a = val_a;
|
||||
int64 b = val_b;
|
||||
return a == b;
|
||||
}
|
||||
|
||||
public uint uint64_hash (uint64? val) {
|
||||
uint64 v = val;
|
||||
return (uint) ((v >> 32) ^ (v & 0xffffffffU));
|
||||
|
||||
+563
-353
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,391 @@
|
||||
namespace Frida {
|
||||
public interface ObjectBuilder : Object {
|
||||
public abstract unowned ObjectBuilder begin_dictionary ();
|
||||
public abstract unowned ObjectBuilder set_member_name (string name);
|
||||
public abstract unowned ObjectBuilder end_dictionary ();
|
||||
|
||||
public abstract unowned ObjectBuilder begin_array ();
|
||||
public abstract unowned ObjectBuilder end_array ();
|
||||
|
||||
public abstract unowned ObjectBuilder add_null_value ();
|
||||
public abstract unowned ObjectBuilder add_bool_value (bool val);
|
||||
public abstract unowned ObjectBuilder add_int64_value (int64 val);
|
||||
public abstract unowned ObjectBuilder add_uint64_value (uint64 val);
|
||||
public abstract unowned ObjectBuilder add_data_value (Bytes val);
|
||||
public abstract unowned ObjectBuilder add_string_value (string val);
|
||||
public abstract unowned ObjectBuilder add_uuid_value (uint8[] val);
|
||||
public abstract unowned ObjectBuilder add_raw_value (Bytes val);
|
||||
|
||||
public abstract Bytes build ();
|
||||
}
|
||||
|
||||
public interface ObjectReader : Object {
|
||||
public abstract bool has_member (string name) throws Error;
|
||||
public abstract unowned ObjectReader read_member (string name) throws Error;
|
||||
public abstract unowned ObjectReader end_member ();
|
||||
|
||||
public abstract uint count_elements () throws Error;
|
||||
public abstract unowned ObjectReader read_element (uint index) throws Error;
|
||||
public abstract unowned ObjectReader end_element () throws Error;
|
||||
|
||||
public abstract bool get_bool_value () throws Error;
|
||||
public abstract uint8 get_uint8_value () throws Error;
|
||||
public abstract uint16 get_uint16_value () throws Error;
|
||||
public abstract int64 get_int64_value () throws Error;
|
||||
public abstract uint64 get_uint64_value () throws Error;
|
||||
public abstract Bytes get_data_value () throws Error;
|
||||
public abstract unowned string get_string_value () throws Error;
|
||||
public abstract unowned string get_uuid_value () throws Error;
|
||||
}
|
||||
|
||||
public class VariantReader : Object, ObjectReader {
|
||||
public Variant root_object {
|
||||
get {
|
||||
return scopes.peek_head ().val;
|
||||
}
|
||||
}
|
||||
|
||||
public Variant current_object {
|
||||
get {
|
||||
return scopes.peek_tail ().val;
|
||||
}
|
||||
}
|
||||
|
||||
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
|
||||
|
||||
public VariantReader (Variant v) {
|
||||
push_scope (v);
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
|
||||
return scope.dict.contains (name);
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
|
||||
|
||||
Variant? v = scope.dict.lookup_value (name, null);
|
||||
if (v == null)
|
||||
throw new Error.PROTOCOL ("Key '%s' not found in dictionary: %s", name, scope.val.print (true));
|
||||
|
||||
push_scope (v);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_member () {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint count_elements () throws Error {
|
||||
var scope = peek_scope ();
|
||||
scope.check_array ();
|
||||
return (uint) scope.val.n_children ();
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_element (uint index) throws Error {
|
||||
var scope = peek_scope ();
|
||||
scope.check_array ();
|
||||
push_scope (scope.val.get_child_value (index).get_variant ());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_element () throws Error {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool get_bool_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.BOOLEAN).get_boolean ();
|
||||
}
|
||||
|
||||
public uint8 get_uint8_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.BYTE).get_byte ();
|
||||
}
|
||||
|
||||
public uint16 get_uint16_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.UINT16).get_uint16 ();
|
||||
}
|
||||
|
||||
public int64 get_int64_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.INT64).get_int64 ();
|
||||
}
|
||||
|
||||
public uint64 get_uint64_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.UINT64).get_uint64 ();
|
||||
}
|
||||
|
||||
public Bytes get_data_value () throws Error {
|
||||
return peek_scope ().get_value (new VariantType.array (VariantType.BYTE)).get_data_as_bytes ();
|
||||
}
|
||||
|
||||
public unowned string get_string_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.STRING).get_string ();
|
||||
}
|
||||
|
||||
public unowned string get_uuid_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.STRING).get_string (); // TODO: Use a tuple to avoid ambiguity.
|
||||
}
|
||||
|
||||
private void push_scope (Variant v) {
|
||||
scopes.offer_tail (new Scope (v));
|
||||
}
|
||||
|
||||
private Scope peek_scope () {
|
||||
return scopes.peek_tail ();
|
||||
}
|
||||
|
||||
private Scope pop_scope () {
|
||||
return scopes.poll_tail ();
|
||||
}
|
||||
|
||||
private class Scope {
|
||||
public Variant val;
|
||||
public VariantDict? dict;
|
||||
public bool is_array = false;
|
||||
|
||||
public Scope (Variant v) {
|
||||
val = v;
|
||||
|
||||
VariantType t = v.get_type ();
|
||||
if (t.equal (VariantType.VARDICT))
|
||||
dict = new VariantDict (v);
|
||||
else if (t.is_subtype_of (VariantType.ARRAY))
|
||||
is_array = true;
|
||||
}
|
||||
|
||||
public Variant get_value (VariantType expected_type) throws Error {
|
||||
if (!val.get_type ().equal (expected_type)) {
|
||||
throw new Error.PROTOCOL ("Expected type '%s', got '%s'",
|
||||
(string) expected_type.peek_string (),
|
||||
(string) val.get_type ().peek_string ());
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void check_array () throws Error {
|
||||
if (!is_array)
|
||||
throw new Error.PROTOCOL ("Array expected, but at %s", val.print (true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonObjectBuilder : Object, ObjectBuilder {
|
||||
private Json.Builder builder = new Json.Builder ();
|
||||
private Gee.Map<string, Bytes> raw_values = new Gee.HashMap<string, Bytes> ();
|
||||
|
||||
public unowned ObjectBuilder begin_dictionary () {
|
||||
builder.begin_object ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder set_member_name (string name) {
|
||||
builder.set_member_name (name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder end_dictionary () {
|
||||
builder.end_object ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder begin_array () {
|
||||
builder.begin_array ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder end_array () {
|
||||
builder.end_array ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_null_value () {
|
||||
builder.add_null_value ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_bool_value (bool val) {
|
||||
builder.add_boolean_value (val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_int64_value (int64 val) {
|
||||
builder.add_int_value (val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_uint64_value (uint64 val) {
|
||||
builder.add_int_value ((int64) val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_data_value (Bytes val) {
|
||||
builder.add_string_value (Base64.encode (val.get_data ()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_string_value (string val) {
|
||||
builder.add_string_value (val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_uuid_value (uint8[] val) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_raw_value (Bytes val) {
|
||||
string uuid = Uuid.string_random ();
|
||||
builder.add_string_value (uuid);
|
||||
raw_values[uuid] = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Bytes build () {
|
||||
string json = Json.to_string (builder.get_root (), false);
|
||||
|
||||
foreach (var e in raw_values.entries) {
|
||||
unowned string uuid = e.key;
|
||||
Bytes val = e.value;
|
||||
|
||||
unowned string raw_str = (string) val.get_data ();
|
||||
string str = raw_str[:(long) val.get_size ()];
|
||||
|
||||
json = json.replace ("\"" + uuid + "\"", str);
|
||||
}
|
||||
|
||||
return new Bytes (json.data);
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonObjectReader : Object, ObjectReader {
|
||||
private Json.Reader reader;
|
||||
|
||||
public JsonObjectReader (string json) throws Error {
|
||||
try {
|
||||
reader = new Json.Reader (Json.from_string (json));
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
bool result = reader.read_member (name);
|
||||
reader.end_member ();
|
||||
return result;
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_member (string name) throws Error {
|
||||
if (!reader.read_member (name))
|
||||
throw_dict_access_error ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_member () {
|
||||
reader.end_member ();
|
||||
return this;
|
||||
}
|
||||
|
||||
[NoReturn]
|
||||
private void throw_dict_access_error () throws Error {
|
||||
GLib.Error e = reader.get_error ();
|
||||
reader.end_member ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
public uint count_elements () throws Error {
|
||||
int n = reader.count_elements ();
|
||||
if (n == -1)
|
||||
throw_array_access_error ();
|
||||
return n;
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_element (uint index) throws Error {
|
||||
if (!reader.read_element (index)) {
|
||||
GLib.Error e = reader.get_error ();
|
||||
reader.end_element ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_element () throws Error {
|
||||
reader.end_element ();
|
||||
return this;
|
||||
}
|
||||
|
||||
[NoReturn]
|
||||
private void throw_array_access_error () throws Error {
|
||||
GLib.Error e = reader.get_error ();
|
||||
reader.end_element ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
public bool get_bool_value () throws Error {
|
||||
bool v = reader.get_boolean_value ();
|
||||
if (!v)
|
||||
maybe_throw_value_access_error ();
|
||||
return v;
|
||||
}
|
||||
|
||||
public uint8 get_uint8_value () throws Error {
|
||||
int64 v = get_int64_value ();
|
||||
if (v < 0 || v > uint8.MAX)
|
||||
throw new Error.PROTOCOL ("Invalid uint8");
|
||||
return (uint8) v;
|
||||
}
|
||||
|
||||
public uint16 get_uint16_value () throws Error {
|
||||
int64 v = get_int64_value ();
|
||||
if (v < 0 || v > uint16.MAX)
|
||||
throw new Error.PROTOCOL ("Invalid uint16");
|
||||
return (uint16) v;
|
||||
}
|
||||
|
||||
public int64 get_int64_value () throws Error {
|
||||
int64 v = reader.get_int_value ();
|
||||
if (v == 0)
|
||||
maybe_throw_value_access_error ();
|
||||
return v;
|
||||
}
|
||||
|
||||
public uint64 get_uint64_value () throws Error {
|
||||
int64 v = get_int64_value ();
|
||||
if (v < 0)
|
||||
throw new Error.PROTOCOL ("Invalid uint64");
|
||||
return v;
|
||||
}
|
||||
|
||||
public Bytes get_data_value () throws Error {
|
||||
return new Bytes (Base64.decode (get_string_value ()));
|
||||
}
|
||||
|
||||
public unowned string get_string_value () throws Error {
|
||||
unowned string? v = reader.get_string_value ();
|
||||
if (v == null)
|
||||
maybe_throw_value_access_error ();
|
||||
return v;
|
||||
}
|
||||
|
||||
public unowned string get_uuid_value () throws Error {
|
||||
return get_string_value ();
|
||||
}
|
||||
|
||||
private void maybe_throw_value_access_error () throws Error {
|
||||
GLib.Error? e = reader.get_error ();
|
||||
if (e == null)
|
||||
return;
|
||||
reader.end_member ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
namespace Frida {
|
||||
public class XpcClient : Object {
|
||||
public signal void message (Darwin.Xpc.Object obj);
|
||||
|
||||
public State state {
|
||||
get {
|
||||
return _state;
|
||||
}
|
||||
}
|
||||
|
||||
public Darwin.Xpc.Connection connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Darwin.GCD.DispatchQueue queue {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private State _state;
|
||||
private string? close_reason;
|
||||
private MainContext main_context;
|
||||
|
||||
public enum State {
|
||||
OPEN,
|
||||
CLOSING,
|
||||
CLOSED,
|
||||
}
|
||||
|
||||
public static XpcClient make_for_mach_service (string name, Darwin.GCD.DispatchQueue queue) {
|
||||
return new XpcClient (Darwin.Xpc.Connection.create_mach_service (name, queue, 0), queue);
|
||||
}
|
||||
|
||||
public XpcClient (Darwin.Xpc.Connection connection, Darwin.GCD.DispatchQueue queue) {
|
||||
Object (connection: connection, queue: queue);
|
||||
}
|
||||
|
||||
construct {
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
|
||||
connection.set_event_handler (on_event);
|
||||
connection.activate ();
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
if (close_reason != null) {
|
||||
change_state (CLOSED);
|
||||
} else {
|
||||
change_state (CLOSING);
|
||||
this.ref ();
|
||||
connection.cancel ();
|
||||
}
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
public async Darwin.Xpc.Object request (Darwin.Xpc.Object message, Cancellable? cancellable) throws Error, IOError {
|
||||
Darwin.Xpc.Object? reply = null;
|
||||
connection.send_message_with_reply (message, queue, r => {
|
||||
schedule_on_frida_thread (() => {
|
||||
reply = r;
|
||||
request.callback ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
});
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (() => {
|
||||
connection.cancel ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
cancel_source.attach (main_context);
|
||||
|
||||
yield;
|
||||
|
||||
cancel_source.destroy ();
|
||||
|
||||
if (reply.type == Darwin.Xpc.Error.TYPE) {
|
||||
var e = (Darwin.Xpc.Error) reply;
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION));
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public void post (Darwin.Xpc.Object message) throws Error, IOError {
|
||||
connection.send_message (message);
|
||||
}
|
||||
|
||||
private void change_state (State new_state) {
|
||||
_state = new_state;
|
||||
notify_property ("state");
|
||||
}
|
||||
|
||||
private void on_event (Darwin.Xpc.Object obj) {
|
||||
schedule_on_frida_thread (() => {
|
||||
if (obj.type == Darwin.Xpc.Error.TYPE) {
|
||||
var e = (Darwin.Xpc.Error) obj;
|
||||
close_reason = e.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION);
|
||||
switch (state) {
|
||||
case OPEN:
|
||||
change_state (CLOSED);
|
||||
break;
|
||||
case CLOSING:
|
||||
change_state (CLOSED);
|
||||
unref ();
|
||||
break;
|
||||
case CLOSED:
|
||||
assert_not_reached ();
|
||||
}
|
||||
} else {
|
||||
message (obj);
|
||||
}
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void schedule_on_frida_thread (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
|
||||
public class XpcObjectReader {
|
||||
public Darwin.Xpc.Object root_object {
|
||||
get {
|
||||
return scopes.peek_head ().object;
|
||||
}
|
||||
}
|
||||
|
||||
public Darwin.Xpc.Object current_object {
|
||||
get {
|
||||
return scopes.peek_tail ().object;
|
||||
}
|
||||
}
|
||||
|
||||
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
|
||||
|
||||
public XpcObjectReader (Darwin.Xpc.Object obj) {
|
||||
push_scope (obj);
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
return peek_scope ().get_dictionary ().get_value (name) != null;
|
||||
}
|
||||
|
||||
public bool try_read_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
var dict = scope.get_dictionary ();
|
||||
var val = dict.get_value (name);
|
||||
if (val == null)
|
||||
return false;
|
||||
|
||||
push_scope (val);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader read_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
var dict = scope.get_dictionary ();
|
||||
var val = dict.get_value (name);
|
||||
if (val == null)
|
||||
throw new Error.PROTOCOL ("Key '%s' not found in dictionary: %s", name, scope.object.to_string ());
|
||||
|
||||
push_scope (val);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader end_member () {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public size_t count_elements () throws Error {
|
||||
return peek_scope ().get_array ().count;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader read_element (size_t index) throws Error {
|
||||
push_scope (peek_scope ().get_array ().get_value (index));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader end_element () throws Error {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool get_bool_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Bool> (Darwin.Xpc.Bool.TYPE).get_value ();
|
||||
}
|
||||
|
||||
public int64 get_int64_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Int64> (Darwin.Xpc.Int64.TYPE).get_value ();
|
||||
}
|
||||
|
||||
public uint64 get_uint64_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.UInt64> (Darwin.Xpc.UInt64.TYPE).get_value ();
|
||||
}
|
||||
|
||||
public unowned uint8[] get_data_value () throws Error {
|
||||
var data = peek_scope ().get_object<Darwin.Xpc.Data> (Darwin.Xpc.Data.TYPE);
|
||||
unowned uint8[] buf = (uint8[]) data.get_bytes_ptr ();
|
||||
buf.length = (int) data.get_length ();
|
||||
return buf;
|
||||
}
|
||||
|
||||
public unowned string get_string_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.String> (Darwin.Xpc.String.TYPE).get_string_ptr ();
|
||||
}
|
||||
|
||||
public unowned uint8[] get_uuid_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Uuid> (Darwin.Xpc.Uuid.TYPE).get_bytes ()[:16];
|
||||
}
|
||||
|
||||
public unowned string get_error_description () throws Error {
|
||||
var error = peek_scope ().get_object<Darwin.Xpc.Error> (Darwin.Xpc.Error.TYPE);
|
||||
return error.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION);
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Object get_object_value (Darwin.Xpc.Type expected_type) throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Object> (expected_type);
|
||||
}
|
||||
|
||||
private void push_scope (Darwin.Xpc.Object obj) {
|
||||
scopes.offer_tail (new Scope (obj));
|
||||
}
|
||||
|
||||
private Scope peek_scope () {
|
||||
return scopes.peek_tail ();
|
||||
}
|
||||
|
||||
private Scope pop_scope () {
|
||||
return scopes.poll_tail ();
|
||||
}
|
||||
|
||||
private class Scope {
|
||||
public Darwin.Xpc.Object object;
|
||||
private unowned Darwin.Xpc.Type type;
|
||||
|
||||
public Scope (Darwin.Xpc.Object obj) {
|
||||
object = obj;
|
||||
type = obj.type;
|
||||
}
|
||||
|
||||
public unowned T get_object<T> (Darwin.Xpc.Type expected_type) throws Error {
|
||||
if (type != expected_type)
|
||||
throw new Error.PROTOCOL ("Expected type '%s', got '%s'", expected_type.name, type.name);
|
||||
return object;
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Array get_array () throws Error {
|
||||
return get_object<Darwin.Xpc.Array> (Darwin.Xpc.Array.TYPE);
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Dictionary get_dictionary () throws Error {
|
||||
return get_object<Darwin.Xpc.Dictionary> (Darwin.Xpc.Dictionary.TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
-13
@@ -751,7 +751,7 @@ namespace Frida.Gadget {
|
||||
|
||||
string config_data;
|
||||
try {
|
||||
FileUtils.get_contents (config_path, out config_data);
|
||||
load_asset_text (config_path, out config_data);
|
||||
} catch (FileError e) {
|
||||
if (e is FileError.NOENT)
|
||||
return new Config ();
|
||||
@@ -1226,7 +1226,7 @@ namespace Frida.Gadget {
|
||||
private ScriptConfig load_config (string path) throws Error {
|
||||
string data;
|
||||
try {
|
||||
FileUtils.get_contents (path, out data);
|
||||
load_asset_text (path, out data);
|
||||
} catch (FileError e) {
|
||||
if (e is FileError.NOENT)
|
||||
return new ScriptConfig ();
|
||||
@@ -1349,9 +1349,9 @@ namespace Frida.Gadget {
|
||||
try {
|
||||
var path = this.path;
|
||||
|
||||
uint8[] contents;
|
||||
Bytes contents;
|
||||
try {
|
||||
FileUtils.get_data (path, out contents);
|
||||
load_asset_bytes (path, out contents);
|
||||
} catch (FileError e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
@@ -1360,11 +1360,10 @@ namespace Frida.Gadget {
|
||||
options.name = Path.get_basename (path).split (".", 2)[0];
|
||||
|
||||
ScriptEngine.ScriptInstance instance;
|
||||
if (contents.length > 0 && contents[0] == QUICKJS_BYTECODE_MAGIC) {
|
||||
instance = yield engine.create_script (null, new Bytes (contents), options);
|
||||
} else {
|
||||
instance = yield engine.create_script ((string) contents, null, options);
|
||||
}
|
||||
if (contents.length > 0 && contents[0] == QUICKJS_BYTECODE_MAGIC)
|
||||
instance = yield engine.create_script (null, contents, options);
|
||||
else
|
||||
instance = yield engine.create_script ((string) contents.get_data (), null, options);
|
||||
|
||||
if (id.handle != 0)
|
||||
yield engine.destroy_script (id);
|
||||
@@ -1381,7 +1380,7 @@ namespace Frida.Gadget {
|
||||
var stage = new Json.Node.alloc ().init_string ((peek_state () == State.CREATED) ? "early" : "late");
|
||||
|
||||
try {
|
||||
yield rpc_client.call ("init", new Json.Node[] { stage, parameters }, null);
|
||||
yield rpc_client.call ("init", new Json.Node[] { stage, parameters }, null, null);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
@@ -1449,8 +1448,8 @@ namespace Frida.Gadget {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void post_rpc_message (string json, Cancellable? cancellable) throws Error, IOError {
|
||||
engine.post_to_script (id, json);
|
||||
private async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError {
|
||||
engine.post_to_script (id, json, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1497,7 +1496,8 @@ namespace Frida.Gadget {
|
||||
var endpoint_params = new EndpointParameters (interaction.address, interaction.port,
|
||||
parse_certificate (interaction.certificate, location), interaction.origin, auth_service, asset_root);
|
||||
|
||||
service = new WebService (endpoint_params, CONTROL, interaction.on_port_conflict);
|
||||
service = new WebService (endpoint_params, WebServiceFlavor.CONTROL, interaction.on_port_conflict,
|
||||
new TunnelInterfaceObserver ());
|
||||
service.incoming.connect (on_incoming_connection);
|
||||
yield service.start (io_cancellable);
|
||||
}
|
||||
@@ -2075,6 +2075,60 @@ namespace Frida.Gadget {
|
||||
}
|
||||
#endif
|
||||
|
||||
private void load_asset_text (string filename, out string text) throws FileError {
|
||||
Bytes raw_contents;
|
||||
load_asset_bytes (filename, out raw_contents);
|
||||
|
||||
unowned string str = (string) raw_contents.get_data ();
|
||||
if (!str.validate ())
|
||||
throw new FileError.FAILED ("%s: invalid UTF-8", filename);
|
||||
|
||||
text = str;
|
||||
}
|
||||
|
||||
private void load_asset_bytes (string filename, out Bytes bytes) throws FileError {
|
||||
#if ANDROID
|
||||
if (maybe_load_asset_bytes_from_apk (filename, out bytes))
|
||||
return;
|
||||
#endif
|
||||
|
||||
uint8[] data;
|
||||
FileUtils.get_data (filename, out data);
|
||||
bytes = new Bytes.take ((owned) data);
|
||||
}
|
||||
|
||||
#if ANDROID
|
||||
private bool maybe_load_asset_bytes_from_apk (string filename, out Bytes contents) throws FileError {
|
||||
contents = null;
|
||||
|
||||
var tokens = filename.split ("!", 2);
|
||||
if (tokens.length != 2 || !tokens[0].has_suffix (".apk"))
|
||||
return false;
|
||||
unowned string apk_path = tokens[0];
|
||||
unowned string file_path = tokens[1];
|
||||
|
||||
var reader = Minizip.Reader.create ();
|
||||
try {
|
||||
if (reader.open_file (apk_path) != OK)
|
||||
throw new FileError.FAILED ("Unable to open APK");
|
||||
|
||||
if (reader.locate_entry (file_path[1:], true) != OK)
|
||||
throw new FileError.FAILED ("Unable to locate %s inside APK", file_path);
|
||||
|
||||
var size = reader.entry_save_buffer_length ();
|
||||
var data = new uint8[size + 1];
|
||||
if (reader.entry_save_buffer (data[:size]) != OK)
|
||||
throw new FileError.FAILED ("Unable to extract %s from APK", file_path);
|
||||
|
||||
contents = new Bytes.take ((owned) data);
|
||||
return true;
|
||||
} finally {
|
||||
reader.close ();
|
||||
Minizip.Reader.destroy (ref reader);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private Json.Node make_empty_json_object () {
|
||||
return new Json.Node.alloc ().init_object (new Json.Object ());
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ if host_os_family == 'darwin'
|
||||
extra_link_args += '-Wl,-framework,Foundation'
|
||||
endif
|
||||
|
||||
if host_os == 'android'
|
||||
extra_vala_args += '--pkg=minizip'
|
||||
platform_deps += minizip_dep
|
||||
endif
|
||||
|
||||
if host_os_family == 'windows'
|
||||
if host_toolchain != 'microsoft'
|
||||
symfile = 'frida-gadget.symbols'
|
||||
@@ -60,7 +65,7 @@ raw_gadget = shared_library('frida-gadget-raw', gadget_sources,
|
||||
link_depends: extra_link_depends,
|
||||
vs_module_defs: 'frida-gadget.def',
|
||||
override_options: ['b_asneeded=true'],
|
||||
dependencies: [gio_dep, gee_dep, json_glib_dep, gum_dep, gumjs_dep, base_dep, tls_provider_dep, payload_dep] + platform_deps,
|
||||
dependencies: [gio_dep, gee_dep, json_glib_dep, gum_dep, gumjs_dep, base_dep, netif_dep, tls_provider_dep, payload_dep] + platform_deps,
|
||||
)
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -2,6 +2,7 @@ if host_os == 'android'
|
||||
subdir('selinux')
|
||||
endif
|
||||
subdir('base')
|
||||
subdir('netif')
|
||||
subdir('pipe')
|
||||
subdir('payload')
|
||||
if have_local_backend
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
netif_sources = [
|
||||
'tunnel-interface-observer.vala',
|
||||
]
|
||||
|
||||
extra_vala_args = []
|
||||
extra_link_args = []
|
||||
|
||||
if host_os in ['ios', 'tvos']
|
||||
extra_vala_args += [
|
||||
'--pkg=corefoundation',
|
||||
'--pkg=darwin-gcd',
|
||||
'--pkg=darwin-systemconfiguration',
|
||||
]
|
||||
extra_link_args += '-Wl,-framework,SystemConfiguration'
|
||||
endif
|
||||
|
||||
netif = static_library('frida-netif', netif_sources,
|
||||
c_args: frida_component_cflags,
|
||||
vala_args: [base_vala_args, extra_vala_args],
|
||||
dependencies: [base_dep, gee_dep],
|
||||
)
|
||||
|
||||
netif_dep = declare_dependency(
|
||||
include_directories: include_directories('.'),
|
||||
link_args: extra_link_args,
|
||||
link_with: netif,
|
||||
dependencies: base_dep,
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
public class Frida.TunnelInterfaceObserver : Object, DynamicInterfaceObserver {
|
||||
#if IOS || TVOS
|
||||
private Gee.Map<string, DynamicInterface> interfaces = new Gee.HashMap<string, DynamicInterface> ();
|
||||
|
||||
private Darwin.SystemConfiguration.DynamicStore? store;
|
||||
private Darwin.GCD.DispatchQueue event_queue =
|
||||
new Darwin.GCD.DispatchQueue ("re.frida.endpoint-enumerator", Darwin.GCD.DispatchQueueAttr.SERIAL);
|
||||
|
||||
private MainContext? main_context;
|
||||
|
||||
public override void dispose () {
|
||||
if (store != null) {
|
||||
store.set_dispatch_queue (null);
|
||||
store = null;
|
||||
ref ();
|
||||
event_queue.dispatch_async (do_dispose);
|
||||
}
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
private void do_dispose () {
|
||||
unref ();
|
||||
}
|
||||
|
||||
public void start () {
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
|
||||
Darwin.SystemConfiguration.DynamicStoreContext context = { 0, };
|
||||
context.info = this;
|
||||
store = new Darwin.SystemConfiguration.DynamicStore (null, CoreFoundation.String.make ("Frida"),
|
||||
on_interfaces_changed_wrapper, context);
|
||||
|
||||
var pattern = CoreFoundation.String.make ("State:/Network/Interface/utun.*/IPv6");
|
||||
var patterns = new CoreFoundation.Array (null, ((CoreFoundation.Type[]) &pattern)[:1]);
|
||||
store.set_notification_keys (null, patterns);
|
||||
|
||||
store.set_dispatch_queue (event_queue);
|
||||
|
||||
var initial_keys = store.copy_key_list (pattern);
|
||||
if (initial_keys != null)
|
||||
handle_interface_changes (initial_keys);
|
||||
}
|
||||
|
||||
private static void on_interfaces_changed_wrapper (Darwin.SystemConfiguration.DynamicStore store,
|
||||
CoreFoundation.Array changed_keys, void * info) {
|
||||
unowned TunnelInterfaceObserver enumerator = (TunnelInterfaceObserver) info;
|
||||
enumerator.on_interfaces_changed (changed_keys);
|
||||
}
|
||||
|
||||
private void on_interfaces_changed (CoreFoundation.Array changed_keys) {
|
||||
schedule_on_frida_thread (() => {
|
||||
if (store != null)
|
||||
handle_interface_changes (changed_keys);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void handle_interface_changes (CoreFoundation.Array changed_keys) {
|
||||
var addresses_str = CoreFoundation.String.make ("Addresses");
|
||||
|
||||
foreach (var key in CFArray.wrap<CoreFoundation.String> (changed_keys)) {
|
||||
string name = key.to_string ().split ("/")[3];
|
||||
|
||||
var val = (CoreFoundation.Dictionary) store.copy_value (key);
|
||||
if (val != null) {
|
||||
InetAddress? address = null;
|
||||
foreach (var raw_address in CFArray.wrap<CoreFoundation.String> (val[addresses_str])) {
|
||||
var str = raw_address.to_string ();
|
||||
bool is_reserved_ipv6_range = str.has_prefix ("fc") || str.has_prefix ("fd");
|
||||
bool is_tunnel = is_reserved_ipv6_range && str.has_suffix ("::1");
|
||||
if (is_tunnel) {
|
||||
address = new InetAddress.from_string (str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (address != null && !interfaces.has_key (name)) {
|
||||
var iface = new DynamicInterface (name, address);
|
||||
interfaces[name] = iface;
|
||||
interface_attached (iface);
|
||||
}
|
||||
} else {
|
||||
DynamicInterface iface;
|
||||
if (interfaces.unset (name, out iface))
|
||||
interface_detached (iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void schedule_on_frida_thread (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
#else
|
||||
public void start () {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
+19
-16
@@ -53,15 +53,18 @@ namespace Frida {
|
||||
private delegate ssize_t ReadFunc (int fd, void * buf, size_t count);
|
||||
|
||||
construct {
|
||||
Gum.Module.enumerate_imports ("libart.so", imp => {
|
||||
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);
|
||||
var art = Gum.Process.find_module_by_name ("libart.so");
|
||||
if (art != null) {
|
||||
art.enumerate_imports (imp => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
~ThreadCountCloaker () {
|
||||
@@ -212,34 +215,34 @@ namespace Frida {
|
||||
construct {
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
|
||||
unowned string libc = Gum.Process.query_libc_name ();
|
||||
var libc = Gum.Process.get_libc_module ();
|
||||
|
||||
var open_listener = new OpenDirListener (this);
|
||||
listeners.add (open_listener);
|
||||
interceptor.attach ((void *) Gum.Module.find_export_by_name (libc, "opendir"), open_listener);
|
||||
interceptor.attach ((void *) libc.find_export_by_name ("opendir"), open_listener);
|
||||
|
||||
var close_listener = new CloseDirListener (this);
|
||||
listeners.add (close_listener);
|
||||
interceptor.attach ((void *) Gum.Module.find_export_by_name (libc, "closedir"), close_listener);
|
||||
interceptor.attach ((void *) libc.find_export_by_name ("closedir"), close_listener);
|
||||
|
||||
var readdir_impl = Gum.Module.find_export_by_name (libc, "readdir");
|
||||
var readdir_impl = libc.find_export_by_name ("readdir");
|
||||
var readdir_listener = new ReadDirListener (this, LEGACY);
|
||||
listeners.add (readdir_listener);
|
||||
interceptor.attach ((void *) readdir_impl, readdir_listener);
|
||||
|
||||
var readdir64_impl = Gum.Module.find_export_by_name (libc, "readdir64");
|
||||
var readdir64_impl = libc.find_export_by_name ("readdir64");
|
||||
if (readdir64_impl != 0 && readdir64_impl != readdir_impl) {
|
||||
var listener = new ReadDirListener (this, MODERN);
|
||||
listeners.add (listener);
|
||||
interceptor.attach ((void *) readdir64_impl, listener);
|
||||
}
|
||||
|
||||
var readdir_r_impl = Gum.Module.find_export_by_name (libc, "readdir_r");
|
||||
var readdir_r_impl = libc.find_export_by_name ("readdir_r");
|
||||
var readdir_r_listener = new ReadDirRListener (this, LEGACY);
|
||||
listeners.add (readdir_r_listener);
|
||||
interceptor.attach ((void *) readdir_r_impl, readdir_r_listener);
|
||||
|
||||
var readdir64_r_impl = Gum.Module.find_export_by_name (libc, "readdir64_r");
|
||||
var readdir64_r_impl = libc.find_export_by_name ("readdir64_r");
|
||||
if (readdir64_r_impl != 0 && readdir64_r_impl != readdir_r_impl) {
|
||||
var listener = new ReadDirRListener (this, MODERN);
|
||||
listeners.add (listener);
|
||||
|
||||
@@ -32,16 +32,17 @@ namespace Frida {
|
||||
unowned Gum.InvocationListener listener = this;
|
||||
|
||||
#if WINDOWS
|
||||
interceptor.attach ((void *) Gum.Module.find_export_by_name ("kernel32.dll", "ExitProcess"), listener);
|
||||
interceptor.attach ((void *) Gum.Process.find_module_by_name ("kernel32.dll").find_export_by_name ("ExitProcess"),
|
||||
listener);
|
||||
#else
|
||||
unowned string libc = Gum.Process.query_libc_name ();
|
||||
var libc = Gum.Process.get_libc_module ();
|
||||
const string[] apis = {
|
||||
"exit",
|
||||
"_exit",
|
||||
"abort",
|
||||
};
|
||||
foreach (var symbol in apis) {
|
||||
interceptor.attach ((void *) Gum.Module.find_export_by_name (libc, symbol), listener);
|
||||
interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Frida {
|
||||
construct {
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
|
||||
var close = Gum.Module.find_export_by_name (Gum.Process.query_libc_name (), "close");
|
||||
var close = Gum.Process.get_libc_module ().find_export_by_name ("close");
|
||||
close_listener = new CloseListener (this);
|
||||
interceptor.attach ((void *) close, close_listener);
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ namespace Frida {
|
||||
}
|
||||
|
||||
static construct {
|
||||
unowned string libc = Gum.Process.query_libc_name ();
|
||||
fork_impl = (void *) Gum.Module.find_export_by_name (libc, "fork");
|
||||
vfork_impl = (void *) Gum.Module.find_export_by_name (libc, "vfork");
|
||||
var libc = Gum.Process.get_libc_module ();
|
||||
fork_impl = (void *) libc.find_export_by_name ("fork");
|
||||
vfork_impl = (void *) libc.find_export_by_name ("vfork");
|
||||
}
|
||||
|
||||
construct {
|
||||
@@ -61,15 +61,21 @@ namespace Frida {
|
||||
string cmdline;
|
||||
FileUtils.get_contents ("/proc/self/cmdline", out cmdline);
|
||||
if (cmdline == "zygote" || cmdline == "zygote64" || cmdline == "usap32" || cmdline == "usap64") {
|
||||
var set_argv0 = (void *) Gum.Module.find_export_by_name ("libandroid_runtime.so", "_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring");
|
||||
if (set_argv0 != null) {
|
||||
interceptor.attach (set_argv0, listener, (void *) HookId.SET_ARGV0);
|
||||
child_recovery_behavior = DEFERRED_UNTIL_SET_ARGV0;
|
||||
var runtime = Gum.Process.find_module_by_name ("libandroid_runtime.so");
|
||||
if (runtime != null) {
|
||||
var set_argv0 = (void *) runtime.find_export_by_name ("_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring");
|
||||
if (set_argv0 != null) {
|
||||
interceptor.attach (set_argv0, listener, (void *) HookId.SET_ARGV0);
|
||||
child_recovery_behavior = DEFERRED_UNTIL_SET_ARGV0;
|
||||
}
|
||||
}
|
||||
|
||||
var setcontext = (void *) Gum.Module.find_export_by_name ("libselinux.so", "selinux_android_setcontext");
|
||||
if (setcontext != null)
|
||||
interceptor.attach (setcontext, listener, (void *) HookId.SET_CTX);
|
||||
var selinux = Gum.Process.find_module_by_name ("libselinux.so");
|
||||
if (selinux != null) {
|
||||
var setcontext = (void *) selinux.find_export_by_name ("selinux_android_setcontext");
|
||||
if (setcontext != null)
|
||||
interceptor.attach (setcontext, listener, (void *) HookId.SET_CTX);
|
||||
}
|
||||
}
|
||||
} catch (FileError e) {
|
||||
}
|
||||
|
||||
@@ -25,6 +25,24 @@ frida_run_atexit_handlers (void)
|
||||
{
|
||||
}
|
||||
|
||||
# ifdef HAVE_ASAN
|
||||
|
||||
__attribute__ ((constructor)) static void
|
||||
frida_init_memory (void)
|
||||
{
|
||||
asm volatile ("");
|
||||
}
|
||||
|
||||
# ifndef HAVE_DARWIN
|
||||
__attribute__ ((destructor)) static void
|
||||
frida_deinit_memory (void)
|
||||
{
|
||||
asm volatile ("");
|
||||
}
|
||||
# endif
|
||||
|
||||
# endif
|
||||
|
||||
#else
|
||||
|
||||
#define FRIDA_SHIM_LOCK() gum_spinlock_acquire (&frida_shim_lock)
|
||||
|
||||
@@ -420,7 +420,7 @@ namespace Frida {
|
||||
var reason_value = new Json.Node.alloc ().init_string (reason.to_nick ());
|
||||
|
||||
try {
|
||||
yield rpc_client.call ("dispose", new Json.Node[] { reason_value }, null);
|
||||
yield rpc_client.call ("dispose", new Json.Node[] { reason_value }, null, null);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
|
||||
@@ -467,10 +467,10 @@ namespace Frida {
|
||||
this.debug_message (message);
|
||||
}
|
||||
|
||||
private async void post_rpc_message (string json, Cancellable? cancellable) throws Error, IOError {
|
||||
private async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError {
|
||||
if (script == null)
|
||||
throw new Error.INVALID_OPERATION ("Script is destroyed");
|
||||
script.post (json);
|
||||
script.post (json, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,21 +38,24 @@ namespace Frida {
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
|
||||
#if WINDOWS
|
||||
var create_process_internal = Gum.Module.find_export_by_name ("kernelbase.dll", "CreateProcessInternalW");
|
||||
if (create_process_internal == 0)
|
||||
create_process_internal = Gum.Module.find_export_by_name ("kernel32.dll", "CreateProcessInternalW");
|
||||
var kernelbase = Gum.Process.find_module_by_name ("kernelbase.dll");
|
||||
var create_process_internal = (kernelbase != null) ? kernelbase.find_export_by_name ("CreateProcessInternalW") : 0;
|
||||
if (create_process_internal == 0) {
|
||||
create_process_internal = Gum.Process.find_module_by_name ("kernel32.dll")
|
||||
.find_export_by_name ("CreateProcessInternalW");
|
||||
}
|
||||
assert (create_process_internal != 0);
|
||||
interceptor.attach ((void *) create_process_internal, this);
|
||||
#else
|
||||
unowned string libc = Gum.Process.query_libc_name ();
|
||||
var libc = Gum.Process.get_libc_module ();
|
||||
#if DARWIN
|
||||
posix_spawn = (PosixSpawnFunc) Gum.Module.find_export_by_name (libc, "posix_spawn");
|
||||
posix_spawnattr_init = (PosixSpawnAttrInitFunc) Gum.Module.find_export_by_name (libc, "posix_spawnattr_init");
|
||||
posix_spawnattr_destroy = (PosixSpawnAttrDestroyFunc) Gum.Module.find_export_by_name (libc, "posix_spawnattr_destroy");
|
||||
posix_spawnattr_getflags = (PosixSpawnAttrSetFlagsFunc) Gum.Module.find_export_by_name (libc, "posix_spawnattr_getflags");
|
||||
posix_spawnattr_setflags = (PosixSpawnAttrSetFlagsFunc) Gum.Module.find_export_by_name (libc, "posix_spawnattr_setflags");
|
||||
posix_spawn = (PosixSpawnFunc) libc.find_export_by_name ("posix_spawn");
|
||||
posix_spawnattr_init = (PosixSpawnAttrInitFunc) libc.find_export_by_name ("posix_spawnattr_init");
|
||||
posix_spawnattr_destroy = (PosixSpawnAttrDestroyFunc) libc.find_export_by_name ("posix_spawnattr_destroy");
|
||||
posix_spawnattr_getflags = (PosixSpawnAttrSetFlagsFunc) libc.find_export_by_name ("posix_spawnattr_getflags");
|
||||
posix_spawnattr_setflags = (PosixSpawnAttrSetFlagsFunc) libc.find_export_by_name ("posix_spawnattr_setflags");
|
||||
|
||||
execve = (void *) Gum.Module.find_export_by_name (libc, "execve");
|
||||
execve = (void *) libc.find_export_by_name ("execve");
|
||||
|
||||
interceptor.attach ((void *) posix_spawn, this);
|
||||
|
||||
@@ -60,10 +63,10 @@ namespace Frida {
|
||||
#else
|
||||
Gum.Address execve = 0;
|
||||
#if ANDROID
|
||||
execve = Gum.Module.find_symbol_by_name (libc, "__execve");
|
||||
execve = libc.find_symbol_by_name ("__execve");
|
||||
#endif
|
||||
if (execve == 0)
|
||||
execve = Gum.Module.find_export_by_name (libc, "execve");
|
||||
execve = libc.find_export_by_name ("execve");
|
||||
interceptor.attach ((void *) execve, this);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -26,9 +26,10 @@ namespace Frida {
|
||||
construct {
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
|
||||
task_threads = (TaskThreadsFunc) Gum.Module.find_export_by_name (LIBSYSTEM_KERNEL, "task_threads");
|
||||
thread_suspend = (ThreadSuspendFunc) Gum.Module.find_export_by_name (LIBSYSTEM_KERNEL, "thread_suspend");
|
||||
thread_resume = (ThreadResumeFunc) Gum.Module.find_export_by_name (LIBSYSTEM_KERNEL, "thread_resume");
|
||||
var kernel = Gum.Process.find_module_by_name (LIBSYSTEM_KERNEL);
|
||||
task_threads = (TaskThreadsFunc) kernel.find_export_by_name ("task_threads");
|
||||
thread_suspend = (ThreadSuspendFunc) kernel.find_export_by_name ("thread_suspend");
|
||||
thread_resume = (ThreadResumeFunc) kernel.find_export_by_name ("thread_resume");
|
||||
|
||||
interceptor.replace ((void *) task_threads, (void *) replacement_task_threads, this);
|
||||
interceptor.replace ((void *) thread_suspend, (void *) replacement_thread_suspend, this);
|
||||
|
||||
@@ -300,6 +300,7 @@ static gpointer
|
||||
frida_find_vtable (void)
|
||||
{
|
||||
GumAddress result = 0;
|
||||
GumModule * libunwind;
|
||||
GumAddress export;
|
||||
uint64_t address;
|
||||
G_GNUC_UNUSED cs_err err;
|
||||
@@ -309,11 +310,15 @@ frida_find_vtable (void)
|
||||
size_t size;
|
||||
const size_t max_size = 2048;
|
||||
|
||||
export = gum_module_find_export_by_name (FRIDA_LIBUNWIND_PATH, "unw_init_local");
|
||||
libunwind = gum_process_find_module_by_name (FRIDA_LIBUNWIND_PATH);
|
||||
if (libunwind == NULL)
|
||||
goto beach;
|
||||
|
||||
export = gum_module_find_export_by_name (libunwind, "unw_init_local");
|
||||
if (export == 0)
|
||||
export = gum_module_find_export_by_name (FRIDA_LIBUNWIND_PATH, "_Unwind_RaiseException");
|
||||
export = gum_module_find_export_by_name (libunwind, "_Unwind_RaiseException");
|
||||
if (export == 0)
|
||||
return NULL;
|
||||
goto beach;
|
||||
export = gum_strip_code_address (export);
|
||||
address = export;
|
||||
|
||||
@@ -340,7 +345,7 @@ frida_find_vtable (void)
|
||||
GumMemoryRange bss_range;
|
||||
|
||||
bss_range.base_address = 0;
|
||||
gum_module_enumerate_sections (FRIDA_LIBUNWIND_PATH, (GumFoundSectionFunc) frida_find_bss_range, &bss_range);
|
||||
gum_module_enumerate_sections (libunwind, (GumFoundSectionFunc) frida_find_bss_range, &bss_range);
|
||||
|
||||
while (cs_disasm_iter (capstone, &code, &size, &address, insn))
|
||||
{
|
||||
@@ -404,6 +409,9 @@ frida_find_vtable (void)
|
||||
cs_free (insn, 1);
|
||||
cs_close (&capstone);
|
||||
|
||||
beach:
|
||||
g_clear_object (&libunwind);
|
||||
|
||||
return GSIZE_TO_POINTER (result);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Frida {
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
|
||||
dyld_find_unwind_sections = (DyldFindUnwindSectionsFunc)
|
||||
Gum.Module.find_export_by_name (LIBDYLD, "_dyld_find_unwind_sections");
|
||||
Gum.Process.find_module_by_name (LIBDYLD).find_export_by_name ("_dyld_find_unwind_sections");
|
||||
|
||||
interceptor.replace ((void *) dyld_find_unwind_sections, (void *) replacement_dyld_find_unwind_sections, this);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "pipe-glue.h"
|
||||
|
||||
#include "frida-tvos.h"
|
||||
|
||||
#include "pipe-glue.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
+105
-14
@@ -92,6 +92,12 @@ else
|
||||
host_arch = host_machine.cpu_family()
|
||||
host_abi = host_arch
|
||||
endif
|
||||
if cc.sizeof('void *') == 8
|
||||
host_cpu_mode = '64'
|
||||
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',
|
||||
@@ -156,12 +162,6 @@ if host_os_family == 'darwin'
|
||||
endif
|
||||
endif
|
||||
|
||||
if cc.sizeof('void *') == 8
|
||||
host_cpu_mode = '64'
|
||||
else
|
||||
host_cpu_mode = '32'
|
||||
endif
|
||||
|
||||
if host_os_family == 'darwin' and host_arch == 'arm64'
|
||||
host_is_modern = host_abi == 'arm64e'
|
||||
else
|
||||
@@ -347,15 +347,35 @@ else
|
||||
gio_unix_dep = dependency('gio-unix-2.0')
|
||||
endif
|
||||
|
||||
if host_os == 'android'
|
||||
minizip_dep = dependency('minizip', required: false, default_options: [
|
||||
'zlib=enabled',
|
||||
'lzma=disabled',
|
||||
])
|
||||
endif
|
||||
|
||||
have_local_backend = get_option('local_backend').allowed()
|
||||
have_fruity_backend = get_option('fruity_backend').allowed()
|
||||
have_droidy_backend = get_option('droidy_backend').allowed()
|
||||
have_fruity_backend = get_option('fruity_backend') \
|
||||
.disable_auto_if(not host_docks_mobile_devices) \
|
||||
.allowed()
|
||||
have_droidy_backend = get_option('droidy_backend') \
|
||||
.disable_auto_if(not host_docks_mobile_devices) \
|
||||
.allowed()
|
||||
have_socket_backend = get_option('socket_backend').allowed()
|
||||
have_barebone_backend = quickjs_dep.found()
|
||||
have_barebone_backend = get_option('barebone_backend') \
|
||||
.disable_if(not quickjs_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()
|
||||
endif
|
||||
|
||||
build_gadget = get_option('gadget') \
|
||||
.disable_auto_if(meson.is_subproject()) \
|
||||
@@ -413,7 +433,7 @@ else
|
||||
endif
|
||||
uninstalled_girdir = meson.current_build_dir() / 'src'
|
||||
|
||||
vapidir = get_option('datadir') / 'vala' / 'vapi'
|
||||
vapidir = get_option('prefix') / get_option('datadir') / 'vala' / 'vapi'
|
||||
|
||||
gum_vala_args = [
|
||||
'--pkg=frida-gum-1.0',
|
||||
@@ -437,6 +457,43 @@ backend_deps_private = []
|
||||
backend_reqs_private = []
|
||||
backend_libs_private = []
|
||||
|
||||
openssl_options = [
|
||||
'cli=disabled',
|
||||
]
|
||||
if host_os_family == 'windows' and cc.get_argument_syntax() != 'msvc'
|
||||
openssl_options += 'asm=disabled'
|
||||
endif
|
||||
openssl_dep = dependency('openssl',
|
||||
required: have_fruity_backend or get_option('connectivity').enabled(),
|
||||
modules: cc.get_argument_syntax() == 'msvc' ? ['OpenSSL::SSL'] : [],
|
||||
default_options: openssl_options,
|
||||
)
|
||||
|
||||
if have_fruity_backend
|
||||
foreach dep : ['libnghttp2', 'libngtcp2', 'libngtcp2_crypto_quictls', 'libusb-1.0']
|
||||
backend_deps_private += dependency(dep)
|
||||
backend_reqs_private += dep
|
||||
endforeach
|
||||
|
||||
lwip_options = [
|
||||
'ipv4=disabled',
|
||||
'ipv6=enabled',
|
||||
'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'
|
||||
else
|
||||
lwip_options += 'lwip_debug=enabled'
|
||||
endif
|
||||
backend_deps_private += dependency('lwip', default_options: lwip_options)
|
||||
backend_reqs_private += 'lwip'
|
||||
endif
|
||||
|
||||
if quickjs_dep.found()
|
||||
backend_deps_private += quickjs_dep
|
||||
backend_reqs_private += 'quickjs'
|
||||
@@ -456,8 +513,19 @@ else
|
||||
termux_elf_cleaner = []
|
||||
endif
|
||||
|
||||
tls_provider_dep = dependency('gioopenssl', required: get_option('connectivity'))
|
||||
tls_provider_dep = dependency('gioopenssl', required: get_option('connectivity'), default_options: [
|
||||
'gnutls=disabled',
|
||||
'openssl=enabled',
|
||||
'libproxy=disabled',
|
||||
'gnome_proxy=disabled',
|
||||
'tests=false',
|
||||
])
|
||||
tls_provider_vala_args = []
|
||||
if tls_provider_dep.found()
|
||||
tls_provider_vala_args += [
|
||||
'--pkg=gioopenssl',
|
||||
'--vapidir=' + tls_provider_dep.get_variable('vapidir'),
|
||||
]
|
||||
cdata.set('HAVE_GIOOPENSSL', 1)
|
||||
vala_flags += ['--define=HAVE_GIOOPENSSL']
|
||||
backend_deps_private += tls_provider_dep
|
||||
@@ -473,7 +541,6 @@ nice_dep = dependency('nice', required: get_option('connectivity'), default_opti
|
||||
'introspection=disabled',
|
||||
])
|
||||
if nice_dep.found()
|
||||
openssl_dep = dependency('openssl')
|
||||
usrsctp_dep = dependency('usrsctp', default_options: [
|
||||
'sctp_inet=false',
|
||||
'sctp_inet6=false',
|
||||
@@ -489,7 +556,6 @@ if nice_dep.found()
|
||||
cdata.set('HAVE_SCONN_LEN', 1)
|
||||
endif
|
||||
else
|
||||
openssl_dep = []
|
||||
usrsctp_dep = []
|
||||
endif
|
||||
|
||||
@@ -627,8 +693,9 @@ if cc.get_id() == 'gcc' and cc.compiles('#if __GNUC__ >= 5\n#error NOTNEEDED\n#e
|
||||
add_project_arguments('-w', language: 'c')
|
||||
endif
|
||||
|
||||
vapidir = meson.current_source_dir() / 'vapi'
|
||||
vala_flags += [
|
||||
'--vapidir=' + meson.current_source_dir() / 'vapi',
|
||||
'--vapidir=' + vapidir,
|
||||
'--pkg', 'config',
|
||||
]
|
||||
|
||||
@@ -641,11 +708,13 @@ if host_abi != host_arch
|
||||
vala_flags += ['--define=' + host_abi.to_upper()]
|
||||
endif
|
||||
add_project_arguments(vala_flags, language: 'vala')
|
||||
add_project_arguments('-I' + vapidir, language: 'c')
|
||||
|
||||
native_vala_flags = [
|
||||
'--vapidir=' + meson.current_source_dir() / 'vapi',
|
||||
]
|
||||
add_project_arguments(native_vala_flags, language: 'vala', native: true)
|
||||
add_project_arguments('-I' + vapidir, language: 'c', native: true)
|
||||
|
||||
subdir('compat')
|
||||
subdir('tools')
|
||||
@@ -664,9 +733,31 @@ if build_tests
|
||||
subdir('tests')
|
||||
endif
|
||||
|
||||
summary(
|
||||
{
|
||||
'local': have_local_backend,
|
||||
'fruity': have_fruity_backend,
|
||||
'droidy': have_droidy_backend,
|
||||
'socket': have_socket_backend,
|
||||
'barebone': have_barebone_backend,
|
||||
'compiler': have_compiler_backend,
|
||||
},
|
||||
section: 'Backends',
|
||||
bool_yn: true,
|
||||
)
|
||||
summary(
|
||||
{
|
||||
'compat': compat_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,
|
||||
'inject': build_inject,
|
||||
'tests': build_tests,
|
||||
},
|
||||
section: 'Features',
|
||||
bool_yn: true,
|
||||
)
|
||||
+15
-13
@@ -68,13 +68,25 @@ option('compat',
|
||||
option('helper_modern',
|
||||
type: 'string',
|
||||
value: 'auto',
|
||||
description: 'Prebuilt frida-helper to embed for use on modern systems'
|
||||
description: 'Prebuilt frida-helper to embed for supporting modern targets'
|
||||
)
|
||||
|
||||
option('helper_legacy',
|
||||
type: 'string',
|
||||
value: 'auto',
|
||||
description: 'Prebuilt frida-helper to embed for use on legacy systems'
|
||||
description: 'Prebuilt frida-helper to embed for supporting legacy targets'
|
||||
)
|
||||
|
||||
option('helper_emulated_modern',
|
||||
type: 'string',
|
||||
value: 'auto',
|
||||
description: 'Prebuilt frida-helper to embed for supporting modern emulated targets'
|
||||
)
|
||||
|
||||
option('helper_emulated_legacy',
|
||||
type: 'string',
|
||||
value: 'auto',
|
||||
description: 'Prebuilt frida-helper to embed for supporting legacy emulated targets'
|
||||
)
|
||||
|
||||
option('agent_modern',
|
||||
@@ -101,16 +113,6 @@ option('agent_emulated_legacy',
|
||||
description: 'Prebuilt agent to embed for use on legacy emulated targets'
|
||||
)
|
||||
|
||||
option('agent_dbghelp_prefix',
|
||||
type: 'string',
|
||||
description: 'Location of DbgHelp binaries to embed alongside agent (Windows only)'
|
||||
)
|
||||
|
||||
option('agent_symsrv_prefix',
|
||||
type: 'string',
|
||||
description: 'Location of SymSrv binaries to embed alongside agent (Windows only)'
|
||||
)
|
||||
|
||||
option('gadget',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
@@ -137,7 +139,7 @@ option('inject',
|
||||
|
||||
option('connectivity',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
value: 'enabled',
|
||||
description: 'Internet connectivity features (TLS and ICE support)'
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ if tls_provider_dep.found()
|
||||
endif
|
||||
|
||||
raw_portal = executable('frida-portal-raw', portal_sources,
|
||||
vala_args: [core_vala_args, '--pkg=posix'],
|
||||
vala_args: [core_vala_args, tls_provider_vala_args, '--pkg=posix'],
|
||||
link_args: extra_link_args,
|
||||
link_depends: extra_link_depends,
|
||||
dependencies: [core_dep] + extra_deps,
|
||||
|
||||
+1
-1
Submodule releng updated: 56b262207c...2eecc41cbe.
+51
-8
@@ -3,6 +3,7 @@ namespace Frida.Server {
|
||||
|
||||
private const string DEFAULT_DIRECTORY = "re.frida.server";
|
||||
private static bool output_version = false;
|
||||
private static string? device_id = null;
|
||||
private static string? listen_address = null;
|
||||
private static string? certpath = null;
|
||||
private static string? origin = null;
|
||||
@@ -30,6 +31,7 @@ namespace Frida.Server {
|
||||
|
||||
const OptionEntry[] option_entries = {
|
||||
{ "version", 0, 0, OptionArg.NONE, ref output_version, "Output version information and exit", null },
|
||||
{ "device", 0, 0, OptionArg.STRING, ref device_id, "Serve device with the given ID", "ID" },
|
||||
{ "listen", 'l', 0, OptionArg.STRING, ref listen_address, "Listen on ADDRESS", "ADDRESS" },
|
||||
{ "certificate", 0, 0, OptionArg.FILENAME, ref certpath, "Enable TLS using CERTIFICATE", "CERTIFICATE" },
|
||||
{ "origin", 0, 0, OptionArg.STRING, ref origin, "Only accept requests with “Origin” header matching ORIGIN " +
|
||||
@@ -178,7 +180,7 @@ namespace Frida.Server {
|
||||
|
||||
#if DARWIN
|
||||
var worker = new Thread<int> ("frida-server-main-loop", () => {
|
||||
var exit_code = run_application (endpoint_params, options, on_ready);
|
||||
var exit_code = run_application (device_id, endpoint_params, options, on_ready);
|
||||
|
||||
_stop_run_loop ();
|
||||
|
||||
@@ -190,15 +192,16 @@ namespace Frida.Server {
|
||||
|
||||
return exit_code;
|
||||
#else
|
||||
return run_application (endpoint_params, options, on_ready);
|
||||
return run_application (device_id, endpoint_params, options, on_ready);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static int run_application (EndpointParameters endpoint_params, ControlServiceOptions options, ReadyHandler on_ready) {
|
||||
private static int run_application (string? device_id, EndpointParameters endpoint_params, ControlServiceOptions options,
|
||||
ReadyHandler on_ready) {
|
||||
TemporaryDirectory.always_use ((directory != null) ? directory : DEFAULT_DIRECTORY);
|
||||
TemporaryDirectory.use_sysroot (options.sysroot);
|
||||
|
||||
application = new Application (new ControlService (endpoint_params, options));
|
||||
application = new Application (device_id, endpoint_params, options);
|
||||
|
||||
Posix.signal (Posix.Signal.INT, (sig) => {
|
||||
application.stop ();
|
||||
@@ -235,19 +238,36 @@ namespace Frida.Server {
|
||||
private class Application : Object {
|
||||
public signal void ready (bool success);
|
||||
|
||||
public ControlService service {
|
||||
public string? device_id {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public EndpointParameters endpoint_params {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public ControlServiceOptions options {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private DeviceManager? manager;
|
||||
private ControlService? service;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
private MainLoop loop = new MainLoop ();
|
||||
private int exit_code;
|
||||
private bool stopping;
|
||||
|
||||
public Application (ControlService service) {
|
||||
Object (service: service);
|
||||
public Application (string? device_id, EndpointParameters endpoint_params, ControlServiceOptions options) {
|
||||
Object (
|
||||
device_id: device_id,
|
||||
endpoint_params: endpoint_params,
|
||||
options: options
|
||||
);
|
||||
}
|
||||
|
||||
public int run () {
|
||||
@@ -265,6 +285,17 @@ namespace Frida.Server {
|
||||
|
||||
private async void start () {
|
||||
try {
|
||||
if (device_id != null && device_id != "local") {
|
||||
manager = new DeviceManager.with_nonlocal_backends_only ();
|
||||
|
||||
var device = yield manager.get_device_by_id (device_id, 0, io_cancellable);
|
||||
device.lost.connect (on_device_lost);
|
||||
|
||||
service = yield new ControlService.with_device (device, endpoint_params, options);
|
||||
} else {
|
||||
service = new ControlService (endpoint_params, options);
|
||||
}
|
||||
|
||||
yield service.start (io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
@@ -297,7 +328,15 @@ namespace Frida.Server {
|
||||
io_cancellable.cancel ();
|
||||
|
||||
try {
|
||||
yield service.stop ();
|
||||
if (service != null) {
|
||||
yield service.stop ();
|
||||
service = null;
|
||||
}
|
||||
|
||||
if (manager != null) {
|
||||
yield manager.close ();
|
||||
manager = null;
|
||||
}
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
|
||||
@@ -306,6 +345,10 @@ namespace Frida.Server {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void on_device_lost () {
|
||||
stop ();
|
||||
}
|
||||
}
|
||||
|
||||
private TlsCertificate? parse_certificate (string? path) throws GLib.Error {
|
||||
|
||||
+120
-32
@@ -1,36 +1,49 @@
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
from typing import List, Set
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
CORE_NAMESPACE = "http://www.gtk.org/introspection/core/1.0"
|
||||
C_NAMESPACE = "http://www.gtk.org/introspection/c/1.0"
|
||||
GLIB_NAMESPACE = "http://www.gtk.org/introspection/glib/1.0"
|
||||
GIR_NAMESPACES = {
|
||||
"": CORE_NAMESPACE,
|
||||
"c": C_NAMESPACE,
|
||||
"glib": GLIB_NAMESPACE,
|
||||
}
|
||||
|
||||
CORE_TAG_FIELD = f"{{{CORE_NAMESPACE}}}field"
|
||||
CORE_TAG_CONSTRUCTOR = f"{{{CORE_NAMESPACE}}}constructor"
|
||||
CORE_TAG_METHOD = f"{{{CORE_NAMESPACE}}}method"
|
||||
|
||||
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('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'))
|
||||
parser.add_argument('core_vapi', metavar='/path/to/frida-core.vapi', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('core_header', metavar='/path/to/frida-core.h', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('base_vapi', metavar='/path/to/frida-base.vapi', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('base_header', metavar='/path/to/frida-base.h', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('base_gir', metavar='/path/to/FridaBase-x.y.gir', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('base_vapi', metavar='/path/to/frida-base.vapi', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('output_dir', metavar='/output/dir')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
output_type = args.output_type
|
||||
api_version = args.api_version
|
||||
core_header = args.core_header.read()
|
||||
core_gir = args.core_gir.read()
|
||||
core_vapi = args.core_vapi.read()
|
||||
core_header = args.core_header.read()
|
||||
base_vapi = args.base_vapi.read()
|
||||
base_header = args.base_header.read()
|
||||
base_gir = args.base_gir.read()
|
||||
base_vapi = args.base_vapi.read()
|
||||
output_dir = Path(args.output_dir)
|
||||
|
||||
if output_type == 'gir':
|
||||
(output_dir / f"Frida-{api_version}.gir").write_text(core_gir, encoding='utf-8')
|
||||
return
|
||||
|
||||
if output_type == 'vapi-stamp':
|
||||
(output_dir / f"frida-core-{api_version}.vapi.stamp").write_bytes(b"")
|
||||
return
|
||||
@@ -45,30 +58,36 @@ def main():
|
||||
toplevel_sources = []
|
||||
src_dir = Path(__file__).parent.parent.resolve()
|
||||
for name in toplevel_names:
|
||||
with open(src_dir / name, 'r', encoding='utf-8') as f:
|
||||
toplevel_sources.append(f.read())
|
||||
toplevel_sources.append((src_dir / name).read_text(encoding='utf-8'))
|
||||
toplevel_code = "\n".join(toplevel_sources)
|
||||
|
||||
enable_header = False
|
||||
enable_gir = False
|
||||
enable_vapi = False
|
||||
if output_type == 'bundle':
|
||||
enable_header = True
|
||||
enable_gir = True
|
||||
enable_vapi = True
|
||||
elif output_type == 'header':
|
||||
enable_header = True
|
||||
elif output_type == 'gir':
|
||||
enable_gir = True
|
||||
elif output_type == 'vapi':
|
||||
enable_vapi = True
|
||||
|
||||
api = parse_api(api_version, toplevel_code, core_vapi, core_header, base_vapi, base_header)
|
||||
api = parse_api(api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi)
|
||||
|
||||
if enable_header:
|
||||
emit_header(api, output_dir)
|
||||
|
||||
if enable_gir:
|
||||
emit_gir(api, core_gir, base_gir, output_dir)
|
||||
|
||||
if enable_vapi:
|
||||
emit_vapi(api, output_dir)
|
||||
|
||||
def emit_header(api, output_dir):
|
||||
with open(output_dir / 'frida-core.h', 'w', encoding='utf-8') as output_header_file:
|
||||
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")
|
||||
@@ -79,7 +98,6 @@ def emit_header(api, output_dir):
|
||||
output_header_file.write("\ntypedef struct _%s %s;" % (object_type.c_name, object_type.c_name))
|
||||
if object_type.c_iface_definition is not None:
|
||||
output_header_file.write("\ntypedef struct _%sIface %sIface;" % (object_type.c_name, object_type.c_name))
|
||||
output_header_file.write("\ntypedef struct _FridaHostSession FridaHostSession;")
|
||||
|
||||
for enum in api.enum_types:
|
||||
output_header_file.write("\n\n" + enum.c_definition)
|
||||
@@ -150,8 +168,61 @@ def emit_header(api, output_dir):
|
||||
|
||||
output_header_file.write("\n\n#endif\n")
|
||||
|
||||
def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> str:
|
||||
ET.register_namespace("", CORE_NAMESPACE)
|
||||
ET.register_namespace("c", C_NAMESPACE)
|
||||
ET.register_namespace("glib", GLIB_NAMESPACE)
|
||||
|
||||
core_tree = ET.ElementTree(ET.fromstring(core_gir))
|
||||
base_tree = ET.ElementTree(ET.fromstring(base_gir))
|
||||
|
||||
core_root = core_tree.getroot()
|
||||
base_root = base_tree.getroot()
|
||||
|
||||
merged_root = ET.Element(core_root.tag, core_root.attrib)
|
||||
|
||||
for elem in core_root.findall("include", GIR_NAMESPACES):
|
||||
name = elem.get("name")
|
||||
if name in {"GLib", "GObject", "Gio"}:
|
||||
merged_root.append(elem)
|
||||
|
||||
for tag in ["package", "c:include"]:
|
||||
for elem in core_root.findall(tag, GIR_NAMESPACES):
|
||||
merged_root.append(elem)
|
||||
|
||||
core_namespace = core_root.find("namespace", GIR_NAMESPACES)
|
||||
merged_namespace = ET.SubElement(merged_root, core_namespace.tag, core_namespace.attrib)
|
||||
|
||||
object_type_names = {obj.name for obj in api.object_types}
|
||||
enum_type_names = {enum.name for enum in api.enum_types}
|
||||
error_type_names = {error.name for error in api.error_types}
|
||||
|
||||
def merge_and_transform_elements(tag_name: str, spec_set: Set[str]):
|
||||
core_elements = filter_elements(core_root.findall(f".//{tag_name}", GIR_NAMESPACES), spec_set)
|
||||
base_elements = filter_elements(base_root.findall(f".//{tag_name}", GIR_NAMESPACES), spec_set)
|
||||
for elem in core_elements + base_elements:
|
||||
if tag_name == "class":
|
||||
for child in list(elem):
|
||||
if child.tag == CORE_TAG_FIELD or child.get("name").startswith("_"):
|
||||
elem.remove(child)
|
||||
merged_namespace.append(elem)
|
||||
|
||||
merge_and_transform_elements("class", object_type_names)
|
||||
merge_and_transform_elements("interface", object_type_names)
|
||||
merge_and_transform_elements("enumeration", enum_type_names | error_type_names)
|
||||
|
||||
ET.indent(merged_root, space=" ")
|
||||
result = ET.tostring(merged_root,
|
||||
encoding="unicode",
|
||||
xml_declaration=True)
|
||||
with OutputFile(output_dir / f"Frida-{api.version}.gir") as output_gir:
|
||||
output_gir.write(result)
|
||||
|
||||
def filter_elements(elements: List[ET.Element], spec_set: Set[str]):
|
||||
return [elem for elem in elements if elem.get("name") in spec_set]
|
||||
|
||||
def emit_vapi(api, output_dir):
|
||||
with open(output_dir / "frida-core-{0}.vapi".format(api.version), "w", encoding='utf-8') as output_vapi_file:
|
||||
with OutputFile(output_dir / f"frida-core-{api.version}.vapi") as output_vapi_file:
|
||||
output_vapi_file.write("[CCode (cheader_filename = \"frida-core.h\", cprefix = \"Frida\", lower_case_cprefix = \"frida_\")]")
|
||||
output_vapi_file.write("\nnamespace Frida {")
|
||||
output_vapi_file.write("\n\tpublic static void init ();")
|
||||
@@ -189,12 +260,12 @@ def emit_vapi(api, output_dir):
|
||||
|
||||
output_vapi_file.write("\n}\n")
|
||||
|
||||
with open(output_dir / "frida-core-{0}.deps".format(api.version), "w", encoding='utf-8') as output_deps_file:
|
||||
with OutputFile(output_dir / f"frida-core-{api.version}.deps") as output_deps_file:
|
||||
output_deps_file.write("glib-2.0\n")
|
||||
output_deps_file.write("gobject-2.0\n")
|
||||
output_deps_file.write("gio-2.0\n")
|
||||
|
||||
def parse_api(api_version, toplevel_code, core_vapi, core_header, base_vapi, base_header):
|
||||
def parse_api(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)]
|
||||
@@ -287,8 +358,7 @@ def parse_api(api_version, toplevel_code, core_vapi, core_header, base_vapi, bas
|
||||
method_cname_lc = object_type.c_name_lc + '_' + method_name
|
||||
if method_cname_lc not in seen_cfunctions:
|
||||
seen_cfunctions.add(method_cname_lc)
|
||||
if method_name not in ('construct', 'construct_with_host_session', 'get_main_context', 'get_provider', 'get_session') \
|
||||
and not (object_type.name in ("Session", "Script") and method_name == 'get_id'):
|
||||
if method_name != 'construct':
|
||||
if (object_type.c_name + '*') in m.group(0):
|
||||
if method_name == 'new' or method_name.startswith('new_'):
|
||||
object_type.c_constructors.append(method_cprototype)
|
||||
@@ -327,8 +397,10 @@ def parse_api(api_version, toplevel_code, core_vapi, core_header, base_vapi, bas
|
||||
if stripped_line == "}":
|
||||
ignoring = False
|
||||
else:
|
||||
if stripped_line.startswith("public abstract") or stripped_line.startswith("public class Promise") \
|
||||
or stripped_line.startswith("public interface Future"):
|
||||
if stripped_line.startswith("public abstract") \
|
||||
or stripped_line.startswith("public class Promise") \
|
||||
or stripped_line.startswith("public interface Future") \
|
||||
or stripped_line.startswith("public class CF"):
|
||||
ignoring = True
|
||||
elif stripped_line.startswith("public enum") or stripped_line.startswith("public errordomain"):
|
||||
name = re.match(r"^public (?:enum|errordomain) (\w+) ", stripped_line).group(1)
|
||||
@@ -360,8 +432,7 @@ def parse_api(api_version, toplevel_code, core_vapi, core_header, base_vapi, bas
|
||||
current_object_type.vapi_signals.append(stripped_line)
|
||||
elif "{ get" in stripped_line:
|
||||
name = re.match(r".+?(\w+)\s+{", stripped_line).group(1)
|
||||
if name not in ('main_context', 'provider', 'session'):
|
||||
current_object_type.vapi_properties.append(stripped_line)
|
||||
current_object_type.vapi_properties.append(stripped_line)
|
||||
else:
|
||||
m = re.match(r".+?(\w+)\s+\(", stripped_line)
|
||||
if m is not None:
|
||||
@@ -398,19 +469,19 @@ def function_is_public(name):
|
||||
"negotiate_connection"
|
||||
]
|
||||
|
||||
def parse_vala_object_types(source):
|
||||
def parse_vala_object_types(source) -> List[ApiObjectType]:
|
||||
return [ApiObjectType(m.group(2), m.group(1)) for m in re.finditer(r"^\t+public\s+(class|interface)\s+(\w+)\s+", source, re.MULTILINE)]
|
||||
|
||||
def parse_vapi_functions(vapi):
|
||||
def parse_vapi_functions(vapi) -> List[ApiFunction]:
|
||||
return [ApiFunction(m.group(1), m.group(0)) for m in re.finditer(r"^\tpublic static .+ (\w+) \(.+;", vapi, re.MULTILINE)]
|
||||
|
||||
@dataclass
|
||||
class ApiSpec:
|
||||
def __init__(self, version, object_types, functions, enum_types, error_types):
|
||||
self.version = version
|
||||
self.object_types = object_types
|
||||
self.functions = functions
|
||||
self.enum_types = enum_types
|
||||
self.error_types = error_types
|
||||
version: str
|
||||
object_types: List[ApiObjectType]
|
||||
functions: List[ApiFunction]
|
||||
enum_types: List[ApiEnum]
|
||||
error_types: List[ApiEnum]
|
||||
|
||||
class ApiEnum:
|
||||
def __init__(self, name):
|
||||
@@ -512,6 +583,23 @@ def fuzzysort(items, keys):
|
||||
result.extend(remaining)
|
||||
return result
|
||||
|
||||
class OutputFile:
|
||||
def __init__(self, output_path):
|
||||
self._output_path = output_path
|
||||
self._io = StringIO()
|
||||
|
||||
def __enter__(self):
|
||||
return self._io
|
||||
|
||||
def __exit__(self, *exc):
|
||||
result = self._io.getvalue()
|
||||
if self._output_path.exists():
|
||||
existing_contents = self._output_path.read_text(encoding='utf-8')
|
||||
if existing_contents == result:
|
||||
return False
|
||||
self._output_path.write_text(result, encoding='utf-8')
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+28
-45
@@ -2,54 +2,37 @@ generate_api = [python, files('generate.py')]
|
||||
|
||||
generate_api_args = [
|
||||
api_version,
|
||||
core_build_dir / 'frida-core.h',
|
||||
core_build_dir / core_gir_name,
|
||||
core_build_dir / 'frida-core.vapi',
|
||||
core_build_dir / 'frida-core.h',
|
||||
base_build_dir / f'frida-base-@[email protected]',
|
||||
base_build_dir / 'frida-base.h',
|
||||
base_build_dir / base_gir_name,
|
||||
base_build_dir / f'frida-base-@[email protected]',
|
||||
meson.current_build_dir(),
|
||||
]
|
||||
|
||||
core_public_h = custom_target('frida-core-header',
|
||||
input: [core, base],
|
||||
output: ['frida-core.h'],
|
||||
command: [
|
||||
generate_api,
|
||||
'--output=header',
|
||||
generate_api_args,
|
||||
],
|
||||
install: true,
|
||||
install_dir: header_install_dir,
|
||||
)
|
||||
|
||||
core_public_gir = custom_target('frida-core-gir',
|
||||
input: [core, base],
|
||||
output: [core_gir_name],
|
||||
command: [
|
||||
generate_api,
|
||||
'--output=gir',
|
||||
generate_api_args,
|
||||
],
|
||||
install: true,
|
||||
install_dir: girdir,
|
||||
)
|
||||
|
||||
core_public_vapi = custom_target('frida-core-vapi',
|
||||
core_api = custom_target('frida-core-api',
|
||||
input: [core, base],
|
||||
output: [
|
||||
'frida-core.h',
|
||||
core_gir_name,
|
||||
f'frida-core-@[email protected]',
|
||||
f'frida-core-@[email protected]',
|
||||
],
|
||||
command: [
|
||||
generate_api,
|
||||
'--output=vapi',
|
||||
'--output=bundle',
|
||||
generate_api_args,
|
||||
],
|
||||
install: true,
|
||||
install_dir: vapidir,
|
||||
install_dir: [header_install_dir, girdir, vapidir, vapidir],
|
||||
)
|
||||
|
||||
core_public_vapi_stamp = custom_target('frida-core-vapi-stamp',
|
||||
core_public_h = core_api[0]
|
||||
core_public_gir = core_api[1]
|
||||
core_public_vapi = [core_api[2], core_api[3]]
|
||||
|
||||
core_public_vapi_stamp = custom_target('frida-core-vapi-stamp',
|
||||
input: core_public_vapi,
|
||||
output: f'frida-core-@[email protected]',
|
||||
command: [
|
||||
@@ -90,17 +73,25 @@ else
|
||||
endif
|
||||
uninstalled_vapidir = uninstalled_incdir
|
||||
|
||||
uninstalled_variables = {
|
||||
'frida_includedir': uninstalled_incdir,
|
||||
'frida_girdir': uninstalled_girdir,
|
||||
'frida_vapidir': uninstalled_vapidir,
|
||||
}
|
||||
|
||||
installed_variables = {
|
||||
'frida_includedir': '${includedir}',
|
||||
'frida_girdir': girdir,
|
||||
'frida_vapidir': vapidir,
|
||||
}
|
||||
|
||||
core_public_dep = declare_dependency(
|
||||
sources: [core_public_h, core_public_gir, core_public_vapi_stamp, core_public_extra_headers],
|
||||
include_directories: include_directories('.'),
|
||||
link_args: backend_libs_private,
|
||||
link_with: core_public_lib,
|
||||
dependencies: core_deps,
|
||||
variables: {
|
||||
'frida_includedir': uninstalled_incdir,
|
||||
'frida_girdir': uninstalled_girdir,
|
||||
'frida_vapidir': uninstalled_vapidir,
|
||||
},
|
||||
variables: uninstalled_variables,
|
||||
)
|
||||
|
||||
pkg = import('pkgconfig')
|
||||
@@ -115,16 +106,8 @@ pkg.generate(
|
||||
libraries: core_public_lib,
|
||||
libraries_private: backend_libs_private,
|
||||
extra_cflags: ['-I${frida_includedir}'],
|
||||
variables: {
|
||||
'frida_includedir': '${includedir}',
|
||||
'frida_girdir': girdir,
|
||||
'frida_vapidir': vapidir,
|
||||
},
|
||||
uninstalled_variables: {
|
||||
'frida_includedir': uninstalled_incdir,
|
||||
'frida_girdir': uninstalled_girdir,
|
||||
'frida_vapidir': uninstalled_vapidir,
|
||||
},
|
||||
variables: installed_variables,
|
||||
uninstalled_variables: uninstalled_variables,
|
||||
)
|
||||
|
||||
meson.override_dependency(f'frida-core-@api_version@', core_public_dep)
|
||||
@@ -420,7 +420,9 @@ namespace Frida {
|
||||
|
||||
public void post (string json, Bytes? data) {
|
||||
var json_val = ctx.make_string (json);
|
||||
invoke_void (dispatch_message_func, { json_val }, runtime_obj);
|
||||
var data_val = (data != null) ? ctx.make_array_buffer (data.get_data ()) : QuickJS.Null;
|
||||
invoke_void (dispatch_message_func, { json_val, data_val }, runtime_obj);
|
||||
ctx.free_value (data_val);
|
||||
ctx.free_value (json_val);
|
||||
|
||||
perform_pending_io ();
|
||||
|
||||
@@ -275,7 +275,7 @@ namespace Frida {
|
||||
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 ()),
|
||||
}, cancellable);
|
||||
}, null, cancellable);
|
||||
return bundle.get_string ();
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ namespace Frida {
|
||||
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 ()),
|
||||
}, cancellable);
|
||||
}, null, cancellable);
|
||||
}
|
||||
|
||||
private static string compute_project_root (string entrypoint, CompilerOptions options) {
|
||||
|
||||
@@ -3,13 +3,7 @@ compiler_sources = files(
|
||||
)
|
||||
|
||||
if have_compiler_backend
|
||||
if get_option('compiler_snapshot').auto()
|
||||
# Generating our snapshot with V8's CPU simulator takes a very long time.
|
||||
use_snapshot = meson.can_run_host_binaries()
|
||||
else
|
||||
use_snapshot = get_option('compiler_snapshot').enabled()
|
||||
endif
|
||||
compiler_mksnapshot = use_snapshot ? v8_mksnapshot : ''
|
||||
compiler_mksnapshot = generate_compiler_snapshot ? v8_mksnapshot : ''
|
||||
|
||||
compiler_agent = custom_target('frida-compiler-agent',
|
||||
input: [
|
||||
|
||||
Generated
+9
-8
@@ -17,7 +17,7 @@
|
||||
"@rollup/plugin-node-resolve": "^14.1.0",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.4",
|
||||
"@types/frida-gum": "18.7.0",
|
||||
"@types/frida-gum": "18.8.1",
|
||||
"@types/glob": "^8.0.0",
|
||||
"@types/node": "18.7.23",
|
||||
"glob": "^8.0.3",
|
||||
@@ -468,10 +468,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/frida-gum": {
|
||||
"version": "18.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-18.7.0.tgz",
|
||||
"integrity": "sha512-HhBomXE23fLDAWXEKi3BjJLrlH9wAv9IEQNfO/PaYHQNNbh0Bi06gx6JbXTspVpbqlbVqkWAuU7n6HaS9B6yXA==",
|
||||
"dev": true
|
||||
"version": "18.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-18.8.1.tgz",
|
||||
"integrity": "sha512-FhlHORjaw43QbNYIyj5Ji7iYSvk7t5nPiC+GuYDVndqc1bFheeAs88b1odp8HhmpQUrrYVU90wapapKVLlmJHg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/glob": {
|
||||
"version": "8.0.0",
|
||||
@@ -1465,9 +1466,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/frida-gum": {
|
||||
"version": "18.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-18.7.0.tgz",
|
||||
"integrity": "sha512-HhBomXE23fLDAWXEKi3BjJLrlH9wAv9IEQNfO/PaYHQNNbh0Bi06gx6JbXTspVpbqlbVqkWAuU7n6HaS9B6yXA==",
|
||||
"version": "18.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-18.8.1.tgz",
|
||||
"integrity": "sha512-FhlHORjaw43QbNYIyj5Ji7iYSvk7t5nPiC+GuYDVndqc1bFheeAs88b1odp8HhmpQUrrYVU90wapapKVLlmJHg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/glob": {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"@rollup/plugin-node-resolve": "^14.1.0",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.4",
|
||||
"@types/frida-gum": "18.7.0",
|
||||
"@types/frida-gum": "18.8.1",
|
||||
"@types/glob": "^8.0.0",
|
||||
"@types/node": "18.7.23",
|
||||
"glob": "^8.0.3",
|
||||
|
||||
+304
-167
@@ -1,11 +1,6 @@
|
||||
namespace Frida {
|
||||
#if HAVE_LOCAL_BACKEND
|
||||
public class ControlService : Object {
|
||||
public HostSession host_session {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public EndpointParameters endpoint_params {
|
||||
get;
|
||||
construct;
|
||||
@@ -16,24 +11,23 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
private HostSession host_session;
|
||||
|
||||
private State state = STOPPED;
|
||||
|
||||
private WebService service;
|
||||
private Gee.Map<DBusConnection, Peer> peers = new Gee.HashMap<DBusConnection, Peer> ();
|
||||
private ConnectionHandler main_handler;
|
||||
private Gee.Map<string, ConnectionHandler> dynamic_interface_handlers = new Gee.HashMap<string, ConnectionHandler> ();
|
||||
|
||||
private Gee.Set<ControlChannel> spawn_gaters = new Gee.HashSet<ControlChannel> ();
|
||||
private Gee.Map<uint, PendingSpawn> pending_spawn = new Gee.HashMap<uint, PendingSpawn> ();
|
||||
private Gee.Map<AgentSessionId?, AgentSessionEntry> sessions =
|
||||
new Gee.HashMap<AgentSessionId?, AgentSessionEntry> (AgentSessionId.hash, AgentSessionId.equal);
|
||||
|
||||
private SocketService broker_service = new SocketService ();
|
||||
#if !WINDOWS
|
||||
private uint16 broker_port = 0;
|
||||
#endif
|
||||
private Gee.Map<string, Transport> transports = new Gee.HashMap<string, Transport> ();
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
private MainContext? main_context;
|
||||
|
||||
private enum State {
|
||||
STOPPED,
|
||||
STARTING,
|
||||
@@ -44,43 +38,69 @@ namespace Frida {
|
||||
public ControlService (EndpointParameters endpoint_params, ControlServiceOptions? options = null) {
|
||||
ControlServiceOptions opts = (options != null) ? options : new ControlServiceOptions ();
|
||||
|
||||
HostSession host_session;
|
||||
HostSession session;
|
||||
#if WINDOWS
|
||||
var tempdir = new TemporaryDirectory ();
|
||||
host_session = new WindowsHostSession (new WindowsHelperProcess (tempdir), tempdir);
|
||||
session = new WindowsHostSession (new WindowsHelperProcess (tempdir), tempdir);
|
||||
#endif
|
||||
#if DARWIN
|
||||
host_session = new DarwinHostSession (new DarwinHelperBackend (), new TemporaryDirectory (),
|
||||
session = new DarwinHostSession (new DarwinHelperBackend (), new TemporaryDirectory (),
|
||||
opts.sysroot, opts.report_crashes);
|
||||
#endif
|
||||
#if LINUX
|
||||
var tempdir = new TemporaryDirectory ();
|
||||
host_session = new LinuxHostSession (new LinuxHelperProcess (tempdir), tempdir, opts.report_crashes);
|
||||
session = new LinuxHostSession (new LinuxHelperProcess (tempdir), tempdir, opts.report_crashes);
|
||||
#endif
|
||||
#if FREEBSD
|
||||
host_session = new FreebsdHostSession ();
|
||||
session = new FreebsdHostSession ();
|
||||
#endif
|
||||
#if QNX
|
||||
host_session = new QnxHostSession ();
|
||||
session = new QnxHostSession ();
|
||||
#endif
|
||||
|
||||
Object (
|
||||
host_session: host_session,
|
||||
endpoint_params: endpoint_params,
|
||||
options: opts
|
||||
);
|
||||
|
||||
assign_session (session);
|
||||
}
|
||||
|
||||
public ControlService.with_host_session (HostSession host_session, EndpointParameters endpoint_params,
|
||||
public async ControlService.with_device (Device device, EndpointParameters endpoint_params,
|
||||
ControlServiceOptions? options = null, Cancellable? cancellable = null) throws Error, IOError {
|
||||
ControlServiceOptions opts = (options != null) ? options : new ControlServiceOptions ();
|
||||
|
||||
var session = yield device.get_host_session (cancellable);
|
||||
|
||||
Object (
|
||||
endpoint_params: endpoint_params,
|
||||
options: opts
|
||||
);
|
||||
|
||||
assign_session (session);
|
||||
}
|
||||
|
||||
internal ControlService.with_host_session (HostSession host_session, EndpointParameters endpoint_params,
|
||||
ControlServiceOptions? options = null) {
|
||||
Object (
|
||||
host_session: host_session,
|
||||
endpoint_params: endpoint_params,
|
||||
options: (options != null) ? options : new ControlServiceOptions ()
|
||||
);
|
||||
|
||||
assign_session (host_session);
|
||||
}
|
||||
|
||||
construct {
|
||||
var iface_observer = new TunnelInterfaceObserver ();
|
||||
iface_observer.interface_detached.connect (on_interface_detached);
|
||||
|
||||
service = new WebService (endpoint_params, WebServiceFlavor.CONTROL, PortConflictBehavior.FAIL, iface_observer);
|
||||
|
||||
main_handler = new ConnectionHandler (this, null);
|
||||
}
|
||||
|
||||
private void assign_session (HostSession session) {
|
||||
host_session = session;
|
||||
host_session.spawn_added.connect (notify_spawn_added);
|
||||
host_session.child_added.connect (notify_child_added);
|
||||
host_session.child_removed.connect (notify_child_removed);
|
||||
@@ -88,11 +108,6 @@ namespace Frida {
|
||||
host_session.output.connect (notify_output);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
host_session.uninjected.connect (notify_uninjected);
|
||||
|
||||
service = new WebService (endpoint_params, CONTROL);
|
||||
service.incoming.connect (on_server_connection);
|
||||
|
||||
broker_service.incoming.connect (on_broker_service_connection);
|
||||
}
|
||||
|
||||
public async void start (Cancellable? cancellable = null) throws Error, IOError {
|
||||
@@ -100,6 +115,10 @@ namespace Frida {
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
state = STARTING;
|
||||
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
|
||||
service.incoming.connect (on_server_connection);
|
||||
|
||||
try {
|
||||
yield service.start (cancellable);
|
||||
|
||||
@@ -131,21 +150,17 @@ namespace Frida {
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
state = STOPPING;
|
||||
|
||||
broker_service.stop ();
|
||||
service.stop ();
|
||||
service.incoming.disconnect (on_server_connection);
|
||||
|
||||
io_cancellable.cancel ();
|
||||
|
||||
transports.clear ();
|
||||
service.stop ();
|
||||
|
||||
foreach (var peer in peers.values.to_array ()) {
|
||||
try {
|
||||
yield peer.close ();
|
||||
} catch (IOError e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
peers.clear ();
|
||||
foreach (var handler in dynamic_interface_handlers.values.to_array ())
|
||||
yield handler.close (cancellable);
|
||||
dynamic_interface_handlers.clear ();
|
||||
|
||||
yield main_handler.close (cancellable);
|
||||
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
if (base_host_session != null)
|
||||
@@ -175,7 +190,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private void on_server_connection (IOStream connection, SocketAddress remote_address) {
|
||||
private void on_server_connection (IOStream connection, SocketAddress remote_address, DynamicInterface? dynamic_iface) {
|
||||
#if IOS || TVOS
|
||||
/*
|
||||
* We defer the launchd injection until the first connection is established in order
|
||||
@@ -186,50 +201,28 @@ namespace Frida {
|
||||
darwin_host_session.activate_crash_reporter_integration ();
|
||||
#endif
|
||||
|
||||
handle_server_connection.begin (connection);
|
||||
ConnectionHandler handler;
|
||||
unowned string iface_name = dynamic_iface?.name;
|
||||
if (iface_name != null) {
|
||||
handler = dynamic_interface_handlers[iface_name];
|
||||
if (handler == null) {
|
||||
handler = new ConnectionHandler (this, dynamic_iface);
|
||||
dynamic_interface_handlers[iface_name] = handler;
|
||||
}
|
||||
} else {
|
||||
handler = main_handler;
|
||||
}
|
||||
|
||||
handler.handle_server_connection.begin (connection);
|
||||
}
|
||||
|
||||
private async void handle_server_connection (IOStream raw_connection) throws GLib.Error {
|
||||
var connection = yield new DBusConnection (raw_connection, null, DELAY_MESSAGE_PROCESSING, null, io_cancellable);
|
||||
connection.on_closed.connect (on_connection_closed);
|
||||
|
||||
Peer peer;
|
||||
AuthenticationService? auth_service = endpoint_params.auth_service;
|
||||
if (auth_service != null)
|
||||
peer = new AuthenticationChannel (this, connection, auth_service);
|
||||
else
|
||||
peer = setup_control_channel (connection);
|
||||
peers[connection] = peer;
|
||||
|
||||
connection.start_message_processing ();
|
||||
}
|
||||
|
||||
private void on_connection_closed (DBusConnection connection, bool remote_peer_vanished, GLib.Error? error) {
|
||||
Peer peer;
|
||||
if (peers.unset (connection, out peer))
|
||||
peer.close.begin (io_cancellable);
|
||||
}
|
||||
|
||||
private async void promote_authentication_channel (AuthenticationChannel channel) throws GLib.Error {
|
||||
DBusConnection connection = channel.connection;
|
||||
|
||||
peers.unset (connection);
|
||||
yield channel.close (io_cancellable);
|
||||
|
||||
peers[connection] = setup_control_channel (connection);
|
||||
}
|
||||
|
||||
private void kick_authentication_channel (AuthenticationChannel channel) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
channel.connection.close.begin (io_cancellable);
|
||||
return false;
|
||||
private void on_interface_detached (DynamicInterface iface) {
|
||||
schedule_on_frida_thread (() => {
|
||||
ConnectionHandler handler;
|
||||
if (dynamic_interface_handlers.unset (iface.name, out handler))
|
||||
handler.close.begin (io_cancellable);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
|
||||
private ControlChannel setup_control_channel (DBusConnection connection) {
|
||||
return new ControlChannel (this, connection);
|
||||
}
|
||||
|
||||
private async void teardown_control_channel (ControlChannel channel) {
|
||||
@@ -258,8 +251,235 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private void schedule_on_frida_thread (owned SourceFunc function) {
|
||||
assert (main_context != null);
|
||||
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
private class ConnectionHandler : Object {
|
||||
public weak ControlService parent {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public DynamicInterface? dynamic_iface {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public HostSession host_session {
|
||||
get {
|
||||
return parent.host_session;
|
||||
}
|
||||
}
|
||||
|
||||
private Gee.Map<DBusConnection, Peer> peers = new Gee.HashMap<DBusConnection, Peer> ();
|
||||
|
||||
private SocketService broker_service = new SocketService ();
|
||||
#if !WINDOWS
|
||||
private uint16 broker_port = 0;
|
||||
#endif
|
||||
private Gee.Map<string, Transport> transports = new Gee.HashMap<string, Transport> ();
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public ConnectionHandler (ControlService parent, DynamicInterface? dynamic_iface) {
|
||||
Object (parent: parent, dynamic_iface: dynamic_iface);
|
||||
}
|
||||
|
||||
construct {
|
||||
broker_service.incoming.connect (on_broker_service_connection);
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
broker_service.incoming.disconnect (on_broker_service_connection);
|
||||
|
||||
io_cancellable.cancel ();
|
||||
|
||||
broker_service.stop ();
|
||||
|
||||
transports.clear ();
|
||||
|
||||
foreach (var peer in peers.values.to_array ())
|
||||
yield peer.close (cancellable);
|
||||
peers.clear ();
|
||||
}
|
||||
|
||||
public Gee.Iterator<ControlChannel> all_control_channels () {
|
||||
return (Gee.Iterator<ControlChannel>) peers.values.filter (peer => peer is ControlChannel);
|
||||
}
|
||||
|
||||
public async void handle_server_connection (IOStream raw_connection) throws GLib.Error {
|
||||
var connection = yield new DBusConnection (raw_connection, null, DELAY_MESSAGE_PROCESSING, null,
|
||||
io_cancellable);
|
||||
connection.on_closed.connect (on_connection_closed);
|
||||
|
||||
AuthenticationService? auth_service = parent.endpoint_params.auth_service;
|
||||
peers[connection] = (auth_service != null)
|
||||
? (Peer) new AuthenticationChannel (this, connection, auth_service)
|
||||
: (Peer) new ControlChannel (this, connection);
|
||||
|
||||
connection.start_message_processing ();
|
||||
}
|
||||
|
||||
private void on_connection_closed (DBusConnection connection, bool remote_peer_vanished, GLib.Error? error) {
|
||||
Peer peer;
|
||||
if (peers.unset (connection, out peer))
|
||||
peer.close.begin (io_cancellable);
|
||||
}
|
||||
|
||||
public async void promote_authentication_channel (AuthenticationChannel channel) throws GLib.Error {
|
||||
DBusConnection connection = channel.connection;
|
||||
|
||||
peers.unset (connection);
|
||||
yield channel.close (io_cancellable);
|
||||
|
||||
peers[connection] = new ControlChannel (this, connection);
|
||||
}
|
||||
|
||||
public void kick_authentication_channel (AuthenticationChannel channel) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
channel.connection.close.begin (io_cancellable);
|
||||
return false;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
|
||||
public async void teardown_control_channel (ControlChannel channel) {
|
||||
yield parent.teardown_control_channel (channel);
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
yield parent.enable_spawn_gating (requester);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
yield parent.disable_spawn_gating (requester);
|
||||
}
|
||||
|
||||
public HostSpawnInfo[] enumerate_pending_spawn () {
|
||||
return parent.enumerate_pending_spawn ();
|
||||
}
|
||||
|
||||
public async void resume (uint pid, ControlChannel requester) throws GLib.Error {
|
||||
yield parent.resume (pid, requester);
|
||||
}
|
||||
|
||||
public async AgentSessionId attach (uint pid, HashTable<string, Variant> options, ControlChannel requester,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
return yield parent.attach (pid, options, requester, cancellable);
|
||||
}
|
||||
|
||||
public async void reattach (AgentSessionId id, ControlChannel requester, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
yield parent.reattach (id, requester, cancellable);
|
||||
}
|
||||
|
||||
public void open_tcp_transport (AgentSessionId id, Cancellable? cancellable, out uint16 port, out string token)
|
||||
throws Error {
|
||||
#if WINDOWS
|
||||
throw new Error.NOT_SUPPORTED ("Not yet supported on Windows");
|
||||
#else
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
if (base_host_session == null)
|
||||
throw new Error.NOT_SUPPORTED ("Not supported for remote host sessions");
|
||||
if (!base_host_session.can_pass_file_descriptors_to_agent_session (id))
|
||||
throw new Error.INVALID_ARGUMENT ("Not supported by this particular agent session");
|
||||
|
||||
if (broker_port == 0) {
|
||||
try {
|
||||
if (dynamic_iface != null) {
|
||||
SocketAddress effective_address;
|
||||
broker_service.add_address (
|
||||
new InetSocketAddress (dynamic_iface.ip, 0),
|
||||
STREAM,
|
||||
TCP,
|
||||
null,
|
||||
out effective_address);
|
||||
broker_port = ((InetSocketAddress) effective_address).get_port ();
|
||||
} else {
|
||||
broker_port = broker_service.add_any_inet_port (null);
|
||||
}
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to listen: %s", e.message);
|
||||
}
|
||||
|
||||
broker_service.start ();
|
||||
}
|
||||
|
||||
string transport_id = Uuid.string_random ();
|
||||
|
||||
var expiry_source = new TimeoutSource.seconds (20);
|
||||
expiry_source.set_callback (() => {
|
||||
transports.unset (transport_id);
|
||||
return false;
|
||||
});
|
||||
expiry_source.attach (MainContext.get_thread_default ());
|
||||
|
||||
transports[transport_id] = new Transport (id, expiry_source);
|
||||
|
||||
port = broker_port;
|
||||
token = transport_id;
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool on_broker_service_connection (SocketConnection connection, Object? source_object) {
|
||||
handle_broker_connection.begin (connection);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void handle_broker_connection (SocketConnection connection) throws GLib.Error {
|
||||
var socket = connection.socket;
|
||||
if (socket.get_family () != UNIX)
|
||||
Tcp.enable_nodelay (socket);
|
||||
|
||||
const size_t uuid_length = 36;
|
||||
|
||||
var raw_token = new uint8[uuid_length + 1];
|
||||
size_t bytes_read;
|
||||
yield connection.input_stream.read_all_async (raw_token[0:uuid_length], Priority.DEFAULT, io_cancellable,
|
||||
out bytes_read);
|
||||
unowned string token = (string) raw_token;
|
||||
|
||||
Transport transport;
|
||||
if (!transports.unset (token, out transport))
|
||||
return;
|
||||
|
||||
transport.expiry_source.destroy ();
|
||||
|
||||
#if !WINDOWS
|
||||
AgentSessionId session_id = transport.session_id;
|
||||
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
if (base_host_session == null)
|
||||
throw new Error.NOT_SUPPORTED ("Not supported for remote host sessions");
|
||||
|
||||
AgentSessionProvider provider = base_host_session.obtain_session_provider (session_id);
|
||||
yield provider.migrate (session_id, socket, io_cancellable);
|
||||
#endif
|
||||
}
|
||||
|
||||
private class Transport {
|
||||
public AgentSessionId session_id;
|
||||
public Source expiry_source;
|
||||
|
||||
public Transport (AgentSessionId session_id, Source expiry_source) {
|
||||
this.session_id = session_id;
|
||||
this.expiry_source = expiry_source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Gee.Iterator<ControlChannel> all_control_channels () {
|
||||
return (Gee.Iterator<ControlChannel>) peers.values.filter (peer => peer is ControlChannel);
|
||||
var channels = new Gee.ArrayList<ControlChannel> ();
|
||||
channels.add_all_iterator (main_handler.all_control_channels ());
|
||||
foreach (var handler in dynamic_interface_handlers.values)
|
||||
channels.add_all_iterator (handler.all_control_channels ());
|
||||
return channels.iterator ();
|
||||
}
|
||||
|
||||
private async void enable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
@@ -446,85 +666,12 @@ namespace Frida {
|
||||
sessions.unset (entry.id);
|
||||
}
|
||||
|
||||
private void open_tcp_transport (AgentSessionId id, Cancellable? cancellable, out uint16 port,
|
||||
out string token) throws Error {
|
||||
#if WINDOWS
|
||||
throw new Error.NOT_SUPPORTED ("Not yet supported on Windows");
|
||||
#else
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
if (base_host_session == null)
|
||||
throw new Error.NOT_SUPPORTED ("Not supported for remote host sessions");
|
||||
if (!base_host_session.can_pass_file_descriptors_to_agent_session (id))
|
||||
throw new Error.INVALID_ARGUMENT ("Not supported by this particular agent session");
|
||||
|
||||
if (broker_port == 0) {
|
||||
try {
|
||||
broker_port = broker_service.add_any_inet_port (null);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to listen: %s", e.message);
|
||||
}
|
||||
|
||||
broker_service.start ();
|
||||
}
|
||||
|
||||
string transport_id = Uuid.string_random ();
|
||||
|
||||
var expiry_source = new TimeoutSource.seconds (20);
|
||||
expiry_source.set_callback (() => {
|
||||
transports.unset (transport_id);
|
||||
return false;
|
||||
});
|
||||
expiry_source.attach (MainContext.get_thread_default ());
|
||||
|
||||
transports[transport_id] = new Transport (id, expiry_source);
|
||||
|
||||
port = broker_port;
|
||||
token = transport_id;
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool on_broker_service_connection (SocketConnection connection, Object? source_object) {
|
||||
handle_broker_connection.begin (connection);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void handle_broker_connection (SocketConnection connection) throws GLib.Error {
|
||||
var socket = connection.socket;
|
||||
if (socket.get_family () != UNIX)
|
||||
Tcp.enable_nodelay (socket);
|
||||
|
||||
const size_t uuid_length = 36;
|
||||
|
||||
var raw_token = new uint8[uuid_length + 1];
|
||||
size_t bytes_read;
|
||||
yield connection.input_stream.read_all_async (raw_token[0:uuid_length], Priority.DEFAULT, io_cancellable,
|
||||
out bytes_read);
|
||||
unowned string token = (string) raw_token;
|
||||
|
||||
Transport transport;
|
||||
if (!transports.unset (token, out transport))
|
||||
return;
|
||||
|
||||
transport.expiry_source.destroy ();
|
||||
|
||||
#if !WINDOWS
|
||||
AgentSessionId session_id = transport.session_id;
|
||||
|
||||
var base_host_session = host_session as BaseDBusHostSession;
|
||||
if (base_host_session == null)
|
||||
throw new Error.NOT_SUPPORTED ("Not supported for remote host sessions");
|
||||
|
||||
AgentSessionProvider provider = base_host_session.obtain_session_provider (session_id);
|
||||
yield provider.migrate (session_id, socket, io_cancellable);
|
||||
#endif
|
||||
}
|
||||
|
||||
private interface Peer : Object {
|
||||
public abstract async void close (Cancellable? cancellable = null) throws IOError;
|
||||
}
|
||||
|
||||
private class AuthenticationChannel : Object, Peer, AuthenticationService {
|
||||
public weak ControlService parent {
|
||||
public weak ConnectionHandler parent {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
@@ -541,7 +688,7 @@ namespace Frida {
|
||||
|
||||
private Gee.Collection<uint> registrations = new Gee.ArrayList<uint> ();
|
||||
|
||||
public AuthenticationChannel (ControlService parent, DBusConnection connection, AuthenticationService service) {
|
||||
public AuthenticationChannel (ConnectionHandler parent, DBusConnection connection, AuthenticationService service) {
|
||||
Object (
|
||||
parent: parent,
|
||||
connection: connection,
|
||||
@@ -581,7 +728,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private class ControlChannel : Object, Peer, HostSession, TransportBroker {
|
||||
public weak ControlService parent {
|
||||
public weak ConnectionHandler parent {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
@@ -599,7 +746,7 @@ namespace Frida {
|
||||
private Gee.Set<uint> registrations = new Gee.HashSet<uint> ();
|
||||
private TimeoutSource? ping_timer;
|
||||
|
||||
public ControlChannel (ControlService parent, DBusConnection connection) {
|
||||
public ControlChannel (ConnectionHandler parent, DBusConnection connection) {
|
||||
Object (parent: parent, connection: connection);
|
||||
}
|
||||
|
||||
@@ -848,16 +995,6 @@ namespace Frida {
|
||||
expiry_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class Transport {
|
||||
public AgentSessionId session_id;
|
||||
public Source expiry_source;
|
||||
|
||||
public Transport (AgentSessionId session_id, Source expiry_source) {
|
||||
this.session_id = session_id;
|
||||
this.expiry_source = expiry_source;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
public class ControlService : Object {
|
||||
|
||||
@@ -933,27 +933,28 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void prepare_for_launch (string identifier, Cancellable? cancellable) throws Error, IOError {
|
||||
yield call ("prepareForLaunch", new Json.Node[] { new Json.Node.alloc ().init_string (identifier) }, cancellable);
|
||||
yield call ("prepareForLaunch", new Json.Node[] { new Json.Node.alloc ().init_string (identifier) }, null,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
public async void cancel_launch (string identifier, Cancellable? cancellable) throws Error, IOError {
|
||||
yield call ("cancelLaunch", new Json.Node[] { new Json.Node.alloc ().init_string (identifier) }, cancellable);
|
||||
yield call ("cancelLaunch", new Json.Node[] { new Json.Node.alloc ().init_string (identifier) }, null, cancellable);
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
yield call ("enableSpawnGating", new Json.Node[] {}, cancellable);
|
||||
yield call ("enableSpawnGating", new Json.Node[] {}, null, cancellable);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
yield call ("disableSpawnGating", new Json.Node[] {}, cancellable);
|
||||
yield call ("disableSpawnGating", new Json.Node[] {}, null, cancellable);
|
||||
}
|
||||
|
||||
public async void claim_process (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
yield call ("claimProcess", new Json.Node[] { new Json.Node.alloc ().init_int (pid) }, cancellable);
|
||||
yield call ("claimProcess", new Json.Node[] { new Json.Node.alloc ().init_int (pid) }, null, cancellable);
|
||||
}
|
||||
|
||||
public async void unclaim_process (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
yield call ("unclaimProcess", new Json.Node[] { new Json.Node.alloc ().init_int (pid) }, cancellable);
|
||||
yield call ("unclaimProcess", new Json.Node[] { new Json.Node.alloc ().init_int (pid) }, null, cancellable);
|
||||
}
|
||||
|
||||
protected override void on_event (string type, Json.Array event) {
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
# define CORE_FOUNDATION "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
|
||||
#endif
|
||||
|
||||
typedef struct _FridaHelperContext FridaHelperContext;
|
||||
typedef struct _FridaDispatchContext FridaDispatchContext;
|
||||
typedef struct _FridaSpawnInstance FridaSpawnInstance;
|
||||
typedef guint FridaDyldFlavor;
|
||||
typedef struct _FridaSpawnInstanceDyldData FridaSpawnInstanceDyldData;
|
||||
@@ -106,7 +106,7 @@ typedef union _FridaDebugState FridaDebugState;
|
||||
typedef int FridaConvertThreadStateDirection;
|
||||
typedef guint FridaAslr;
|
||||
|
||||
struct _FridaHelperContext
|
||||
struct _FridaDispatchContext
|
||||
{
|
||||
dispatch_queue_t dispatch_queue;
|
||||
};
|
||||
@@ -183,6 +183,7 @@ struct _FridaSpawnInstance
|
||||
GumAddress modern_entry_address;
|
||||
|
||||
/* V4+ */
|
||||
GumAddress notify_objc_init;
|
||||
GumAddress info_ptr_address;
|
||||
|
||||
/* V3- */
|
||||
@@ -375,7 +376,7 @@ enum _FridaAslr
|
||||
};
|
||||
|
||||
static FridaSpawnInstance * frida_spawn_instance_new (FridaDarwinHelperBackend * backend);
|
||||
static void frida_spawn_instance_free (FridaSpawnInstance * instance);
|
||||
static void frida_spawn_instance_close (FridaSpawnInstance * instance);
|
||||
static void frida_spawn_instance_resume (FridaSpawnInstance * self);
|
||||
|
||||
static void frida_spawn_instance_on_server_cancel (void * context);
|
||||
@@ -390,12 +391,12 @@ static void frida_spawn_instance_set_libc_initialized (FridaSpawnInstance * self
|
||||
static kern_return_t frida_spawn_instance_create_dyld_data (FridaSpawnInstance * self);
|
||||
static void frida_spawn_instance_destroy_dyld_data (FridaSpawnInstance * self);
|
||||
#if defined (HAVE_IOS) || defined (HAVE_TVOS)
|
||||
static gboolean frida_pick_ios_tvos_bootstrapper (const GumModuleDetails * details, gpointer user_data);
|
||||
static gboolean frida_pick_ios_tvos_bootstrapper (GumModule * module, gpointer user_data);
|
||||
#endif
|
||||
static void frida_spawn_instance_unset_helpers (FridaSpawnInstance * self);
|
||||
static void frida_spawn_instance_call_set_helpers (FridaSpawnInstance * self, GumDarwinUnifiedThreadState * state, mach_vm_address_t helpers);
|
||||
static void frida_spawn_instance_call_dlopen (FridaSpawnInstance * self, GumDarwinUnifiedThreadState * state, mach_vm_address_t lib_name, int mode);
|
||||
static gboolean frida_find_cf_initialize (const GumModuleDetails * details, gpointer user_data);
|
||||
static gboolean frida_find_cf_initialize (GumModule * module, gpointer user_data);
|
||||
static void frida_spawn_instance_call_cf_initialize (FridaSpawnInstance * self, GumDarwinUnifiedThreadState * state);
|
||||
static void frida_spawn_instance_set_nth_breakpoint (FridaSpawnInstance * self, guint n, GumAddress break_at, FridaBreakpointRepeat repeat);
|
||||
static void frida_spawn_instance_enable_nth_breakpoint (FridaSpawnInstance * self, guint n);
|
||||
@@ -409,7 +410,7 @@ static void frida_configure_terminal_attributes (gint fd);
|
||||
|
||||
static FridaInjectInstance * frida_inject_instance_new (FridaDarwinHelperBackend * backend, guint id, guint pid);
|
||||
static FridaInjectInstance * frida_inject_instance_clone (const FridaInjectInstance * instance, guint id);
|
||||
static void frida_inject_instance_free (FridaInjectInstance * instance);
|
||||
static void frida_inject_instance_close (FridaInjectInstance * instance);
|
||||
static gboolean frida_inject_instance_task_did_not_exec (FridaInjectInstance * instance);
|
||||
|
||||
static gboolean frida_inject_instance_start_thread (FridaInjectInstance * self, GError ** error);
|
||||
@@ -428,6 +429,8 @@ static void frida_agent_context_emit_mach_stub_code (FridaAgentContext * self, g
|
||||
static void frida_agent_context_emit_pthread_stub_code (FridaAgentContext * self, guint8 * code, GumDarwinModuleResolver * resolver,
|
||||
GumDarwinMapper * mapper);
|
||||
|
||||
static gboolean frida_convert_thread_state_for_task (mach_port_t task, thread_state_flavor_t flavor, gconstpointer in_state,
|
||||
mach_msg_type_number_t in_state_count, gpointer out_state, mach_msg_type_number_t * out_state_count, GError ** error);
|
||||
static mach_port_t frida_obtain_thread_port_for_thread_id (mach_port_t task, uint64_t thread_id);
|
||||
static kern_return_t frida_get_thread_state (mach_port_t thread, thread_state_flavor_t flavor, gpointer state,
|
||||
mach_msg_type_number_t * count);
|
||||
@@ -759,30 +762,30 @@ permission_denied:
|
||||
}
|
||||
|
||||
void
|
||||
_frida_darwin_helper_backend_create_context (FridaDarwinHelperBackend * self)
|
||||
_frida_darwin_helper_backend_create_dispatch_context (FridaDarwinHelperBackend * self)
|
||||
{
|
||||
FridaHelperContext * ctx;
|
||||
FridaDispatchContext * ctx;
|
||||
|
||||
ctx = g_slice_new (FridaHelperContext);
|
||||
ctx = g_slice_new (FridaDispatchContext);
|
||||
ctx->dispatch_queue = dispatch_queue_create ("re.frida.helper.queue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
self->context = ctx;
|
||||
self->dispatch_context = ctx;
|
||||
}
|
||||
|
||||
void
|
||||
_frida_darwin_helper_backend_destroy_context (FridaDarwinHelperBackend * self)
|
||||
_frida_darwin_helper_backend_destroy_dispatch_context (FridaDarwinHelperBackend * self)
|
||||
{
|
||||
FridaHelperContext * ctx = self->context;
|
||||
FridaDispatchContext * ctx = self->dispatch_context;
|
||||
|
||||
dispatch_release (ctx->dispatch_queue);
|
||||
|
||||
g_slice_free (FridaHelperContext, ctx);
|
||||
g_slice_free (FridaDispatchContext, ctx);
|
||||
}
|
||||
|
||||
void
|
||||
_frida_darwin_helper_backend_schedule_on_dispatch_queue (FridaDarwinHelperBackend * self, FridaDarwinHelperBackendDispatchWorker worker, gpointer user_data)
|
||||
{
|
||||
FridaHelperContext * ctx = self->context;
|
||||
FridaDispatchContext * ctx = self->dispatch_context;
|
||||
|
||||
dispatch_async (ctx->dispatch_queue, ^
|
||||
{
|
||||
@@ -931,7 +934,7 @@ any_failure:
|
||||
{
|
||||
if (instance->pid != 0)
|
||||
kill (instance->pid, SIGKILL);
|
||||
frida_spawn_instance_free (instance);
|
||||
frida_spawn_instance_close (instance);
|
||||
|
||||
pid = 0;
|
||||
|
||||
@@ -1866,7 +1869,7 @@ void
|
||||
_frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHelperBackend * self, void * opaque_instance, guint task, GError ** error)
|
||||
{
|
||||
FridaSpawnInstance * instance = opaque_instance;
|
||||
FridaHelperContext * ctx = self->context;
|
||||
FridaDispatchContext * ctx = self->dispatch_context;
|
||||
const gchar * failed_operation;
|
||||
kern_return_t kr;
|
||||
mach_port_t self_task, child_thread;
|
||||
@@ -2028,6 +2031,10 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
|
||||
instance->dyld_flavor = (modern_entry_address != 0) ? FRIDA_DYLD_V4_PLUS : FRIDA_DYLD_V3_MINUS;
|
||||
if (instance->dyld_flavor == FRIDA_DYLD_V4_PLUS)
|
||||
{
|
||||
instance->notify_objc_init = gum_darwin_module_resolve_symbol_address (dyld, "__ZN5dyld412RuntimeState14notifyObjCInitEPKNS_6LoaderE");
|
||||
if (instance->notify_objc_init != 0)
|
||||
modern_entry_address = instance->notify_objc_init;
|
||||
|
||||
instance->modern_entry_address = modern_entry_address;
|
||||
legacy_entry_address = 0;
|
||||
|
||||
@@ -2106,7 +2113,12 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
|
||||
if (instance->dyld_flavor == FRIDA_DYLD_V4_PLUS)
|
||||
{
|
||||
GumAddress restart_with_dyld_in_cache = gum_darwin_module_resolve_symbol_address (dyld,
|
||||
"__ZN5dyld422restartWithDyldInCacheEPKNS_10KernelArgsEPKN5dyld39MachOFileEPK15DyldSharedCachePv");
|
||||
if (restart_with_dyld_in_cache == 0)
|
||||
{
|
||||
restart_with_dyld_in_cache = gum_darwin_module_resolve_symbol_address (dyld,
|
||||
"__ZN5dyld422restartWithDyldInCacheEPKNS_10KernelArgsEPKN5dyld39MachOFileEPv");
|
||||
}
|
||||
if (restart_with_dyld_in_cache != 0)
|
||||
frida_spawn_instance_set_nth_breakpoint (instance, i++, restart_with_dyld_in_cache, FRIDA_BREAKPOINT_REPEAT_NEVER);
|
||||
}
|
||||
@@ -2188,9 +2200,9 @@ _frida_darwin_helper_backend_resume_spawn_instance (FridaDarwinHelperBackend * s
|
||||
}
|
||||
|
||||
void
|
||||
_frida_darwin_helper_backend_free_spawn_instance (FridaDarwinHelperBackend * self, void * instance)
|
||||
_frida_darwin_helper_backend_close_spawn_instance (FridaDarwinHelperBackend * self, void * instance)
|
||||
{
|
||||
frida_spawn_instance_free (instance);
|
||||
frida_spawn_instance_close (instance);
|
||||
}
|
||||
|
||||
guint
|
||||
@@ -2477,7 +2489,7 @@ mach_failure:
|
||||
}
|
||||
failure:
|
||||
{
|
||||
frida_inject_instance_free (instance);
|
||||
frida_inject_instance_close (instance);
|
||||
goto beach;
|
||||
}
|
||||
beach:
|
||||
@@ -2583,16 +2595,24 @@ static gboolean
|
||||
frida_inject_instance_start_thread (FridaInjectInstance * self, GError ** error)
|
||||
{
|
||||
gboolean success = FALSE;
|
||||
thread_state_t thread_state;
|
||||
mach_msg_type_number_t thread_state_count;
|
||||
kern_return_t kr;
|
||||
const gchar * failed_operation;
|
||||
FridaHelperContext * ctx = self->backend->context;
|
||||
FridaDispatchContext * ctx = self->backend->dispatch_context;
|
||||
dispatch_source_t source;
|
||||
|
||||
kr = thread_create (self->task, &self->thread);
|
||||
CHECK_MACH_RESULT (kr, ==, KERN_SUCCESS, "thread_create");
|
||||
thread_state = g_alloca (self->thread_state_count * sizeof (natural_t));
|
||||
thread_state_count = self->thread_state_count;
|
||||
|
||||
kr = frida_set_thread_state (self->thread, self->thread_state_flavor, self->thread_state_data, self->thread_state_count);
|
||||
CHECK_MACH_RESULT (kr, ==, KERN_SUCCESS, "set_thread_state");
|
||||
if (!frida_convert_thread_state_for_task (self->task, self->thread_state_flavor, self->thread_state_data, self->thread_state_count,
|
||||
thread_state, &thread_state_count, error))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
kr = thread_create_running (self->task, self->thread_state_flavor, thread_state, thread_state_count, &self->thread);
|
||||
CHECK_MACH_RESULT (kr, ==, KERN_SUCCESS, "thread_create");
|
||||
|
||||
source = dispatch_source_create (DISPATCH_SOURCE_TYPE_MACH_SEND, self->thread, DISPATCH_MACH_SEND_DEAD, ctx->dispatch_queue);
|
||||
self->thread_monitor_source = source;
|
||||
@@ -2601,9 +2621,6 @@ frida_inject_instance_start_thread (FridaInjectInstance * self, GError ** error)
|
||||
dispatch_source_set_event_handler_f (source, frida_inject_instance_on_mach_thread_dead);
|
||||
dispatch_resume (source);
|
||||
|
||||
kr = thread_resume (self->thread);
|
||||
CHECK_MACH_RESULT (kr, ==, KERN_SUCCESS, "thread_resume");
|
||||
|
||||
success = TRUE;
|
||||
|
||||
goto beach;
|
||||
@@ -2629,7 +2646,7 @@ frida_inject_instance_on_thread_monitor_cancel (void * context)
|
||||
FridaInjectInstance * self = context;
|
||||
|
||||
dispatch_release (g_steal_pointer (&self->thread_monitor_source));
|
||||
frida_inject_instance_free (self);
|
||||
frida_inject_instance_close (self);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -2688,7 +2705,7 @@ _frida_darwin_helper_backend_join_inject_instance_posix_thread (FridaDarwinHelpe
|
||||
static void
|
||||
frida_inject_instance_join_posix_thread (FridaInjectInstance * self, mach_port_t posix_thread)
|
||||
{
|
||||
FridaHelperContext * ctx = self->backend->context;
|
||||
FridaDispatchContext * ctx = self->backend->dispatch_context;
|
||||
mach_port_t self_task;
|
||||
dispatch_source_t source;
|
||||
|
||||
@@ -2721,9 +2738,9 @@ _frida_darwin_helper_backend_get_pid_of_inject_instance (FridaDarwinHelperBacken
|
||||
}
|
||||
|
||||
void
|
||||
_frida_darwin_helper_backend_free_inject_instance (FridaDarwinHelperBackend * self, void * instance)
|
||||
_frida_darwin_helper_backend_close_inject_instance (FridaDarwinHelperBackend * self, void * instance)
|
||||
{
|
||||
frida_inject_instance_free (instance);
|
||||
frida_inject_instance_close (instance);
|
||||
}
|
||||
|
||||
static FridaSpawnInstance *
|
||||
@@ -2733,7 +2750,7 @@ frida_spawn_instance_new (FridaDarwinHelperBackend * backend)
|
||||
guint i;
|
||||
|
||||
instance = g_slice_new0 (FridaSpawnInstance);
|
||||
instance->backend = backend;
|
||||
instance->backend = g_object_ref (backend);
|
||||
instance->thread = MACH_PORT_NULL;
|
||||
|
||||
instance->server_port = MACH_PORT_NULL;
|
||||
@@ -2756,11 +2773,13 @@ frida_spawn_instance_new (FridaDarwinHelperBackend * backend)
|
||||
instance->page_pool[i].scratch_page = 0;
|
||||
}
|
||||
|
||||
_frida_darwin_helper_backend_on_instance_created (backend, instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_spawn_instance_free (FridaSpawnInstance * instance)
|
||||
frida_spawn_instance_close (FridaSpawnInstance * instance)
|
||||
{
|
||||
task_t self_task;
|
||||
FridaExceptionPortSet * previous_ports;
|
||||
@@ -2799,6 +2818,9 @@ frida_spawn_instance_free (FridaSpawnInstance * instance)
|
||||
if (instance->dyld != NULL)
|
||||
g_object_unref (instance->dyld);
|
||||
|
||||
_frida_darwin_helper_backend_on_instance_destroyed (instance->backend, instance);
|
||||
g_object_unref (instance->backend);
|
||||
|
||||
g_slice_free (FridaSpawnInstance, instance);
|
||||
}
|
||||
|
||||
@@ -2841,7 +2863,7 @@ frida_spawn_instance_on_server_cancel (void * context)
|
||||
FridaSpawnInstance * self = context;
|
||||
|
||||
dispatch_release (g_steal_pointer (&self->server_recv_source));
|
||||
frida_spawn_instance_free (self);
|
||||
frida_spawn_instance_close (self);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -3078,9 +3100,15 @@ frida_spawn_instance_handle_breakpoint (FridaSpawnInstance * self, FridaBreakpoi
|
||||
if (self->dyld_flavor == FRIDA_DYLD_V4_PLUS)
|
||||
{
|
||||
if (pc == self->modern_entry_address)
|
||||
self->breakpoint_phase = FRIDA_BREAKPOINT_SET_LIBDYLD_INITIALIZE_CALLER_BREAKPOINT;
|
||||
{
|
||||
self->breakpoint_phase = (pc == self->notify_objc_init)
|
||||
? FRIDA_BREAKPOINT_LIBSYSTEM_INITIALIZED
|
||||
: FRIDA_BREAKPOINT_SET_LIBDYLD_INITIALIZE_CALLER_BREAKPOINT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return frida_spawn_instance_handle_dyld_restart (self);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3346,6 +3374,7 @@ frida_spawn_instance_handle_dyld_restart (FridaSpawnInstance * self)
|
||||
GumAddress * info_ptr;
|
||||
struct dyld_all_image_infos * info = NULL;
|
||||
GumDarwinModule * dyld = NULL;
|
||||
gssize delta;
|
||||
|
||||
info_ptr = (GumAddress *) gum_darwin_read (self->task, self->info_ptr_address, sizeof (GumAddress), NULL);
|
||||
if (info_ptr == NULL)
|
||||
@@ -3360,7 +3389,11 @@ frida_spawn_instance_handle_dyld_restart (FridaSpawnInstance * self)
|
||||
if (dyld == NULL)
|
||||
goto beach;
|
||||
|
||||
self->modern_entry_address = GUM_ADDRESS (info->dyldImageLoadAddress) + (self->modern_entry_address - self->dyld->base_address);
|
||||
delta = (gssize) info->dyldImageLoadAddress - (gssize) self->dyld->base_address;
|
||||
|
||||
self->modern_entry_address += delta;
|
||||
if (self->notify_objc_init != 0)
|
||||
self->notify_objc_init += delta;
|
||||
|
||||
g_object_unref (self->dyld);
|
||||
self->dyld = g_steal_pointer (&dyld);
|
||||
@@ -3618,22 +3651,25 @@ frida_spawn_instance_destroy_dyld_data (FridaSpawnInstance * self)
|
||||
#if defined (HAVE_IOS) || defined (HAVE_TVOS)
|
||||
|
||||
static gboolean
|
||||
frida_pick_ios_tvos_bootstrapper (const GumModuleDetails * details, gpointer user_data)
|
||||
frida_pick_ios_tvos_bootstrapper (GumModule * module, gpointer user_data)
|
||||
{
|
||||
FridaSpawnInstanceDyldData * data = user_data;
|
||||
const gchar * path;
|
||||
const gchar * candidates[] = {
|
||||
"/usr/lib/substitute-inserter.dylib",
|
||||
"/usr/lib/pspawn_payload-stg2.dylib"
|
||||
};
|
||||
guint i;
|
||||
|
||||
path = gum_module_get_path (module);
|
||||
|
||||
for (i = 0; i != G_N_ELEMENTS (candidates); i++)
|
||||
{
|
||||
const gchar * bootstrapper = candidates[i];
|
||||
|
||||
if (strcmp (details->path, bootstrapper) == 0)
|
||||
if (strcmp (path, bootstrapper) == 0)
|
||||
{
|
||||
strcpy (data->bootstrapper, details->path);
|
||||
strcpy (data->bootstrapper, path);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
@@ -3796,15 +3832,15 @@ frida_spawn_instance_call_dlopen (FridaSpawnInstance * self, GumDarwinUnifiedThr
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_find_cf_initialize (const GumModuleDetails * details, gpointer user_data)
|
||||
frida_find_cf_initialize (GumModule * module, gpointer user_data)
|
||||
{
|
||||
FridaSpawnInstance * self = user_data;
|
||||
GumDarwinModule * core_foundation;
|
||||
|
||||
if (strcmp (details->path, CORE_FOUNDATION) != 0)
|
||||
if (strcmp (gum_module_get_path (module), CORE_FOUNDATION) != 0)
|
||||
return TRUE;
|
||||
|
||||
core_foundation = gum_darwin_module_new_from_memory (CORE_FOUNDATION, self->task, details->range->base_address,
|
||||
core_foundation = gum_darwin_module_new_from_memory (CORE_FOUNDATION, self->task, gum_module_get_range (module)->base_address,
|
||||
GUM_DARWIN_MODULE_FLAGS_NONE, NULL);
|
||||
|
||||
self->cf_initialize_address = gum_darwin_module_resolve_symbol_address (core_foundation, "___CFInitialize");
|
||||
@@ -4083,6 +4119,8 @@ frida_inject_instance_new (FridaDarwinHelperBackend * backend, guint id, guint p
|
||||
|
||||
instance->backend = g_object_ref (backend);
|
||||
|
||||
_frida_darwin_helper_backend_on_instance_created (backend, instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -4112,11 +4150,13 @@ frida_inject_instance_clone (const FridaInjectInstance * instance, guint id)
|
||||
|
||||
g_object_ref (clone->backend);
|
||||
|
||||
_frida_darwin_helper_backend_on_instance_created (clone->backend, clone);
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_inject_instance_free (FridaInjectInstance * instance)
|
||||
frida_inject_instance_close (FridaInjectInstance * instance)
|
||||
{
|
||||
FridaAgentContext * agent_context = instance->agent_context;
|
||||
task_t self_task;
|
||||
@@ -4156,6 +4196,7 @@ frida_inject_instance_free (FridaInjectInstance * instance)
|
||||
if (instance->task != MACH_PORT_NULL)
|
||||
mach_port_deallocate (self_task, instance->task);
|
||||
|
||||
_frida_darwin_helper_backend_on_instance_destroyed (instance->backend, instance);
|
||||
g_object_unref (instance->backend);
|
||||
|
||||
g_slice_free (FridaInjectInstance, instance);
|
||||
@@ -4261,9 +4302,10 @@ frida_agent_context_init (FridaAgentContext * self, const FridaAgentDetails * de
|
||||
static gboolean
|
||||
frida_agent_context_init_functions (FridaAgentContext * self, GumDarwinModuleResolver * resolver, GumDarwinMapper * mapper, GError ** error)
|
||||
{
|
||||
gboolean success = FALSE;
|
||||
GumDarwinModule * module;
|
||||
|
||||
module = gum_darwin_module_resolver_find_module (resolver, "/usr/lib/system/libsystem_kernel.dylib");
|
||||
module = gum_darwin_module_resolver_find_module_by_name (resolver, "/usr/lib/system/libsystem_kernel.dylib");
|
||||
if (module == NULL)
|
||||
goto no_libc;
|
||||
FRIDA_AGENT_CONTEXT_RESOLVE (mach_task_self);
|
||||
@@ -4272,8 +4314,10 @@ frida_agent_context_init_functions (FridaAgentContext * self, GumDarwinModuleRes
|
||||
FRIDA_AGENT_CONTEXT_RESOLVE (mach_msg_receive);
|
||||
FRIDA_AGENT_CONTEXT_RESOLVE (mach_port_destroy);
|
||||
FRIDA_AGENT_CONTEXT_RESOLVE (thread_terminate);
|
||||
g_object_unref (module);
|
||||
module = NULL;
|
||||
|
||||
module = gum_darwin_module_resolver_find_module (resolver, "/usr/lib/system/libsystem_pthread.dylib");
|
||||
module = gum_darwin_module_resolver_find_module_by_name (resolver, "/usr/lib/system/libsystem_pthread.dylib");
|
||||
if (module == NULL)
|
||||
goto no_libc;
|
||||
FRIDA_AGENT_CONTEXT_TRY_RESOLVE (pthread_create_from_mach_thread);
|
||||
@@ -4284,18 +4328,23 @@ frida_agent_context_init_functions (FridaAgentContext * self, GumDarwinModuleRes
|
||||
FRIDA_AGENT_CONTEXT_TRY_RESOLVE (pthread_threadid_np);
|
||||
FRIDA_AGENT_CONTEXT_RESOLVE (pthread_detach);
|
||||
FRIDA_AGENT_CONTEXT_RESOLVE (pthread_self);
|
||||
g_object_unref (module);
|
||||
module = NULL;
|
||||
|
||||
if (mapper == NULL)
|
||||
{
|
||||
module = gum_darwin_module_resolver_find_module (resolver, "/usr/lib/system/libdyld.dylib");
|
||||
module = gum_darwin_module_resolver_find_module_by_name (resolver, "/usr/lib/system/libdyld.dylib");
|
||||
if (module == NULL)
|
||||
goto no_libc;
|
||||
FRIDA_AGENT_CONTEXT_RESOLVE (dlopen);
|
||||
FRIDA_AGENT_CONTEXT_RESOLVE (dlsym);
|
||||
FRIDA_AGENT_CONTEXT_RESOLVE (dlclose);
|
||||
g_object_unref (module);
|
||||
module = NULL;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
success = TRUE;
|
||||
goto beach;
|
||||
|
||||
no_libc:
|
||||
{
|
||||
@@ -4303,7 +4352,7 @@ no_libc:
|
||||
FRIDA_ERROR,
|
||||
FRIDA_ERROR_NOT_SUPPORTED,
|
||||
"Unable to attach to processes without Apple's libc (for now)");
|
||||
goto failure;
|
||||
goto beach;
|
||||
}
|
||||
missing_symbol:
|
||||
{
|
||||
@@ -4311,11 +4360,13 @@ missing_symbol:
|
||||
FRIDA_ERROR,
|
||||
FRIDA_ERROR_NOT_SUPPORTED,
|
||||
"Unexpected error while resolving functions");
|
||||
goto failure;
|
||||
goto beach;
|
||||
}
|
||||
failure:
|
||||
beach:
|
||||
{
|
||||
return FALSE;
|
||||
g_clear_object (&module);
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5072,6 +5123,52 @@ frida_agent_context_emit_arm64_pthread_stub_body (FridaAgentContext * self, Frid
|
||||
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
frida_convert_thread_state_for_task (mach_port_t task, thread_state_flavor_t flavor, gconstpointer in_state,
|
||||
mach_msg_type_number_t in_state_count, gpointer out_state, mach_msg_type_number_t * out_state_count,
|
||||
GError ** error)
|
||||
{
|
||||
gboolean success = FALSE;
|
||||
kern_return_t kr;
|
||||
const gchar * failed_operation;
|
||||
thread_act_array_t threads = NULL;
|
||||
mach_msg_type_number_t thread_count = 0;
|
||||
|
||||
kr = task_threads (task, &threads, &thread_count);
|
||||
CHECK_MACH_RESULT (kr, ==, KERN_SUCCESS, "task_threads");
|
||||
|
||||
kr = frida_convert_thread_state (threads[0], FRIDA_CONVERT_THREAD_STATE_OUT, flavor, in_state, in_state_count,
|
||||
out_state, out_state_count);
|
||||
CHECK_MACH_RESULT (kr, ==, KERN_SUCCESS, "thread_convert_thread_state");
|
||||
|
||||
success = TRUE;
|
||||
|
||||
goto beach;
|
||||
|
||||
mach_failure:
|
||||
{
|
||||
g_set_error (error,
|
||||
FRIDA_ERROR,
|
||||
FRIDA_ERROR_NOT_SUPPORTED,
|
||||
"Unexpected error while converting thread state for task (%s returned '%s')",
|
||||
failed_operation, mach_error_string (kr));
|
||||
goto beach;
|
||||
}
|
||||
beach:
|
||||
{
|
||||
mach_msg_type_number_t i;
|
||||
|
||||
if (threads != NULL)
|
||||
{
|
||||
for (i = 0; i != thread_count; i++)
|
||||
mach_port_deallocate (mach_task_self (), threads[i]);
|
||||
vm_deallocate (mach_task_self (), (vm_address_t) threads, thread_count * sizeof (thread_t));
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
static mach_port_t
|
||||
frida_obtain_thread_port_for_thread_id (mach_port_t task, uint64_t thread_id)
|
||||
{
|
||||
|
||||
@@ -20,7 +20,8 @@ namespace Frida {
|
||||
protected delegate void DispatchWorker ();
|
||||
protected delegate void LaunchCompletionHandler (owned StdioPipes? pipes, owned Error? error);
|
||||
|
||||
public void * context;
|
||||
private MainContext main_context;
|
||||
public void * dispatch_context;
|
||||
|
||||
public Gee.HashMap<uint, void *> spawn_instances = new Gee.HashMap<uint, void *> ();
|
||||
private Gee.HashMap<uint, OutputStream> stdin_streams = new Gee.HashMap<uint, OutputStream> ();
|
||||
@@ -32,6 +33,9 @@ namespace Frida {
|
||||
private Gee.HashMap<void *, uint> inject_cleaner_by_instance = new Gee.HashMap<void *, uint> ();
|
||||
private Gee.HashMap<uint, uint> inject_expiry_timers = new Gee.HashMap<uint, uint> ();
|
||||
|
||||
private Gee.Set<void *> instances = new Gee.HashSet<void *> ();
|
||||
private SourceFunc? on_last_instance_destroyed = null;
|
||||
|
||||
public uint next_id = 1;
|
||||
|
||||
private PolicySoftener policy_softener;
|
||||
@@ -40,7 +44,8 @@ namespace Frida {
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
construct {
|
||||
_create_context ();
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
_create_dispatch_context ();
|
||||
|
||||
dtrace_agent = DTraceAgent.try_open ();
|
||||
if (dtrace_agent != null) {
|
||||
@@ -63,11 +68,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
~DarwinHelperBackend () {
|
||||
foreach (var instance in spawn_instances.values)
|
||||
_free_spawn_instance (instance);
|
||||
foreach (var instance in inject_instances.values)
|
||||
_free_inject_instance (instance);
|
||||
_destroy_context ();
|
||||
_destroy_dispatch_context ();
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
@@ -77,11 +78,24 @@ namespace Frida {
|
||||
pending.complete ();
|
||||
|
||||
foreach (var entry in inject_cleaner_by_instance.entries) {
|
||||
_free_inject_instance (entry.key);
|
||||
_close_inject_instance (entry.key);
|
||||
Source.remove (entry.value);
|
||||
}
|
||||
inject_cleaner_by_instance.clear ();
|
||||
|
||||
foreach (var instance in spawn_instances.values)
|
||||
_close_spawn_instance (instance);
|
||||
spawn_instances.clear ();
|
||||
|
||||
bool any_pending = false;
|
||||
lock (instances) {
|
||||
any_pending = !instances.is_empty;
|
||||
if (any_pending)
|
||||
on_last_instance_destroyed = close.callback;
|
||||
}
|
||||
if (any_pending)
|
||||
yield;
|
||||
|
||||
if (dtrace_agent != null) {
|
||||
dtrace_agent.spawn_added.disconnect (on_dtrace_agent_spawn_added);
|
||||
dtrace_agent.spawn_removed.disconnect (on_dtrace_agent_spawn_removed);
|
||||
@@ -139,11 +153,11 @@ namespace Frida {
|
||||
Error error = null;
|
||||
|
||||
_launch (identifier, options, (p, e) => {
|
||||
Idle.add (() => {
|
||||
schedule_on_frida_thread (() => {
|
||||
pipes = p;
|
||||
error = e;
|
||||
launch.callback ();
|
||||
return false;
|
||||
return Source.REMOVE;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -187,7 +201,7 @@ namespace Frida {
|
||||
inject_cleaner_by_instance.unset (instance, out source_id);
|
||||
Source.remove (source_id);
|
||||
|
||||
_free_inject_instance (instance);
|
||||
_close_inject_instance (instance);
|
||||
}
|
||||
|
||||
policy_softener.forget (pid);
|
||||
@@ -205,7 +219,7 @@ namespace Frida {
|
||||
|
||||
void * instance;
|
||||
if (spawn_instances.unset (pid, out instance))
|
||||
_free_spawn_instance (instance);
|
||||
_close_spawn_instance (instance);
|
||||
|
||||
child_dead (pid);
|
||||
}
|
||||
@@ -293,7 +307,7 @@ namespace Frida {
|
||||
void * instance;
|
||||
if (spawn_instances.unset (pid, out instance)) {
|
||||
_resume_spawn_instance (instance);
|
||||
_free_spawn_instance (instance);
|
||||
_close_spawn_instance (instance);
|
||||
} else {
|
||||
resume_with_validation (pid);
|
||||
}
|
||||
@@ -412,7 +426,7 @@ namespace Frida {
|
||||
} catch (GLib.Error e) {
|
||||
if (instance_created_here) {
|
||||
spawn_instances.unset (pid);
|
||||
_free_spawn_instance (spawn_instance);
|
||||
_close_spawn_instance (spawn_instance);
|
||||
}
|
||||
|
||||
throw_api_error (e);
|
||||
@@ -495,21 +509,21 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public void _on_spawn_instance_ready (uint pid) {
|
||||
Idle.add (() => {
|
||||
schedule_on_frida_thread (() => {
|
||||
spawn_instance_ready (pid);
|
||||
return false;
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
public void _on_spawn_instance_error (uint pid, Error error) {
|
||||
Idle.add (() => {
|
||||
schedule_on_frida_thread (() => {
|
||||
spawn_instance_error (pid, error);
|
||||
return false;
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
public void _on_mach_thread_dead (uint id, void * posix_thread) {
|
||||
Idle.add (() => {
|
||||
schedule_on_frida_thread (() => {
|
||||
var instance = inject_instances[id];
|
||||
assert (instance != null);
|
||||
|
||||
@@ -518,14 +532,14 @@ namespace Frida {
|
||||
else
|
||||
_destroy_inject_instance (id);
|
||||
|
||||
return false;
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
public void _on_posix_thread_dead (uint id) {
|
||||
Idle.add (() => {
|
||||
schedule_on_frida_thread (() => {
|
||||
_destroy_inject_instance (id);
|
||||
return false;
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -545,7 +559,7 @@ namespace Frida {
|
||||
private void schedule_inject_instance_cleanup (void * instance) {
|
||||
var cleanup_source = new TimeoutSource (50);
|
||||
cleanup_source.set_callback (() => {
|
||||
_free_inject_instance (instance);
|
||||
_close_inject_instance (instance);
|
||||
|
||||
var removed = inject_cleaner_by_instance.unset (instance);
|
||||
assert (removed);
|
||||
@@ -601,6 +615,19 @@ namespace Frida {
|
||||
policy_softener.forget (pid);
|
||||
}
|
||||
|
||||
public void _on_instance_created (void * instance) {
|
||||
lock (instances)
|
||||
instances.add (instance);
|
||||
}
|
||||
|
||||
public void _on_instance_destroyed (void * instance) {
|
||||
lock (instances) {
|
||||
instances.remove (instance);
|
||||
if (on_last_instance_destroyed != null && instances.is_empty)
|
||||
schedule_on_frida_thread ((owned) on_last_instance_destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_dtrace_agent_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
@@ -611,11 +638,17 @@ namespace Frida {
|
||||
|
||||
private async void flush_dispatch_queue () {
|
||||
_schedule_on_dispatch_queue (() => {
|
||||
Idle.add (flush_dispatch_queue.callback);
|
||||
schedule_on_frida_thread (flush_dispatch_queue.callback);
|
||||
});
|
||||
yield;
|
||||
}
|
||||
|
||||
private void schedule_on_frida_thread (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
public extern static PipeEndpoints make_pipe_endpoints (uint local_task, uint remote_pid, uint remote_task) throws Error;
|
||||
public extern static void make_pipe_endpoint_from_socket (uint pid, uint task, GLib.Socket sock, out string address)
|
||||
throws Error;
|
||||
@@ -626,8 +659,8 @@ namespace Frida {
|
||||
public extern static bool is_mmap_available ();
|
||||
public extern static MappedLibraryBlob mmap (uint task, Bytes blob) throws Error;
|
||||
|
||||
protected extern void _create_context ();
|
||||
protected extern void _destroy_context ();
|
||||
protected extern void _create_dispatch_context ();
|
||||
protected extern void _destroy_dispatch_context ();
|
||||
protected extern void _schedule_on_dispatch_queue (DispatchWorker worker);
|
||||
|
||||
protected extern uint _spawn (string path, HostSpawnOptions options, out StdioPipes? pipes) throws Error;
|
||||
@@ -642,7 +675,7 @@ namespace Frida {
|
||||
protected extern void * _create_spawn_instance (uint pid);
|
||||
protected extern void _prepare_spawn_instance_for_injection (void * instance, uint task) throws Error;
|
||||
protected extern void _resume_spawn_instance (void * instance);
|
||||
protected extern void _free_spawn_instance (void * instance);
|
||||
protected extern void _close_spawn_instance (void * instance);
|
||||
|
||||
protected extern uint _inject_into_task (uint pid, uint task, string path_or_name, MappedLibraryBlob? blob, string entrypoint, string data) throws Error;
|
||||
protected extern void _demonitor (void * instance);
|
||||
@@ -650,7 +683,7 @@ namespace Frida {
|
||||
protected extern void _recreate_injectee_thread (void * instance, uint pid, uint task) throws Error;
|
||||
protected extern void _join_inject_instance_posix_thread (void * instance, void * posix_thread);
|
||||
protected extern uint _get_pid_of_inject_instance (void * instance);
|
||||
protected extern void _free_inject_instance (void * instance);
|
||||
protected extern void _close_inject_instance (void * instance);
|
||||
}
|
||||
|
||||
public class DTraceAgent : Object {
|
||||
|
||||
@@ -151,10 +151,12 @@ frida_replacement_xpc_look_up_endpoint (const char * service_name, uint32_t type
|
||||
static FridaXpcLookUpEndpointFunc
|
||||
frida_find_xpc_look_up_endpoint_implementation (void)
|
||||
{
|
||||
GumModule * libxpc;
|
||||
guint32 * cursor;
|
||||
|
||||
cursor = GSIZE_TO_POINTER (
|
||||
gum_strip_code_address (gum_module_find_export_by_name ("/usr/lib/system/libxpc.dylib", "xpc_endpoint_create_bs_named")));
|
||||
libxpc = gum_process_find_module_by_name ("/usr/lib/system/libxpc.dylib");
|
||||
cursor = GSIZE_TO_POINTER (gum_strip_code_address (gum_module_find_export_by_name (libxpc, "xpc_endpoint_create_bs_named")));
|
||||
g_object_unref (libxpc);
|
||||
if (cursor == NULL)
|
||||
return NULL;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
devkit_outputs = [
|
||||
'frida-core.gir',
|
||||
'frida-core.h',
|
||||
static_lib_prefix + 'frida-core' + static_lib_suffix,
|
||||
'frida-core-example.c',
|
||||
|
||||
@@ -791,7 +791,7 @@ namespace Frida.Droidy {
|
||||
}
|
||||
|
||||
private const uint16 ADB_SERVER_DEFAULT_PORT = 5037;
|
||||
private const uint16 MAX_MESSAGE_LENGTH = 1024;
|
||||
private const size_t MAX_MESSAGE_LENGTH = 65536;
|
||||
|
||||
public static async Client open (Cancellable? cancellable = null) throws Error, IOError {
|
||||
SocketConnectable? connectable = null;
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public class DroidyHostSessionProvider : Object, HostSessionProvider, ChannelProvider {
|
||||
public class DroidyHostSessionProvider : Object, HostSessionProvider, HostChannelProvider {
|
||||
public string id {
|
||||
get { return device_details.serial; }
|
||||
}
|
||||
@@ -162,7 +162,7 @@ namespace Frida {
|
||||
agent_session_detached (id, reason, crash);
|
||||
}
|
||||
|
||||
public async IOStream open_channel (string address, Cancellable? cancellable = null) throws Error, IOError {
|
||||
public async IOStream open_channel (string address, Cancellable? cancellable) throws Error, IOError {
|
||||
if (address.contains (":")) {
|
||||
Droidy.Client client = null;
|
||||
try {
|
||||
@@ -188,7 +188,7 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public weak ChannelProvider channel_provider {
|
||||
public weak HostChannelProvider channel_provider {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
@@ -216,7 +216,7 @@ namespace Frida {
|
||||
private const double MIN_SERVER_CHECK_INTERVAL = 5.0;
|
||||
private const string GADGET_APP_ID = "re.frida.Gadget";
|
||||
|
||||
public DroidyHostSession (Droidy.DeviceDetails device_details, ChannelProvider channel_provider) {
|
||||
public DroidyHostSession (Droidy.DeviceDetails device_details, HostChannelProvider channel_provider) {
|
||||
Object (
|
||||
device_details: device_details,
|
||||
channel_provider: channel_provider
|
||||
|
||||
+42
-18
@@ -2,6 +2,7 @@ from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import struct
|
||||
|
||||
|
||||
def main(argv):
|
||||
@@ -27,29 +28,36 @@ def main(argv):
|
||||
|
||||
embedded_assets = []
|
||||
if host_os == "windows":
|
||||
for agent, flavor in [(agent_modern, "64"),
|
||||
(agent_legacy, "32")]:
|
||||
embedded_agent = priv_dir / f"frida-agent-{flavor}.dll"
|
||||
embedded_dbghelp = priv_dir / f"dbghelp-{flavor}.dll"
|
||||
embedded_symsrv = priv_dir / f"symsrv-{flavor}.dll"
|
||||
pending_archs = {"arm64", "x86_64", "x86"}
|
||||
for agent in {agent_modern, agent_legacy, agent_emulated_modern, agent_emulated_legacy}:
|
||||
if agent is None:
|
||||
continue
|
||||
arch = detect_pefile_arch(agent)
|
||||
embedded_agent = priv_dir / f"frida-agent-{arch}.dll"
|
||||
embedded_dbghelp = priv_dir / f"dbghelp-{arch}.dll"
|
||||
embedded_symsrv = priv_dir / f"symsrv-{arch}.dll"
|
||||
|
||||
if agent is not None:
|
||||
shutil.copy(agent, embedded_agent)
|
||||
shutil.copy(agent, embedded_agent)
|
||||
|
||||
if agent_dbghelp_prefix is not None:
|
||||
shutil.copy(agent_dbghelp_prefix / f"dbghelp-{flavor}.dll", embedded_dbghelp)
|
||||
else:
|
||||
embedded_dbghelp.write_bytes(b"")
|
||||
|
||||
if agent_symsrv_prefix is not None:
|
||||
shutil.copy(agent_symsrv_prefix / f"symsrv-{flavor}.dll", embedded_symsrv)
|
||||
else:
|
||||
embedded_symsrv.write_bytes(b"")
|
||||
if agent_dbghelp_prefix is not None:
|
||||
shutil.copy(agent_dbghelp_prefix / arch / "dbghelp.dll", embedded_dbghelp)
|
||||
else:
|
||||
for f in [embedded_agent, embedded_dbghelp, embedded_symsrv]:
|
||||
f.write_bytes(b"")
|
||||
embedded_dbghelp.write_bytes(b"")
|
||||
|
||||
if agent_symsrv_prefix is not None:
|
||||
shutil.copy(agent_symsrv_prefix / arch / "symsrv.dll", embedded_symsrv)
|
||||
else:
|
||||
embedded_symsrv.write_bytes(b"")
|
||||
|
||||
embedded_assets += [embedded_agent, embedded_dbghelp, embedded_symsrv]
|
||||
pending_archs.remove(arch)
|
||||
for missing_arch in pending_archs:
|
||||
embedded_agent = priv_dir / f"frida-agent-{missing_arch}.dll"
|
||||
embedded_dbghelp = priv_dir / f"dbghelp-{missing_arch}.dll"
|
||||
embedded_symsrv = priv_dir / f"symsrv-{missing_arch}.dll"
|
||||
for asset in {embedded_agent, embedded_dbghelp, embedded_symsrv}:
|
||||
asset.write_bytes(b"")
|
||||
embedded_assets += [asset]
|
||||
elif host_os in {"macos", "ios", "watchos", "tvos"}:
|
||||
embedded_agent = priv_dir / "frida-agent.dylib"
|
||||
if agent_modern is not None and agent_legacy is not None:
|
||||
@@ -103,5 +111,21 @@ def pop_cmd_array_arg(args):
|
||||
return result
|
||||
|
||||
|
||||
def detect_pefile_arch(location):
|
||||
with location.open(mode="rb") as pe:
|
||||
pe.seek(0x3c)
|
||||
e_lfanew, = struct.unpack("<I", pe.read(4))
|
||||
pe.seek(e_lfanew + 4)
|
||||
machine, = struct.unpack("<H", pe.read(2))
|
||||
return PE_MACHINES[machine]
|
||||
|
||||
|
||||
PE_MACHINES = {
|
||||
0x014c: "x86",
|
||||
0x8664: "x86_64",
|
||||
0xaa64: "arm64",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
+37
-6
@@ -2,6 +2,7 @@ from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import struct
|
||||
|
||||
|
||||
def main(argv):
|
||||
@@ -14,12 +15,28 @@ def main(argv):
|
||||
output_dir = Path(args.pop(0))
|
||||
priv_dir = Path(args.pop(0))
|
||||
resource_config = args.pop(0)
|
||||
helper_modern, helper_legacy = [Path(p) if p else None for p in args[:2]]
|
||||
helper_modern, helper_legacy, \
|
||||
helper_emulated_modern, helper_emulated_legacy \
|
||||
= [Path(p) if p else None for p in args[:4]]
|
||||
|
||||
priv_dir.mkdir(exist_ok=True)
|
||||
|
||||
embedded_assets = []
|
||||
if host_os in {"macos", "ios", "watchos", "tvos"}:
|
||||
if host_os == "windows":
|
||||
pending_archs = {"arm64", "x86_64", "x86"}
|
||||
for helper in {helper_modern, helper_legacy, helper_emulated_modern, helper_emulated_legacy}:
|
||||
if helper is None:
|
||||
continue
|
||||
arch = detect_pefile_arch(helper)
|
||||
embedded_helper = priv_dir / f"frida-helper-{arch}.exe"
|
||||
shutil.copy(helper, embedded_helper)
|
||||
embedded_assets += [embedded_helper]
|
||||
pending_archs.remove(arch)
|
||||
for missing_arch in pending_archs:
|
||||
embedded_helper = priv_dir / f"frida-helper-{missing_arch}.exe"
|
||||
embedded_helper.write_bytes(b"")
|
||||
embedded_assets += [embedded_helper]
|
||||
elif host_os in {"macos", "ios", "watchos", "tvos"}:
|
||||
embedded_helper = priv_dir / "frida-helper"
|
||||
|
||||
if helper_modern is not None and helper_legacy is not None:
|
||||
@@ -34,10 +51,8 @@ def main(argv):
|
||||
|
||||
embedded_assets += [embedded_helper]
|
||||
else:
|
||||
exe_suffix = ".exe" if host_os == "windows" else ""
|
||||
|
||||
embedded_helper_modern = priv_dir / f"frida-helper-64{exe_suffix}"
|
||||
embedded_helper_legacy = priv_dir / f"frida-helper-32{exe_suffix}"
|
||||
embedded_helper_modern = priv_dir / f"frida-helper-64"
|
||||
embedded_helper_legacy = priv_dir / f"frida-helper-32"
|
||||
|
||||
if helper_modern is not None:
|
||||
shutil.copy(helper_modern, embedded_helper_modern)
|
||||
@@ -74,5 +89,21 @@ def pop_cmd_array_arg(args):
|
||||
return result
|
||||
|
||||
|
||||
def detect_pefile_arch(location):
|
||||
with location.open(mode="rb") as pe:
|
||||
pe.seek(0x3c)
|
||||
e_lfanew, = struct.unpack("<I", pe.read(4))
|
||||
pe.seek(e_lfanew + 4)
|
||||
machine, = struct.unpack("<H", pe.read(2))
|
||||
return PE_MACHINES[machine]
|
||||
|
||||
|
||||
PE_MACHINES = {
|
||||
0x014c: "x86",
|
||||
0x8664: "x86_64",
|
||||
0xaa64: "arm64",
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
+198
-119
@@ -1,3 +1,4 @@
|
||||
[CCode (gir_namespace = "Frida", gir_version = "1.0")]
|
||||
namespace Frida {
|
||||
public extern void init ();
|
||||
public extern void init_with_runtime (Runtime runtime);
|
||||
@@ -33,12 +34,24 @@ namespace Frida {
|
||||
private Promise<bool> start_request;
|
||||
private Promise<bool> stop_request;
|
||||
|
||||
private HostSessionService service = null;
|
||||
private HostSessionService service;
|
||||
private Gee.ArrayList<Device> devices = new Gee.ArrayList<Device> ();
|
||||
private Gee.ArrayList<DeviceObserverEntry> on_device_added = new Gee.ArrayList<DeviceObserverEntry> ();
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public DeviceManager () {
|
||||
service = new HostSessionService.with_default_backends ();
|
||||
}
|
||||
|
||||
public DeviceManager.with_nonlocal_backends_only () {
|
||||
service = new HostSessionService.with_nonlocal_backends_only ();
|
||||
}
|
||||
|
||||
public DeviceManager.with_socket_backend_only () {
|
||||
service = new HostSessionService.with_socket_backend_only ();
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable = null) throws IOError {
|
||||
yield stop_service (cancellable);
|
||||
}
|
||||
@@ -242,7 +255,7 @@ namespace Frida {
|
||||
opts["keepalive_interval"] = interval;
|
||||
}
|
||||
|
||||
var device = new Device (this, id, name, HostSessionProviderKind.REMOTE, socket_device.provider, raw_options);
|
||||
var device = new Device (this, socket_device.provider, id, name, raw_options);
|
||||
devices.add (device);
|
||||
added (device);
|
||||
changed ();
|
||||
@@ -303,7 +316,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public void _release_device (Device device) {
|
||||
internal void _release_device (Device device) {
|
||||
var device_did_exist = devices.remove (device);
|
||||
assert (device_did_exist);
|
||||
}
|
||||
@@ -339,7 +352,6 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private async void start_service () {
|
||||
service = new HostSessionService.with_default_backends ();
|
||||
try {
|
||||
service.provider_available.connect (on_provider_available);
|
||||
service.provider_unavailable.connect (on_provider_unavailable);
|
||||
@@ -358,7 +370,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private void on_provider_available (HostSessionProvider provider) {
|
||||
var device = new Device (this, provider.id, provider.name, provider.kind, provider);
|
||||
var device = new Device (this, provider);
|
||||
devices.add (device);
|
||||
|
||||
foreach (var observer in on_device_added.to_array ())
|
||||
@@ -483,13 +495,19 @@ namespace Frida {
|
||||
public signal void lost ();
|
||||
|
||||
public string id {
|
||||
get;
|
||||
construct;
|
||||
get {
|
||||
if (_id != null)
|
||||
return _id;
|
||||
return provider.id;
|
||||
}
|
||||
}
|
||||
|
||||
public string name {
|
||||
get;
|
||||
construct;
|
||||
get {
|
||||
if (_name != null)
|
||||
return _name;
|
||||
return provider.name;
|
||||
}
|
||||
}
|
||||
|
||||
public Variant? icon {
|
||||
@@ -498,8 +516,18 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public DeviceType dtype {
|
||||
get;
|
||||
construct;
|
||||
get {
|
||||
switch (provider.kind) {
|
||||
case HostSessionProviderKind.LOCAL:
|
||||
return DeviceType.LOCAL;
|
||||
case HostSessionProviderKind.REMOTE:
|
||||
return DeviceType.REMOTE;
|
||||
case HostSessionProviderKind.USB:
|
||||
return DeviceType.USB;
|
||||
default:
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Bus bus {
|
||||
@@ -508,17 +536,10 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public HostSessionProvider provider {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public weak DeviceManager? manager {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public delegate bool ProcessPredicate (Process process);
|
||||
private string? _id;
|
||||
private string? _name;
|
||||
internal HostSessionProvider provider;
|
||||
private unowned DeviceManager? manager;
|
||||
|
||||
private HostSessionOptions? host_session_options;
|
||||
private Promise<HostSession>? host_session_request;
|
||||
@@ -530,42 +551,31 @@ namespace Frida {
|
||||
private Gee.HashSet<Promise<Session>> pending_attach_requests = new Gee.HashSet<Promise<Session>> ();
|
||||
private Gee.HashMap<AgentSessionId?, Promise<bool>> pending_detach_requests =
|
||||
new Gee.HashMap<AgentSessionId?, Promise<bool>> (AgentSessionId.hash, AgentSessionId.equal);
|
||||
private Gee.Set<Service> services = new Gee.HashSet<Service> ();
|
||||
private Bus _bus;
|
||||
|
||||
internal Device (DeviceManager? manager, string id, string name, HostSessionProviderKind kind, HostSessionProvider provider,
|
||||
public delegate bool ProcessPredicate (Process process);
|
||||
|
||||
internal Device (DeviceManager? mgr, HostSessionProvider prov, string? id = null, string? name = null,
|
||||
HostSessionOptions? options = null) {
|
||||
DeviceType dtype;
|
||||
switch (kind) {
|
||||
case HostSessionProviderKind.LOCAL:
|
||||
dtype = DeviceType.LOCAL;
|
||||
break;
|
||||
case HostSessionProviderKind.REMOTE:
|
||||
dtype = DeviceType.REMOTE;
|
||||
break;
|
||||
case HostSessionProviderKind.USB:
|
||||
dtype = DeviceType.USB;
|
||||
break;
|
||||
default:
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
Object (
|
||||
id: id,
|
||||
name: name,
|
||||
icon: provider.icon,
|
||||
dtype: dtype,
|
||||
provider: provider,
|
||||
manager: manager
|
||||
);
|
||||
Object (icon: prov.icon);
|
||||
|
||||
_id = id;
|
||||
_name = name;
|
||||
manager = mgr;
|
||||
host_session_options = options;
|
||||
|
||||
assign_provider (prov);
|
||||
}
|
||||
|
||||
construct {
|
||||
_bus = new Bus (this);
|
||||
}
|
||||
|
||||
private void assign_provider (HostSessionProvider prov) {
|
||||
provider = prov;
|
||||
provider.host_session_detached.connect (on_host_session_detached);
|
||||
provider.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
_bus = new Bus (this);
|
||||
}
|
||||
|
||||
public bool is_lost () {
|
||||
@@ -1228,7 +1238,7 @@ namespace Frida {
|
||||
public async IOStream open_channel (string address, Cancellable? cancellable = null) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
var channel_provider = provider as ChannelProvider;
|
||||
var channel_provider = provider as HostChannelProvider;
|
||||
if (channel_provider == null)
|
||||
throw new Error.NOT_SUPPORTED ("Channels are not supported by this device");
|
||||
|
||||
@@ -1249,6 +1259,38 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public async Service open_service (string address, Cancellable? cancellable = null) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
var service_provider = provider as HostServiceProvider;
|
||||
if (service_provider == null)
|
||||
throw new Error.NOT_SUPPORTED ("Services are not supported by this device");
|
||||
|
||||
var service = yield service_provider.open_service (address, cancellable);
|
||||
service.close.connect (on_service_closed);
|
||||
services.add (service);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
public Service open_service_sync (string address, Cancellable? cancellable = null) throws Error, IOError {
|
||||
var task = create<OpenServiceTask> ();
|
||||
task.address = address;
|
||||
return task.execute (cancellable);
|
||||
}
|
||||
|
||||
private class OpenServiceTask : DeviceTask<Service> {
|
||||
public string address;
|
||||
|
||||
protected override async Service perform_operation () throws Error, IOError {
|
||||
return yield parent.open_service (address, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_service_closed (Service service) {
|
||||
services.remove (service);
|
||||
}
|
||||
|
||||
public async void unpair (Cancellable? cancellable = null) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
@@ -1274,7 +1316,7 @@ namespace Frida {
|
||||
throw new Error.INVALID_OPERATION ("Device is gone");
|
||||
}
|
||||
|
||||
public async HostSession get_host_session (Cancellable? cancellable) throws Error, IOError {
|
||||
internal async HostSession get_host_session (Cancellable? cancellable) throws Error, IOError {
|
||||
while (host_session_request != null) {
|
||||
try {
|
||||
return yield host_session_request.future.wait_async (cancellable);
|
||||
@@ -1302,16 +1344,6 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public HostSession get_host_session_sync (Cancellable? cancellable = null) throws Error, IOError {
|
||||
return create<GetHostSessionTask> ().execute (cancellable);
|
||||
}
|
||||
|
||||
private class GetHostSessionTask : DeviceTask<HostSession> {
|
||||
protected override async HostSession perform_operation () throws Error, IOError {
|
||||
return yield parent.get_host_session (cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
private void on_host_session_detached (HostSession session) {
|
||||
if (session != current_host_session)
|
||||
return;
|
||||
@@ -1357,6 +1389,10 @@ namespace Frida {
|
||||
close_request = new Promise<bool> ();
|
||||
|
||||
try {
|
||||
foreach (var service in services.to_array ())
|
||||
yield service.cancel (cancellable);
|
||||
services.clear ();
|
||||
|
||||
while (!pending_detach_requests.is_empty) {
|
||||
var iterator = pending_detach_requests.entries.iterator ();
|
||||
iterator.next ();
|
||||
@@ -1422,7 +1458,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public async void _release_session (Session session, bool may_block, Cancellable? cancellable) throws IOError {
|
||||
internal async void _release_session (Session session, bool may_block, Cancellable? cancellable) throws IOError {
|
||||
AgentSessionId? session_id = null;
|
||||
foreach (var entry in agent_sessions.entries) {
|
||||
if (entry.value == session) {
|
||||
@@ -1964,6 +2000,68 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public interface Service : Object {
|
||||
public signal void close ();
|
||||
public signal void message (Variant message);
|
||||
|
||||
public abstract bool is_closed ();
|
||||
|
||||
public abstract async void activate (Cancellable? cancellable = null) throws Error, IOError;
|
||||
|
||||
public void activate_sync (Cancellable? cancellable = null) throws Error, IOError {
|
||||
create<ActivateTask> ().execute (cancellable);
|
||||
}
|
||||
|
||||
private class ActivateTask : ServiceTask<void> {
|
||||
protected override async void perform_operation () throws Error, IOError {
|
||||
yield parent.activate (cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract async void cancel (Cancellable? cancellable = null) throws IOError;
|
||||
|
||||
public void cancel_sync (Cancellable? cancellable = null) throws IOError {
|
||||
try {
|
||||
create<CancelTask> ().execute (cancellable);
|
||||
} catch (Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
private class CancelTask : ServiceTask<void> {
|
||||
protected override async void perform_operation () throws IOError {
|
||||
yield parent.cancel (cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract async Variant request (Variant parameters, Cancellable? cancellable = null) throws Error, IOError;
|
||||
|
||||
public Variant request_sync (Variant parameters, Cancellable? cancellable = null) throws Error, IOError {
|
||||
var task = create<RequestTask> () as RequestTask;
|
||||
task.parameters = parameters;
|
||||
return task.execute (cancellable);
|
||||
}
|
||||
|
||||
private class RequestTask : ServiceTask<Variant> {
|
||||
public Variant parameters;
|
||||
|
||||
protected override async Variant perform_operation () throws Error, IOError {
|
||||
return yield parent.request (parameters, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
private T create<T> () {
|
||||
return Object.new (typeof (T), parent: this);
|
||||
}
|
||||
|
||||
private abstract class ServiceTask<T> : AsyncTask<T> {
|
||||
public weak Service parent {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Session : Object, AgentMessageSink {
|
||||
public signal void detached (SessionDetachReason reason, Crash? crash);
|
||||
|
||||
@@ -1972,26 +2070,13 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public AgentSessionId id {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public AgentSession session {
|
||||
get {
|
||||
return active_session;
|
||||
}
|
||||
}
|
||||
|
||||
public uint persist_timeout {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public weak Device device {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
private AgentSessionId id;
|
||||
private unowned Device device;
|
||||
|
||||
private State state = ATTACHED;
|
||||
private Promise<bool> close_request;
|
||||
@@ -2029,12 +2114,10 @@ namespace Frida {
|
||||
}
|
||||
|
||||
internal Session (Device device, uint pid, AgentSessionId id, SessionOptions options) {
|
||||
Object (
|
||||
pid: pid,
|
||||
id: id,
|
||||
persist_timeout: options.persist_timeout,
|
||||
device: device
|
||||
);
|
||||
Object (pid: pid, persist_timeout: options.persist_timeout);
|
||||
|
||||
this.id = id;
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
public bool is_detached () {
|
||||
@@ -2090,7 +2173,7 @@ namespace Frida {
|
||||
|
||||
uint last_tx_batch_id;
|
||||
try {
|
||||
yield session.resume (last_rx_batch_id, cancellable, out last_tx_batch_id);
|
||||
yield active_session.resume (last_rx_batch_id, cancellable, out last_tx_batch_id);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -2122,7 +2205,7 @@ namespace Frida {
|
||||
check_open ();
|
||||
|
||||
try {
|
||||
yield session.enable_child_gating (cancellable);
|
||||
yield active_session.enable_child_gating (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -2142,7 +2225,7 @@ namespace Frida {
|
||||
check_open ();
|
||||
|
||||
try {
|
||||
yield session.disable_child_gating (cancellable);
|
||||
yield active_session.disable_child_gating (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -2166,7 +2249,7 @@ namespace Frida {
|
||||
|
||||
AgentScriptId script_id;
|
||||
try {
|
||||
script_id = yield session.create_script (source, raw_options, cancellable);
|
||||
script_id = yield active_session.create_script (source, raw_options, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -2204,7 +2287,7 @@ namespace Frida {
|
||||
|
||||
AgentScriptId script_id;
|
||||
try {
|
||||
script_id = yield session.create_script_from_bytes (bytes.get_data (), raw_options,
|
||||
script_id = yield active_session.create_script_from_bytes (bytes.get_data (), raw_options,
|
||||
cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
@@ -2243,7 +2326,7 @@ namespace Frida {
|
||||
|
||||
uint8[] data;
|
||||
try {
|
||||
data = yield session.compile_script (source, raw_options, cancellable);
|
||||
data = yield active_session.compile_script (source, raw_options, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -2276,7 +2359,7 @@ namespace Frida {
|
||||
|
||||
uint8[] data;
|
||||
try {
|
||||
data = yield session.snapshot_script (embed_script, raw_options, cancellable);
|
||||
data = yield active_session.snapshot_script (embed_script, raw_options, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -2680,7 +2763,7 @@ namespace Frida {
|
||||
|
||||
PortalMembershipId membership_id;
|
||||
try {
|
||||
membership_id = yield session.join_portal (address, raw_options, cancellable);
|
||||
membership_id = yield active_session.join_portal (address, raw_options, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -2842,7 +2925,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public void _release_script (AgentScriptId script_id) {
|
||||
internal void _release_script (AgentScriptId script_id) {
|
||||
var script_did_exist = scripts.unset (script_id);
|
||||
assert (script_did_exist);
|
||||
}
|
||||
@@ -2908,7 +2991,7 @@ namespace Frida {
|
||||
|
||||
private async void close_session_and_peer_connection (Cancellable? cancellable) throws IOError {
|
||||
try {
|
||||
yield session.close (cancellable);
|
||||
yield active_session.close (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED) {
|
||||
discard_peer_connection ();
|
||||
@@ -2931,6 +3014,10 @@ namespace Frida {
|
||||
}
|
||||
#endif
|
||||
|
||||
public DBusConnection _get_connection () {
|
||||
return ((DBusProxy) active_session).g_connection;
|
||||
}
|
||||
|
||||
private T create<T> () {
|
||||
return Object.new (typeof (T), parent: this);
|
||||
}
|
||||
@@ -2947,22 +3034,18 @@ namespace Frida {
|
||||
public signal void destroyed ();
|
||||
public signal void message (string json, Bytes? data);
|
||||
|
||||
public AgentScriptId id {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public weak Session session {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
private AgentScriptId id;
|
||||
private unowned Session session;
|
||||
|
||||
private Promise<bool> close_request;
|
||||
|
||||
private Gum.InspectorServer? inspector_server;
|
||||
|
||||
internal Script (Session session, AgentScriptId script_id) {
|
||||
Object (id: script_id, session: session);
|
||||
Object ();
|
||||
|
||||
this.id = script_id;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public bool is_destroyed () {
|
||||
@@ -2973,7 +3056,7 @@ namespace Frida {
|
||||
check_open ();
|
||||
|
||||
try {
|
||||
yield session.session.load_script (id, cancellable);
|
||||
yield session.active_session.load_script (id, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -3009,7 +3092,7 @@ namespace Frida {
|
||||
check_open ();
|
||||
|
||||
try {
|
||||
yield session.session.eternalize_script (id, cancellable);
|
||||
yield session.active_session.eternalize_script (id, cancellable);
|
||||
|
||||
yield _do_close (false, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
@@ -3060,7 +3143,7 @@ namespace Frida {
|
||||
inspector_server.message.connect (on_debugger_message_from_frontend);
|
||||
|
||||
try {
|
||||
yield session.session.enable_debugger (id, cancellable);
|
||||
yield session.active_session.enable_debugger (id, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
inspector_server = null;
|
||||
|
||||
@@ -3074,7 +3157,7 @@ namespace Frida {
|
||||
inspector_server = null;
|
||||
|
||||
try {
|
||||
yield session.session.disable_debugger (id, cancellable);
|
||||
yield session.active_session.disable_debugger (id, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
|
||||
@@ -3108,7 +3191,7 @@ namespace Frida {
|
||||
inspector_server = null;
|
||||
|
||||
try {
|
||||
yield session.session.disable_debugger (id, cancellable);
|
||||
yield session.active_session.disable_debugger (id, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -3162,7 +3245,7 @@ namespace Frida {
|
||||
|
||||
if (may_block) {
|
||||
try {
|
||||
yield parent.session.destroy_script (id, cancellable);
|
||||
yield parent.active_session.destroy_script (id, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
@@ -3185,23 +3268,19 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public class PortalMembership : Object {
|
||||
public uint id {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Session session {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
private uint id;
|
||||
private Session session;
|
||||
|
||||
internal PortalMembership (Session session, PortalMembershipId membership_id) {
|
||||
Object (id: membership_id.handle, session: session);
|
||||
Object ();
|
||||
|
||||
this.id = membership_id.handle;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public async void terminate (Cancellable? cancellable = null) throws Error, IOError {
|
||||
try {
|
||||
yield session.session.leave_portal (PortalMembershipId (id), cancellable);
|
||||
yield session.active_session.leave_portal (PortalMembershipId (id), cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ static const FridaFruityModel fruity_models[] =
|
||||
};
|
||||
|
||||
void
|
||||
_frida_fruity_host_session_backend_extract_details_for_device (gint product_id, const char * udid, char ** name, GVariant ** icon, GError ** error)
|
||||
_frida_fruity_usbmux_backend_extract_details_for_device (gint product_id, const char * udid, char ** name, GVariant ** icon,
|
||||
GError ** error)
|
||||
{
|
||||
const FridaFruityModel * model;
|
||||
guint i;
|
||||
@@ -0,0 +1,426 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public class LinuxFruitFinder : Object, FruitFinder {
|
||||
public string? udid_from_iface (string ifname) throws Error {
|
||||
var net = "/sys/class/net";
|
||||
|
||||
var directory = File.new_build_filename (net, ifname);
|
||||
if (!directory.query_exists ())
|
||||
return null;
|
||||
|
||||
try {
|
||||
var info = directory.query_info ("standard::*", 0);
|
||||
if (!info.get_is_symlink ())
|
||||
return null;
|
||||
var dev_path = Path.build_filename (net, info.get_symlink_target ());
|
||||
|
||||
var iface = File.new_build_filename (dev_path, "..", "..", "interface");
|
||||
if (!iface.query_exists ())
|
||||
return null;
|
||||
var iface_stream = new DataInputStream (iface.read ());
|
||||
string iface_name = iface_stream.read_line ();
|
||||
if (iface_name != "NCM Control")
|
||||
return null;
|
||||
|
||||
var serial = File.new_build_filename (dev_path, "..", "..", "..", "serial");
|
||||
if (!serial.query_exists ())
|
||||
return null;
|
||||
|
||||
var serial_stream = new DataInputStream (serial.read ());
|
||||
return UsbDevice.udid_from_serial_number (serial_stream.read_line ());
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LinuxPairingBrowser : Object, PairingBrowser {
|
||||
private Resolved.Manager? resolved;
|
||||
private Source? timer;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
private const size_t IF_NAMESIZE = 16;
|
||||
|
||||
public async void start (Cancellable? cancellable) throws IOError {
|
||||
try {
|
||||
var connection = yield GLib.Bus.get (BusType.SYSTEM, cancellable);
|
||||
|
||||
resolved = yield connection.get_proxy (Resolved.SERVICE_NAME, Resolved.SERVICE_PATH, DO_NOT_LOAD_PROPERTIES,
|
||||
cancellable);
|
||||
|
||||
try {
|
||||
yield resolve_services (MDNS_IPV6 | NO_NETWORK, cancellable);
|
||||
schedule_next_poll ();
|
||||
} catch (Error e) {
|
||||
handle_poll_timer_tick.begin ();
|
||||
}
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
public async void stop (Cancellable? cancellable) throws IOError {
|
||||
if (timer != null) {
|
||||
timer.destroy ();
|
||||
timer = null;
|
||||
}
|
||||
|
||||
io_cancellable.cancel ();
|
||||
}
|
||||
|
||||
private void schedule_next_poll () {
|
||||
timer = new TimeoutSource.seconds (5);
|
||||
timer.set_callback (() => {
|
||||
timer = null;
|
||||
handle_poll_timer_tick.begin ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
timer.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
|
||||
private async void handle_poll_timer_tick () {
|
||||
try {
|
||||
yield resolve_services (MDNS_IPV6, io_cancellable);
|
||||
schedule_next_poll ();
|
||||
} catch (GLib.Error e) {
|
||||
if (!(e is IOError.CANCELLED))
|
||||
schedule_next_poll ();
|
||||
}
|
||||
}
|
||||
|
||||
private async void resolve_services (Resolved.Flags flags, Cancellable? cancellable) throws Error, IOError {
|
||||
Resolved.RRItem[] items;
|
||||
uint64 ptr_flags;
|
||||
try {
|
||||
yield resolved.resolve_record (Resolved.ANY_INTERFACE, PairingService.DNS_SD_NAME, DnsRecordClass.IN,
|
||||
DnsRecordType.PTR, flags, cancellable, out items, out ptr_flags);
|
||||
} catch (GLib.Error e) {
|
||||
throw (Error) parse_error (e);
|
||||
}
|
||||
|
||||
var promises = new Gee.ArrayQueue<Promise<PairingServiceDetails?>> ();
|
||||
foreach (var item in items) {
|
||||
var pr = new DnsPacketReader (new Bytes (item.data));
|
||||
DnsPtrRecord ptr = pr.read_ptr ();
|
||||
var promise = new Promise<PairingServiceDetails> ();
|
||||
resolve_service.begin (ptr, item.ifindex, flags, cancellable, promise);
|
||||
promises.offer (promise);
|
||||
}
|
||||
|
||||
Promise<PairingServiceDetails?>? p;
|
||||
while ((p = promises.poll ()) != null) {
|
||||
try {
|
||||
yield p.future.wait_async (cancellable);
|
||||
} catch (Error e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void resolve_service (DnsPtrRecord ptr, int32 ifindex, Resolved.Flags flags, Cancellable? cancellable,
|
||||
Promise<PairingServiceDetails?> promise) {
|
||||
try {
|
||||
PairingServiceDetails? service = yield do_resolve_service (ptr, ifindex, flags, cancellable);
|
||||
promise.resolve (service);
|
||||
} catch (GLib.Error e) {
|
||||
promise.reject (e);
|
||||
}
|
||||
}
|
||||
|
||||
private async PairingServiceDetails? do_resolve_service (DnsPtrRecord ptr, int32 ifindex, Resolved.Flags flags,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Resolved.SrvItem[] srv_items;
|
||||
Variant txt_items;
|
||||
string canonical_name;
|
||||
string canonical_type;
|
||||
string canonical_domain;
|
||||
uint64 srv_flags;
|
||||
try {
|
||||
yield resolved.resolve_service (ifindex, "", "", ptr.name, Posix.AF_INET6, flags, cancellable,
|
||||
out srv_items, out txt_items, out canonical_name, out canonical_type, out canonical_domain,
|
||||
out srv_flags);
|
||||
} catch (GLib.Error e) {
|
||||
throw (Error) parse_error (e);
|
||||
}
|
||||
|
||||
var txt_record = new Gee.ArrayList<string> ();
|
||||
foreach (var raw_item in txt_items) {
|
||||
string item = ((string *) raw_item.get_data ())->substring (0, (long) raw_item.get_size ());
|
||||
if (!item.validate ())
|
||||
throw new Error.PROTOCOL ("Invalid TXT item");
|
||||
txt_record.add (item);
|
||||
}
|
||||
|
||||
var meta = PairingServiceMetadata.from_txt_record (txt_record);
|
||||
var ip = new InetAddress.from_bytes (srv_items[0].addresses[0].ip, IPV6);
|
||||
var service = new PairingServiceDetails () {
|
||||
identifier = meta.identifier,
|
||||
auth_tag = meta.auth_tag,
|
||||
endpoint = (InetSocketAddress) Object.new (typeof (InetSocketAddress),
|
||||
address: ip,
|
||||
port: srv_items[0].port,
|
||||
scope_id: ip.get_is_link_local () ? ifindex : 0
|
||||
),
|
||||
interface_address = resolve_interface_address (ifindex),
|
||||
};
|
||||
|
||||
service_discovered (service);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
private static InetSocketAddress resolve_interface_address (int32 ifindex) throws Error {
|
||||
char ifname_buf[IF_NAMESIZE];
|
||||
unowned string? ifname = Linux.Network.if_indextoname (ifindex, (string) ifname_buf);
|
||||
if (ifname == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Unable to resolve interface name");
|
||||
|
||||
Linux.Network.IfAddrs ifaddrs;
|
||||
Linux.Network.getifaddrs (out ifaddrs);
|
||||
for (unowned Linux.Network.IfAddrs candidate = ifaddrs; candidate != null; candidate = candidate.ifa_next) {
|
||||
unowned Posix.SockAddr? address = candidate.ifa_addr;
|
||||
if (address == null || address.sa_family != Posix.AF_INET6)
|
||||
continue;
|
||||
|
||||
if (candidate.ifa_name != ifname)
|
||||
continue;
|
||||
|
||||
return (InetSocketAddress) SocketAddress.from_native ((void *) address, sizeof (Posix.SockAddrIn6));
|
||||
}
|
||||
|
||||
throw new Error.NOT_SUPPORTED ("Unable to resolve interface address");
|
||||
}
|
||||
|
||||
private static GLib.Error parse_error (GLib.Error e) {
|
||||
if (e is Error || e is IOError.CANCELLED)
|
||||
return e;
|
||||
return new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Network {
|
||||
public async void wait_until_interfaces_ready (Gee.Collection<string> interface_names, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
try {
|
||||
var connection = yield GLib.Bus.get (BusType.SYSTEM, cancellable);
|
||||
|
||||
NetworkManager.Service? nm = null;
|
||||
Networkd.Service? netd = null;
|
||||
if (yield system_has_service (NetworkManager.SERVICE_NAME, connection, cancellable)) {
|
||||
nm = yield connection.get_proxy (NetworkManager.SERVICE_NAME, NetworkManager.SERVICE_PATH,
|
||||
DO_NOT_LOAD_PROPERTIES, cancellable);
|
||||
} else if (yield system_has_service (Networkd.SERVICE_NAME, connection, cancellable)) {
|
||||
netd = yield connection.get_proxy (Networkd.SERVICE_NAME, Networkd.SERVICE_PATH,
|
||||
DO_NOT_LOAD_PROPERTIES, cancellable);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
var remaining = interface_names.size + 1;
|
||||
|
||||
NotifyCompleteFunc on_complete = () => {
|
||||
remaining--;
|
||||
if (remaining == 0)
|
||||
wait_until_interfaces_ready.callback ();
|
||||
};
|
||||
|
||||
foreach (var name in interface_names) {
|
||||
if (nm != null) {
|
||||
NetworkManager.wait_until_interface_ready.begin (name, nm, connection, cancellable,
|
||||
on_complete);
|
||||
} else {
|
||||
Networkd.wait_until_interface_ready.begin (name, netd, connection, cancellable,
|
||||
on_complete);
|
||||
}
|
||||
}
|
||||
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
on_complete ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
|
||||
yield;
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private async bool system_has_service (string name, DBusConnection connection, Cancellable? cancellable) throws GLib.Error {
|
||||
var v = yield connection.call (
|
||||
"org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
|
||||
"NameHasOwner",
|
||||
new Variant.tuple ({ name }),
|
||||
new VariantType.tuple ({ VariantType.BOOLEAN }),
|
||||
DBusCallFlags.NONE, -1, cancellable);
|
||||
|
||||
bool has_owner;
|
||||
v.get ("(b)", out has_owner);
|
||||
return has_owner;
|
||||
}
|
||||
}
|
||||
|
||||
private delegate void NotifyCompleteFunc ();
|
||||
|
||||
namespace NetworkManager {
|
||||
private async void wait_until_interface_ready (string name, Service service, DBusConnection connection,
|
||||
Cancellable? cancellable, NotifyCompleteFunc on_complete) {
|
||||
try {
|
||||
string device_path = yield service.get_device_by_ip_iface (name);
|
||||
|
||||
Device device = yield connection.get_proxy (SERVICE_NAME, device_path, DBusProxyFlags.NONE, cancellable);
|
||||
|
||||
var device_proxy = (DBusProxy) device;
|
||||
|
||||
ulong handler = device_proxy.g_properties_changed.connect ((changed, invalidated) => {
|
||||
if (changed.lookup_value ("StateReason", null) != null)
|
||||
wait_until_interface_ready.callback ();
|
||||
});
|
||||
|
||||
while (!cancellable.is_cancelled ()) {
|
||||
uint32 state, reason;
|
||||
device_proxy.get_cached_property ("StateReason").get ("(uu)", out state, out reason);
|
||||
if (state == DEVICE_STATE_ACTIVATED)
|
||||
break;
|
||||
if (state == DEVICE_STATE_DISCONNECTED && reason != DEVICE_STATE_REASON_NONE)
|
||||
break;
|
||||
yield;
|
||||
}
|
||||
|
||||
device_proxy.disconnect (handler);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
|
||||
on_complete ();
|
||||
}
|
||||
|
||||
private const string SERVICE_NAME = "org.freedesktop.NetworkManager";
|
||||
private const string SERVICE_PATH = "/org/freedesktop/NetworkManager";
|
||||
|
||||
[DBus (name = "org.freedesktop.NetworkManager")]
|
||||
private interface Service : Object {
|
||||
public abstract async string get_device_by_ip_iface (string iface) throws GLib.Error;
|
||||
}
|
||||
|
||||
[DBus (name = "org.freedesktop.NetworkManager.Device")]
|
||||
private interface Device : Object {
|
||||
}
|
||||
|
||||
private const uint32 DEVICE_STATE_DISCONNECTED = 30;
|
||||
private const uint32 DEVICE_STATE_ACTIVATED = 100;
|
||||
|
||||
private const uint32 DEVICE_STATE_REASON_NONE = 0;
|
||||
}
|
||||
|
||||
namespace Networkd {
|
||||
private async void wait_until_interface_ready (string name, Service service, DBusConnection connection,
|
||||
Cancellable? cancellable, NotifyCompleteFunc on_complete) {
|
||||
try {
|
||||
int32 ifindex;
|
||||
string link_path;
|
||||
yield service.get_link_by_name (name, out ifindex, out link_path);
|
||||
|
||||
Link link = yield connection.get_proxy (SERVICE_NAME, link_path, DBusProxyFlags.NONE, cancellable);
|
||||
|
||||
var link_proxy = (DBusProxy) link;
|
||||
|
||||
ulong handler = link_proxy.g_properties_changed.connect ((changed, invalidated) => {
|
||||
wait_until_interface_ready.callback ();
|
||||
});
|
||||
|
||||
while (!cancellable.is_cancelled ()) {
|
||||
string operational_state;
|
||||
link_proxy.get_cached_property ("OperationalState").get ("s", out operational_state);
|
||||
if (operational_state != "carrier")
|
||||
break;
|
||||
yield;
|
||||
}
|
||||
|
||||
link_proxy.disconnect (handler);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
|
||||
on_complete ();
|
||||
}
|
||||
|
||||
private const string SERVICE_NAME = "org.freedesktop.network1";
|
||||
private const string SERVICE_PATH = "/org/freedesktop/network1";
|
||||
|
||||
[DBus (name = "org.freedesktop.network1.Manager")]
|
||||
private interface Service : Object {
|
||||
public abstract async void get_link_by_name (string name, out int32 ifindex, out string path) throws GLib.Error;
|
||||
}
|
||||
|
||||
[DBus (name = "org.freedesktop.network1.Link")]
|
||||
private interface Link : Object {
|
||||
}
|
||||
}
|
||||
|
||||
namespace Resolved {
|
||||
public const string SERVICE_NAME = "org.freedesktop.resolve1";
|
||||
public const string SERVICE_PATH = "/org/freedesktop/resolve1";
|
||||
|
||||
public const int32 ANY_INTERFACE = 0;
|
||||
|
||||
[DBus (name = "org.freedesktop.resolve1.Manager")]
|
||||
public interface Manager : Object {
|
||||
public abstract async void resolve_record (int32 ifindex, string name, uint16 klass, uint16 type, uint64 flags,
|
||||
Cancellable? cancellable, out RRItem[] items, out uint64 result_flags) throws GLib.Error;
|
||||
public abstract async void resolve_service (int32 ifindex, string name, string type, string domain, int32 family,
|
||||
uint64 flags, Cancellable? cancellable, out SrvItem[] srv_items,
|
||||
[DBus (signature = "aay")] out Variant txt_items, out string canonical_name, out string canonical_type,
|
||||
out string canonical_domain, out uint64 result_flags) throws GLib.Error;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Flags {
|
||||
DNS = 1 << 0,
|
||||
LLMNR_IPV4 = 1 << 1,
|
||||
LLMNR_IPV6 = 1 << 2,
|
||||
MDNS_IPV4 = 1 << 3,
|
||||
MDNS_IPV6 = 1 << 4,
|
||||
NO_CNAME = 1 << 5,
|
||||
NO_TXT = 1 << 6,
|
||||
NO_ADDRESS = 1 << 7,
|
||||
NO_SEARCH = 1 << 8,
|
||||
AUTHENTICATED = 1 << 9,
|
||||
NO_VALIDATE = 1 << 10,
|
||||
NO_SYNTHESIZE = 1 << 11,
|
||||
NO_CACHE = 1 << 12,
|
||||
NO_ZONE = 1 << 13,
|
||||
NO_TRUST_ANCHOR = 1 << 14,
|
||||
NO_NETWORK = 1 << 15,
|
||||
REQUIRE_PRIMARY = 1 << 16,
|
||||
CLAMP_TTL = 1 << 17,
|
||||
CONFIDENTIAL = 1 << 18,
|
||||
SYNTHETIC = 1 << 19,
|
||||
FROM_CACHE = 1 << 20,
|
||||
FROM_ZONE = 1 << 21,
|
||||
FROM_TRUST_ANCHOR = 1 << 22,
|
||||
FROM_NETWORK = 1 << 23,
|
||||
NO_STALE = 1 << 24,
|
||||
RELAX_SINGLE_LABEL = 1 << 25,
|
||||
}
|
||||
|
||||
public struct RRItem {
|
||||
public int32 ifindex;
|
||||
public uint16 klass;
|
||||
public uint16 type;
|
||||
public uint8[] data;
|
||||
}
|
||||
|
||||
public struct SrvItem {
|
||||
public uint16 priority;
|
||||
public uint16 weight;
|
||||
public uint16 port;
|
||||
public string name;
|
||||
public SrvAddress[] addresses;
|
||||
public string canonical_name;
|
||||
}
|
||||
|
||||
public struct SrvAddress {
|
||||
public int32 ifindex;
|
||||
public int32 family;
|
||||
public uint8[] ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,610 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public sealed class MacOSCoreDeviceBackend : Object, Backend {
|
||||
private Gee.Map<string, MacOSCoreDeviceTransport> transports = new Gee.HashMap<string, MacOSCoreDeviceTransport> ();
|
||||
|
||||
private Promise<bool> all_current_devices_listed = new Promise<bool> ();
|
||||
private Promise<bool> browse_request = new Promise<bool> ();
|
||||
|
||||
private XpcClient? pairingd;
|
||||
private Darwin.GCD.DispatchQueue queue =
|
||||
new Darwin.GCD.DispatchQueue ("re.frida.fruity.remotepairing", Darwin.GCD.DispatchQueueAttr.SERIAL);
|
||||
|
||||
public async void start (Cancellable? cancellable) throws IOError {
|
||||
pairingd = XpcClient.make_for_mach_service ("com.apple.CoreDevice.remotepairingd", queue);
|
||||
pairingd.notify["state"].connect (on_state_changed);
|
||||
pairingd.message.connect (on_message);
|
||||
|
||||
do_browse.begin ();
|
||||
|
||||
try {
|
||||
yield all_current_devices_listed.future.wait_async (cancellable);
|
||||
} catch (Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private async void do_browse () {
|
||||
try {
|
||||
var r = new PairingdRequest ("RemotePairing.BrowseRequest");
|
||||
r.body.set_bool ("currentDevicesOnly", false);
|
||||
yield pairingd.request (r.message, null);
|
||||
browse_request.resolve (true);
|
||||
} catch (GLib.Error e) {
|
||||
browse_request.reject (e);
|
||||
}
|
||||
}
|
||||
|
||||
public async void stop (Cancellable? cancellable) throws IOError {
|
||||
}
|
||||
|
||||
private void on_state_changed (Object obj, ParamSpec pspec) {
|
||||
if (pairingd.state == CLOSED && !all_current_devices_listed.future.ready) {
|
||||
all_current_devices_listed.reject (
|
||||
new Error.TRANSPORT ("Connection closed while waiting for initial device list"));
|
||||
}
|
||||
}
|
||||
|
||||
private void on_message (Darwin.Xpc.Object obj) {
|
||||
var reader = new XpcObjectReader (obj);
|
||||
try {
|
||||
reader.read_member ("mangledTypeName");
|
||||
if (reader.get_string_value () == "RemotePairing.ServiceEvent") {
|
||||
reader
|
||||
.end_member ()
|
||||
.read_member ("value");
|
||||
if (reader.try_read_member ("deviceFound")) {
|
||||
reader
|
||||
.read_member ("_0")
|
||||
.read_member ("deviceInfo");
|
||||
|
||||
var udid = reader.read_member ("udid").get_string_value ();
|
||||
reader.end_member ();
|
||||
if (transports.has_key (udid))
|
||||
return;
|
||||
|
||||
var device_info = (Darwin.Xpc.Dictionary) reader.current_object;
|
||||
|
||||
var pairing_device = new XpcClient (device_info.create_connection ("endpoint"), queue);
|
||||
|
||||
on_device_found (udid, pairing_device, device_info);
|
||||
} else if (reader.try_read_member ("allCurrentDevicesListed")) {
|
||||
all_current_devices_listed.resolve (true);
|
||||
}
|
||||
}
|
||||
} catch (Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private void on_device_found (string udid, XpcClient pairing_device, Darwin.Xpc.Dictionary device_info) throws Error {
|
||||
var transport = new MacOSCoreDeviceTransport (udid, pairing_device, device_info);
|
||||
transport.closed.connect (on_transport_closed);
|
||||
transports[udid] = transport;
|
||||
transport_attached (transport);
|
||||
}
|
||||
|
||||
private void on_transport_closed (MacOSCoreDeviceTransport transport) {
|
||||
transport_detached (transport);
|
||||
transports.unset (transport.udid);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MacOSCoreDeviceTransport : Object, Transport {
|
||||
public signal void closed ();
|
||||
|
||||
public XpcClient pairing_device {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public ConnectionType connection_type {
|
||||
get {
|
||||
return _connection_type;
|
||||
}
|
||||
}
|
||||
|
||||
public string udid {
|
||||
get {
|
||||
return _udid;
|
||||
}
|
||||
}
|
||||
|
||||
public string? name {
|
||||
get {
|
||||
return _name;
|
||||
}
|
||||
}
|
||||
|
||||
public Variant? icon {
|
||||
get {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public UsbmuxDevice? usbmux_device {
|
||||
get {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ConnectionType _connection_type;
|
||||
private string _udid;
|
||||
private string _name;
|
||||
private Bytes? _remote_unlock_host_key;
|
||||
|
||||
private Promise<Tunnel>? tunnel_request;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public MacOSCoreDeviceTransport (string udid, XpcClient pairing_device, Darwin.Xpc.Dictionary device_info) throws Error {
|
||||
Object (pairing_device: pairing_device);
|
||||
_udid = udid;
|
||||
update_device_info (device_info);
|
||||
}
|
||||
|
||||
construct {
|
||||
pairing_device.notify["state"].connect (on_state_changed);
|
||||
pairing_device.message.connect (on_message);
|
||||
}
|
||||
|
||||
private void update_device_info (Darwin.Xpc.Dictionary device_info) throws Error {
|
||||
_connection_type = (device_info.get_value ("untrustedRSDDeviceInfo") != null)
|
||||
? ConnectionType.USB
|
||||
: ConnectionType.NETWORK;
|
||||
|
||||
var reader = new XpcObjectReader (device_info);
|
||||
_name = reader.read_member ("name").get_string_value ();
|
||||
reader.end_member ();
|
||||
|
||||
if (reader.has_member ("remoteUnlockHostKey")) {
|
||||
_remote_unlock_host_key = new Bytes (reader.read_member ("remoteUnlockHostKey").get_data_value ());
|
||||
reader.end_member ();
|
||||
} else {
|
||||
_remote_unlock_host_key = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void on_state_changed (Object obj, ParamSpec pspec) {
|
||||
if (pairing_device.state == CLOSED) {
|
||||
io_cancellable.cancel ();
|
||||
|
||||
closed ();
|
||||
}
|
||||
}
|
||||
|
||||
private void on_message (Darwin.Xpc.Object obj) {
|
||||
var reader = new XpcObjectReader (obj);
|
||||
try {
|
||||
reader.read_member ("mangledTypeName");
|
||||
if (reader.get_string_value () == "RemotePairing.ServiceEvent") {
|
||||
reader
|
||||
.end_member ()
|
||||
.read_member ("value");
|
||||
if (reader.try_read_member ("deviceFound")) {
|
||||
reader
|
||||
.read_member ("_0")
|
||||
.read_member ("deviceInfo");
|
||||
var device_info = (Darwin.Xpc.Dictionary)
|
||||
reader.get_object_value (Darwin.Xpc.Dictionary.TYPE);
|
||||
update_device_info (device_info);
|
||||
} else if (reader.try_read_member ("tunnelUsageAssertionsInvalidated")) {
|
||||
reader.read_member ("assertionIdentifiers");
|
||||
var ids = new Gee.ArrayList<Bytes> ();
|
||||
size_t n = reader.count_elements ();
|
||||
for (size_t i = 0; i != n; i++) {
|
||||
reader.read_element (i);
|
||||
ids.add (new Bytes (reader.get_uuid_value ()));
|
||||
reader.end_element ();
|
||||
}
|
||||
on_tunnel_usage_assertions_invalidated.begin (ids);
|
||||
}
|
||||
}
|
||||
} catch (Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private async void on_tunnel_usage_assertions_invalidated (Gee.List<Bytes> ids) {
|
||||
if (tunnel_request == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
var tunnel = (MacOSTunnel) yield tunnel_request.future.wait_async (io_cancellable);
|
||||
unowned uint8[] tunnel_assertion_id = tunnel.assertion_identifier.get_bytes ();
|
||||
foreach (var id in ids) {
|
||||
if (Memory.cmp (id.get_data (), tunnel_assertion_id, id.get_size ()) == 0) {
|
||||
tunnel_request = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
public async Tunnel? find_tunnel (UsbmuxDevice? device, Cancellable? cancellable) throws Error, IOError {
|
||||
while (tunnel_request != null) {
|
||||
try {
|
||||
return yield tunnel_request.future.wait_async (cancellable);
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (IOError e) {
|
||||
cancellable.set_error_if_cancelled ();
|
||||
}
|
||||
}
|
||||
tunnel_request = new Promise<Tunnel> ();
|
||||
|
||||
try {
|
||||
var tunnel = new MacOSTunnel (pairing_device, _remote_unlock_host_key);
|
||||
yield tunnel.attach (cancellable);
|
||||
|
||||
tunnel_request.resolve (tunnel);
|
||||
|
||||
return tunnel;
|
||||
} catch (GLib.Error e) {
|
||||
tunnel_request.reject (e);
|
||||
tunnel_request = null;
|
||||
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class MacOSTunnel : Object, Tunnel {
|
||||
public DiscoveryService discovery {
|
||||
get {
|
||||
return _discovery;
|
||||
}
|
||||
}
|
||||
|
||||
public int64 opened_at {
|
||||
get {
|
||||
return _opened_at;
|
||||
}
|
||||
}
|
||||
|
||||
public Bytes? remote_unlock_host_key {
|
||||
get {
|
||||
return _remote_unlock_host_key;
|
||||
}
|
||||
}
|
||||
|
||||
public Darwin.Xpc.Uuid? assertion_identifier {
|
||||
get {
|
||||
return _assertion_identifier;
|
||||
}
|
||||
}
|
||||
|
||||
private XpcClient pairing_device;
|
||||
private Bytes? _remote_unlock_host_key;
|
||||
private Darwin.Xpc.Uuid? _assertion_identifier;
|
||||
private InetAddress? tunnel_device_address;
|
||||
private DiscoveryService? _discovery;
|
||||
private int64 _opened_at = -1;
|
||||
|
||||
public MacOSTunnel (XpcClient pairing_device, Bytes? remote_unlock_host_key) {
|
||||
this.pairing_device = pairing_device;
|
||||
_remote_unlock_host_key = remote_unlock_host_key;
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
var r = new PairingdRequest ("RemotePairing.ReleaseAssertionRequest");
|
||||
r.body.set_value ("assertionIdentifier", _assertion_identifier);
|
||||
try {
|
||||
yield pairing_device.request (r.message, cancellable);
|
||||
} catch (Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
public async void attach (Cancellable? cancellable) throws Error, IOError {
|
||||
var r = new PairingdRequest ("RemotePairing.CreateAssertionCommand");
|
||||
r.body.set_int64 ("flags", 0);
|
||||
var response = yield pairing_device.request (r.message, cancellable);
|
||||
|
||||
_opened_at = get_monotonic_time ();
|
||||
|
||||
var reader = new XpcObjectReader (response);
|
||||
reader.read_member ("response");
|
||||
|
||||
_assertion_identifier = (Darwin.Xpc.Uuid) reader
|
||||
.read_member ("assertionIdentifier")
|
||||
.get_object_value (Darwin.Xpc.Uuid.TYPE);
|
||||
reader.end_member ();
|
||||
|
||||
string tunnel_ip_address = reader
|
||||
.read_member ("info")
|
||||
.read_member ("tunnelIPAddress")
|
||||
.get_string_value ();
|
||||
tunnel_device_address = new InetAddress.from_string (tunnel_ip_address);
|
||||
|
||||
_discovery = yield locate_discovery_service (tunnel_device_address, cancellable);
|
||||
}
|
||||
|
||||
public async IOStream open_tcp_connection (uint16 port, Cancellable? cancellable) throws Error, IOError {
|
||||
SocketConnection connection;
|
||||
try {
|
||||
var service_address = new InetSocketAddress (tunnel_device_address, port);
|
||||
|
||||
var client = new SocketClient ();
|
||||
connection = yield client.connect_async (service_address, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CONNECTION_REFUSED)
|
||||
throw new Error.SERVER_NOT_RUNNING ("%s", e.message);
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
Tcp.enable_nodelay (connection.socket);
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
private static async DiscoveryService locate_discovery_service (InetAddress tunnel_device_address, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var main_context = MainContext.get_thread_default ();
|
||||
|
||||
var path_buf = new char[4096];
|
||||
unowned string path = (string) path_buf;
|
||||
|
||||
uint delays[] = { 0, 50, 250 };
|
||||
for (uint attempts = 0; attempts != delays.length; attempts++) {
|
||||
uint delay = delays[attempts];
|
||||
if (delay != 0) {
|
||||
var timeout_source = new TimeoutSource (delay);
|
||||
timeout_source.set_callback (locate_discovery_service.callback);
|
||||
timeout_source.attach (main_context);
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (locate_discovery_service.callback);
|
||||
cancel_source.attach (main_context);
|
||||
|
||||
yield;
|
||||
|
||||
cancel_source.destroy ();
|
||||
timeout_source.destroy ();
|
||||
|
||||
if (cancellable.is_cancelled ())
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var item in XNU.query_active_tcp_connections ()) {
|
||||
if (item.family != IPV6)
|
||||
continue;
|
||||
if (!item.foreign_address.equal (tunnel_device_address))
|
||||
continue;
|
||||
if (Darwin.XNU.proc_pidpath (item.effective_pid, path_buf) <= 0)
|
||||
continue;
|
||||
if (path != "/usr/libexec/remoted")
|
||||
continue;
|
||||
|
||||
try {
|
||||
var connectable = new InetSocketAddress (tunnel_device_address, item.foreign_port);
|
||||
|
||||
var sc = new SocketClient ();
|
||||
SocketConnection connection = yield sc.connect_async (connectable, cancellable);
|
||||
Tcp.enable_nodelay (connection.socket);
|
||||
|
||||
return yield DiscoveryService.open (connection, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error.NOT_SUPPORTED ("Unable to detect RSD port");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PairingdRequest {
|
||||
public Darwin.Xpc.Dictionary message = new Darwin.Xpc.Dictionary ();
|
||||
public Darwin.Xpc.Dictionary body = new Darwin.Xpc.Dictionary ();
|
||||
|
||||
public PairingdRequest (string name) {
|
||||
message.set_string ("mangledTypeName", name);
|
||||
message.set_value ("value", body);
|
||||
}
|
||||
}
|
||||
|
||||
namespace XNU {
|
||||
public PcbList query_active_tcp_connections () {
|
||||
size_t size = 0;
|
||||
Darwin.XNU.sysctlbyname ("net.inet.tcp.pcblist_n", null, &size);
|
||||
|
||||
var pcbs = new uint8[size];
|
||||
Darwin.XNU.sysctlbyname ("net.inet.tcp.pcblist_n", pcbs, &size);
|
||||
|
||||
return new PcbList (pcbs);
|
||||
}
|
||||
|
||||
public class PcbList {
|
||||
private uint8[] pcbs;
|
||||
|
||||
internal PcbList (owned uint8[] pcbs) {
|
||||
this.pcbs = (owned) pcbs;
|
||||
}
|
||||
|
||||
public Iterator iterator () {
|
||||
return new Iterator (this);
|
||||
}
|
||||
|
||||
public class Iterator {
|
||||
private PcbList list;
|
||||
private InetItem * cursor;
|
||||
|
||||
internal Iterator (PcbList list) {
|
||||
this.list = list;
|
||||
|
||||
var gen = (Darwin.XNU.InetPcbGeneration *) list.pcbs;
|
||||
cursor = (InetItem *) ((uint8 *) list.pcbs + gen->length);
|
||||
}
|
||||
|
||||
public Item? next_value () {
|
||||
InetPcb * pcb = null;
|
||||
while (true) {
|
||||
if (cursor->length == 24)
|
||||
return null;
|
||||
|
||||
switch (cursor->kind) {
|
||||
case InetItemKind.PCB:
|
||||
pcb = (InetPcb *) cursor;
|
||||
break;
|
||||
case InetItemKind.SOCKET:
|
||||
var item = new Item (*pcb, *((InetSocket *) cursor));
|
||||
advance ();
|
||||
return item;
|
||||
}
|
||||
|
||||
advance ();
|
||||
}
|
||||
}
|
||||
|
||||
private void advance () {
|
||||
uint32 l = cursor->length;
|
||||
if (l % 8 != 0)
|
||||
l += 8 - (l % 8);
|
||||
cursor = (InetItem *) ((uint8 *) cursor + l);
|
||||
}
|
||||
}
|
||||
|
||||
public class Item {
|
||||
public SocketFamily family {
|
||||
get {
|
||||
return ((pcb.version_flag & Darwin.XNU.InetVersionFlags.IPV6) != 0)
|
||||
? SocketFamily.IPV6
|
||||
: SocketFamily.IPV4;
|
||||
}
|
||||
}
|
||||
|
||||
public InetAddress local_address {
|
||||
get {
|
||||
if (cached_local_address == null)
|
||||
cached_local_address = parse_address (pcb.local_address);
|
||||
return cached_local_address;
|
||||
}
|
||||
}
|
||||
|
||||
public uint16 local_port {
|
||||
get {
|
||||
return uint16.from_big_endian (pcb.local_port);
|
||||
}
|
||||
}
|
||||
|
||||
public InetAddress foreign_address {
|
||||
get {
|
||||
if (cached_foreign_address == null)
|
||||
cached_foreign_address = parse_address (pcb.foreign_address);
|
||||
return cached_foreign_address;
|
||||
}
|
||||
}
|
||||
|
||||
public uint16 foreign_port {
|
||||
get {
|
||||
return uint16.from_big_endian (pcb.foreign_port);
|
||||
}
|
||||
}
|
||||
|
||||
public int32 effective_pid {
|
||||
get {
|
||||
return sock.effective_pid;
|
||||
}
|
||||
}
|
||||
|
||||
private InetPcb pcb;
|
||||
private InetSocket sock;
|
||||
private InetAddress? cached_local_address;
|
||||
private InetAddress? cached_foreign_address;
|
||||
|
||||
public Item (InetPcb pcb, InetSocket sock) {
|
||||
this.pcb = pcb;
|
||||
this.sock = sock;
|
||||
}
|
||||
|
||||
private InetAddress parse_address (uint8[] bytes) {
|
||||
if ((pcb.version_flag & Darwin.XNU.InetVersionFlags.IPV6) != 0)
|
||||
return new InetAddress.from_bytes (bytes, IPV6);
|
||||
var addr = (Darwin.XNU.InetAddr4in6 *) bytes;
|
||||
return new InetAddress.from_bytes ((uint8[]) &addr->addr4.s_addr, IPV4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SimpleType]
|
||||
public struct InetItem {
|
||||
public uint32 length;
|
||||
public uint32 kind;
|
||||
}
|
||||
|
||||
public enum InetItemKind {
|
||||
SOCKET = 0x001,
|
||||
PCB = 0x010,
|
||||
}
|
||||
|
||||
[SimpleType]
|
||||
public struct InetPcb {
|
||||
public uint32 length;
|
||||
public uint32 kind;
|
||||
|
||||
public uint64 inpp;
|
||||
public uint16 foreign_port;
|
||||
public uint16 local_port;
|
||||
public uint32 per_protocol_pcb_low;
|
||||
public uint32 per_protocol_pcb_high;
|
||||
public uint32 generation_count_low;
|
||||
public uint32 generation_count_high;
|
||||
public int flags;
|
||||
public uint32 flow;
|
||||
public uint8 version_flag;
|
||||
public uint8 ip_ttl;
|
||||
public uint8 ip_protocol;
|
||||
public uint8 padding;
|
||||
public uint8 foreign_address[16];
|
||||
public uint8 local_address[16];
|
||||
public InetDepend4 depend4;
|
||||
public InetDepend6 depend6;
|
||||
public uint32 flowhash;
|
||||
public uint32 flags2;
|
||||
}
|
||||
|
||||
[SimpleType]
|
||||
public struct InetSocket {
|
||||
public uint32 length;
|
||||
public uint32 kind;
|
||||
|
||||
public uint64 so;
|
||||
public int16 type;
|
||||
public uint16 options_low;
|
||||
public uint16 options_high;
|
||||
public int16 linger;
|
||||
public int16 state;
|
||||
public uint16 pcb[4];
|
||||
public uint16 protocol_low;
|
||||
public uint16 protocol_high;
|
||||
public uint16 family_low;
|
||||
public uint16 family_high;
|
||||
public int16 qlen;
|
||||
public int16 incqlen;
|
||||
public int16 qlimit;
|
||||
public int16 timeo;
|
||||
public uint16 error;
|
||||
public int32 pgid;
|
||||
public uint32 oobmark;
|
||||
public uint32 uid;
|
||||
public int32 last_pid;
|
||||
public int32 effective_pid;
|
||||
public uint64 gencnt;
|
||||
public uint32 flags;
|
||||
public uint32 flags1;
|
||||
public int32 usecount;
|
||||
public int32 retaincnt;
|
||||
public uint32 filter_flags;
|
||||
}
|
||||
|
||||
[SimpleType]
|
||||
public struct InetDepend4 {
|
||||
public uint8 ip_tos;
|
||||
}
|
||||
|
||||
[SimpleType]
|
||||
public struct InetDepend6 {
|
||||
public uint8 hlim;
|
||||
public int checksum;
|
||||
public uint16 interface_index;
|
||||
public int16 hops;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "frida-base.h"
|
||||
|
||||
void
|
||||
_frida_fruity_host_session_backend_extract_details_for_device (gint product_id, const char * udid, char ** name, GVariant ** icon,
|
||||
_frida_fruity_usbmux_backend_extract_details_for_device (gint product_id, const char * udid, char ** name, GVariant ** icon,
|
||||
GError ** error)
|
||||
{
|
||||
*name = g_strdup ("iOS Device");
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
#include "../windows/icon-helpers.h"
|
||||
|
||||
#include <setupapi.h>
|
||||
#include <devguid.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <setupapi.h>
|
||||
#include <windns.h>
|
||||
|
||||
typedef struct _FridaPairingBrowserBackend FridaPairingBrowserBackend;
|
||||
|
||||
typedef struct _FridaMobileDeviceInfo FridaMobileDeviceInfo;
|
||||
typedef struct _FridaImageDeviceInfo FridaImageDeviceInfo;
|
||||
@@ -13,8 +17,18 @@ typedef struct _FridaFindImageDeviceContext FridaFindImageDeviceContext;
|
||||
|
||||
typedef struct _FridaDeviceInfo FridaDeviceInfo;
|
||||
|
||||
typedef struct _FridaDnsApi FridaDnsApi;
|
||||
|
||||
typedef gboolean (* FridaEnumerateDeviceFunc) (const FridaDeviceInfo * device_info, gpointer user_data);
|
||||
|
||||
struct _FridaPairingBrowserBackend
|
||||
{
|
||||
FridaFruityWindowsPairingBrowserResultCallback callback;
|
||||
gpointer callback_target;
|
||||
|
||||
DNS_SERVICE_CANCEL browse_handle;
|
||||
};
|
||||
|
||||
struct _FridaMobileDeviceInfo
|
||||
{
|
||||
WCHAR * location;
|
||||
@@ -49,6 +63,14 @@ struct _FridaDeviceInfo
|
||||
PSP_DEVINFO_DATA device_info_data;
|
||||
};
|
||||
|
||||
struct _FridaDnsApi
|
||||
{
|
||||
DNS_STATUS (WINAPI * browse) (DNS_SERVICE_BROWSE_REQUEST * request, DNS_SERVICE_CANCEL * cancel);
|
||||
DNS_STATUS (WINAPI * browse_cancel) (DNS_SERVICE_CANCEL * cancel_handle);
|
||||
};
|
||||
|
||||
static void WINAPI frida_fruity_windows_pairing_browser_on_browse_results (void * query_context, DNS_QUERY_RESULT * query_results);
|
||||
|
||||
static FridaMobileDeviceInfo * find_mobile_device_by_udid (const WCHAR * udid);
|
||||
static FridaImageDeviceInfo * find_image_device_by_location (const WCHAR * location);
|
||||
|
||||
@@ -68,10 +90,110 @@ static WCHAR * frida_read_registry_string (HKEY key, WCHAR * value_name);
|
||||
static WCHAR * frida_read_registry_multi_string (HKEY key, WCHAR * value_name);
|
||||
static gpointer frida_read_registry_value (HKEY key, WCHAR * value_name, DWORD expected_type);
|
||||
|
||||
static FridaDnsApi * frida_try_get_dns_api (void);
|
||||
|
||||
static GUID GUID_APPLE_USB = { 0xF0B32BE3, 0x6678, 0x4879, { 0x92, 0x30, 0x0E4, 0x38, 0x45, 0xD8, 0x05, 0xEE } };
|
||||
|
||||
void
|
||||
_frida_fruity_host_session_backend_extract_details_for_device (gint product_id, const char * udid, char ** name, GVariant ** icon,
|
||||
_frida_fruity_windows_pairing_browser_enumerate_network_interfaces (FridaFruityWindowsPairingBrowserNetifFoundFunc func,
|
||||
gpointer func_target)
|
||||
{
|
||||
IP_ADAPTER_ADDRESSES * adapters;
|
||||
ULONG buffer_size, result;
|
||||
IP_ADAPTER_ADDRESSES_LH * adapter;
|
||||
|
||||
if (frida_try_get_dns_api () == NULL)
|
||||
return;
|
||||
|
||||
buffer_size = 32768;
|
||||
adapters = g_malloc (buffer_size);
|
||||
|
||||
do
|
||||
{
|
||||
result = GetAdaptersAddresses (AF_INET6, 0, NULL, adapters, &buffer_size);
|
||||
if (result != ERROR_BUFFER_OVERFLOW)
|
||||
break;
|
||||
adapters = g_realloc (adapters, buffer_size);
|
||||
}
|
||||
while (result == ERROR_BUFFER_OVERFLOW);
|
||||
if (result != ERROR_SUCCESS)
|
||||
goto beach;
|
||||
|
||||
for (adapter = adapters; adapter != NULL; adapter = adapter->Next)
|
||||
{
|
||||
SOCKET_ADDRESS * raw_addr;
|
||||
GInetSocketAddress * addr;
|
||||
|
||||
if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
|
||||
continue;
|
||||
|
||||
if (adapter->FirstUnicastAddress == NULL)
|
||||
continue;
|
||||
|
||||
raw_addr = &adapter->FirstUnicastAddress->Address;
|
||||
addr = G_INET_SOCKET_ADDRESS (g_socket_address_new_from_native (raw_addr->lpSockaddr, raw_addr->iSockaddrLength));
|
||||
|
||||
func (adapter->IfIndex, adapter->AdapterName, addr, func_target);
|
||||
}
|
||||
|
||||
beach:
|
||||
g_free (adapters);
|
||||
}
|
||||
|
||||
void *
|
||||
_frida_fruity_windows_pairing_browser_monitor_create_backend (gulong interface_index,
|
||||
FridaFruityWindowsPairingBrowserResultCallback callback, gpointer callback_target)
|
||||
{
|
||||
FridaPairingBrowserBackend * backend;
|
||||
FridaDnsApi * api;
|
||||
DNS_SERVICE_BROWSE_REQUEST r = { 0, };
|
||||
|
||||
api = frida_try_get_dns_api ();
|
||||
if (api == NULL)
|
||||
return NULL;
|
||||
|
||||
backend = g_slice_new0 (FridaPairingBrowserBackend);
|
||||
backend->callback = callback;
|
||||
backend->callback_target = callback_target;
|
||||
|
||||
r.Version = DNS_QUERY_REQUEST_VERSION2;
|
||||
r.InterfaceIndex = interface_index;
|
||||
r.QueryName = G_PASTE (L, FRIDA_FRUITY_PAIRING_SERVICE_DNS_SD_NAME);
|
||||
r.pBrowseCallbackV2 = frida_fruity_windows_pairing_browser_on_browse_results;
|
||||
r.pQueryContext = backend;
|
||||
|
||||
if (api->browse (&r, &backend->browse_handle) != DNS_REQUEST_PENDING)
|
||||
{
|
||||
g_slice_free (FridaPairingBrowserBackend, backend);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return backend;
|
||||
}
|
||||
|
||||
void
|
||||
_frida_fruity_windows_pairing_browser_monitor_destroy_backend (void * opaque_backend)
|
||||
{
|
||||
FridaPairingBrowserBackend * backend = opaque_backend;
|
||||
|
||||
if (backend == NULL)
|
||||
return;
|
||||
|
||||
frida_try_get_dns_api ()->browse_cancel (&backend->browse_handle);
|
||||
|
||||
g_slice_free (FridaPairingBrowserBackend, backend);
|
||||
}
|
||||
|
||||
static void WINAPI
|
||||
frida_fruity_windows_pairing_browser_on_browse_results (void * query_context, DNS_QUERY_RESULT * query_results)
|
||||
{
|
||||
FridaPairingBrowserBackend * backend = query_context;
|
||||
|
||||
backend->callback (query_results, backend->callback_target);
|
||||
}
|
||||
|
||||
void
|
||||
_frida_fruity_usbmux_backend_extract_details_for_device (gint product_id, const char * udid, char ** name, GVariant ** icon,
|
||||
GError ** error)
|
||||
{
|
||||
gboolean result = FALSE;
|
||||
@@ -426,3 +548,30 @@ beach:
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static FridaDnsApi *
|
||||
frida_try_get_dns_api (void)
|
||||
{
|
||||
static gsize api_value = 0;
|
||||
|
||||
if (g_once_init_enter (&api_value))
|
||||
{
|
||||
HMODULE mod;
|
||||
FARPROC browse;
|
||||
FridaDnsApi * api = NULL;
|
||||
|
||||
mod = GetModuleHandleW (L"dnsapi.dll");
|
||||
|
||||
browse = GetProcAddress (mod, "DnsServiceBrowse");
|
||||
if (browse != NULL)
|
||||
{
|
||||
api = g_slice_new (FridaDnsApi);
|
||||
api->browse = (gpointer) browse;
|
||||
api->browse_cancel = (gpointer) GetProcAddress (mod, "DnsServiceBrowseCancel");
|
||||
}
|
||||
|
||||
g_once_init_leave (&api_value, GPOINTER_TO_SIZE (api) + 1);
|
||||
}
|
||||
|
||||
return GSIZE_TO_POINTER (api_value - 1);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public class WindowsPairingBrowser : Object, PairingBrowser {
|
||||
private Gee.Map<string, Monitor> monitors = new Gee.HashMap<string, Monitor> ();
|
||||
|
||||
private MainContext main_context = MainContext.ref_thread_default ();
|
||||
|
||||
public async void start (Cancellable? cancellable) throws IOError {
|
||||
_enumerate_network_interfaces ((index, name, address) => {
|
||||
var monitor = new Monitor (this, index, address);
|
||||
monitors[name] = monitor;
|
||||
});
|
||||
}
|
||||
|
||||
public async void stop (Cancellable? cancellable) throws IOError {
|
||||
monitors.clear ();
|
||||
}
|
||||
|
||||
private void on_result (WinDns.QueryResult * result, InetSocketAddress interface_address) {
|
||||
try {
|
||||
PairingServiceDetails service = parse_result (result, interface_address);
|
||||
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
service_discovered (service);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private static PairingServiceDetails parse_result (WinDns.QueryResult * res,
|
||||
InetSocketAddress interface_address) throws GLib.Error {
|
||||
PairingServiceMetadata? meta = null;
|
||||
InetAddress? ip = null;
|
||||
uint16 port = 0;
|
||||
for (WinDns.Record * r = res->query_records; r != null; r = r->next) {
|
||||
switch (r->type) {
|
||||
case TEXT:
|
||||
WinDns.TxtData * txt = &r->txt;
|
||||
var txt_record = new Gee.ArrayList<string> ();
|
||||
foreach (unowned string16 str in txt->strings)
|
||||
txt_record.add (str.to_utf8 ());
|
||||
meta = PairingServiceMetadata.from_txt_record (txt_record);
|
||||
break;
|
||||
case AAAA:
|
||||
ip = new InetAddress.from_bytes (r->aaaa.ip.data, IPV6);
|
||||
break;
|
||||
case SRV:
|
||||
port = r->srv.port;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (meta == null || ip == null || port == 0)
|
||||
throw new Error.PROTOCOL ("Incomplete result");
|
||||
|
||||
return new PairingServiceDetails () {
|
||||
identifier = meta.identifier,
|
||||
auth_tag = meta.auth_tag,
|
||||
endpoint = (InetSocketAddress) Object.new (typeof (InetSocketAddress),
|
||||
address: ip,
|
||||
port: port,
|
||||
scope_id: ip.get_is_link_local () ? interface_address.get_scope_id () : 0
|
||||
),
|
||||
interface_address = interface_address,
|
||||
};
|
||||
}
|
||||
|
||||
private class Monitor {
|
||||
private weak WindowsPairingBrowser parent;
|
||||
private InetSocketAddress interface_address;
|
||||
|
||||
private void * backend;
|
||||
|
||||
public Monitor (WindowsPairingBrowser parent, ulong interface_index, InetSocketAddress interface_address) {
|
||||
this.parent = parent;
|
||||
this.interface_address = interface_address;
|
||||
|
||||
backend = _create_backend (interface_index, on_result);
|
||||
}
|
||||
|
||||
~Monitor () {
|
||||
_destroy_backend (backend);
|
||||
}
|
||||
|
||||
private void on_result (void * result) {
|
||||
parent.on_result (result, interface_address);
|
||||
}
|
||||
|
||||
public extern static void * _create_backend (ulong interface_index, ResultCallback callback);
|
||||
public extern static void _destroy_backend (void * backend);
|
||||
}
|
||||
|
||||
public extern static void _enumerate_network_interfaces (NetifFoundFunc func);
|
||||
|
||||
public delegate void NetifFoundFunc (ulong index, string identifier, owned InetSocketAddress address);
|
||||
public delegate void ResultCallback (void * result);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,174 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public class DnsPacketReader {
|
||||
private BufferReader reader;
|
||||
|
||||
public DnsPacketReader (Bytes packet) {
|
||||
reader = new BufferReader (new Buffer (packet, BIG_ENDIAN));
|
||||
}
|
||||
|
||||
public DnsPtrRecord read_ptr () throws Error {
|
||||
var rr = read_record ();
|
||||
if (rr.key.type != PTR)
|
||||
throw new Error.PROTOCOL ("Expected a PTR record");
|
||||
if (rr.key.klass != IN)
|
||||
throw new Error.PROTOCOL ("Expected a PTR record of class IN");
|
||||
var subreader = new DnsPacketReader (rr.data);
|
||||
var name = subreader.read_name ();
|
||||
return new DnsPtrRecord () {
|
||||
key = rr.key,
|
||||
ttl = rr.ttl,
|
||||
data = rr.data,
|
||||
name = name,
|
||||
};
|
||||
}
|
||||
|
||||
public DnsTxtRecord read_txt () throws Error {
|
||||
var rr = read_record ();
|
||||
if (rr.key.type != TXT)
|
||||
throw new Error.PROTOCOL ("Expected a TXT record");
|
||||
if (rr.key.klass != IN)
|
||||
throw new Error.PROTOCOL ("Expected a TXT record of class IN");
|
||||
var entries = new Gee.ArrayList<string> ();
|
||||
var subreader = new DnsPacketReader (rr.data);
|
||||
while (subreader.reader.available != 0) {
|
||||
string text = subreader.read_string ();
|
||||
entries.add (text);
|
||||
}
|
||||
return new DnsTxtRecord () {
|
||||
key = rr.key,
|
||||
ttl = rr.ttl,
|
||||
data = rr.data,
|
||||
entries = entries.to_array (),
|
||||
};
|
||||
}
|
||||
|
||||
public DnsAaaaRecord read_aaaa () throws Error {
|
||||
var rr = read_record ();
|
||||
if (rr.key.type != AAAA)
|
||||
throw new Error.PROTOCOL ("Expected an AAAA record");
|
||||
if (rr.key.klass != IN)
|
||||
throw new Error.PROTOCOL ("Expected an AAAA record of class IN");
|
||||
var subreader = new DnsPacketReader (rr.data);
|
||||
var raw_address = subreader.reader.read_bytes (16);
|
||||
var address = new InetAddress.from_bytes (raw_address.get_data (), IPV6);
|
||||
return new DnsAaaaRecord () {
|
||||
key = rr.key,
|
||||
ttl = rr.ttl,
|
||||
data = rr.data,
|
||||
address = address,
|
||||
};
|
||||
}
|
||||
|
||||
public DnsSrvRecord read_srv () throws Error {
|
||||
var rr = read_record ();
|
||||
if (rr.key.type != SRV)
|
||||
throw new Error.PROTOCOL ("Expected a SRV record");
|
||||
if (rr.key.klass != IN)
|
||||
throw new Error.PROTOCOL ("Expected a SRV record of class IN");
|
||||
var subreader = new DnsPacketReader (rr.data);
|
||||
var priority = subreader.reader.read_uint16 ();
|
||||
var weight = subreader.reader.read_uint16 ();
|
||||
var port = subreader.reader.read_uint16 ();
|
||||
var name = subreader.read_name ();
|
||||
return new DnsSrvRecord () {
|
||||
key = rr.key,
|
||||
ttl = rr.ttl,
|
||||
data = rr.data,
|
||||
priority = priority,
|
||||
weight = weight,
|
||||
port = port,
|
||||
name = name,
|
||||
};
|
||||
}
|
||||
|
||||
public DnsResourceRecord read_record () throws Error {
|
||||
var key = read_key ();
|
||||
var ttl = reader.read_uint32 ();
|
||||
var size = reader.read_uint16 ();
|
||||
var data = reader.read_bytes (size);
|
||||
return new DnsResourceRecord () {
|
||||
key = key,
|
||||
ttl = ttl,
|
||||
data = data,
|
||||
};
|
||||
}
|
||||
|
||||
public DnsResourceKey read_key () throws Error {
|
||||
var name = read_name ();
|
||||
var type = reader.read_uint16 ();
|
||||
var klass = reader.read_uint16 ();
|
||||
return new DnsResourceKey () {
|
||||
name = name,
|
||||
type = type,
|
||||
klass = klass,
|
||||
};
|
||||
}
|
||||
|
||||
public string read_name () throws Error {
|
||||
var name = new StringBuilder.sized (256);
|
||||
|
||||
while (true) {
|
||||
size_t size = reader.read_uint8 ();
|
||||
if (size == 0)
|
||||
break;
|
||||
if (size > 63)
|
||||
throw new Error.PROTOCOL ("Invalid DNS name length");
|
||||
|
||||
var label = reader.read_fixed_string (size);
|
||||
if (name.len != 0)
|
||||
name.append_c ('.');
|
||||
name.append (label);
|
||||
}
|
||||
|
||||
return name.str;
|
||||
}
|
||||
|
||||
public string read_string () throws Error {
|
||||
size_t size = reader.read_uint8 ();
|
||||
return reader.read_fixed_string (size);
|
||||
}
|
||||
}
|
||||
|
||||
public class DnsPtrRecord : DnsResourceRecord {
|
||||
public string name;
|
||||
}
|
||||
|
||||
public class DnsTxtRecord : DnsResourceRecord {
|
||||
public string[] entries;
|
||||
}
|
||||
|
||||
public class DnsAaaaRecord : DnsResourceRecord {
|
||||
public InetAddress address;
|
||||
}
|
||||
|
||||
public class DnsSrvRecord : DnsResourceRecord {
|
||||
public uint16 priority;
|
||||
public uint16 weight;
|
||||
public uint16 port;
|
||||
public string name;
|
||||
}
|
||||
|
||||
public class DnsResourceRecord {
|
||||
public DnsResourceKey key;
|
||||
public uint32 ttl;
|
||||
public Bytes data;
|
||||
}
|
||||
|
||||
public class DnsResourceKey {
|
||||
public string name;
|
||||
public DnsRecordType type;
|
||||
public DnsRecordClass klass;
|
||||
}
|
||||
|
||||
public enum DnsRecordType {
|
||||
PTR = 12,
|
||||
TXT = 16,
|
||||
AAAA = 28,
|
||||
SRV = 33,
|
||||
}
|
||||
|
||||
public enum DnsRecordClass {
|
||||
IN = 1,
|
||||
}
|
||||
}
|
||||
+167
-152
@@ -1,18 +1,18 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public class DeviceInfoService : Object, AsyncInitable {
|
||||
public ChannelProvider channel_provider {
|
||||
public HostChannelProvider channel_provider {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private DTXChannel channel;
|
||||
|
||||
private DeviceInfoService (ChannelProvider channel_provider) {
|
||||
private DeviceInfoService (HostChannelProvider channel_provider) {
|
||||
Object (channel_provider: channel_provider);
|
||||
}
|
||||
|
||||
public static async DeviceInfoService open (ChannelProvider channel_provider, Cancellable? cancellable = null)
|
||||
public static async DeviceInfoService open (HostChannelProvider channel_provider, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
var service = new DeviceInfoService (channel_provider);
|
||||
|
||||
@@ -74,21 +74,53 @@ namespace Frida.Fruity {
|
||||
return "/private" + name;
|
||||
return name;
|
||||
}
|
||||
|
||||
public class ProcessInfo : Object {
|
||||
public uint pid {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string real_app_name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool is_application {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool foreground_running {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public DateTime? start_date {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ApplicationListingService : Object, AsyncInitable {
|
||||
public ChannelProvider channel_provider {
|
||||
public HostChannelProvider channel_provider {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private DTXChannel channel;
|
||||
|
||||
private ApplicationListingService (ChannelProvider channel_provider) {
|
||||
private ApplicationListingService (HostChannelProvider channel_provider) {
|
||||
Object (channel_provider: channel_provider);
|
||||
}
|
||||
|
||||
public static async ApplicationListingService open (ChannelProvider channel_provider, Cancellable? cancellable = null)
|
||||
public static async ApplicationListingService open (HostChannelProvider channel_provider, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
var service = new ApplicationListingService (channel_provider);
|
||||
|
||||
@@ -178,21 +210,115 @@ namespace Frida.Fruity {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public class ApplicationInfo : Object {
|
||||
public ApplicationType app_type {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string display_name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string bundle_identifier {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string bundle_path {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? version {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool placeholder {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool restricted {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? executable_name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string[]? app_extension_uuids {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? plugin_uuid {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? plugin_identifier {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? container_bundle_identifier {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? container_bundle_path {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ApplicationType {
|
||||
SYSTEM = 1,
|
||||
USER,
|
||||
PLUGIN_KIT;
|
||||
|
||||
public static ApplicationType from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<ApplicationType> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<ApplicationType> (this);
|
||||
}
|
||||
|
||||
internal static ApplicationType from_dtx (string type) {
|
||||
if (type == "System")
|
||||
return SYSTEM;
|
||||
|
||||
if (type == "User")
|
||||
return USER;
|
||||
|
||||
if (type == "PluginKit")
|
||||
return PLUGIN_KIT;
|
||||
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProcessControlService : Object, AsyncInitable {
|
||||
public ChannelProvider channel_provider {
|
||||
public HostChannelProvider channel_provider {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private DTXChannel channel;
|
||||
|
||||
private ProcessControlService (ChannelProvider channel_provider) {
|
||||
private ProcessControlService (HostChannelProvider channel_provider) {
|
||||
Object (channel_provider: channel_provider);
|
||||
}
|
||||
|
||||
public static async ProcessControlService open (ChannelProvider channel_provider, Cancellable? cancellable = null)
|
||||
public static async ProcessControlService open (HostChannelProvider channel_provider, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
var service = new ProcessControlService (channel_provider);
|
||||
|
||||
@@ -220,132 +346,6 @@ namespace Frida.Fruity {
|
||||
}
|
||||
}
|
||||
|
||||
public class ProcessInfo : Object {
|
||||
public uint pid {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string real_app_name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool is_application {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool foreground_running {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public DateTime? start_date {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public class ApplicationInfo : Object {
|
||||
public ApplicationType app_type {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string display_name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string bundle_identifier {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string bundle_path {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? version {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool placeholder {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool restricted {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? executable_name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string[]? app_extension_uuids {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? plugin_uuid {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? plugin_identifier {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? container_bundle_identifier {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? container_bundle_path {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ApplicationType {
|
||||
SYSTEM = 1,
|
||||
USER,
|
||||
PLUGIN_KIT;
|
||||
|
||||
public static ApplicationType from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<ApplicationType> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<ApplicationType> (this);
|
||||
}
|
||||
|
||||
internal static ApplicationType from_dtx (string type) {
|
||||
if (type == "System")
|
||||
return SYSTEM;
|
||||
|
||||
if (type == "User")
|
||||
return USER;
|
||||
|
||||
if (type == "PluginKit")
|
||||
return PLUGIN_KIT;
|
||||
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
public class DTXConnection : Object, DTXTransport {
|
||||
public IOStream stream {
|
||||
get;
|
||||
@@ -363,7 +363,7 @@ namespace Frida.Fruity {
|
||||
CLOSED
|
||||
}
|
||||
|
||||
private static Gee.HashMap<ChannelProvider, Future<DTXConnection>> connections;
|
||||
private static Gee.HashMap<HostChannelProvider, Future<DTXConnection>> connections;
|
||||
|
||||
private State _state = OPEN;
|
||||
|
||||
@@ -383,19 +383,21 @@ namespace Frida.Fruity {
|
||||
private const uint32 DTX_FRAGMENT_MAGIC = 0x1f3d5b79U;
|
||||
private const uint MAX_BUFFERED_COUNT = 100;
|
||||
private const size_t MAX_BUFFERED_SIZE = 30 * 1024 * 1024;
|
||||
private const size_t MAX_MESSAGE_SIZE = 1024 * 1024;
|
||||
private const size_t MAX_MESSAGE_SIZE = 128 * 1024 * 1024;
|
||||
private const size_t MAX_FRAGMENT_SIZE = 128 * 1024;
|
||||
private const string REMOTESERVER_ENDPOINT_MODERN = "lockdown:com.apple.instruments.remoteserver.DVTSecureSocketProxy";
|
||||
private const string REMOTESERVER_ENDPOINT_17PLUS = "lockdown:com.apple.instruments.dtservicehub";
|
||||
private const string REMOTESERVER_ENDPOINT_14PLUS = "lockdown:com.apple.instruments.remoteserver.DVTSecureSocketProxy";
|
||||
private const string REMOTESERVER_ENDPOINT_LEGACY = "lockdown:com.apple.instruments.remoteserver?tls=handshake-only";
|
||||
private const string[] REMOTESERVER_ENDPOINT_CANDIDATES = {
|
||||
REMOTESERVER_ENDPOINT_MODERN,
|
||||
REMOTESERVER_ENDPOINT_17PLUS,
|
||||
REMOTESERVER_ENDPOINT_14PLUS,
|
||||
REMOTESERVER_ENDPOINT_LEGACY,
|
||||
};
|
||||
|
||||
public static async DTXConnection obtain (ChannelProvider channel_provider, Cancellable? cancellable)
|
||||
public static async DTXConnection obtain (HostChannelProvider channel_provider, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
if (connections == null)
|
||||
connections = new Gee.HashMap<ChannelProvider, Future<DTXConnection>> ();
|
||||
connections = new Gee.HashMap<HostChannelProvider, Future<DTXConnection>> ();
|
||||
|
||||
while (connections.has_key (channel_provider)) {
|
||||
var future = connections[channel_provider];
|
||||
@@ -442,7 +444,7 @@ namespace Frida.Fruity {
|
||||
throw api_error;
|
||||
}
|
||||
|
||||
public static async void close_all (ChannelProvider channel_provider, Cancellable? cancellable) throws IOError {
|
||||
public static async void close_all (HostChannelProvider channel_provider, Cancellable? cancellable) throws IOError {
|
||||
if (connections == null)
|
||||
return;
|
||||
|
||||
@@ -502,6 +504,14 @@ namespace Frida.Fruity {
|
||||
process_incoming_fragments.begin ();
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
foreach (var channel in channels.values)
|
||||
channel.transport = null;
|
||||
channels.clear ();
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
private async void close (Cancellable? cancellable) throws IOError {
|
||||
io_cancellable.cancel ();
|
||||
|
||||
@@ -705,7 +715,9 @@ namespace Frida.Fruity {
|
||||
|
||||
int32 channel_code = message.channel_code;
|
||||
bool is_notification = false;
|
||||
if (message.type == RESULT && channel_code < 0) {
|
||||
if (message.type == INVOKE) {
|
||||
channel_code = -channel_code;
|
||||
} else if (message.type == RESULT && channel_code < 0) {
|
||||
channel_code = -channel_code;
|
||||
is_notification = true;
|
||||
}
|
||||
@@ -866,7 +878,7 @@ namespace Frida.Fruity {
|
||||
|
||||
public class DTXChannel : Object {
|
||||
public signal void invocation (string method_name, DTXArgumentList args, DTXMessageTransportFlags transport_flags);
|
||||
public signal void notification (NSDictionary dict);
|
||||
public signal void notification (NSObject obj);
|
||||
public signal void barrier ();
|
||||
|
||||
public int32 code {
|
||||
@@ -874,9 +886,9 @@ namespace Frida.Fruity {
|
||||
construct;
|
||||
}
|
||||
|
||||
public weak DTXTransport transport {
|
||||
public weak DTXTransport? transport {
|
||||
get;
|
||||
construct;
|
||||
set;
|
||||
}
|
||||
|
||||
public State state {
|
||||
@@ -899,7 +911,7 @@ namespace Frida.Fruity {
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
transport.remove_channel (this);
|
||||
close ();
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
@@ -911,6 +923,11 @@ namespace Frida.Fruity {
|
||||
var error = new Error.TRANSPORT ("Channel closed");
|
||||
foreach (var request in pending_responses.values.to_array ())
|
||||
request.reject (error);
|
||||
|
||||
if (transport != null) {
|
||||
transport.remove_channel (this);
|
||||
transport = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async NSObject? invoke (string method_name, DTXArgumentListBuilder? args, Cancellable? cancellable)
|
||||
@@ -1016,10 +1033,8 @@ namespace Frida.Fruity {
|
||||
}
|
||||
|
||||
internal void handle_notification (DTXMessage message) throws Error {
|
||||
NSDictionary? dict = NSKeyedArchive.decode (message.payload_data) as NSDictionary;
|
||||
if (dict == null)
|
||||
throw new Error.PROTOCOL ("Malformed notification payload");
|
||||
notification (dict);
|
||||
var payload = NSKeyedArchive.decode (message.payload_data);
|
||||
notification (payload);
|
||||
}
|
||||
|
||||
internal void handle_barrier (DTXMessage message) throws Error {
|
||||
@@ -1062,7 +1077,7 @@ namespace Frida.Fruity {
|
||||
}
|
||||
|
||||
public class DTXArgumentList {
|
||||
private Value[] elements;
|
||||
public Value[] elements;
|
||||
|
||||
private DTXArgumentList (owned Value[] elements) {
|
||||
this.elements = (owned) elements;
|
||||
|
||||
+913
-486
File diff suppressed because it is too large.
Load diff
@@ -50,9 +50,11 @@ frida_fetch_dyld_symbols (char * output_buffer, const void * dyld_load_address)
|
||||
const char * name = strings + sym->n_un.n_strx;
|
||||
|
||||
if (frida_str_contains (name, "libdyld_initialize") ||
|
||||
frida_str_contains (name, "notifyObjCInit") ||
|
||||
frida_str_contains (name, "restartWithDyldInCache") ||
|
||||
frida_str_equals (name, "_gProcessInfo") ||
|
||||
frida_str_equals (name, "__ZN5dyld412gProcessInfoE") ||
|
||||
frida_str_equals (name, "__ZL12sProcessInfo") ||
|
||||
frida_str_contains (name, "launchWithClosure") ||
|
||||
frida_str_contains (name, "initializeMainExecutable") ||
|
||||
frida_str_contains (name, "registerThreadHelpers") ||
|
||||
|
||||
@@ -5,11 +5,15 @@ frida_listen (int rx_buffer_size, const FridaUploadApi * api)
|
||||
{
|
||||
uint8_t error_code;
|
||||
int fd;
|
||||
struct sockaddr_in addr;
|
||||
struct sockaddr_in6 addr = {
|
||||
.sin6_family = AF_INET6,
|
||||
.sin6_addr = IN6ADDR_ANY_INIT,
|
||||
.sin6_port = 0,
|
||||
};
|
||||
socklen_t addr_len;
|
||||
int res;
|
||||
|
||||
fd = api->socket (AF_INET, SOCK_STREAM, 0);
|
||||
fd = api->socket (AF_INET6, SOCK_STREAM, 0);
|
||||
if (fd == -1)
|
||||
goto socket_failed;
|
||||
|
||||
@@ -17,10 +21,6 @@ frida_listen (int rx_buffer_size, const FridaUploadApi * api)
|
||||
if (res == -1)
|
||||
goto setsockopt_failed;
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
|
||||
addr.sin_port = 0;
|
||||
|
||||
addr_len = sizeof (addr);
|
||||
|
||||
res = api->bind (fd, (const struct sockaddr *) &addr, addr_len);
|
||||
@@ -35,7 +35,7 @@ frida_listen (int rx_buffer_size, const FridaUploadApi * api)
|
||||
if (res == -1)
|
||||
goto listen_failed;
|
||||
|
||||
return ((uint64_t) fd << 16) | ntohs (addr.sin_port);
|
||||
return ((uint64_t) fd << 16) | ntohs (addr.sin6_port);
|
||||
|
||||
socket_failed:
|
||||
{
|
||||
|
||||
+116
-93
@@ -1,6 +1,6 @@
|
||||
[CCode (gir_namespace = "FridaFruityInjector", gir_version = "1.0")]
|
||||
namespace Frida.Fruity.Injector {
|
||||
public static async GadgetDetails inject (owned Gum.DarwinModule module, LLDB.Client lldb, ChannelProvider channel_provider,
|
||||
public static async GadgetDetails inject (owned Gum.DarwinModule module, LLDB.Client lldb, HostChannelProvider channel_provider,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var session = new Session (module, lldb, channel_provider);
|
||||
return yield session.run (cancellable);
|
||||
@@ -32,7 +32,7 @@ namespace Frida.Fruity.Injector {
|
||||
construct;
|
||||
}
|
||||
|
||||
public ChannelProvider channel_provider {
|
||||
public HostChannelProvider channel_provider {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
@@ -64,13 +64,12 @@ namespace Frida.Fruity.Injector {
|
||||
|
||||
private LLDB.AppleDyldFields? dyld_fields;
|
||||
private uint64 dyld_base;
|
||||
private Gee.Map<string, uint64?>? dyld_symbols;
|
||||
|
||||
private bool libsystem_initialized;
|
||||
|
||||
private size_t module_size;
|
||||
|
||||
public Session (Gum.DarwinModule module, LLDB.Client lldb, ChannelProvider channel_provider) {
|
||||
public Session (Gum.DarwinModule module, LLDB.Client lldb, HostChannelProvider channel_provider) {
|
||||
Object (
|
||||
module: module,
|
||||
lldb: lldb,
|
||||
@@ -160,7 +159,6 @@ namespace Frida.Fruity.Injector {
|
||||
return true;
|
||||
}, cancellable);
|
||||
}
|
||||
dyld_symbols = yield fetch_dyld_symbols (cancellable);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -629,22 +627,24 @@ namespace Frida.Fruity.Injector {
|
||||
|
||||
/* Compiled from helpers/upload-listener.c */
|
||||
private const uint8[] UPLOAD_LISTENER_CODE = {
|
||||
0xff, 0x43, 0x01, 0xd1, 0xf6, 0x57, 0x02, 0xa9, 0xf4, 0x4f, 0x03, 0xa9, 0xfd, 0x7b, 0x04, 0xa9, 0xfd, 0x03, 0x01,
|
||||
0x91, 0xf3, 0x03, 0x01, 0xaa, 0xe0, 0x1f, 0x00, 0xb9, 0x28, 0x00, 0x40, 0xf9, 0x40, 0x00, 0x80, 0x52, 0x21, 0x00,
|
||||
0x80, 0x52, 0x02, 0x00, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0x80, 0x05, 0x00, 0x54, 0xf4,
|
||||
0x03, 0x00, 0xaa, 0x68, 0x06, 0x40, 0xf9, 0xe3, 0x73, 0x00, 0x91, 0xe1, 0xff, 0x9f, 0x52, 0x42, 0x00, 0x82, 0x52,
|
||||
0x84, 0x00, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0xa0, 0x04, 0x00, 0x54, 0x48, 0x00, 0x80,
|
||||
0x52, 0xe8, 0x27, 0x00, 0x39, 0xe8, 0x0f, 0x80, 0x52, 0x08, 0x20, 0xa0, 0x72, 0xe8, 0x0f, 0x00, 0xb9, 0xff, 0x17,
|
||||
0x00, 0x79, 0x08, 0x02, 0x80, 0x52, 0xe8, 0x07, 0x00, 0xb9, 0x68, 0x0a, 0x40, 0xf9, 0xe1, 0x23, 0x00, 0x91, 0xe0,
|
||||
0x03, 0x14, 0xaa, 0x02, 0x02, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0x00, 0x03, 0x00, 0x54,
|
||||
0x68, 0x12, 0x40, 0xf9, 0xe1, 0x23, 0x00, 0x91, 0xe2, 0x13, 0x00, 0x91, 0xe0, 0x03, 0x14, 0xaa, 0x00, 0x01, 0x3f,
|
||||
0xff, 0x83, 0x01, 0xd1, 0xf6, 0x57, 0x03, 0xa9, 0xf4, 0x4f, 0x04, 0xa9, 0xfd, 0x7b, 0x05, 0xa9, 0xfd, 0x43, 0x01,
|
||||
0x91, 0xf3, 0x03, 0x01, 0xaa, 0x08, 0x00, 0x00, 0x90, 0x08, 0xb1, 0x04, 0x91, 0xa0, 0xc3, 0x1d, 0xb8, 0x00, 0x01,
|
||||
0xc0, 0x3d, 0xe0, 0x07, 0x80, 0x3d, 0x00, 0xc1, 0xc0, 0x3c, 0xe0, 0xc3, 0x81, 0x3c, 0x28, 0x00, 0x40, 0xf9, 0xc0,
|
||||
0x03, 0x80, 0x52, 0x21, 0x00, 0x80, 0x52, 0x02, 0x00, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31,
|
||||
0xc0, 0x04, 0x00, 0x54, 0xf4, 0x03, 0x00, 0xaa, 0x68, 0x06, 0x40, 0xf9, 0xa3, 0x93, 0x00, 0xd1, 0xe1, 0xff, 0x9f,
|
||||
0x52, 0x42, 0x00, 0x82, 0x52, 0x84, 0x00, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0xe0, 0x03,
|
||||
0x00, 0x54, 0x88, 0x03, 0x80, 0x52, 0xe8, 0x0f, 0x00, 0xb9, 0x68, 0x0a, 0x40, 0xf9, 0xe1, 0x43, 0x00, 0x91, 0xe0,
|
||||
0x03, 0x14, 0xaa, 0x82, 0x03, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0x00, 0x03, 0x00, 0x54,
|
||||
0x68, 0x12, 0x40, 0xf9, 0xe1, 0x43, 0x00, 0x91, 0xe2, 0x33, 0x00, 0x91, 0xe0, 0x03, 0x14, 0xaa, 0x00, 0x01, 0x3f,
|
||||
0xd6, 0x1f, 0x04, 0x00, 0x31, 0x60, 0x02, 0x00, 0x54, 0x68, 0x0e, 0x40, 0xf9, 0xe0, 0x03, 0x14, 0xaa, 0x21, 0x00,
|
||||
0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0xe0, 0x01, 0x00, 0x54, 0x88, 0x7e, 0x40, 0x93, 0xe9,
|
||||
0x17, 0x40, 0x79, 0x29, 0x09, 0xc0, 0x5a, 0x35, 0x7d, 0x10, 0x53, 0x15, 0xbd, 0x70, 0xb3, 0x0d, 0x00, 0x00, 0x14,
|
||||
0x27, 0x40, 0x79, 0x29, 0x09, 0xc0, 0x5a, 0x35, 0x7d, 0x10, 0x53, 0x15, 0xbd, 0x70, 0xb3, 0x0d, 0x00, 0x00, 0x14,
|
||||
0x15, 0x20, 0xe0, 0xd2, 0x0b, 0x00, 0x00, 0x14, 0x15, 0x40, 0xe0, 0xd2, 0x06, 0x00, 0x00, 0x14, 0x15, 0x60, 0xe0,
|
||||
0xd2, 0x04, 0x00, 0x00, 0x14, 0x15, 0x80, 0xe0, 0xd2, 0x02, 0x00, 0x00, 0x14, 0x15, 0xa0, 0xe0, 0xd2, 0x68, 0x42,
|
||||
0x40, 0xf9, 0xe0, 0x03, 0x14, 0xaa, 0x00, 0x01, 0x3f, 0xd6, 0xe0, 0x03, 0x15, 0xaa, 0xfd, 0x7b, 0x44, 0xa9, 0xf4,
|
||||
0x4f, 0x43, 0xa9, 0xf6, 0x57, 0x42, 0xa9, 0xff, 0x43, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6
|
||||
0x40, 0xf9, 0xe0, 0x03, 0x14, 0xaa, 0x00, 0x01, 0x3f, 0xd6, 0xe0, 0x03, 0x15, 0xaa, 0xfd, 0x7b, 0x45, 0xa9, 0xf4,
|
||||
0x4f, 0x44, 0xa9, 0xf6, 0x57, 0x43, 0xa9, 0xff, 0x83, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6, 0x00, 0x1e, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
/* Compiled from helpers/upload-receiver.c */
|
||||
@@ -810,7 +810,7 @@ namespace Frida.Fruity.Injector {
|
||||
return total;
|
||||
}
|
||||
|
||||
private uint64 resolve_dyld_symbol (string name, string nick) throws Error {
|
||||
private static uint64 resolve_dyld_symbol (string name, string nick, Gee.Map<string, uint64?> dyld_symbols) throws Error {
|
||||
uint64? val = dyld_symbols[name];
|
||||
if (val == null)
|
||||
throw new Error.UNSUPPORTED ("Unsupported iOS version (%s not found)", nick);
|
||||
@@ -821,13 +821,15 @@ namespace Frida.Fruity.Injector {
|
||||
if (libsystem_initialized)
|
||||
return;
|
||||
|
||||
var dyld_symbols = yield fetch_dyld_symbols (cancellable);
|
||||
|
||||
yield restore_main_thread_state (cancellable);
|
||||
|
||||
uint64? libdyld_initialize = dyld_symbols["__ZN5dyld44APIs19_libdyld_initializeEPKNS_16LibSystemHelpersE"];
|
||||
if (libdyld_initialize != null)
|
||||
yield ensure_libsystem_initialized_for_dyld_v4_and_above (libdyld_initialize, cancellable);
|
||||
yield ensure_libsystem_initialized_for_dyld_v4_and_above (libdyld_initialize, dyld_symbols, cancellable);
|
||||
else
|
||||
yield ensure_libsystem_initialized_for_dyld_v3_and_below (cancellable);
|
||||
yield ensure_libsystem_initialized_for_dyld_v3_and_below (dyld_symbols, cancellable);
|
||||
|
||||
yield save_main_thread_state (cancellable);
|
||||
|
||||
@@ -836,15 +838,21 @@ namespace Frida.Fruity.Injector {
|
||||
}
|
||||
|
||||
private async void ensure_libsystem_initialized_for_dyld_v4_and_above (uint64 libdyld_initialize,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
uint64? process_info_ptr = dyld_symbols["_gProcessInfo"];
|
||||
Gee.Map<string, uint64?> dyld_symbols, Cancellable? cancellable) throws GLib.Error {
|
||||
uint64? process_info_ptr = dyld_symbols["__ZL12sProcessInfo"];
|
||||
if (process_info_ptr == null)
|
||||
process_info_ptr = dyld_symbols["__ZN5dyld412gProcessInfoE"];
|
||||
if (process_info_ptr == null)
|
||||
process_info_ptr = dyld_symbols["_gProcessInfo"];
|
||||
|
||||
GDB.Breakpoint init_breakpoint = yield lldb.add_breakpoint (SOFT, libdyld_initialize, 4, cancellable);
|
||||
uint64? notify_objc_init = dyld_symbols["__ZN5dyld412RuntimeState14notifyObjCInitEPKNS_6LoaderE"];
|
||||
uint64 initializer = (notify_objc_init != null) ? notify_objc_init : libdyld_initialize;
|
||||
GDB.Breakpoint init_breakpoint = yield lldb.add_breakpoint (SOFT, initializer, 4, cancellable);
|
||||
|
||||
GDB.Breakpoint? restart_breakpoint = null;
|
||||
uint64? restart_with_dyld_in_cache = dyld_symbols["__ZN5dyld422restartWithDyldInCacheEPKNS_10KernelArgsEPKN5dyld39MachOFileEPv"];
|
||||
uint64? restart_with_dyld_in_cache = dyld_symbols["__ZN5dyld422restartWithDyldInCacheEPKNS_10KernelArgsEPKN5dyld39MachOFileEPK15DyldSharedCachePv"];
|
||||
if (restart_with_dyld_in_cache == null)
|
||||
restart_with_dyld_in_cache = dyld_symbols["__ZN5dyld422restartWithDyldInCacheEPKNS_10KernelArgsEPKN5dyld39MachOFileEPv"];
|
||||
if (restart_with_dyld_in_cache != null)
|
||||
restart_breakpoint = yield lldb.add_breakpoint (SOFT, restart_with_dyld_in_cache, 4, cancellable);
|
||||
|
||||
@@ -854,12 +862,18 @@ namespace Frida.Fruity.Injector {
|
||||
if (hit_breakpoint == null)
|
||||
throw new Error.UNSUPPORTED ("Unexpected exception");
|
||||
|
||||
if (restart_breakpoint != null)
|
||||
yield restart_breakpoint.remove (cancellable);
|
||||
yield init_breakpoint.remove (cancellable);
|
||||
|
||||
if (hit_breakpoint == restart_breakpoint) {
|
||||
if (process_info_ptr == null)
|
||||
throw new Error.UNSUPPORTED ("Missing gProcessInfo");
|
||||
uint64 process_info = yield lldb.read_pointer (process_info_ptr, cancellable);
|
||||
uint64 dyld_image_load_address = yield lldb.read_pointer (process_info + 0x20, cancellable);
|
||||
|
||||
initializer = dyld_image_load_address + (initializer - dyld_base);
|
||||
|
||||
var rebased_symbols = new Gee.HashMap<string, uint64?> ();
|
||||
foreach (var e in dyld_symbols.entries)
|
||||
rebased_symbols[e.key] = dyld_image_load_address + (e.value - dyld_base);
|
||||
@@ -867,13 +881,12 @@ namespace Frida.Fruity.Injector {
|
||||
dyld_base = dyld_image_load_address;
|
||||
dyld_symbols = rebased_symbols;
|
||||
|
||||
yield restart_breakpoint.remove (cancellable);
|
||||
yield init_breakpoint.remove (cancellable);
|
||||
uint64 real_libdyld_initialize = dyld_symbols["__ZN5dyld44APIs19_libdyld_initializeEPKNS_16LibSystemHelpersE"];
|
||||
init_breakpoint = yield lldb.add_breakpoint (SOFT, real_libdyld_initialize, 4, cancellable);
|
||||
init_breakpoint = yield lldb.add_breakpoint (SOFT, initializer, 4, cancellable);
|
||||
|
||||
exception = (LLDB.Exception) yield lldb.continue_until_exception (cancellable);
|
||||
|
||||
yield init_breakpoint.remove (cancellable);
|
||||
|
||||
hit_breakpoint = exception.breakpoint;
|
||||
if (hit_breakpoint == null)
|
||||
throw new Error.UNSUPPORTED ("Unexpected exception");
|
||||
@@ -882,7 +895,10 @@ namespace Frida.Fruity.Injector {
|
||||
}
|
||||
|
||||
assert (hit_breakpoint == init_breakpoint);
|
||||
yield init_breakpoint.remove (cancellable);
|
||||
|
||||
bool done_because_we_hit_notify_objc_init = initializer != libdyld_initialize;
|
||||
if (done_because_we_hit_notify_objc_init)
|
||||
return;
|
||||
|
||||
uint64 dyld_size = dyld_symbols["dyld_size"];
|
||||
uint64 frame_here = exception.context["fp"];
|
||||
@@ -911,7 +927,8 @@ namespace Frida.Fruity.Injector {
|
||||
yield caller_breakpoint.remove (cancellable);
|
||||
}
|
||||
|
||||
private async void ensure_libsystem_initialized_for_dyld_v3_and_below (Cancellable? cancellable) throws GLib.Error {
|
||||
private async void ensure_libsystem_initialized_for_dyld_v3_and_below (Gee.Map<string, uint64?> dyld_symbols,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
GDB.Breakpoint? modern_breakpoint = null;
|
||||
uint64 launch_with_closure = 0;
|
||||
|
||||
@@ -931,7 +948,8 @@ namespace Frida.Fruity.Injector {
|
||||
modern_breakpoint = yield lldb.add_breakpoint (SOFT, run_initializers_call, 4, cancellable);
|
||||
}
|
||||
|
||||
uint64 initialize_main_executable = resolve_dyld_symbol ("__ZN4dyld24initializeMainExecutableEv", "initializeMainExecutable");
|
||||
uint64 initialize_main_executable = resolve_dyld_symbol ("__ZN4dyld24initializeMainExecutableEv",
|
||||
"initializeMainExecutable", dyld_symbols);
|
||||
GDB.Breakpoint legacy_breakpoint = yield lldb.add_breakpoint (SOFT, initialize_main_executable, 4, cancellable);
|
||||
|
||||
var exception = yield lldb.continue_until_exception (cancellable);
|
||||
@@ -946,7 +964,7 @@ namespace Frida.Fruity.Injector {
|
||||
yield modern_breakpoint.remove (cancellable);
|
||||
|
||||
if (hit_breakpoint == legacy_breakpoint)
|
||||
yield initialize_libsystem_from_legacy_codepath (cancellable);
|
||||
yield initialize_libsystem_from_legacy_codepath (dyld_symbols, cancellable);
|
||||
else
|
||||
assert (hit_breakpoint == modern_breakpoint);
|
||||
}
|
||||
@@ -987,7 +1005,8 @@ namespace Frida.Fruity.Injector {
|
||||
throw new Error.UNSUPPORTED ("Unable to probe dyld v3 internals; please file a bug");
|
||||
}
|
||||
|
||||
private async void initialize_libsystem_from_legacy_codepath (Cancellable? cancellable) throws GLib.Error {
|
||||
private async void initialize_libsystem_from_legacy_codepath (Gee.Map<string, uint64?> dyld_symbols,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
uint64 code = jit_page;
|
||||
|
||||
var code_builder_pac = lldb.make_buffer_builder ();
|
||||
@@ -1053,8 +1072,8 @@ namespace Frida.Fruity.Injector {
|
||||
code_builder.write_pointer (error_buf_literal_offset, helpers + error_buffer_offset);
|
||||
yield lldb.write_byte_array (code, code_builder.build (), cancellable);
|
||||
|
||||
uint64 register_thread_helpers =
|
||||
resolve_dyld_symbol ("__ZL21registerThreadHelpersPKN4dyld16LibSystemHelpersE", "registerThreadHelpers");
|
||||
uint64 register_thread_helpers = resolve_dyld_symbol ("__ZL21registerThreadHelpersPKN4dyld16LibSystemHelpersE",
|
||||
"registerThreadHelpers", dyld_symbols);
|
||||
yield invoke_remote_function (register_thread_helpers, {
|
||||
helpers
|
||||
}, null, cancellable);
|
||||
@@ -1073,8 +1092,8 @@ namespace Frida.Fruity.Injector {
|
||||
ExceptionHandler? strcmp_handler = null;
|
||||
|
||||
var dlopen = dyld_symbols.has_key ("_dlopen_internal")
|
||||
? resolve_dyld_symbol ("_dlopen_internal", "dlopen")
|
||||
: resolve_dyld_symbol ("_dlopen", "dlopen");
|
||||
? resolve_dyld_symbol ("_dlopen_internal", "dlopen", dyld_symbols)
|
||||
: resolve_dyld_symbol ("_dlopen", "dlopen", dyld_symbols);
|
||||
yield invoke_remote_function (dlopen, {
|
||||
libsystem_string,
|
||||
9,
|
||||
@@ -1181,67 +1200,71 @@ namespace Frida.Fruity.Injector {
|
||||
private const uint8[] SYMBOL_FETCHER_CODE = {
|
||||
0xff, 0x03, 0x02, 0xd1, 0xfc, 0x6f, 0x02, 0xa9, 0xfa, 0x67, 0x03, 0xa9, 0xf8, 0x5f, 0x04, 0xa9, 0xf6, 0x57, 0x05,
|
||||
0xa9, 0xf4, 0x4f, 0x06, 0xa9, 0xfd, 0x7b, 0x07, 0xa9, 0xfd, 0xc3, 0x01, 0x91, 0xf4, 0x03, 0x01, 0xaa, 0xe0, 0x07,
|
||||
0x00, 0xf9, 0x13, 0x00, 0x80, 0xd2, 0x19, 0x00, 0x80, 0xd2, 0x35, 0x80, 0x00, 0x91, 0x3a, 0x10, 0x40, 0xb9, 0xd6,
|
||||
0x22, 0x00, 0x30, 0x1f, 0x20, 0x03, 0xd5, 0xd7, 0x22, 0x00, 0x10, 0x1f, 0x20, 0x03, 0xd5, 0x1a, 0x04, 0x00, 0x34,
|
||||
0x00, 0xf9, 0x13, 0x00, 0x80, 0xd2, 0x19, 0x00, 0x80, 0xd2, 0x35, 0x80, 0x00, 0x91, 0x3a, 0x10, 0x40, 0xb9, 0x16,
|
||||
0x00, 0x00, 0x90, 0xd6, 0x3e, 0x13, 0x91, 0x17, 0x00, 0x00, 0x90, 0xf7, 0x5a, 0x13, 0x91, 0x1a, 0x04, 0x00, 0x34,
|
||||
0xa8, 0x02, 0x40, 0xb9, 0x1f, 0x09, 0x00, 0x71, 0xa0, 0x01, 0x00, 0x54, 0x1f, 0x2d, 0x00, 0x71, 0xa0, 0x01, 0x00,
|
||||
0x54, 0x1f, 0x65, 0x00, 0x71, 0x81, 0x01, 0x00, 0x54, 0xb8, 0x22, 0x00, 0x91, 0xe0, 0x03, 0x18, 0xaa, 0xe1, 0x03,
|
||||
0x16, 0xaa, 0xa3, 0x00, 0x00, 0x94, 0x80, 0x01, 0x00, 0x34, 0xb3, 0xa2, 0x41, 0xa9, 0xe8, 0x0b, 0x00, 0xf9, 0x04,
|
||||
0x16, 0xaa, 0xaa, 0x00, 0x00, 0x94, 0x80, 0x01, 0x00, 0x34, 0xb3, 0xa2, 0x41, 0xa9, 0xe8, 0x0b, 0x00, 0xf9, 0x04,
|
||||
0x00, 0x00, 0x14, 0xfc, 0x03, 0x15, 0xaa, 0x02, 0x00, 0x00, 0x14, 0xfb, 0x03, 0x15, 0xaa, 0xa8, 0x06, 0x40, 0xb9,
|
||||
0xb5, 0x02, 0x08, 0x8b, 0x5a, 0x07, 0x00, 0x51, 0x7a, 0xfd, 0xff, 0x35, 0x09, 0x00, 0x00, 0x14, 0xe0, 0x03, 0x18,
|
||||
0xaa, 0xe1, 0x03, 0x17, 0xaa, 0x94, 0x00, 0x00, 0x94, 0x00, 0xff, 0xff, 0x34, 0xa8, 0x0e, 0x40, 0xf9, 0xa9, 0x16,
|
||||
0xaa, 0xe1, 0x03, 0x17, 0xaa, 0x9b, 0x00, 0x00, 0x94, 0x00, 0xff, 0xff, 0x34, 0xa8, 0x0e, 0x40, 0xf9, 0xa9, 0x16,
|
||||
0x40, 0xf9, 0x19, 0x01, 0x09, 0xcb, 0xf4, 0xff, 0xff, 0x17, 0x88, 0x02, 0x13, 0xcb, 0x28, 0x03, 0x08, 0x8b, 0x89,
|
||||
0x0b, 0x40, 0xb9, 0x13, 0x01, 0x09, 0x8b, 0x89, 0x13, 0x40, 0xb9, 0x19, 0x01, 0x09, 0x8b, 0x16, 0x17, 0x00, 0x10,
|
||||
0x1f, 0x20, 0x03, 0xd5, 0xe8, 0x07, 0x40, 0xf9, 0xe8, 0x0f, 0x00, 0xf9, 0x35, 0x01, 0x80, 0x52, 0x78, 0x0b, 0x40,
|
||||
0xb9, 0x68, 0x0f, 0x40, 0xb9, 0x1f, 0x03, 0x08, 0x6b, 0xc0, 0x09, 0x00, 0x54, 0xf7, 0x03, 0x18, 0x2a, 0xe8, 0xee,
|
||||
0x7c, 0xd3, 0x68, 0x6a, 0x68, 0xb8, 0x3c, 0x03, 0x08, 0x8b, 0xe0, 0x03, 0x1c, 0xaa, 0xe1, 0x03, 0x16, 0xaa, 0x61,
|
||||
0x00, 0x00, 0x94, 0x60, 0x06, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x41, 0x15, 0x00, 0x70, 0x1f, 0x20, 0x03, 0xd5,
|
||||
0x5c, 0x00, 0x00, 0x94, 0xc0, 0x05, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x61, 0x15, 0x00, 0x50, 0x1f, 0x20, 0x03,
|
||||
0xd5, 0x6f, 0x00, 0x00, 0x94, 0x20, 0x05, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x41, 0x15, 0x00, 0x10, 0x1f, 0x20,
|
||||
0x03, 0xd5, 0x6a, 0x00, 0x00, 0x94, 0x80, 0x04, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x61, 0x15, 0x00, 0x50, 0x1f,
|
||||
0x20, 0x03, 0xd5, 0x4d, 0x00, 0x00, 0x94, 0xe0, 0x03, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x61, 0x15, 0x00, 0x10,
|
||||
0x1f, 0x20, 0x03, 0xd5, 0x48, 0x00, 0x00, 0x94, 0x40, 0x03, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x81, 0x15, 0x00,
|
||||
0x30, 0x1f, 0x20, 0x03, 0xd5, 0x43, 0x00, 0x00, 0x94, 0xa0, 0x02, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x81, 0x15,
|
||||
0x00, 0x70, 0x1f, 0x20, 0x03, 0xd5, 0x5e, 0x00, 0x00, 0x94, 0x00, 0x02, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x21,
|
||||
0x15, 0x00, 0x70, 0x1f, 0x20, 0x03, 0xd5, 0x59, 0x00, 0x00, 0x94, 0x60, 0x01, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa,
|
||||
0xc1, 0x14, 0x00, 0x70, 0x1f, 0x20, 0x03, 0xd5, 0x34, 0x00, 0x00, 0x94, 0xc0, 0x00, 0x00, 0x37, 0xe0, 0x03, 0x1c,
|
||||
0xaa, 0xc1, 0x14, 0x00, 0x50, 0x1f, 0x20, 0x03, 0xd5, 0x2f, 0x00, 0x00, 0x94, 0x40, 0x02, 0x00, 0x34, 0xba, 0x00,
|
||||
0x00, 0x34, 0xe8, 0x0f, 0x40, 0xf9, 0x49, 0x01, 0x80, 0x52, 0x09, 0x15, 0x00, 0x38, 0xe8, 0x0f, 0x00, 0xf9, 0x68,
|
||||
0x12, 0x17, 0x8b, 0x08, 0x05, 0x40, 0xf9, 0x81, 0x02, 0x08, 0x8b, 0xe0, 0x63, 0x00, 0x91, 0x4d, 0x00, 0x00, 0x94,
|
||||
0xe8, 0x0f, 0x40, 0xf9, 0x15, 0x15, 0x00, 0x38, 0xe8, 0x0f, 0x00, 0xf9, 0xe0, 0x63, 0x00, 0x91, 0xe1, 0x03, 0x1c,
|
||||
0xaa, 0x5e, 0x00, 0x00, 0x94, 0x5a, 0x07, 0x00, 0x11, 0x18, 0x07, 0x00, 0x11, 0xb1, 0xff, 0xff, 0x17, 0xe1, 0x23,
|
||||
0x41, 0xa9, 0x49, 0x01, 0x80, 0x52, 0x09, 0x15, 0x00, 0x38, 0xe8, 0x0f, 0x00, 0xf9, 0xe0, 0x63, 0x00, 0x91, 0x3e,
|
||||
0x00, 0x00, 0x94, 0xe8, 0x0f, 0x40, 0xf9, 0x29, 0x01, 0x80, 0x52, 0x09, 0x15, 0x00, 0x38, 0xe8, 0x0f, 0x00, 0xf9,
|
||||
0x21, 0x11, 0x00, 0x70, 0x1f, 0x20, 0x03, 0xd5, 0xe0, 0x63, 0x00, 0x91, 0x4d, 0x00, 0x00, 0x94, 0xe8, 0x0f, 0x40,
|
||||
0xf9, 0xe9, 0x07, 0x40, 0xf9, 0x00, 0x01, 0x09, 0xcb, 0x1f, 0x01, 0x00, 0x39, 0xfd, 0x7b, 0x47, 0xa9, 0xf4, 0x4f,
|
||||
0x46, 0xa9, 0xf6, 0x57, 0x45, 0xa9, 0xf8, 0x5f, 0x44, 0xa9, 0xfa, 0x67, 0x43, 0xa9, 0xfc, 0x6f, 0x42, 0xa9, 0xff,
|
||||
0x03, 0x02, 0x91, 0xc0, 0x03, 0x5f, 0xd6, 0xf6, 0x57, 0xbd, 0xa9, 0xf4, 0x4f, 0x01, 0xa9, 0xfd, 0x7b, 0x02, 0xa9,
|
||||
0xfd, 0x83, 0x00, 0x91, 0xf4, 0x03, 0x01, 0xaa, 0xf3, 0x03, 0x00, 0xaa, 0x35, 0x00, 0x40, 0x39, 0x68, 0x02, 0x40,
|
||||
0x39, 0x28, 0x01, 0x00, 0x34, 0x1f, 0x01, 0x15, 0x6b, 0xa1, 0x00, 0x00, 0x54, 0xe0, 0x03, 0x13, 0xaa, 0xe1, 0x03,
|
||||
0x14, 0xaa, 0x13, 0x00, 0x00, 0x94, 0x80, 0x00, 0x00, 0x37, 0x73, 0x06, 0x00, 0x91, 0xf7, 0xff, 0xff, 0x17, 0x13,
|
||||
0x00, 0x80, 0xd2, 0x7f, 0x02, 0x00, 0xf1, 0xe0, 0x07, 0x9f, 0x1a, 0xfd, 0x7b, 0x42, 0xa9, 0xf4, 0x4f, 0x41, 0xa9,
|
||||
0xf6, 0x57, 0xc3, 0xa8, 0xc0, 0x03, 0x5f, 0xd6, 0x08, 0x14, 0x40, 0x38, 0x29, 0x14, 0x40, 0x38, 0x1f, 0x01, 0x00,
|
||||
0x71, 0x00, 0x11, 0x49, 0x7a, 0x80, 0xff, 0xff, 0x54, 0x1f, 0x01, 0x09, 0x6b, 0xe0, 0x17, 0x9f, 0x1a, 0xc0, 0x03,
|
||||
0x5f, 0xd6, 0x28, 0x00, 0x40, 0x39, 0xa8, 0x00, 0x00, 0x34, 0x21, 0x04, 0x00, 0x91, 0x09, 0x14, 0x40, 0x38, 0x3f,
|
||||
0x01, 0x08, 0x6b, 0x60, 0xff, 0xff, 0x54, 0x1f, 0x01, 0x00, 0x71, 0xe0, 0x17, 0x9f, 0x1a, 0xc0, 0x03, 0x5f, 0xd6,
|
||||
0x09, 0x00, 0x80, 0x52, 0x08, 0x00, 0x40, 0xf9, 0x8a, 0x07, 0x80, 0x52, 0xab, 0x0a, 0x00, 0x10, 0x1f, 0x20, 0x03,
|
||||
0xd5, 0x5f, 0x11, 0x00, 0x31, 0x80, 0x01, 0x00, 0x54, 0x2c, 0x24, 0xca, 0x9a, 0x8c, 0x0d, 0x00, 0x72, 0x2d, 0x01,
|
||||
0x00, 0x12, 0xa0, 0x19, 0x40, 0x7a, 0x29, 0x15, 0x9f, 0x1a, 0x89, 0x00, 0x00, 0x36, 0x8c, 0x1d, 0x40, 0x92, 0x6c,
|
||||
0x69, 0x6c, 0x38, 0x0c, 0x15, 0x00, 0x38, 0x4a, 0x11, 0x00, 0x51, 0xf4, 0xff, 0xff, 0x17, 0x69, 0x00, 0x00, 0x37,
|
||||
0x09, 0x06, 0x80, 0x52, 0x09, 0x15, 0x00, 0x38, 0x08, 0x00, 0x00, 0xf9, 0xc0, 0x03, 0x5f, 0xd6, 0x08, 0x00, 0x40,
|
||||
0xf9, 0x29, 0x00, 0x40, 0x39, 0x89, 0x00, 0x00, 0x34, 0x21, 0x04, 0x00, 0x91, 0x09, 0x15, 0x00, 0x38, 0xfc, 0xff,
|
||||
0xff, 0x17, 0x08, 0x00, 0x00, 0xf9, 0xc0, 0x03, 0x5f, 0xd6, 0x6c, 0x69, 0x62, 0x64, 0x79, 0x6c, 0x64, 0x5f, 0x69,
|
||||
0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x00, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x57, 0x69,
|
||||
0x74, 0x68, 0x44, 0x79, 0x6c, 0x64, 0x49, 0x6e, 0x43, 0x61, 0x63, 0x68, 0x65, 0x00, 0x5f, 0x67, 0x50, 0x72, 0x6f,
|
||||
0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x00, 0x5f, 0x5f, 0x5a, 0x4e, 0x35, 0x64, 0x79, 0x6c, 0x64, 0x34,
|
||||
0x31, 0x32, 0x67, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x00, 0x6c, 0x61, 0x75,
|
||||
0x6e, 0x63, 0x68, 0x57, 0x69, 0x74, 0x68, 0x43, 0x6c, 0x6f, 0x73, 0x75, 0x72, 0x65, 0x00, 0x69, 0x6e, 0x69, 0x74,
|
||||
0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4d, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c,
|
||||
0x65, 0x00, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x48, 0x65, 0x6c,
|
||||
0x70, 0x65, 0x72, 0x73, 0x00, 0x5f, 0x64, 0x6c, 0x6f, 0x70, 0x65, 0x6e, 0x00, 0x5f, 0x73, 0x74, 0x72, 0x63, 0x6d,
|
||||
0x70, 0x00, 0x64, 0x6f, 0x4d, 0x6f, 0x64, 0x49, 0x6e, 0x69, 0x74, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x00, 0x64, 0x6f, 0x47, 0x65, 0x74, 0x44, 0x4f, 0x46, 0x53, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x00,
|
||||
0x64, 0x79, 0x6c, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x5f, 0x5f,
|
||||
0x4c, 0x49, 0x4e, 0x4b, 0x45, 0x44, 0x49, 0x54, 0x00, 0x00, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x0b, 0x40, 0xb9, 0x13, 0x01, 0x09, 0x8b, 0x89, 0x13, 0x40, 0xb9, 0x19, 0x01, 0x09, 0x8b, 0xe8, 0x07, 0x40, 0xf9,
|
||||
0xe8, 0x0f, 0x00, 0xf9, 0x16, 0x00, 0x00, 0x90, 0xd6, 0x72, 0x0f, 0x91, 0x35, 0x01, 0x80, 0x52, 0x78, 0x0b, 0x40,
|
||||
0xb9, 0x68, 0x0f, 0x40, 0xb9, 0x1f, 0x03, 0x08, 0x6b, 0xc0, 0x0a, 0x00, 0x54, 0x77, 0x52, 0x38, 0x8b, 0xe8, 0x02,
|
||||
0x40, 0xb9, 0x3c, 0x03, 0x08, 0x8b, 0xe0, 0x03, 0x1c, 0xaa, 0xe1, 0x03, 0x16, 0xaa, 0x6a, 0x00, 0x00, 0x94, 0xa0,
|
||||
0x07, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00, 0x90, 0x21, 0xbc, 0x0f, 0x91, 0x65, 0x00, 0x00, 0x94,
|
||||
0x00, 0x07, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00, 0x90, 0x21, 0xf8, 0x0f, 0x91, 0x60, 0x00, 0x00,
|
||||
0x94, 0x60, 0x06, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00, 0x90, 0x21, 0x54, 0x10, 0x91, 0x72, 0x00,
|
||||
0x00, 0x94, 0xc0, 0x05, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00, 0x90, 0x21, 0x8c, 0x10, 0x91, 0x6d,
|
||||
0x00, 0x00, 0x94, 0x20, 0x05, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00, 0x90, 0x21, 0xf4, 0x10, 0x91,
|
||||
0x68, 0x00, 0x00, 0x94, 0x80, 0x04, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00, 0x90, 0x21, 0x40, 0x11,
|
||||
0x91, 0x4c, 0x00, 0x00, 0x94, 0xe0, 0x03, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00, 0x90, 0x21, 0x88,
|
||||
0x11, 0x91, 0x47, 0x00, 0x00, 0x94, 0x40, 0x03, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00, 0x90, 0x21,
|
||||
0xec, 0x11, 0x91, 0x42, 0x00, 0x00, 0x94, 0xa0, 0x02, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00, 0x90,
|
||||
0x21, 0x44, 0x12, 0x91, 0x5c, 0x00, 0x00, 0x94, 0x00, 0x02, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00, 0x00,
|
||||
0x90, 0x21, 0x64, 0x12, 0x91, 0x57, 0x00, 0x00, 0x94, 0x60, 0x01, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01, 0x00,
|
||||
0x00, 0x90, 0x21, 0x84, 0x12, 0x91, 0x33, 0x00, 0x00, 0x94, 0xc0, 0x00, 0x00, 0x37, 0xe0, 0x03, 0x1c, 0xaa, 0x01,
|
||||
0x00, 0x00, 0x90, 0x21, 0xd0, 0x12, 0x91, 0x2e, 0x00, 0x00, 0x94, 0x20, 0x02, 0x00, 0x34, 0xba, 0x00, 0x00, 0x34,
|
||||
0xe8, 0x0f, 0x40, 0xf9, 0x49, 0x01, 0x80, 0x52, 0x09, 0x15, 0x00, 0x38, 0xe8, 0x0f, 0x00, 0xf9, 0xe8, 0x06, 0x40,
|
||||
0xf9, 0x81, 0x02, 0x08, 0x8b, 0xe0, 0x63, 0x00, 0x91, 0x4c, 0x00, 0x00, 0x94, 0xe8, 0x0f, 0x40, 0xf9, 0x15, 0x15,
|
||||
0x00, 0x38, 0xe8, 0x0f, 0x00, 0xf9, 0xe0, 0x63, 0x00, 0x91, 0xe1, 0x03, 0x1c, 0xaa, 0x5d, 0x00, 0x00, 0x94, 0x5a,
|
||||
0x07, 0x00, 0x11, 0x18, 0x07, 0x00, 0x11, 0xa9, 0xff, 0xff, 0x17, 0xe1, 0x23, 0x41, 0xa9, 0x49, 0x01, 0x80, 0x52,
|
||||
0x09, 0x15, 0x00, 0x38, 0xe8, 0x0f, 0x00, 0xf9, 0xe0, 0x63, 0x00, 0x91, 0x3d, 0x00, 0x00, 0x94, 0xe8, 0x0f, 0x40,
|
||||
0xf9, 0x29, 0x01, 0x80, 0x52, 0x09, 0x15, 0x00, 0x38, 0xe8, 0x0f, 0x00, 0xf9, 0x01, 0x00, 0x00, 0x90, 0x21, 0x14,
|
||||
0x13, 0x91, 0xe0, 0x63, 0x00, 0x91, 0x4c, 0x00, 0x00, 0x94, 0xe8, 0x0f, 0x40, 0xf9, 0xe9, 0x07, 0x40, 0xf9, 0x00,
|
||||
0x01, 0x09, 0xcb, 0x1f, 0x01, 0x00, 0x39, 0xfd, 0x7b, 0x47, 0xa9, 0xf4, 0x4f, 0x46, 0xa9, 0xf6, 0x57, 0x45, 0xa9,
|
||||
0xf8, 0x5f, 0x44, 0xa9, 0xfa, 0x67, 0x43, 0xa9, 0xfc, 0x6f, 0x42, 0xa9, 0xff, 0x03, 0x02, 0x91, 0xc0, 0x03, 0x5f,
|
||||
0xd6, 0xf6, 0x57, 0xbd, 0xa9, 0xf4, 0x4f, 0x01, 0xa9, 0xfd, 0x7b, 0x02, 0xa9, 0xfd, 0x83, 0x00, 0x91, 0xf3, 0x03,
|
||||
0x01, 0xaa, 0xf4, 0x03, 0x00, 0xaa, 0x35, 0x00, 0x40, 0x39, 0x96, 0x02, 0x40, 0x39, 0x36, 0x01, 0x00, 0x34, 0xdf,
|
||||
0x02, 0x15, 0x6b, 0xa1, 0x00, 0x00, 0x54, 0xe0, 0x03, 0x14, 0xaa, 0xe1, 0x03, 0x13, 0xaa, 0x12, 0x00, 0x00, 0x94,
|
||||
0x60, 0x00, 0x00, 0x37, 0x94, 0x06, 0x00, 0x91, 0xf7, 0xff, 0xff, 0x17, 0xdf, 0x02, 0x00, 0x71, 0xe0, 0x07, 0x9f,
|
||||
0x1a, 0xfd, 0x7b, 0x42, 0xa9, 0xf4, 0x4f, 0x41, 0xa9, 0xf6, 0x57, 0xc3, 0xa8, 0xc0, 0x03, 0x5f, 0xd6, 0x08, 0x14,
|
||||
0x40, 0x38, 0x29, 0x14, 0x40, 0x38, 0x1f, 0x01, 0x00, 0x71, 0x00, 0x11, 0x49, 0x7a, 0x80, 0xff, 0xff, 0x54, 0x1f,
|
||||
0x01, 0x09, 0x6b, 0xe0, 0x17, 0x9f, 0x1a, 0xc0, 0x03, 0x5f, 0xd6, 0x28, 0x00, 0x40, 0x39, 0xa8, 0x00, 0x00, 0x34,
|
||||
0x21, 0x04, 0x00, 0x91, 0x09, 0x14, 0x40, 0x38, 0x3f, 0x01, 0x08, 0x6b, 0x60, 0xff, 0xff, 0x54, 0x1f, 0x01, 0x00,
|
||||
0x71, 0xe0, 0x17, 0x9f, 0x1a, 0xc0, 0x03, 0x5f, 0xd6, 0x09, 0x00, 0x80, 0x52, 0x08, 0x00, 0x40, 0xf9, 0x8a, 0x07,
|
||||
0x80, 0x52, 0x0b, 0x00, 0x00, 0x90, 0x6b, 0xc1, 0x13, 0x91, 0x5f, 0x11, 0x00, 0x31, 0x80, 0x01, 0x00, 0x54, 0x2c,
|
||||
0x24, 0xca, 0x9a, 0x8c, 0x0d, 0x40, 0x92, 0x2d, 0x01, 0x00, 0x12, 0x9f, 0x01, 0x00, 0x71, 0xa0, 0x19, 0x40, 0x7a,
|
||||
0x29, 0x15, 0x9f, 0x1a, 0x69, 0x00, 0x00, 0x36, 0x6c, 0x69, 0x6c, 0x38, 0x0c, 0x15, 0x00, 0x38, 0x4a, 0x11, 0x00,
|
||||
0x51, 0xf4, 0xff, 0xff, 0x17, 0x69, 0x00, 0x00, 0x37, 0x09, 0x06, 0x80, 0x52, 0x09, 0x15, 0x00, 0x38, 0x08, 0x00,
|
||||
0x00, 0xf9, 0xc0, 0x03, 0x5f, 0xd6, 0x08, 0x00, 0x40, 0xf9, 0x29, 0x00, 0x40, 0x39, 0x89, 0x00, 0x00, 0x34, 0x21,
|
||||
0x04, 0x00, 0x91, 0x09, 0x15, 0x00, 0x38, 0xfc, 0xff, 0xff, 0x17, 0x08, 0x00, 0x00, 0xf9, 0xc0, 0x03, 0x5f, 0xd6,
|
||||
0x6c, 0x69, 0x62, 0x64, 0x79, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x00,
|
||||
0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x4f, 0x62, 0x6a, 0x43, 0x49, 0x6e, 0x69, 0x74, 0x00, 0x72, 0x65, 0x73, 0x74,
|
||||
0x61, 0x72, 0x74, 0x57, 0x69, 0x74, 0x68, 0x44, 0x79, 0x6c, 0x64, 0x49, 0x6e, 0x43, 0x61, 0x63, 0x68, 0x65, 0x00,
|
||||
0x5f, 0x67, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x00, 0x5f, 0x5f, 0x5a, 0x4e, 0x35,
|
||||
0x64, 0x79, 0x6c, 0x64, 0x34, 0x31, 0x32, 0x67, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66, 0x6f,
|
||||
0x45, 0x00, 0x5f, 0x5f, 0x5a, 0x4c, 0x31, 0x32, 0x73, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x49, 0x6e, 0x66,
|
||||
0x6f, 0x00, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x57, 0x69, 0x74, 0x68, 0x43, 0x6c, 0x6f, 0x73, 0x75, 0x72, 0x65,
|
||||
0x00, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4d, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63,
|
||||
0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x00, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x54, 0x68, 0x72, 0x65,
|
||||
0x61, 0x64, 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x73, 0x00, 0x5f, 0x64, 0x6c, 0x6f, 0x70, 0x65, 0x6e, 0x00, 0x5f,
|
||||
0x73, 0x74, 0x72, 0x63, 0x6d, 0x70, 0x00, 0x64, 0x6f, 0x4d, 0x6f, 0x64, 0x49, 0x6e, 0x69, 0x74, 0x46, 0x75, 0x6e,
|
||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x00, 0x64, 0x6f, 0x47, 0x65, 0x74, 0x44, 0x4f, 0x46, 0x53, 0x65, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x00, 0x64, 0x79, 0x6c, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x5f, 0x5f, 0x54, 0x45,
|
||||
0x58, 0x54, 0x00, 0x5f, 0x5f, 0x4c, 0x49, 0x4e, 0x4b, 0x45, 0x44, 0x49, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66
|
||||
};
|
||||
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public class InstallationProxyClient : Object, AsyncInitable {
|
||||
public LockdownClient lockdown {
|
||||
public Device device {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private PlistServiceClient service;
|
||||
|
||||
private InstallationProxyClient (LockdownClient lockdown) {
|
||||
Object (lockdown: lockdown);
|
||||
private InstallationProxyClient (Device device) {
|
||||
Object (device: device);
|
||||
}
|
||||
|
||||
public static async InstallationProxyClient open (LockdownClient lockdown, Cancellable? cancellable = null) throws InstallationProxyError, IOError {
|
||||
var client = new InstallationProxyClient (lockdown);
|
||||
public static async InstallationProxyClient open (Device device, Cancellable? cancellable = null) throws Error, IOError {
|
||||
var client = new InstallationProxyClient (device);
|
||||
|
||||
try {
|
||||
yield client.init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_local_error (e);
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws InstallationProxyError, IOError {
|
||||
try {
|
||||
var stream = yield lockdown.start_service ("com.apple.mobile.installation_proxy", cancellable);
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
|
||||
var stream = yield device.open_lockdown_service ("com.apple.mobile.installation_proxy", cancellable);
|
||||
|
||||
service = new PlistServiceClient (stream);
|
||||
} catch (LockdownError e) {
|
||||
throw new InstallationProxyError.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
service = new PlistServiceClient (stream);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -40,8 +36,7 @@ namespace Frida.Fruity {
|
||||
yield service.close (cancellable);
|
||||
}
|
||||
|
||||
public async Gee.ArrayList<ApplicationDetails> browse (Cancellable? cancellable = null)
|
||||
throws InstallationProxyError, IOError {
|
||||
public async Gee.ArrayList<ApplicationDetails> browse (Cancellable? cancellable = null) throws Error, IOError {
|
||||
try {
|
||||
var result = new Gee.ArrayList<ApplicationDetails> ();
|
||||
|
||||
@@ -75,7 +70,7 @@ namespace Frida.Fruity {
|
||||
}
|
||||
|
||||
public async Gee.HashMap<string, ApplicationDetails> lookup (PlistDict query, Cancellable? cancellable = null)
|
||||
throws InstallationProxyError, IOError {
|
||||
throws Error, IOError {
|
||||
try {
|
||||
var result = new Gee.HashMap<string, ApplicationDetails> ();
|
||||
|
||||
@@ -189,28 +184,13 @@ namespace Frida.Fruity {
|
||||
return new ApplicationDetails (identifier, name, version, build, path, containers, debuggable);
|
||||
}
|
||||
|
||||
private static void throw_local_error (GLib.Error e) throws InstallationProxyError, IOError {
|
||||
if (e is InstallationProxyError)
|
||||
throw (InstallationProxyError) e;
|
||||
|
||||
if (e is IOError)
|
||||
throw (IOError) e;
|
||||
|
||||
assert_not_reached ();
|
||||
private static Error error_from_service (PlistServiceError e) {
|
||||
return new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
private static InstallationProxyError error_from_service (PlistServiceError e) {
|
||||
return new InstallationProxyError.PROTOCOL ("%s", e.message);
|
||||
private static Error error_from_plist (PlistError e) {
|
||||
return new Error.PROTOCOL ("Unexpected response: %s", e.message);
|
||||
}
|
||||
|
||||
private static InstallationProxyError error_from_plist (PlistError e) {
|
||||
return new InstallationProxyError.PROTOCOL ("Unexpected response: %s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public errordomain InstallationProxyError {
|
||||
INVALID_ARGUMENT,
|
||||
PROTOCOL
|
||||
}
|
||||
|
||||
public class ApplicationDetails : Object {
|
||||
|
||||
@@ -23,6 +23,11 @@ namespace Frida.Fruity {
|
||||
}
|
||||
|
||||
public class NSNumber : NSObject {
|
||||
public Kind kind {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool boolean {
|
||||
get;
|
||||
private set;
|
||||
@@ -33,14 +38,44 @@ namespace Frida.Fruity {
|
||||
private set;
|
||||
}
|
||||
|
||||
public double number {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public enum Kind {
|
||||
BOOLEAN,
|
||||
INTEGER,
|
||||
FLOAT,
|
||||
DOUBLE,
|
||||
}
|
||||
|
||||
public NSNumber.from_boolean (bool val) {
|
||||
kind = BOOLEAN;
|
||||
boolean = val;
|
||||
integer = val ? 1 : 0;
|
||||
number = val ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
public NSNumber.from_integer (int64 val) {
|
||||
kind = INTEGER;
|
||||
boolean = (val != 0) ? true : false;
|
||||
integer = val;
|
||||
number = val;
|
||||
}
|
||||
|
||||
public NSNumber.from_float (float val) {
|
||||
kind = FLOAT;
|
||||
boolean = (val != 0.0f) ? true : false;
|
||||
integer = (int64) val;
|
||||
number = val;
|
||||
}
|
||||
|
||||
public NSNumber.from_double (double val) {
|
||||
kind = DOUBLE;
|
||||
boolean = (val != 0.0) ? true : false;
|
||||
integer = (int64) val;
|
||||
number = val;
|
||||
}
|
||||
|
||||
public override uint hash () {
|
||||
@@ -51,7 +86,21 @@ namespace Frida.Fruity {
|
||||
var other_number = other as NSNumber;
|
||||
if (other_number == null)
|
||||
return false;
|
||||
return other_number.integer == integer;
|
||||
|
||||
if (other_number.kind != kind)
|
||||
return false;
|
||||
|
||||
switch (kind) {
|
||||
case BOOLEAN:
|
||||
return other_number.boolean == boolean;
|
||||
case INTEGER:
|
||||
return other_number.integer == integer;
|
||||
case FLOAT:
|
||||
case DOUBLE:
|
||||
return other_number.number == number;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string to_string () {
|
||||
@@ -85,6 +134,32 @@ namespace Frida.Fruity {
|
||||
}
|
||||
}
|
||||
|
||||
public class NSData : NSObject {
|
||||
public Bytes bytes {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public NSData (Bytes bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public override uint hash () {
|
||||
return bytes.hash ();
|
||||
}
|
||||
|
||||
public override bool is_equal_to (NSObject other) {
|
||||
var other_data = other as NSData;
|
||||
if (other_data == null)
|
||||
return false;
|
||||
return other_data.bytes.compare (bytes) == 0;
|
||||
}
|
||||
|
||||
public override string to_string () {
|
||||
return "NSData";
|
||||
}
|
||||
}
|
||||
|
||||
public class NSDictionary : NSObject {
|
||||
public int size {
|
||||
get {
|
||||
@@ -198,6 +273,10 @@ namespace Frida.Fruity {
|
||||
public NSArray (Gee.ArrayList<NSObject>? storage = null) {
|
||||
this.storage = (storage != null) ? storage : new Gee.ArrayList<NSObject> (NSObject.equal_func);
|
||||
}
|
||||
|
||||
public void add_object (NSObject obj) {
|
||||
storage.add (obj);
|
||||
}
|
||||
}
|
||||
|
||||
public class NSDate : NSObject {
|
||||
@@ -242,11 +321,23 @@ namespace Frida.Fruity {
|
||||
}
|
||||
}
|
||||
|
||||
public class DTTapMessage : NSObject {
|
||||
public NSDictionary plist {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public DTTapMessage (NSDictionary plist) {
|
||||
this.plist = plist;
|
||||
}
|
||||
}
|
||||
|
||||
namespace NSKeyedArchive {
|
||||
private Gee.HashMap<Type, EncodeFunc> encoders;
|
||||
private Gee.HashMap<string, DecodeFunc> decoders;
|
||||
|
||||
private const string[] DICTIONARY_CLASS = { "NSDictionary", "NSObject" };
|
||||
private const string[] ARRAY_CLASS = { "NSArray", "NSObject" };
|
||||
|
||||
[CCode (has_target = false)]
|
||||
private delegate PlistUid EncodeFunc (NSObject instance, EncodingContext ctx);
|
||||
@@ -319,9 +410,18 @@ namespace Frida.Fruity {
|
||||
if (t == typeof (int64))
|
||||
return new NSNumber.from_integer (val.get_int64 ());
|
||||
|
||||
if (t == typeof (float))
|
||||
return new NSNumber.from_float (val.get_float ());
|
||||
|
||||
if (t == typeof (double))
|
||||
return new NSNumber.from_double (val.get_double ());
|
||||
|
||||
if (t == typeof (string))
|
||||
return new NSString (val.get_string ());
|
||||
|
||||
if (t == typeof (Bytes))
|
||||
return new NSData ((Bytes) val.get_boxed ());
|
||||
|
||||
if (t == typeof (PlistDict)) {
|
||||
var instance = (PlistDict) val.get_object ();
|
||||
var klass = objects.get_dict ((int) instance.get_uid ("$class").uid);
|
||||
@@ -354,6 +454,7 @@ namespace Frida.Fruity {
|
||||
encoders[typeof (NSNumber)] = encode_number;
|
||||
encoders[typeof (NSString)] = encode_string;
|
||||
encoders[typeof (NSDictionary)] = encode_dictionary;
|
||||
encoders[typeof (NSArray)] = encode_array;
|
||||
}
|
||||
|
||||
private static void ensure_decoders_registered () {
|
||||
@@ -365,11 +466,41 @@ namespace Frida.Fruity {
|
||||
decoders["NSArray"] = decode_array;
|
||||
decoders["NSDate"] = decode_date;
|
||||
decoders["NSError"] = decode_error;
|
||||
decoders["DTTapMessage"] = decode_tap_message;
|
||||
decoders["DTSysmonTapMessage"] = decode_tap_message;
|
||||
decoders["DTActivityTraceTapMessage"] = decode_tap_message;
|
||||
decoders["DTTapStatusMessage"] = decode_tap_message;
|
||||
decoders["DTTapHeartbeatMessage"] = decode_tap_message;
|
||||
decoders["DTKTraceTapMessage"] = decode_tap_message;
|
||||
}
|
||||
|
||||
private static PlistUid encode_number (NSObject instance, EncodingContext ctx) {
|
||||
int64 val = ((NSNumber) instance).integer;
|
||||
var n = (NSNumber) instance;
|
||||
switch (n.kind) {
|
||||
case BOOLEAN:
|
||||
return encode_boolean (n.boolean, ctx);
|
||||
case INTEGER:
|
||||
return encode_integer (n.integer, ctx);
|
||||
case FLOAT:
|
||||
return encode_float (n.integer, ctx);
|
||||
case DOUBLE:
|
||||
return encode_double (n.integer, ctx);
|
||||
}
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
private static PlistUid encode_boolean (bool val, EncodingContext ctx) {
|
||||
var uid = ctx.find_existing_object (e => e.holds (typeof (bool)) && e.get_boolean () == val);
|
||||
if (uid != null)
|
||||
return uid;
|
||||
|
||||
var objects = ctx.objects;
|
||||
uid = new PlistUid (objects.length);
|
||||
objects.add_boolean (val);
|
||||
return uid;
|
||||
}
|
||||
|
||||
private static PlistUid encode_integer (int64 val, EncodingContext ctx) {
|
||||
var uid = ctx.find_existing_object (e => e.holds (typeof (int64)) && e.get_int64 () == val);
|
||||
if (uid != null)
|
||||
return uid;
|
||||
@@ -380,6 +511,28 @@ namespace Frida.Fruity {
|
||||
return uid;
|
||||
}
|
||||
|
||||
private static PlistUid encode_float (float val, EncodingContext ctx) {
|
||||
var uid = ctx.find_existing_object (e => e.holds (typeof (float)) && e.get_float () == val);
|
||||
if (uid != null)
|
||||
return uid;
|
||||
|
||||
var objects = ctx.objects;
|
||||
uid = new PlistUid (objects.length);
|
||||
objects.add_float (val);
|
||||
return uid;
|
||||
}
|
||||
|
||||
private static PlistUid encode_double (double val, EncodingContext ctx) {
|
||||
var uid = ctx.find_existing_object (e => e.holds (typeof (double)) && e.get_double () == val);
|
||||
if (uid != null)
|
||||
return uid;
|
||||
|
||||
var objects = ctx.objects;
|
||||
uid = new PlistUid (objects.length);
|
||||
objects.add_double (val);
|
||||
return uid;
|
||||
}
|
||||
|
||||
private static PlistUid encode_string (NSObject instance, EncodingContext ctx) {
|
||||
string str = ((NSString) instance).str;
|
||||
|
||||
@@ -451,6 +604,21 @@ namespace Frida.Fruity {
|
||||
}
|
||||
}
|
||||
|
||||
private static PlistUid encode_array (NSObject instance, EncodingContext ctx) {
|
||||
NSArray array = (NSArray) instance;
|
||||
|
||||
var object = new PlistDict ();
|
||||
var uid = ctx.add_object (object);
|
||||
|
||||
var objs = new PlistArray ();
|
||||
foreach (var element in array.elements)
|
||||
objs.add_uid (encode_value (element, ctx));
|
||||
object.set_array ("NS.objects", objs);
|
||||
object.set_uid ("$class", ctx.get_class (ARRAY_CLASS));
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
private static NSObject decode_array (PlistDict instance, DecodingContext ctx) throws Error, PlistError {
|
||||
var objs = instance.get_array ("NS.objects");
|
||||
|
||||
@@ -486,6 +654,10 @@ namespace Frida.Fruity {
|
||||
return new NSError (domain, code, (NSDictionary) user_info);
|
||||
}
|
||||
|
||||
private static NSObject decode_tap_message (PlistDict instance, DecodingContext ctx) throws Error, PlistError {
|
||||
return new DTTapMessage ((NSDictionary) decode_value (instance.get_uid ("DTTapMessagePlist"), ctx));
|
||||
}
|
||||
|
||||
private class EncodingContext {
|
||||
public PlistArray objects;
|
||||
|
||||
|
||||
+29
-41
@@ -1,18 +1,14 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public class LockdownClient : Object, AsyncInitable {
|
||||
public class LockdownClient : Object {
|
||||
public signal void closed ();
|
||||
|
||||
public DeviceDetails device_details {
|
||||
public PlistServiceClient service {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public IOStream stream {
|
||||
get { return service.stream; }
|
||||
}
|
||||
|
||||
private PlistServiceClient service;
|
||||
private UsbmuxDevice? usbmux_device;
|
||||
private Plist? pair_record;
|
||||
private string? host_id;
|
||||
private string? system_buid;
|
||||
@@ -22,29 +18,20 @@ namespace Frida.Fruity {
|
||||
|
||||
private const uint16 LOCKDOWN_PORT = 62078;
|
||||
|
||||
private LockdownClient (DeviceDetails device_details) {
|
||||
Object (device_details: device_details);
|
||||
public LockdownClient (IOStream stream) {
|
||||
Object (service: new PlistServiceClient (stream));
|
||||
}
|
||||
|
||||
public static async LockdownClient open (DeviceDetails device_details, Cancellable? cancellable = null)
|
||||
construct {
|
||||
service.closed.connect (on_service_closed);
|
||||
}
|
||||
|
||||
public static async LockdownClient open (UsbmuxDevice device, Cancellable? cancellable = null)
|
||||
throws LockdownError, IOError {
|
||||
var client = new LockdownClient (device_details);
|
||||
|
||||
try {
|
||||
yield client.init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_local_error (e);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws LockdownError, IOError {
|
||||
var device = device_details;
|
||||
|
||||
try {
|
||||
var usbmux = yield UsbmuxClient.open (cancellable);
|
||||
|
||||
Plist pair_record;
|
||||
try {
|
||||
pair_record = yield usbmux.read_pair_record (device.udid, cancellable);
|
||||
} catch (UsbmuxError e) {
|
||||
@@ -52,6 +39,10 @@ namespace Frida.Fruity {
|
||||
throw new LockdownError.NOT_PAIRED ("Not paired");
|
||||
throw e;
|
||||
}
|
||||
|
||||
string? host_id = null;
|
||||
string? system_buid = null;
|
||||
TlsCertificate? tls_certificate = null;
|
||||
try {
|
||||
host_id = pair_record.get_string ("HostID");
|
||||
system_buid = pair_record.get_string ("SystemBUID");
|
||||
@@ -64,15 +55,19 @@ namespace Frida.Fruity {
|
||||
|
||||
yield usbmux.connect_to_port (device.id, LOCKDOWN_PORT, cancellable);
|
||||
|
||||
service = new PlistServiceClient (usbmux.connection);
|
||||
service.closed.connect (on_service_closed);
|
||||
var client = new LockdownClient (usbmux.connection);
|
||||
client.usbmux_device = device;
|
||||
client.pair_record = pair_record;
|
||||
client.host_id = host_id;
|
||||
client.system_buid = system_buid;
|
||||
client.tls_certificate = tls_certificate;
|
||||
|
||||
yield query_type (cancellable);
|
||||
yield client.query_type (cancellable);
|
||||
|
||||
return client;
|
||||
} catch (UsbmuxError e) {
|
||||
throw new LockdownError.UNSUPPORTED ("%s", e.message);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable = null) throws IOError {
|
||||
@@ -182,7 +177,7 @@ namespace Frida.Fruity {
|
||||
bool enable_encryption = response.has ("EnableServiceSSL") && response.get_boolean ("EnableServiceSSL");
|
||||
|
||||
var client = yield UsbmuxClient.open (cancellable);
|
||||
yield client.connect_to_port (device_details.id, (uint16) response.get_integer ("Port"), cancellable);
|
||||
yield client.connect_to_port (usbmux_device.id, (uint16) response.get_integer ("Port"), cancellable);
|
||||
|
||||
SocketConnection raw_connection = client.connection;
|
||||
IOStream stream = raw_connection;
|
||||
@@ -242,7 +237,7 @@ namespace Frida.Fruity {
|
||||
|
||||
try {
|
||||
var usbmux = yield UsbmuxClient.open (cancellable);
|
||||
yield usbmux.delete_pair_record (device_details.udid, cancellable);
|
||||
yield usbmux.delete_pair_record (usbmux_device.udid, cancellable);
|
||||
} catch (UsbmuxError e) {
|
||||
if (!(e is UsbmuxError.INVALID_ARGUMENT))
|
||||
throw new LockdownError.PROTOCOL ("%s", e.message);
|
||||
@@ -269,17 +264,9 @@ namespace Frida.Fruity {
|
||||
return request;
|
||||
}
|
||||
|
||||
private static void throw_local_error (GLib.Error e) throws LockdownError, IOError {
|
||||
if (e is LockdownError)
|
||||
throw (LockdownError) e;
|
||||
|
||||
if (e is IOError)
|
||||
throw (IOError) e;
|
||||
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
private static LockdownError error_from_service (PlistServiceError e) {
|
||||
if (e is PlistServiceError.CONNECTION_CLOSED)
|
||||
return new LockdownError.CONNECTION_CLOSED ("%s", e.message);
|
||||
return new LockdownError.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
@@ -289,6 +276,7 @@ namespace Frida.Fruity {
|
||||
}
|
||||
|
||||
public errordomain LockdownError {
|
||||
CONNECTION_CLOSED,
|
||||
INVALID_SERVICE,
|
||||
NOT_PAIRED,
|
||||
UNSUPPORTED,
|
||||
|
||||
@@ -0,0 +1,551 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
internal sealed class UsbNcmDriver : Object, AsyncInitable {
|
||||
public UsbDevice device {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public UsbNcmConfig config {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public VirtualNetworkStack netstack {
|
||||
get {
|
||||
return _netstack;
|
||||
}
|
||||
}
|
||||
|
||||
public InetAddress? remote_ipv6_address {
|
||||
get {
|
||||
return _remote_ipv6_address;
|
||||
}
|
||||
}
|
||||
|
||||
private uint32 ntb_in_max_size;
|
||||
private uint32 ntb_out_max_size;
|
||||
private uint16 ndp_out_divisor;
|
||||
private uint16 ndp_out_payload_remainder;
|
||||
private uint16 ndp_out_alignment;
|
||||
private uint16 ntb_out_max_datagrams;
|
||||
private size_t max_in_transfers;
|
||||
private size_t max_out_transfers;
|
||||
private VirtualNetworkStack? _netstack;
|
||||
private Gee.Queue<Bytes> pending_output = new Gee.ArrayQueue<Bytes> ();
|
||||
private bool writing = false;
|
||||
private uint16 next_outgoing_sequence = 1;
|
||||
|
||||
private InetAddress? _remote_ipv6_address;
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
private const uint16 TRANSFER_HEADER_SIZE = 4 + 2 + 2 + 2 + 2;
|
||||
private const size_t MAX_TRANSFER_MEMORY = 60 * 1518;
|
||||
|
||||
private enum CdcRequest {
|
||||
GET_NTB_PARAMETERS = 0x80,
|
||||
SET_NTB_INPUT_SIZE = 0x86,
|
||||
}
|
||||
|
||||
private const uint16 NTB_PARAMETERS_MIN_SIZE = 28;
|
||||
|
||||
private enum EtherType {
|
||||
IPV6 = 0x86dd,
|
||||
}
|
||||
|
||||
private enum IPV6NextHeader {
|
||||
UDP = 0x11,
|
||||
}
|
||||
|
||||
public static async UsbNcmDriver open (UsbDevice device, UsbNcmConfig config, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
var driver = new UsbNcmDriver (device, config);
|
||||
|
||||
try {
|
||||
yield driver.init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
return driver;
|
||||
}
|
||||
|
||||
private UsbNcmDriver (UsbDevice device, UsbNcmConfig config) {
|
||||
Object (device: device, config: config);
|
||||
}
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
|
||||
var language_id = yield device.query_default_language_id (cancellable);
|
||||
|
||||
uint8 mac_address[6];
|
||||
string mac_address_str = yield device.read_string_descriptor (config.mac_address_index, language_id, cancellable);
|
||||
if (mac_address_str.length != 12)
|
||||
throw new Error.PROTOCOL ("Invalid MAC address");
|
||||
for (uint i = 0; i != 6; i++) {
|
||||
uint v;
|
||||
mac_address_str.substring (i * 2, 2).scanf ("%02X", out v);
|
||||
mac_address[i] = (uint8) v;
|
||||
}
|
||||
|
||||
unowned LibUSB.DeviceHandle handle = device.handle;
|
||||
try {
|
||||
Usb.check (handle.claim_interface (config.ctrl_iface), "Failed to claim control interface");
|
||||
Usb.check (handle.claim_interface (config.data_iface), "Failed to claim data interface");
|
||||
} catch (Error e) {
|
||||
throw new Error.PERMISSION_DENIED ("%s",
|
||||
make_user_error_message (@"Unable to claim USB CDC-NCM interface ($(e.message))"));
|
||||
}
|
||||
|
||||
uint8 raw_ntb_params[NTB_PARAMETERS_MIN_SIZE];
|
||||
var raw_ntb_params_size = yield device.control_transfer (
|
||||
LibUSB.RequestRecipient.INTERFACE | LibUSB.RequestType.CLASS | LibUSB.EndpointDirection.IN,
|
||||
CdcRequest.GET_NTB_PARAMETERS,
|
||||
0,
|
||||
config.ctrl_iface,
|
||||
raw_ntb_params,
|
||||
1000,
|
||||
cancellable);
|
||||
if (raw_ntb_params_size < NTB_PARAMETERS_MIN_SIZE)
|
||||
throw new Error.PROTOCOL ("Truncated NTB parameters response");
|
||||
var ntb_params = new Buffer (new Bytes (raw_ntb_params[:raw_ntb_params_size]), LITTLE_ENDIAN);
|
||||
uint32 device_ntb_in_max_size = ntb_params.read_uint32 (4);
|
||||
ntb_in_max_size = uint32.min (device_ntb_in_max_size, 16384);
|
||||
ntb_out_max_size = uint32.min (ntb_params.read_uint32 (16), 16384);
|
||||
ndp_out_divisor = ntb_params.read_uint16 (20);
|
||||
ndp_out_payload_remainder = ntb_params.read_uint16 (22);
|
||||
ndp_out_alignment = ntb_params.read_uint16 (24);
|
||||
ntb_out_max_datagrams = uint16.min (ntb_params.read_uint16 (26), 16);
|
||||
|
||||
if (ntb_in_max_size != device_ntb_in_max_size) {
|
||||
var ntb_size_buf = new BufferBuilder (LITTLE_ENDIAN)
|
||||
.append_uint32 (ntb_in_max_size)
|
||||
.build ();
|
||||
yield device.control_transfer (
|
||||
LibUSB.RequestRecipient.INTERFACE | LibUSB.RequestType.CLASS | LibUSB.EndpointDirection.OUT,
|
||||
CdcRequest.SET_NTB_INPUT_SIZE,
|
||||
0,
|
||||
config.ctrl_iface,
|
||||
ntb_size_buf.get_data (),
|
||||
1000,
|
||||
cancellable);
|
||||
}
|
||||
|
||||
var speed = device.raw_device.get_device_speed ();
|
||||
if (speed >= LibUSB.Speed.SUPER) {
|
||||
max_in_transfers = (5 * MAX_TRANSFER_MEMORY) / ntb_in_max_size;
|
||||
max_out_transfers = (5 * MAX_TRANSFER_MEMORY) / ntb_out_max_size;
|
||||
} else if (speed == LibUSB.Speed.HIGH) {
|
||||
max_in_transfers = MAX_TRANSFER_MEMORY / ntb_in_max_size;
|
||||
max_out_transfers = MAX_TRANSFER_MEMORY / ntb_out_max_size;
|
||||
} else {
|
||||
max_in_transfers = 4;
|
||||
max_out_transfers = 4;
|
||||
}
|
||||
|
||||
Usb.check (handle.set_interface_alt_setting (config.data_iface, config.data_altsetting),
|
||||
"Failed to set USB interface alt setting");
|
||||
|
||||
_netstack = new VirtualNetworkStack (new Bytes (mac_address), null, 1500);
|
||||
_netstack.outgoing_datagram.connect (on_netif_outgoing_datagram);
|
||||
|
||||
process_incoming_datagrams.begin ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void close () {
|
||||
io_cancellable.cancel ();
|
||||
_netstack.stop ();
|
||||
}
|
||||
|
||||
private async void process_incoming_datagrams () {
|
||||
var pending = new Gee.ArrayQueue<Promise<Bytes>> ();
|
||||
while (true) {
|
||||
for (uint i = pending.size; i != max_in_transfers; i++) {
|
||||
var request = transfer_next_input_batch ();
|
||||
pending.offer (request);
|
||||
}
|
||||
|
||||
try {
|
||||
var frame = yield pending.poll ().future.wait_async (io_cancellable);
|
||||
handle_ncm_frame (frame);
|
||||
} catch (GLib.Error e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Promise<Bytes> transfer_next_input_batch () {
|
||||
var request = new Promise<Bytes> ();
|
||||
do_transfer_input_batch.begin (request);
|
||||
return request;
|
||||
}
|
||||
|
||||
private async void do_transfer_input_batch (Promise<Bytes> request) {
|
||||
var data = new uint8[ntb_in_max_size];
|
||||
try {
|
||||
size_t n = yield device.bulk_transfer (config.rx_address, data, uint.MAX, io_cancellable);
|
||||
request.resolve (new Bytes (data[:n]));
|
||||
} catch (GLib.Error e) {
|
||||
request.reject (e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handle_ncm_frame (Bytes frame) throws GLib.Error {
|
||||
var input = new DataInputStream (new MemoryInputStream.from_bytes (frame));
|
||||
input.byte_order = LITTLE_ENDIAN;
|
||||
|
||||
uint8 raw_signature[4 + 1];
|
||||
unowned string signature = (string) raw_signature;
|
||||
size_t bytes_read;
|
||||
|
||||
input.read_all (raw_signature[:4], out bytes_read);
|
||||
if (signature != "NCMH")
|
||||
throw new Error.PROTOCOL ("Invalid NTH16 signature");
|
||||
input.skip (6);
|
||||
var ndp_index = input.read_uint16 ();
|
||||
|
||||
do {
|
||||
input.seek (ndp_index, SET);
|
||||
input.read_all (raw_signature[:4], out bytes_read);
|
||||
if (signature != "NCM0")
|
||||
throw new Error.PROTOCOL ("Invalid NDP16 signature");
|
||||
input.skip (2);
|
||||
var next_ndp_index = input.read_uint16 ();
|
||||
|
||||
while (true) {
|
||||
var datagram_index = input.read_uint16 ();
|
||||
var datagram_length = input.read_uint16 ();
|
||||
if (datagram_index == 0 || datagram_length == 0)
|
||||
break;
|
||||
|
||||
int64 previous_offset = input.tell ();
|
||||
input.seek (datagram_index, SET);
|
||||
var datagram_buf = new uint8[datagram_length];
|
||||
input.read_all (datagram_buf, out bytes_read);
|
||||
input.seek (previous_offset, SET);
|
||||
|
||||
var datagram = new Bytes.take ((owned) datagram_buf);
|
||||
|
||||
if (_remote_ipv6_address == null) {
|
||||
_remote_ipv6_address = try_infer_remote_address_from_datagram (datagram);
|
||||
if (_remote_ipv6_address != null)
|
||||
notify_property ("remote-ipv6-address");
|
||||
}
|
||||
|
||||
_netstack.handle_incoming_datagram (datagram);
|
||||
}
|
||||
|
||||
ndp_index = next_ndp_index;
|
||||
} while (ndp_index != 0);
|
||||
}
|
||||
|
||||
private void on_netif_outgoing_datagram (Bytes datagram) {
|
||||
pending_output.offer (datagram);
|
||||
|
||||
if (!writing) {
|
||||
writing = true;
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
process_pending_output.begin ();
|
||||
return false;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
}
|
||||
|
||||
private async void process_pending_output () {
|
||||
try {
|
||||
while (!pending_output.is_empty) {
|
||||
var pending = new Gee.ArrayList<Promise<uint>> ();
|
||||
|
||||
do {
|
||||
var request = transfer_next_output_batch ();
|
||||
pending.add (request);
|
||||
} while (pending.size < max_out_transfers && !pending_output.is_empty);
|
||||
|
||||
foreach (var request in pending)
|
||||
yield request.future.wait_async (io_cancellable);
|
||||
}
|
||||
} catch (GLib.Error e) {
|
||||
} finally {
|
||||
writing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private Promise<uint> transfer_next_output_batch () {
|
||||
size_t num_datagrams = ntb_out_max_datagrams;
|
||||
TransferLayout layout;
|
||||
while ((layout = TransferLayout.compute (pending_output, num_datagrams, ndp_out_alignment,
|
||||
ndp_out_divisor, ndp_out_payload_remainder)).size > ntb_out_max_size) {
|
||||
num_datagrams--;
|
||||
}
|
||||
|
||||
var batch = new Gee.ArrayList<Bytes> ();
|
||||
for (var i = 0; i != layout.offsets.size; i++)
|
||||
batch.add (pending_output.poll ());
|
||||
|
||||
var transfer = build_output_transfer (batch, layout, next_outgoing_sequence++);
|
||||
|
||||
var request = new Promise<uint> ();
|
||||
do_transfer_output_batch.begin (transfer, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
private async void do_transfer_output_batch (Bytes transfer, Promise<uint> request) {
|
||||
try {
|
||||
var size = yield device.bulk_transfer (config.tx_address, transfer.get_data (), uint.MAX, io_cancellable);
|
||||
request.resolve ((uint) size);
|
||||
} catch (GLib.Error e) {
|
||||
request.reject (e);
|
||||
}
|
||||
}
|
||||
|
||||
private class TransferLayout {
|
||||
public uint16 size;
|
||||
public uint16 ndp_header_offset;
|
||||
public uint16 ndp_header_size;
|
||||
public Gee.List<uint16> offsets;
|
||||
|
||||
public static TransferLayout compute (Gee.Collection<Bytes> datagrams, size_t max_datagrams, size_t ndp_alignment,
|
||||
size_t datagram_modulus, size_t datagram_remainder) {
|
||||
size_t ndp_header_base_size = 4 + 2 + 2;
|
||||
size_t ndp_entry_size = 2 + 2;
|
||||
size_t ethernet_header_size = 14;
|
||||
|
||||
size_t ndp_header_offset = align (TRANSFER_HEADER_SIZE, ndp_alignment, 0);
|
||||
size_t num_datagram_slots = size_t.min (datagrams.size, max_datagrams);
|
||||
size_t ndp_header_size = ndp_header_base_size + ((num_datagram_slots + 1) * ndp_entry_size);
|
||||
|
||||
size_t current_transfer_size = ndp_header_offset + ndp_header_size;
|
||||
var offsets = new Gee.ArrayList<uint16> ();
|
||||
|
||||
uint i = 0;
|
||||
foreach (var datagram in datagrams) {
|
||||
var size = (uint16) datagram.get_size ();
|
||||
|
||||
size_t start_offset =
|
||||
align (current_transfer_size + ethernet_header_size, datagram_modulus, datagram_remainder);
|
||||
size_t end_offset = start_offset + size;
|
||||
if (end_offset > uint16.MAX)
|
||||
break;
|
||||
|
||||
current_transfer_size = end_offset;
|
||||
offsets.add ((uint16) start_offset);
|
||||
|
||||
i++;
|
||||
if (i == max_datagrams)
|
||||
break;
|
||||
}
|
||||
|
||||
return new TransferLayout () {
|
||||
size = (uint16) current_transfer_size,
|
||||
ndp_header_offset = (uint16) ndp_header_offset,
|
||||
ndp_header_size = (uint16) ndp_header_size,
|
||||
offsets = offsets,
|
||||
};
|
||||
}
|
||||
|
||||
private static size_t align (size_t val, size_t modulus, size_t remainder) {
|
||||
var delta = val % modulus;
|
||||
if (delta != remainder)
|
||||
return val + modulus - delta + remainder;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
private static Bytes build_output_transfer (Gee.List<Bytes> datagrams, TransferLayout layout, uint16 sequence_number) {
|
||||
var builder = new BufferBuilder (LITTLE_ENDIAN)
|
||||
.append_string ("NCMH", StringTerminator.NONE)
|
||||
.append_uint16 (TRANSFER_HEADER_SIZE)
|
||||
.append_uint16 (sequence_number)
|
||||
.append_uint16 (layout.size)
|
||||
.append_uint16 (layout.ndp_header_offset);
|
||||
|
||||
uint16 next_ndp_index = 0;
|
||||
builder
|
||||
.seek (layout.ndp_header_offset)
|
||||
.append_string ("NCM0", StringTerminator.NONE)
|
||||
.append_uint16 (layout.ndp_header_size)
|
||||
.append_uint16 (next_ndp_index);
|
||||
|
||||
int i;
|
||||
|
||||
i = 0;
|
||||
foreach (var datagram in datagrams) {
|
||||
builder
|
||||
.append_uint16 (layout.offsets[i])
|
||||
.append_uint16 ((uint16) datagram.get_size ());
|
||||
i++;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
foreach (var datagram in datagrams) {
|
||||
builder
|
||||
.seek (layout.offsets[i])
|
||||
.append_bytes (datagram);
|
||||
i++;
|
||||
}
|
||||
|
||||
builder.seek (layout.size);
|
||||
|
||||
return builder.build ();
|
||||
}
|
||||
|
||||
private static InetAddress? try_infer_remote_address_from_datagram (Bytes datagram) {
|
||||
if (datagram.get_size () < 0x3e)
|
||||
return null;
|
||||
|
||||
var buf = new Buffer (datagram, BIG_ENDIAN);
|
||||
|
||||
var ethertype = (EtherType) buf.read_uint16 (12);
|
||||
if (ethertype != IPV6)
|
||||
return null;
|
||||
|
||||
var next_header = (IPV6NextHeader) buf.read_uint8 (20);
|
||||
if (next_header != UDP)
|
||||
return null;
|
||||
|
||||
return new InetAddress.from_bytes (datagram[22:22 + 16].get_data (), IPV6);
|
||||
}
|
||||
}
|
||||
|
||||
internal class UsbNcmConfig {
|
||||
public uint8 ctrl_iface;
|
||||
public uint8 data_iface;
|
||||
public int data_altsetting;
|
||||
public uint8 rx_address;
|
||||
public uint8 tx_address;
|
||||
public uint8 mac_address_index;
|
||||
|
||||
private enum UsbDescriptorType {
|
||||
INTERFACE = 0x04,
|
||||
}
|
||||
|
||||
private enum UsbCommSubclass {
|
||||
NCM = 0x0d,
|
||||
}
|
||||
|
||||
private enum UsbDataSubclass {
|
||||
UNDEFINED = 0x00,
|
||||
}
|
||||
|
||||
private enum UsbCdcDescriptorSubtype {
|
||||
ETHERNET = 0x0f,
|
||||
}
|
||||
|
||||
public static UsbNcmConfig prepare (UsbDevice device, out bool device_configuration_changed) throws Error {
|
||||
unowned LibUSB.Device raw_device = device.raw_device;
|
||||
|
||||
var dev_desc = LibUSB.DeviceDescriptor (raw_device);
|
||||
|
||||
LibUSB.ConfigDescriptor current_config;
|
||||
Usb.check (raw_device.get_active_config_descriptor (out current_config), "Failed to get active config descriptor");
|
||||
|
||||
var config = new UsbNcmConfig ();
|
||||
int desired_config_value = -1;
|
||||
bool found_cdc_header = false;
|
||||
bool found_data_interface = false;
|
||||
for (uint8 config_value = dev_desc.bNumConfigurations; config_value != 0; config_value--) {
|
||||
LibUSB.ConfigDescriptor config_desc;
|
||||
Usb.check (raw_device.get_config_descriptor_by_value (config_value, out config_desc),
|
||||
"Failed to get config descriptor");
|
||||
|
||||
foreach (var iface in config_desc.@interface) {
|
||||
foreach (var setting in iface.altsetting) {
|
||||
if (setting.bInterfaceClass == LibUSB.ClassCode.COMM &&
|
||||
setting.bInterfaceSubClass == UsbCommSubclass.NCM) {
|
||||
config.ctrl_iface = setting.bInterfaceNumber;
|
||||
|
||||
try {
|
||||
parse_cdc_header (setting.extra, out config.mac_address_index);
|
||||
found_cdc_header = true;
|
||||
} catch (Error e) {
|
||||
break;
|
||||
}
|
||||
} else if (setting.bInterfaceClass == LibUSB.ClassCode.DATA &&
|
||||
setting.bInterfaceSubClass == UsbDataSubclass.UNDEFINED &&
|
||||
setting.endpoint.length == 2) {
|
||||
found_data_interface = true;
|
||||
|
||||
config.data_iface = setting.bInterfaceNumber;
|
||||
config.data_altsetting = setting.bAlternateSetting;
|
||||
|
||||
foreach (var ep in setting.endpoint) {
|
||||
if ((ep.bEndpointAddress & LibUSB.EndpointDirection.MASK) ==
|
||||
LibUSB.EndpointDirection.IN) {
|
||||
config.rx_address = ep.bEndpointAddress;
|
||||
} else {
|
||||
config.tx_address = ep.bEndpointAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found_cdc_header || found_data_interface) {
|
||||
desired_config_value = config_value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_cdc_header || !found_data_interface)
|
||||
throw new Error.NOT_SUPPORTED ("%s", make_user_error_message ("No USB CDC-NCM interface found"));
|
||||
|
||||
if (current_config.bConfigurationValue != desired_config_value) {
|
||||
unowned LibUSB.DeviceHandle handle = device.handle;
|
||||
foreach (var iface in current_config.@interface) {
|
||||
unowned LibUSB.InterfaceDescriptor setting = iface.altsetting[0];
|
||||
var res = handle.kernel_driver_active (setting.bInterfaceNumber);
|
||||
if (res == 1)
|
||||
handle.detach_kernel_driver (setting.bInterfaceNumber);
|
||||
}
|
||||
Usb.check (handle.set_configuration (desired_config_value), "Failed to set configuration");
|
||||
device_configuration_changed = true;
|
||||
} else {
|
||||
device_configuration_changed = false;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private static void parse_cdc_header (uint8[] header, out uint8 mac_address_index) throws Error {
|
||||
var input = new DataInputStream (new MemoryInputStream.from_data (header));
|
||||
input.set_byte_order (LITTLE_ENDIAN);
|
||||
|
||||
try {
|
||||
for (int offset = 0; offset != header.length;) {
|
||||
uint8 length = input.read_byte ();
|
||||
if (length < 3)
|
||||
throw new Error.PROTOCOL ("Invalid descriptor length");
|
||||
|
||||
uint8 descriptor_type = input.read_byte ();
|
||||
if (descriptor_type != (LibUSB.RequestType.CLASS | UsbDescriptorType.INTERFACE))
|
||||
throw new Error.PROTOCOL ("Invalid descriptor type");
|
||||
|
||||
uint8 descriptor_subtype = input.read_byte ();
|
||||
if (descriptor_subtype == UsbCdcDescriptorSubtype.ETHERNET) {
|
||||
mac_address_index = input.read_byte ();
|
||||
return;
|
||||
}
|
||||
|
||||
input.skip (length - 3);
|
||||
offset += length;
|
||||
}
|
||||
} catch (IOError e) {
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
throw new Error.PROTOCOL ("CDC Ethernet descriptor not found");
|
||||
}
|
||||
}
|
||||
|
||||
private string make_user_error_message (string message) {
|
||||
#if WINDOWS
|
||||
return message + "; use https://zadig.akeo.ie to switch from Apple's official driver onto Microsoft's WinUSB " +
|
||||
"driver, so libusb can access it";
|
||||
#else
|
||||
return message;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,245 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public class OpackBuilder {
|
||||
protected BufferBuilder builder = new BufferBuilder (LITTLE_ENDIAN);
|
||||
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
|
||||
|
||||
public OpackBuilder () {
|
||||
push_scope (new Scope (ROOT));
|
||||
}
|
||||
|
||||
public unowned OpackBuilder begin_dictionary () {
|
||||
begin_value ();
|
||||
|
||||
size_t type_offset = builder.offset;
|
||||
builder.append_uint8 (0x00);
|
||||
|
||||
push_scope (new CollectionScope (type_offset));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned OpackBuilder set_member_name (string name) {
|
||||
return add_string_value (name);
|
||||
}
|
||||
|
||||
public unowned OpackBuilder end_dictionary () {
|
||||
CollectionScope scope = pop_scope ();
|
||||
|
||||
size_t n = scope.num_values / 2;
|
||||
if (n < 0xf) {
|
||||
builder.write_uint8 (scope.type_offset, 0xe0 | n);
|
||||
} else {
|
||||
builder
|
||||
.write_uint8 (scope.type_offset, 0xef)
|
||||
.append_uint8 (0x03);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned OpackBuilder add_string_value (string val) {
|
||||
begin_value ();
|
||||
|
||||
size_t len = val.length;
|
||||
|
||||
if (len > uint32.MAX) {
|
||||
builder
|
||||
.append_uint8 (0x6f)
|
||||
.append_string (val, StringTerminator.NUL);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
if (len <= 0x20)
|
||||
builder.append_uint8 ((uint8) (0x40 + len));
|
||||
else if (len <= uint8.MAX)
|
||||
builder.append_uint8 (0x61).append_uint8 ((uint8) len);
|
||||
else if (len <= uint16.MAX)
|
||||
builder.append_uint8 (0x62).append_uint16 ((uint16) len);
|
||||
else if (len <= 0xffffff)
|
||||
builder.append_uint8 (0x63).append_uint8 ((uint8) (len & 0xff)).append_uint16 ((uint16) (len >> 8));
|
||||
else
|
||||
builder.append_uint8 (0x64).append_uint32 ((uint32) len);
|
||||
|
||||
builder.append_string (val, StringTerminator.NONE);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned OpackBuilder add_data_value (Bytes val) {
|
||||
begin_value ();
|
||||
|
||||
size_t size = val.get_size ();
|
||||
if (size <= 0x20)
|
||||
builder.append_uint8 ((uint8) (0x70 + size));
|
||||
else if (size <= uint8.MAX)
|
||||
builder.append_uint8 (0x91).append_uint8 ((uint8) size);
|
||||
else if (size <= uint16.MAX)
|
||||
builder.append_uint8 (0x92).append_uint16 ((uint16) size);
|
||||
else if (size <= 0xffffff)
|
||||
builder.append_uint8 (0x93).append_uint8 ((uint8) (size & 0xff)).append_uint16 ((uint16) (size >> 8));
|
||||
else
|
||||
builder.append_uint8 (0x94).append_uint32 ((uint32) size);
|
||||
|
||||
builder.append_bytes (val);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private unowned OpackBuilder begin_value () {
|
||||
peek_scope ().num_values++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Bytes build () {
|
||||
return builder.build ();
|
||||
}
|
||||
|
||||
private void push_scope (Scope scope) {
|
||||
scopes.offer_tail (scope);
|
||||
}
|
||||
|
||||
private Scope peek_scope () {
|
||||
return scopes.peek_tail ();
|
||||
}
|
||||
|
||||
private T pop_scope<T> () {
|
||||
return (T) scopes.poll_tail ();
|
||||
}
|
||||
|
||||
private class Scope {
|
||||
public Kind kind;
|
||||
public size_t num_values = 0;
|
||||
|
||||
public enum Kind {
|
||||
ROOT,
|
||||
COLLECTION,
|
||||
}
|
||||
|
||||
public Scope (Kind kind) {
|
||||
this.kind = kind;
|
||||
}
|
||||
}
|
||||
|
||||
private class CollectionScope : Scope {
|
||||
public size_t type_offset;
|
||||
|
||||
public CollectionScope (size_t type_offset) {
|
||||
base (COLLECTION);
|
||||
this.type_offset = type_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OpackParser {
|
||||
private BufferReader reader;
|
||||
|
||||
[Flags]
|
||||
private enum ValueFlags {
|
||||
ALLOW_TERMINATOR = 1 << 0,
|
||||
}
|
||||
|
||||
public static Variant parse (Bytes opack) throws Error {
|
||||
var parser = new OpackParser (opack);
|
||||
return parser.read_value ();
|
||||
}
|
||||
|
||||
private OpackParser (Bytes opack) {
|
||||
reader = new BufferReader (new Buffer (opack, LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
private Variant? read_value (ValueFlags flags = 0) throws Error {
|
||||
uint8 v = reader.read_uint8 ();
|
||||
uint8 top = v >> 4;
|
||||
uint8 bottom = v & 0b1111;
|
||||
switch (top) {
|
||||
case 0:
|
||||
switch (bottom) {
|
||||
case 3:
|
||||
if ((flags & ValueFlags.ALLOW_TERMINATOR) == 0)
|
||||
throw new Error.PROTOCOL ("Unexpected OPACK terminator");
|
||||
return null;
|
||||
}
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported OPACK type 0x%02x", v);
|
||||
case 3:
|
||||
switch (bottom) {
|
||||
case 0: return (int64) reader.read_int8 ();
|
||||
case 1: return (int64) reader.read_int16 ();
|
||||
case 2: return (int64) reader.read_int32 ();
|
||||
case 3: return (int64) reader.read_int64 ();
|
||||
}
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported OPACK type 0x%02x", v);
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
return read_string (v - 0x40);
|
||||
case 7:
|
||||
case 8:
|
||||
case 9:
|
||||
return read_data (v - 0x70);
|
||||
case 0xe:
|
||||
return read_dictionary (bottom);
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported OPACK type 0x%02x", v);
|
||||
}
|
||||
}
|
||||
|
||||
private Variant read_string (size_t len) throws Error {
|
||||
if (len == 0x2f)
|
||||
return reader.read_string ();
|
||||
len = read_variable_length (len);
|
||||
return reader.read_fixed_string (len);
|
||||
}
|
||||
|
||||
private Variant read_data (size_t len) throws Error {
|
||||
len = read_variable_length (len);
|
||||
var bytes = reader.read_bytes (len);
|
||||
return Variant.new_from_data (new VariantType.array (VariantType.BYTE), bytes.get_data (), true, bytes);
|
||||
}
|
||||
|
||||
private size_t read_variable_length (size_t len) throws Error {
|
||||
if (len <= 0x20)
|
||||
return len;
|
||||
|
||||
switch (len) {
|
||||
case 0x21:
|
||||
return reader.read_uint8 ();
|
||||
case 0x22:
|
||||
return reader.read_uint16 ();
|
||||
case 0x23:
|
||||
uint32 bottom = reader.read_uint8 ();
|
||||
uint32 top = reader.read_uint16 ();
|
||||
return top << 8 | bottom;
|
||||
case 0x24:
|
||||
return reader.read_uint32 ();
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported OPACK length: 0x%zx", len);
|
||||
}
|
||||
}
|
||||
|
||||
private Variant read_dictionary (size_t n) throws Error {
|
||||
var builder = new VariantBuilder (VariantType.VARDICT);
|
||||
|
||||
bool has_terminator = n == 0xf;
|
||||
|
||||
size_t i = 0;
|
||||
while (true) {
|
||||
if (!has_terminator && i == n)
|
||||
break;
|
||||
|
||||
var key = read_value (has_terminator ? ValueFlags.ALLOW_TERMINATOR : 0);
|
||||
if (key == null)
|
||||
break;
|
||||
if (!key.is_of_type (VariantType.STRING))
|
||||
throw new Error.PROTOCOL ("Unsupported OPACK dictionary key type");
|
||||
var val = read_value ();
|
||||
builder.add_value (new Variant.dict_entry (key, new Variant.variant (val)));
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return builder.end ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1573,6 +1573,12 @@ namespace Frida.Fruity {
|
||||
if (t == typeof (int64))
|
||||
return (uint) v.get_int64 ();
|
||||
|
||||
if (t == typeof (float))
|
||||
return (uint) v.get_float ();
|
||||
|
||||
if (t == typeof (double))
|
||||
return (uint) v.get_double ();
|
||||
|
||||
if (t == typeof (string))
|
||||
return str_hash (v.get_string ());
|
||||
|
||||
@@ -1598,6 +1604,12 @@ namespace Frida.Fruity {
|
||||
if (t == typeof (int64))
|
||||
return a.get_int64 () == b.get_int64 ();
|
||||
|
||||
if (t == typeof (float))
|
||||
return a.get_float () == b.get_float ();
|
||||
|
||||
if (t == typeof (double))
|
||||
return a.get_double () == b.get_double ();
|
||||
|
||||
if (t == typeof (string))
|
||||
return a.get_string () == b.get_string ();
|
||||
|
||||
|
||||
@@ -1,38 +1,33 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public class SpringboardServicesClient : Object, AsyncInitable {
|
||||
public LockdownClient lockdown {
|
||||
public Device device {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private PlistServiceClient service;
|
||||
|
||||
private SpringboardServicesClient (LockdownClient lockdown) {
|
||||
Object (lockdown: lockdown);
|
||||
private SpringboardServicesClient (Device device) {
|
||||
Object (device: device);
|
||||
}
|
||||
|
||||
public static async SpringboardServicesClient open (LockdownClient lockdown,
|
||||
Cancellable? cancellable = null) throws SpringboardServicesError, IOError {
|
||||
var client = new SpringboardServicesClient (lockdown);
|
||||
public static async SpringboardServicesClient open (Device device, Cancellable? cancellable = null) throws Error, IOError {
|
||||
var client = new SpringboardServicesClient (device);
|
||||
|
||||
try {
|
||||
yield client.init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_local_error (e);
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws SpringboardServicesError, IOError {
|
||||
try {
|
||||
var stream = yield lockdown.start_service ("com.apple.springboardservices", cancellable);
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
|
||||
var stream = yield device.open_lockdown_service ("com.apple.springboardservices", cancellable);
|
||||
|
||||
service = new PlistServiceClient (stream);
|
||||
} catch (LockdownError e) {
|
||||
throw new SpringboardServicesError.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
service = new PlistServiceClient (stream);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -41,15 +36,14 @@ namespace Frida.Fruity {
|
||||
yield service.close (cancellable);
|
||||
}
|
||||
|
||||
public async Bytes get_icon_png_data (string bundle_id,
|
||||
Cancellable? cancellable = null) throws SpringboardServicesError, IOError {
|
||||
public async Bytes get_icon_png_data (string bundle_id, Cancellable? cancellable = null) throws Error, IOError {
|
||||
try {
|
||||
var request = make_request ("getIconPNGData");
|
||||
request.set_string ("bundleId", bundle_id);
|
||||
|
||||
var response = yield service.query (request, cancellable);
|
||||
if (response.has ("Error"))
|
||||
throw new SpringboardServicesError.INVALID_ARGUMENT ("%s", response.get_string ("Error"));
|
||||
throw new Error.INVALID_ARGUMENT ("%s", response.get_string ("Error"));
|
||||
|
||||
return response.get_bytes ("pngData");
|
||||
} catch (PlistServiceError e) {
|
||||
@@ -59,8 +53,8 @@ namespace Frida.Fruity {
|
||||
}
|
||||
}
|
||||
|
||||
public async Gee.HashMap<string, Bytes> get_icon_png_data_batch (string[] bundle_ids,
|
||||
Cancellable? cancellable = null) throws SpringboardServicesError, IOError {
|
||||
public async Gee.HashMap<string, Bytes> get_icon_png_data_batch (string[] bundle_ids, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
try {
|
||||
foreach (unowned string bundle_id in bundle_ids) {
|
||||
var request = make_request ("getIconPNGData");
|
||||
@@ -73,7 +67,7 @@ namespace Frida.Fruity {
|
||||
do {
|
||||
foreach (Plist response in yield service.read_messages (0, cancellable)) {
|
||||
if (response.has ("Error")) {
|
||||
throw new SpringboardServicesError.INVALID_ARGUMENT ("%s",
|
||||
throw new Error.INVALID_ARGUMENT ("%s",
|
||||
response.get_string ("Error"));
|
||||
}
|
||||
|
||||
@@ -98,27 +92,12 @@ namespace Frida.Fruity {
|
||||
return request;
|
||||
}
|
||||
|
||||
private static void throw_local_error (GLib.Error e) throws SpringboardServicesError, IOError {
|
||||
if (e is SpringboardServicesError)
|
||||
throw (SpringboardServicesError) e;
|
||||
|
||||
if (e is IOError)
|
||||
throw (IOError) e;
|
||||
|
||||
assert_not_reached ();
|
||||
private static Error error_from_service (PlistServiceError e) {
|
||||
return new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
private static SpringboardServicesError error_from_service (PlistServiceError e) {
|
||||
return new SpringboardServicesError.PROTOCOL ("%s", e.message);
|
||||
private static Error error_from_plist (PlistError e) {
|
||||
return new Error.PROTOCOL ("Unexpected response: %s", e.message);
|
||||
}
|
||||
|
||||
private static SpringboardServicesError error_from_plist (PlistError e) {
|
||||
return new SpringboardServicesError.PROTOCOL ("Unexpected response: %s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public errordomain SpringboardServicesError {
|
||||
INVALID_ARGUMENT,
|
||||
PROTOCOL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
internal sealed class UsbDevice : Object {
|
||||
public string udid {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public LibUSB.Device? raw_device {
|
||||
get {
|
||||
return _raw_device;
|
||||
}
|
||||
}
|
||||
|
||||
public UsbDeviceBackend backend {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public LibUSB.DeviceHandle? handle {
|
||||
get {
|
||||
return _handle;
|
||||
}
|
||||
}
|
||||
|
||||
private LibUSB.Device? _raw_device;
|
||||
private LibUSB.DeviceHandle? _handle;
|
||||
private uint num_pending_operations;
|
||||
private Promise<bool>? pending_operations_completed;
|
||||
|
||||
private enum AppleSpecificRequest {
|
||||
GET_MODE = 0x45,
|
||||
SET_MODE = 0x52,
|
||||
}
|
||||
|
||||
private const string MODE_INITIAL_UNTETHERED = "3:3:3:0"; // => 5:3:3:0
|
||||
private const string MODE_INITIAL_TETHERED = "4:4:3:4"; // => 5:4:3:4
|
||||
|
||||
public UsbDevice (LibUSB.Device raw_device, UsbDeviceBackend backend) throws Error {
|
||||
char serial[LibUSB.DEVICE_STRING_BYTES_MAX + 1];
|
||||
var res = raw_device.get_device_string (SERIAL_NUMBER, serial);
|
||||
Usb.check (res, "Failed to get serial number");
|
||||
serial[res] = '\0';
|
||||
|
||||
Object (
|
||||
udid: udid_from_serial_number ((string) serial),
|
||||
backend: backend
|
||||
);
|
||||
|
||||
_raw_device = raw_device;
|
||||
}
|
||||
|
||||
public void ensure_open (Cancellable? cancellable = null) throws Error {
|
||||
if (_handle != null)
|
||||
return;
|
||||
Usb.check (_raw_device.open (out _handle), "Failed to open USB device");
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
if (num_pending_operations != 0) {
|
||||
pending_operations_completed = new Promise<bool> ();
|
||||
try {
|
||||
yield pending_operations_completed.future.wait_async (cancellable);
|
||||
} catch (Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
pending_operations_completed = null;
|
||||
}
|
||||
|
||||
_handle = null;
|
||||
_raw_device = null;
|
||||
}
|
||||
|
||||
public async bool maybe_modeswitch (Cancellable? cancellable) throws Error, IOError {
|
||||
uint8 current_mode[4];
|
||||
var n = yield control_transfer (
|
||||
LibUSB.RequestRecipient.DEVICE | LibUSB.RequestType.VENDOR | LibUSB.EndpointDirection.IN,
|
||||
AppleSpecificRequest.GET_MODE,
|
||||
0,
|
||||
0,
|
||||
current_mode,
|
||||
1000,
|
||||
cancellable);
|
||||
string mode = parse_mode (current_mode[:n]);
|
||||
bool is_initial_mode = mode == MODE_INITIAL_UNTETHERED || mode == MODE_INITIAL_TETHERED;
|
||||
if (!is_initial_mode)
|
||||
return false;
|
||||
|
||||
uint8 set_mode_result[1];
|
||||
var set_mode_result_size = yield control_transfer (
|
||||
LibUSB.RequestRecipient.DEVICE | LibUSB.RequestType.VENDOR | LibUSB.EndpointDirection.IN,
|
||||
AppleSpecificRequest.SET_MODE,
|
||||
0,
|
||||
3,
|
||||
set_mode_result,
|
||||
1000,
|
||||
cancellable);
|
||||
if (set_mode_result_size != 1 || set_mode_result[0] != 0x00)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string parse_mode (uint8[] mode) throws Error {
|
||||
var result = new StringBuilder.sized (7);
|
||||
foreach (uint8 byte in mode) {
|
||||
if (result.len != 0)
|
||||
result.append_c (':');
|
||||
result.append_printf ("%u", byte);
|
||||
}
|
||||
return result.str;
|
||||
}
|
||||
|
||||
public static string udid_from_serial_number (string serial) {
|
||||
if (serial.length == 24)
|
||||
return serial[:8] + "-" + serial[8:];
|
||||
return serial;
|
||||
}
|
||||
|
||||
public async uint16 query_default_language_id (Cancellable? cancellable) throws Error, IOError {
|
||||
Bytes language_ids_response = yield read_string_descriptor_bytes (0, 0, cancellable);
|
||||
if (language_ids_response.get_size () < sizeof (uint16))
|
||||
throw new Error.PROTOCOL ("Invalid language IDs response");
|
||||
Buffer language_ids = new Buffer (language_ids_response, LITTLE_ENDIAN);
|
||||
return language_ids.read_uint16 (0);
|
||||
}
|
||||
|
||||
public async string read_string_descriptor (uint8 index, uint16 language_id, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var response = yield read_string_descriptor_bytes (index, language_id, cancellable);
|
||||
try {
|
||||
var input = new DataInputStream (new MemoryInputStream.from_bytes (response));
|
||||
input.byte_order = LITTLE_ENDIAN;
|
||||
|
||||
size_t size = response.get_size ();
|
||||
if (size % sizeof (unichar2) != 0)
|
||||
throw new Error.PROTOCOL ("Invalid string descriptor");
|
||||
size_t n = size / sizeof (unichar2);
|
||||
var chars = new unichar2[n];
|
||||
for (size_t i = 0; i != n; i++)
|
||||
chars[i] = input.read_uint16 ();
|
||||
|
||||
unowned string16 str = (string16) chars;
|
||||
return str.to_utf8 ((long) n);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Bytes read_string_descriptor_bytes (uint8 index, uint16 language_id, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
uint8 response[1024];
|
||||
var response_size = yield control_transfer (
|
||||
LibUSB.RequestRecipient.DEVICE | LibUSB.RequestType.STANDARD | LibUSB.EndpointDirection.IN,
|
||||
LibUSB.StandardRequest.GET_DESCRIPTOR,
|
||||
(LibUSB.DescriptorType.STRING << 8) | index,
|
||||
language_id,
|
||||
response,
|
||||
1000,
|
||||
cancellable);
|
||||
try {
|
||||
var input = new DataInputStream (new MemoryInputStream.from_data (response[:response_size]));
|
||||
input.byte_order = LITTLE_ENDIAN;
|
||||
|
||||
uint8 length = input.read_byte ();
|
||||
if (length < 2)
|
||||
throw new Error.PROTOCOL ("Invalid string descriptor length");
|
||||
|
||||
uint8 type = input.read_byte ();
|
||||
if (type != LibUSB.DescriptorType.STRING)
|
||||
throw new Error.PROTOCOL ("Invalid string descriptor type");
|
||||
|
||||
size_t remainder = response_size - 2;
|
||||
length -= 2;
|
||||
if (length > remainder)
|
||||
throw new Error.PROTOCOL ("Invalid string descriptor length");
|
||||
|
||||
return new Bytes (response[2:2 + length]);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async size_t control_transfer (uint8 request_type, uint8 request, uint16 val, uint16 index, uint8[] buffer,
|
||||
uint timeout, Cancellable? cancellable) throws Error, IOError {
|
||||
var op = backend.allocate_usb_operation ();
|
||||
unowned LibUSB.Transfer transfer = op.transfer;
|
||||
var ready_closure = new TransferReadyClosure (control_transfer.callback);
|
||||
|
||||
size_t control_setup_size = 8;
|
||||
var transfer_buffer = new uint8[control_setup_size + buffer.length];
|
||||
LibUSB.Transfer.fill_control_setup (transfer_buffer, request_type, request, val, index, (uint16) buffer.length);
|
||||
if ((request_type & LibUSB.EndpointDirection.IN) == 0)
|
||||
Memory.copy ((uint8 *) transfer_buffer + control_setup_size, buffer, buffer.length);
|
||||
transfer.fill_control_transfer (_handle, transfer_buffer, on_transfer_ready, ready_closure, timeout);
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (() => {
|
||||
transfer.cancel ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
cancel_source.attach (MainContext.get_thread_default ());
|
||||
|
||||
try {
|
||||
Usb.check (transfer.submit (), "Failed to submit control transfer");
|
||||
on_operation_started ();
|
||||
yield;
|
||||
on_operation_ended ();
|
||||
} finally {
|
||||
cancel_source.destroy ();
|
||||
}
|
||||
|
||||
Usb.check_transfer (transfer.status, "Control transfer failed");
|
||||
|
||||
var n = transfer.actual_length;
|
||||
|
||||
if ((request_type & LibUSB.EndpointDirection.IN) != 0)
|
||||
Memory.copy (buffer, transfer.control_get_data (), n);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
public async size_t bulk_transfer (uint8 endpoint, uint8[] buffer, uint timeout, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var op = backend.allocate_usb_operation ();
|
||||
unowned LibUSB.Transfer transfer = op.transfer;
|
||||
var ready_closure = new TransferReadyClosure (bulk_transfer.callback);
|
||||
|
||||
transfer.fill_bulk_transfer (_handle, endpoint, buffer, on_transfer_ready, ready_closure, timeout);
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (() => {
|
||||
transfer.cancel ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
cancel_source.attach (MainContext.get_thread_default ());
|
||||
|
||||
try {
|
||||
Usb.check (transfer.submit (), "Failed to submit bulk transfer");
|
||||
on_operation_started ();
|
||||
yield;
|
||||
on_operation_ended ();
|
||||
} finally {
|
||||
cancel_source.destroy ();
|
||||
}
|
||||
|
||||
Usb.check_transfer (transfer.status, "Bulk transfer failed");
|
||||
|
||||
return transfer.actual_length;
|
||||
}
|
||||
|
||||
private void on_operation_started () {
|
||||
num_pending_operations++;
|
||||
}
|
||||
|
||||
private void on_operation_ended () {
|
||||
num_pending_operations--;
|
||||
if (num_pending_operations == 0 && pending_operations_completed != null)
|
||||
pending_operations_completed.resolve (true);
|
||||
}
|
||||
|
||||
private static void on_transfer_ready (LibUSB.Transfer transfer) {
|
||||
TransferReadyClosure * closure = transfer.user_data;
|
||||
closure->schedule ();
|
||||
}
|
||||
|
||||
private class TransferReadyClosure {
|
||||
private SourceFunc? handler;
|
||||
private MainContext main_context;
|
||||
|
||||
public TransferReadyClosure (owned SourceFunc handler) {
|
||||
this.handler = (owned) handler;
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
}
|
||||
|
||||
public void schedule () {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
handler ();
|
||||
handler = null;
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal interface UsbDeviceBackend : Object {
|
||||
public abstract UsbOperation allocate_usb_operation () throws Error;
|
||||
}
|
||||
|
||||
internal interface UsbOperation : Object {
|
||||
public abstract LibUSB.Transfer transfer {
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Usb {
|
||||
internal static void check (LibUSB.Error error, string prefix) throws Error {
|
||||
if (error >= LibUSB.Error.SUCCESS)
|
||||
return;
|
||||
|
||||
string message = @"$prefix: $(error.get_description ())";
|
||||
|
||||
switch (error) {
|
||||
case ACCESS:
|
||||
throw new Error.PERMISSION_DENIED ("%s", message);
|
||||
case NOT_FOUND:
|
||||
throw new Error.INVALID_OPERATION ("%s", message);
|
||||
case TIMEOUT:
|
||||
throw new Error.TIMED_OUT ("%s", message);
|
||||
default:
|
||||
throw new Error.TRANSPORT ("%s", message);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void check_transfer (LibUSB.TransferStatus status, string prefix) throws Error, IOError {
|
||||
if (status == COMPLETED)
|
||||
return;
|
||||
|
||||
string message = @"$prefix: $(status.to_string ())";
|
||||
|
||||
switch (status) {
|
||||
case TIMED_OUT:
|
||||
throw new Error.TIMED_OUT ("%s", message);
|
||||
case CANCELLED:
|
||||
throw new IOError.CANCELLED ("%s", message);
|
||||
default:
|
||||
throw new Error.TRANSPORT ("%s", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-65
@@ -1,13 +1,16 @@
|
||||
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
|
||||
namespace Frida.Fruity {
|
||||
public class UsbmuxClient : Object, AsyncInitable {
|
||||
public signal void device_attached (DeviceDetails details);
|
||||
public signal void device_detached (DeviceId id);
|
||||
public signal void device_attached (UsbmuxDevice device);
|
||||
public signal void device_detached (UsbmuxDevice device);
|
||||
|
||||
public SocketConnection? connection {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private Gee.Map<uint, UsbmuxDevice> devices = new Gee.HashMap<uint, UsbmuxDevice> ();
|
||||
|
||||
private InputStream? input;
|
||||
private OutputStream? output;
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
@@ -132,11 +135,12 @@ namespace Frida.Fruity {
|
||||
}
|
||||
}
|
||||
|
||||
public async void connect_to_port (DeviceId device_id, uint16 port, Cancellable? cancellable = null) throws UsbmuxError, IOError {
|
||||
public async void connect_to_port (uint device_id, uint16 port, Cancellable? cancellable = null)
|
||||
throws UsbmuxError, IOError {
|
||||
assert (is_processing_messages);
|
||||
|
||||
var request = create_request ("Connect");
|
||||
request.set_integer ("DeviceID", device_id.raw_value);
|
||||
request.set_integer ("DeviceID", device_id);
|
||||
request.set_integer ("PortNumber", port.to_big_endian ());
|
||||
|
||||
var response = yield query (request, MODE_SWITCH, cancellable);
|
||||
@@ -160,9 +164,9 @@ namespace Frida.Fruity {
|
||||
}
|
||||
}
|
||||
|
||||
public async Plist read_pair_record (Udid udid, Cancellable? cancellable = null) throws UsbmuxError, IOError {
|
||||
public async Plist read_pair_record (string udid, Cancellable? cancellable = null) throws UsbmuxError, IOError {
|
||||
var request = create_request ("ReadPairRecord");
|
||||
request.set_string ("PairRecordID", udid.raw_value);
|
||||
request.set_string ("PairRecordID", udid);
|
||||
|
||||
var response = yield query (request, REGULAR, cancellable);
|
||||
try {
|
||||
@@ -175,9 +179,9 @@ namespace Frida.Fruity {
|
||||
}
|
||||
}
|
||||
|
||||
public async void delete_pair_record (Udid udid, Cancellable? cancellable = null) throws UsbmuxError, IOError {
|
||||
public async void delete_pair_record (string udid, Cancellable? cancellable = null) throws UsbmuxError, IOError {
|
||||
var request = create_request ("DeletePairRecord");
|
||||
request.set_string ("PairRecordID", udid.raw_value);
|
||||
request.set_string ("PairRecordID", udid);
|
||||
|
||||
var response = yield query (request, REGULAR, cancellable);
|
||||
check_pair_response (response, "DeletePairRecord");
|
||||
@@ -276,28 +280,26 @@ namespace Frida.Fruity {
|
||||
if (props.has ("ConnectionType") && props.get_string ("ConnectionType") == "Network")
|
||||
connection_type = NETWORK;
|
||||
|
||||
var device_id = DeviceId ((uint) body.get_integer ("DeviceID"));
|
||||
var device_id = (uint) body.get_integer ("DeviceID");
|
||||
|
||||
ProductId product_id;
|
||||
if (connection_type == USB)
|
||||
product_id = ProductId ((int) props.get_integer ("ProductID"));
|
||||
else
|
||||
product_id = ProductId (-1);
|
||||
int product_id = (connection_type == USB) ? (int) props.get_integer ("ProductID") : -1;
|
||||
|
||||
string raw_udid = props.get_string ("SerialNumber");
|
||||
if (raw_udid.length == 24)
|
||||
raw_udid = raw_udid[:8] + "-" + raw_udid[8:];
|
||||
var udid = Udid (raw_udid);
|
||||
string udid = props.get_string ("SerialNumber");
|
||||
if (udid.length == 24)
|
||||
udid = udid[:8] + "-" + udid[8:];
|
||||
|
||||
InetSocketAddress? network_address = null;
|
||||
if (connection_type == NETWORK)
|
||||
network_address = parse_network_address (props.get_bytes ("NetworkAddress"));
|
||||
|
||||
var details =
|
||||
new DeviceDetails (connection_type, device_id, product_id, udid, network_address);
|
||||
device_attached (details);
|
||||
var device =
|
||||
new UsbmuxDevice (connection_type, device_id, product_id, udid, network_address);
|
||||
devices[device_id] = device;
|
||||
device_attached (device);
|
||||
} else if (message_type == "Detached") {
|
||||
device_detached (DeviceId ((uint) body.get_integer ("DeviceID")));
|
||||
UsbmuxDevice? device;
|
||||
if (devices.unset ((uint) body.get_integer ("DeviceID"), out device))
|
||||
device_detached (device);
|
||||
} else {
|
||||
throw new UsbmuxError.PROTOCOL ("Unexpected message type: %s", message_type);
|
||||
}
|
||||
@@ -546,23 +548,23 @@ namespace Frida.Fruity {
|
||||
PROTOCOL
|
||||
}
|
||||
|
||||
public class DeviceDetails : Object {
|
||||
public class UsbmuxDevice : Object {
|
||||
public ConnectionType connection_type {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public DeviceId id {
|
||||
public uint id {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public ProductId product_id {
|
||||
public int product_id {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Udid udid {
|
||||
public string udid {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
@@ -572,7 +574,7 @@ namespace Frida.Fruity {
|
||||
construct;
|
||||
}
|
||||
|
||||
public DeviceDetails (ConnectionType connection_type, DeviceId id, ProductId product_id, Udid udid,
|
||||
public UsbmuxDevice (ConnectionType connection_type, uint id, int product_id, string udid,
|
||||
InetSocketAddress? network_address) {
|
||||
Object (
|
||||
connection_type: connection_type,
|
||||
@@ -583,42 +585,4 @@ namespace Frida.Fruity {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ConnectionType {
|
||||
USB,
|
||||
NETWORK
|
||||
}
|
||||
|
||||
public struct DeviceId {
|
||||
public uint raw_value {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public DeviceId (uint raw_value) {
|
||||
this.raw_value = raw_value;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ProductId {
|
||||
public int raw_value {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ProductId (int raw_value) {
|
||||
this.raw_value = raw_value;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Udid {
|
||||
public string raw_value {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Udid (string raw_value) {
|
||||
this.raw_value = raw_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
+4322
File diff suppressed because it is too large.
Load diff
@@ -9,26 +9,17 @@ namespace Frida {
|
||||
|
||||
public HostSessionService.with_default_backends () {
|
||||
add_local_backends ();
|
||||
#if !IOS && !ANDROID && !TVOS
|
||||
#if HAVE_FRUITY_BACKEND
|
||||
add_backend (new FruityHostSessionBackend ());
|
||||
#endif
|
||||
#if HAVE_DROIDY_BACKEND
|
||||
add_backend (new DroidyHostSessionBackend ());
|
||||
#endif
|
||||
#endif
|
||||
#if HAVE_SOCKET_BACKEND
|
||||
add_backend (new SocketHostSessionBackend ());
|
||||
#endif
|
||||
#if HAVE_BAREBONE_BACKEND
|
||||
add_backend (new BareboneHostSessionBackend ());
|
||||
#endif
|
||||
add_nonlocal_backends ();
|
||||
}
|
||||
|
||||
public HostSessionService.with_local_backend_only () {
|
||||
add_local_backends ();
|
||||
}
|
||||
|
||||
public HostSessionService.with_nonlocal_backends_only () {
|
||||
add_nonlocal_backends ();
|
||||
}
|
||||
|
||||
public HostSessionService.with_socket_backend_only () {
|
||||
#if HAVE_SOCKET_BACKEND
|
||||
add_backend (new SocketHostSessionBackend ());
|
||||
@@ -55,6 +46,23 @@ namespace Frida {
|
||||
#endif
|
||||
}
|
||||
|
||||
private void add_nonlocal_backends () {
|
||||
#if !IOS && !ANDROID && !TVOS
|
||||
#if HAVE_FRUITY_BACKEND
|
||||
add_backend (new FruityHostSessionBackend ());
|
||||
#endif
|
||||
#if HAVE_DROIDY_BACKEND
|
||||
add_backend (new DroidyHostSessionBackend ());
|
||||
#endif
|
||||
#endif
|
||||
#if HAVE_SOCKET_BACKEND
|
||||
add_backend (new SocketHostSessionBackend ());
|
||||
#endif
|
||||
#if HAVE_BAREBONE_BACKEND
|
||||
add_backend (new BareboneHostSessionBackend ());
|
||||
#endif
|
||||
}
|
||||
|
||||
public async void start (Cancellable? cancellable = null) throws IOError {
|
||||
var remaining = backends.size + 1;
|
||||
|
||||
@@ -177,12 +185,16 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public interface ChannelProvider : Object {
|
||||
public interface HostChannelProvider : Object {
|
||||
public abstract async IOStream open_channel (string address, Cancellable? cancellable = null) throws Error, IOError;
|
||||
}
|
||||
|
||||
public interface HostServiceProvider : Object {
|
||||
public abstract async Service open_service (string address, Cancellable? cancellable = null) throws Error, IOError;
|
||||
}
|
||||
|
||||
public interface Pairable : Object {
|
||||
public abstract async void unpair (Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void unpair (Cancellable? cancellable = null) throws Error, IOError;
|
||||
}
|
||||
|
||||
public interface HostSessionBackend : Object {
|
||||
@@ -1331,10 +1343,11 @@ namespace Frida {
|
||||
protected virtual void on_event (string type, Json.Array event) {
|
||||
}
|
||||
|
||||
protected async Json.Node call (string method, Json.Node[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
protected async Json.Node call (string method, Json.Node[] args, Bytes? data, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
yield ensure_loaded (cancellable);
|
||||
|
||||
return yield rpc_client.call (method, args, cancellable);
|
||||
return yield rpc_client.call (method, args, data, cancellable);
|
||||
}
|
||||
|
||||
protected async void post (Json.Node message, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -1504,9 +1517,11 @@ namespace Frida {
|
||||
printerr ("%s\n", json);
|
||||
}
|
||||
|
||||
private async void post_rpc_message (string json, Cancellable? cancellable) throws Error, IOError {
|
||||
private async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError {
|
||||
var has_data = data != null;
|
||||
var data_param = has_data ? data.get_data () : new uint8[0];
|
||||
try {
|
||||
yield session.post_messages ({ AgentMessage (SCRIPT, script, json, false, {}) }, 0, cancellable);
|
||||
yield session.post_messages ({ AgentMessage (SCRIPT, script, json, has_data, data_param) }, 0, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_dbus_error (e);
|
||||
}
|
||||
@@ -1537,7 +1552,7 @@ namespace Frida {
|
||||
|
||||
#if HAVE_FRUITY_BACKEND || HAVE_DROIDY_BACKEND
|
||||
internal async DBusConnection establish_direct_connection (TransportBroker broker, AgentSessionId id,
|
||||
ChannelProvider channel_provider, Cancellable? cancellable) throws Error, IOError {
|
||||
HostChannelProvider channel_provider, Cancellable? cancellable) throws Error, IOError {
|
||||
uint16 port;
|
||||
string token;
|
||||
try {
|
||||
|
||||
@@ -844,22 +844,22 @@ namespace Frida {
|
||||
private const uint64 SOCK_CLOEXEC = 0x80000;
|
||||
|
||||
private class InjectSession : SeizeSession {
|
||||
private static ProcMapsEntry local_libc;
|
||||
private static ProcMapsSoEntry local_libc;
|
||||
private static uint64 mmap_offset;
|
||||
private static uint64 munmap_offset;
|
||||
|
||||
private static string fallback_ld;
|
||||
private static string fallback_libc;
|
||||
|
||||
private static ProcMapsEntry? local_android_ld;
|
||||
private static ProcMapsSoEntry? local_android_ld;
|
||||
|
||||
static construct {
|
||||
string libc_name = Gum.Process.query_libc_name ();
|
||||
var libc = Gum.Process.get_libc_module ();
|
||||
uint local_pid = Posix.getpid ();
|
||||
local_libc = ProcMapsEntry.find_by_path (local_pid, libc_name);
|
||||
local_libc = ProcMapsSoEntry.find_by_path (local_pid, libc.path);
|
||||
assert (local_libc != null);
|
||||
mmap_offset = (uint64) (uintptr) Gum.Module.find_export_by_name (libc_name, "mmap") - local_libc.base_address;
|
||||
munmap_offset = (uint64) (uintptr) Gum.Module.find_export_by_name (libc_name, "munmap") - local_libc.base_address;
|
||||
mmap_offset = (uint64) (uintptr) libc.find_export_by_name ("mmap") - local_libc.base_address;
|
||||
munmap_offset = (uint64) (uintptr) libc.find_export_by_name ("munmap") - local_libc.base_address;
|
||||
|
||||
try {
|
||||
var program = new Gum.ElfModule.from_file ("/proc/self/exe");
|
||||
@@ -877,7 +877,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
#if ANDROID
|
||||
local_android_ld = ProcMapsEntry.find_by_path (local_pid, fallback_ld);
|
||||
local_android_ld = ProcMapsSoEntry.find_by_path (local_pid, fallback_ld);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1017,8 +1017,9 @@ namespace Frida {
|
||||
establish_connection (launch, spec, bres, agent_ctrl, fallback_address, cancellable);
|
||||
|
||||
uint64 loader_base = (uintptr) bres.context.allocation_base;
|
||||
|
||||
var call_builder = new RemoteCallBuilder (loader_base, saved_regs);
|
||||
GPRegs regs = saved_regs;
|
||||
regs.stack_pointer = bres.allocated_stack.stack_root;
|
||||
var call_builder = new RemoteCallBuilder (loader_base, regs);
|
||||
call_builder.add_argument (loader_base + loader_layout.ctx_offset);
|
||||
RemoteCall loader_call = call_builder.build (this);
|
||||
RemoteCallResult loader_result = yield loader_call.execute (cancellable);
|
||||
@@ -1076,13 +1077,25 @@ namespace Frida {
|
||||
unowned uint8[] bootstrapper_code = Frida.Data.HelperBackend.get_bootstrapper_bin_blob ().data;
|
||||
size_t bootstrapper_size = round_size_to_page_size (bootstrapper_code.length);
|
||||
|
||||
size_t stack_size = 64 * 1024;
|
||||
|
||||
uint64 allocation_base = 0;
|
||||
size_t allocation_size = size_t.max (bootstrapper_size, loader_size);
|
||||
size_t allocation_size = size_t.max (bootstrapper_size, loader_size) + stack_size;
|
||||
|
||||
uint64 remote_mmap = 0;
|
||||
uint64 remote_munmap = 0;
|
||||
ProcMapsEntry? remote_libc = ProcMapsEntry.find_by_path (pid, local_libc.path);
|
||||
ProcMapsSoEntry? remote_libc = ProcMapsSoEntry.find_by_path (pid, local_libc.path);
|
||||
#if ANDROID
|
||||
bool same_libc = false;
|
||||
if (remote_libc != null) {
|
||||
bool same_device = remote_libc.identity.split (":")[0] == local_libc.identity.split (":")[0];
|
||||
bool same_inode = remote_libc.identity.split (" ")[1] == local_libc.identity.split (" ")[1];
|
||||
bool same_path = remote_libc.path == local_libc.path;
|
||||
same_libc = same_device && same_inode && same_path;
|
||||
}
|
||||
#else
|
||||
bool same_libc = remote_libc != null && remote_libc.identity == local_libc.identity;
|
||||
#endif
|
||||
if (same_libc) {
|
||||
remote_mmap = remote_libc.base_address + mmap_offset;
|
||||
remote_munmap = remote_libc.base_address + munmap_offset;
|
||||
@@ -1117,10 +1130,12 @@ namespace Frida {
|
||||
Memory.copy (&bootstrap_ctx, output_context, output_context.length);
|
||||
|
||||
allocation_base = (uintptr) bootstrap_ctx.allocation_base;
|
||||
|
||||
code_swap.revert ();
|
||||
}
|
||||
|
||||
result.allocated_stack.stack_base = (void *) (allocation_base + allocation_size - stack_size);
|
||||
result.allocated_stack.stack_size = stack_size;
|
||||
|
||||
try {
|
||||
write_memory (allocation_base, bootstrapper_code);
|
||||
maybe_fixup_helper_code (allocation_base, bootstrapper_code);
|
||||
@@ -1129,7 +1144,9 @@ namespace Frida {
|
||||
|
||||
HelperBootstrapStatus status = SUCCESS;
|
||||
do {
|
||||
var call_builder = new RemoteCallBuilder (code_start, saved_regs);
|
||||
GPRegs regs = saved_regs;
|
||||
regs.stack_pointer = result.allocated_stack.stack_root;
|
||||
var call_builder = new RemoteCallBuilder (code_start, regs);
|
||||
|
||||
unowned uint8[] fallback_ld_data = fallback_ld.data;
|
||||
unowned uint8[] fallback_libc_data = fallback_libc.data;
|
||||
@@ -1179,7 +1196,7 @@ namespace Frida {
|
||||
result.context.libc = &result.libc;
|
||||
|
||||
if (result.context.rtld_flavor == ANDROID && result.libc.dlopen == null) {
|
||||
ProcMapsEntry? remote_ld = ProcMapsEntry.find_by_address (pid, (uintptr) result.context.rtld_base);
|
||||
ProcMapsSoEntry? remote_ld = ProcMapsSoEntry.find_by_address (pid, (uintptr) result.context.rtld_base);
|
||||
bool same_ld = remote_ld != null && local_android_ld != null && remote_ld.identity == local_android_ld.identity;
|
||||
if (!same_ld)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to locate Android dynamic linker; please file a bug");
|
||||
@@ -1229,7 +1246,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private static void * rebase_pointer (uintptr local_ptr, ProcMapsEntry local_module, ProcMapsEntry remote_module) {
|
||||
private static void * rebase_pointer (uintptr local_ptr, ProcMapsSoEntry local_module, ProcMapsSoEntry remote_module) {
|
||||
var offset = local_ptr - local_module.base_address;
|
||||
return (void *) (remote_module.base_address + offset);
|
||||
}
|
||||
@@ -1416,14 +1433,28 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private struct AllocatedStack {
|
||||
public void * stack_base;
|
||||
public size_t stack_size;
|
||||
|
||||
public uint64 stack_root {
|
||||
get {
|
||||
return (uint64) stack_base + (uint64) stack_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class BootstrapResult {
|
||||
public HelperBootstrapContext context;
|
||||
public HelperLibcApi libc;
|
||||
public AllocatedStack allocated_stack;
|
||||
|
||||
public BootstrapResult clone () {
|
||||
var res = new BootstrapResult ();
|
||||
res.context = context;
|
||||
res.libc = libc;
|
||||
res.allocated_stack = allocated_stack;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -1931,6 +1962,10 @@ namespace Frida {
|
||||
try {
|
||||
yield suspend (null);
|
||||
close ();
|
||||
} catch (Error e) {
|
||||
// If the process is gone, then there's no point in retrying.
|
||||
if (e is Error.PROCESS_NOT_FOUND)
|
||||
attach_state = ALREADY_ATTACHED;
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
@@ -1963,15 +1998,15 @@ namespace Frida {
|
||||
#elif ARM
|
||||
target_address &= ~1;
|
||||
|
||||
uint32 arm_breakpoint_val = 0xe7f001f0U;
|
||||
uint16 thumb_breakpoint_val = 0xde01;
|
||||
uint32 arm_breakpoint_val = (0xe7f001f0U).to_little_endian ();
|
||||
uint16 thumb_breakpoint_val = ((uint16) 0xde01).to_little_endian ();
|
||||
bool is_thumb = (target & 1) != 0;
|
||||
if (is_thumb)
|
||||
breakpoint_data = (uint8[]) &thumb_breakpoint_val;
|
||||
else
|
||||
breakpoint_data = (uint8[]) &arm_breakpoint_val;
|
||||
#elif ARM64
|
||||
uint32 breakpoint_val = 0xd4200000U;
|
||||
uint32 breakpoint_val = (0xd4200000U).to_little_endian ();
|
||||
breakpoint_data = (uint8[]) &breakpoint_val;
|
||||
#elif MIPS
|
||||
uint32 breakpoint_val = 0x0000000dU;
|
||||
@@ -2255,6 +2290,9 @@ namespace Frida {
|
||||
this.session = session;
|
||||
|
||||
Gum.Linux.enumerate_ranges ((Posix.pid_t) session.pid, READ | EXECUTE, d => {
|
||||
unowned Gum.FileMapping? file = d.file;
|
||||
if (file != null && file.path.has_prefix ("memfd:"))
|
||||
return true;
|
||||
if (d.range.size >= code.length) {
|
||||
code_start = d.range.base_address + d.range.size - round_size_to_page_size (code.length);
|
||||
code_end = code_start + code.length;
|
||||
@@ -3281,38 +3319,87 @@ namespace Frida {
|
||||
}
|
||||
#endif
|
||||
|
||||
private class ProcMapsEntry {
|
||||
private class ProcMapsSoEntry {
|
||||
public uint64 base_address;
|
||||
public string path;
|
||||
public string identity;
|
||||
|
||||
private ProcMapsEntry (uint64 base_address, string path, string identity) {
|
||||
private ProcMapsSoEntry (uint64 base_address, string path, string identity) {
|
||||
this.base_address = base_address;
|
||||
this.path = path;
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
public static ProcMapsEntry? find_by_address (uint pid, uint64 address) {
|
||||
public static ProcMapsSoEntry? find_by_address (uint pid, uint64 address) {
|
||||
var iter = MapsIter.for_pid (pid);
|
||||
while (iter.next ()) {
|
||||
uint64 start = iter.start_address;
|
||||
uint64 end = iter.end_address;
|
||||
if (address >= start && address < end)
|
||||
return new ProcMapsEntry (start, iter.path, iter.identity);
|
||||
return new ProcMapsSoEntry (start, iter.path, iter.identity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ProcMapsEntry? find_by_path (uint pid, string path) {
|
||||
public static ProcMapsSoEntry? find_by_path (uint pid, string path) {
|
||||
var candidates = new Gee.ArrayList<Candidate> ();
|
||||
Candidate? latest_candidate = null;
|
||||
var iter = MapsIter.for_pid (pid);
|
||||
#if ANDROID
|
||||
unowned string libc_path = Gum.Process.get_libc_module ().path;
|
||||
#endif
|
||||
while (iter.next ()) {
|
||||
string candidate_path = iter.path;
|
||||
if (candidate_path == path)
|
||||
return new ProcMapsEntry (iter.start_address, candidate_path, iter.identity);
|
||||
string current_path = iter.path;
|
||||
if (current_path == "[page size compat]")
|
||||
continue;
|
||||
if (current_path != path) {
|
||||
latest_candidate = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
string flags = iter.flags;
|
||||
|
||||
#if ANDROID
|
||||
if (current_path == libc_path && flags[3] == 's')
|
||||
continue;
|
||||
#endif
|
||||
|
||||
if (iter.file_offset == 0) {
|
||||
latest_candidate = new Candidate () {
|
||||
entry = new ProcMapsSoEntry (iter.start_address, current_path, iter.identity),
|
||||
total_ranges = 0,
|
||||
executable_ranges = 0,
|
||||
};
|
||||
candidates.add (latest_candidate);
|
||||
}
|
||||
|
||||
if (latest_candidate != null) {
|
||||
latest_candidate.total_ranges++;
|
||||
if (flags[2] == 'x')
|
||||
latest_candidate.executable_ranges++;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
candidates.sort ((a, b) => b.score () - a.score ());
|
||||
|
||||
if (candidates.is_empty)
|
||||
return null;
|
||||
|
||||
return candidates.first ().entry;
|
||||
}
|
||||
|
||||
private class Candidate {
|
||||
public ProcMapsSoEntry entry;
|
||||
public uint total_ranges;
|
||||
public uint executable_ranges;
|
||||
|
||||
public int score () {
|
||||
int result = (int) total_ranges;
|
||||
if (executable_ranges == 0)
|
||||
result = -result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private class MapsIter {
|
||||
@@ -3332,15 +3419,27 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public string identity {
|
||||
public string flags {
|
||||
owned get {
|
||||
return info.fetch (3);
|
||||
}
|
||||
}
|
||||
|
||||
public uint64 file_offset {
|
||||
get {
|
||||
return uint64.parse (info.fetch (4), 16);
|
||||
}
|
||||
}
|
||||
|
||||
public string identity {
|
||||
owned get {
|
||||
return info.fetch (5);
|
||||
}
|
||||
}
|
||||
|
||||
public string path {
|
||||
owned get {
|
||||
return info.fetch (4);
|
||||
return info.fetch (6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3355,8 +3454,8 @@ namespace Frida {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^([0-9a-f]+)-([0-9a-f]+) \S{4} [0-9a-f]+ ([0-9a-f]{2,}:[0-9a-f]{2,} \d+) +([^\n]+)$/m.match (contents,
|
||||
0, out info)) {
|
||||
if (!/^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,} \d+) +([^\n]+)$/m.match (
|
||||
contents, 0, out info)) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,8 +204,14 @@ namespace Frida {
|
||||
private async LinuxHelper obtain_for_64bit (Cancellable? cancellable) throws Error, IOError {
|
||||
if (factory64 == null) {
|
||||
var store = get_resource_store ();
|
||||
#if !ARM64
|
||||
/*
|
||||
* If we are building for ARM64 and our pointer size is not 8-bytes, then we must be building for
|
||||
* ILP32, so avoid the spurious error.
|
||||
*/
|
||||
if (sizeof (void *) != 8 && store.helper64 == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to handle 64-bit processes due to build configuration");
|
||||
#endif
|
||||
factory64 = new HelperFactory (store.helper64, store, main_context);
|
||||
factory64.lost.connect (on_factory_lost);
|
||||
factory64.output.connect (on_factory_output);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef __FRIDA_SYSCALL_H__
|
||||
#define __FRIDA_SYSCALL_H__
|
||||
|
||||
#include <linux/unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#undef SYS_process_vm_readv
|
||||
|
||||
@@ -2,24 +2,21 @@ top_srcdir := ../../..
|
||||
releng := $(top_srcdir)/releng
|
||||
|
||||
BUILDDIR ?= $(top_srcdir)/build
|
||||
FRIDA_MONOREPO ?= $(top_srcdir)/..
|
||||
MESON ?= $(releng)/meson/meson.py
|
||||
|
||||
ifdef FRIDA_HOST
|
||||
host_os := $(shell echo $(FRIDA_HOST) | cut -f1 -d"-")
|
||||
host_arch := $(shell echo $(FRIDA_HOST) | cut -f2 -d"-")
|
||||
host_machine := $(FRIDA_HOST)
|
||||
else
|
||||
host_os := $(shell $(releng)/detect-os.sh)
|
||||
host_arch := $(shell $(releng)/detect-arch.sh)
|
||||
host_machine := $(shell basename $$(ls -1 $(BUILDDIR)/frida-linux-*.txt | tail -1) | cut -d"." -f1 | cut -d"-" -f2-)
|
||||
endif
|
||||
host_arch := $(shell echo $(host_machine) | cut -d"-" -f2)
|
||||
|
||||
crossfile := $(shell test -d "$(BUILDDIR)" \
|
||||
&& echo "$(BUILDDIR)/frida-$(host_os)-$(host_arch).txt" \
|
||||
|| echo $(FRIDA_MONOREPO)/build/frida*-$(host_os)-$(host_arch).txt)
|
||||
crossfile := $(BUILDDIR)/frida-$(host_machine).txt
|
||||
|
||||
build: ext/linux/tools/include/nolibc/nolibc.h
|
||||
rm -rf build
|
||||
meson setup --cross-file $(crossfile) -Db_lto=true build
|
||||
meson compile -C build
|
||||
$(MESON) setup --cross-file $(crossfile) -Db_lto=true build
|
||||
$(MESON) compile -C build
|
||||
cp build/bootstrapper.bin bootstrapper-$(host_arch).bin
|
||||
cp build/loader.bin loader-$(host_arch).bin
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -550,7 +550,9 @@ frida_infer_rtld_flavor_from_filename (const char * name)
|
||||
if (frida_str_has_prefix (name, "ld-uClibc"))
|
||||
return FRIDA_RTLD_UCLIBC;
|
||||
|
||||
if (strcmp (name, "libc.so") == 0)
|
||||
if (strcmp (name, "libc.so") == 0 ||
|
||||
frida_str_has_prefix (name, "libc.musl") ||
|
||||
frida_str_has_prefix (name, "ld-musl"))
|
||||
return FRIDA_RTLD_MUSL;
|
||||
|
||||
if (strcmp (name, "ld-android.so") == 0)
|
||||
@@ -607,7 +609,9 @@ frida_path_is_libc (const char * path, FridaRtldFlavor rtld_flavor)
|
||||
else
|
||||
name = path;
|
||||
|
||||
return frida_str_has_prefix (name, "libc.so");
|
||||
return frida_str_has_prefix (name, "libc.so") ||
|
||||
frida_str_has_prefix (name, "libc.musl") ||
|
||||
frida_str_has_prefix (name, "ld-musl");
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
|
||||
Loaded 100 of 149 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user