Skip to content

Instantly share code, notes, and snippets.

@PsychoTea
Created September 21, 2025 22:07
Show Gist options
  • Save PsychoTea/4f2698a70fd1386f6a8465ee987b1404 to your computer and use it in GitHub Desktop.
Save PsychoTea/4f2698a70fd1386f6a8465ee987b1404 to your computer and use it in GitHub Desktop.
A small Python script using r2 to fix up adrp/add instructions which become invalid after creating a kernel cache with kmutil.
#!/usr/bin/env python3
import argparse
from dataclasses import dataclass
import re
import shutil
import r2pipe
@dataclass
class Region:
name: str
start: int
end: int
def find_text_exec_regions(r2: r2pipe.open) -> list[Region]:
ranges: list[Region] = []
for section in r2.cmdj("iSj") or []:
if not (name := section.get("name")):
raise ValueError("failed to get section name")
if "__TEXT_EXEC" not in name:
continue # Only want TEXT_EXEC segments.
if "__text" not in name:
continue # Only want the actual code in those segments.
if "com.apple.kernel" in name:
continue # Don't need to do fixups in the kernel itself.
if not (start := section.get("vaddr")):
raise ValueError(f"failed to get section start: {name}")
if not (size := section.get("vsize")):
raise ValueError(f"failed to get section size: {name}")
ranges.append(Region(name, start, start + size))
return ranges
def r2_set_search_range(r2: r2pipe.open, start: int, end: int):
r2.cmd("e search.flags=false")
r2.cmd("e search.align=4")
r2.cmd(f"e search.from={start:#x}")
r2.cmd(f"e search.to={end:#x}")
ADRP_REGEX = re.compile(
r"adrp (x[0-9]+), ([0-9xa-f]+); add (x[0-9]+), (x[0-9]+), (0x[0-9xa-f]+)"
)
@dataclass
class ADRL:
pc: int # Address of the instruction
page: int # Target page
offset: int # Target page offset/index
reg: str # Register used
asm: list[str] # Assembly lines
@property
def target(self) -> int:
return self.page + self.offset
@classmethod
def from_r2_search_json(cls, insn_json) -> "ADRL | None":
if not (matches := re.findall(ADRP_REGEX, insn_json["code"])):
return None
reg1, page, reg2, reg3, offset = matches[0]
if reg1 != reg2 != reg3:
return None
asm = [a.strip() for a in insn_json["code"].split(";")]
return cls(insn_json["addr"], int(page, 16), int(offset, 16), reg1, asm)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("infile", help="input file")
parser.add_argument("outfile", help="output file")
args = parser.parse_args()
shutil.copyfile(args.infile, args.outfile) # Since r2 writes in-place.
r2 = r2pipe.open(args.outfile)
r2.cmd("oo+") # Enable writing to the input (output) file.
for fr in find_text_exec_regions(r2):
adjustment = fr.start & 0xFFF
print(f"Fixing range: {fr.name} ({fr.start:#x}-{fr.end:#x}) (+{adjustment:#x})")
r2_set_search_range(r2, fr.start, fr.end)
adrls = r2.cmdj('"/adj adrp; add"') or []
for adrl_json in adrls:
if not (adrl := ADRL.from_r2_search_json(adrl_json)):
continue
target_seg_json = r2.cmdj(f"iSj. @ {adrl.target:#x}") or {}
target_seg_name = target_seg_json.get("name") or ""
if "__TEXT_EXEC.__text" not in target_seg_name:
continue # ADRL doesn't point inside TEXT_EXEC segment.
print(f"Found ADRL at {adrl.pc:#x}:")
new_target = adrl.target + adjustment
adrp_value = new_target & ~0xfff
add_value = new_target & 0xfff
adrp_line = f"adrp {adrl.reg}, {adrp_value:#x}"
add_line = f"add {adrl.reg}, {adrl.reg}, {add_value:#x}"
print(f" Original code: {'; '.join(adrl.asm)}")
print(f" Replaced code: {adrp_line}; {add_line}")
r2.cmd(f"s {adrl.pc:#x}")
r2.cmd(f'"wa {adrp_line}; {add_line}"')
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment