处理器如何知道寄存器值的顺序?
How do processors know the order of registers' values?
在汇编中,可以通过不易失性寄存器或易失性寄存器传递值。例如,我可以使用 edi
和 esi
将参数传递给 printf
我也可以使用 ebx
和 ecx
这个例子是一个非常简单的人为设计的例子。我更好奇这如何与更复杂的程序一起工作,从 libc
.
调用多个函数
例如,在 Return 面向编程 攻击中,攻击者可以使用小工具来使用与先前函数相同的寄存器,将新值从堆栈弹出到它们然后 return 到另一个使用相同寄存器的 libc
函数,例如 write
和 read
可以在 [= 中使用 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?
我似乎无法理解处理器如何知道要从哪些寄存器中选择(rdx
或 edx
)。一般而言,处理器如何将 select 值传递给 libc
函数或 functions/routines?
处理器很笨,什么都不知道。从字面上看,它只执行指令所说的操作,指令最终由程序员直接或间接(通过编译)编写。编译器知道,由于编译器作者决定的调用约定,他们可以自由选择他们想要的目标约定,他们不必遵守特定的先前定义的约定。如果他们碰巧这样做是他们的自由选择。归根结底,编译器作者知道并围绕它构建了编译器......处理器只做它被告知它不能自己思考的事情。
处理器什么都不知道;寄存器不可索引,就 CPU 而言,它们唯一的顺序是机器代码中使用的寄存器编号。 (对于诸如遗留 32 位模式 pusha
/ popa
或 xsave
之类的保存多寄存器指令,以保存 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
在汇编中,可以通过不易失性寄存器或易失性寄存器传递值。例如,我可以使用 edi
和 esi
将参数传递给 printf
我也可以使用 ebx
和 ecx
这个例子是一个非常简单的人为设计的例子。我更好奇这如何与更复杂的程序一起工作,从 libc
.
例如,在 Return 面向编程 攻击中,攻击者可以使用小工具来使用与先前函数相同的寄存器,将新值从堆栈弹出到它们然后 return 到另一个使用相同寄存器的 libc
函数,例如 write
和 read
可以在 [= 中使用 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?
我似乎无法理解处理器如何知道要从哪些寄存器中选择(rdx
或 edx
)。一般而言,处理器如何将 select 值传递给 libc
函数或 functions/routines?
处理器很笨,什么都不知道。从字面上看,它只执行指令所说的操作,指令最终由程序员直接或间接(通过编译)编写。编译器知道,由于编译器作者决定的调用约定,他们可以自由选择他们想要的目标约定,他们不必遵守特定的先前定义的约定。如果他们碰巧这样做是他们的自由选择。归根结底,编译器作者知道并围绕它构建了编译器......处理器只做它被告知它不能自己思考的事情。
处理器什么都不知道;寄存器不可索引,就 CPU 而言,它们唯一的顺序是机器代码中使用的寄存器编号。 (对于诸如遗留 32 位模式 pusha
/ popa
或 xsave
之类的保存多寄存器指令,以保存 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