IOS underlying principles + reverse article summary

The main purpose of this article is to understand the function stack and the related instructions involved

Before we get into the nature of functions, we need to talk about the following concepts stack, SP, FP

Common sense

The stack

  • Stack: Is oneStorage space with special access modesFirst In First, Last In Out First,LIFO)

– Store data from high address to low address (Storage: high --> low)

- Stack space open: open to low address (' open: high --> low ')Copy the code

SP and FP registers

  • SP register: saves the top address of the stack at any time

  • FP register (also known as X29 register) : A general purpose register, but at certain points (such as when a function is nested) it can be used to hold the address at the bottom of the stack

Note:

  • In arm64, the 32-bit LDM, STM, PUSH, POP instructions were cancelled and replaced by LDR/LDP, STR/STP (the difference between R and P is the number of registers to process, r means 1 register to process, p means 2 registers to process).

  • In ARM64, stack operations are 16 bytes aligned!!

Here is a comparison between before and after ARM64

  • Before the ARM64, the stack top pointer was one cell per data move when the stack was pressed

  • Starting with ARM64, there is no push or pop operation, since the stack space is created from the higher address to the lower address (as determined by the compiler) and then the data is put in. In this case, it can be manipulated by memory read/write instructions (LDR/LDP, STR/STP)

Function call stack

The following is common function calls open up (sub) and restore stack space (add) assembly code

Sub sp, sp, #0x40; Stretch 0x40 (64 bytes) space STP x29, x30, [sp, #0x30]; X29 \x30 register push protection add x29, sp, #0x30; X29 points to the bottom of the stack frame... ldp x29, x30, [sp, #0x30] ; // Restore stack space add sp, sp, #0x40; Stack balance retCopy the code

Memory read and write instruction

  • STR (Store register) instructions (specialized instructions that interact with memory and registers) : read data from a register and store it in memory (i.e., a register is 8 bytes to 64 bits)

  • LDR (Load register) instruction: to read data from memory and save it in a register

  • LDR and STR variants LDP and STP can also operate 2 registers (i.e. 128-16 bytes).

Note:

  • Read/write data is read/write to a higher address

  • Write data: Stretch the stack space first and then write data with SP, that is, apply for space first and then write data

practice

Use 32 bytes of space as the stack space for this program, and then use the stack to swap the values of X0 and x1

sub sp, sp, #0x20 ; Stretch stack space by 32 bytes STP x0, x1, [sp, #0x10]; LDP x1, x0, [sp, #0x10]; Add sp, sp, #0x20; add sp, sp, #0x20; Stack balancing RET; returnCopy the code

The stack operation is shown in the following figure

Debug view stack

  • Rewrite the values of x0 and x1

  • register read spCheck the stack storage status:debug - debug workflow - view Memory

  • We then step down to find that x0 and x1 have become the values we wrote

) Check the memory change, found that SP stretched 32 bytes

  • stp x0, x1, [sp, #0x10]: Write x0 and x1 to the position offset 0x10 of FP, and proceed to the next step

The value of sp doesn’t change, it’s going to point to 40

  • ldp x1, x0, [sp, #0x10]: read the data of x0, x1, and exchange, continue to execute the next step, at this time memory is not changed

doubt: Is there any change in SP? In terms of results, there was no change. So hereThe swap is read out and does not cause memory changes

  • add sp, sp, #0x20: Go ahead and go toThe stack balancing, i.e.,Sp recoveryAt this point, a and B are still in memory, waiting to be overwritten after the next stack stretch. If you read at this point, you are reading junk data

Question: the stack space continues to open up, endless loop, will crash?

Here we will address some of the issues left over from the last iOS Reverse 01: Initial Assembly article

Let’s demonstrate this with an assembly code

<! --asm.s--> .text .global _B _B: sub sp,sp,#0x20 stp x0,x1,[sp,#0x10] ldp x1,x0,[sp,#0x10]; Bl _B add sp,sp,#0x20 ret <! -- call --> int B(); int main(int argc, char * argv[]) { B(); }Copy the code

Run the result: the infinite loop will crash, resulting inStack overflow

Bl and RET instructions

  • Label B: Jump

  • Bl label

    • Put the address of the next instruction inLr (X30) register(LR saves the way home)
    • Go to the label to execute the instruction (i.e. B)

When B functions ret, get the way home from LR (note: LR is saved as the way home)

  • ret
    • By default, the value of the LR (X30) register is used, and the underlying instruction prompts the CPU here as the next instruction address

    • Arm64 platform characteristic instruction, it has done the hardware optimization processing

practice

The following bl and RET instructions are demonstrated in assembly code

.text
.global _A, _B

_A:
    mov x0. #0xaaaa
    bl _B
    mov x0, #0xaaaa
    ret

_B:
    mov x0, #0xbbbb
    ret
Copy the code
  • Breakpoint run

doubt: You have A few assembly operations between A and print. What does this mean?

  • performmov x0. #0xaaaa: x0 becomes aaaa, lr register saves 5F34 at this moment

  • validationlrIf 5F34 is saved, check the register and find that the result is as expected

  • Continue to performbl _B, jump to B, then LR will become the address 5eb8 of the next instruction of BL in A

  • Finish the execution in Bmov x0, #0xbbbbX0 becomes BBBB

  • In execution BretI’m going to go back to 5eb8 in A

  • Continuing with the RET in A will bring you back to 5eb8 again

When I got here, I found an infinite loop, mainly becauselrIt’s always 5eb8,Ret will only look at LR. Among thempcIs the address of memory to be executed next, ret is to have the CPU set LR as the address of the next execution (equivalent to assigning LR to PC)

Question 1: At this point B back to A no problem, so A back to viewDidload how to return?

  • It needs to be before the BL of AProtect LR register
    • Question 2: Can it be saved to another register?

    The answer is no, because it’s not safe, because you’re not sure when the register is going to be used by someone else, right

    • In: Save to the stack area

How does a nested function return in the system? D -> C -> viewDidLoad

void d(){
}
void c(){
    d();
    return;
}
- (void)viewDidLoad{
    [super viewDidLoad];
    printf("A");
    c();
    printf("B");
}
Copy the code
  • Looking at assembly, the breakpoint is in c function

  • Enter the assembly of C functions

stp x29,x30,[sp,#-0x10]!: while opening the stack, while writing, whereX29 is FP, and x30 is LR.!That means I’m going to assign to sp what I’m doing here

- 'LSP x29,x30,[sp],#0x10' : read the data that sp points to, put it in x29,x30, and then ',#0x10 'indicates that sp+0x10 is assigned to spCopy the code
  • conclusion: When there are nested function calls, willThe address of the last functionthroughX30 (i.e. Lr)On theThe stack to saveMake sure you can find your way home, as shown in the picture below

Custom assembly code improvement: save the way home in _A so according to the system’s function nesting operation, finally add the following assembly code in _A, used to save the way home

<! #0xaaaa bl _B mov x0, #0xaaaa ret <! -- Added LR save: can find the way home --> _A: Sub sp, sp, #0x10 // stretch x30, [sp] // save lr x0, #0xaaaa // save LR x0, #0xaaa LDR x30, [sp] Add sp, sp, #0x10 // stack balance retCopy the code

Change _A and _B to shorthand

  • Among themlrisx30One of theThe alias
_A: Sub sp, sp, #0x10 // stretch x30, [sp] // save lr x0, #0xaaaa // save LR x0, #0xaaa LDR x30, [sp] Add sp, sp, #0x10 // stack balance ret _B: mov x0, #0xbbbb ret <! --> _A --> _A [sp, #-0x10] mov x0, #0xaaaa, [sp, #-0x10] mov x0, #0xaaaa //add sp, sp, #0x10 // stack balance LDR x30, [sp], #0x10 // stack balance LDR x30, [sp], #0x10 // stack balance LDR x30, [sp], #0x10 // stack balance LDR x30, [sp], #0x10 // stack balance Give to x30, then sp += 0x10 ret _B: mov x0, #0xbbbb retCopy the code

Breakpoint debugging

  • Check the address of the SP register at this time

  • performstr x30, [sp, #-0x10], continue to check SP, found that SP changed, but lr did not change at this time

To view0x16f5a1c50In the memory oflrThe value of the861f2cThe address of the next bl instruction in ViewDidLoad, currently only puts 8 bytes (1 register)

  • In executing Amov x0, #0xaaaa:x0变成aaaa

  • In executing Abl _B, jump to BlrIt becomes 1e94, and x0 becomes BBBB

  • The implementation of Bret: from B to A, lr is still 1e94

  • In executing Aldr x30, [sp], #0x10

Discovered this time SP also changed, from0x16f5a1c50->0x16f5a1c60. From here, we can see that A has found his way home

Question: Why stretch 16 bytes instead of 8? By manual attempt, the following instructions are provided:

  • Write ok

  • Crash on read: since stack operations must be 16-byte aligned in SP, it will crash on stack operations

X30 register

  • The x30 register stores the return address of the function. When the RET instruction is executed, the address value stored in the X30 register will be looked for

  • Note: X30 needs to be pushed when a function is nested

  • Lr is an alias for X30

  • Sp stack operations must be 16 bytes aligned, crash is on the stack operation

conclusion

  • Stack: A storage space with a special access mode (Last in First out, LIFO)

    • ARM64 in faceThe operation of the stackis16-byte alignmentthe
  • SP and FP registers

    • SPThe register will at any timeSave the top of the stack address
    • FPRegisters are also calledx29Register, which is a general purpose register, but is used at some pointSave the address at the bottom of the stack
  • Stack read and write instructions

    • Read: Load Register (LDR) directive LDR and LDP

    • Write: store register (STR) instruction STR and STP

  • Assembly practice

    • instruction
      • sub sp,sp,$0x10 ; Stretch stack space by 18 bytes

      • stp x0,x1,[sp] ; X0 and X1 are stored in sp

    • shorthand
      • str x0,x1,[sp,$-0x10]! (! The result in [] is assigned to sp)
  • Bl instruction

    • Jump instruction: BL label, indicating that the program is executed to the label, and the address of the next instruction is saved to the LR register

    • B stands for jump

    • L indicates that LR (X30) stores the IOS_reverse_02

  • Ret instruction

    • Functionally likereturn
    • Causes the CPU to execute the instruction pointed to by the LR register
  • Avoid nested functions that cannot go back: need to protect the BL (i.e. LR register, save the way home) in the current function’s own stack space