Категория > Новости > Фундаментальные основы хакерства. Практикуемся в поиске циклов при реверсе - «Новости»
Фундаментальные основы хакерства. Практикуемся в поиске циклов при реверсе - «Новости»9-07-2022, 00:02. Автор: Аделаида |
прошлой статьей, где мы рассмотрели теорию устройства разных видов циклов на языках высокого уровня и их отражение в дизассемблерных листингах. А сейчас настало время практики!Фундаментальные основы хакерстваПятнадцать лет назад эпический труд Криса Касперски «Фундаментальные основы хакерства» был настольной книгой каждого начинающего исследователя в области компьютерной безопасности. Однако время идет, и знания, опубликованные Крисом, теряют актуальность. Редакторы «Хакера» попытались обновить этот объемный труд и перенести его из времен Windows 2000 и Visual Studio 6.0 во времена Windows 10 и Visual Studio 2019. Ссылки на другие статьи из этого цикла ищи на странице автора. Циклы while/doVisual C++ 2022 с отключенной оптимизациейДля закрепления пройденного в прошлой статье материала рассмотрим несколько живых примеров. Начнем с самого простого — идентификации циклов while/do: int main(){ Откомпилируем этот код с помощью Visual C++ 2022 с отключенной оптимизацией. Результат выполнения примера while-do Результат компиляции должен выглядеть примерно так:
; 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:0000000140004018↓o
var_18= dword ptr -18h
var_14= dword ptr -14h
; резервируем память для двух локальных переменных,
; только откуда взялась вторая?
sub rsp, 38h
; заносим в переменную var_18 значение 0
; следовательно, это переменная "a"
mov [rsp+38h+var_18], 0
Ниже следует перекрестная ссылка -
loc_1400010EC:; CODE XREF: main+31↓j
; Загружаем в EAX значение переменной "a" (var_18)
mov eax, [rsp+38h+var_18]
; Загружаем в var_14 значение переменной "a", вот, мы нашли,
; где используется вторая переменная
mov [rsp+38h+var_14], eax
; Зачем-то снова загружаем то же значение в регистр EAX
mov eax, [rsp+38h+var_18]
; Увеличение значения в регистре EAX на 1
inc eax
; Загружаем значение из регистра EAX в переменную var_18 ("a")
mov [rsp+38h+var_18], eax
; Сравниваем старое (до обновления) значение переменной "a",
; ранее сохраненное в var_14, с числом 0xA
cmp [rsp+38h+var_14], 0Ah
Если ( Для его отображения на цикл Сделав это, мы получаем: ; Начало тела цикла:; заносим ссылку на строку "Оператор цикла whilen"lea Между
Далее следует начало цикла с постусловием. Однако на данном этапе мы этого еще не знаем, хотя и можем догадаться благодаря наличию перекрестной ссылки, направленной вниз.
loc_140001113:; CODE XREF: main+23↑j
; main+4E↓j
Ага, никакого условия в начале цикла не присутствует, значит, это цикл с условием в конце или в середине. lea Поскольку условие расположено в конце цикла, это цикл while (--a 0);
; возвращаем 0
xor eax, eax
; восстанавливаем стек
add rsp, 38h
retn
mainendp
Visual C++ 2022 с включенной оптимизациейСовсем другой результат получится, если включить оптимизацию. Откомпилируем тот же самый пример с ключом mainproc near; CODE XREF: __scrt_common_main_seh+107↓p; DATA XREF: .pdаta:000000014000400C↓o; сохраняем регистр в стекеpush Инструкция
jnz short loc_140001080
Компилятор в порыве оптимизации превратил неэффективный цикл с предусловием в более компактный и быстрый цикл с постусловием. Имел ли он на это право? А почему нет?! Проанализировав код, компилятор понял, что этот цикл выполняется, по крайней мере, один раз. Следовательно, скорректировав условие продолжения, его проверку можно вынести в конец цикла. Также в исходном тексте был инкремент счетчика цикла от нуля до Причем, что интересно, он не сравнивал переменную цикла с константой, а поместил константу в регистр и уменьшал его до тех пор, пока тот не стал равен нулю! Зачем? А затем, что так короче, да и работает быстрее. Хорошо, но как нам декомпилировать этот цикл? Непосредственное отображение на язык C/C++ дает следующую инструкцию: do { Вполне красивый и оптимальный цикл с одной переменной. ; Этот код выполняется после завершения предыдущего цикла.mov Этот цикл прямиком отображается в конструкцию языка C/C++: do { printf("Оператор цикла don"); }while (--var_EBX 0);
; возвращаем ноль
xor eax, eax
; восстанавливаем стек
add rsp, 20h
; восстанавливаем регистр
pop rbx
retn
mainendp
C++ Builder 10 без оптимизацииНесколько иначе обрабатывает циклы компилятор Embarcadero C++ Builder 10.4. Смотри пример public mainmainproc near; DATA XREF: __acrtused+29↑o; объявляем шесть переменныхvar_1C= dword ptr -1Chvar_18= dword ptr -18hvar_14= dword ptr -14hvar_10= qword ptr -10hvar_8= dword ptr -8var_4= dword ptr -4; сохраняем в стеке RBPpush Вот так‑то C++ Builder оптимизировал код! Начальный цикл с предусловием выполнения он превратил в бесконечный цикл с условием выхода посередине (за подробностями обратись к прошлой статье)! Как мы можем декомпилировать этот цикл? Напрашивается такой вариант:
Этот вариант кардинально отличается от первоначального, и я очень сомневаюсь, что в лучшую сторону! Что ж, издержки производства... loc_401440:; CODE XREF: main+2D↑j; сюда происходит переход при выходе из предыдущего цикла; как мы знаем, эта инструкция только переводит управление через себяjmp Проматываем дизассемблерный листинг вверх, чтобы вспомнить, какое значение находится в регистре
mov [rbp+var_1C], eax
; Записываем в регистр EAX значение переменной var_14.
; А в ней содержится значение на 1 больше, чем в EAX! То есть, 0xB.
mov eax, [rbp+var_14]
; Какой хитрый C++ Builder!
; Вместо реального вычитания он прибавляет к значению в EAX -1
add eax, 0FFFFFFFFh
; Присваивает результат переменной var_14
mov [rbp+var_14], eax
; И сравнивает уменьшенное значение с нулем
cmp eax, 0
; Если (EAX > 0), то мы прыгаем назад к началу "нового цикла"
; и осуществлению очередной итерации
jgshort loc_401442
Во что C++ Builder превратил изначальный цикл с постусловием? В целом, никаких изменений он не внес, оставив все на своих местах. И декомпилированный листинг этого цикла должен выглядеть примерно так: do{
; В ином случае, когда (EAX <= 0), пропускаем переход
; и продолжаем выполнение кода программы
mov [rbp+var_4], 0
; Возвращаем ноль
mov eax, [rbp+var_4]
; Восстанавливаем стек
add rsp, 40h
; Восстанавливаем регистр
pop rbp
retn
mainendp
Перейти обратно к новости |