Дизассемблирование программ, откомпилированных 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 Code: .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 ;и выход [/LEFT][/COLOR] Из этого листинга видно, что данная функция отвечает за логику работы программы. Эта программа оперирует числами целого типа, и анализ такой программы довольно тривиальное занятие. Сейчас мы усложним немного программу, изменив в исходном коде тип переменных с целочисленных(int) на дробные(double). Изменяем, компилим, загружаем в Иду и видим: Code: .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 ;конец [/LEFT][/COLOR] В этом примере мы более – менее разобрали работу с дробными числами. Из этого всего стоит сделать несколько выводов: 1. Операции с дробными числами выполняет сопроцессор 2. Дробные числа хранятся в специальном наборе регистров ST0 … ST7
_=(mac)=_ и так вопросы) как ты узнал адрес где находится главная функция программы? и вот первый же call хорошо, указатель на строку я вижу. а вот остальное совсем не понятно. пуш и кол собственно не о чом не говорят. и что такое "экземпляр объекта" и зачем он нужен?)