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

Discussion in 'Реверсинг' started by _=(mac)=_, 25 May 2009.

  1. _=(mac)=_

    _=(mac)=_ Member

    Joined:
    28 Feb 2009
    Messages:
    9
    Likes Received:
    8
    Reputations:
    0
    Дизассемблирование программ, откомпилированных 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
     
    #1 _=(mac)=_, 25 May 2009
    Last edited: 26 May 2009
    2 people like this.
  2. Balvan

    Balvan Member

    Joined:
    11 Mar 2009
    Messages:
    66
    Likes Received:
    5
    Reputations:
    0
    Молодец, продолжай! =)
     
    #2 Balvan, 25 May 2009
    Last edited: 25 May 2009
  3. _=(mac)=_

    _=(mac)=_ Member

    Joined:
    28 Feb 2009
    Messages:
    9
    Likes Received:
    8
    Reputations:
    0
    Ха!!! А я то думал что говно полное))))))
     
  4. zeppe1in

    zeppe1in Elder - Старейшина

    Joined:
    12 Jul 2006
    Messages:
    343
    Likes Received:
    66
    Reputations:
    18
    надо оформить получше, а то так читать не удобно
     
  5. _=(mac)=_

    _=(mac)=_ Member

    Joined:
    28 Feb 2009
    Messages:
    9
    Likes Received:
    8
    Reputations:
    0
    Вот блиин... Накосячил немного с оформлением( Извиняюсь и исправляю)))
     
  6. zeppe1in

    zeppe1in Elder - Старейшина

    Joined:
    12 Jul 2006
    Messages:
    343
    Likes Received:
    66
    Reputations:
    18
    _=(mac)=_
    и так вопросы)
    как ты узнал адрес где находится главная функция программы?
    и вот первый же call
    хорошо, указатель на строку я вижу.
    а вот остальное совсем не понятно. пуш и кол собственно не о чом не говорят.
    и что такое "экземпляр объекта" и зачем он нужен?)
     
  7. Lamia

    Lamia Elder - Старейшина

    Joined:
    11 Jul 2007
    Messages:
    186
    Likes Received:
    77
    Reputations:
    -9
    Да лано придераться!Автор всё разжевал и в рот положил!Чего уж лучше!
     
  8. zeppe1in

    zeppe1in Elder - Старейшина

    Joined:
    12 Jul 2006
    Messages:
    343
    Likes Received:
    66
    Reputations:
    18
    Lamia
    я не придераюсь. я хочу научица юзать дизасемблер.
     
  9. Lamia

    Lamia Elder - Старейшина

    Joined:
    11 Jul 2007
    Messages:
    186
    Likes Received:
    77
    Reputations:
    -9
    :D Шутка понята////