处理器如何知道寄存器值的顺序?

How do processors know the order of registers' values?

在汇编中,可以通过不易失性寄存器或易失性寄存器传递值。例如,我可以使用 ediesi 将参数传递给 printf 我也可以使用 ebxecx 这个例子是一个非常简单的人为设计的例子。我更好奇这如何与更复杂的程序一起工作,从 libc.

调用多个函数

例如,在 Return 面向编程 攻击中,攻击者可以使用小工具来使用与先前函数相同的寄存器,将新值从堆栈弹出到它们然后 return 到另一个使用相同寄存器的 libc 函数,例如 writeread 可以在 [= 中使用 pop rsi 22=] 攻击,如果它们泄露了 全局偏移量 table,则使用任一函数。我的总体问题可以这样问:

如果攻击者从之前对 read 的调用中继承寄存器,如下所示:

    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>

如果传递给 write 的寄存器不同,处理器如何知道要写入哪些参数:

    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>  

显然 read 不使用 rdx 并且 write 不使用 edx,那么处理器如何知道选择哪个,例如如果攻击者只使用 a gadget that pops a value into rsi?

我似乎无法理解处理器如何知道要从哪些寄存器中选择(rdxedx)。一般而言,处理器如何将 select 值传递给 libc 函数或 functions/routines?

处理器很笨,什么都不知道。从字面上看,它只执行指令所说的操作,指令最终由程序员直接或间接(通过编译)编写。编译器知道,由于编译器作者决定的调用约定,他们可以自由选择他们想要的目标约定,他们不必遵守特定的先前定义的约定。如果他们碰巧这样做是他们的自由选择。归根结底,编译器作者知道并围绕它构建了编译器......处理器只做它被告知它不能自己思考的事情。

处理器什么都不知道;寄存器不可索引,就 CPU 而言,它们唯一的顺序是机器代码中使用的寄存器编号。 (对于诸如遗留 32 位模式 pusha / popaxsave 之类的保存多寄存器指令,以保存 FPU / SIMD 状态。)

在被调用函数的某些地方寻找 args 的是...更多代码(软件),由编译器生成,该编译器编译一个函数,其 args 以某种方式声明。请记住,printf 只是更多软件,而不是内置于 CPU。

编译器知道目标平台的标准调用约定(在这种情况下在 x86-64 System V ABI 中定义),因此调用者和被调用者都同意调用约定会导致调用代码将 args在被叫者寻找它们的地方。

标准化此调用约定是我们如何 link 将来自不同编译器的代码合并到一个程序中,并调用库。

顺便说一句,进行系统调用也是如此;您将调用号放入某个寄存器和 运行 切换到内核模式的指令(例如 syscall)。现在内核是 运行ning,可以查看寄存器中的值。它使用调用号来索引 table 函数指针,并使用标准参数传递寄存器中的其他参数调用它。 (或者他们需要根据 C 调用约定去的任何地方,这通常不同于系统调用调用约定。)

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