Skip to content

Instantly share code, notes, and snippets.

@leptos-null
Forked from dlevi309/abi.py
Last active September 10, 2023 22:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leptos-null/3fb36f024dc41981ecf7c409ac5e3a91 to your computer and use it in GitHub Desktop.
Save leptos-null/3fb36f024dc41981ecf7c409ac5e3a91 to your computer and use it in GitHub Desktop.
script to switch between iOS13 (00000002) and iOS14 (80000002) arm64e ABI
import sys
FAT_MAGIC = b'\xca\xfe\xba\xbe' # big endian
FAT_MAGIC_64 = b'\xca\xfe\xba\xbf' # big endian
MH_MAGIC_64 = b'\xfe\xed\xfa\xcf' # big endian
MH_CIGAM_64 = b'\xcf\xfa\xed\xfe' # little endian
CPU_TYPE_ARM64 = b'\x01\x00\x00\x0c' # big endian
CPU_SUBTYPE_IOS13 = b'\x00\x00\x00\x02' # big endian
CPU_SUBTYPE_IOS14 = b'\x80\x00\x00\x02' # big endian
def handle_file(file: str, targetSubtype: bytes):
intTarget = int.from_bytes(targetSubtype, byteorder='big')
with open(file, mode="r+b") as f:
original = f.read()
buf = bytearray(original)
matchOffsets: list[bytes] = []
fatEntrySize = None
fatOffsetSize = None
if buf[0:4] == FAT_MAGIC:
fatEntrySize = 20
fatOffsetSize = 4
if buf[0:4] == FAT_MAGIC_64:
fatEntrySize = 32
fatOffsetSize = 8
if fatEntrySize is not None:
# everything in the FAT entry is big endian
slices = int.from_bytes(buf[4:8], byteorder='big')
print(f"FAT file with {slices} slices")
for sliceIndex in range(slices):
offset = 8 + sliceIndex * fatEntrySize
fatArch = buf[offset:(offset + fatEntrySize)]
print(sliceIndex, fatArch[0:4], fatArch[4:8])
if fatArch[0:4] != CPU_TYPE_ARM64:
continue
subtype = fatArch[4:8]
if subtype in [CPU_SUBTYPE_IOS13, CPU_SUBTYPE_IOS14]:
if subtype == targetSubtype:
print(f"Skipping {subtype} -> {targetSubtype}")
continue
print(f"Swapping (fat) {subtype} -> {targetSubtype}")
buf[(offset+4):(offset+8)] = targetSubtype
matchOffsets.append(int.from_bytes(fatArch[8:(8+fatOffsetSize)], byteorder='big'))
if buf[0:4] in [MH_MAGIC_64, MH_CIGAM_64]:
matchOffsets.append(0)
for matchOffset in matchOffsets:
byteorder = None
if buf[matchOffset:(matchOffset+4)] == MH_MAGIC_64:
byteorder = 'big'
if buf[matchOffset:(matchOffset+4)] == MH_CIGAM_64:
byteorder = 'little'
if byteorder is None:
print(f"Requested switch at {matchOffset:x}, but no known header")
continue
if int.from_bytes(buf[(matchOffset+4):(matchOffset+8)], byteorder=byteorder) != int.from_bytes(CPU_TYPE_ARM64, byteorder='big'):
continue # not ARM64
intSubtype = int.from_bytes(buf[(matchOffset+8):(matchOffset+12)], byteorder=byteorder)
bigEndianSubtypes = [CPU_SUBTYPE_IOS13, CPU_SUBTYPE_IOS14]
if intSubtype in [int.from_bytes(x, byteorder='big') for x in bigEndianSubtypes]:
if intSubtype == intTarget:
print(f"Skipping {intSubtype:x} -> {intTarget:x}")
continue
print(f"Swapping (thin) {intSubtype:x} -> {intTarget:x}")
buf[(matchOffset+8):(matchOffset+12)] = int.to_bytes(intTarget, length=4, byteorder=byteorder)
if buf != original:
f.seek(0)
f.write(buf)
else:
print("No changes")
def main():
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <path>")
return
path = sys.argv[1]
handle_file(path, CPU_SUBTYPE_IOS14)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment