Skip to content

Instantly share code, notes, and snippets.

@bazad
Last active April 12, 2024 05:18
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bazad/fe4e76a0a3b761d9fde7e74654ac14e4 to your computer and use it in GitHub Desktop.
Save bazad/fe4e76a0a3b761d9fde7e74654ac14e4 to your computer and use it in GitHub Desktop.
Split a decrypted Apple SEP firmware image into individual Mach-O files.
#! /usr/bin/env python3
#
# sep_firmware_split.py
# Brandon Azad
#
# Split a decrypted Apple SEP firmware image into individual Mach-O files.
#
# iPhone11,8 17C5053a https://twitter.com/s1guza/status/1203550760102969345
# iPhone11,8 17E255 https://twitter.com/s1guza/status/1244683851957522435
#
import struct
import sys
def main():
# Read the file.
filename = sys.argv[1]
with open(filename, 'rb') as f:
data = f.read()
# An assert that a file is a Mach-O.
def assert_macho(offset):
assert(struct.unpack('<I', data[offset:offset+4])[0] == 0xfeedfacf)
# Find the header.
offset = data.find(b'Built by legion2')
assert(offset > 0)
offset += 0x10
offset = struct.unpack('<I', data[offset:offset+4])[0]
# Unpack the header, which describes 2 SEPOS components: the L4-based kernel and the SEPOS root
# process.
# 00000000 sep_fw_header struc ; (sizeof=0xC8, mappedto_25)
# [ 0] 00000000 kernel_uuid DCB 16 dup(?)
# [ 1] 00000010 field_10 DCQ ?
# [ 2] 00000018 kernel_text_offset DCQ ? ; offset
# [ 3] 00000020 kernel_data_offset DCQ ? ; offset
# [ 4] 00000028 start_of_text DCQ ? ; offset
# [ 5] 00000030 start_of_data DCQ ? ; offset
# [ 6] 00000038 sep_fw_size DCQ ?
# [ 7] 00000040 is_zero_1 DCQ ?
# [ 8] 00000048 is_zero_2 DCQ ?
# [ 9] 00000050 is_zero_3 DCQ ?
# [10] 00000058 root_text_offset DCQ ? ; offset
# [11] 00000060 root_text_size DCQ ?
# [12] 00000068 root_vm_size DCQ ?
# [13] 00000070 root_start DCQ ?
# [14] 00000078 is_zero_4 DCQ ?
# [15] 00000080 is_zero_5 DCQ ?
# [16] 00000088 is_zero_6 DCQ ?
# [17] 00000090 name DCB 16 dup(?) ; string(C)
# [18] 000000A0 root_uuid DCB 16 dup(?)
# [19] 000000B0 source_version DCQ ?
# [20] 000000B8 field_B8 DCQ ?
# [21] 000000C0 app_count DCQ ?
# 000000C8 sep_fw_header ends
# 0 10 20 30 40 50 60 70 80 90 a0 b0 c0 : c8
sep_fw_header_format = '< 16s QQ QQ QQ QQ QQ QQ QQ QQ 16s 16s QQ Q'
sep_fw_header = struct.unpack(sep_fw_header_format, data[offset:offset+0xc8])
kernel_text_offset = sep_fw_header[2]
root_text_offset = sep_fw_header[10]
name = sep_fw_header[17].decode('ascii').strip()
app_count = sep_fw_header[21]
offset += 0xc8
# Extract the SEPOS kernel.
assert_macho(kernel_text_offset)
size_offset = data.find(b'__LINKEDIT', kernel_text_offset)
assert(size_offset > 0)
size_offset += 0x20 # segname -> fileoff
kernel_size = struct.unpack('<Q', data[size_offset:size_offset+8])[0]
print('{:#08x}-{:#08x} {}'.format(kernel_text_offset,
kernel_text_offset + kernel_size, name + '_kernel'))
with open(name + '_kernel', 'wb') as f:
f.write(data[kernel_text_offset:kernel_text_offset+kernel_size])
# Extract the SEPOS root process.
assert_macho(root_text_offset)
size_offset = data.find(b'__LINKEDIT', root_text_offset)
assert(size_offset > 0)
size_offset += 0x20 # segname -> fileoff
root_size = struct.unpack('<Q', data[size_offset:size_offset+8])[0]
print('{:#08x}-{:#08x} {}'.format(root_text_offset,
root_text_offset + root_size, name + '_root'))
with open(name + '_root', 'wb') as f:
f.write(data[root_text_offset:root_text_offset+root_size])
# 00000000 sep_fw_app struc ; (sizeof=0x78, mappedto_24)
# [ 0] 00000000 text_offset DCQ ? ; offset
# [ 1] 00000008 text_size DCQ ?
# [ 2] 00000010 data_offset DCQ ? ; offset
# [ 3] 00000018 data_size DCQ ?
# [ 4] 00000020 vm_base DCQ ?
# [ 5] 00000028 start DCQ ?
# [ 6] 00000030 is_zero_1 DCQ ?
# [ 7] 00000038 is_zero_2 DCQ ?
# [ 8] 00000040 is_zero_3 DCQ ?
# [ 9] 00000048 is_ffffffff DCQ ?
# [10] 00000050 name DCB 16 dup(?) ; string(C)
# [11] 00000060 uuid DCB 16 dup(?)
# [12] 00000070 source_version DCQ ?
# 00000078 sep_fw_app ends
# 0 10 20 30 40 50 60 70 : 78
sep_fw_app_format = '< QQ QQ QQ QQ QQ 16s 16s Q'
# Process the individual apps.
for i in range(app_count):
# Unpack the entry for this app.
sep_fw_app = struct.unpack(sep_fw_app_format, data[offset:offset+0x78])
text_offset = sep_fw_app[0]
text_size = sep_fw_app[1]
data_offset = sep_fw_app[2]
data_size = sep_fw_app[3]
name = sep_fw_app[10].decode('ascii').strip()
assert_macho(text_offset)
print('{:#08x}-{:#08x} {:#08x}-{:#08x} {}'.format(
text_offset, text_offset + text_size,
data_offset, data_offset + data_size, name))
offset += 0x78
# Reconstruct the app binary.
with open(name + '-{:02d}'.format(i), 'wb') as f:
f.write(data[text_offset:text_offset+text_size])
f.write(data[data_offset:data_offset+data_size])
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment