preface
For application layer developers, only mastering Objective-C and system framework can complete development well, but only application layer development skills are very weak when it comes to application reinforcement, reverse analysis and other content. Therefore, mastering assembly is very effective for breaking the bottleneck of iOS development level.
A case in point
In the case of undebugging, we know that we can prevent the debugger from attaching by calling the ptrace function.
ptrace(31.0.0.0)
Copy the code
This approach can be easily broken by hook functions, such as Facebook’s FishHook. To prevent the function from being hooked, we can convert the function call to an assembler system call, using the following code.
mov x0, #31
mov x1, #0
mov x2, #0
mov x3, #0
mov x16, #26
svc #0x80
Copy the code
Where x0-x3 stores the function entry parameter, x16 stores the function number. According to the System Call Table provided by Apple, the number of Ptrace can be found to be 26. The last line of instruction initiates the System Call. By using the __asm__ directive, we can embed assembly code in our functions, constituting an undebugging method.
// Use inline to force the function to expand at the call to prevent being hooked and tracing symbols
static __attribute__((always_inline)) void anti_debug() {
// Check whether it is an ARM64 processor instruction set
#ifdef __arm64__
// The volatile modifier prevents assembly instructions from being ignored by the compiler
__asm__ __volatile__(
"mov x0, #31\n"
"mov x1, #0\n"
"mov x2, #0\n"
"mov x3, #0\n"
"mov x16, #26\n"
"svc #0x80\n"
);
#endif
}
Copy the code
While the above de-debugging mechanism is not perfect, it is many times better than calling pTrace directly, and from this point of view, mastering assembly skills is very beneficial for iOS application security and underlying research.
Entry strategy
IOS devices mostly use ARM64 assembler, so this article will focus on getting started with ARM64 assembler. The most difficult part of assembly is to understand the stack. All instruction operations of assembly are implemented around the stack. In assembly, there is no concept of variables, only registers and memory.
The stack
The stack in assembly is a data structure that grows from high address to low address. Sp pointer always points to the top of the stack. Remember that when storing at a certain location, it is to the high address. Let’s take a simple piece of C code as an example.
// hello.c
#include <stdio.h>
int test(int a, int b) {
int res = a + b;
return res;
}
int main(a) {
int res = test(1.2);
return 0;
}
Copy the code
Using Clang, you can compile it into assembly code for a specific instruction set; here we compile it into assembly code for the ARM64 instruction set.
clang -S -arch arm64 -isysroot `xcrun --sdk iphoneos --show-sdk-path` hello.c
Copy the code
The complete assembly code is shown below.
.section __TEXT,__text,regular,pure_instructions
.ios_version_min 11, 2
.globl _test
.p2align 2
_test: ; @test
; BB#0:
sub sp, sp, #16 ; =16
str w0, [sp, #12]
str w1, [sp, #8]
ldr w0, [sp, #12]
ldr w1, [sp, #8]
add w0, w0, w1
str w0, [sp, #4]
ldr w0, [sp, #4]
add sp, sp, #16 ; =16
ret
.globl _main
.p2align 2
_main: ; @main
; BB#0:
sub sp, sp, #32 ; =32
stp x29, x30, [sp, #16] ; 8-byte Folded Spill
add x29, sp, #16 ; =16
orr w0, wzr, #0x1
orr w1, wzr, #0x2
stur wzr, [x29, #-4]
bl _test
mov w1, #0
str w0, [sp, #8]
mov x0, x1
ldp x29, x30, [sp, #16] ; 8-byte Folded Reload
add sp, sp, #32 ; =32
ret
.subsections_via_symbols
Copy the code
In this section we are only talking about stack operations, so we ignore the main and printf calls and look only at the test calls.
sub sp, sp, #16 ; =16
str w0, [sp, #12]
str w1, [sp, #8]
ldr w0, [sp, #12]
ldr w1, [sp, #8]
add w0, w0, w1
str w0, [sp, #4]
ldr w0, [sp, #4]
add sp, sp, #16 ; =16
ret
Copy the code
The best way to inquire how to use a certain instruction is to inquire the official document provided by ARM company. On the official document page, you can directly search the instruction and check the usage and routine. This paper will briefly explain the instructions in the above assembly code.
Sub is used to subtract a register. Sub A, b, c is equivalent to a = b-C. In ARM assembly, the destination operand usually comes first, for example, mov RA, rb stands for copying the value of rb register to ra register. Add and sub do the same thing, except they turn subtraction into addition.
STR and LDR are a pair of instructions. STR stands for Store Register, which stores the value of a register into memory. LDR stands for Load Register, which reads the value from memory into a register. [sp, #12] represents the address sp+12, and [sp, #-12] represents the address sp-12. For example, STR w0, [sp, #12], w is a 4-byte register. This instruction represents storing the value of register W0 at sp+12. Since W0 has 4 bytes, it will occupy the memory area sp+12 to SP +16.
When the compiler generates assembly, it first calculates the required stack space and uses the SP pointer to open the corresponding space to the lower address. Let’s look at the test function.
int test(int a, int b) {
int res = a + b;
return res;
}
Copy the code
Here involves three int variables, respectively is a and b, the res, int variables occupy 4 bytes, so need a total of 12 bytes, but ARM64 assembly in order to improve the efficiency of access for 16-byte aligned, so need 16 byte space, also is the need to create 16 bytes of space on the stack, we come to the first line of the assembly, It is to move the SP pointer down 16 bytes.
sub sp, sp, #16
Copy the code
Sp drops 16, leaving four 4-byte memory Spaces for a total of 16 bytes. Let’s continue with the following sentence.
str w0, [sp, #12]
str w1, [sp, #8]
Copy the code
X0 and w0 are different sizes of the same register. X0 is 8 bytes, w0 is the first 4 bytes of x0, and w0 is the first 4 bytes of x0. Therefore, w0 is the first input parameter a of the function, and w1 is the second input parameter B of the function. Since the storage is from the low address to the high address, A will occupy SP +12~ SP +16, and similarly, B will occupy SP +8~sp+12. Then the stack structure becomes the following figure.
According to god’s view, the test function should then add a and B. Note that only registers are involved, so the assembly code reads the values of the variables out of memory and adds them.
ldr w0, [sp, #12]
ldr w1, [sp, #8]
add w0, w0, w1
Copy the code
Thus, it can be seen that storing and then reading and then computing is actually redundant, which is the result of no compilation optimization. Learning assembly without compilation optimization can make us understand its working mechanism better.
The following code stores W0 into sp+4, the memory region of the RES variable.
str w0, [sp, #4]
Copy the code
The next step is to return. As mentioned in the example, the return value of a function is usually stored in the X0 register, so we need to load the value of res into the X0 register.
ldr w0, [sp, #4]
Copy the code
The w register is used here because int is 4 bytes, which is why the type conversion causes information loss. For example, converting from long to int is similar to storing the value of register X as w. The final code is to restore the stack and return to the function call to continue.
add sp, sp, #16
ret
Copy the code
Obviously, after the operation, the stack is fully restore before the function call, need to pay attention to detail is, stack space memory in the unit has not been cleared, this also leads to the next use low address stack, uninitialized unit value is uncertain, which is a local variable is not the root cause of the random initialization values.
Through the above examples, we have a basic understanding of the stack, assembly operations are basically on the stack, as long as you understand the stack mechanism, just need to learn a variety of instructions, you can master enough to use the assembly skills.
in-depth
After knowing the stack, you can see some of the more complicated assembly section to study, the primary stage can try looking at function to write assembly code, is required for advanced stage at assembly logic, as a function of reduction foundation for simply introduced in this paper, the following recommendations for Daniel’s blog study assembly skills.
1. Zhihu column of Zhibing
2. Liu Kun’s introduction to assembly
conclusion
Mastering ARM assembly can help developers better understand the compiler and CPU working principle, in addition to guide coding, but also to broaden the horizon, through decomcompiling analysis of some closed source code logic or some security hardening, so it is very worthwhile to spend time on assembly.
The resources
1. Zhibing: iOS debugging advanced
2.ARM official documents
3. Reverse debugging and bypass