PDA

Просмотр полной версии : Отладка программ, откомпилированных Visual C++. Часть 1.


_=(mac)=_
25.05.2009, 20:25
Дизассемблирование программ, откомпилированных 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 Шутка понята////