series

  1. IOS assembler introductory tutorial (I) ARM64 assembler basics
  2. Embedding assembly code in Xcode projects
  3. IOS assembler introduction (3) Assembler Section and data access

preface

In the first three articles, mainly introduced the common instructions and addressing methods in assembly, this article will combine these knowledge to introduce a fast analysis of function logic based on assembly code and dynamic debugging method.

For reverse engineering or for low-level forward development (such as performance optimization and security), system-level functions are often used. Sometimes it is necessary to understand the logic of these functions in detail, such as objc_getClass and object_getClass. The former gets the class object by name, the latter returns the class object, the metaclass object, and the root metaclass object by entering the instance, the class object, and the metaclass object. Without reading the source code, we can only analyze the function logic by experiment, but the source code of Objective-C Runtime is open. We can read the source code of the two functions directly to analyze the logic, which is a good way, but what if the function we are analyzing does not have the source code? So NSClassFromString, for example.

Static analysis

NSClassFromString comes from Foundation. A straightforward idea is to do static analysis of Foundation using IDA or Hopper, and then decompile NSClassFromString into C pseudocode for analysis. The advantage of this method is that the code is more readable, can quickly analyze the logic, but the disadvantage is also obvious, for large Framework static analysis speed is very slow, encountered inexplicable logic or need to combine dynamic debugging to analyze.

A dynamic analysis

Dynamic analysis is generally based on IDA or Hopper results for specific symbols or addresses under the breakpoint analysis, of course, can also be directly based on memory __TEXT, __TEXT segment, with the help of LLDB disassembler to directly analysis, this paper will adopt this method, Take NSClassFromString as an example.

Dynamically analyze NSClassFromString

Add sign breakpoint

First, find any iOS project and add a symbol breakpoint for NSClassFromString:

It then connects to a 64-bit iOS device, runs, and breaks into the NSClassFromString function, showing the disassembly result of the symbol:

Foundation`NSClassFromString:
->  0x18bfd2c98 <+0>:   stp    x28, x27, [sp# -0x40]!
    0x18bfd2c9c <+4>:   stp    x22, x21, [sp.#0x10]
    0x18bfd2ca0 <+8>:   stp    x20, x19, [sp.#0x20]
    0x18bfd2ca4 <+12>:  stp    x29, x30, [sp.#0x30]
    0x18bfd2ca8 <+16> :add    x29, sp.#0x30            ; =0x30 
    0x18bfd2cac <+20> :sub    sp.sp.#0x3f0            ; =0x3f0 
    0x18bfd2cb0 <+24> :mov    x19, x0
    0x18bfd2cb4 <+28> :adrp   x8, 196266
    0x18bfd2cb8 <+32> :ldr    x8, [x8, #0xf0]
    0x18bfd2cbc <+36> :ldr    x8, [x8]
    0x18bfd2cc0 <+40>:  stur   x8, [x29, #-0x38]
    0x18bfd2cc4 <+44> :cbz    x19, 0x18bfd2d6c          ; The < 212 > +
    0x18bfd2cc8 <+48> :adrp   x8, 224951
    0x18bfd2ccc <+52> :ldr    x1, [x8, #0x4c0]
    0x18bfd2cd0 <+56> :mov    x0, x19
    0x18bfd2cd4 <+60> :bl     0x18a72cd60               ; objc_msgSend
    0x18bfd2cd8 <+64> :mov    x20, x0
    0x18bfd2cdc <+68> :adrp   x8, 183685
    0x18bfd2ce0 <+72> :add    x1, x8, #0x8b8            ; =0x8b8 
    0x18bfd2ce4 <+76> :mov    x21, sp
    0x18bfd2ce8 <+80> :mov    x2, sp
    0x18bfd2cec <+84> :mov    w3, #0x3e8
    0x18bfd2cf0 <+88> :orr    w4, wzr, #0x4
    0x18bfd2cf4 <+92> :mov    x0, x19
    0x18bfd2cf8 <+96> :bl     0x18a72cd60               ; objc_msgSend
    0x18bfd2cfc <+100> :cmp    w0, #0x0                  ; =0x0 
    0x18bfd2d00 <+104>: csel   x21, x21, xzr, ne
    0x18bfd2d04 <+108> :cbz    w0, 0x18bfd2d18           ; The < 128 > +
    0x18bfd2d08 <+112> :mov    x0, x21
    0x18bfd2d0c <+116> :bl     0x18c1391a8               ; symbol stub for: +[NSUnitElectricPotentialDifference megavolts]
    0x18bfd2d10 <+120> :cmp    x0, x20
    0x18bfd2d14 <+124> :b.eq   0x18bfd2d5c               ; The < 196 > +
    0x18bfd2d18 <+128> :cbz    x20, 0x18bfd2d48          ; The < 176 > +
    0x18bfd2d1c <+132> :mov    x21, #0x0
    0x18bfd2d20 <+136> :adrp   x8, 183600
    0x18bfd2d24 <+140> :add    x22, x8, #0x9ce           ; =0x9ce 
    0x18bfd2d28 <+144> :mov    x0, x19
    0x18bfd2d2c <+148> :mov    x1, x22
    0x18bfd2d30 <+152> :mov    x2, x21
    0x18bfd2d34 <+156> :bl     0x18a72cd60               ; objc_msgSend
    0x18bfd2d38 <+160> :cbz    w0, 0x18bfd2d68           ; The < 208 > +
    0x18bfd2d3c <+164> :add    x21, x21, #0x1            ; =0x1 
    0x18bfd2d40 <+168> :cmp    x21, x20
    0x18bfd2d44 <+172> :b.lo   0x18bfd2d28               ; The < 144 > +
    0x18bfd2d48 <+176> :adrp   x8, 183538
    0x18bfd2d4c <+180> :add    x1, x8, #0x800            ; =0x800 
    0x18bfd2d50 <+184> :mov    x0, x19
    0x18bfd2d54 <+188> :bl     0x18a72cd60               ; objc_msgSend
    0x18bfd2d58 <+192> :mov    x21, x0
    0x18bfd2d5c <+196> :mov    x0, x21
    0x18bfd2d60 <+200> :bl     0x18a7273e0               ; objc_lookUpClass
    0x18bfd2d64 <+204> :b      0x18bfd2d6c               ; The < 212 > +
    0x18bfd2d68 <+208> :mov    x0, #0x0
    0x18bfd2d6c <+212>: ldur   x8, [x29, #-0x38]
    0x18bfd2d70 <+216> :adrp   x9, 196266
    0x18bfd2d74 <+220> :ldr    x9, [x9, #0xf0]
    0x18bfd2d78 <+224> :ldr    x9, [x9]
    0x18bfd2d7c <+228> :cmp    x9, x8
    0x18bfd2d80 <+232> :b.ne   0x18bfd2d9c               ; The < 260 > +
    0x18bfd2d84 <+236> :add    sp.sp.#0x3f0            ; =0x3f0 
    0x18bfd2d88 <+240>: ldp    x29, x30, [sp.#0x30]
    0x18bfd2d8c <+244>: ldp    x20, x19, [sp.#0x20]
    0x18bfd2d90 <+248>: ldp    x22, x21, [sp.#0x10]
    0x18bfd2d94 <+252>: ldp    x28, x27, [sp].#0x40
    0x18bfd2d98 <+256>: ret    
    0x18bfd2d9c <+260> :bl     0x18b03358c               ; __stack_chk_fail
Copy the code

According to the comments in the debugger, we can roughly see that the NSClassFromString call contains four OC method calls. The logic is not very straightforward, so let’s start at the beginning.

To skip the first six statements about state ephemeris, let’s look at the logic of the first objc_msgSend:

0x18bfd2cb0 <+24> :mov    x19, x0
0x18bfd2cb4 <+28> :adrp   x8, 196266
0x18bfd2cb8 <+32> :ldr    x8, [x8, #0xf0]
0x18bfd2cbc <+36> :ldr    x8, [x8]
0x18bfd2cc0 <+40>:  stur   x8, [x29, #-0x38]
0x18bfd2cc4 <+44> :cbz    x19, 0x18bfd2d6c          ; The < 212 > +
0x18bfd2cc8 <+48> :adrp   x8, 224951
0x18bfd2ccc <+52> :ldr    x1, [x8, #0x4c0]
0x18bfd2cd0 <+56> :mov    x0, x19
0x18bfd2cd4 <+60> :bl     0x18a72cd60               ; objc_msgSend
0x18bfd2cd8 <+64> :mov    x20, x0
Copy the code

Stack overflow protection

The code from +28 to +40 is used to hold the stack’s sentry stack_chk_guard. It takes multiple indirection addresses to get the sentry’s value and writes it to x29-0x38. This is a program protection added to prevent stack overflow.

Stack_guard effectively protects registers at the bottom of the stack from being overwritten. When a stack overflow occurs, the value of stack_guard will be overwritten first. Before the function returns, check whether stack_guard is valid to determine whether the stack overflow occurs.

0x18bfd2d6c <+212>: ldur   x8, [x29, #-0x38]
0x18bfd2d70 <+216> :adrp   x9, 196266
0x18bfd2d74 <+220> :ldr    x9, [x9, #0xf0]
0x18bfd2d78 <+224> :ldr    x9, [x9]
0x18bfd2d7c <+228> :cmp    x9, x8
0x18bfd2d80 <+232> :b.ne   0x18bfd2d9c               ; The < 260 > +

# __stack_chk_fail
0x18bfd2d9c <+260> :bl     0x18b03358c               ; __stack_chk_fail
Copy the code

This code takes the stack_guard from the same position on the stack, and then compares it to the original sentry value. If the value is not equal, it jumps directly to the stack overflow processing logic. The main purpose of this protection is to protect developers from strange and unknown bugs caused by stack overflow. However, stack overflow attacks cannot be effectively prevented, because attackers can deliberately construct stack overflow content and bypass inspection by forging sentries. Therefore, it is necessary to pay attention to improving code quality when writing low-level code and avoiding stack overflow vulnerabilities through CodeReview, static and dynamic scanning.

The first objc_msgSend analysis

Based on the above analysis, we omit the stack overflow protection code and get the following code:

0x18bfd2cb0 <+24> :mov    x19, x0
0x18bfd2cc8 <+48> :adrp   x8, 224951
0x18bfd2ccc <+52> :ldr    x1, [x8, #0x4c0]
0x18bfd2cd0 <+56> :mov    x0, x19
0x18bfd2cd4 <+60> :bl     0x18a72cd60               ; objc_msgSend
0x18bfd2cd8 <+64> :mov    x20, x0
Copy the code

As we know, the first argument to objc_msgSend is an instance of the class and the second argument is SEL. The key here is to calculate SEL from the indirection instruction to get the method called. There are two ways: The LLDB register read command can be used to obtain SEL value directly on line +60 because there is no branching logic in this code:

(lldb) register read x1
x1 = 0x00000001b8c67778  "length"
Copy the code

This is obviously a call to -[NSString Length], which takes the length of the OC string and stores it in x20:

x20 = [aClassName length]
Copy the code

The second objc_msgSend analysis

We then analyze the +68 through +108 code snippets:

0x18bfd2cdc <+68> :adrp   x8, 183685
0x18bfd2ce0 <+72> :add    x1, x8, #0x8b8            ; =0x8b8 
0x18bfd2ce4 <+76> :mov    x21, sp
0x18bfd2ce8 <+80> :mov    x2, sp
0x18bfd2cec <+84> :mov    w3, #0x3e8
0x18bfd2cf0 <+88> :orr    w4, wzr, #0x4
0x18bfd2cf4 <+92> :mov    x0, x19
0x18bfd2cf8 <+96> :bl     0x18a72cd60               ; objc_msgSend
0x18bfd2cfc <+100> :cmp    w0, #0x0                  ; =0x0 
0x18bfd2d00 <+104>: csel   x21, x21, xzr, ne
0x18bfd2d04 <+108> :cbz    w0, 0x18bfd2d18           ; The < 128 > +
Copy the code

Lines +68 through +92 are clearly preparing arguments for objc_msgSend. There are three more arguments besides the first two fixed inputs:

  1. Line +80 prepares the method’s first entry, sp, whose value is subtracted 0x40 at +0 and 0x3f0 at +20, i.e., stored at 0x430 from the bottom of the stack. This is a local pointer variable, and x21 stores the address of this pointer according to +76.
  2. Line +84 prepares the method’s second input parameter, 0x3E8
  3. Line +88 prepares the method’s third entry 0x4, where ORR is logical or, and WZR is Word zero register, similar to /dev/zero, with invalid writes and zero reads.

Using the same strategy, break at +96 to get SEL content:

(lldb) register read x1
x1 = 0x00000001b8d578b8  "getCString:maxLength:encoding:"
Copy the code

Based on the above analysis, this method call can be expressed as follows:

char *aClassNameC; // x21
x0 = [aClassName getCString:aClassNameC maxLength:0x3e8 encoding:0x4];
Copy the code

The subsequent lines +100 to +108 deal with the return value and CString of the function, where a CSEL instruction appears on line +104, which is a trinary operation in ARM:

cselWd, Wn, Wm, cond # is equivalent toWd = cond ? Wn : Wm
Copy the code

So the whole can be translated as:

char *aClassNameC; // x21
bool success = [aClassName getCString:aClassNameC maxLength:0x3e8 encoding:0x4];
aClassNameC = success ? aClassNameC : NULL;
if(! success) {goto 0x18bfd2d18;
}
Copy the code

This jump is an exception handling that converts an NSString to a CString with a growth of less than 1000, and we’ll examine this exception handling next.

Exception handling of NSString2CString

Starting at 0x18BFd2d18, another branch logic is involved:

0x18bfd2d18 <+128> :cbz    x20, 0x18bfd2d48          ; The < 176 > +
0x18bfd2d1c <+132> :mov    x21, #0x0
0x18bfd2d20 <+136> :adrp   x8, 183600
0x18bfd2d24 <+140> :add    x22, x8, #0x9ce           ; =0x9ce 
0x18bfd2d28 <+144> :mov    x0, x19
0x18bfd2d2c <+148> :mov    x1, x22
0x18bfd2d30 <+152> :mov    x2, x21
0x18bfd2d34 <+156> :bl     0x18a72cd60               ; objc_msgSend
Copy the code

NSString of length 0

X20 is the length of the input parameter ClassName. If conversion fails and the input parameter length is 0, 0x18BFd2d48 is jumped:

0x18bfd2d48 <+176> :adrp   x8, 183538
0x18bfd2d4c <+180> :add    x1, x8, #0x800            ; =0x800 
0x18bfd2d50 <+184> :mov    x0, x19
0x18bfd2d54 <+188> :bl     0x18a72cd60               ; objc_msgSend
0x18bfd2d58 <+192> :mov    x21, x0
0x18bfd2d5c <+196> :mov    x0, x21
0x18bfd2d60 <+200> :bl     0x18a7273e0               ; objc_lookUpClass
0x18bfd2d64 <+204> :b      0x18bfd2d6c               ; The < 212 > +
Copy the code

This code calls an OC method and then looks for class objects directly through objc_lookUpClass. Since the objc_lookUpClass entry is const char *, So this OC method must be a method of converting NSString to CString, and to determine the method, we need to solve SEL, and unlike this, there are two branches involved before that, so the code doesn’t necessarily go there, Therefore, we can no longer be lazy and register read to get SEL directly. Instead, we need to calculate the SEL manually. Here is how to calculate adRP addressing manually.

Calculate ADRP addressing manually

The length of the ARM64 instruction is 32 bits, so it is difficult for a single instruction to store a large offset, in the pc-based addressing, using the form of instruction split, in order to carry out +-4GB based on PC addressing, We first use ADRP to fetch the high 21 bits (@page) of the PAGE offset based on the binary base address into the register, and then add the low 12 bits (@pageoff) to get the target address. The SEL value process above is taken as an example:

0x18bfd2d48 <+176> :adrp   x8, 183538
0x18bfd2d4c <+180> :add    x1, x8, #0x800            ; =0x800 
Copy the code

We first empty the lower 12 bits of the address where ADRP is located to get 0x18BFD2000. 183538 is the value of the higher 21 bits in the 33 bits, so we need to move 33-21 to the left = 12 bits, and then add 0x800 to get the target address. The SEL value can be obtained by memory read:

(lldb) p/x 0x18bfd2000 + (183538 << 12) + 0x800
(long) The $1 = 0x00000001b8cc4800
(lldb) memory read 0x00000001b8cc4800
0x1b8cc4800: 55 54 46 38 53 74 72 69 6e 67 00 73 74 72 69 6e  UTF8String.strin
0x1b8cc4810: 67 57 69 74 68 43 68 61 72 61 63 74 65 72 73 3a  gWithCharacters:
Copy the code

This is a call to [aClassName UTF8String], so +176 to +204 can be translated as:

aClassNameC = [aClassName UTF8String];
Class clazz = objc_lookUpClass(aClassNameC);
goto 0x18bfd2d6c;
Copy the code

Next we look at the code 0x18BFD2d6c:

0x18bfd2d6c <+212>: ldur   x8, [x29, #-0x38]
0x18bfd2d70 <+216> :adrp   x9, 196266
0x18bfd2d74 <+220> :ldr    x9, [x9, #0xf0]
0x18bfd2d78 <+224> :ldr    x9, [x9]
0x18bfd2d7c <+228> :cmp    x9, x8
0x18bfd2d80 <+232> :b.ne   0x18bfd2d9c               ; The < 260 > +
0x18bfd2d84 <+236> :add    sp.sp.#0x3f0            ; =0x3f0 
0x18bfd2d88 <+240>: ldp    x29, x30, [sp.#0x30]
0x18bfd2d8c <+244>: ldp    x20, x19, [sp.#0x20]
0x18bfd2d90 <+248>: ldp    x22, x21, [sp.#0x10]
0x18bfd2d94 <+252>: ldp    x28, x27, [sp].#0x40
0x18bfd2d98 <+256>: ret    
0x18bfd2d9c <+260> :bl     0x18b03358c               ; __stack_chk_fail
Copy the code

Where +212 to +232 and +260 are stack overflow detection codes as mentioned above, +236 to +252 are recovery work before function return. After removing these codes, 0x18BFD2d6c only has one RET left, that is, function return. Therefore, the above code can be translated as:

aClassNameC = [aClassName UTF8String];
return objc_lookUpClass(aClassNameC);
Copy the code

NSString has a non-zero length

From +132 to +156:

0x18bfd2d1c <+132> :mov    x21, #0x0
0x18bfd2d20 <+136> :adrp   x8, 183600
0x18bfd2d24 <+140> :add    x22, x8, #0x9ce           ; =0x9ce 
0x18bfd2d28 <+144> :mov    x0, x19
0x18bfd2d2c <+148> :mov    x1, x22
0x18bfd2d30 <+152> :mov    x2, x21
0x18bfd2d34 <+156> :bl     0x18a72cd60               ; objc_msgSend
0x18bfd2d38 <+160> :cbz    w0, 0x18bfd2d68           ; The < 208 > +
0x18bfd2d3c <+164> :add    x21, x21, #0x1            ; =0x1 
0x18bfd2d40 <+168> :cmp    x21, x20
0x18bfd2d44 <+172> :b.lo   0x18bfd2d28               ; The < 144 > +
Copy the code

Using the method of hand calculation, we can get the SEL of this method to be characterAtIndex: :

(lldb) p/x 0x18bfd2000 + (183600 << 12) + 0x9ce
(long) $2 = 0x00000001b8d029ce
(lldb) memory read 0x00000001b8d029ce
0x1b8d029ce: 63 68 61 72 61 63 74 65 72 41 74 49 6e 64 65 78  characterAtIndex
0x1b8d029de: 3a 00 67 65 74 41 72 67 75 6d 65 6e 74 3a 61 74  :.getArgument:at
Copy the code

This code is a loop structure, where x21 is the iterator starting from 0 (+132), continuously fetch the x21 character (+152 to +156) from the aClassName, determine whether it is 0 (+160), if it is 0 jump to 0x18BFD2d68. If x21 is not 0, loop x21 +1 until the end of the string (+168 to + 172), where B. Lo is unsigned less than comparison.

// x20 is the length of aClassName
// x20 = [aClassName length];
for (int i = 0; i < aClassName.length; i++) {
    if ([aClassName characterAtIndex:i] == '\ 0') {
        goto 0x18bfd2d68; }}Copy the code

This code checks to see if NSString contains \0 in the middle. If it does, the exception is 0x18BFD2d68. This is because \0 is the terminator in CString, which will truncate the string during conversion, with unknown consequences. Next we look at 0x18BFD2d68 handling:

0x18bfd2d68 <+208> :mov    x0, #0x0
0x18bfd2d6c <+212>: ldur   x8, [x29, #-0x38]
0x18bfd2d70 <+216> :adrp   x9, 196266
0x18bfd2d74 <+220> :ldr    x9, [x9, #0xf0]
0x18bfd2d78 <+224> :ldr    x9, [x9]
0x18bfd2d7c <+228> :cmp    x9, x8
0x18bfd2d80 <+232> :b.ne   0x18bfd2d9c               ; The < 260 > +
Copy the code

+212 is the return of the function, so +208 sets the return value to 0, or nil.

for (int i = 0; i < aClassName.length; i++) {
    if ([aClassName characterAtIndex:i] == '\ 0') {
        returnnil; }}Copy the code

The code at the end of the loop starts with +176. This code, which we examined in the case of NSString length 0, calls objc_lookUpClass after converting NSString to UTF-8 encoded CString and returns. Combining the above analysis, we can get the following code snippet:

NSUInteger lengthOC = [aClassName length];
char *aClassNameC; // x21
bool success = [aClassName getCString:aClassNameC maxLength:0x3e8 encoding:0x4];
aClassNameC = success ? aClassNameC : NULL;
if(! success) {if (lengthOC == 0) {
        aClassNameC = [aClassName UTF8String];
        return objc_lookUpClass(aClassNameC);
    } else {
        for (int i = 0; i < aClassName.length; i++) {
            if ([aClassName characterAtIndex:i] == '\ 0') {
                return nil;
            }
        }
        aClassNameC = [aClassName UTF8String];
        returnobjc_lookUpClass(aClassNameC); }}Copy the code

Don’t be fooled by Xcode and LLDB

The following analysis of the successful branching logic for getCString, the code starting at +112, is much the same as above. I’ll leave it to the reader, but I’ll just cover the calls to line +116:

0x18bfd2d0c <+116> :bl     0x18c1391a8 ; symbol stub for: +[NSUnitElectricPotentialDifference megavolts]
Copy the code

This is a BUG in LLDB dynamic analysis. We can discompile 0x18C1391a8 to find out what it is:

(lldb) dis -a 0x18c1391a8
Foundation`+[NSUnitElectricPotentialDifference megavolts]:
0x18c1391a8 <+0> :adrp   x16, 195909
0x18c1391ac <+4> :ldr    x16, [x16, #0x8b8]
0x18c1391b0 <+8> :br     x16
Copy the code

X16 = x16; x16 = x16;

(lldb) p/x 0x18c139000 + (195909 << 12) + 0x8b8
(long) $3 = 0x00000001bbe7e8b8
(lldb) memory read 0x00000001bbe7e8b8
0x1bbe7e8b8: 80 23 15 8b 01 00 00 00 38 06 03 8b 01 00 00 00  .#... 8...
0x1bbe7e8c8: f0 23 15 8b 01 00 00 00 1c 1c 15 8b 01 00 00 00  .#...
(lldb) dis -a 0x018b152380
libsystem_platform.dylib`_platform_strlen:
    0x18b152380 <+0>:  and    x1, x0, #0xfffffffffffffff0
    0x18b152384 <+4>:  ldr    q0, [x1]
Copy the code

It turns out to be Strlen. If you just take Xcode’s comments at face value, the analysis won’t work, which tells us to take things with a grain of salt.

Complete analysis results

Note that this is code translated exactly as a control flow, with a lot of repetitive logic that can be simplified.

NSClassFromString(NSString *aClassName) {
    if (aClassName == nil) {
    	return nil;
    }
    NSUInteger lengthOC = [aClassName length];
    char *aClassNameC;
    bool success = [aClassName getCString:aClassNameC maxLength:0x3e8 encoding:0x4];
    aClassNameC = success ? aClassNameC : NULL;
    if(! success) {if (lengthOC == 0) { 
    	    aStringNameC = [aClassName UTF8String];
    	    return objc_lookUpClass(aStringNameC);
    	} else {
    	    for (int i = 0; i < lengthOC; i++) {
    	    	if ([aClassName characterAtIndex:0] = ='\ 0') {
                    return nil;
                }
            }
            aStringNameC = [aClassName UTF8String];
            returnobjc_lookUpClass(aStringNameC); }}else {
    	if (strlen(aClassNameC) == lengthOC) {
            return objc_lookUpClass(aClassNameC);
    	} else if (lengthOC == 0) {
            aStringNamcC = [aClassName UTF8String];
            return objc_lookUpClass(aStringNameC);
    	} else {
    	    for (int i = 0; i < lengthOC; i++) {
    	    	if ([aClassName characterAtIndex:0] = ='\ 0') {
                    return nil;
                }
            }
            aStringNameC = [aClassName UTF8String];
            returnobjc_lookUpClass(aStringNameC); }}}Copy the code

conclusion

While it is still a waste of time to implement dynamic analysis functions directly based on LLDB, there are tools that can be packaged in LLDB to assist analysis, such as stack overflow protection code identification and removal, automatic parsing loops, automatic parsing of indirection, and so on, which will be covered in a subsequent article.