NOP the cache validation check in launchd_cache_loader. Anchor strategy: Search for "unsecure_cache" substring, resolve to full null-terminated string start, find ADRP+ADD xref to it, NOP the nearby cbz/cbnz branch. The binary checks boot-arg "launchd_unsecure_cache=" — if not foun
(filepath)
| 8 | _adrp_cs.detail = True |
| 9 | |
| 10 | def patch_launchd_cache_loader(filepath): |
| 11 | """NOP the cache validation check in launchd_cache_loader. |
| 12 | |
| 13 | Anchor strategy: |
| 14 | Search for "unsecure_cache" substring, resolve to full null-terminated |
| 15 | string start, find ADRP+ADD xref to it, NOP the nearby cbz/cbnz branch. |
| 16 | |
| 17 | The binary checks boot-arg "launchd_unsecure_cache=" — if not found, |
| 18 | it skips the unsecure path via a conditional branch. NOPping that branch |
| 19 | allows modified launchd.plist to be loaded. |
| 20 | """ |
| 21 | data = bytearray(open(filepath, "rb").read()) |
| 22 | sections = parse_macho_sections(data) |
| 23 | |
| 24 | text_sec = find_section(sections, "__TEXT,__text") |
| 25 | if not text_sec: |
| 26 | print(" [-] __TEXT,__text not found") |
| 27 | return False |
| 28 | |
| 29 | text_va, text_size, text_foff = text_sec |
| 30 | |
| 31 | # Strategy 1: Search for anchor strings in __cstring |
| 32 | # Code always references the START of a C string, so after finding a |
| 33 | # substring match, back-scan to the enclosing string's first byte. |
| 34 | cstring_sec = find_section(sections, "__TEXT,__cstring") |
| 35 | anchor_strings = [ |
| 36 | b"unsecure_cache", |
| 37 | b"unsecure", |
| 38 | b"cache_valid", |
| 39 | b"validation", |
| 40 | ] |
| 41 | |
| 42 | for anchor_str in anchor_strings: |
| 43 | anchor_off = data.find(anchor_str) |
| 44 | if anchor_off < 0: |
| 45 | continue |
| 46 | |
| 47 | # Find which section this belongs to and compute VA |
| 48 | anchor_sec_foff = -1 |
| 49 | anchor_sec_va = -1 |
| 50 | for sec_name, (sva, ssz, sfoff) in sections.items(): |
| 51 | if sfoff <= anchor_off < sfoff + ssz: |
| 52 | anchor_sec_foff = sfoff |
| 53 | anchor_sec_va = sva |
| 54 | break |
| 55 | |
| 56 | if anchor_sec_foff < 0: |
| 57 | continue |
| 58 | |
| 59 | # Back-scan to the start of the enclosing null-terminated C string. |
| 60 | # Code loads strings from their beginning, not from a substring. |
| 61 | str_start_off = _find_cstring_start(data, anchor_off, anchor_sec_foff) |
| 62 | str_start_va = anchor_sec_va + (str_start_off - anchor_sec_foff) |
| 63 | substr_va = anchor_sec_va + (anchor_off - anchor_sec_foff) |
| 64 | |
| 65 | if str_start_off != anchor_off: |
| 66 | end = data.index(0, str_start_off) |
| 67 | full_str = data[str_start_off:end].decode("ascii", errors="replace") |
no test coverage detected