Created
February 9, 2025 21:19
-
-
Save BlueFalconHD/f50339a00ef39659bc1c9dc11d53f1b5 to your computer and use it in GitHub Desktop.
DYLD Extracted Shared Cache CFString offset fixer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import idc | |
import idaapi | |
import idautils | |
import ida_auto | |
import re | |
import random | |
# CFString structure offsets | |
OFFSET_ISA = 0x0 # pointer to isa | |
OFFSET_INFO = 0x8 # info pointer | |
OFFSET_DATA = 0x10 # pointer to the string data (thing fixed) | |
OFFSET_LENGTH = 0x18 # length of the string | |
SUBTRACT_VAL = 0x20000000000000 # set this to the value prepended to the actual address in the list of cfstrings, for me it was 0x20000000000000 | |
ADD_VAL = 0x180000000 # set this to base address of DYLD shared cache. | |
# Maximum allowed length for new names | |
MAX_NAME_LENGTH = 20 | |
def unique_name(base, addr, max_length=MAX_NAME_LENGTH): | |
""" | |
Ensure the name is unique. If the base name is already in use | |
(and not by the current address), append a suffix of an underscore and | |
three random digits, adjusting so that the final length does not exceed max_length. | |
""" | |
if len(base) > max_length: | |
base = base[:max_length] | |
new_name = base | |
while idc.get_name_ea_simple(new_name) != idc.BADADDR and idc.get_name_ea_simple(new_name) != addr: | |
suffix = "_%03d" % random.randint(0, 999) | |
if len(base) + len(suffix) > max_length: | |
new_name = base[:max_length - len(suffix)] + suffix | |
else: | |
new_name = base + suffix | |
return new_name | |
def fix_cfstring(addr): | |
""" | |
Fixes the CFString structure at 'addr': | |
- Patches the data pointer. | |
- Reads the string content. | |
- Renames the structure to "cf<content>" (limited to 20 characters, unique). | |
""" | |
# 1. Read the original data pointer. | |
data_field_addr = addr + OFFSET_DATA | |
orig_data_ptr = idc.get_qword(data_field_addr) | |
if orig_data_ptr is None: | |
print("Failed to read data pointer at 0x{:X}".format(data_field_addr)) | |
return | |
# 2. Calculate the corrected pointer. | |
corrected_ptr = orig_data_ptr - SUBTRACT_VAL + ADD_VAL | |
# 3. Patch the data field. | |
idc.patch_qword(data_field_addr, corrected_ptr) | |
# 4. Read the length field. | |
length_field_addr = addr + OFFSET_LENGTH | |
length = idc.get_qword(length_field_addr) | |
if length is None or length <= 0: | |
print("Invalid length at 0x{:X}".format(length_field_addr)) | |
string_content = "" | |
else: | |
# 5. Read the string bytes from the corrected pointer. | |
string_bytes = idc.get_bytes(corrected_ptr, length) | |
if string_bytes is None: | |
print("Unable to read bytes at 0x{:X}".format(corrected_ptr)) | |
string_content = "" | |
else: | |
try: | |
# Assume UTF-8 encoding (change if needed) | |
string_content = string_bytes.decode('utf-8', errors='replace') | |
except Exception as e: | |
print("Decoding error at 0x{:X}: {}".format(corrected_ptr, e)) | |
string_content = "" | |
# 6. Build a base name from the string content. | |
# Replace non-alphanumeric characters with underscores. | |
sanitized = re.sub(r'\W+', '_', string_content).strip('_') | |
if not sanitized: | |
sanitized = "_unknown" | |
new_name_base = "cf" + sanitized | |
# 7. Limit the name to MAX_NAME_LENGTH characters. | |
new_name_base = new_name_base[:MAX_NAME_LENGTH] | |
unique_new_name = unique_name(new_name_base, addr, MAX_NAME_LENGTH) | |
# 8. Rename the CFString instance. | |
idc.set_name(addr, unique_new_name, idc.SN_NOWARN) | |
print("CFString at 0x{:X}: data pointer 0x{:X} -> 0x{:X}, length 0x{:X}, renamed to '{}'".format( | |
addr, orig_data_ptr, corrected_ptr, length, unique_new_name)) | |
def main(): | |
""" | |
Process all __cfstring segments: | |
- Fix each CFString structure. | |
- Record modified address ranges. | |
- Trigger reanalysis on modified ranges to update cross-references. | |
""" | |
modified_ranges = [] | |
for seg_ea in idautils.Segments(): | |
seg_name = idc.get_segm_name(seg_ea) | |
if seg_name.startswith("__cfstring"): | |
print("Processing segment: {}".format(seg_name)) | |
seg_end = idc.get_segm_end(seg_ea) | |
ea = seg_ea | |
# Process each CFString structure (assuming size 0x20 bytes) | |
while ea < seg_end: | |
fix_cfstring(ea) | |
modified_ranges.append((ea, ea + 0x20)) | |
ea += 0x20 | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment