Search code examples
ioscassemblyjailbreakcydia-substrate

iOS Patch program instruction at runtime


How would one go about modifying individual assembly instructions in an application while it is running?

I have a Mobile Substrate tweak that I am writing for an existing application. In the tweak's constructor (MSInitialize), I need to be able to rewrite individual instruction(s) in the app's code. What I mean by this is that there may be multiple places in the application's address space that I wish to modify, but in each instance, only a single instruction needs to be modified. I have already disabled ASLR for the application and know the exact memory address of the instruction to be patched, and I have the hex bytes (as a char[], but this is uninportant and can be changed if necessary) of the new instruction. I just need to figure out how to perform the change.

I know that iOS uses Data Execution Prevention (DEP) to specify that executable memory pages cannot also be writeable and vice versa, but I know that it is possible to bypass this on a jailbroken device. I also know that the ARM processor used by iDevices has an instruction cache that needs to be updated to reflect the change. However, I do not even know where to begin to do this.

So, to answer the question that would surely otherwise be asked, I have not tried anything. This is not because I am lazy; rather, it is because I have absolutely no clue how this could be accomplished. Any help at all would be greatly appreciated.

Edit:

If it helps at all, my ultimate goal is to use this in a Mobile Substrate tweak that hooks an App Store application. Previously, in order to mod this application, one would have to first crack it to decrypt the app so the binary could be patched. I want to make it so people wouldn't have to crack the app, since that can lead to piracy which I am strongly against. I can't use Mobile Substrate normally because all of the work is done in C++, not Objective-C, and the application is stripped, leaving no symbols to use MSHookFunction on.


Solution

  • Completely forgot I asked this question, so I'll show what I ended up with now. The comments should explain how and why it works.

    #include <stdio.h>
    #include <stdbool.h>
    #include <mach/mach.h>
    #include <libkern/OSCacheControl.h>
    
    #define kerncall(x) ({ \
        kern_return_t _kr = (x); \
        if(_kr != KERN_SUCCESS) \
            fprintf(stderr, "%s failed with error code: 0x%x\n", #x, _kr); \
        _kr; \
    })
    
    
    bool patch32(void* dst, uint32_t data) {
        mach_port_t task;
        vm_region_basic_info_data_t info;
        mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
        vm_region_flavor_t flavor = VM_REGION_BASIC_INFO;
    
        vm_address_t region = (vm_address_t)dst;
        vm_size_t region_size = 0;
    
        /* Get region boundaries */
        if(kerncall(vm_region(mach_task_self(), &region, &region_size, flavor, (vm_region_info_t)&info, (mach_msg_type_number_t*)&info_count, (mach_port_t*)&task))) return false;
        /* Change memory protections to rw- */
        if(kerncall(vm_protect(mach_task_self(), region, region_size, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY))) return false;
    
        /* Actually perform the write */
        *(uint32_t*)dst = data;
    
        /* Flush CPU data cache to save write to RAM */
        sys_dcache_flush(dst, sizeof(data));
        /* Invalidate instruction cache to make the CPU read patched instructions from RAM */
        sys_icache_invalidate(dst, sizeof(data));
    
        /* Change memory protections back to r-x */
        kerncall(vm_protect(mach_task_self(), region, region_size, false, VM_PROT_EXECUTE | VM_PROT_READ));
        return true;
    }