preface

The real purpose of learning reverse is to protect, and the reason why we want to learn cracking is to understand what means hackers will use to crack, and how we should targeted protection. No app is completely secure, and the point of protection is to make cracking so difficult that the cost of cracking far exceeds the benefit. Offense and defense are more like a game, and if you hide it deep enough to keep others guessing, you win. According to the Hook principle and dynamic debugging in the last article, we explore attack and defense from two dimensions, dynamic debugging and static analysis.

Dynamic debugging

When we break an APP, we tend to debug it dynamically first. For example, to crack wechat red envelope grab, first Reveal or Cycript will be used to analyze the UI view, then LLDB dynamic debugging page, locate the view controller where the red envelope grab button is located and function call stack. On jailbroken phones, it’s hard to restrict anyone from using Reveal or Cycript to analyze our app, but we can limit LLDB dynamic debugging, which makes it more difficult to crack with one less channel of analysis.

Ptrace protection

The key to dynamic debugging with LLDB is the pTrace function, which allows one process to monitor and control another process (essentially attaching APP process via debugServer), and to read and change the values in memory and registers of the controlled process. We can set this function in our APP to prevent our APP from being debugged. To use ptrace() in ios, you need to import a header file. This header file exists in the Mac project. You can create a Mac project by Xcode -> open the

header file -> copy the contents of the header file to the ios project.

// Parameter 1: PT_DENY_ATTACH indicates that the current process is not allowed to attach
// Parameter 2: indicates the process ID. 0 indicates the process id
// Parameter 3: address, based on parameter 1
// data 4: data, according to parameter 1, pass 0 here
ptrace(PT_DENY_ATTACH, 0.0.0);
Copy the code

The LLDB breakpoint will crash when the APP is debugged when ptrace() is called, for example, from the Main function. Therefore, it is most likely that pTrace protection has been added to see LLDB debugging crash while manually opening APP works.

Crack thinking

  • addedPtraceProtection, thenlldbDebugging will crash, which is very unfriendly in normal development, so depending on the environment variables, inDebugenvironmentClose the Ptrace.ReleasE environmentOpen PtraceProtection.
  • Due to the usePtraceThe phenomenon of protection is particularly obvious, which is equivalent to exposing others to the use of Ptrace, so it is easy for hackers to crack, such as usingfishhookIntercept the Ptrace function, or be more violent and directModifying binary filesKill pTrace, and the pTrace protection is meaningless.

Ptrace crack

  • fishhookcrack

The article Hook principle and dynamic debugging explains in detail how to use Fishhook to intercept system functions, which will not be explained in detail here, the key code is as follows.

MonkeyAPPI use it by defaultfishhookinterceptedPtraceFunction, you can see that Ptrace is intercepted as wellDlsym, syscall, and sySCTL.

  • Modified binary cracking

Use the IDA(or Hopper) tool to open the pTrace protected demo and fully search the PTrace tag

Locate the ptrace function as follows

The cursorpositioningBL _ptraceThis line,IDA->Edit->patch program->change typeModify the first four instructions toNOPNOP represents empty code that jumps to the next line. How do you knowNOP=1F 20 03 D5? IDA do a full search of the projectNOPTag, and display binary data.

IDA->Edit-> Patch Program ->Apply patchs to Input file saves the modified executable. Using a script or Monkey to re-sign the executable so that the LLDB add-on process does not crash, there is little solution to this direct brute force modification of the binary, because by directly modifying the program, all we can do is hide our defenses as much as possible.

Protective ideas

  • If the hackers get throughfishhookcrackptrace, then you must Hook by inserting the dynamic library.The prison breakEnvironment by modifying executable filesLoad Commands, let DYLD load the dynamic library to be inserted,Prison break the environmentThe following is through modificationDYLD_INSERT_LIBRARYSEnvironment variables that allow DYLD to automatically load the inserted dynamic library. If we can do pTrace protection before the inserted dynamic library, we can avoid the Fishhook hack. We knew we were working on itFrameworkExecution orderGive priority toOn othersInto the Framework ofWe can create a new Framework and add pTrace protection to it.
  • If the hacker hacked PTrace by modifying the binary, then can weHide this pTraceWhat about symbolic tags?
  • Due to theptraceThe phenomenon of direct collapse of defenses is too obvious, even when we are in our ownFrameworkBut what if a hacker locates the Framework and modifies the pTrace tag? soProtective FrameworkIt’s best to name it so that no one can guess that the Framework is being defended, and then replace ptrace with another function, such assysctl()We can customize the behavior in this function, for exampleDon't let the program crashInstead, it detectslldbAfter dynamic debugging, the user’s IP address, device information and other data are reported to the server, so that the server can block the IP address or the APP can be directly disconnected from the network.In short, avoid providing a strong signal of what you have done.

Sysctl protection

Since the ptrace protection feature is too obvious, we can use the sysctl() function instead, which can check whether the process has been attached, but does not crash directly. Based on the above analysis, we create a new Guess. Framework and add the sysctl function as follows:

+ (void)load{
    if(isAttached()){
        NSLog(@"Attach detected, collect phone information and other data to background."); }}Yes indicates that it is attached
BOOL isAttached(void){
    int name[4];             // Put bytecode inside. Query information
    name[0] = CTL_KERN;      // kernel query
    name[1] = KERN_PROC;     // Query the process
    name[2] = KERN_PROC_PID; // The argument passed is the process ID
    name[3] = getpid();      // Get the current process ID
    struct kinfo_proc info;  // The structure that accepts the query result
    size_t info_size = sizeof(info);  // Structure size
    if(sysctl(name,4, &info, &info_size, 0.0)){
        NSLog(@"Query failed");
        return NO;
    }
    /* The 12th bit of info.kp_proc.p_flag is set to 1. P_flag & P_TRACED to get the 12th bit */
    return((info.kp_proc.p_flag & P_TRACED) ! =0);
}
Copy the code

Crack thinking

  • Although theFrameworkAdding sysctl() to the directory can be preemptively defended, butsysctlandptraceSame, still oneExternal symbolIt can be very violent like the one aboveModify binary,sysctlDirectly modify toNOP, the specific steps will not map. There are also people who usesyscall()The syscall() function shields the sysctl notation, which is not recommended hereios10It’s been abandoned since. Number twosyscallIt’s a symbol in itself.

Dlopen calls guard dynamically

Since sySCTL symbols can be statically analyzed and modified using tools such as IDA, we need to hide sySCTL symbols using dynamic calls. Here we use dlopen+dlopen dynamic call. Note that we need to import the #import header file

// Whether to attach
BOOL isAttached(void){
    int name[4];             // Put bytecode inside. Query information
    name[0] = CTL_KERN;      // kernel query
    name[1] = KERN_PROC;     // Query the process
    name[2] = KERN_PROC_PID; // The argument passed is the process ID
    name[3] = getpid();      // Get the current process ID
    struct kinfo_proc info;  // The structure that accepts the query result
    size_t info_size = sizeof(info);  // Structure size

    //A xor B waits for C,C xor A to get B, hide sysctl
     unsigned char str[] = {
             ('q' ^ 's'),
             ('q' ^ 'y'),
             ('q' ^ 's'),
             ('q' ^ 'c'),
             ('q' ^ 't'),
             ('q' ^ 'l'),
             ('q' ^ '\ 0')}; unsigned char * p = str;while (((*p) ^= 'q') != '\ 0') p++;
      int (*m_sysctl)(int *, u_int, void *, size_t *, void *, size_t);
     void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);// Get the handle
       m_sysctl=dlsym(handle,(const char *)str);// Dynamically find sysctl symbols
      if(m_sysctl(name,4, &info, &info_size, 0.0)){
            NSLog(@"Query failed");
            return NO;
      }
      /* The 12th bit of info.kp_proc.p_flag is set to 1. P_flag & P_TRACED to get the 12th bit */
    return((info.kp_proc.p_flag & P_TRACED) ! =0);
Copy the code

Crack thinking

  • sysctlThe function name passesExclusive or operationOperator and hiddenThe dynamic invocation(Exclusive or operationThe way characters hide strings is veryThe commonly used) although this way is utilizedIDAAnd the tools are gonesysctlExternal notation, but we can still get throughSysctl symbol breakpointTo analyzeFunction call stack, as shown below

The symbol breakpoint is brokensysctl.sbtView the function call stack and locate theGuess.frameworkIn the"isAttached"The function is called, and the memory address of the function is0x100bdfe10. lldbimage listThe list of images for querying executable files is as follows

Guess.frameworkThe first address of MachO is0x100bd8000, then"isAttached"Function in theGuess.frameworkIn theThe offsetAddress 0 x100bdfe10 x100bd8000 = 00x7e10.IDAOpen the Guess. FrameworkExecutable file, change the”IsAttached"for"The NOP", can be cracked after savingsysctlProtection.

Assembly protection

In fact, the above kind of hidden SYSCTL symbol and then dlopen dynamic call protection has been very good, first remove sySCTL symbol, moreover is in the framework of the custom call, hackers want to crack it first have to locate which dynamic library added protection. The only downside is that the binaries can still be modified directly by locating the calling logic through the SYscTL function call stack. So this hiding is not very thorough, so optimize it as follows

// Whether to attach
BOOL isAttached(void){
    int name[4];             // Put bytecode inside. Query information
    name[0] = CTL_KERN;      // kernel query
    name[1] = KERN_PROC;     // Query the process
    name[2] = KERN_PROC_PID; // The argument passed is the process ID
    name[3] = getpid();      // Get the current process ID
    struct kinfo_proc info;  // The structure that accepts the query result
    size_t info_size = sizeof(info);  // Structure size
    
  #ifdef __arm64__
    asm volatile(
                 "mov x0,%[name_p]\n"
                 "mov x1,#4\n"
                 "mov x2,%[info_p]\n"
                 "mov x3,%[infozize_p]\n"
                 "mov x4,#0\n"
                 "mov x5,#0\n"
                 "mov x16,#202\n" / / 202 representative sysctl
                 "svc #0x80"   // Triggers a soft interrupt
                 :
                 : [name_p] "r"(name),[info_p] "r"(&info),[infozize_p]  "r"(&info_size)
                 );
  #else / / 32 bits
    asm volatile(
                 "mov r0,%[name_p]\n"
                 "mov r1,#4\n"
                 "mov r2,%[info_p]\n"
                 "mov r3,%[infozize_p]\n"
                 "mov r4,#0\n"
                 "mov r5,#0\n"
                 "mov r16,#202\n" / / 202 representative sysctl
                 "svc #0x80"   // Triggers a soft interrupt
                 :
                 : [name_p] "r"(name),[info_p] "r"(&info),[infozize_p]  "r"(&info_size)
                 );
   #endif
      /* The 12th bit of info.kp_proc.p_flag is set to 1. P_flag & P_TRACED to get the 12th bit */
    return((info.kp_proc.p_flag & P_TRACED) ! =0);
Copy the code

The SYSCTL function is triggered by assembly, which has two instruction sets, 32-bit and 64-bit. SVC #0x80″ assembly instruction will trigger the function in register X16, namely sySCtl. The value in register X0 ~ X5 is the parameter required by sySCTL function. Parameter transfer involves assembly knowledge, which will not be explained in detail here. The values of the functions in the x16 register can be seen in the #import

header, such as 202 for sysctl, 26 for ptrace, and 1 for exit. As a reminder, while it is not recommended to crash the program when an intrusion is detected, such as calling exit(0), it is recommended to use the assembly instruction SVC if you must do so.

#define SYS_syscall        0
#define SYS_exit           1
//....
#define SYS_setuid         23
#define SYS_getuid         24
#define SYS_geteuid        25
#define SYS_ptrace         26
#define SYS_recvmsg        27
#define SYS_sendmsg        28
#define SYS_recvfrom       29
/ /...
#define SYS_sysctl         202
Copy the code

Crack thinking

  • By assembly instructionsvcTriggering soft interrupts is trueHidden symbolAlso,A sign breakpoint cannot be setBut if you directly modify the binary file, thesvcThe instruction is set directly to, as aboveNOP? Then everything is back to square one

Assembly protection advanced

If a small function can be written to check whether SVC instruction can be used normally, the risk of SVC being modified by Patch can be avoided. For example, write an SVC instruction to obtain the process ID. If the SVC instruction can be obtained, the SVC instruction is normal; if the SVC instruction cannot be obtained, the SVC has been modified to NOP.

BOOL isAttached(void){
    int pid = 0;
    // SVC obtains pid and checks whether SVC is available
    asm volatile(
                 "mov x0,#0\n"
                 "mov x16,#20\n" //20 indicates to obtain pid
                 "svc #0x80\n"   
                 "cmp x0,#0\n" 
                 "b.ne #24\n" // not equal to 0, so long execute mov result code

                 "mov x1,#0\n"  // This assembly is very detailed, clear the stack
                 "mov sp,x1\n"
                 "mov x29,x1\n"
                 "mov x30,x1\n"
                 "ret\n"

                 "mov %[result],x0\n"
                 : [result] "=r"(pid)
                 :
                 :
                 );
    int name[4];             // Put bytecode inside. Query information
    name[0] = CTL_KERN;      // kernel query
    name[1] = KERN_PROC;     // Query the process
    name[2] = KERN_PROC_PID; // The argument passed is the process ID
    name[3] = getpid();      // Get the current process ID
    struct kinfo_proc info;  // The structure that accepts the query result
    size_t info_size = sizeof(info);  // Structure size
    
  #ifdef __arm64__
    asm volatile(
                 "mov x0,%[name_p]\n"
                 "mov x1,#4\n"
                 "mov x2,%[info_p]\n"
                 "mov x3,%[infozize_p]\n"
                 "mov x4,#0\n"
                 "mov x5,#0\n"
                 "mov x16,#202\n" / / 202 representative sysctl
                 "svc #0x80"   // Triggers a soft interrupt
                 :
                 : [name_p] "r"(name),[info_p] "r"(&info),[infozize_p]  "r"(&info_size)
                 );
  #else / / 32 bits
    asm volatile(
                 "mov r0,%[name_p]\n"
                 "mov r1,#4\n"
                 "mov r2,%[info_p]\n"
                 "mov r3,%[infozize_p]\n"
                 "mov r4,#0\n"
                 "mov r5,#0\n"
                 "mov r16,#202\n" / / 202 representative sysctl
                 "svc #0x80"   // Triggers a soft interrupt
                 :
                 : [name_p] "r"(name),[info_p] "r"(&info),[infozize_p]  "r"(&info_size)
                 );
   #endif
      /* The 12th bit of info.kp_proc.p_flag is set to 1. P_flag & P_TRACED to get the 12th bit */
    return((info.kp_proc.p_flag & P_TRACED) ! =0);
Copy the code

Mov %[result], X0 \n”, mov %[result], X0 \n”, mov %[result], X0 \n One line of assembly instruction is 4 bytes, and there are 6 lines of instruction from “B.n #24\n” to “mov %[result],x0\n”. If the value is 0, it indicates that the SVC instruction has been modified, and then the stack information will be cleared by assembly. After the stack information is cleared, SBT will not see any call stack, which will be very boring.

The static protection

IDA, Hopper, class-dump, MachOView, etc., can also be used to static analyze the logic of the program and locate the key functions or key constants for cracking. Therefore, we need to protect the key functions or key static constants in the application. Make it as difficult as possible for hackers to crack.

Code confusion

During static analysis of applications, class-dump is often used to export application header files, guess the functions of these functions based on the function names or variable names in the header files, and then perform Hook dynamic analysis. If we make method names, variable names, and class names meaningless in their names, we can disrupt hacker guessing to some extent, a method called code obfuscation. Write a login button, click login AES encryption string and send it to the back-end server, demo is as follows:

EncryptionToolsforAESEncrypted classes,loginactionFor click events, useclass-dumpExport the application’s header file and take a lookViewController.handEncryptionTools.h

It’s easy to guess from the header file"loginaction"The name should be login event,"decryptString"It should be an encryption function,"EncryptionTools"Should be encrypted class, guess these key functions or classes, you can Hook dynamic verification crack. Write apchHeader file, passPrecompiled instructionAdd obfuscation to class and method names as follows:

#define loginaction qsign_asdfas2134sfs
#define EncryptionTools qsign_tsdfsfsacs
#define encryptString qsign_ds23sdfasasf
Copy the code

Dump headers after adding precompiled obfuscation as follows:

The effect of this confusion is obvious. It is impossible to guess the function from the definition of the name, and it can be confusing to statically analyze functions that look like gibberish. Note: it is best to only confuse the key functions, key classes, and key variables in the application. Do not use the script to confuse the entire application, otherwise APPStroe is likely to be rejected.

String constant hiding

By analyzing the MachO executable, we can find the constants defined in the application in the constants section, such as the demo above, AES encryption key is “ABC”, we use MachOView to analyze the demo as follows:

The key “ABC” is easily located in the string constants area, and the risk of an application being hacked increases once the encrypted key is exposed. So we need to hide the string constant key. We can use the unsigned feature of the C function to hide it, as follows: return key by calling C function.

static NSString * MyAESKEY(){ 
unsigned char aeskey[] = { 'a'.'b'.'c'.'\ 0'};return [NSString stringWithUTF8String:(const char *)aeskey];
}
Copy the code

This removes the “ABC” constant from the constant area, and we can also confuse the MyAESKEY() function name to make it look meaningless. MyAESKEY() will still have the “ABC” character in the function body, so we need to optimize it as follows:

static NSString * MyAESKEY(){unsigned char aeskey[] = {('a'^'a'), ('a'^'b'), ('a'^'c'), ('a'^'\ 0'),}; unsigned char* m=aeskey;while(((*m)^='a')! ='\ 0'){
    m++
}
return [NSString stringWithUTF8String:(const char *)aeskey]; 
}
Copy the code

“ABC” xor a fixed string “a” followed by xor “A” to restore the string “ABC” (a xor B equals C, C xor B equals A). The xOR result is compiled directly. The compiled function body does not contain ABC characters.

Whitelist detection

In addition to protecting critical code in your application, you can also check the code to see if the dynamic libraries in your application are legitimate. Whether in jailbroken or non-jailbroken environment, if we want to invade, we can either modify binary or inject dynamic library, we can write a small function to check whether there are any dynamic library in the APP except our own dynamic library. The dyLD API function is used to obtain the names of dynamic libraries in the application and print them out in printf. These string names are combined as a whitelist. If any dynamic library names are not in the whitelist, then the library is an illegally invaded dynamic library.

const char * whitstr=""// omit, obtained by cyclic printing
void checkWhiteStr(){
    uint32_t count= _dyld_image_count();
    for(int i=1; i<count; i++){const char* dyname=_dyld_get_image_name(i);
        //printf(dyname);
        if(! strstr(whitstr, dyname)){// Not in the whitelist
            // Processing after detection
            NSLog([NSString stringWithFormat:@"Non-whitelist detected: %s",dyname]); }}}Copy the code

Printf (dyname) loops over the Image name and pastes the output from the console to whitstr. Note that the loop starts at 0, but the STRSTR () function needs to start at 1 to determine if it is a whitelist library. Because the first image file is the main project image associated with the sandbox path that needs to be removed. Note: it is best to obtain the whitelist from the server, so that the whitelist can be maintained flexibly in case of APP updates or bugs.