Compare commits

...
16 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 5aeb800a7a subprojects: Prepare for release 2024-07-13 00:34:04 +02:00
Ole André Vadla Ravnås a01f5dcd3d subprojects: Bump outdated 2024-07-13 00:06:51 +02:00
Ole André Vadla Ravnås 23b15b5f4b ncm: Switch USB device configuration if needed 2024-07-12 15:31:43 +02:00
Ole André Vadla Ravnås f1fdd980aa fruity: Add support for networked devices on Windows
By making use of DnsServiceBrowse() if the OS supports it -- which means
Windows 10 and newer.
2024-07-12 15:29:15 +02:00
Ole André Vadla Ravnås 05a68332f6 network-stack: Fix TcpConnection use-after-free
When lwIP notifies us about the PCB being closed or an error, we must
clear its user data so any future callbacks don't end up using the
TcpConnection after it's gone.
2024-07-12 00:06:17 +02:00
Ole André Vadla Ravnås 13521724f7 fruity: Remove commented-out variable 2024-07-11 23:57:55 +02:00
Ole André Vadla Ravnås 6c700a511e fruity: Split out DnsPacketReader 2024-07-11 23:14:01 +02:00
Ole André Vadla Ravnås 02ccafe516 fruity: Move LinuxPairingBrowser to ResolveService
Which is simpler and avoids some roundtrips to systemd-resolved.
2024-07-11 23:09:08 +02:00
Ole André Vadla Ravnås fb672d7a0b ci: Move the macOS x86_64 job to macos-latest
As the macos-11 runner is gone.

Also skip running tests on macOS entirely for now, as a stopgap until
__CFInitialize() has been dealt with.
2024-07-11 15:13:14 +02:00
Ole André Vadla RavnåsandHåvard Sørbø 81fbfe2ab5 fruity: Add support for networked devices on Linux
By making use of systemd-resolved for mDNS-SD lookups.

Co-authored-by: Håvard Sørbø <[email protected]>
2024-07-11 15:09:12 +02:00
Ole André Vadla Ravnås 3aef106ec2 fruity: Fix the LWIP.NetworkInterface bindings
Where we would end up passing in a copy of the struct and have e.g.
neighbor discovery timers make use of dangling pointers.
2024-07-11 15:09:12 +02:00
Ole André Vadla Ravnås 370f1836a7 dtx: Fix teardown of DTXConnection vs. DTXChannel
So channels never make use of the connection once it's gone.
2024-07-11 15:09:12 +02:00
Ole André Vadla RavnåsandHåvard Sørbø 70bb82bb5d fruity: Remove forgotten .pcap debug output
Co-authored-by: Håvard Sørbø <[email protected]>
2024-07-11 15:09:12 +02:00
Ole André Vadla Ravnås dc43be94c5 buffer: Add BufferReader 2024-07-11 15:09:12 +02:00
Ole André Vadla Ravnås 7c7367b010 buffer: Add content validation to read_fixed_string()
To support parsing untrusted data.
2024-07-11 15:09:12 +02:00
Ole André Vadla Ravnås a9073c07eb buffer: Add content validation to read_string()
To support parsing untrusted data.
2024-07-11 15:09:12 +02:00
17 changed files with 1719 additions and 590 deletions

No files matched your search

+2 -2
View File
@@ -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"' }
@@ -68,7 +68,7 @@ jobs:
name: core-devkit-${{ matrix.id }}
path: build/src/devkit/
- name: Test
if: matrix.id != 'macos-arm64'
if: ${{ !startsWith(matrix.id, 'macos-') }}
run: make test
cross:
+135 -9
View File
@@ -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");
}
}
}
+204 -181
View File
@@ -35,209 +35,232 @@ namespace Frida.Fruity {
}
public class LinuxPairingBrowser : Object, PairingBrowser {
private DBusConnection connection;
private AvahiServer server;
private AvahiServiceBrowser browser;
private Gee.List<PairingServiceDetails> current_batch = new Gee.ArrayList<PairingServiceDetails> ();
private Resolved.Manager? resolved;
private Source? timer;
private Cancellable io_cancellable = new Cancellable ();
construct {
start.begin ();
}
private const size_t IF_NAMESIZE = 16;
private async void start () {
public async void start (Cancellable? cancellable) throws IOError {
try {
connection = yield GLib.Bus.get (BusType.SYSTEM, io_cancellable);
var connection = yield GLib.Bus.get (BusType.SYSTEM, cancellable);
server = yield connection.get_proxy (AVAHI_SERVICE_NAME, "/", DO_NOT_LOAD_PROPERTIES, io_cancellable);
resolved = yield connection.get_proxy (Resolved.SERVICE_NAME, Resolved.SERVICE_PATH, DO_NOT_LOAD_PROPERTIES,
cancellable);
GLib.ObjectPath browser_path = yield server.service_browser_prepare (-1, INET6, PAIRING_REGTYPE,
PAIRING_DOMAIN, 0, io_cancellable);
browser = yield connection.get_proxy (AVAHI_SERVICE_NAME, browser_path, DO_NOT_LOAD_PROPERTIES,
io_cancellable);
browser.item_new.connect (on_item_new);
browser.all_for_now.connect (on_all_for_now);
yield browser.start (io_cancellable);
} catch (GLib.Error e) {
printerr ("Oopsie: %s\n", e.message);
}
}
private void on_item_new (int interface_index, AvahiProtocol protocol, string name, string type, string domain, uint flags) {
char raw_interface_name[Linux.Network.INTERFACE_NAME_SIZE];
Linux.Network.if_indextoname (interface_index, (string) raw_interface_name);
unowned string interface_name = (string) raw_interface_name;
current_batch.add (new LinuxPairingServiceDetails (name, interface_index, interface_name, protocol, server));
}
private void on_all_for_now () {
services_discovered (current_batch.to_array ());
current_batch.clear ();
}
}
private class LinuxPairingServiceDetails : Object, PairingServiceDetails {
public string name {
get { return _name; }
}
public uint interface_index {
get { return _interface_index; }
}
public string interface_name {
get { return _interface_name; }
}
private string _name;
private uint _interface_index;
private string _interface_name;
private AvahiProtocol protocol;
private AvahiServer server;
internal LinuxPairingServiceDetails (string name, uint interface_index, string interface_name, AvahiProtocol protocol,
AvahiServer server) {
_name = name;
_interface_index = interface_index;
_interface_name = interface_name;
this.protocol = protocol;
this.server = server;
}
public async Gee.List<PairingServiceHost> resolve (Cancellable? cancellable) throws Error, IOError {
AvahiServiceResolver resolver;
try {
GLib.ObjectPath path = yield server.service_resolver_prepare ((int) interface_index, protocol, name,
PAIRING_REGTYPE, PAIRING_DOMAIN, INET6, 0, cancellable);
DBusConnection connection = ((DBusProxy) server).get_connection ();
resolver = yield connection.get_proxy (AVAHI_SERVICE_NAME, path, DO_NOT_LOAD_PROPERTIES, cancellable);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
var promise = new Promise<Gee.List<PairingServiceHost>> ();
var hosts = new Gee.ArrayList<PairingServiceHost> ();
resolver.found.connect ((interface_index, protocol, name, type, domain, host, address_protocol, address, port, txt,
flags) => {
var txt_record = new Gee.ArrayList<string> ();
var iter = new VariantIter (txt);
Variant? cur;
while ((cur = iter.next_value ()) != null) {
unowned string raw_val = (string) cur.get_data ();
string val = raw_val.make_valid ((ssize_t) cur.get_size ());
txt_record.add (val);
try {
yield resolve_services (MDNS_IPV6 | NO_NETWORK, cancellable);
schedule_next_poll ();
} catch (Error e) {
handle_poll_timer_tick.begin ();
}
hosts.add (new LinuxPairingServiceHost (
host,
new InetSocketAddress.from_string (address, port),
port,
txt_record));
if (!promise.future.ready)
promise.resolve (hosts);
});
resolver.failure.connect (error => {
if (!promise.future.ready)
promise.reject (new Error.NOT_SUPPORTED ("%s", error));
});
try {
yield resolver.start (cancellable);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
}
public async void stop (Cancellable? cancellable) throws IOError {
if (timer != null) {
timer.destroy ();
timer = null;
}
return yield promise.future.wait_async (cancellable);
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) {
if (candidate.ifa_name != ifname)
continue;
if (candidate.ifa_addr.sa_family != Posix.AF_INET6)
continue;
return (InetSocketAddress)
SocketAddress.from_native ((void *) candidate.ifa_addr, 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);
}
}
public class LinuxPairingServiceHost : Object, PairingServiceHost {
public string name {
get { return _name; }
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;
}
public InetSocketAddress address {
get { return _address; }
[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 uint16 port {
get { return _port; }
public struct RRItem {
public int32 ifindex;
public uint16 klass;
public uint16 type;
public uint8[] data;
}
public Gee.List<string> txt_record {
get { return _txt_record; }
public struct SrvItem {
public uint16 priority;
public uint16 weight;
public uint16 port;
public string name;
public SrvAddress[] addresses;
public string canonical_name;
}
private string _name;
private InetSocketAddress _address;
private uint16 _port;
private Gee.List<string> _txt_record;
internal LinuxPairingServiceHost (string name, InetSocketAddress address, uint16 port, Gee.List<string> txt_record) {
_name = name;
_address = address;
_port = port;
_txt_record = txt_record;
public struct SrvAddress {
public int32 ifindex;
public int32 family;
public uint8[] ip;
}
public async Gee.List<InetSocketAddress> resolve (Cancellable? cancellable) throws Error, IOError {
var result = new Gee.ArrayList<InetSocketAddress> ();
result.add (address);
return result;
}
}
private const string AVAHI_SERVICE_NAME = "org.freedesktop.Avahi";
[DBus (name = "org.freedesktop.Avahi.Server2")]
private interface AvahiServer : Object {
public abstract async GLib.ObjectPath service_browser_prepare (int interface_index, AvahiProtocol protocol, string type,
string domain, uint flags, Cancellable? cancellable) throws GLib.Error;
public abstract async GLib.ObjectPath service_resolver_prepare (int interface_index, AvahiProtocol protocol, string name,
string type, string domain, AvahiProtocol aprotocol, AvahiLookupFlags flags, Cancellable? cancellable)
throws GLib.Error;
}
[DBus (name = "org.freedesktop.Avahi.ServiceBrowser")]
private interface AvahiServiceBrowser : Object {
public signal void item_new (int interface_index, AvahiProtocol protocol, string name, string type, string domain,
uint flags);
public signal void item_remove (int interface_index, AvahiProtocol protocol, string name, string type, string domain,
uint flags);
public signal void failure (string error);
public signal void all_for_now ();
public signal void cache_exhausted ();
public abstract async void start (Cancellable? cancellable) throws GLib.Error;
public abstract async void free (Cancellable? cancellable) throws GLib.Error;
}
[DBus (name = "org.freedesktop.Avahi.ServiceResolver")]
private interface AvahiServiceResolver : Object {
public signal void found (int interface_index, AvahiProtocol protocol, string name, string type, string domain, string host,
AvahiProtocol address_protocol, string address, uint16 port, [DBus (signature = "aay")] Variant txt, uint flags);
public signal void failure (string error);
public abstract async void start (Cancellable? cancellable) throws GLib.Error;
public abstract async void free (Cancellable? cancellable) throws GLib.Error;
}
private enum AvahiProtocol {
INET,
INET6,
UNSPEC = -1,
}
[Flags]
private enum AvahiLookupFlags {
USE_WIDE_AREA = 1,
USE_MULTICAST = 2,
NO_TXT = 4,
NO_ADDRESS = 8,
}
}
+146 -1
View File
@@ -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,8 +90,104 @@ 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_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 = &adapter->FirstUnicastAddress->Address;
GInetSocketAddress * addr;
if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
continue;
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)
@@ -426,3 +544,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);
}
+102
View File
@@ -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);
}
}
+301 -90
View File
@@ -708,8 +708,9 @@ namespace Frida.Fruity {
}
private State state = CREATED;
private Gee.Set<PortableCoreDeviceTransport> transports = new Gee.HashSet<PortableCoreDeviceTransport> ();
private Promise<bool> started = new Promise<bool> ();
private Gee.Set<PortableCoreDeviceUsbTransport> usb_transports = new Gee.HashSet<PortableCoreDeviceUsbTransport> ();
private Promise<bool> usb_started = new Promise<bool> ();
private bool modeswitch_allowed = false;
private Promise<bool>? modeswitch_activated;
private Gee.Set<string>? modeswitch_udids_pending;
@@ -718,10 +719,16 @@ namespace Frida.Fruity {
private LibUSB.Context? usb_context;
private LibUSB.HotCallbackHandle iphone_callback;
private LibUSB.HotCallbackHandle ipad_callback;
private uint pending_device_arrivals = 0;
private Gee.Map<uint32, LibUSB.Device> polled_devices = new Gee.HashMap<uint32, LibUSB.Device> ();
private Source? polled_timer;
private uint polled_outdated = 0;
private uint pending_usb_device_arrivals = 0;
private Gee.Map<uint32, LibUSB.Device> polled_usb_devices = new Gee.HashMap<uint32, LibUSB.Device> ();
private Source? polled_usb_timer;
private uint polled_usb_outdated = 0;
private PairingBrowser network_browser = PairingBrowser.make_default ();
private Gee.Map<string, PortableCoreDeviceNetworkTransport> network_transports =
new Gee.HashMap<string, PortableCoreDeviceNetworkTransport> ();
private Gee.List<PairingIdentity.Peer> peers = PairingIdentity.load_peers ();
private MainContext main_context;
@@ -741,6 +748,8 @@ namespace Frida.Fruity {
construct {
main_context = MainContext.ref_thread_default ();
network_browser.service_discovered.connect (on_network_pairing_service_discovered);
}
public async void start (Cancellable? cancellable) throws IOError {
@@ -748,8 +757,10 @@ namespace Frida.Fruity {
usb_worker = new Thread<void> ("frida-core-device-usb", perform_usb_work);
yield network_browser.start (cancellable);
try {
yield started.future.wait_async (cancellable);
yield usb_started.future.wait_async (cancellable);
} catch (Error e) {
assert_not_reached ();
}
@@ -765,17 +776,23 @@ namespace Frida.Fruity {
if (usb_context != null)
usb_context.interrupt_event_handler ();
yield network_browser.stop (cancellable);
foreach (var transport in network_transports.values.to_array ())
yield transport.close (cancellable);
network_transports.clear ();
usb_worker.join ();
usb_worker = null;
if (polled_timer != null) {
polled_timer.destroy ();
polled_timer = null;
if (polled_usb_timer != null) {
polled_usb_timer.destroy ();
polled_usb_timer = null;
}
foreach (var transport in transports)
foreach (var transport in usb_transports.to_array ())
yield transport.close (cancellable);
transports.clear ();
usb_transports.clear ();
usb_context = null;
@@ -787,7 +804,7 @@ namespace Frida.Fruity {
modeswitch_activated = new Promise<bool> ();
modeswitch_udids_pending = new Gee.HashSet<string> ();
foreach (var transport in transports.to_array ()) {
foreach (var transport in usb_transports.to_array ()) {
var usb_device = transport.usb_device;
try {
if (yield usb_device.maybe_modeswitch (cancellable))
@@ -805,32 +822,37 @@ namespace Frida.Fruity {
}
private void perform_usb_work () {
if (LibUSB.Context.init (out usb_context) != SUCCESS)
if (LibUSB.Context.init (out usb_context) != SUCCESS) {
schedule_on_frida_thread (() => {
usb_started.resolve (true);
return Source.REMOVE;
});
return;
}
AtomicUint.inc (ref pending_device_arrivals);
AtomicUint.inc (ref pending_usb_device_arrivals);
if (LibUSB.has_capability (HAS_HOTPLUG) != 0) {
usb_context.hotplug_register_callback (DEVICE_ARRIVED | DEVICE_LEFT, ENUMERATE, VENDOR_ID_APPLE,
PRODUCT_ID_IPHONE, LibUSB.HotPlugEvent.MATCH_ANY, on_hotplug_event, out iphone_callback);
PRODUCT_ID_IPHONE, LibUSB.HotPlugEvent.MATCH_ANY, on_usb_hotplug_event, out iphone_callback);
usb_context.hotplug_register_callback (DEVICE_ARRIVED | DEVICE_LEFT, ENUMERATE, VENDOR_ID_APPLE,
PRODUCT_ID_IPAD, LibUSB.HotPlugEvent.MATCH_ANY, on_hotplug_event, out ipad_callback);
PRODUCT_ID_IPAD, LibUSB.HotPlugEvent.MATCH_ANY, on_usb_hotplug_event, out ipad_callback);
} else {
refresh_polled_devices ();
refresh_polled_usb_devices ();
var source = new TimeoutSource.seconds (2);
source.set_callback (() => {
AtomicUint.set (ref polled_outdated, 1);
AtomicUint.set (ref polled_usb_outdated, 1);
usb_context.interrupt_event_handler ();
return Source.CONTINUE;
});
source.attach (main_context);
polled_timer = source;
polled_usb_timer = source;
}
if (AtomicUint.dec_and_test (ref pending_device_arrivals)) {
if (AtomicUint.dec_and_test (ref pending_usb_device_arrivals)) {
schedule_on_frida_thread (() => {
started.resolve (true);
usb_started.resolve (true);
return Source.REMOVE;
});
}
@@ -839,35 +861,35 @@ namespace Frida.Fruity {
int completed = 0;
usb_context.handle_events_completed (out completed);
if (AtomicUint.compare_and_exchange (ref polled_outdated, 1, 0))
refresh_polled_devices ();
if (AtomicUint.compare_and_exchange (ref polled_usb_outdated, 1, 0))
refresh_polled_usb_devices ();
}
}
private int on_hotplug_event (LibUSB.Context ctx, LibUSB.Device device, LibUSB.HotPlugEvent event) {
private int on_usb_hotplug_event (LibUSB.Context ctx, LibUSB.Device device, LibUSB.HotPlugEvent event) {
if (event == DEVICE_ARRIVED)
on_device_arrived (device);
on_usb_device_arrived (device);
else
on_device_left (device);
on_usb_device_left (device);
return 0;
}
private void on_device_arrived (LibUSB.Device device) {
AtomicUint.inc (ref pending_device_arrivals);
private void on_usb_device_arrived (LibUSB.Device device) {
AtomicUint.inc (ref pending_usb_device_arrivals);
schedule_on_frida_thread (() => {
handle_device_arrival.begin (device);
handle_usb_device_arrival.begin (device);
return Source.REMOVE;
});
}
private void on_device_left (LibUSB.Device device) {
private void on_usb_device_left (LibUSB.Device device) {
schedule_on_frida_thread (() => {
handle_device_departure.begin (device);
handle_usb_device_departure.begin (device);
return Source.REMOVE;
});
}
private async void handle_device_arrival (LibUSB.Device raw_device) {
private async void handle_usb_device_arrival (LibUSB.Device raw_device) {
UsbDevice? device = null;
uint delays[] = { 0, 50, 250 };
@@ -875,11 +897,11 @@ namespace Frida.Fruity {
uint delay = delays[attempts];
if (delay != 0) {
var timeout_source = new TimeoutSource (delay);
timeout_source.set_callback (handle_device_arrival.callback);
timeout_source.set_callback (handle_usb_device_arrival.callback);
timeout_source.attach (main_context);
var cancel_source = new CancellableSource (io_cancellable);
cancel_source.set_callback (handle_device_arrival.callback);
cancel_source.set_callback (handle_usb_device_arrival.callback);
cancel_source.attach (main_context);
yield;
@@ -897,8 +919,8 @@ namespace Frida.Fruity {
if (modeswitch_allowed && yield device.maybe_modeswitch (io_cancellable))
break;
var transport = new PortableCoreDeviceTransport (device);
transports.add (transport);
var transport = new PortableCoreDeviceUsbTransport (device);
usb_transports.add (transport);
transport_attached (transport);
break;
@@ -909,8 +931,8 @@ namespace Frida.Fruity {
}
}
if (AtomicUint.dec_and_test (ref pending_device_arrivals) && state == STARTING)
started.resolve (true);
if (AtomicUint.dec_and_test (ref pending_usb_device_arrivals) && state == STARTING)
usb_started.resolve (true);
if (device != null && modeswitch_udids_pending != null) {
modeswitch_udids_pending.remove (device.udid);
@@ -919,13 +941,13 @@ namespace Frida.Fruity {
}
}
private async void handle_device_departure (LibUSB.Device raw_device) {
var transport = transports.first_match (t => t.usb_device.raw_device == raw_device);
private async void handle_usb_device_departure (LibUSB.Device raw_device) {
var transport = usb_transports.first_match (t => t.usb_device.raw_device == raw_device);
if (transport == null)
return;
transport_detached (transport);
transports.remove (transport);
usb_transports.remove (transport);
try {
yield transport.close (io_cancellable);
@@ -933,7 +955,7 @@ namespace Frida.Fruity {
}
}
private void refresh_polled_devices () {
private void refresh_polled_usb_devices () {
var current_devices = new Gee.HashMap<uint32, LibUSB.Device> ();
foreach (var device in usb_context.get_device_list ()) {
var desc = LibUSB.DeviceDescriptor (device);
@@ -944,27 +966,73 @@ namespace Frida.Fruity {
if (desc.idProduct != PRODUCT_ID_IPHONE && desc.idProduct != PRODUCT_ID_IPAD)
continue;
uint id = make_device_id (device, desc);
uint id = make_usb_device_id (device, desc);
current_devices[id] = device;
if (!polled_devices.has_key (id))
on_device_arrived (device);
if (!polled_usb_devices.has_key (id))
on_usb_device_arrived (device);
}
foreach (var e in polled_devices.entries) {
foreach (var e in polled_usb_devices.entries) {
if (!current_devices.has_key (e.key))
on_device_left (e.value);
on_usb_device_left (e.value);
}
polled_devices = current_devices;
polled_usb_devices = current_devices;
}
private static uint32 make_device_id (LibUSB.Device device, LibUSB.DeviceDescriptor desc) {
private static uint32 make_usb_device_id (LibUSB.Device device, LibUSB.DeviceDescriptor desc) {
return ((uint32) device.get_port_number () << 24) |
((uint32) device.get_device_address () << 16) |
(uint32) desc.idProduct;
}
private void on_network_pairing_service_discovered (PairingServiceDetails service) {
var peer = find_peer (service);
if (peer == null)
return;
var transport = network_transports[peer.udid];
if (transport == null) {
transport = new PortableCoreDeviceNetworkTransport (peer, service.endpoint, service.interface_address);
network_transports[peer.udid] = transport;
transport_attached (transport);
} else {
transport.endpoint = service.endpoint;
transport.interface_address = service.interface_address;
}
}
private PairingIdentity.Peer? find_peer (PairingServiceDetails service) {
var mac = OpenSSL.Envelope.MessageAuthCode.fetch (null, OpenSSL.ShortName.siphash);
size_t hash_size = 8;
size_t return_size = OpenSSL.ParamReturnSize.UNMODIFIED;
OpenSSL.Param mac_params[] = {
{ OpenSSL.Envelope.MessageAuthParameter.SIZE, UNSIGNED_INTEGER,
(uint8[]) &hash_size, return_size },
{ null, INTEGER, null, return_size },
};
foreach (var peer in peers) {
var ctx = new OpenSSL.Envelope.MessageAuthCodeContext (mac);
ctx.init (peer.irk.get_data (), mac_params);
ctx.update (service.identifier.data);
uint8 output[8];
size_t outlen = 0;
ctx.final (output, out outlen);
uint8 tag[6];
for (uint i = 0; i != 6; i++)
tag[i] = output[5 - i];
if (Memory.cmp (tag, service.auth_tag.get_data (), service.auth_tag.get_size ()) == 0)
return peer;
}
return null;
}
private void schedule_on_frida_thread (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
@@ -972,7 +1040,7 @@ namespace Frida.Fruity {
}
}
private sealed class PortableCoreDeviceTransport : Object, Transport {
private sealed class PortableCoreDeviceUsbTransport : Object, Transport {
public UsbDevice usb_device {
get;
construct;
@@ -1010,7 +1078,7 @@ namespace Frida.Fruity {
private Promise<Tunnel>? tunnel_request;
public PortableCoreDeviceTransport (UsbDevice usb_device) {
public PortableCoreDeviceUsbTransport (UsbDevice usb_device) {
Object (usb_device: usb_device);
}
@@ -1041,7 +1109,7 @@ namespace Frida.Fruity {
tunnel_request = new Promise<Tunnel> ();
try {
var tunnel = new PortableTunnel (usb_device);
var tunnel = new PortableUsbTunnel (usb_device);
yield tunnel.open (cancellable);
tunnel_request.resolve (tunnel);
@@ -1056,7 +1124,7 @@ namespace Frida.Fruity {
}
}
private sealed class PortableTunnel : Object, Tunnel {
private sealed class PortableUsbTunnel : Object, Tunnel {
public UsbDevice usb_device {
get;
construct;
@@ -1072,7 +1140,7 @@ namespace Frida.Fruity {
private TunnelConnection? tunnel_connection;
private DiscoveryService? _discovery_service;
public PortableTunnel (UsbDevice usb_device) {
public PortableUsbTunnel (UsbDevice usb_device) {
Object (usb_device: usb_device);
}
@@ -1332,6 +1400,165 @@ namespace Frida.Fruity {
}
}
private sealed class PortableCoreDeviceNetworkTransport : Object, Transport {
public PairingIdentity.Peer peer {
get;
construct;
}
public InetSocketAddress endpoint {
get;
set;
}
public InetSocketAddress interface_address {
get;
set;
}
public ConnectionType connection_type {
get {
return NETWORK;
}
}
public string udid {
get {
return peer.udid;
}
}
public string? name {
get {
return peer.name;
}
}
public Variant? icon {
get {
return null;
}
}
public UsbmuxDevice? usbmux_device {
get {
return null;
}
}
private Promise<Tunnel>? tunnel_request;
public PortableCoreDeviceNetworkTransport (PairingIdentity.Peer peer, InetSocketAddress endpoint,
InetSocketAddress interface_address) {
Object (
peer: peer,
endpoint: endpoint,
interface_address: interface_address
);
}
public async void close (Cancellable? cancellable) throws IOError {
if (tunnel_request != null) {
try {
var tunnel = yield tunnel_request.future.wait_async (cancellable);
yield tunnel.close (cancellable);
} catch (Error e) {
} finally {
tunnel_request = null;
}
}
}
public async Tunnel? find_tunnel (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 PortableNetworkTunnel (endpoint, interface_address);
yield tunnel.open (cancellable);
tunnel_request.resolve (tunnel);
return tunnel;
} catch (GLib.Error e) {
tunnel_request.reject (e);
tunnel_request = null;
throw_api_error (e);
}
}
}
private sealed class PortableNetworkTunnel : Object, Tunnel {
public InetSocketAddress endpoint {
get;
construct;
}
public InetSocketAddress interface_address {
get;
construct;
}
public DiscoveryService discovery {
get {
return _discovery_service;
}
}
private TunnelConnection? tunnel_connection;
private DiscoveryService? _discovery_service;
public PortableNetworkTunnel (InetSocketAddress endpoint, InetSocketAddress interface_address) {
Object (endpoint: endpoint, interface_address: interface_address);
}
public async void open (Cancellable? cancellable) throws Error, IOError {
var netstack = new SystemNetworkStack (interface_address.get_address (), interface_address.scope_id);
var pairing_connection = yield netstack.open_tcp_connection (endpoint, cancellable);
var pairing_transport = new PlainPairingTransport (pairing_connection);
var pairing_service = yield PairingService.open (pairing_transport, cancellable);
TunnelConnection tc = yield pairing_service.open_tunnel (endpoint.get_address (), netstack, cancellable);
var rsd_endpoint = (InetSocketAddress) Object.new (typeof (InetSocketAddress),
address: tc.remote_address,
port: tc.remote_rsd_port,
scope_id: tc.tunnel_netstack.scope_id
);
var rsd_connection = yield tc.tunnel_netstack.open_tcp_connection (rsd_endpoint, cancellable);
var disco = yield DiscoveryService.open (rsd_connection, cancellable);
tunnel_connection = tc;
_discovery_service = disco;
}
public async void close (Cancellable? cancellable) throws IOError {
_discovery_service.close ();
tunnel_connection.cancel ();
}
public async IOStream open_tcp_connection (uint16 port, Cancellable? cancellable) throws Error, IOError {
var netstack = tunnel_connection.tunnel_netstack;
var endpoint = (InetSocketAddress) Object.new (typeof (InetSocketAddress),
address: tunnel_connection.remote_address,
port: port,
scope_id: netstack.scope_id
);
return yield netstack.open_tcp_connection (endpoint, cancellable);
}
}
public interface FruitFinder : Object {
public static FruitFinder make_default () {
#if LINUX && !ANDROID
@@ -1350,53 +1577,37 @@ namespace Frida.Fruity {
}
}
internal const string PAIRING_REGTYPE = "_remoted._tcp";
internal const string PAIRING_DOMAIN = "local.";
public interface PairingBrowser : Object {
public const string SERVICE_NAME = "_remotepairing._tcp.local";
public signal void service_discovered (PairingServiceDetails service);
public static PairingBrowser make_default () {
#if LINUX && !ANDROID
#if WINDOWS
return new WindowsPairingBrowser ();
#elif LINUX && !ANDROID
return new LinuxPairingBrowser ();
#else
return new NullPairingBrowser ();
#endif
}
public signal void services_discovered (PairingServiceDetails[] services);
public abstract async void start (Cancellable? cancellable) throws IOError;
public abstract async void stop (Cancellable? cancellable) throws IOError;
}
public class NullPairingBrowser : Object, PairingBrowser {
public async void start (Cancellable? cancellable) throws IOError {
}
public async void stop (Cancellable? cancellable) throws IOError {
}
}
public interface PairingServiceDetails : Object {
public abstract string name {
get;
}
public abstract uint interface_index {
get;
}
public abstract string interface_name {
get;
}
public abstract async Gee.List<PairingServiceHost> resolve (Cancellable? cancellable = null) throws Error, IOError;
}
public interface PairingServiceHost : Object {
public abstract string name {
get;
}
public abstract uint16 port {
get;
}
public abstract Gee.List<string> txt_record {
get;
}
public abstract async Gee.List<InetSocketAddress> resolve (Cancellable? cancellable = null) throws Error, IOError;
public class PairingServiceDetails {
public string identifier;
public Bytes auth_tag;
public InetSocketAddress endpoint;
public InetSocketAddress interface_address;
}
}
+174
View File
@@ -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,
}
}
+16 -3
View File
@@ -504,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 ();
@@ -878,9 +886,9 @@ namespace Frida.Fruity {
construct;
}
public weak DTXTransport transport {
public weak DTXTransport? transport {
get;
construct;
set;
}
public State state {
@@ -903,7 +911,7 @@ namespace Frida.Fruity {
}
public override void dispose () {
transport.remove_channel (this);
close ();
base.dispose ();
}
@@ -915,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)
+40 -24
View File
@@ -71,41 +71,55 @@ namespace Frida.Fruity {
}
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
unowned LibUSB.Device raw_device = device.raw_device;
unowned LibUSB.DeviceHandle handle = device.handle;
LibUSB.ConfigDescriptor config;
Usb.check (device.raw_device.get_active_config_descriptor (out config),
"Failed to get active USB config descriptor");
var dev_desc = LibUSB.DeviceDescriptor (raw_device);
int current_config;
Usb.check (handle.get_configuration (out current_config), "Failed to get current configuration");
int desired_config = -1;
bool found_cdc_header = false;
bool found_data_interface = false;
uint8 mac_address_index = 0;
foreach (var iface in config.@interface) {
foreach (var setting in iface.altsetting) {
if (setting.bInterfaceClass == LibUSB.ClassCode.COMM &&
setting.bInterfaceSubClass == UsbCommSubclass.NCM) {
try {
parse_cdc_header (setting.extra, out 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;
for (uint8 config_value = dev_desc.bNumConfigurations; config_value != 0; config_value--) {
LibUSB.ConfigDescriptor config;
Usb.check (raw_device.get_config_descriptor_by_value (config_value, out config),
"Failed to get config descriptor");
data_iface = setting.bInterfaceNumber;
data_altsetting = setting.bAlternateSetting;
foreach (var iface in config.@interface) {
foreach (var setting in iface.altsetting) {
if (setting.bInterfaceClass == LibUSB.ClassCode.COMM &&
setting.bInterfaceSubClass == UsbCommSubclass.NCM) {
try {
parse_cdc_header (setting.extra, out 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;
foreach (var ep in setting.endpoint) {
if ((ep.bEndpointAddress & LibUSB.EndpointDirection.MASK) == LibUSB.EndpointDirection.IN)
rx_address = ep.bEndpointAddress;
else
tx_address = ep.bEndpointAddress;
data_iface = setting.bInterfaceNumber;
data_altsetting = setting.bAlternateSetting;
foreach (var ep in setting.endpoint) {
if ((ep.bEndpointAddress & LibUSB.EndpointDirection.MASK) == LibUSB.EndpointDirection.IN)
rx_address = ep.bEndpointAddress;
else
tx_address = ep.bEndpointAddress;
}
}
}
}
if (found_cdc_header || found_data_interface) {
desired_config = config_value;
break;
}
}
if (!found_cdc_header || !found_data_interface)
throw_user_error ("No USB CDC-NCM interface found");
@@ -122,6 +136,8 @@ namespace Frida.Fruity {
}
handle.detach_kernel_driver (data_iface);
if (current_config != desired_config)
Usb.check (handle.set_configuration (desired_config), "Failed to set configuration");
try {
Usb.check (handle.claim_interface (data_iface), "Failed to claim USB interface");
} catch (Error e) {
+11 -52
View File
@@ -154,8 +154,6 @@ namespace Frida.Fruity {
private MainContext main_context;
private DataOutputStream? pcap;
private enum State {
STARTED,
STOPPED
@@ -249,13 +247,11 @@ namespace Frida.Fruity {
public void handle_incoming_datagram (Bytes datagram) throws Error {
check_started ();
log_datagram (datagram);
check (perform_on_lwip_thread (() => {
var pbuf = LWIP.PacketBuffer.alloc (RAW, (uint16) datagram.get_size (), POOL);
pbuf.take (datagram.get_data ());
var err = handle.input (pbuf, handle);
var err = handle.input (pbuf, ref handle);
if (err == OK)
*((void **) &pbuf) = null;
@@ -282,10 +278,8 @@ namespace Frida.Fruity {
var datagram = new Bytes (packet[:pbuf.tot_len]);
schedule_on_frida_thread (() => {
if (state == STARTED) {
log_datagram (datagram);
if (state == STARTED)
outgoing_datagram (datagram);
}
return Source.REMOVE;
});
}
@@ -352,45 +346,6 @@ namespace Frida.Fruity {
source.attach (main_context);
}
private void log_datagram (Bytes datagram) {
lock (pcap) {
try {
if (pcap == null) {
char buf[40];
unowned string addr = raw_ipv6_address.to_string (buf);
var f = File.new_build_filename (Environment.get_user_special_dir (DESKTOP),
"vns-%s.pcap".printf (addr.replace (":", "")));
try {
f.delete ();
} catch (GLib.Error e) {
}
pcap = new DataOutputStream (f.create (REPLACE_DESTINATION));
pcap.set_byte_order (HOST_ENDIAN);
pcap.put_uint32 (0xa1b2c3d4U);
pcap.put_uint16 (2);
pcap.put_uint16 (4);
pcap.put_uint32 (0);
pcap.put_uint32 (0);
pcap.put_uint32 (16384);
pcap.put_uint32 ((ethernet_address != null) ? 1 : 229); // Ethernet or IPv6
pcap.flush ();
}
int64 timestamp = get_real_time ();
pcap.put_uint32 ((uint32) (timestamp / 1000000));
pcap.put_uint32 ((uint32) (timestamp % 1000000));
pcap.put_uint32 (datagram.length);
pcap.put_uint32 (datagram.length);
size_t written;
pcap.write_all (datagram.get_data (), out written);
pcap.flush ();
} catch (GLib.Error e) {
printerr ("%s\n", e.message);
assert_not_reached ();
}
}
}
private class TcpConnection : IOStream, AsyncInitable {
public VirtualNetworkStack netstack {
get;
@@ -516,7 +471,7 @@ namespace Frida.Fruity {
self->on_error (err);
});
pcb.nagle_disable ();
pcb.bind_netif (netstack.handle);
pcb.bind_netif (&netstack.handle);
var err = pcb.connect (ip6_address_from_inet_socket_address (address), address.get_port (), (user_data, pcb, err) => {
TcpConnection * self = user_data;
@@ -544,6 +499,11 @@ namespace Frida.Fruity {
});
}
private void detach_from_pcb () {
pcb.set_user_data (null);
pcb = null;
}
private void on_connect () {
lock (state)
tx_space_available = pcb.query_available_send_buffer_space ();
@@ -561,7 +521,7 @@ namespace Frida.Fruity {
private void on_recv (owned LWIP.PacketBuffer? pbuf, LWIP.ErrorCode err) {
if (pbuf == null) {
pcb = null;
detach_from_pcb ();
schedule_on_frida_thread (() => {
_state = CLOSED;
update_events ();
@@ -584,7 +544,7 @@ namespace Frida.Fruity {
}
private void on_error (LWIP.ErrorCode err) {
pcb = null;
detach_from_pcb ();
schedule_on_frida_thread (() => {
_state = CLOSED;
update_events ();
@@ -957,7 +917,6 @@ namespace Frida.Fruity {
}
}
//private VirtualNetworkStack? _netstack;
private unowned LWIP.UdpPcb? pcb;
private IOCondition events = OUT;
private Gee.Queue<Packet> rx_queue = new Gee.ArrayQueue<Packet> ();
@@ -972,7 +931,7 @@ namespace Frida.Fruity {
netstack.perform_on_lwip_thread (() => {
pcb = LWIP.UdpPcb.make (V6);
pcb.set_recv_callback (on_recv);
pcb.bind_netif (netstack.handle);
pcb.bind_netif (&netstack.handle);
return OK;
});
}
+245
View File
@@ -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 ();
}
}
}
+191 -217
View File
@@ -99,6 +99,8 @@ namespace Frida.Fruity {
}
public class PairingService : Object, AsyncInitable {
public const string DNS_SD_NAME = "_remotepairing._tcp.local";
public PairingTransport transport {
get;
construct;
@@ -119,7 +121,6 @@ namespace Frida.Fruity {
private uint64 next_control_sequence_number = 0;
private uint64 next_encrypted_sequence_number = 0;
private File config_file;
private string host_identifier;
private Key pair_record_key;
private ChaCha20Poly1305? client_cipher;
@@ -146,29 +147,15 @@ namespace Frida.Fruity {
transport.close.connect (on_close);
transport.message.connect (on_message);
#if DARWIN
config_file = File.new_for_path ("/var/db/lockdown/RemotePairing/user_%u/selfIdentity.plist".printf (
(uint) Posix.getuid ()));
#else
config_file = File.new_build_filename (Environment.get_user_config_dir (), "frida", "remote-xpc.plist");
#endif
Bytes? key = null;
try {
uint8[] raw_identity;
FileUtils.get_data (config_file.get_path (), out raw_identity);
Plist identity = new Plist.from_data (raw_identity);
unowned string identifier = identity.get_string ("identifier");
key = identity.get_bytes ("privateKey");
host_identifier = identifier;
} catch (GLib.Error e) {
host_identifier = make_host_identifier ();
}
if (key == null) {
Bytes? key;
var identity = PairingIdentity.try_load ();
if (identity != null) {
key = identity.private_key;
host_identifier = identity.identifier;
} else {
uint8 dummy_key[32] = { 0, };
key = new Bytes (dummy_key);
host_identifier = make_host_identifier ();
}
pair_record_key = new Key.from_raw_private_key (ED25519, null, key.get_data ());
@@ -389,7 +376,7 @@ namespace Frida.Fruity {
// TODO: Verify signature using peer's public key.
/* var start_inner_response = */ new VariantReader (PairingParamsParser.parse (cipher.decrypt (
new Bytes.static ("\x00\x00\x00\x00PV-Msg02".data[:12]),
start_response.read_member ("encrypted-data").get_data_value ()).get_data ()));
start_response.read_member ("encrypted-data").get_data_value ())));
var message = new ByteArray.sized (100);
message.append (raw_host_pubkey);
@@ -524,7 +511,7 @@ namespace Frida.Fruity {
message.append (new_pair_record_pubkey.get_data ());
Bytes signature = compute_message_signature (ByteArray.free_to_bytes ((owned) message), new_pair_record_key);
Bytes info = new OpackBuilder ()
Bytes self_info = new OpackBuilder ()
.begin_dictionary ()
.set_member_name ("name")
.add_string_value (Environment.get_host_name ())
@@ -547,7 +534,7 @@ namespace Frida.Fruity {
.add_identifier (host_identifier)
.add_raw_public_key (new_pair_record_pubkey)
.add_signature (signature)
.add_info (info)
.add_info (self_info)
.build ();
Bytes outer_params = new PairingParamsBuilder ()
@@ -571,27 +558,26 @@ namespace Frida.Fruity {
.end_dictionary ()
.build ();
var finish_response = yield request_pairing_data (finish_payload, cancellable);
var outer_finish_response = yield request_pairing_data (finish_payload, cancellable);
var inner_finish_response = new VariantReader (PairingParamsParser.parse (
cipher.decrypt (new Bytes.static ("\x00\x00\x00\x00PS-Msg06".data[:12]),
outer_finish_response.read_member ("encrypted-data").get_data_value ())));
Bytes encrypted_response = finish_response.read_member ("encrypted-data").get_data_value ();
Bytes raw_response = cipher.decrypt (new Bytes.static ("\x00\x00\x00\x00PS-Msg06".data[:12]), encrypted_response);
// TODO: Wire up if/when needed.
/* Variant response = */ PairingParamsParser.parse (raw_response.get_data ());
string peer_identifier = inner_finish_response.read_member ("identifier").get_string_value ();
inner_finish_response.end_member ();
Bytes peer_pubkey = inner_finish_response.read_member ("public-key").get_data_value ();
inner_finish_response.end_member ();
Bytes peer_info = inner_finish_response.read_member ("info").get_data_value ();
inner_finish_response.end_member ();
PairingIdentity.save_peer (peer_identifier, peer_pubkey, peer_info);
var config = new Plist ();
config.set_string ("identifier", host_identifier);
config.set_bytes ("publicKey", new_pair_record_pubkey);
config.set_bytes ("privateKey", get_raw_private_key (new_pair_record_key));
config.set_bytes ("irk", irk);
try {
config_file.get_parent ().make_directory_with_parents (cancellable);
} catch (GLib.Error e) {
}
try {
FileUtils.set_contents (config_file.get_path (), config.to_xml ());
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
var identity = new PairingIdentity () {
identifier = host_identifier,
public_key = new_pair_record_pubkey,
private_key = get_raw_private_key (new_pair_record_key),
irk = irk
};
identity.save ();
pair_record_key = (owned) new_pair_record_key;
@@ -636,7 +622,7 @@ namespace Frida.Fruity {
.read_member ("_0")
.read_member ("data")
.get_data_value ();
Variant data = PairingParamsParser.parse (raw_data.get_data ());
Variant data = PairingParamsParser.parse (raw_data);
return new VariantReader (data);
}
@@ -1345,6 +1331,148 @@ namespace Frida.Fruity {
}
}
public class PairingIdentity {
public string identifier;
public Bytes public_key;
public Bytes private_key;
public Bytes irk;
public static PairingIdentity? try_load () {
try {
Bytes raw_identity = query_self_identity_location ().load_bytes ();
Plist identity = new Plist.from_data (raw_identity.get_data ());
return new PairingIdentity () {
identifier = identity.get_string ("identifier"),
public_key = identity.get_bytes ("publicKey"),
private_key = identity.get_bytes ("privateKey"),
irk = identity.get_bytes ("irk")
};
} catch (GLib.Error e) {
return null;
}
}
public void save () throws Error {
var plist = new Plist ();
plist.set_string ("identifier", identifier);
plist.set_bytes ("publicKey", public_key);
plist.set_bytes ("privateKey", private_key);
plist.set_bytes ("irk", irk);
save_plist (plist, query_self_identity_location ());
}
public static Gee.List<Peer> load_peers () {
var peers = new Gee.ArrayList<Peer> ();
try {
var enumerator = query_peers_location ().enumerate_children (FileAttribute.STANDARD_NAME, FileQueryInfoFlags.NONE);
File? child;
while (enumerator.iterate (null, out child) && child != null) {
Plist plist = new Plist.from_data (child.load_bytes ().get_data ());
var reader = new VariantReader (OpackParser.parse (plist.get_bytes ("info")));
unowned string udid = reader.read_member ("remotepairing_udid").get_string_value ();
peers.add (new Peer () {
identifier = plist.get_string ("identifier"),
public_key = plist.get_bytes ("publicKey"),
irk = plist.get_bytes ("irk"),
name = plist.get_string ("name"),
model = plist.get_string ("model"),
udid = udid,
});
}
} catch (GLib.Error e) {
}
return peers;
}
public static void save_peer (string identifier, Bytes public_key, Bytes info) throws Error {
var r = new VariantReader (OpackParser.parse (info));
Bytes irk = r.read_member ("altIRK").get_data_value ();
r.end_member ();
unowned string name = r.read_member ("name").get_string_value ();
r.end_member ();
unowned string model = r.read_member ("model").get_string_value ();
r.end_member ();
var plist = new Plist ();
plist.set_string ("identifier", identifier);
plist.set_bytes ("publicKey", public_key);
plist.set_bytes ("irk", irk);
plist.set_string ("name", name);
plist.set_string ("model", model);
plist.set_bytes ("info", info);
save_plist (plist, query_peers_location ().get_child (identifier + ".plist"));
}
public static File query_self_identity_location () {
return query_base_location ().get_child ("self-identity.plist");
}
public static File query_peers_location () {
return query_base_location ().get_child ("peers");
}
private static File query_base_location () {
return File.new_build_filename (Environment.get_user_config_dir (), "frida");
}
private static void save_plist (Plist plist, File location) throws Error {
try {
location.get_parent ().make_directory_with_parents ();
} catch (GLib.Error e) {
}
try {
location.replace_contents (plist.to_xml ().data, null, false, PRIVATE | REPLACE_DESTINATION, null);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
}
public class Peer {
public string identifier;
public Bytes public_key;
public Bytes irk;
public string name;
public string model;
public string udid;
}
}
public class PairingServiceMetadata {
public string identifier;
public Bytes auth_tag;
public static PairingServiceMetadata from_txt_record (Gee.Iterable<string> record) throws Error {
string? identifier = null;
Bytes? auth_tag = null;
foreach (string item in record) {
string[] tokens = item.split ("=", 2);
if (tokens.length != 2)
continue;
unowned string key = tokens[0];
unowned string val = tokens[1];
if (key == "identifier")
identifier = val;
else if (key == "authTag")
auth_tag = new Bytes (Base64.decode (val));
}
if (identifier == null || auth_tag == null)
throw new Error.PROTOCOL ("Missing TXT metadata");
return new PairingServiceMetadata () {
identifier = identifier,
auth_tag = auth_tag,
};
}
}
public class DeviceOptions {
public bool allows_pair_setup;
public bool allows_pinless_pairing;
@@ -1435,54 +1563,51 @@ namespace Frida.Fruity {
}
private class PairingParamsParser {
private Buffer buf;
private size_t cursor = 0;
private BufferReader reader;
private EnumClass param_type_class;
public static Variant parse (uint8[] data) throws Error {
var parser = new PairingParamsParser (new Bytes.static (data));
public static Variant parse (Bytes pairing_params) throws Error {
var parser = new PairingParamsParser (pairing_params);
return parser.read_params ();
}
private PairingParamsParser (Bytes bytes) {
this.buf = new Buffer (bytes, LITTLE_ENDIAN);
this.param_type_class = (EnumClass) typeof (PairingParamType).class_ref ();
reader = new BufferReader (new Buffer (bytes, LITTLE_ENDIAN));
param_type_class = (EnumClass) typeof (PairingParamType).class_ref ();
}
private Variant read_params () throws Error {
var byte_array = new VariantType.array (VariantType.BYTE);
var parameters = new Gee.HashMap<string, Variant> ();
size_t size = buf.bytes.get_size ();
while (cursor != size) {
var raw_type = read_raw_uint8 ();
while (reader.available != 0) {
var raw_type = reader.read_uint8 ();
unowned EnumValue? type_enum_val = param_type_class.get_value (raw_type);
if (type_enum_val == null)
throw new Error.INVALID_ARGUMENT ("Unsupported pairing parameter type (0x%x)", raw_type);
var type = (PairingParamType) raw_type;
unowned string key = type_enum_val.value_nick;
var val_size = read_raw_uint8 ();
Bytes val_bytes = read_raw_bytes (val_size);
var val_size = reader.read_uint8 ();
Variant val;
switch (type) {
case IDENTIFIER:
val = new Variant.string (reader.read_fixed_string (val_size));
break;
case STATE:
case ERROR:
if (val_bytes.length != 1) {
throw new Error.INVALID_ARGUMENT ("Invalid value for '%s': length=%d",
key, val_bytes.length);
}
val = new Variant.byte (val_bytes[0]);
if (val_size != 1)
throw new Error.INVALID_ARGUMENT ("Invalid value for '%s': size=%u", key, val_size);
val = new Variant.byte (reader.read_uint8 ());
break;
case RETRY_DELAY: {
uint16 delay;
switch (val_bytes.length) {
switch (val_size) {
case 1:
delay = val_bytes[0];
delay = reader.read_uint8 ();
break;
case 2:
delay = new Buffer (val_bytes, LITTLE_ENDIAN).read_uint16 (0);
delay = reader.read_uint16 ();
break;
default:
throw new Error.INVALID_ARGUMENT ("Invalid value for 'retry-delay'");
@@ -1491,6 +1616,7 @@ namespace Frida.Fruity {
break;
}
default: {
Bytes val_bytes = reader.read_bytes (val_size);
var val_bytes_copy = new Bytes (val_bytes.get_data ());
val = Variant.new_from_data (byte_array, val_bytes_copy.get_data (), true, val_bytes_copy);
break;
@@ -1517,26 +1643,6 @@ namespace Frida.Fruity {
builder.add ("{sv}", e.key, e.value);
return builder.end ();
}
private uint8 read_raw_uint8 () throws Error {
check_available (sizeof (uint8));
var result = buf.read_uint8 (cursor);
cursor += sizeof (uint8);
return result;
}
private Bytes read_raw_bytes (size_t n) throws Error {
check_available (n);
Bytes result = buf.bytes[cursor:cursor + n];
cursor += n;
return result;
}
private void check_available (size_t required) throws Error {
size_t available = buf.bytes.get_size () - cursor;
if (available < required)
throw new Error.INVALID_ARGUMENT ("Invalid pairing parameters: truncated");
}
}
private enum PairingParamType {
@@ -1553,138 +1659,6 @@ namespace Frida.Fruity {
INFO = 17,
}
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 sealed class TunnelConnection : Object, AsyncInitable {
public InetSocketAddress address {
get;
+8 -2
View File
@@ -490,15 +490,19 @@ if have_fruity_backend
'fruity' / 'lldb.vala',
'fruity' / 'injector.vala',
'fruity' / 'usbmux.vala',
'fruity' / 'dns.vala',
'fruity' / 'keyed-archive.vala',
'fruity' / 'plist.vala',
'fruity' / 'plist-service.vala',
'fruity' / 'opack.vala',
'fruity' / 'network-stack.vala',
'fruity' / 'usb.vala',
'fruity' / 'ncm.vala',
]
if host_os == 'macos'
if host_os_family == 'windows'
backend_sources += 'fruity' / 'device-monitor-windows.vala'
elif host_os == 'macos'
backend_sources += 'fruity' / 'device-monitor-macos.vala'
elif host_os == 'linux'
backend_sources += 'fruity' / 'device-monitor-linux.vala'
@@ -521,7 +525,9 @@ if have_fruity_backend
'--pkg=lwip',
'--pkg=openssl',
]
if host_os_family == 'darwin'
if host_os_family == 'windows'
backend_vala_args += '--pkg=windows-windns'
elif host_os_family == 'darwin'
backend_vala_args += [
'--pkg=darwin-xnu',
'--pkg=darwin-xpc',
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 16.4.2
revision = 16.4.3
depth = 1
[provide]
+8 -8
View File
@@ -33,7 +33,7 @@ namespace LWIP {
public void ip6_addr_set_state (int8 addr_index, IP6AddressState state);
[CCode (cname = "netif_input")]
public static ErrorCode default_input_handler (PacketBuffer pbuf, NetworkInterface netif);
public static ErrorCode default_input_handler (PacketBuffer pbuf, ref NetworkInterface netif);
public IP6Address ip6_addr[IPV6_NUM_ADDRESSES];
@@ -56,16 +56,16 @@ namespace LWIP {
}
[CCode (cname = "netif_init_fn", has_target = false)]
public delegate ErrorCode NetworkInterfaceInitFunc (NetworkInterface netif);
public delegate ErrorCode NetworkInterfaceInitFunc (ref NetworkInterface netif);
[CCode (cname = "netif_input_fn", has_target = false)]
public delegate ErrorCode NetworkInterfaceInputFunc (PacketBuffer pbuf, NetworkInterface netif);
public delegate ErrorCode NetworkInterfaceInputFunc (PacketBuffer pbuf, ref NetworkInterface netif);
[CCode (cname = "netif_linkoutput_fn", has_target = false)]
public delegate ErrorCode NetworkInterfaceLinkOutputFunc (NetworkInterface netif, PacketBuffer pbuf);
public delegate ErrorCode NetworkInterfaceLinkOutputFunc (ref NetworkInterface netif, PacketBuffer pbuf);
[CCode (cname = "netif_output_ip6_fn", has_target = false)]
public delegate ErrorCode NetworkInterfaceOutputIP6Func (NetworkInterface netif, PacketBuffer pbuf, IP6Address address);
public delegate ErrorCode NetworkInterfaceOutputIP6Func (ref NetworkInterface netif, PacketBuffer pbuf, IP6Address address);
[Flags]
[CCode (cheader_filename = "lwip/netif.h", cprefix = "NETIF_FLAG_")]
@@ -85,7 +85,7 @@ namespace LWIP {
namespace IPv6 {
[CCode (cheader_filename = "lwip/ethip6.h", cname = "ethip6_output")]
public ErrorCode output (NetworkInterface netif, PacketBuffer pbuf, IP6Address address);
public ErrorCode output (ref NetworkInterface netif, PacketBuffer pbuf, IP6Address address);
}
}
@@ -196,7 +196,7 @@ namespace LWIP {
public ErrorCode close ();
public ErrorCode shutdown (bool shut_rx, bool shut_tx);
public void bind_netif (NetworkInterface? netif);
public void bind_netif (NetworkInterface * netif);
public ErrorCode connect (IP6Address address, uint16 port, ConnectedFunc connected);
@@ -241,7 +241,7 @@ namespace LWIP {
public void set_recv_callback (RecvFunc f);
public ErrorCode bind (IP6Address? ipaddr = null, uint16 port = 0);
public void bind_netif (NetworkInterface? netif);
public void bind_netif (NetworkInterface * netif);
public ErrorCode connect (IP6Address ipaddr, uint16 port);
+39
View File
@@ -384,6 +384,44 @@ namespace OpenSSL {
public class MessageDigest {
public static MessageDigest fetch (LibraryContext? ctx, string algorithm, string? properties = null);
}
[Compact]
[CCode (cname = "EVP_MAC_CTX", cprefix = "EVP_MAC_CTX_")]
public class MessageAuthCodeContext {
public MessageAuthCodeContext (MessageAuthCode mac);
[CCode (cname = "EVP_MAC_init")]
public int init (uint8[] key, [CCode (array_length = false)] Param[]? params = null);
[CCode (cname = "EVP_MAC_update")]
public int update (uint8[] data);
[CCode (cname = "EVP_MAC_final")]
public int final ([CCode (array_length_pos = 2)] uint8[]? outdata, out size_t outlen);
}
[Compact]
[CCode (cname = "EVP_MAC", cprefix = "EVP_MAC_")]
public class MessageAuthCode {
public static MessageAuthCode fetch (LibraryContext? ctx, string algorithm, string? properties = null);
}
[CCode (cheader_filename = "openssl/core_names.h", lower_case_cprefix = "OSSL_MAC_PARAM_")]
namespace MessageAuthParameter {
public const string KEY;
public const string IV;
public const string CUSTOM;
public const string SALT;
public const string XOF;
public const string DIGEST_NOINIT;
public const string DIGEST_ONESHOT;
public const string C_ROUNDS;
public const string D_ROUNDS;
public const string CIPHER;
public const string DIGEST;
public const string PROPERTIES;
public const string SIZE;
public const string BLOCK_SIZE;
public const string TLS_DATA_SIZE;
}
}
[Compact]
@@ -426,6 +464,7 @@ namespace OpenSSL {
public const string sha256;
public const string sha384;
public const string sha512;
public const string siphash;
public const string chacha20_poly1305;
}
+96
View File
@@ -0,0 +1,96 @@
[CCode (cheader_filename = "windns.h", gir_namespace = "Windows", gir_version = "1.0")]
namespace WinDns {
[CCode (cname = "DNS_QUERY_RESULT")]
public struct QueryResult {
[CCode (cname = "Version")]
public ulong version;
[CCode (cname = "QueryStatus")]
public long query_status;
[CCode (cname = "QueryOptions")]
public uint64 query_options;
[CCode (cname = "pQueryRecords")]
public Record * query_records;
[CCode (cname = "Reserved")]
public void * reserved;
}
[CCode (cname = "DNS_RECORDW")]
public struct Record {
[CCode (cname = "pNext")]
public Record * next;
[CCode (cname = "pName")]
public unowned string16 name;
[CCode (cname = "wType")]
public RecordType type;
[CCode (cname = "wDataLength")]
public uint16 data_length;
[CCode (cname = "Flags.DW")]
public uint32 flags;
[CCode (cname = "dwTtl")]
public uint32 ttl;
[CCode (cname = "dwReserved")]
public uint32 reserved;
[CCode (cname = "Data.A")]
public AData a;
[CCode (cname = "Data.PTR")]
public PtrData ptr;
[CCode (cname = "Data.TXT")]
public TxtData txt;
[CCode (cname = "Data.AAAA")]
public AaaaData aaaa;
[CCode (cname = "Data.SRV")]
public SrvData srv;
}
[CCode (cname = "WORD", cprefix = "DNS_TYPE_", has_type_id = false)]
public enum RecordType {
A,
PTR,
TEXT,
AAAA,
SRV,
}
[CCode (cname = "DNS_A_DATA")]
public struct AData {
[CCode (cname = "IpAddress")]
public uint32 ip;
}
[CCode (cname = "DNS_PTR_DATAW")]
public struct PtrData {
[CCode (cname = "pNameHost")]
public unowned string16 name;
}
[CCode (cname = "DNS_TXT_DATAW")]
public struct TxtData {
[CCode (cname = "pStringArray", array_length_cname = "dwStringCount")]
public unowned string16[] strings;
}
[CCode (cname = "DNS_AAAA_DATA")]
public struct AaaaData {
[CCode (cname = "Ip6Address")]
public IP6Address ip;
}
[CCode (cname = "DNS_SRV_DATAW")]
public struct SrvData {
[CCode (cname = "pNameTarget")]
public unowned string16 name_target;
[CCode (cname = "wPriority")]
public uint16 priority;
[CCode (cname = "wWeight")]
public uint16 weight;
[CCode (cname = "wPort")]
public uint16 port;
}
[CCode (cname = "IP6_ADDRESS")]
public struct IP6Address {
[CCode (cname = "IP6Byte")]
public uint8 data[16];
}
}