How do processors know the order of registers' values?

Viewed 130

In assembly one can pass values via less volatile registers or volatile registers. I can for instance pass arguments to printf using edi and esi I can also instead use ebx and ecx This example is a very simple contrived one. I'm more curious to how this works with much more intricate programs calling multiple functions from libc.

For instance in Return Oriented Programming attacks, an attacker can use gadgets to use the same registers used for a previous function to pop new values from the stack into them and then return to another libc function that uses the same register(s), for instance with write and read one could use pop rsi in ROP attacks to use either function if they've leaked the global offset table. My overall question could be asked this way:

If an attacker inherits registers from a previous call to read like so:

    0x00005555555552d0 <+107>:   lea    rcx,[rbp-0xd0] <- Memory address of buffer "msg"
    0x00005555555552d7 <+114>:   mov    eax,DWORD PTR [rbp-0xe4] <- contains client fd 0x4
    0x00005555555552dd <+120>:   mov    edx,0x400 <- 1024 (size of bytes to write to memory location/buffer)
    0x00005555555552e2 <+125>:   mov    rsi,rcx
    0x00005555555552e5 <+128>:   mov    edi,eax
    0x00005555555552e7 <+130>:   call   0x5555555550d0 <read@plt>

How does the processor know which arguments to supply write to if the registers passed to write are different:

    0x00005555555552b1 <+76>:    call   0x555555555080 <strlen@plt>
    0x00005555555552b6 <+81>:    mov    rdx,rax <- store return value from strlen into rdx
    0x00005555555552b9 <+84>:    lea    rcx,[rbp-0xe0] <- message to write
    0x00005555555552c0 <+91>:    mov    eax,DWORD PTR [rbp-0xe4] <- client file descriptor
    0x00005555555552c6 <+97>:    mov    rsi,rcx
    0x00005555555552c9 <+100>:   mov    edi,eax
    0x00005555555552cb <+102>:   call   0x555555555060 <write@plt>  

Clearly read does not use rdx and write does not use edx, so how does the processor know which to choose, for example if an attacker only used a gadget that pops a value into rsi?

I can't seem to understand how the processor knows which registers to chose from (rdx or edx). How do processors select values to pass to libc functions or functions/routines for that matter in general?

2 Answers

The processor doesn't know anything; the registers aren't indexable and the only order they have as far as the CPU is concerned are the register numbers used in machine code. (And for stuff like save-multiple-register instructions like legacy 32-bit mode pusha / popa, or xsave to save the FPU / SIMD state.)

What looks for args in certain places in the called function is... more code (software), generated by a compiler that compiled a function with its args declared a certain way. Remember, printf is just more software, not built-in to the CPU.

The compiler knows the standard calling convention for the target platform (defined in the x86-64 System V ABI in this case), so having both caller and callee agree on a calling convention results in calling code that will put args in the places that callees look for them.

Standardizing this calling convention is how we can link together code from different compilers into one program, and make calls into libraries.

BTW, the same goes for making system calls; you put a call number into a certain register and run an instruction that switches to kernel mode (e.g. syscall). Now the kernel is running, and can look at the values still in registers. It uses the call number to index a table of function pointers, calling it with the other args in the standard arg-passing registers. (Or wherever they need to go according to the C calling convention, which is typically different from the system-call calling convention.)

What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64

The processor is very dumb it knows nothing. Literally it only does what the instructions say to do and the instructions are ultimately written by the programmer directly or indirectly (via compilation). The compiler knows because of the calling convention decided on by the compiler authors and they are free to choose the convention they want for that target, they do not have to conform to an specific previously defined convention. If they happen to it is their free choice to do so. At the end of the day the compiler authors know and build the compiler around that...The processor does only what it is told it cannot think for itself.

Related