两者之间有什么区别吗?
JMP EDI 和
PUSH EDI
RET什么样的c代码会以这种方式反汇编?动态解析的函数指针,然后被调用?
发布于 2020-02-27 23:35:39
如果您正在寻找ROP小工具,volatile int tmp = 0xffe7ffe4将编译为mov,其立即数包含JMP EDI和JMP ESP的机器代码字节。但当然,当正常执行时,它只是mov dword [esp], 0xffe7ffe4或编译器发出的任何东西。该0xffe7常量的任何其他用法都可以编译为包含它的立即数的机器码。
编译器永远不会发出推送/返回。它破坏了返回地址预测器堆栈,导致在随后的调用堆栈向上返回时发生错误预测。即使是retpoline通常也只使用调用/修改堆栈/ret。(GCC将使用-mindirect-branch=thunk自动发出retpolines )。当然,您也可以将这个3字节的序列作为立即数。
jmp edi可能会作为编译器输出的普通部分出现(而不仅仅是作为另一条指令的一部分)。但这不太可能。在32位调用约定中,edi是调用保留的,因此您不会发现jmp edi实现了尾部调用;在返回或尾部调用之前必须恢复寄存器。如果你让GCC在寄存器中保存一个函数指针,它会在尾调用之前用mov eax, edi / pop edi / jmp eax来恢复它的调用者的值。(Godbolt)。
如果你使用了足够多的其他局部变量,以至于编译器的寄存器分配器选择了call edi,那么使用局部函数指针就不难获得EDI。但不是jmp。
在x86-64system V中,RDI是一个arg传递寄存器和调用阻塞寄存器,但是当然指针是64位的,所以你总是会得到jmp rdi。在ILP32变体( x32 ABI)中,调用约定显然仍然需要将指针args从零扩展到64位,因为是GCC -mx32 still emits jmp rdi,而不是jmp edi。(使用mov eax,edi /jmp rax,Clang将截断为32位)。禁用优化是没有用的;编译器随后只会存储到堆栈中并重新加载到EAX中。
gcc9.2的工作示例
一个不错的选择是GNU C computed-goto to the address of a local label
// function arg and target both need call-preserved registers to survive across ext()
int use_local_label(int *p)
{
void *volatile target_launder = &&label1;
void *target = target_launder; // defeat constant-propagation
label1:
ext(); // non-inline function call forces using call-preserved registers
if (++*p == 0) target = &&label2;
goto *target;
return 0;
label2:
return 1;
} -O2 -m32 -fcall-used-esi (而不是使用第三个本地变量,我只是告诉编译器ESI是被调用的,以阻止它在EDI之前选择它。p和target最终分别进入了EBX和EDI。)
use_local_label:
push edi # save call-preserved regs
push ebx
sub esp, 20
mov DWORD PTR [esp+12], OFFSET FLAT:.L4 # volatile init
mov ebx, DWORD PTR [esp+32] # p = function arg
mov edi, DWORD PTR [esp+12] # target = volatile reload
.L4:
call ext
add DWORD PTR [ebx], 1
je .L5 # if = conditional jump over the goto
jmp edi # goto *target, normally to .L4
.L5:
add esp, 20
mov eax, 1
pop ebx
pop edi
rethttps://stackoverflow.com/questions/60428627
复制相似问题