Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quick fix for fuzzying iOS on MacOS ? #61

Open
rainbowcardiod opened this issue Feb 1, 2025 · 4 comments
Open

Quick fix for fuzzying iOS on MacOS ? #61

rainbowcardiod opened this issue Feb 1, 2025 · 4 comments

Comments

@rainbowcardiod
Copy link

Using p0tools I am able to run a simple program compiled for iOS, with a manual trick[1] though, because the amfi part does not seem to work anymore.
I patched TinyInst to use posix_spawnattr_set_platform_np(..., PLATFORM_IOS, ...) and jackalope is able to spawn the program, and with the manual trick, everything runs[2]. However, tinyinst does not ever detect the target function to be called, and if the program exits it complains with WARNING: Target function not reached, retrying with a clean process.

My question is if there is a simple fix for continuing this experiment, or if the nature of TinyInst does not allow to fuzz iOS-on-Mac binaries.

Thank you in advance.

[1] Manual trick: after the target program has been spawned in paused mode, keep Jackalope paused (using a getc() after the posix_spawn), and do the following with lldb:

  1. attach to the target program
  2. break set -n xpc_copy_entitlements_for_self, continue and hit the breakpoint
  3. get the address of my_xpc_copy_entitlements_for_self (from interpose.c of p0tools )
  4. re write $pc 0x100dce000 -- where the address is from step 2
  5. continue and detach, tell Jackalope to continue
  6. last note: for doing step 4. the program must wait some seconds to give time to jacklope to attach before starting the target function

[2] ./fuzzer -t 1000 -in in -out out -delivery shmem -target_module testcrashme -instrument_module crashme2_ios.dylib -target_method __Z4fuzzPc -nargs 1 -iterations 10000 -persist -loop -cmp_coverage -- ./testcrashme -m @@

@ifratric
Copy link
Collaborator

ifratric commented Feb 4, 2025

Hey, If you'd like to debug why the -target_method wasn't reached, the symbol lookup happens in https://github.com/googleprojectzero/TinyInst/blob/master/macOS/debugger.cpp#L1224 . You could uncomment the printf on the previous line, and then it would print all the symbols it found in the binary. But I wonder if something is different about the mach-o structures in the iOS binaries, so it doesn't even reach that code. In any case, if you go this route, I'd be interested in what you found.

Another option is to use -target_offset instead of -target_method and avoid symbol lookup altogether.

It's also possible that something got messed up because IIUC you used a second debugger to redirect xpc_copy_entitlements_for_self to my_xpc_copy_entitlements_for_self while Jackalope was running. Note that Jackalope (actually, TinyInst to be precise) is already a debugger so attaching a second debugger might prevent TinyInst from getting exceptions correctly, mess up the exception port etc. You can fix this by simply relying on Jackalope itself to set the breakpoint and redirect execution. You can use TinyInst hook API, see https://github.com/googleprojectzero/TinyInst/blob/master/hook.md, or simply write a breakpoint instruction and let TinyInst catch it (see Debugger::AddBreakpoint and Debugger::HandleDebuggerBreakpoint).

@rainbowcardiod
Copy link
Author

The symbol is found, otherwise it prints [-] PROGRAM ABORT : Unable to find address of target method. I tried to use the printf as you said, and it is there.
Maybe the cause of the problem is the second point: lldb may mess things up. However, keep in mind that I attach to the pid with lldb before tinyinst attaches to it. To do this I put a getc() call exactly after posix_spawn() in Debugger::StartProcess(). Then I modify the $pc in lldb, as I said in the issue, let the process continue (which will enter in a long enough sleep()), detach, and let Jackalope/TinyInst continue by pressing enter.
Anyway, I was trying as you suggested with Debugger::AddBreakpoint. I am able to hit a breakpoint on a function of the main binary, but I am not able to hit the call to xpc_copy_entitlements_for_self. This is what I did:

  1. In Debugger::OnModuleLoaded I added:
  if (!strcasecmp(module_name, "libxpc.dylib")) {

    libxpc_copy_entitlements_address = GetLibXPCAddress(module);

    SAY("Libxpc found: %s at %p\n", module_name, libxpc_copy_entitlements_address);

    if (!libxpc_copy_entitlements_address) {
      FATAL("Error determining libxpc_copy_entitlements_address method address\n");
    }

    AddBreakpoint(libxpc_copy_entitlements_address, BREAKPOINT_NOTIFICATION);
  }
...
void *Debugger::GetLibXPCAddress(void *base_address) {
  if (!target_offset) {
    void *method_address = GetSymbolAddress(base_address, "_xpc_copy_entitlements_for_self");
    if (method_address == NULL) {
      FATAL("Unable to find address of target method\n");
    }

    target_offset = (uint64_t)method_address - (uint64_t)base_address;
  }

  return (void*)((uint64_t)base_address + target_offset);
}

This step is successful, libxpc_copy_entitlements_address gets set.

  1. In Debugger::HandleDebuggerBreakpoint I added:
  if (tmp_breakpoint->address == (void*)((uint64_t)last_exception.ip)) {
      breakpoint = tmp_breakpoint;
  
      SAY("brk at %p\n", breakpoint);
    
      if (tmp_breakpoint->address == libxpc_copy_entitlements_address) {
        SAY("libxpc address hit\n");
        SetRegister(ARCH_PC, (size_t)interpose_copy_entitlements_address);  // Is this right to modify the Program Counter?
//      Continue(1000);
        return BREAKPOINT_UNKNOWN;
      }

Let me know.

@ifratric
Copy link
Collaborator

ifratric commented Feb 7, 2025

One thing I see is that AddBreakpoint function takes a type as a second argument, instead of using BREAKPOINT_NOTIFICATION which is reserved for dyld load notification, you should create a new one, e.g.

#define BREAKPOINT_COPYENTITLEMENTS 0x10

(for the value, not these are bit flags)

In Debugger::HandleDebuggerBreakpoint you can then test

if (breakpoint->type & BREAKPOINT_COPYENTITLEMENTS)

To see if your breakpoint was reached.

@rainbowcardiod
Copy link
Author

rainbowcardiod commented Feb 7, 2025

Ok. So, (breakpoint->type & BREAKPOINT_COPYENTITLEMENTS) is right, now I hit xpc_copy_entitlements_for_self with the breakpoint, and with a mac binary SetRegister(ARCH_PC, (size_t)interpose_copy_entitlements_address) works somehow because my_xpc_copy_entitlements_for_self gets called. However, the program does not resume normally.
For what regards an ios binary, the xpc_copy_entitlements_for_self breakpoint is hit if I do not set -target_method, it is not hit if I set -target_method.

So more or less I believe it is the right road, while still not functioning. However, for a while I cannot continue working on this.

Note well: I forgot to mention in the issue an important point: I clear the environment before spawning the ios process, otherwise DYLD_SHARED_REGION=private gives problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants