Home iOS - Binary Patching via ARM64 Branch Flip
Post
Cancel

iOS - Binary Patching via ARM64 Branch Flip

Frida runtime hooks fail when the code you want to bypass runs before Frida can attach, or when the app implements pinning entirely in C++ with no ObjC or Swift symbols to intercept. Some apps use a custom TLS stack like Facebook Fizz or roll their own certificate verification in a static initializer that fires before main. Frida arrives too late. The alternative: patch the binary on disk, flip the branch instruction that sends execution down the failure path, and side-load the patched binary. The device never sees the original flow.


Prerequisites

  1. Decrypted binary - cryptid=0 from frida-ios-dump (see the IPA Decryption post).
  2. Ghidra or Hopper for disassembly.
  3. Hex editor or xxd + dd for the patch.
  4. Jailbroken device with a development-signed entitlement, or a developer cert for re-signing.


Locating the pinning branch in Ghidra

Import the decrypted Mach-O into Ghidra. Let the auto-analysis run.

Find the pinning logic:

  1. Search for the string certificate or pinnedCertificate in the string table (Search -> For Strings).
  2. Double-click the reference - it lands you in the function that handles the certificate check.
  3. Look for a call to SecTrustEvaluate or SecTrustEvaluateWithError followed by a conditional branch.
  4. The failure path calls cancel() or returns early. The success path continues to the network call.

The branch that separates the two paths is what to flip.


ARM64 branch opcodes

ARM64 instructions are 4 bytes, fixed-width. Branch instructions:

MnemonicEncoding (little-endian)Meaning
B.EQ00 00 00 54 (offset-dependent)branch if equal (ZF=1)
B.NE01 00 00 54 (offset-dependent)branch if not equal (ZF=0)
B.LT0B 00 00 54branch if less than
Bxx xx xx 14unconditional branch
NOP1F 20 03 D5no-operation

To always take the success path: replace the B.EQ/B.NE that jumps to the failure path with an unconditional B to the success path, or replace it with NOP so execution falls through to success.

The exact 4-byte encoding depends on the branch target offset. Ghidra shows the instruction’s address and the encoded bytes in the Listing view.


Reading the bytes to patch

In Ghidra’s Listing view, click the instruction. The bottom bar shows the hex bytes at that address. Or from the command line:

1
2
3
4
5
6
# Find the offset of the instruction in the binary
# Ghidra shows the virtual address; convert to file offset
# file_offset = vaddr - load_address (from LC_SEGMENT_64 vmaddr - fileoff)

# Read 4 bytes at the offset
xxd -s <file_offset> -l 4 Payload/VulnLabAppiOS.app/VulnLabAppiOS

Note the 4 bytes. This is what you are replacing.


Patching with dd

To overwrite 4 bytes at <file_offset> with a NOP:

1
2
3
4
5
6
7
8
9
10
# Copy the binary first - never patch the original
cp Payload/VulnLabAppiOS.app/VulnLabAppiOS Payload/VulnLabAppiOS.app/VulnLabAppiOS.orig

# Write NOP (1F 20 03 D5) at the branch instruction's file offset
printf '\x1f\x20\x03\xd5' \
  | dd of=Payload/VulnLabAppiOS.app/VulnLabAppiOS \
       bs=1 seek=<file_offset> conv=notrunc

# Confirm
xxd -s <file_offset> -l 4 Payload/VulnLabAppiOS.app/VulnLabAppiOS

To write an unconditional branch that jumps forward by N instructions:

  • The encoding is ((N & 0x3FFFFFF) | 0x14000000) in little-endian.
  • For a forward jump of 1 instruction (skip the next): 0x14000001 = \x01\x00\x00\x14.


Re-signing the patched binary

After patching, the binary’s code signature is invalid. Re-sign before installing:

1
2
3
4
5
6
7
# Ad-hoc signing (works on jailbroken device with platform binary entitlement)
codesign -f -s - Payload/VulnLabAppiOS.app/VulnLabAppiOS

# Or with a development identity from Xcode
codesign -f -s "iPhone Developer: Your Name (TEAMID)" \
  --entitlements VulnLabAppiOS.entitlements \
  Payload/VulnLabAppiOS.app/VulnLabAppiOS

Ad-hoc signing (-s -) produces a self-signed binary. It installs on jailbroken devices. It will not install on stock devices without a developer identity.


Repacking the IPA

1
cd Payload && zip -qry ../VulnLabAppiOSPatched.ipa .

Side-load via ios-deploy or AltStore on a jailbroken device:

1
ios-deploy --bundle Payload/VulnLabAppiOS.app --justlaunch


Frida in-process patching as an alternative

If you have a Frida attach window but the pinning check runs in a constructor, use Memory.patchCode to flip the branch before the check runs. This does not modify the file:

1
2
3
4
5
6
7
8
9
10
// Find the address of the branch instruction at runtime
const baseAddr = Module.findBaseAddress('VulnLabAppiOS');
const branchAddr = baseAddr.add(0x12345);   // offset from Ghidra analysis

Memory.patchCode(branchAddr, 4, function(code) {
    // ARM64 NOP: 1F 20 03 D5
    code.writeByteArray([0x1f, 0x20, 0x03, 0xd5]);
});

console.log('[patch] branch at ' + branchAddr + ' replaced with NOP');

The patch is applied to the live process only. On next launch the original binary runs. Combine with a --no-pause spawn:

1
frida -U -f com.vulnlab.iosapp -l patch.js --no-pause


Verification

With the pinning bypass in place:

  1. Configure Burp Suite proxy on the same Wi-Fi network.
  2. Set the device’s HTTP proxy to the Burp host.
  3. Trigger the network call that previously failed.

Burp should show the intercepted TLS connection. If it does, the patch succeeded. If the connection still fails, the app has a second pinning check or a certificate transparency check that also needs patching.


When static patching is not needed

For most apps, the objection SSL pinning bypass is sufficient:

1
2
objection -g com.vulnlab.iosapp explore
ios sslpinning disable

Objection hooks SecTrustEvaluate, SSLHandshake, and common third-party pinning libraries at runtime. Binary patching is a last resort for custom TLS stacks and constructor-time checks that fire before Frida attaches.


Closing

ARM64 branch flipping is a one-instruction patch. Ghidra finds the offset, dd applies it, codesign re-signs it, and Burp confirms it. The skill transfers to any binary check: root detection, license validation, jailbreak detection. Find the branch, flip it.

Happy Hacking !!

This post is licensed under CC BY 4.0 by the author.