Search code examples
cassemblyarmmemory-addresskeil

Determining return address of function on ARM Cortex-M series


I want to determine the return address of a function in Keil. I opened diassembly section at debugging mode in Keil uvision. What is shown is some assembly code like this:

enter image description here

My intention is to inject a simple binary code to microcontroller via using buffer overflow at microcontroller.see: Buffer overflow I want to determine the return address of "test" function . Is it a must to know how to read assembly code or are there any trick to find the return address?

I am newbie to assembly.


Solution

  • When a function is called, R14 will be overwritten with the address following the call ("BL" or "BLX") instruction. If that function doesn't call any other functions, R14 will often be left holding the return address for its duration. Further, if the function tail-calls another function, the tail call may be replaced with a branch ("B" or "BX"), with R14 holding the return address of the original caller. If a function makes a non-tail call to another function, it will be necessary to save R14 "somewhere" (typically the stack, but possibly to another previously-used caller-saved register) at some time before that, and retrieve that value from the stack at some later time, but if optimizations are enabled the location where R14 is saved will generally be unpredictable.

    Some compilers may have a mode that would stack things consistently enough to be usable, but code will be very compiler-dependent. The technique most likely to be successful may be to do something like:

    extern int getStackAddress(uint8_t **addr);  // Always returns zero
    void myFunction(...whavever...)
    {
      uint8_t *returnAddress;
      if (getStackAddress(&returnAddress)) return; // Put this first.
    }
    

    where the getStackAddress would be a machine-code function that stores R14 to the address in R0, loads R0 with zero, and then branches to R14. There are relatively few code sequences that would be likely to follow that, and if a code examines instructions at the address stored in returnAddress and recognizes one of these code sequences, it would know that the return address for myFunction is stored in a spot appropriate for the sequence in question. For example, if it sees:

        test r0,r0
        be ...
        pop {r0,pc}
    

    It would know that the caller's address is second on the stack. Likewise if it sees:

        cmp r0,#0
        bne somewhere:
    somewhere: ; Compute address based on lower byte of bne
        pop {r0,r1,r2,r4,r5,pc}
    

    then it would know that the caller's address is sixth.

    There are a few instructons compilers could use to test a register against zero, and some compilers might use be while others use bne, but for the code above compilers would be likely to use the above pattern, and so counting how many bits are set in the pop instruction would reveal the whereabouts of the return address on the stack. One wouldn't know until runtime whether this test would actually work, but in cases where it claims to identify the return address it should actually be correct.