Категория > Новости > Фундаментальные основы хакерства. Изучаем условные переходы в обработке современными компиляторами - «Новости»
Фундаментальные основы хакерства. Изучаем условные переходы в обработке современными компиляторами - «Новости»26-05-2022, 00:02. Автор: Jacobson |
странице автора. Сегодняшняя статья — продолжение предыдущей, если ее не прочитать, разобраться в коде будет трудновато. Что ж, продолжим наш заплыв в океан условий, ветвлений, отношений и компромиссов. Идентификация тернарного оператораКонструкция
#include <iostream>
int main() {
int a = -2;
int b = 2;
a = (a > 0)? 1 :-1; // Тернарный оператор
if (b > 0) // Ветвление
b = 1;
else
b = -1;
std::cout <<"a + b = " << a+ b;
}
Если пропустить эту программу сквозь компилятор Microsoft Visual C++ 2022 с отключенной оптимизацией, на выходе получим такой код:
; int __cdecl main(int argc, const char **argv, const char **envp)
mainproc near ; CODE XREF: __scrt_common_main_seh+107↓p
; DATA XREF: .pdаta:ExceptionDir↓o
; Четыре локальные переменные
var_18= dword ptr -18h
var_14= dword ptr -14h
var_10= dword ptr -10h
var_C= dword ptr -0Ch
; Резервируем место для локальных переменных
sub rsp, 38h
; Инициализация локальных переменных:
; var_14 присваиваем -2, компилятор выразил отрицательное число
; сверхбольшим положительным
mov [rsp+38h+var_14], 0FFFFFFFEh
; var_18 присваиваем 2, здесь все прямолинейно
mov [rsp+38h+var_18], 2
; Сравниваем переменную var_14 с 0
cmp [rsp+38h+var_14], 0
; Переход по метке, если в результате предыдущего сравнения первый операнд меньше второго или равен ему
jle short loc_140001025 ; В нашем случае все сходится, делаем прыжок
mov [rsp+38h+var_10], 1
jmp short loc_14000102D
; --------------------------------------
loc_140001025:; CODE XREF: main+19↑j
; Делаем переход сюда
mov [rsp+38h+var_10], 0FFFFFFFFh ; var_10 присваиваем -1
loc_14000102D:; CODE XREF: main+23↑j
mov eax, [rsp+38h+var_10]
mov [rsp+38h+var_14], eax ; var_14 = -1
; Сравниваем var_18 с 0, припоминаем или прокручиваем экран дизассемблера вверх,
; в var_18 помещено значение 2
cmp [rsp+38h+var_18], 0
; Переход по метке, если в результате предыдущего сравнения первый операнд меньше второго или равен ему
jle short loc_140001046 ; В данном случае карты не сходятся, поэтому продолжаем выполнение
mov [rsp+38h+var_18], 1 ; Переменной var_18 присваиваем значение 1
jmp short loc_14000104E ; Безусловный переход
; --------------------------------------
loc_140001046:; CODE XREF: main+3A↑j
mov [rsp+38h+var_18], 0FFFFFFFFh
loc_14000104E:; CODE XREF: main+44↑j
; Прыгаем сюда
mov eax, [rsp+38h+var_18] ; EAX = 1
mov ecx, [rsp+38h+var_14] ; ECX = -1
add ecx, eax; -1 + 1
mov eax, ecx
mov [rsp+38h+var_C], eax ; Переменной var_C присваиваем сумму — значение 0
; Готовим параметры
lea rdx, _Val; "a + b = "
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout ; _Ostr
; Вызываем оператор << для вывода строки
call std::operator<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char const *)
mov ecx, [rsp+38h+var_C]
mov edx, ecx
mov rcx, rax
; Вызываем оператор << для вывода числа
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(int)
xor eax, eax ; Возвращаем 0
add rsp, 38h ; Восстанавливаем стек
retn
mainendp
Во дела! Компилятор оба условных оператора скомпилил в одинаковый код! Теперь переведем компилятор в режим максимальной оптимизации с приоритетом по скорости —
mainproc near
sub rsp, 28h
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout ; _Ostr
call std::operator<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char const *)
mov rcx, rax
xor edx, edx
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(int)
xor eax, eax
add rsp, 28h
retn
mainendp
Что за чертовщина?! Здесь не осталось никаких условностей! Даже если включить режим оптимизации с приоритетом размера — А что нам продемонстрирует C++Builder? Сначала без оптимизации:
; int __cdecl main(int argc, const char **argv, const char **envp)
public main
mainproc near; DATA XREF: __acrtused+29↑o
var_20= qword ptr -20h
var_18= dword ptr -18h
var_14= dword ptr -14h
var_10= qword ptr -10h
var_8= dword ptr -8
var_4= dword ptr -4
; Открываем кадр стека
push rbp
; Выделяем память под локальные переменные
sub rsp, 40h
; Запоминаем адрес дна стека
lea rbp, [rsp+40h]
mov eax, 0FFFFFFFFh ; EAX = -1
mov r8d, 1; R8D = 1
; Инициализация локальных переменных
mov [rbp+var_4], 0
mov [rbp+var_8], ecx
mov [rbp+var_10], rdx
mov [rbp+var_14], 0FFFFFFFEh ; var_14 = -2
mov [rbp+var_18], 2; var_18 = 2
mov ecx, [rbp+var_14]; ECX = -2
; Сравниваем ECX с 0
cmp ecx, 0
Ага! Используется команда условной пересылки (за подробностями обратись к предыдущей статье): G значит Greater. Другими словами, данные копируются, если первый операнд больше второго (в таком случае флаги
cmovg eax, r8d
mov [rbp+var_14], eax ; var_14 = -1
; Сравнение var_18 с 0
cmp [rbp+var_18], 0
; Переход по метке, если в результате предыдущего сравнения первый операнд меньше второго или равен ему
jle short loc_4013ED ; В данном случае карты не сходятся (2 > 0), поэтому продолжаем выполнение
mov [rbp+var_18], 1 ; var_18 = 1
jmp short loc_4013F4 ; Безусловный прыжок
; --------------------------------------
loc_4013ED:; CODE XREF: main+42↑j
mov [rbp+var_18], 0FFFFFFFFh
loc_4013F4:; CODE XREF: main+4B↑j
; Безусловный прыжок сюда
; Подготовка параметров для вызова оператора вывода
lea rcx, a008_free_list
lea rdx, aAB; "a + b = "
; Вывод строки оператором <<
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc ; std::operator<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char const*)
mov r8d, [rbp+var_14] ; R8D = -1
add r8d, [rbp+var_18] ; R8D = -1 + 1
; Готовим параметры для вызова оператора вывода
mov rcx, rax
mov edx, r8d
; Вывод числа (суммы) оператором <<
call _ZNSolsEi; std::ostream::operator<<(int)
mov [rbp+var_4], 0
mov [rbp+var_20], rax
; Возвращаем 0
mov eax, [rbp+var_4]
; Восстанавливаем стек
add rsp, 40h
; Закрываем кадр стека
pop rbp
retn
mainendp
Было интересно увидеть результат работы C++Builder, в особенности использование команды условной пересылки, которая позволила уменьшить количество условных переходов с двух до одного. За подробностями обратись к подготовленному Visual C++ неоптимизированному дизассемблерному листингу, чтобы увидеть оба условных перехода в действии. Перейти обратно к новости |