Skip to content

Instantly share code, notes, and snippets.

@Lunarixus
Created January 6, 2026 20:56
Show Gist options
  • Select an option

  • Save Lunarixus/698d9fee99e75578660a42ef6525d18c to your computer and use it in GitHub Desktop.

Select an option

Save Lunarixus/698d9fee99e75578660a42ef6525d18c to your computer and use it in GitHub Desktop.
iOS 10.x+ kernelcache decompressor
import sys
import os
import struct
# Try to import lzfse
try:
import lzfse
LZFSE_AVAILABLE = True
except ImportError:
LZFSE_AVAILABLE = False
def decompress_lzss(data):
"""
A pure Python implementation of Apple's LZSS decompression.
Note: This is slower than C implementations but works for scripts.
"""
# Skip the 0x180 byte header usually found in complzss
# The header format is usually: Magic(8) + ...
# But often we just skip to the data.
# For complzss, the data usually starts after the header.
# We will assume 'data' passed here is the whole payload starting at 'complzss'
if len(data) < 12:
raise ValueError("Data too short for LZSS")
# complzss header parsing
magic = data[:8]
if magic != b'complzss':
raise ValueError("Invalid LZSS magic")
# Big Endian Uncompressed size is at offset 0x8
uncompressed_size = struct.unpack('>I', data[8:12])[0]
# The compressed stream usually starts at 0x180 (384 decimal)
# However, sometimes it is packed tighter.
# Standard kernelcache complzss header size is 0x180.
header_size = 0x180
src = data[header_size:]
dst = bytearray()
src_idx = 0
# Ring buffer for LZSS
text_buf = bytearray(b' ' * 4096)
r = 4078
flags = 0
while src_idx < len(src) and len(dst) < uncompressed_size:
# 8 bits of flags, read one byte
flags >>= 1
if (flags & 256) == 0:
if src_idx >= len(src): break
c = src[src_idx]
src_idx += 1
flags = c | 0xff00
if (flags & 1):
# Literal byte
if src_idx >= len(src): break
c = src[src_idx]
src_idx += 1
dst.append(c)
text_buf[r] = c
r = (r + 1) & 4095
else:
# Reference to previous data
if src_idx + 1 >= len(src): break
i = src[src_idx]
j = src[src_idx + 1]
src_idx += 2
# Decode offset and length
k = i | ((j & 0xf0) << 4)
length = (j & 0x0f) + 3
for _ in range(length):
c = text_buf[(k + _) & 4095]
dst.append(c)
text_buf[r] = c
r = (r + 1) & 4095
return bytes(dst)
def decompress_kernel(input_path, output_path):
with open(input_path, 'rb') as f:
file_data = f.read()
print(f"[*] Analyzing {input_path} ({len(file_data)} bytes)...")
# Modern iOS (LZFSE) uses 'bvx2'
# Older iOS (LZSS) uses 'complzss'
offset_lzfse = file_data.find(b'bvx2')
offset_lzss = file_data.find(b'complzss')
matches = []
if offset_lzfse != -1: matches.append((offset_lzfse, 'LZFSE'))
if offset_lzss != -1: matches.append((offset_lzss, 'LZSS'))
if not matches:
print("[!] No supported compression magic (bvx2/complzss) found.")
print(" Is this file already decompressed or encrypted?")
return
matches.sort(key=lambda x: x[0])
best_offset, algo = matches[0]
print(f"[*] Detected {algo} compression at offset {hex(best_offset)}")
payload = file_data[best_offset:]
decompressed_data = None
if algo == 'LZFSE':
if not LZFSE_AVAILABLE:
print("[!] Error: LZFSE detected but 'lzfse' module not installed.")
print(" Please run: pip install lzfse")
return
try:
# bvx2 is the magic, usually followed by 4 bytes size, then data.
decompressed_data = lzfse.decompress(payload)
except Exception as e:
print(f"[!] LZFSE Decompression failed: {e}")
return
elif algo == 'LZSS':
print("[*] Starting LZSS decompression (this might take a moment in pure Python)...")
try:
decompressed_data = decompress_lzss(payload)
except Exception as e:
print(f"[!] LZSS Decompression failed: {e}")
return
# Check for Mach-O Magic (0xfeedfacf) in the result
if decompressed_data:
# Check the first 4 bytes for Mach-O 64 magic
# 0xfeedfacf in Little Endian is cf fa ed fe
if decompressed_data.startswith(b'\xcf\xfa\xed\xfe'):
print("[*] Validation Successful: Found Mach-O 64-bit header.")
else:
print("[!] Warning: Decompressed data does not start with Mach-O magic.")
print(f" First 4 bytes: {decompressed_data[:4].hex()}")
with open(output_path, 'wb') as out:
out.write(decompressed_data)
print(f"[*] Success! Decompressed kernel written to: {output_path}")
else:
print("[!] Decompression resulted in empty data.")
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python3 kernelcache_decompressor.py <input_kernel> <output_raw>")
sys.exit(1)
in_file = sys.argv[1]
out_file = sys.argv[2]
if not os.path.exists(in_file):
print(f"Error: Input file '{in_file}' not found.")
sys.exit(1)
decompress_kernel(in_file, out_file)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment