gxpc icon indicating copy to clipboard operation
gxpc copied to clipboard

Coresymbolication Sandbox / Jetsam Crash

Open jiska2342 opened this issue 11 months ago • 17 comments

Some of the symbol lookups are using the DebugSymbol Frida API. In most cases, this works well :)

From script.js:

const __CFBinaryPlistCreate15 = DebugSymbol.fromName('__CFBinaryPlistCreate15').address;
const _xpc_connection_call_event_handler = DebugSymbol.fromName("_xpc_connection_call_event_handler").address;

This is necessary since the two non-exported symbols cannot be looked up using Module.getBaseAddress(). However, some hardened daemons like imagent don't allow using the DebugSymbol API. This in turn crashes the daemon like this:

default	22:56:23.233756+0100	imagent	[0xc68bb1930] activating connection: mach=true listener=false peer=false name=com.apple.coresymbolicationd
error	22:56:23.234062+0100	kernel	Sandbox: imagent(441) deny(1) mach-lookup com.apple.coresymbolicationd
default	22:56:23.234492+0100	imagent	[0xc68bb1930] failed to do a bootstrap look-up: xpc_error=[159: Unknown error: 159]
default	22:56:23.234539+0100	imagent	[0xc68bb1930] invalidated after a failed init
error	22:56:23.234616+0100	imagent	Invalid connection: com.apple.coresymbolicationd
default	22:56:30.745460+0100	kernel	EXC_RESOURCE -> imagent[441] exceeded mem limit: InactiveHard 500 MB (fatal)
default	22:56:30.745614+0100	kernel	memorystatus: killing process 441 [imagent] in high band ? (140) - memorystatus_available_pages: 78501
default	22:56:30.745631+0100	kernel	imagent[441] Corpse allowed 1 of 5

Increasing the process memory limit does not help, this is just a follow-up error by Frida.

I haven't found an elegant workaround yet :( What I did to get gxpc working again was to first attach to a process that allows lookups (like bluetoothd), use Frida for the lookup and then add this back to the script like so:

frida -U bluetoothd

[iPad::bluetoothd ]-> ptr(DebugSymbol.fromName("_xpc_connection_call_event_handler").address - Module.getBaseAddress('libxpc.dylib'))
"0xf98c"
[iPad::bluetoothd ]-> ptr(DebugSymbol.fromName("__CFBinaryPlistCreate15").address - Module.getBaseAddress('CoreFoundation'))
"0x7dbf4"

And then replace it for that particular iOS/iPadOS version within script.js:

const __CFBinaryPlistCreate15 = Module.getBaseAddress('CoreFoundation').add(0x7dbf4);
const _xpc_connection_call_event_handler = Module.getBaseAddress('libxpc.dylib').add(0xf98c);

Maybe this is useful for anyone who gets similar crashes :) And maybe there's a more elegant workaround?

jiska2342 avatar Feb 11 '25 22:02 jiska2342

Great explanation and workaround, maybe we could add a command argument to pass the offsets.

NSEcho avatar Feb 11 '25 22:02 NSEcho

Either that or maybe a configuration file. I could imagine two possibilities here:

  • Configuration (file?) within gxpc, which has a list of known iOS versions and their offsets.
  • Configuration file within the user's home directory to specify offsets for the iOS versions they use.

I think that most people don't use that many different jailbroken iOS versions, especially since downgrading is an issue.

Here is an example that I'm using now for some of my devices. Note that attaching to a daemon is now much faster, as most of the time of attaching to a process was the symbol lookup 🚀

AFAIK the DYLD shared cache has similar offsets for arm64 devices, and then another set of offsets for arm64e devices. However, I haven't verified that, so maybe the safest option is to just provide one offset per device and build version.

// When we know the offset of these functions, lookup is much faster and there's no
// coresymbolication sandbox issue.
const sysctlbyname_addr = Module.getExportByName(null, 'sysctlbyname');
const sysctlbyname = new NativeFunction(sysctlbyname_addr, 'int', ['pointer', 'pointer', 'pointer', 'pointer', 'int']);

const machine = sysctl("hw.machine");
const osversion = sysctl("kern.osversion");
console.log(`Running on an ${machine}, system version ${osversion}`);

var __CFBinaryPlistCreate15;
var _xpc_connection_call_event_handler;

// We cannot check for only osversion, as there's differences between arm64/arm64e
if (machine == 'iPhone14,7' && osversion == '20B110') {
    __CFBinaryPlistCreate15 = Module.getBaseAddress('CoreFoundation').add(0xb1c00);
    _xpc_connection_call_event_handler = Module.getBaseAddress('libxpc.dylib').add(0x11c00);
} else if (machine == 'iPad7,11' && osversion == '22B83') {
    __CFBinaryPlistCreate15 = Module.getBaseAddress('CoreFoundation').add(0x7dbf4);
    _xpc_connection_call_event_handler = Module.getBaseAddress('libxpc.dylib').add(0xf98c);
} else {

    __CFBinaryPlistCreate15 = DebugSymbol.fromName('__CFBinaryPlistCreate15').address;
    _xpc_connection_call_event_handler = DebugSymbol.fromName("_xpc_connection_call_event_handler").address;
}

const CFBinaryPlistCreate15 = new NativeFunction(__CFBinaryPlistCreate15, "pointer", ["pointer", "int", "pointer"]);
const xpc_connection_call_event_handler = new NativeFunction(_xpc_connection_call_event_handler, "void", ["pointer", "pointer"]);


function sysctl(name) {
    const size = Memory.alloc(0x4);
    sysctlbyname(Memory.allocUtf8String(name), ptr(0), size, ptr(0), 0);
    const value = Memory.alloc(size.readU32());
    sysctlbyname(Memory.allocUtf8String(name), value, size, ptr(0), 0);
    return value.readCString();
}

jiska2342 avatar Feb 17 '25 23:02 jiska2342

Seems like the safest option would be go with your second option, to have a configuration file inside of user's home directory as containing offsets inside of gxpc would require users to create a PR with their own offsets.

I will start working on it today, this will probably be a .json file that would like something like this:

[
  {
    "iPhone14,7": [
      {
        "20B110": {
          "PlistCreate": "0xb1c00",
          "callHandler": "0x11c00"
        }
      },
      {
        "OtherBuildVersion": {
          "PlistCreate": "0xb1c00",
          "callHandler": "0x11c00"
        }
      }
    ]
  },
  {
    "iPad7,11": [
      {
        "22B83": {
          "PlistCreate": "0x7dbf4",
          "callHandler": "0xf98c"
        }
      }
    ]
  }
]

NSEcho avatar Feb 18 '25 09:02 NSEcho

Awesome, looks good to me 🥳

jiska2342 avatar Feb 18 '25 17:02 jiska2342

I belive I have implemented it, let me know if everything is working okay.

The first step is to generate a config file calling gxpc --init that will create gxpc.conf file inside of user's home directory, optionally you can pass --config or -c flag to pass custom location.

$ nsecho | ~/t/gxpc ⦿ ./gxpc --init
⚡  Created new config at /Users/.../gxpc.conf
nsecho | ~/t/gxpc ⦿ cat ~/gxpc.conf
{
      "offsets": [
          {
              "os": "iPhone14,7",
              "builds": [
                  {
                      "20B110": {
                          "PlistCreate": "0xb1c00",
                          "CallHandler": "0x11c00"
                      }
                  }
              ]
          },
          {
              "os": "iPad7,11",
              "builds": [
                  {
                      "22B83": {
                          "PlistCreate": "0x7dbf4",
                          "CallHandler": "0xf98c"
                      }
                  }
              ]
          }
      ]
  }

Then simply obtain offset values using the method you provided in the first place by attaching to non-hardened service:

frida -U bluetoothd

[iPad::bluetoothd ]-> ptr(DebugSymbol.fromName("_xpc_connection_call_event_handler").address - Module.getBaseAddress('libxpc.dylib'))
"0xf98c"
[iPad::bluetoothd ]-> ptr(DebugSymbol.fromName("__CFBinaryPlistCreate15").address - Module.getBaseAddress('CoreFoundation'))
"0x7dbf4"

And populate the config file accordingly. You don't need to worry about config file anymore because it will be read each time gxpc is run and it will check whether there are offsets defined for current os and machine.

NSEcho avatar Feb 18 '25 21:02 NSEcho

Awesome, works for me!

jiska2342 avatar Feb 18 '25 23:02 jiska2342

Glad to hear that, I will leave this open until I update README.md with instructions for offsets.

NSEcho avatar Feb 19 '25 10:02 NSEcho

One more idea on this, but not sure how practical it would be.

gxpc could also get the offsets when it is first used with a device and then add them to the config file. It would be possible to automate all of this 🦄 ✨ Considering the speedup that one gets from saving offsets instead of using the DebugSymbol API, it might be worth it. However, it would also be somewhat magic and do more than some users could expect from a tool that just intercepts XPC.

jiska2342 avatar Feb 22 '25 21:02 jiska2342

Since the speed is a major factor of course and this would speed work later, this is really great idea and because the logic for config file is there, I could easily extend it to make it save automatically.

NSEcho avatar Feb 22 '25 22:02 NSEcho

Hi :) Just a note on this, apparently gxpc now exits with an error if there is no config file, so people always have to run gxpc --init first and that's a bit confusing.

jiska2342 avatar Feb 26 '25 14:02 jiska2342

@jiska2342 yeah, that is the case right now. I will probably find time tonight to handle automatic creation of config file and saving offsets.

NSEcho avatar Feb 26 '25 15:02 NSEcho

Implemented in https://github.com/ReverseApple/gxpc/releases/tag/v1.20.0. You can remove ~/gxpc.conf and it will save automatically. Let me know if it is okay now.

NSEcho avatar Feb 26 '25 20:02 NSEcho

Thank you for making this change, works for me :)

jiska2342 avatar May 08 '25 21:05 jiska2342

... but it seems to save offsets multiple times?

Not sure if this is my fault, as the program crashed when saving offsets for the first time. However, my gxpc.conf now looks like this:

{
  "offsets": [
    {
      "os": "iPad7,11",
      "builds": [
        {
          "22E252": {
            "PlistCreate": "0x68dbc",
            "CallHandler": "0xf830"
          }
        }
      ]
    },
    {
      "os": "iPad7,11",
      "builds": [
        {
          "22E252": {
            "PlistCreate": "0x68dbc",
            "CallHandler": "0xf830"
          }
        }
      ]
    }
  ]
}

jiska2342 avatar May 08 '25 21:05 jiska2342

Did you keep the file gxpc.conf prior to running this new version or this happens to be the case when running it basically the first time without config file created prior to it.

NSEcho avatar May 11 '25 12:05 NSEcho

@jiska2342 you are right, I can confirm that it does not properly check whether the offsets are previously saved

NSEcho avatar May 11 '25 12:05 NSEcho

@jiska2342, the issue is fixed now, although gxpc v2.x requires Frida 17.x.

NSEcho avatar May 20 '25 22:05 NSEcho