[{"data":1,"prerenderedAt":1789},["ShallowReactive",2],{"post:\u002Fwriting\u002Fsafe-runtime-rva-patching":3},{"id":4,"title":5,"body":6,"date":1781,"description":1782,"extension":1783,"meta":1784,"navigation":160,"path":1785,"seo":1786,"stem":1787,"__hash__":1788},"writing\u002Fwriting\u002Fsafe-runtime-rva-patching.md","Safe Runtime RVA Patching in Mach-O",{"type":7,"value":8,"toc":1764},"minimark",[9,26,29,46,49,54,69,88,91,95,107,113,174,177,249,252,255,259,262,270,273,301,308,310,314,317,550,561,575,582,591,609,707,717,775,786,793,798,801,830,839,846,856,1235,1246,1260,1291,1305,1307,1311,1314,1385,1391,1399,1402,1408,1411,1415,1418,1449,1463,1466,1468,1472,1484,1491,1555,1558,1588,1591,1595,1607,1610,1619,1622,1655,1671,1674,1727,1729,1733,1740,1757,1760],[10,11,12,13,21,22,25],"p",{},"You want to change behavior at a known RVA while a process is running—for\ninstrumentation, for studying how a piece of code behaves, or to make a fixed value\nconfigurable. The straightforward implementation (",[14,15,17],"tn",{"note":16},"The call that asks the kernel to make a page writable. It is the one that refuses.",[18,19,20],"code",{},"mach_vm_protect"," + ",[18,23,24],{},"memcpy",") works in\nsome debug, development, or otherwise unrestricted environments. Where signed-code page\nvalidation is enforced, modifying an executable page can terminate the process.",[10,27,28],{},"Adding a writable segment does not, by itself, make the instruction at the original RVA\nwritable or redirect execution through that segment. The robust design for the constrained\nenvironment discussed here has two distinct phases:",[30,31,32,40],"ol",{},[33,34,35,39],"li",{},[36,37,38],"strong",{},"Before signing",", rewrite each executable patch site and emit any required dispatch\ncode into a file-backed, executable segment.",[33,41,42,45],{},[36,43,44],{},"At runtime",", change only data in a separate writable segment. The prepared code reads\nthat state and selects the required behavior.",[10,47,48],{},"The writable pool is runtime state storage, not a substitute for the original code page.",[50,51,53],"h2",{"id":52},"scope","Scope",[10,55,56,57,60,61,64,65,68],{},"This article discusses a ",[36,58,59],{},"thin arm64 Mach-O",": one slice in one file, 64-bit,\nlittle-endian, and not encrypted (",[18,62,63],{},"LC_ENCRYPTION_INFO_64"," absent, or present with ",[18,66,67],{},"cryptid","\nclear). A fat binary is a container of slices and is not handled here—split it first, or\napply the surgery independently to each slice.",[10,70,71,72,75,76,79,80,83,84,87],{},"The load-command walk assumes ",[18,73,74],{},"mach_header_64",", ",[18,77,78],{},"segment_command_64",", and ",[18,81,82],{},"section_64",".\nThe 32-bit spelling and big-endian targets are different formats to edit. The resulting\nfile must be signed ",[36,85,86],{},"after"," all structural and executable changes, and the signature must\ndescribe the exact bytes the loader will map.",[10,89,90],{},"This is a layout pattern, not a complete general-purpose Mach-O rewriter. Production code\nmust validate every command size, range, alignment, integer addition, and file read or\nwrite before mutating the file.",[50,92,94],{"id":93},"why-the-runtime-write-fails","Why the runtime write fails",[10,96,97,98,102,103,106],{},"Two separate questions are often confused: ",[99,100,101],"em",{},"can"," I write to this page, and ",[99,104,105],{},"will the\nplatform accept the resulting page","?",[10,108,109,112],{},[18,110,111],{},"VM_PROT_COPY"," addresses the first question. It can allow a private writable mapping even\nwhen the segment did not originally declare write permission. It changes VM behavior; it\ndoes not bypass signed-code validation.",[114,115,121],"pre",{"className":116,"code":117,"filename":118,"language":119,"meta":120,"style":120},"language-c shiki shiki-themes one-light one-dark-pro","\u002F* This can be useful in an environment where direct code patching is permitted.\n   It is not a signed-code bypass. *\u002F\nkern_return_t kr = mach_vm_protect(mach_task_self(), page, span, FALSE,\n                                   VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);\nif (kr != KERN_SUCCESS) return false;\n\nmemcpy((void *)addr, bytes, len);\nsys_icache_invalidate((void *)addr, len);\n","patch.c","c","",[18,122,123,131,137,143,149,155,162,168],{"__ignoreMap":120},[124,125,128],"span",{"class":126,"line":127},"line",1,[124,129,130],{},"\u002F* This can be useful in an environment where direct code patching is permitted.\n",[124,132,134],{"class":126,"line":133},2,[124,135,136],{},"   It is not a signed-code bypass. *\u002F\n",[124,138,140],{"class":126,"line":139},3,[124,141,142],{},"kern_return_t kr = mach_vm_protect(mach_task_self(), page, span, FALSE,\n",[124,144,146],{"class":126,"line":145},4,[124,147,148],{},"                                   VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);\n",[124,150,152],{"class":126,"line":151},5,[124,153,154],{},"if (kr != KERN_SUCCESS) return false;\n",[124,156,158],{"class":126,"line":157},6,[124,159,161],{"emptyLinePlaceholder":160},true,"\n",[124,163,165],{"class":126,"line":164},7,[124,166,167],{},"memcpy((void *)addr, bytes, len);\n",[124,169,171],{"class":126,"line":170},8,[124,172,173],{},"sys_icache_invalidate((void *)addr, len);\n",[10,175,176],{},"Where executable-page validation is enforced, changing bytes covered by the code\nsignature can cause termination even if the VM write itself succeeded. Debugger state,\nspecial platform facilities, JIT entitlements, and jailbroken environments can change the\nconstraints; this article assumes none of those exceptions is available.",[178,179,182],"div",{"className":180},[181],"table-scroll",[183,184,185,198],"table",{},[186,187,188],"thead",{},[189,190,191,195],"tr",{},[192,193,194],"th",{},"Symptom",[192,196,197],{},"Likely meaning",[199,200,201,213,227,241],"tbody",{},[189,202,203,210],{},[204,205,206,209],"td",{},[18,207,208],{},"EXC_BAD_ACCESS"," at the target address",[204,211,212],{},"The page was not writable; the protection call failed or covered the wrong range",[189,214,215,224],{},[204,216,217,220,221],{},[18,218,219],{},"SIGKILL",", termination reason ",[18,222,223],{},"Namespace CODESIGNING",[204,225,226],{},"The platform rejected a modified sealed page",[189,228,229,238],{},[204,230,231,234,235],{},[18,232,233],{},"EXC_BREAKPOINT",", PAC-related ",[18,236,237],{},"brk",[204,239,240],{},"An unauthenticated or incorrectly diversified pointer reached PAC-protected control flow",[189,242,243,246],{},[204,244,245],{},"Delayed crash elsewhere with a normal stack",[204,247,248],{},"Often an application-level integrity check rather than the kernel",[10,250,251],{},"In the environment considered here, the fix is architectural: prepare executable behavior\noffline and keep runtime mutation in writable data.",[253,254],"hr",{},[50,256,258],{"id":257},"the-closed-loop-design","The closed-loop design",[10,260,261],{},"The complete path is:",[263,264],"svg-canvas",{"alt":265,"caption":266,"height":267,"src":268,"width":269},"Original code at the RVA is rewritten before signing to branch into a signed read\u002Fexecute dispatch stub; the stub reads the RW HookSlot table in __HOOK_DATA, and at runtime only writes of state into that slot happen.","The complete path: the executable half is written before signing; the runtime writes only the slot.",380,"hook-flow.svg",980,[10,271,272],{},"For every prepared site, the offline tool must do more than allocate space:",[274,275,276,279,282,289,295,298],"ul",{},[33,277,278],{},"identify a safe overwrite span at the original RVA;",[33,280,281],{},"relocate any displaced instructions whose PC-relative semantics would otherwise change;",[33,283,284,285,288],{},"emit a dispatch stub into ",[18,286,287],{},"__HOOK_CODE",";",[33,290,291,292,288],{},"create a corresponding slot in ",[18,293,294],{},"__HOOK_DATA",[33,296,297],{},"rewrite the original site to reach the stub; and",[33,299,300],{},"sign the finished image.",[10,302,303,304,307],{},"The exact stub is application-specific. It might branch on ",[18,305,306],{},"HookSlot.state",", load a\nreplacement constant, call a prepared handler, or select one of several prebuilt RX code\npaths. What matters is that all instructions are present before signing. Runtime code only\nchanges the slot.",[253,309],{},[50,311,313],{"id":312},"separate-executable-code-from-mutable-state","Separate executable code from mutable state",[10,315,316],{},"Use two segments with deliberately different protections:",[114,318,321],{"className":116,"code":319,"filename":320,"language":119,"meta":120,"style":120},"struct segment_command_64 hook_code_seg = {\n    .cmd      = LC_SEGMENT_64,\n    .cmdsize  = sizeof(struct segment_command_64) + sizeof(struct section_64),\n    .segname  = \"__HOOK_CODE\",\n    .vmaddr   = 0,          \u002F* assigned after the existing VM ranges *\u002F\n    .vmsize   = CODE_SIZE,\n    .fileoff  = 0,          \u002F* assigned before relocated __LINKEDIT *\u002F\n    .filesize = CODE_SIZE,\n    .maxprot  = VM_PROT_READ | VM_PROT_EXECUTE,\n    .initprot = VM_PROT_READ | VM_PROT_EXECUTE,\n    .nsects   = 1,\n};\n\nstruct section_64 hook_code_sec = {\n    .sectname = \"__hook_code\",\n    .segname  = \"__HOOK_CODE\",\n    .size     = CODE_SIZE,\n    .align    = 2, \u002F* 2^2 = 4-byte arm64 instruction alignment *\u002F\n    .flags    = S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,\n};\n\nstruct segment_command_64 hook_data_seg = {\n    .cmd      = LC_SEGMENT_64,\n    .cmdsize  = sizeof(struct segment_command_64) + sizeof(struct section_64),\n    .segname  = \"__HOOK_DATA\",\n    .vmaddr   = 0,\n    .vmsize   = DATA_SIZE,\n    .fileoff  = 0,\n    .filesize = DATA_SIZE,\n    .maxprot  = VM_PROT_READ | VM_PROT_WRITE,\n    .initprot = VM_PROT_READ | VM_PROT_WRITE,\n    .nsects   = 1,\n};\n\nstruct section_64 hook_data_sec = {\n    .sectname = \"__hook_data\",\n    .segname  = \"__HOOK_DATA\",\n    .size     = DATA_SIZE,\n    .align    = 3, \u002F* 2^3 = 8-byte alignment; choose for the actual slot ABI *\u002F\n    .flags    = S_REGULAR,\n};\n","add_hook_segments.c",[18,322,323,328,333,338,343,348,353,358,363,369,375,381,387,392,398,404,409,415,421,427,432,437,443,448,453,459,465,471,477,483,489,495,500,505,510,516,522,527,533,539,545],{"__ignoreMap":120},[124,324,325],{"class":126,"line":127},[124,326,327],{},"struct segment_command_64 hook_code_seg = {\n",[124,329,330],{"class":126,"line":133},[124,331,332],{},"    .cmd      = LC_SEGMENT_64,\n",[124,334,335],{"class":126,"line":139},[124,336,337],{},"    .cmdsize  = sizeof(struct segment_command_64) + sizeof(struct section_64),\n",[124,339,340],{"class":126,"line":145},[124,341,342],{},"    .segname  = \"__HOOK_CODE\",\n",[124,344,345],{"class":126,"line":151},[124,346,347],{},"    .vmaddr   = 0,          \u002F* assigned after the existing VM ranges *\u002F\n",[124,349,350],{"class":126,"line":157},[124,351,352],{},"    .vmsize   = CODE_SIZE,\n",[124,354,355],{"class":126,"line":164},[124,356,357],{},"    .fileoff  = 0,          \u002F* assigned before relocated __LINKEDIT *\u002F\n",[124,359,360],{"class":126,"line":170},[124,361,362],{},"    .filesize = CODE_SIZE,\n",[124,364,366],{"class":126,"line":365},9,[124,367,368],{},"    .maxprot  = VM_PROT_READ | VM_PROT_EXECUTE,\n",[124,370,372],{"class":126,"line":371},10,[124,373,374],{},"    .initprot = VM_PROT_READ | VM_PROT_EXECUTE,\n",[124,376,378],{"class":126,"line":377},11,[124,379,380],{},"    .nsects   = 1,\n",[124,382,384],{"class":126,"line":383},12,[124,385,386],{},"};\n",[124,388,390],{"class":126,"line":389},13,[124,391,161],{"emptyLinePlaceholder":160},[124,393,395],{"class":126,"line":394},14,[124,396,397],{},"struct section_64 hook_code_sec = {\n",[124,399,401],{"class":126,"line":400},15,[124,402,403],{},"    .sectname = \"__hook_code\",\n",[124,405,407],{"class":126,"line":406},16,[124,408,342],{},[124,410,412],{"class":126,"line":411},17,[124,413,414],{},"    .size     = CODE_SIZE,\n",[124,416,418],{"class":126,"line":417},18,[124,419,420],{},"    .align    = 2, \u002F* 2^2 = 4-byte arm64 instruction alignment *\u002F\n",[124,422,424],{"class":126,"line":423},19,[124,425,426],{},"    .flags    = S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,\n",[124,428,430],{"class":126,"line":429},20,[124,431,386],{},[124,433,435],{"class":126,"line":434},21,[124,436,161],{"emptyLinePlaceholder":160},[124,438,440],{"class":126,"line":439},22,[124,441,442],{},"struct segment_command_64 hook_data_seg = {\n",[124,444,446],{"class":126,"line":445},23,[124,447,332],{},[124,449,451],{"class":126,"line":450},24,[124,452,337],{},[124,454,456],{"class":126,"line":455},25,[124,457,458],{},"    .segname  = \"__HOOK_DATA\",\n",[124,460,462],{"class":126,"line":461},26,[124,463,464],{},"    .vmaddr   = 0,\n",[124,466,468],{"class":126,"line":467},27,[124,469,470],{},"    .vmsize   = DATA_SIZE,\n",[124,472,474],{"class":126,"line":473},28,[124,475,476],{},"    .fileoff  = 0,\n",[124,478,480],{"class":126,"line":479},29,[124,481,482],{},"    .filesize = DATA_SIZE,\n",[124,484,486],{"class":126,"line":485},30,[124,487,488],{},"    .maxprot  = VM_PROT_READ | VM_PROT_WRITE,\n",[124,490,492],{"class":126,"line":491},31,[124,493,494],{},"    .initprot = VM_PROT_READ | VM_PROT_WRITE,\n",[124,496,498],{"class":126,"line":497},32,[124,499,380],{},[124,501,503],{"class":126,"line":502},33,[124,504,386],{},[124,506,508],{"class":126,"line":507},34,[124,509,161],{"emptyLinePlaceholder":160},[124,511,513],{"class":126,"line":512},35,[124,514,515],{},"struct section_64 hook_data_sec = {\n",[124,517,519],{"class":126,"line":518},36,[124,520,521],{},"    .sectname = \"__hook_data\",\n",[124,523,525],{"class":126,"line":524},37,[124,526,458],{},[124,528,530],{"class":126,"line":529},38,[124,531,532],{},"    .size     = DATA_SIZE,\n",[124,534,536],{"class":126,"line":535},39,[124,537,538],{},"    .align    = 3, \u002F* 2^3 = 8-byte alignment; choose for the actual slot ABI *\u002F\n",[124,540,542],{"class":126,"line":541},40,[124,543,544],{},"    .flags    = S_REGULAR,\n",[124,546,548],{"class":126,"line":547},41,[124,549,386],{},[10,551,552,553,556,557,560],{},"Fill each section's ",[18,554,555],{},"addr"," and ",[18,558,559],{},"offset"," after assigning its segment. Both sections are\nfile-backed in this design: the RX section contains the finished stubs, and the RW section\ncontains the initial slot table and pristine state. Do not make the pool RWX merely for\nconvenience. Runtime-generated executable code is a different design with different\nplatform requirements.",[10,562,563,564,567,568,570,571,574],{},"The new commands increase ",[18,565,566],{},"ncmds"," by two, not four: each ",[18,569,82],{}," is embedded in its\nown ",[18,572,573],{},"LC_SEGMENT_64"," command.",[50,576,578,579],{"id":577},"lay-out-the-file-without-corrupting-__linkedit","Lay out the file without corrupting ",[18,580,581],{},"__LINKEDIT",[10,583,584,585,587,588,590],{},"In the layout assumed by this example, ",[18,586,581],{}," is the final file-backed segment. The\nnew segments are inserted immediately before a relocated copy of it. This is a chosen\nlayout, not a Mach-O rule that ",[18,589,581],{}," must always be the final command or segment.",[10,592,593,594,597,598,556,601,604,605,608],{},"Do not hard-code a universal ",[18,595,596],{},"PAGE"," value. Infer and validate compatible file and VM\nalignment from the target image, then use those values consistently. The alignment used\nfor ",[18,599,600],{},"fileoff",[18,602,603],{},"vmaddr"," need not be described by the section's ",[18,606,607],{},"align"," field.",[114,610,613],{"className":116,"code":611,"filename":612,"language":119,"meta":120,"style":120},"const uint64_t old_le_fileoff = linkedit_seg->fileoff;\nconst uint64_t old_le_filesize = linkedit_seg->filesize;\n\nhook_code_seg.fileoff = align_up(original_file_size, file_alignment);\nhook_data_seg.fileoff = align_up(hook_code_seg.fileoff + hook_code_seg.filesize,\n                                 file_alignment);\nconst uint64_t new_le_fileoff =\n    align_up(hook_data_seg.fileoff + hook_data_seg.filesize, file_alignment);\n\nhook_code_seg.vmaddr = align_up(existing_vm_end, vm_alignment);\nhook_data_seg.vmaddr = align_up(hook_code_seg.vmaddr + hook_code_seg.vmsize,\n                                vm_alignment);\nconst uint64_t new_le_vmaddr =\n    align_up(hook_data_seg.vmaddr + hook_data_seg.vmsize, vm_alignment);\n\nhook_code_sec.addr   = hook_code_seg.vmaddr;\nhook_code_sec.offset = (uint32_t)hook_code_seg.fileoff;\nhook_data_sec.addr   = hook_data_seg.vmaddr;\nhook_data_sec.offset = (uint32_t)hook_data_seg.fileoff;\n","layout.c",[18,614,615,620,625,629,634,639,644,649,654,658,663,668,673,678,683,687,692,697,702],{"__ignoreMap":120},[124,616,617],{"class":126,"line":127},[124,618,619],{},"const uint64_t old_le_fileoff = linkedit_seg->fileoff;\n",[124,621,622],{"class":126,"line":133},[124,623,624],{},"const uint64_t old_le_filesize = linkedit_seg->filesize;\n",[124,626,627],{"class":126,"line":139},[124,628,161],{"emptyLinePlaceholder":160},[124,630,631],{"class":126,"line":145},[124,632,633],{},"hook_code_seg.fileoff = align_up(original_file_size, file_alignment);\n",[124,635,636],{"class":126,"line":151},[124,637,638],{},"hook_data_seg.fileoff = align_up(hook_code_seg.fileoff + hook_code_seg.filesize,\n",[124,640,641],{"class":126,"line":157},[124,642,643],{},"                                 file_alignment);\n",[124,645,646],{"class":126,"line":164},[124,647,648],{},"const uint64_t new_le_fileoff =\n",[124,650,651],{"class":126,"line":170},[124,652,653],{},"    align_up(hook_data_seg.fileoff + hook_data_seg.filesize, file_alignment);\n",[124,655,656],{"class":126,"line":365},[124,657,161],{"emptyLinePlaceholder":160},[124,659,660],{"class":126,"line":371},[124,661,662],{},"hook_code_seg.vmaddr = align_up(existing_vm_end, vm_alignment);\n",[124,664,665],{"class":126,"line":377},[124,666,667],{},"hook_data_seg.vmaddr = align_up(hook_code_seg.vmaddr + hook_code_seg.vmsize,\n",[124,669,670],{"class":126,"line":383},[124,671,672],{},"                                vm_alignment);\n",[124,674,675],{"class":126,"line":389},[124,676,677],{},"const uint64_t new_le_vmaddr =\n",[124,679,680],{"class":126,"line":394},[124,681,682],{},"    align_up(hook_data_seg.vmaddr + hook_data_seg.vmsize, vm_alignment);\n",[124,684,685],{"class":126,"line":400},[124,686,161],{"emptyLinePlaceholder":160},[124,688,689],{"class":126,"line":406},[124,690,691],{},"hook_code_sec.addr   = hook_code_seg.vmaddr;\n",[124,693,694],{"class":126,"line":411},[124,695,696],{},"hook_code_sec.offset = (uint32_t)hook_code_seg.fileoff;\n",[124,698,699],{"class":126,"line":417},[124,700,701],{},"hook_data_sec.addr   = hook_data_seg.vmaddr;\n",[124,703,704],{"class":126,"line":423},[124,705,706],{},"hook_data_sec.offset = (uint32_t)hook_data_seg.fileoff;\n",[10,708,709,710,712,713,716],{},"Before changing offsets, copy the complete old ",[18,711,581],{}," file range to its new location.\nChanging ",[18,714,715],{},"segment_command_64.fileoff"," relocates only metadata; it does not move a single\nbyte.",[114,718,721],{"className":116,"code":719,"filename":720,"language":119,"meta":120,"style":120},"uint8_t *le = malloc((size_t)old_le_filesize);\nif (!le) fail(\"allocation failed\");\n\nread_exact(fd, le, (size_t)old_le_filesize, old_le_fileoff);\nwrite_exact(fd, le, (size_t)old_le_filesize, new_le_fileoff);\nfree(le);\n\nlinkedit_seg->fileoff = new_le_fileoff;\nlinkedit_seg->vmaddr  = new_le_vmaddr;\n\nconst uint64_t linkedit_delta = new_le_fileoff - old_le_fileoff;\n","move_linkedit.c",[18,722,723,728,733,737,742,747,752,756,761,766,770],{"__ignoreMap":120},[124,724,725],{"class":126,"line":127},[124,726,727],{},"uint8_t *le = malloc((size_t)old_le_filesize);\n",[124,729,730],{"class":126,"line":133},[124,731,732],{},"if (!le) fail(\"allocation failed\");\n",[124,734,735],{"class":126,"line":139},[124,736,161],{"emptyLinePlaceholder":160},[124,738,739],{"class":126,"line":145},[124,740,741],{},"read_exact(fd, le, (size_t)old_le_filesize, old_le_fileoff);\n",[124,743,744],{"class":126,"line":151},[124,745,746],{},"write_exact(fd, le, (size_t)old_le_filesize, new_le_fileoff);\n",[124,748,749],{"class":126,"line":157},[124,750,751],{},"free(le);\n",[124,753,754],{"class":126,"line":164},[124,755,161],{"emptyLinePlaceholder":160},[124,757,758],{"class":126,"line":170},[124,759,760],{},"linkedit_seg->fileoff = new_le_fileoff;\n",[124,762,763],{"class":126,"line":365},[124,764,765],{},"linkedit_seg->vmaddr  = new_le_vmaddr;\n",[124,767,768],{"class":126,"line":371},[124,769,161],{"emptyLinePlaceholder":160},[124,771,772],{"class":126,"line":377},[124,773,774],{},"const uint64_t linkedit_delta = new_le_fileoff - old_le_fileoff;\n",[10,776,777,778,781,782,785],{},"The destination must already be inside a safely extended file, and any remapping or buffer\ngrowth can invalidate pointers into the old mapping. Re-resolve ",[18,779,780],{},"header",", load commands,\nand ",[18,783,784],{},"linkedit_seg"," after such an operation.",[263,787],{"alt":788,"caption":789,"height":790,"src":791,"width":792},"The Mach-O file before and after the surgery. Before: a header; the load commands, ending with __LINKEDIT's; unused slack; then the __TEXT, __DATA and __LINKEDIT segments. After: two more load commands, for __HOOK_CODE and __HOOK_DATA, sit inside what was the slack; the two new segments — the signed code and the writable state — sit before __LINKEDIT, which has moved to the new end of the file.","The file, before and after. The two new load commands take space the header already reserved; the prepared code and the writable state go at the end and __LINKEDIT moves past them.",360,"macho-layout.svg",1120,[794,795,797],"h3",{"id":796},"the-load-command-block-still-needs-slack","The load-command block still needs slack",[10,799,800],{},"The two new segment commands go into the gap between the end of the load-command block and\nthe first file-backed section. If the gap is too small, copying more commands overwrites\nsection data.",[114,802,804],{"className":116,"code":803,"language":119,"meta":120,"style":120},"uint64_t cmds_end = sizeof(struct mach_header_64) + header->sizeofcmds;\nuint64_t slack = first_file_backed_section_offset - cmds_end;\n\nif (slack \u003C hook_code_seg.cmdsize + hook_data_seg.cmdsize)\n    fail(\"insufficient header slack\");\n",[18,805,806,811,816,820,825],{"__ignoreMap":120},[124,807,808],{"class":126,"line":127},[124,809,810],{},"uint64_t cmds_end = sizeof(struct mach_header_64) + header->sizeofcmds;\n",[124,812,813],{"class":126,"line":133},[124,814,815],{},"uint64_t slack = first_file_backed_section_offset - cmds_end;\n",[124,817,818],{"class":126,"line":139},[124,819,161],{"emptyLinePlaceholder":160},[124,821,822],{"class":126,"line":145},[124,823,824],{},"if (slack \u003C hook_code_seg.cmdsize + hook_data_seg.cmdsize)\n",[124,826,827],{"class":126,"line":151},[124,828,829],{},"    fail(\"insufficient header slack\");\n",[10,831,832,833,556,835,838],{},"The insertion code must preserve the existing command tail, place the two new commands at\nthe intended position, then update ",[18,834,566],{},[18,836,837],{},"sizeofcmds",". Any pointer into the command\nbuffer becomes stale after moving that buffer; recompute it before writing fields.",[794,840,842,843,845],{"id":841},"fix-each-__linkedit-reference-using-its-real-structure","Fix each ",[18,844,581],{}," reference using its real structure",[10,847,848,849,851,852,855],{},"Every file offset that points into the old ",[18,850,581],{}," range must follow the bytes to the\nnew range. Do not cast unrelated commands to ",[18,853,854],{},"linkedit_data_command","; their layouts differ.\nAlso do not shift a field merely because it is nonzero—first verify that it falls inside the\nold range.",[114,857,860],{"className":116,"code":858,"filename":859,"language":119,"meta":120,"style":120},"static void shift_linkedit_offset(uint32_t *off,\n                                  uint64_t old_start,\n                                  uint64_t old_size,\n                                  uint64_t delta)\n{\n    uint64_t value = *off;\n    uint64_t old_end;\n    if (__builtin_add_overflow(old_start, old_size, &old_end))\n        fail(\"invalid __LINKEDIT range\");\n\n    if (value >= old_start && value \u003C old_end) {\n        uint64_t shifted;\n        if (__builtin_add_overflow(value, delta, &shifted) || shifted > UINT32_MAX)\n            fail(\"shifted offset does not fit\");\n        *off = (uint32_t)shifted;\n    }\n}\n\nfor (struct load_command *lc = first_cmd;\n     (uint8_t *)lc \u003C cmds_end_ptr;\n     lc = next_validated_command(lc, cmds_end_ptr)) {\n\n    switch (lc->cmd) {\n    case LC_SYMTAB: {\n        struct symtab_command *c = (void *)lc;\n        shift_linkedit_offset(&c->symoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->stroff, old_le_fileoff, old_le_filesize, linkedit_delta);\n        break;\n    }\n\n    case LC_DYSYMTAB: {\n        struct dysymtab_command *c = (void *)lc;\n        shift_linkedit_offset(&c->tocoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->modtaboff, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->extrefsymoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->indirectsymoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->extreloff, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->locreloff, old_le_fileoff, old_le_filesize, linkedit_delta);\n        break;\n    }\n\n    case LC_DYLD_INFO:\n    case LC_DYLD_INFO_ONLY: {\n        struct dyld_info_command *c = (void *)lc;\n        shift_linkedit_offset(&c->rebase_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->bind_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->weak_bind_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->lazy_bind_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n        shift_linkedit_offset(&c->export_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n        break;\n    }\n\n    case LC_DYLD_CHAINED_FIXUPS:\n    case LC_DYLD_EXPORTS_TRIE:\n    case LC_FUNCTION_STARTS:\n    case LC_DATA_IN_CODE:\n    case LC_SEGMENT_SPLIT_INFO:\n    case LC_DYLIB_CODE_SIGN_DRS:\n    case LC_LINKER_OPTIMIZATION_HINT:\n    case LC_CODE_SIGNATURE: {\n        struct linkedit_data_command *c = (void *)lc;\n        shift_linkedit_offset(&c->dataoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n        break;\n    }\n\n    case LC_TWOLEVEL_HINTS: {\n        struct twolevel_hints_command *c = (void *)lc;\n        shift_linkedit_offset(&c->offset, old_le_fileoff, old_le_filesize, linkedit_delta);\n        break;\n    }\n    }\n}\n","fix_load_commands.c",[18,861,862,867,872,877,882,887,892,897,902,907,911,916,921,926,931,936,941,946,950,955,960,965,969,974,979,984,989,994,999,1003,1007,1012,1017,1022,1027,1032,1037,1042,1047,1051,1055,1059,1065,1071,1077,1083,1089,1095,1101,1107,1112,1117,1122,1128,1134,1140,1146,1152,1158,1164,1170,1176,1182,1187,1192,1197,1203,1209,1215,1220,1225,1230],{"__ignoreMap":120},[124,863,864],{"class":126,"line":127},[124,865,866],{},"static void shift_linkedit_offset(uint32_t *off,\n",[124,868,869],{"class":126,"line":133},[124,870,871],{},"                                  uint64_t old_start,\n",[124,873,874],{"class":126,"line":139},[124,875,876],{},"                                  uint64_t old_size,\n",[124,878,879],{"class":126,"line":145},[124,880,881],{},"                                  uint64_t delta)\n",[124,883,884],{"class":126,"line":151},[124,885,886],{},"{\n",[124,888,889],{"class":126,"line":157},[124,890,891],{},"    uint64_t value = *off;\n",[124,893,894],{"class":126,"line":164},[124,895,896],{},"    uint64_t old_end;\n",[124,898,899],{"class":126,"line":170},[124,900,901],{},"    if (__builtin_add_overflow(old_start, old_size, &old_end))\n",[124,903,904],{"class":126,"line":365},[124,905,906],{},"        fail(\"invalid __LINKEDIT range\");\n",[124,908,909],{"class":126,"line":371},[124,910,161],{"emptyLinePlaceholder":160},[124,912,913],{"class":126,"line":377},[124,914,915],{},"    if (value >= old_start && value \u003C old_end) {\n",[124,917,918],{"class":126,"line":383},[124,919,920],{},"        uint64_t shifted;\n",[124,922,923],{"class":126,"line":389},[124,924,925],{},"        if (__builtin_add_overflow(value, delta, &shifted) || shifted > UINT32_MAX)\n",[124,927,928],{"class":126,"line":394},[124,929,930],{},"            fail(\"shifted offset does not fit\");\n",[124,932,933],{"class":126,"line":400},[124,934,935],{},"        *off = (uint32_t)shifted;\n",[124,937,938],{"class":126,"line":406},[124,939,940],{},"    }\n",[124,942,943],{"class":126,"line":411},[124,944,945],{},"}\n",[124,947,948],{"class":126,"line":417},[124,949,161],{"emptyLinePlaceholder":160},[124,951,952],{"class":126,"line":423},[124,953,954],{},"for (struct load_command *lc = first_cmd;\n",[124,956,957],{"class":126,"line":429},[124,958,959],{},"     (uint8_t *)lc \u003C cmds_end_ptr;\n",[124,961,962],{"class":126,"line":434},[124,963,964],{},"     lc = next_validated_command(lc, cmds_end_ptr)) {\n",[124,966,967],{"class":126,"line":439},[124,968,161],{"emptyLinePlaceholder":160},[124,970,971],{"class":126,"line":445},[124,972,973],{},"    switch (lc->cmd) {\n",[124,975,976],{"class":126,"line":450},[124,977,978],{},"    case LC_SYMTAB: {\n",[124,980,981],{"class":126,"line":455},[124,982,983],{},"        struct symtab_command *c = (void *)lc;\n",[124,985,986],{"class":126,"line":461},[124,987,988],{},"        shift_linkedit_offset(&c->symoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,990,991],{"class":126,"line":467},[124,992,993],{},"        shift_linkedit_offset(&c->stroff, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,995,996],{"class":126,"line":473},[124,997,998],{},"        break;\n",[124,1000,1001],{"class":126,"line":479},[124,1002,940],{},[124,1004,1005],{"class":126,"line":485},[124,1006,161],{"emptyLinePlaceholder":160},[124,1008,1009],{"class":126,"line":491},[124,1010,1011],{},"    case LC_DYSYMTAB: {\n",[124,1013,1014],{"class":126,"line":497},[124,1015,1016],{},"        struct dysymtab_command *c = (void *)lc;\n",[124,1018,1019],{"class":126,"line":502},[124,1020,1021],{},"        shift_linkedit_offset(&c->tocoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1023,1024],{"class":126,"line":507},[124,1025,1026],{},"        shift_linkedit_offset(&c->modtaboff, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1028,1029],{"class":126,"line":512},[124,1030,1031],{},"        shift_linkedit_offset(&c->extrefsymoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1033,1034],{"class":126,"line":518},[124,1035,1036],{},"        shift_linkedit_offset(&c->indirectsymoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1038,1039],{"class":126,"line":524},[124,1040,1041],{},"        shift_linkedit_offset(&c->extreloff, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1043,1044],{"class":126,"line":529},[124,1045,1046],{},"        shift_linkedit_offset(&c->locreloff, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1048,1049],{"class":126,"line":535},[124,1050,998],{},[124,1052,1053],{"class":126,"line":541},[124,1054,940],{},[124,1056,1057],{"class":126,"line":547},[124,1058,161],{"emptyLinePlaceholder":160},[124,1060,1062],{"class":126,"line":1061},42,[124,1063,1064],{},"    case LC_DYLD_INFO:\n",[124,1066,1068],{"class":126,"line":1067},43,[124,1069,1070],{},"    case LC_DYLD_INFO_ONLY: {\n",[124,1072,1074],{"class":126,"line":1073},44,[124,1075,1076],{},"        struct dyld_info_command *c = (void *)lc;\n",[124,1078,1080],{"class":126,"line":1079},45,[124,1081,1082],{},"        shift_linkedit_offset(&c->rebase_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1084,1086],{"class":126,"line":1085},46,[124,1087,1088],{},"        shift_linkedit_offset(&c->bind_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1090,1092],{"class":126,"line":1091},47,[124,1093,1094],{},"        shift_linkedit_offset(&c->weak_bind_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1096,1098],{"class":126,"line":1097},48,[124,1099,1100],{},"        shift_linkedit_offset(&c->lazy_bind_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1102,1104],{"class":126,"line":1103},49,[124,1105,1106],{},"        shift_linkedit_offset(&c->export_off, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1108,1110],{"class":126,"line":1109},50,[124,1111,998],{},[124,1113,1115],{"class":126,"line":1114},51,[124,1116,940],{},[124,1118,1120],{"class":126,"line":1119},52,[124,1121,161],{"emptyLinePlaceholder":160},[124,1123,1125],{"class":126,"line":1124},53,[124,1126,1127],{},"    case LC_DYLD_CHAINED_FIXUPS:\n",[124,1129,1131],{"class":126,"line":1130},54,[124,1132,1133],{},"    case LC_DYLD_EXPORTS_TRIE:\n",[124,1135,1137],{"class":126,"line":1136},55,[124,1138,1139],{},"    case LC_FUNCTION_STARTS:\n",[124,1141,1143],{"class":126,"line":1142},56,[124,1144,1145],{},"    case LC_DATA_IN_CODE:\n",[124,1147,1149],{"class":126,"line":1148},57,[124,1150,1151],{},"    case LC_SEGMENT_SPLIT_INFO:\n",[124,1153,1155],{"class":126,"line":1154},58,[124,1156,1157],{},"    case LC_DYLIB_CODE_SIGN_DRS:\n",[124,1159,1161],{"class":126,"line":1160},59,[124,1162,1163],{},"    case LC_LINKER_OPTIMIZATION_HINT:\n",[124,1165,1167],{"class":126,"line":1166},60,[124,1168,1169],{},"    case LC_CODE_SIGNATURE: {\n",[124,1171,1173],{"class":126,"line":1172},61,[124,1174,1175],{},"        struct linkedit_data_command *c = (void *)lc;\n",[124,1177,1179],{"class":126,"line":1178},62,[124,1180,1181],{},"        shift_linkedit_offset(&c->dataoff, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1183,1185],{"class":126,"line":1184},63,[124,1186,998],{},[124,1188,1190],{"class":126,"line":1189},64,[124,1191,940],{},[124,1193,1195],{"class":126,"line":1194},65,[124,1196,161],{"emptyLinePlaceholder":160},[124,1198,1200],{"class":126,"line":1199},66,[124,1201,1202],{},"    case LC_TWOLEVEL_HINTS: {\n",[124,1204,1206],{"class":126,"line":1205},67,[124,1207,1208],{},"        struct twolevel_hints_command *c = (void *)lc;\n",[124,1210,1212],{"class":126,"line":1211},68,[124,1213,1214],{},"        shift_linkedit_offset(&c->offset, old_le_fileoff, old_le_filesize, linkedit_delta);\n",[124,1216,1218],{"class":126,"line":1217},69,[124,1219,998],{},[124,1221,1223],{"class":126,"line":1222},70,[124,1224,940],{},[124,1226,1228],{"class":126,"line":1227},71,[124,1229,940],{},[124,1231,1233],{"class":126,"line":1232},72,[124,1234,945],{},[10,1236,1237,1238,1241,1242,1245],{},"That list covers common final-image commands, but the correct rule is structural: inspect\nevery command and section in the actual input, and shift every file offset that refers to\nthe moved range. For example, a section's ",[18,1239,1240],{},"reloff"," also needs attention if it points there.\nFields such as ",[18,1243,1244],{},"LC_MAIN.entryoff"," use a different base and must not be shifted merely because\nthey look like offsets.",[10,1247,1248,1249,556,1252,1255,1256,1259],{},"The code-signing blob is regenerated rather than patched in place. The final signing step\nmust also leave ",[18,1250,1251],{},"LC_CODE_SIGNATURE",[18,1253,1254],{},"__LINKEDIT.filesize","\u002F",[18,1257,1258],{},"vmsize"," consistent with the\nnew blob. Validate the finished image with independent Mach-O tooling before installation.",[114,1261,1265],{"className":1262,"code":1263,"language":1264,"meta":120,"style":120},"language-bash shiki shiki-themes one-light one-dark-pro","ldid -S\u003CTarget.entitlements> Payload\u002FApp.app\u002FApp\n","bash",[18,1266,1267],{"__ignoreMap":120},[124,1268,1269,1273,1277,1281,1284,1287],{"class":126,"line":127},[124,1270,1272],{"class":1271},"sAdtL","ldid",[124,1274,1276],{"class":1275},"sAGMh"," -S",[124,1278,1280],{"class":1279},"s5ixo","\u003C",[124,1282,1283],{"class":1275},"Target.entitlements",[124,1285,1286],{"class":1279},">",[124,1288,1290],{"class":1289},"sDhpE"," Payload\u002FApp.app\u002FApp\n",[1292,1293],"repo",{"align":1294,"commit":1295,"description":1296,"forks":1297,"full-name":1298,"issues":1299,"language":1300,"license":1301,"name":1272,"pushed":1302,"stars":1303,"url":1304},"left","af86971","Link Identity Editor. Put real or fake signatures in a Mach-O.","64","ProcursusTeam\u002Fldid","2","C++","AGPL-3.0","2026-07-24","278","https:\u002F\u002Fgithub.com\u002FProcursusTeam\u002Fldid",[253,1306],{},[50,1308,1310],{"id":1309},"preparing-a-site-not-just-a-pool","Preparing a site, not just a pool",[10,1312,1313],{},"A useful offline API makes the distinction explicit:",[114,1315,1319],{"className":1316,"code":1317,"language":1318,"meta":120,"style":120},"language-objc shiki shiki-themes one-light one-dark-pro","\u002F* Offline, before signing. Creates the slot and executable dispatch path, relocates the\n   overwritten instructions, and rewrites this specific site to enter the prepared path.\n   Returns `nil` when the site was prepared, or a message saying why it refused (no header\n   slack, a fat file, and so on) — the example's stand-in for an `NSError` out-parameter. *\u002F\nNSString *PrepareSite(const char *machoPath,\n                      uint64_t targetRVA,\n                      const PatchVariant *variants,\n                      size_t variantCount);\n\n\u002F* Runtime. These functions update only the already-prepared RW slot. *\u002F\nBOOL SetMode(uint64_t targetRVA, uint32_t mode);\nBOOL SetPayload(uint64_t targetRVA, const uint8_t *bytes, size_t len);\nBOOL RestorePreparedState(uint64_t targetRVA);\n","objc",[18,1320,1321,1326,1331,1336,1341,1346,1351,1356,1361,1365,1370,1375,1380],{"__ignoreMap":120},[124,1322,1323],{"class":126,"line":127},[124,1324,1325],{},"\u002F* Offline, before signing. Creates the slot and executable dispatch path, relocates the\n",[124,1327,1328],{"class":126,"line":133},[124,1329,1330],{},"   overwritten instructions, and rewrites this specific site to enter the prepared path.\n",[124,1332,1333],{"class":126,"line":139},[124,1334,1335],{},"   Returns `nil` when the site was prepared, or a message saying why it refused (no header\n",[124,1337,1338],{"class":126,"line":145},[124,1339,1340],{},"   slack, a fat file, and so on) — the example's stand-in for an `NSError` out-parameter. *\u002F\n",[124,1342,1343],{"class":126,"line":151},[124,1344,1345],{},"NSString *PrepareSite(const char *machoPath,\n",[124,1347,1348],{"class":126,"line":157},[124,1349,1350],{},"                      uint64_t targetRVA,\n",[124,1352,1353],{"class":126,"line":164},[124,1354,1355],{},"                      const PatchVariant *variants,\n",[124,1357,1358],{"class":126,"line":170},[124,1359,1360],{},"                      size_t variantCount);\n",[124,1362,1363],{"class":126,"line":365},[124,1364,161],{"emptyLinePlaceholder":160},[124,1366,1367],{"class":126,"line":371},[124,1368,1369],{},"\u002F* Runtime. These functions update only the already-prepared RW slot. *\u002F\n",[124,1371,1372],{"class":126,"line":377},[124,1373,1374],{},"BOOL SetMode(uint64_t targetRVA, uint32_t mode);\n",[124,1376,1377],{"class":126,"line":383},[124,1378,1379],{},"BOOL SetPayload(uint64_t targetRVA, const uint8_t *bytes, size_t len);\n",[124,1381,1382],{"class":126,"line":389},[124,1383,1384],{},"BOOL RestorePreparedState(uint64_t targetRVA);\n",[10,1386,1387,1390],{},[18,1388,1389],{},"targetRVA"," is a virtual-address-relative identifier chosen by this API. It is not a file\noffset. Define the image's preferred base once and use:",[114,1392,1397],{"className":1393,"code":1395,"language":1396,"meta":120},[1394],"language-text","RVA         = target_vmaddr - preferred_image_base\nliveAddress = target_vmaddr + ASLR_slide\n","text",[18,1398,1395],{"__ignoreMap":120},[10,1400,1401],{},"Converting a file offset to a virtual address instead requires locating the containing\nsegment and applying:",[114,1403,1406],{"className":1404,"code":1405,"language":1396,"meta":120},[1394],"vmaddr = segment.vmaddr + (fileOffset - segment.fileoff)\n",[18,1407,1405],{"__ignoreMap":120},[10,1409,1410],{},"The concepts coincide only in simple layouts. Keep them distinct in APIs and variable\nnames.",[50,1412,1414],{"id":1413},"a-runtime-toggle","A runtime toggle",[10,1416,1417],{},"The writable pool can reserve generous capacity, but executable sites cannot be discovered\nlate. Capacity lets an already-prepared site accept new state or payload values without\nanother installation; a previously unseen code site still requires offline instrumentation\nand re-signing.",[114,1419,1422],{"className":116,"code":1420,"filename":1421,"language":119,"meta":120,"style":120},"typedef struct {\n    _Atomic uint32_t state; \u002F* 0 = baseline; other values select prepared behavior *\u002F\n    uint32_t length;\n    uint8_t payload[64];\n} HookSlot;\n","state.c",[18,1423,1424,1429,1434,1439,1444],{"__ignoreMap":120},[124,1425,1426],{"class":126,"line":127},[124,1427,1428],{},"typedef struct {\n",[124,1430,1431],{"class":126,"line":133},[124,1432,1433],{},"    _Atomic uint32_t state; \u002F* 0 = baseline; other values select prepared behavior *\u002F\n",[124,1435,1436],{"class":126,"line":139},[124,1437,1438],{},"    uint32_t length;\n",[124,1440,1441],{"class":126,"line":145},[124,1442,1443],{},"    uint8_t payload[64];\n",[124,1445,1446],{"class":126,"line":151},[124,1447,1448],{},"} HookSlot;\n",[10,1450,1451,1452,556,1455,1458,1459,1462],{},"If readers can observe the slot concurrently, define a publication protocol rather than\nwriting ",[18,1453,1454],{},"length",[18,1456,1457],{},"payload"," while another thread consumes them. One simple pattern is to\nwrite an inactive copy, then publish its index with a release-store; the stub reads the index\nwith acquire semantics. A single atomic ",[18,1460,1461],{},"state"," is enough only when the payload itself is\nimmutable or otherwise synchronized.",[10,1464,1465],{},"Store the pristine baseline in the file-backed slot or in the RX path so restore does not\ndepend on bytes captured after a previous modification.",[253,1467],{},[50,1469,1471],{"id":1470},"kittymemory-useful-api-different-targets","KittyMemory: useful API, different targets",[1292,1473],{"align":1294,"commit":1474,"description":1475,"forks":1476,"full-name":1477,"issues":1478,"language":1300,"license":1479,"name":1480,"pushed":1481,"stars":1482,"url":1483},"fa9cc98","C++ library for runtime memory patching, scanning, dumping, and module (ELF \u002F Mach-O) introspection, targeting Android and iOS.","157","MJx0\u002FKittyMemory","0","MIT","KittyMemory","2026-09-08","554","https:\u002F\u002Fgithub.com\u002FMJx0\u002FKittyMemory",[10,1485,1486,1487,1490],{},"In the signed-code design, point ",[18,1488,1489],{},"MemoryPatch"," at the RW slot—not at the original code RVA.\nResolve the slot from its preferred VM address and the image's ASLR slide:",[114,1492,1496],{"className":1493,"code":1494,"language":1495,"meta":120,"style":120},"language-cpp shiki shiki-themes one-light one-dark-pro","#include \u003Cmach-o\u002Fdyld.h>\n#include \u003CKittyMemory\u002FMemoryPatch.hpp>\n\nuintptr_t slotAddress =\n    static_cast\u003Cuintptr_t>(_dyld_get_image_vmaddr_slide(imageIndex)) + slotVMAddr;\n\nMemoryPatch mode1 = MemoryPatch::createWithHex(slotAddress, \"01 00 00 00\");\nMemoryPatch mode2 = MemoryPatch::createWithHex(slotAddress, \"02 00 00 00\");\n\nmode1.Modify();\nmode1.Restore();\nmode2.Modify();\n","cpp",[18,1497,1498,1503,1508,1512,1517,1522,1526,1531,1536,1540,1545,1550],{"__ignoreMap":120},[124,1499,1500],{"class":126,"line":127},[124,1501,1502],{},"#include \u003Cmach-o\u002Fdyld.h>\n",[124,1504,1505],{"class":126,"line":133},[124,1506,1507],{},"#include \u003CKittyMemory\u002FMemoryPatch.hpp>\n",[124,1509,1510],{"class":126,"line":139},[124,1511,161],{"emptyLinePlaceholder":160},[124,1513,1514],{"class":126,"line":145},[124,1515,1516],{},"uintptr_t slotAddress =\n",[124,1518,1519],{"class":126,"line":151},[124,1520,1521],{},"    static_cast\u003Cuintptr_t>(_dyld_get_image_vmaddr_slide(imageIndex)) + slotVMAddr;\n",[124,1523,1524],{"class":126,"line":157},[124,1525,161],{"emptyLinePlaceholder":160},[124,1527,1528],{"class":126,"line":164},[124,1529,1530],{},"MemoryPatch mode1 = MemoryPatch::createWithHex(slotAddress, \"01 00 00 00\");\n",[124,1532,1533],{"class":126,"line":170},[124,1534,1535],{},"MemoryPatch mode2 = MemoryPatch::createWithHex(slotAddress, \"02 00 00 00\");\n",[124,1537,1538],{"class":126,"line":365},[124,1539,161],{"emptyLinePlaceholder":160},[124,1541,1542],{"class":126,"line":371},[124,1543,1544],{},"mode1.Modify();\n",[124,1546,1547],{"class":126,"line":377},[124,1548,1549],{},"mode1.Restore();\n",[124,1551,1552],{"class":126,"line":383},[124,1553,1554],{},"mode2.Modify();\n",[10,1556,1557],{},"Here the four bytes are data interpreted by the prepared stub. They are not newly generated\ninstructions. In an unrestricted environment where direct executable patching is permitted,\nthe current assembler overload requires the architecture and can take the assembly address:",[114,1559,1561],{"className":1493,"code":1560,"language":1495,"meta":120,"style":120},"MemoryPatch direct = MemoryPatch::createWithAsm(\n    targetAddress,\n    MP_ASM_ARM64,\n    \"mov w0, #1\\nret\",\n    targetAddress);\n",[18,1562,1563,1568,1573,1578,1583],{"__ignoreMap":120},[124,1564,1565],{"class":126,"line":127},[124,1566,1567],{},"MemoryPatch direct = MemoryPatch::createWithAsm(\n",[124,1569,1570],{"class":126,"line":133},[124,1571,1572],{},"    targetAddress,\n",[124,1574,1575],{"class":126,"line":139},[124,1576,1577],{},"    MP_ASM_ARM64,\n",[124,1579,1580],{"class":126,"line":145},[124,1581,1582],{},"    \"mov w0, #1\\nret\",\n",[124,1584,1585],{"class":126,"line":151},[124,1586,1587],{},"    targetAddress);\n",[10,1589,1590],{},"The final argument matters for assembly containing PC-relative expressions. This direct\nexample is outside the enforced signed-code design; an API call cannot make an otherwise\nforbidden executable-page write valid.",[50,1592,1594],{"id":1593},"dobby-what-the-prepared-segments-do-not-solve","Dobby: what the prepared segments do not solve",[1292,1596],{"align":1294,"commit":1597,"description":1598,"forks":1599,"full-name":1600,"issues":1601,"language":1300,"license":1602,"name":1603,"pushed":1604,"stars":1605,"url":1606},"5dfc854","a lightweight, multi-platform, multi-architecture hook framework.","1096","jmpews\u002FDobby","98","Apache-2.0","Dobby","2025-01-26","4830","https:\u002F\u002Fgithub.com\u002Fjmpews\u002FDobby",[10,1608,1609],{},"An inline-hook engine normally has to rewrite the target site and create executable\ntrampoline code. A separate RW pool solves neither operation. Therefore this familiar call:",[114,1611,1613],{"className":116,"code":1612,"language":119,"meta":120,"style":120},"DobbyHook((void *)target, (void *)hook_impl, (void **)&orig);\n",[18,1614,1615],{"__ignoreMap":120},[124,1616,1617],{"class":126,"line":127},[124,1618,1612],{},[10,1620,1621],{},"has two different interpretations depending on the environment:",[178,1623,1625],{"className":1624},[181],[183,1626,1627,1637],{},[186,1628,1629],{},[189,1630,1631,1634],{},[192,1632,1633],{},"Environment",[192,1635,1636],{},"What is valid",[199,1638,1639,1647],{},[189,1640,1641,1644],{},[204,1642,1643],{},"Debug, jailbroken, or otherwise unrestricted",[204,1645,1646],{},"Dobby may patch the target and allocate or use executable trampoline memory normally",[189,1648,1649,1652],{},[204,1650,1651],{},"Enforced signed-code environment discussed here",[204,1653,1654],{},"The interception branch, relocated instructions, and executable dispatch path must already exist before signing; runtime changes stay in RW state",[10,1656,1657,1660,1661,1663,1664,1667,1668,1670],{},[18,1658,1659],{},"DobbyInstrument"," changes the callback model, not this installation constraint: arranging\ninstruction-level instrumentation still requires an interception mechanism at the target.\nAdding ",[18,1662,294],{}," does not silently make an arbitrary ",[18,1665,1666],{},"DobbyHook"," or\n",[18,1669,1659],{}," target patchable.",[10,1672,1673],{},"For the constrained design, Dobby can still be useful as an offline reference or as part of\na build-time integration, but the resulting executable bytes must be materialized in the\nfile before signing. At runtime, the prepared dispatcher can use the same state-driven shape:",[114,1675,1678],{"className":116,"code":1676,"filename":1677,"language":119,"meta":120,"style":120},"int prepared_dispatch(int a, int b, const HookSlot *slot)\n{\n    switch (atomic_load_explicit(&slot->state, memory_order_acquire)) {\n    case 0:  return prepared_original(a, b);\n    case 1:  return prepared_original(a * 2, b * 2);\n    case 2:  return 1;\n    case 3:  return -prepared_original(a, b);\n    default: return prepared_original(a, b);\n    }\n}\n","dispatcher.c",[18,1679,1680,1685,1689,1694,1699,1704,1709,1714,1719,1723],{"__ignoreMap":120},[124,1681,1682],{"class":126,"line":127},[124,1683,1684],{},"int prepared_dispatch(int a, int b, const HookSlot *slot)\n",[124,1686,1687],{"class":126,"line":133},[124,1688,886],{},[124,1690,1691],{"class":126,"line":139},[124,1692,1693],{},"    switch (atomic_load_explicit(&slot->state, memory_order_acquire)) {\n",[124,1695,1696],{"class":126,"line":145},[124,1697,1698],{},"    case 0:  return prepared_original(a, b);\n",[124,1700,1701],{"class":126,"line":151},[124,1702,1703],{},"    case 1:  return prepared_original(a * 2, b * 2);\n",[124,1705,1706],{"class":126,"line":157},[124,1707,1708],{},"    case 2:  return 1;\n",[124,1710,1711],{"class":126,"line":164},[124,1712,1713],{},"    case 3:  return -prepared_original(a, b);\n",[124,1715,1716],{"class":126,"line":170},[124,1717,1718],{},"    default: return prepared_original(a, b);\n",[124,1720,1721],{"class":126,"line":365},[124,1722,940],{},[124,1724,1725],{"class":126,"line":371},[124,1726,945],{},[253,1728],{},[50,1730,1732],{"id":1731},"the-boundary-to-remember","The boundary to remember",[10,1734,1735,1736,1739],{},"The reusable resource is ",[36,1737,1738],{},"runtime data capacity",". The non-reusable resource is an\nunmodified executable site.",[274,1741,1742,1745,1748,1751,1754],{},[33,1743,1744],{},"A prepared site can switch modes, constants, tables, or payload data at runtime.",[33,1746,1747],{},"An unprepared RVA cannot be connected to the pool merely by adding a new slot.",[33,1749,1750],{},"RX code is generated and signed offline.",[33,1752,1753],{},"RW state changes at runtime.",[33,1755,1756],{},"RVA, VM address, file offset, and live address are separate coordinate systems.",[10,1758,1759],{},"Once that boundary is explicit, the design closes cleanly: offline Mach-O surgery creates\nthe executable path, code signing seals it, and runtime code only selects among behavior the\nsigned image already knows how to perform.",[1761,1762,1763],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sAdtL, html code.shiki .sAdtL{--shiki-default:#4078F2;--shiki-dark:#61AFEF}html pre.shiki code .sAGMh, html code.shiki .sAGMh{--shiki-default:#986801;--shiki-dark:#D19A66}html pre.shiki code .s5ixo, html code.shiki .s5ixo{--shiki-default:#383A42;--shiki-dark:#ABB2BF}html pre.shiki code .sDhpE, html code.shiki .sDhpE{--shiki-default:#50A14F;--shiki-dark:#98C379}",{"title":120,"searchDepth":133,"depth":133,"links":1765},[1766,1767,1768,1769,1770,1776,1777,1778,1779,1780],{"id":52,"depth":133,"text":53},{"id":93,"depth":133,"text":94},{"id":257,"depth":133,"text":258},{"id":312,"depth":133,"text":313},{"id":577,"depth":133,"text":1771,"children":1772},"Lay out the file without corrupting __LINKEDIT",[1773,1774],{"id":796,"depth":139,"text":797},{"id":841,"depth":139,"text":1775},"Fix each __LINKEDIT reference using its real structure",{"id":1309,"depth":133,"text":1310},{"id":1413,"depth":133,"text":1414},{"id":1470,"depth":133,"text":1471},{"id":1593,"depth":133,"text":1594},{"id":1731,"depth":133,"text":1732},"2026-09-16","Why patching signed arm64 code pages can terminate the process, and how to prepare target RVAs for safe runtime changes.","md",{},"\u002Fwriting\u002Fsafe-runtime-rva-patching",{"title":5,"description":1782},"writing\u002Fsafe-runtime-rva-patching","bQXd8zVYdwship6iQhEqcJ2OyLztMl4p3OR9LWIgjWw",1789582089558]