Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   Реверсинг (https://forum.antichat.xyz/forumdisplay.php?f=94)
-   -   Отладка программ, откомпилированных Visual C++. Часть 1. (https://forum.antichat.xyz/showthread.php?t=122274)

_=(mac)=_ 25.05.2009 20:25

Отладка программ, откомпилированных Visual C++. Часть 1.
 
Дизассемблирование программ, откомпилированных Visual C++
Это моя первая статья, поэтому заранее прошу прощения за какие-либо неточности или ошибки))
Для начала напишем простенькую программу, которая принимает от нас два числа, затем складывает их, и выводит результат на экран.
Исходный текст нашей подопытной программы numberplus.cpp:

#include <iostream>
using namespace std;
int main()
{
int a, b, c;
cout <<"Enter number 1:\n";
cin >>a;
cout <<"Enter number 2:\n";
cin >>b;
c = a + b;
cout <<c;
}


Компилируем, на выходе получаем numberplus.exe и загружаем его в IDA Pro. OEP находится по адресу 0040A695, но мы переходим по адресу 00401000, по которому находится главная функция нашей программы.

Дизассемблерный листинг numberplus.exe


Код:

.text:00401000 sub_401000      proc near              ; CODE XREF: start-5C p
.text:00401000
.text:00401000 var_C          = dword ptr -0Ch       
.text:00401000 var_8          = dword ptr -8
.text:00401000 var_4          = dword ptr -4
.text:00401000                ;Объявление локальных переменных
.text:00401000                push    ebp
.text:00401001                mov    ebp, esp
.text:00401003                sub    esp, 0Ch
.text:00401003                ;на этой стадии на дне стека сохраняем регистр ebp, копируем  esp .text:00401003                ;в ebp, затем от esp вычитаем С.
.text:00401006                push    offset aEnterNumber1 ; "Enter number 1:\n"
.text:0040100B                push    offset unk_423918
.text:00401010                call    sub_4030D0
.text:00401010                ; передача параметров и вызов функции вывода на экран. Первый параметр – смещение строки в сегменте данных, где находится строка для вывода, второй параметр – экземпляр объекта basic_istream, а затем собственно и сам call
.text:00401015                add    esp, 8
.text:00401015                ;удаляем из стека переданные параметры
.text:00401018                lea    eax, [ebp+var_4]
.text:0040101B                push    eax
.text:0040101C                mov    ecx, offset dword_42387C
.text:00401021                call    sub_4013F0
.text:00401021                ;в этом блоке мы в первую очередь заносим в eax указатель на буфер, где будет хранится первое число, потом идёт push eax, т.е. посылаем содержимое eax в стек в качестве параметра к функции ввода, затем кладём смещение basic_ostream в ecx… И сам вызов функции ввода
.text:00401026                push    offset aEnterNumber2 ; "Enter number 2:\n"
.text:0040102B                push    offset unk_423918
.text:00401030                call    sub_4030D0
.text:00401030                ; функция вывода на экран сообщения. Ничем не отличается от функции вывода, описанной выше, кроме смещения строки
.text:00401035                add    esp, 8
.text:00401035                ;выбрасываем из стека переданные параметры предъидущей функции
.text:00401038                lea    ecx, [ebp+var_8]
.text:0040103B                push    ecx
.text:0040103C                mov    ecx, offset dword_42387C
.text:00401041                call    sub_4013F0
.text:00401041                ;ещё одна функция ввода, идентичная предъидущей
.text:00401046                mov    edx, [ebp+var_4]
.text:00401049                add    edx, [ebp+var_8]
.text:00401049                ;что мы теперь имеем? Первое число, которое мы ввели, находится в ebp+var_4, второе в ebp+var_8. А в этом блоке мы первое число копируем в edx (mov edx,[ebp+var_4]), а второе мы складываем с edx, и заносим в этот же регистр(add edx, [ebp+var_8]).
.text:0040104C                mov    [ebp+var_C], edx
.text:0040104C                ; копируем содержимое edx  в ebp+var_C.
.text:0040104F                mov    eax, [ebp+var_C]
.text:00401052                push    eax
.text:00401053                mov    ecx, offset unk_423918
.text:00401058                call    sub_401070
text:00401058                ;теперь осталось вывести сумму введённых нами чисел на экран. Тут мы копируем из ebp+var_C (кто не понял: тут хранится сумма наших чисел) число в eax, затем мы этот регистр кладём в стек в качестве параметра к функции вывода.
.text:0040105D                xor    eax, eax
.text:0040105F                mov    esp, ebp
.text:00401061                pop    ebp
.text:00401061                ;обнуление eax, выравнивание стека…
.text:00401062                retn
.text:00401062                ;и выход

Из этого листинга видно, что данная функция отвечает за логику работы программы. Эта программа оперирует числами целого типа, и анализ такой программы довольно тривиальное занятие. Сейчас мы усложним немного программу, изменив в исходном коде тип переменных с целочисленных(int) на дробные(double). Изменяем, компилим, загружаем в Иду и видим:

Код:

.text:00401000 sub_401000      proc near             
.text:00401000 var_20          = qword ptr -20h       
.text:00401000 var_18          = qword ptr -18h       
.text:00401000 var_10          = qword ptr -10h
.text:00401000 var_8          = qword ptr -8
.text:00401000
.text:00401000                push    ebp
.text:00401001                mov    ebp, esp
.text:00401003                sub    esp, 18h
.text:00401003                ;отнимаем от регистра – указателя на вершину стека esp 18 байт
.text:00401006                push    offset aEnterNumber1 ; "Enter number 1:\n"
.text:0040100B                push    offset unk_423918
.text:00401010                call    sub_403070
.text:00401010                ; передача функции вывода сообщения на экран параметров через стек. Первый параметр – адрес в сегменте данных, где находится эта строка, вторая – экземпляр объекта basic_istream, а call sub_403070 – вызов самой функции
.text:00401015                add    esp, 8
.text:00401015                ;удаляем из стека переданные параметры
.text:00401018                lea    eax, [ebp+var_8]
.text:0040101B                push    eax
.text:0040101C                mov    ecx, offset dword_42387C
.text:00401021                call    sub_4013A0
.text:00401021                ;Вызов функции ввода. Сначала кладём в eax указатель на буфер ввода, куда будет попадать число, которое мы введём, потом eax помещаем в стек, затем в ecx попадает экземпляр объекта basic_ostream, находящийся в памяти по адресу 42387С, затем идёт call sub_4013A0 – сам вызов функции ввода, которая ожидает
ввода с клавиатуры в консоль числа
.text:00401026                push    offset aEnterNumber2 ; "Enter number 2:\n"
.text:0040102B                push    offset unk_423918
.text:00401030                call    sub_403070
.text:00401030                ;Функция вывода на экран. Первый параметр – строка, второй -  экземпляр объекта basic_istream, находящийся в 423918, а затем сам вызов функции
.text:00401035                add    esp, 8
.text:00401035                ;удаляем из стека переданные параметры(было передано 2 параметра предъидущей функции, каждая по 4 байта)
.text:00401038                lea    ecx, [ebp+var_10]
.text:0040103B                push    ecx
.text:0040103C                mov    ecx, offset dword_42387C
.text:00401041                call    sub_4013A0
.text:00401041                ;функция ввода второго числа во второй буфер. lea ecx, [ebp+var_10] – в ecx после выполнения этой инструкции будет указатель на буфер, где будет храниться второе число. Затем мы ecx пересылаем в стек, после чего идёт call sub_4013A0 – функция ввода.       
.text:00401046                fld    [ebp+var_8]       
.text:00401049                fadd    [ebp+var_10]
.text:0040104C                fstp    [ebp+var_18]
.text:0040104C                ;а здесь начинается самое интересное.  Все числа в памяти, остались вычисления. Первая инструкция из буфера, где хранится первое число, которое мы ввели, копирует это число в регистр сопроцессора ST01. Вторая инструкция складывает второе число с ST0, следовательно в ST0 находится сумма чисел, которые мы ввели. Теперь дело за третьей инструкцией -  fstp [ebp+var_18], которая выполняет сразу две операции – перемещает наш результат из ST0 в ST7 и записывает его же в стек по указателю ebp+var_18
.text:0040104F                sub    esp, 8
.text:0040104F                ;отнимаем от esp 8 байт
.text:00401052                fld    [ebp+var_18]
.text:00401052                ;копируем результат вычисления из памяти по указателю ebp+var_18 в регистр ST0
.text:00401055                fstp    [esp+20h+var_20]
.text:00401055                ;перемещаем наш результат в ST7 и на вершину стека в качестве параметра к функции вывода нашего числа на экран.
.text:00401058                mov    ecx, offset unk_423918
.text:0040105D                call    sub_401070
.text:0040105D                ;сам вызов функции вывода
.text:00401062                xor    eax, eax
.text:00401062                ;обнуление регистра eax
.text:00401064                mov    esp, ebp
.text:00401064                ; восстанавливаем регистр - указатель вершины стека
.text:00401066                pop    ebp
.text:00401066                ;снимаем с верхушки стека значение, и заносим его в ebp
.text:00401067                retn
.text:00401067                ;конец

В этом примере мы более – менее разобрали работу с дробными числами. Из этого всего стоит сделать несколько выводов:
1. Операции с дробными числами выполняет сопроцессор
2. Дробные числа хранятся в специальном наборе регистров ST0 … ST7

Balvan 25.05.2009 21:29

Молодец, продолжай! =)

_=(mac)=_ 25.05.2009 21:49

Ха!!! А я то думал что говно полное))))))

zeppe1in 25.05.2009 22:33

надо оформить получше, а то так читать не удобно

_=(mac)=_ 25.05.2009 23:53

Вот блиин... Накосячил немного с оформлением( Извиняюсь и исправляю)))

zeppe1in 30.05.2009 19:21

_=(mac)=_
и так вопросы)
как ты узнал адрес где находится главная функция программы?
и вот первый же call
Цитата:

Первый параметр – смещение строки в сегменте данных, где находится строка для вывода, второй параметр – экземпляр объекта basic_istream, а затем собственно и сам call
хорошо, указатель на строку я вижу.
а вот остальное совсем не понятно. пуш и кол собственно не о чом не говорят.
и что такое "экземпляр объекта" и зачем он нужен?)

Lamia 30.05.2009 19:33

Да лано придераться!Автор всё разжевал и в рот положил!Чего уж лучше!

zeppe1in 30.05.2009 19:41

Lamia
я не придераюсь. я хочу научица юзать дизасемблер.

Lamia 30.05.2009 19:49

:D Шутка понята////


Время: 01:57