Если вы ищете гаджет ROP, volatile int tmp = 0xffe7ffe4
скомпилируется в mov
, чей непосредственный объект содержит байты машинного кода для JMP EDI и JMP ESP. Но, конечно, когда выполняется нормально, это просто mov dword [esp], 0xffe7ffe4
или что-то, что испускал компилятор. Любое другое использование этой константы 0xffe7
может компилироваться в машинный код, который включает его как непосредственный.
Компиляторы никогда не будут генерировать pu sh / ret . Это нарушает стек предикторов обратного адреса, приводя к неправильным прогнозам, а в дальнейшем возвращает стек вызовов. Даже retpoline обычно использует только вызов / изменение стека / ret. (G CC будет автоматически генерировать ретполины с -mindirect-branch=thunk
). Конечно, вы можете получить эту 3-байтовую последовательность как непосредственную.
jmp edi
может выступить как обычная часть вывода компилятора (не просто встроенная как часть другой инструкции). Но это вряд ли. В 32-битных соглашениях о вызовах edi
сохраняется при вызове, поэтому вы не найдете jmp edi
, реализующего tailcall; регистры должны быть восстановлены до возврата или разговора. Если вы получите G CC, чтобы сохранить указатель на функцию в регистре, он будет вызываться с помощью mov eax, edi
/ pop edi
/ jmp eax
, чтобы восстановить значение вызывающих абонентов перед вызовом. ( Godbolt ).
A call edi
не так сложно найти с помощью локальных указателей на функции, если вы используете достаточно много других локальных объектов, чтобы распределитель регистров компилятора выбирал EDI. Но не jmp
.
В x86-64 System V RDI является регистром передачи аргументов и имеет закрытый вызов, но, конечно, указатели являются 64-битными, поэтому вы всегда получите jmp rdi
. В варианте ILP32 (ABI x32) соглашение о вызовах по-прежнему требует, чтобы аргументы указателя были расширены от нуля до 64-битной, потому что G CC -mx32
по-прежнему излучает jmp rdi
, а не jmp edi
. (Clang усекает до 32 бит с mov eax,edi
/ jmp rax
). Отключение оптимизации бесполезно; Затем компиляторы просто сохранят в стек и перезагрузят в EAX.
Рабочий пример для gcc9.2
Одна верная ставка - это GNU C computed-goto по адресу локальная метка :
// 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;
}
Godbolt gcc9.2 -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
ret