Категория > Новости > Фундаментальные основы хакерства. Находим математические операторы в дизассемблированных программах - «Новости»
Фундаментальные основы хакерства. Находим математические операторы в дизассемблированных программах - «Новости»29-07-2022, 00:02. Автор: Аникита |
странице автора.Идентификация оператора +В общем случае оператор + транслируется либо в машинную инструкцию Оптимизирующие компиляторы могут заменять Рассмотрим пример demo_plus, демонстрирующий использование оператора + со значениями одинарной точности:
#include <iostream>
int main()
{
float a = 0.7f, b = 1.4f, c;
c = a + b;
std::cout << c << std::endl;
c = c + 0.3f;
std::cout << c << std::endl;
}
Результат выполнения demo_plusПрежние версии компиляторов по умолчанию позволяли использовать неинициализированные переменные. Теперь же это не так, и компилятор обрывает трансляцию программы, встретив неинициализированную переменную. Это лишает его возможности загружать значение откуда угодно, тем самым защищая программиста от фатальных ошибок, которые могут произойти, если он забудет присвоить значение объявленной переменной. Результат трансляции этого примера компилятором 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_c= dword ptr -18h
var_a= dword ptr -14h
var_b= dword ptr -10h
; Резервируем память для локальных переменных
sub rsp, 38h
; Загружаем в регистр XMM0 значение из сегмента данных только для чтения
movss xmm0, cs:__real@3f333333
; Перекладываем это значение из регистра в переменную var_a
movss [rsp+38h+var_a], xmm0
; Загружаем в регистр следующее по порядку значение
movss xmm0, cs:__real@3fb33333
; Перекладываем его в переменную var_b
movss [rsp+38h+var_b], xmm0
; Первое значение возвращаем в регистр XMM0 из переменной var_a
movss xmm0, [rsp+38h+var_a]
; Складываем содержимое XMM0 со значением переменной var_b
addss xmm0, [rsp+38h+var_b]
; Копируем сумму var_a и var_b в переменную var_c, следовательно, var_c = var_a + var_b
movss [rsp+38h+var_c], xmm0
; Готовим параметры для передачи оператору <<
; Второй слева — переменная var_c
movss xmm1, [rsp+38h+var_c]
; Первый слева — формат вывода
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout
; Собственно вызов оператора вывода строки
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(float)
; Плюс вывод символа новой строки
lea rdx, std::endl<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &)
mov rcx, rax
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(std::basic_ostream<char,std::char_traits<char>> &(*)(std::basic_ostream<char,std::char_traits<char>> &))
; Загружаем в XMM0 значение переменной var_c...
movss xmm0, [rsp+38h+var_c]
; ...прибавляем к этому значению значение из сегмента данных только для чтения
addss xmm0, cs:__real@3e99999a
; Обновляем var_c: var_c = var_c + const
movss [rsp+38h+var_c], xmm0
; Готовим параметры для передачи оператору <<
; Второй слева — переменная var_c
movss xmm1, [rsp+38h+var_c]
; Первый слева — формат вывода
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout
; Собственно вызов оператора вывода строки
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(float)
; Плюс вывод символа новой строки
lea rdx, std::endl<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &)
mov rcx, rax
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(std::basic_ostream<char,std::char_traits<char>> &(*)(std::basic_ostream<char,std::char_traits<char>> &))
xor eax, eax
add rsp, 38h
retn
mainendp
А теперь посмотрим, как будет выглядеть тот же самый пример, скомпилированный с ключом
mainproc near; CODE XREF: __scrt_common_main_seh+107↓p
; DATA XREF: .pdаta:ExceptionDir↓o
sub rsp, 28h
; Как ловко! Компилятор подсчитал сумму во время компиляции
; и подставил ее непосредственно для вывода
; С чего бы ему идти на такие хитрости без последствий?
; Значения-то представляют собой константы,
; которые хранятся в сегменте данных только для чтения
movss xmm1, cs:__real@40066666
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(float)
mov rcx, rax
lea rdx, std::endl<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &)
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(std::basic_ostream<char,std::char_traits<char>> &(*)(std::basic_ostream<char,std::char_traits<char>> &))
; Второе для вывода значение — с ним такая же история, как с первым значением
movss xmm1, cs:__real@40199999
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(float)
mov rcx, rax
lea rdx, std::endl<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &)
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(std::basic_ostream<char,std::char_traits<char>> &(*)(std::basic_ostream<char,std::char_traits<char>> &))
xor eax, eax
add rsp, 28h
retn
mainendp
В оптимизированном варианте нет ни намека на сложение или другие арифметические операции! С возведенным флагом Embarcadero C++Builder генерирует похожий код, а в случае оптимизации — еще хуже. Поэтому приводить результаты его труда бессмысленно — никаких новых «изюминок» они в себе не несут. Идентификация оператора –В общем случае оператор – транслируется либо в машинную инструкцию Покажем это на примере demo_minus, демонстрирующем использование оператора – со значениями двойной точности:
#include <iostream>
int main()
{
double a = 3.1, b = 1.6, c;
c = a - b;
std::cout << c << std::endl;
c = c - 10;
std::cout << c << std::endl;
}
Результат выполнения demo_minusНеоптимизированный вариант будет выглядеть приблизительно так:
mainproc near; CODE XREF: __scrt_common_main_seh+107↓p
; DATA XREF: .pdаta:ExceptionDir↓o
var_c= qword ptr -28h
var_a= qword ptr -20h
var_b= qword ptr -18h
; Резервируем память для локальных переменных
sub rsp, 48h
; Загружаем в регистр XMM0 значение из сегмента данных только для чтения
movsd xmm0, cs:__real@4008cccccccccccd
; Перекладываем это значение из регистра в переменную var_a
movsd [rsp+48h+var_a], xmm0
; Загружаем в регистр, заменяя имеющееся там значение следующим по порядку значением
movsd xmm0, cs:__real@3ff999999999999a
; Перекладываем его в переменную var_b
movsd [rsp+48h+var_b], xmm0
; Из переменной var_a возвращаем значение в регистр XMM0
movsd xmm0, [rsp+48h+var_a]
; Вычитаем из var_a значение переменной var_b, записывая результат в XMM0
subsd xmm0, [rsp+48h+var_b]
; Записываем в var_c разность var_a и var_b:
; var_c = var_a — var_b
movsd [rsp+48h+var_c], xmm0
movsd xmm1, [rsp+48h+var_c]
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(double)
lea rdx, std::endl<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &)
mov rcx, rax
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(std::basic_ostream<char,std::char_traits<char>> & (*)(std::basic_ostream<char,std::char_traits<char>> &))
; Загружаем в регистр XMM0 значение переменной var_c — готовим к вычислению
movsd xmm0, [rsp+48h+var_c]
; Вычитаем из var_c значение, взятое из сегмента данных только для чтения
; При этом результат записываем в регистр XMM0
subsd xmm0, cs:__real@4024000000000000
; Обновляем содержимое переменной var_c:
; var_c = var_c — const
movsd [rsp+48h+var_c], xmm0
movsd xmm1, [rsp+48h+var_c]
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(double)
lea rdx, std::endl<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &)
mov rcx, rax
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(std::basic_ostream<char,std::char_traits<char>> & (*)(std::basic_ostream<char,std::char_traits<char>> &))
xor eax, eax
add rsp, 48h
retn
mainendp
А теперь рассмотрим оптимизированный вариант того же примера:
mainproc near; CODE XREF: __scrt_common_main_seh+107↓p
; DATA XREF: .pdаta:ExceptionDir↓o
sub rsp, 28h
; Компилятор подсчитал разность во время трансляции и подготовил ее значение для вывода
movsd xmm1, cs:__real@3ff8000000000000
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(double)
mov rcx, rax
lea rdx, std::endl<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &)
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(std::basic_ostream<char,std::char_traits<char>> & (*)(std::basic_ostream<char,std::char_traits<char>> &))
; Результат второй разности также подсчитан во время компиляции
movsd xmm1, cs:__real@c021000000000000
mov rcx, cs:std::basic_ostream<char,std::char_traits<char>> std::cout
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(double)
mov rcx, rax
lea rdx, std::endl<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &)
call cs:std::basic_ostream<char,std::char_traits<char>>::operator<<(std::basic_ostream<char,std::char_traits<char>> & (*)(std::basic_ostream<char,std::char_traits<char>> &))
xor eax, eax
add rsp, 28h
retn
mainendp
Embarcadero C++Builder генерирует практически идентичный код без оптимизации и намного хуже с включенной оптимизацией, поэтому здесь он не рассматривается. Идентификация оператора /В общем случае оператор / транслируется либо в машинную инструкцию Перейти обратно к новости |