какой код C будет использовать "JMP EDI" или "PU SH EDI RET"? - PullRequest
0 голосов
/ 27 февраля 2020

Есть ли разница между

JMP EDI 

и

PUSH EDI
RET

, какой код c будет разбираться таким образом? динамически разрешаемый указатель на функцию, затем вызывается?

1 Ответ

0 голосов
/ 27 февраля 2020

Если вы ищете гаджет 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
...