iOS static way to bypass svc anti-dynamic debugging

In iOS anti-dynamic debugging, “svc #0x80” is commonly used. Through svc assembly, the call to ptrace and syscall is realized, and anti-dynamic debugging is realized, so that lldb cannot be attached to the app process, and it is not easy to locate the code location, adding anti-debugging bypass difficulty.

How to bypass this anti-debugging method?

This article searches the executable file of the app, finds the location of svc-related assembly instructions, and modifies “svc #0x80” to “nop” to bypass anti-dynamic debugging.

In this paper, the address of the last instruction is determined by continuously matching multiple assembly instructions by searching the assembly code block.

1. svc anti-dynamic debugging code

For example, create a new test APP, add svc assembly to the main function, and call svc to implement ptrace:

This test app is compiled to generate an executable file and find the location of svc:

At this point, the APP is running on the mobile phone, and if you try to attach the app, you will be prompted to fail:

debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-900.3.106
 for arm64.
Attaching to process TestSpace...
Segmentation fault: 11

Second, find the svc assembly instruction flow

The general idea is to read the binary file of the APP, parse the macho format, find the corresponding code segment, convert the binary in the code segment into assembly instructions, and traverse to find the corresponding address specified by svc:

3. Main code analysis

(1) Use the capstone library for binary conversion assembly.

intmain(int argc, constchar * argv[]){
    //Original file path
    string sFilePath = "/path/to/TestSpace";
    //Storage path
    string sFilePath_save = "/path/to/TestSpace_2";
    //Assembly instruction to be searched
    vector<string> svc_asm_vec = {"movz x0,#0x1f", "movz x1,#0", "movz x2,#0", "movz x3,#0", "movz w16,#0x1a", "svc # 0x80"};

    uint64_t file_size = FileGetSize((char*)sFilePath.c_str());//Calculate file size void *file_buf = gain_fileBuf(sFilePath.c_str());//Load file to memory//Search "svc #0x80" the address of
    vector<uint64_t> addr_arry = search_svc_from_asm(file_buf, svc_asm_vec);

    if(addr_arry. size()>0){
        void * file_buf_save = alter_svc_to_nop(file_buf, addr_arry[0]);//modify the first svc to nopsave_buf_to_file(file_buf_save, file_size, sFilePath_save);//store the modified binary to the local file
    }

    return0;
}

(2) The gain_text_sections function finds the corresponding code segment from the binary file:

//Get the code segment in the binary file vector<struct section_64 const *> gain_text_sections(void * file_buf){
    // Determine whether it is a fat file
    mach_header * mhHeader = (mach_header*)file_buf;
    vector<struct section_64 const *> sectionArray;
    // Find code section structsection_64const * sectionTxt_DB = findSection64ByName(mhHeader, "__text", "__BD_TEXT");//if(sectionTxt_DB==NULL){
        NSLog(@"Could not find code segment --1");
    }
    else {
        sectionArray.push_back(sectionTxt_DB);
    }

    structsection_64const * sectionTxt = findSection64ByName(mhHeader, "__text", "__TEXT");//if(sectionTxt==NULL){
        NSLog(@"Could not find code segment --2");
    }
    else {
        sectionArray. push_back(sectionTxt);
    }

    return sectionArray;
}

Because most APP code segments are in Section64 (TEXT, text), and some code segments are in Section64 (BD_TEXT, text), so the code needs to determine the location of the code segment.

(3) From the binary file of the app, search for the specified assembly array, and return the address of the last assembly instruction if it matches completely. The search_svc_from_asm function will return an array:

vector<uint64_t> search_svc_from_asm(void * file_buf, vector<string> asmStrArray){
    vector<uint64_t> resultVec;//declare an int type vector
    vector<struct section_64 const *> sectionArray = gain_sections(file_buf);

    if(sectionArray. size()<1){
        NSLog(@"Could not find code segment --3");
        return resultVec;
    }

    for(int i=0; i<sectionArray. size(); i ++ ){
        structsection_64const *sectionTxt = sectionArray[i];

        // Show offset and sizeuint64_t in Section64_Header offset = sectionTxt->offset;
        uint64_t text_size = sectionTxt->size;

        int tmp_length = 4;//memory occupied by a single assembly//disassembly
        Disasm * disasm = [[Disasm alloc] init];

        uint32_t my_offset = (uint32_t)0;
        uint64_t my_addr = (uint64_t)offset;
        uint32_t my_size = 0x640;//400 int asmStrCount = (int)asmStrArray.size();

        while (1) {

            my_size = 0x640;

            if(my_offset==0){
                my_offset = (uint32_t)offset;
            }
            else {
                my_offset = my_offset + my_size - (asmStrCount-1)*tmp_length;
            }

            if(my_offset < text_size + offset & amp; & amp; my_offset + my_size > text_size + offset)
            {
                my_size = (uint32_t)(text_size + offset-my_offset);
            }
            elseif(my_offset>text_size + offset){
                break;
            }

            NSArray * asmArrayTmp = [disasm disAsmWithBuff:file_buf offset:my_offset size:my_size addr:(uint64_t)my_addr];

            int count = (int)[asmArrayTmp count];

            uint64_t first_addr = (uint32_t)my_offset;

            int samecount = 0;

            for(int i = 0; i<count; i ++ ){
                NSString * curAsm = [asmArrayTmp objectAtIndex:i];

                for(int j=0; j<asmStrCount; j ++ ){
                    if(j==samecount){
                        NSString * strTmp = [NSString stringWithCString:(asmStrArray[j]).c_str() encoding:NSUTF8StringEncoding];

                        if([strTmp isEqualToString:curAsm])
                        {
                            samecount = samecount + 1;
                            break;
                        }
                        else
                        {
                            samecount = 0;
                            break;
                        }
                    }
                }

                if(samecount==asmStrCount){

                    uint64_t target_addr = (uint64_t)(first_addr + i*4);
                    NSLog(@"Find svc call anti-dynamic debugging: 0x%lx %@", target_addr, curAsm);

                    resultVec.push_back(target_addr);

                    break;
                }

            }

        }
    }

    return resultVec;
}

The second parameter of the search_svc_from_asm function is an array of assembly instructions, which can search for any assembly instruction.

(4) According to the obtained assembly instruction address, modify “svc #0x80” to “nop”:

void * alter_svc_to_nop(void * file_buf, uint64_t target_addr){
    uint8_t *pBegin = (uint8_t*)file_buf;


    uint64_t target = 0xD4001001;//svc 0x80uint64_t textAddr_base = (uint64_t)pBegin + target_addr;

    structSingleAss * sAss = (struct SingleAss *)(textAddr_base);


    if(sAss->singleAss == target)
    {
        sAss->singleAss = 0xD503201F;// nop
    }

    return file_buf;

}

(5) Store the modified binary to the specified file:

//Store binary to file voidsave_buf_to_file(void * file_buf, uint64_t file_size, string filePath_save){
        FILE *fp = fopen(filePath_save.c_str(), "w");
        uint8_t *pBegin = (uint8_t*)file_buf;

        fwrite((void*)pBegin, 1, file_size, fp);

        fclose(fp);

        free(file_buf);

        printf("rBuff=write complete\
");

        printf("**********************************\
");
}

The test results are as follows:

You can re-sign the newly generated app, and then you can do dynamic debugging.

5. Precautions

(1) The search_svc_from_asm function can search for any assembly instruction, and the second parameter is the assembly instruction array, which is the content to be searched;

(2) If the following arm assembly cannot be found:

{“mov x0,#0x1f”, “mov x1,#0”, “mov x2,#0”, “mov x3,#0”, “mov w16,#0x1a”, “svc #0x80”}

You can try to check:

{“movz x0,#0x1f”, “movz x1,#0”, “movz x2,#0”, “movz x3,#0”, “movz w16,#0x1a”, “svc #0x80”}

Only an exact match is judged to find the corresponding assembly instruction, if this is not found.

The number of assembly instructions can be minimized, for example, only search for the last two instructions {“mov w16,#0x1a”, “svc #0x80”}, or only search for the last instruction {“svc #0x80”}

(3) There are many “svc #0x80” instructions in some apps, and you may get a lot of results by only using the search_svc_from_asm function to search for the “svc #0x80” instruction:

2022-12-1500:19:37.636260 + 0800MachConfuse[4456:36033721] Find svc and call anti-dynamic debugging: 0x7736b94svc#0x802022-12-1500:19:37.642945 + 0800MachConfuse[Call 3437290] Dynamic debugging: 0x77383b4svc#0x802022-12-1500:19:37.651847 + 0800MachConfuse[4456:36033721] Found svc call anti-dynamic debugging: 0x773a824svc#0x802022-12-1500:19:37.608fuse04735 + 033721] Found svc Call anti-dynamic debugging: 0x773b570svc#0x802022-12-1500:19:37.658423 + 0800MachConfuse[4456:36033721] Find svc and call anti-dynamic debugging: 0x773c5d0svc#0x802022-12-1500:19:34.660806[ 36033721] Find Call anti-dynamic debugging to svc: 0x773c66csvc#0x80
?…

Changing all to “nop” will not work normally, try not to change all to nop, you can try to change to nop one by one and then sign and test the effect.

Generally, svc will be in the main function, you can try to find the main function in the APP binary file, and if there is svc, you can try to modify it to nop.

(4) This project can be used to search any assembly instruction in macho

Source address:

https://github.com/luoyanbei/MachConfuse

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge Cloud native entry skill treeHomepageOverview 11973 people are studying systematically