reverseenengineering:соглашения

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Следующая версия
Предыдущая версия
reverseenengineering:соглашения [2025/06/24 00:32] – создано Lexreverseenengineering:соглашения [2025/07/16 21:46] (текущий) – [Соглашения - Calling Conventions на x86 и x64 с примерами и схемами] Lex
Строка 1: Строка 1:
-====== Соглашения ======+====== Соглашения - Calling Conventions на x86 и x64 с примерами и схемами ====== 
 + 
 + 
 + 
 + 
 +==== Термины и основы ==== 
 +  * **Calling convention** — соглашение о способе передачи аргументов функции, возврате значений и очистке стека. 
 +  * Важен при низкоуровневой разработке, реверс-инжиниринге, работе с чужим кодом, ассемблерной вставке. 
 +  * Различие соглашений: кто и как передаёт аргументы, кто очищает стек, используется ли frame pointer. 
 +  * __x86 и Win64 кардинально отличаются!__ 
 + 
 +---- 
 + 
 +==== x86: Основные calling conventions ==== 
 + 
 +^Convention  ^Порядок аргументов^Как передаются^Кто чистит стек^Возврат значения^Имя функции^Где применяется^ 
 +|__cdecl     |Cправа налево     |Стек           |Caller         |EAX             |_func         |C/varargs         | 
 +|__stdcall   |Cправа налево     |Стек           |Callee         |EAX             |_func@N       |WinAPI            | 
 +|__fastcall  |R->L, 1 ECX, 2 EDX, потом стек|Регистр+стек|Callee|EAX|@func@N      |Оптимизация        | 
 +|thiscall    |this via ECX, потом стек|ECX/стек    |Caller         |EAX             |манглинг      |C++ методы        | 
 + 
 +--- 
 + 
 +=== __cdecl === 
 +  * Аргументы: справа налево через стек. 
 +  * Стек чистит вызывающий (caller). 
 +  * Хорошо подходит для функций с переменным числом аргументов (printf и др). 
 +  * Имя: обычно с нижним подчёркиванием на уровне объектного файла. 
 +  * Возврат: EAX. 
 + 
 +==== Пример (cdecl, x86): ==== 
 +<code asm> 
 +int sum(int a, int b) { return a + b; } 
 +</code> 
 +<code asm> 
 +push 2          ; b 
 +push 1          ; a 
 +call sum 
 +add esp, 8      ; очищаем стек 
 +</code> 
 +<code asm> 
 +sum: 
 +    push ebp 
 +    mov ebp, esp 
 +    sub esp, 4          ; локальная переменная 
 +    mov eax, [ebp+8]    ; a 
 +    add eax, [ebp+12]   ; b 
 +    mov esp, ebp 
 +    pop ebp 
 +    ret 
 +</code> 
 + 
 +--- 
 + 
 +=== __stdcall === 
 +  * Аргументы: справа налево через стек. 
 +  * Стек чистит вызываемая функция (callee). 
 +  * Переменные аргументы невозможны. 
 +  * Имя: _func@N (символ + байты аргументов). 
 +  * Возврат: EAX. 
 + 
 +==== Пример (stdcall, x86): ==== 
 +<code asm> 
 +push 2 
 +push 1 
 +call sum@8 
 +; нет add esp, 8! 
 +... 
 +sum@8: 
 +    push ebp 
 +    mov ebp, esp 
 +    sub esp, 4 
 +    ...логика... 
 +    mov esp, ebp 
 +    pop ebp 
 +    ret 8              ; callee очищает стек 
 +</code> 
 + 
 +--- 
 + 
 +=== __fastcall === 
 +  * 1-й аргумент в ECX, 2-й в EDX, остальные через стек. 
 +  * Стек чистит callee (ret N). 
 +  * Имя: @func@N. 
 +  * Возврат: EAX. 
 + 
 +==== Пример (fastcall, x86): ==== 
 +<code asm> 
 +push 3          ; 3-й аргумент 
 +mov edx, 2      ; 2-й 
 +mov ecx, 1      ; 1-й 
 +call sum@12 
 +...             ; нет add esp 
 +sum@12: 
 +    push ebp 
 +    mov ebp, esp 
 +    ... 
 +    mov esp, ebp 
 +    pop ebp 
 +    ret 0Ch 
 +</code> 
 + 
 +--- 
 + 
 +=== thiscall === 
 +  * Для методов С++. 
 +  * Указатель this — ECX, остальные аргументы через стек. 
 +  * Стек чистит caller. 
 +  * Имя видоизменено по схеме C++ манглинга. 
 +  * Используется только для x86. 
 + 
 +==== Пример (thiscall, x86): ==== 
 +<code asm> 
 +push 2            ; b 
 +push 1            ; a 
 +mov ecx, obj      ; this 
 +call Foo::bar 
 +add esp, 8 
 +</code> 
 + 
 +--- 
 + 
 +==== Детали реализации (prologue/epilogue) на x86 ==== 
 + 
 +=== Prologue (вход функции) === 
 +<code asm> 
 +push ebp              ; сохранить старый frame pointer 
 +mov ebp, esp          ; установить новый frame pointer (ebp) 
 +sub esp, X            ; выделить X байт под локальные переменные 
 +</code> 
 + 
 +=== Epilogue (выход из функции) === 
 +<code asm> 
 +mov esp, ebp 
 +pop ebp 
 +ret / ret N           ; ret N для stdcall/fastcall 
 +</code> 
 + 
 +=== Локальные переменные и аргументы === 
 +  * Аргументы функции: [ebp+8], [ebp+12] и т.д. ([[ebp+4]] — return address). 
 +  * Локальные переменные: [ebp-4], [ebp-8], ..., для каждой следующей — минус по 4 байта. 
 + 
 +=== Пример с комментариями === 
 +<code asm> 
 +sum: 
 +    push ebp                ; сохранить ebp 
 +    mov ebp, esp            ; новый frame pointer 
 +    sub esp, 8              ; 2 локальных переменных (например) 
 +    mov eax, [ebp+8]        ; первый аргумент a 
 +    add eax, [ebp+12]       ; второй b 
 +    mov [ebp-4], eax        ; локалка c 
 +    ... 
 +    mov esp, ebp 
 +    pop ebp 
 +    ret / ret N 
 +</code> 
 + 
 +--- 
 + 
 +==== x64 (Win64 ABI): подробный разбор ==== 
 + 
 +=== Передача аргументов === 
 +  * Первые 4 аргумента — через регистры: RCX, RDX, R8, R9 (или XMM0–XMM3 для float/double). 
 +  * 5-й и далее — через стек ([rsp+N]). 
 +  * Caller ОБЯЗАН выделять 32 байта «shadow space» перед вызовом (sub rsp, 28h). 
 +  * **Других соглашений под Win64 нет!** 
 + 
 +=== Prologue (вход функции) === 
 +<code asm> 
 +sub rsp, 28h            ; shadow space (32 байта) + выравнивание (обычно) 
 +; если нужны локальные — sub rsp, больше 
 +</code> 
 +  * Обычно frame pointer (rbp) не используется. Если включени дебаг-опции — возможно будет push rbp; mov rbp, rsp. 
 +  * Аргументы сразу доступны по rcx, rdx, r8, r9. 
 +  * Shadow space: [rsp+0], [rsp+8], [rsp+16], [rsp+24] 
 + 
 +=== Epilogue (выход функции) === 
 +<code asm> 
 +add rsp, 28h 
 +ret 
 +</code> 
 + 
 +=== Локальные переменные === 
 +  * Выделяются через sub rsp, XX. 
 +  * Доступ — [rsp+смещение] (если не используется frame pointer). 
 + 
 +=== Дизассемблированный x64-пример (MSVC / x64dbg) === 
 +<code asm> 
 +sum: 
 +    sub     rsp, 28h              ; Shadow space 
 +    mov     eax, ecx              ; 1-й аргумент 
 +    add     eax, edx              ; 2-й 
 +    add     eax, r8d              ; 3-й 
 +    add     eax, r9d              ; 4-й 
 +    shl     eax, 1                ; умножение на 2 
 +    add     rsp, 28h 
 +    ret 
 +</code> 
 + 
 +  * Если есть 5-й и далее аргумент: размещается в памяти, брать через [rsp+40h], [rsp+48h] и т.д. 
 + 
 +=== Пример вызова (caller, x64) === 
 +<code asm> 
 +mov     ecx, 1        ; первый аргумент 
 +mov     edx, 2        ; второй 
 +mov     r8d, 3        ; третий 
 +mov     r9d, 4        ; четвертый 
 +sub     rsp, 28h      ; shadow space 
 +; здесь push или mov в [rsp+40h] пятый/шестой/и т.д. 
 +call    sum 
 +add     rsp, 28h 
 +</code> 
 + 
 +=== Важные примечания Win64 ABI === 
 +  * Перед каждым call stack должен быть выровнен на 16 байт! 
 +  * Shadow (home) space обязателен, даже если всего 1 аргумент. 
 +  * Все значения до 4-х аргументов необходимо сохранять там callee-стороне при необходимости (spill). 
 +  * strconv, printf и другие vararg: после 4-го аргумента значения только через стек. 
 +  * Возврат: через RAX (int/pointer) или XMM0 (float/double). 
 + 
 +=== Отличие от Linux x64 (System V ABI) === 
 +  * Под Linux: первые шесть аргументов кладутся в RDI, RSI, RDX, RCX, R8, R9. 
 +  * Нет shadow space. 
 +  * Не путать с Win64! 
 + 
 +--- 
 + 
 +==== Как узнать соглашение вызова по дизассемблированному коду (x64dbg) ==== 
 + 
 +  * __cdecl__: после call всегда add esp, X (caller очищает стек). 
 +  * __stdcall/fastcall__: после call нет add esp, а внутри функции — ret X (callee очищает). 
 +  * __fastcall__: часть аргументов переданы через ecx/edx, остальные стек. 
 +  * thiscall: ecx = this. 
 +  * __Win64__: всегда используются rcx/rdx/r8/r9 (caller), выделяется 28h (или >=28h) перед call (shadow space). 
 +  * Внутри функции часто нет frame pointer, доступ к локальным напрямую через rsp. 
 + 
 +--- 
 + 
 +===== Краткая шпаргалка по стеку (Win64, вызов функции с 6 аргументами) ===== 
 + 
 +|          | Значение      | Комментарий                                      | 
 +|----------|--------------|--------------------------------------------------| 
 +|rsp+0     |Shadow RCX    | 1-й аргумент (копия)                              | 
 +|rsp+8     |Shadow RDX    | 2-й                                               | 
 +|rsp+16    |Shadow R8     | 3-й                                               | 
 +|rsp+24    |Shadow R9     | 4-й                                               | 
 +|rsp+32    |5-й           | 5-й аргумент (через стек)                         | 
 +|rsp+40    |6-й           | 6-й аргумент                                      | 
 + 
 +--- 
 + 
 +===== Мини-конспект для реверса Win64 ===== 
 + 
 +  * Все функции вызываются с выравнием и shadow space. 
 +  * До 4-х аргументов — через rcx/rdx/r8/r9 (или xmm0–xmm3). 
 +  * 5+ — через стек по адресам выше shadow space. 
 +  * Внутри функции часто нет frame pointer, локальные и временные размещаются через rsp. 
 +  * После call caller обязан вернуть rsp к прежнему значению (add rsp, XX). 
 + 
 +--- 
 + 
 +===== Примеры для практики ===== 
 + 
 +==== Пример 1: Простая функция (Win64) ==== 
 + 
 +<code asm> 
 +int foo(int a, int b) { return a+b; } 
 +</code> 
 +<code asm> 
 +foo: 
 +    mov eax, ecx   ; a 
 +    add eax, edx   ; b 
 +    ret 
 +</code> 
 +<code asm> 
 +mov ecx, 5 
 +mov edx, 6 
 +sub rsp, 28h      ; выделил shadow space + выравнивание 
 +call foo 
 +add rsp, 28h 
 +</code> 
 + 
 +==== Пример 2: С vararg (Win64, printf) ==== 
 + 
 +  * Первые 4 аргумента — rcx, rdx, r8, r9 + копии кладутся в shadow space. 
 +  * 5-й и выше на стек 
 +  * Внутри функции printf читает все аргументы из shadow space + стек. 
 + 
 +--- 
 + 
 +===== Заключение ===== 
 + 
 +Win64 calling convention:   
 +  * Первые 4 аргумента — через rcx, rdx, r8, r9 
 +  * Shadow space (32 байта) обязателен перед каждым call 
 +  * 5+ аргументов размещаются на стеке выше shadow 
 +  * Возврат значения — через rax 
 +  * Frame pointer часто не используется 
 +  * После call восстановить rsp! 
 + 
 +--- 
 + 
 + 
  • reverseenengineering/соглашения.1750699954.txt.gz
  • Последнее изменение: 2025/06/24 00:32
  • Lex