_=(mac)=_
28.05.2009, 08:51
Дизассемблирование программ, откомпилированных Visual C++
Часть 3.
Идентификация многомерных массивов
В предъидущей статье я описывал идентификацию одномерных массивов целого типа, но на этот раз я решил немного поднапрячь мозг и описать разбор матрицы дробного типа, принимая к сведению некоторые замечания по поводу второй части)))
Для начала мы, как обычно, напишем программу, принимающую от нас матрицу дробных чисел из 5 строк и 5 столбцов. Ну что ж… Приступим!
#include <iostream>
using namespace std;
int main()
{
const int ROW = 5;
const int COLUMN = 10;
double matrix [ ROW ] [ COLUMN ];
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COLUMN; j++)
cin >> matrix [i] [j];
return 0;
}
Матрица – это двумерный массив, поэтому для ввода каждого элемента необходимы два цикла, которые вкладываются друг в друга. В таких случаях всегда выполняется первым самый внутренний цикл. Он будет выполняться столько же, сколько и внешний.
В примере нашем внешний цикл инициализирует переменную i, которая является индексом строки, а индексом столбца является переменная j во втором, вложенном, цикле. Во внутреннем цикле происходит ввод значения элемента матрицы с индексами i, j.
Во время работы программы необходимо ввести 5 строк чисел по 10 элементов в каждой строке.
Ну что ж… Компилим, загружаем в Иду, переходим на 00401000 и начинаем…
.text:00401000 sub_401000 proc near
.text:00401000
.text:00401000 var_1A0 = dword ptr -1A0h
.text:00401000 var_19C = dword ptr -19Ch
.text:00401000 var_198 = dword ptr -198h
.text:00401000 var_8 = dword ptr -8
.text:00401000 var_4 = dword ptr -4
;Объявление локальных переменных…
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 sub esp, 1A0h
.text:00401009 mov [ebp+var_8], 5
.text:00401010 mov [ebp+var_4], 0Ah
.text:00401017 mov [ebp+var_19C], 0
.text:00401021 jmp short loc_401032
;И так… Здесь происходит присвоение ячейкам памяти [ebp+var_8] и [ebp+var_4] значения соответственно 5 и 10 (0Ah), это и есть объявление наших констант ROW и COLUMN, а вот строчка “mov [ebp+var_19C], 0” – это присвоение переменной [ebp+var_19C] (индекс строки - i) нулевого значения (int i = 0), после чего переход на 401032.
.text:00401023 ; ---------------------------------------------------------------------------
.text:00401023
.text:00401023 loc_401023:
.text:00401023 mov eax, [ebp+var_19C]
.text:00401029 add eax, 1
.text:0040102C mov [ebp+var_19C], eax
;Здесь инкременируется значение j (j++).
.text:00401032
.text:00401032 loc_401032:
.text:00401032 cmp [ebp+var_19C], 5
.text:00401039 jge short loc_401087
.text:0040103B mov [ebp+var_1A0], 0
.text:00401045 jmp short loc_401056
;Во!. Сначала сравниваем i с ROW, то бишь 5, и если i больше 5 или равен (jge), то переходим на адрес 401087, т.е. выход из программы, а если иначе, то переходим к выполнению следующих инструкций. А следующая инструкция у нас - mov [ebp+var_1A0], 0. Это присвоение некой ячейке памяти (переменной) нулевого значения. Хм… Опустим глаза ниже… Мы видим, что происходит увеличение нашей этой переменной на 1. Сомнений не остаётся: [ebp+var_1A0] – это j, индекс столбца. Значит теперь мы находимся уже в теле второго, вложенного цикла. А теперь джимп на 401056… Переходим туда…
.text:00401047 ; ---------------------------------------------------------------------------
.text:00401047
.text:00401047 loc_401047:
.text:00401047 mov ecx, [ebp+var_1A0]
.text:0040104D add ecx, 1
.text:00401050 mov [ebp+var_1A0], ecx
;В данном блоке инкременируется значение j и программа выполняется дальше, т.е. переходит управление на 401056))
.text:00401056 loc_401056:
.text:00401056 cmp [ebp+var_1A0], 0Ah
.text:0040105D jge short loc_401085
.text:0040105F mov edx, [ebp+var_19C]
.text:00401065 imul edx, 50h
.text:00401068 lea eax, [ebp+edx+var_198]
.text:0040106F mov ecx, [ebp+var_1A0]
.text:00401075 lea edx, [eax+ecx*8]
; Здесь идёт проверка условия внутреннего цикла, а именно проверка j на 10, т.е. сравнение с COLUMN. Если j больше или равно 10, то на 401085(где нас опять пошлют на 401023, где на единицу увеличивается i), в противном случае заносим в edx индекс i, после чего умножаем его на 50 с занесением результата в edx (imul edx, 50h). Так, а тут (lea eax, [ebp+edx+var_198]) в eax кладём смещение по указателю [ebp+edx – 198]. Теперь в ecx мы копируем j (mov ecx, [ebp+var_1A0]), после чего мы кладём в edx смещение [eax+ecx*8] – это как раз тот адрес, который ниже кладётся в стек как параметр к функции ввода. Это и есть буфер для ввода, в котором окажется число, которое мы введём. Этот буфер будет всегда уникален благодаря блокам 401023 и 401047, которые инкременируют значение индексов j и i, по содержанию которых как раз – таки и высчитывается ячейка памяти, где будет храниться введённое нами число.
.text:00401078 push edx
.text:00401079 mov ecx, offset dword_4207D4
.text:0040107E call sub_401090
.text:00401083 jmp short loc_401047
;Кладём в стек буфер для ввода и вызываем функцию ввода. Напомню, что массив является дробного типа, поэтому каждое введённое нами число будет ещё и в регистре сопроцессора ST7 и в памяти. Но при вводе нового числа в ST7 оно попадает на место старого, но старое так же и остаётся в памяти. Почему же, вы спросите, тогда нет команд fst, fstp и прочих команд сопроцессора, хоть и регистры ST задействованы? Потому, что мы вводим эти числа, но никаких операций над ними не выполняем. Так… Ну более менее разъяснил всё.. Теперь переходим по jmp short loc_401047…
.text:00401085 ; ---------------------------------------------------------------------------
.text:00401085
.text:00401085 loc_401085:
.text:00401085 jmp short loc_401023
; Сюда программа получает управление всегда, когда отработает вложенный цикл, а от сюда мы перейдём на инкремент j….
.text:00401087 ; ---------------------------------------------------------------------------
.text:00401087
.text:00401087 loc_401087:
.text:00401087 xor eax, eax
.text:00401089 mov esp, ebp
.text:0040108B pop ebp
;Так, вот и всё) Сюда программа переходит тогда, когда по 401039 мы не проходим проверку j < ROW, оно же j < 5, оно же cmp [ebp+var_19C], 5/ jge short loc_401087.
Ещё раз немного повторюсь по логике работы этой программы. Главный цикл крутит свою работу 5 раз. В нём есть вложенный цикл, который выполняется 10 раз, запрашивая каждый раз новое число, а когда мы ввели 10 чисел, управление переходит на родительский цикл(как-никак условие вложенного цикла не истинно, потому что i будет равно 10), который увеличивает значение j на единицу, и выполнение вложенного цикла повторяется ещё раз, в памяти оказывается ещё 10 чисел, и управление переходит опять на родительский цикл, где j снова увеличивается. Получается, что когда условие главного цикла станет ложью (j = 5), в памяти будет 50 чисел.
Ну, думаю немного разобрались… Правда единственный минус, что мы не сделали цикл вывода введённого массива. Про это я напишу следующую статью, в которой мы будем через OllyDbg лезть в откомпилированный файл этой нашей программы, и вписывать прямо в отладчике код нашего цикла вывода чисел.
Часть 3.
Идентификация многомерных массивов
В предъидущей статье я описывал идентификацию одномерных массивов целого типа, но на этот раз я решил немного поднапрячь мозг и описать разбор матрицы дробного типа, принимая к сведению некоторые замечания по поводу второй части)))
Для начала мы, как обычно, напишем программу, принимающую от нас матрицу дробных чисел из 5 строк и 5 столбцов. Ну что ж… Приступим!
#include <iostream>
using namespace std;
int main()
{
const int ROW = 5;
const int COLUMN = 10;
double matrix [ ROW ] [ COLUMN ];
for (int i = 0; i < ROW; i++)
for (int j = 0; j < COLUMN; j++)
cin >> matrix [i] [j];
return 0;
}
Матрица – это двумерный массив, поэтому для ввода каждого элемента необходимы два цикла, которые вкладываются друг в друга. В таких случаях всегда выполняется первым самый внутренний цикл. Он будет выполняться столько же, сколько и внешний.
В примере нашем внешний цикл инициализирует переменную i, которая является индексом строки, а индексом столбца является переменная j во втором, вложенном, цикле. Во внутреннем цикле происходит ввод значения элемента матрицы с индексами i, j.
Во время работы программы необходимо ввести 5 строк чисел по 10 элементов в каждой строке.
Ну что ж… Компилим, загружаем в Иду, переходим на 00401000 и начинаем…
.text:00401000 sub_401000 proc near
.text:00401000
.text:00401000 var_1A0 = dword ptr -1A0h
.text:00401000 var_19C = dword ptr -19Ch
.text:00401000 var_198 = dword ptr -198h
.text:00401000 var_8 = dword ptr -8
.text:00401000 var_4 = dword ptr -4
;Объявление локальных переменных…
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 sub esp, 1A0h
.text:00401009 mov [ebp+var_8], 5
.text:00401010 mov [ebp+var_4], 0Ah
.text:00401017 mov [ebp+var_19C], 0
.text:00401021 jmp short loc_401032
;И так… Здесь происходит присвоение ячейкам памяти [ebp+var_8] и [ebp+var_4] значения соответственно 5 и 10 (0Ah), это и есть объявление наших констант ROW и COLUMN, а вот строчка “mov [ebp+var_19C], 0” – это присвоение переменной [ebp+var_19C] (индекс строки - i) нулевого значения (int i = 0), после чего переход на 401032.
.text:00401023 ; ---------------------------------------------------------------------------
.text:00401023
.text:00401023 loc_401023:
.text:00401023 mov eax, [ebp+var_19C]
.text:00401029 add eax, 1
.text:0040102C mov [ebp+var_19C], eax
;Здесь инкременируется значение j (j++).
.text:00401032
.text:00401032 loc_401032:
.text:00401032 cmp [ebp+var_19C], 5
.text:00401039 jge short loc_401087
.text:0040103B mov [ebp+var_1A0], 0
.text:00401045 jmp short loc_401056
;Во!. Сначала сравниваем i с ROW, то бишь 5, и если i больше 5 или равен (jge), то переходим на адрес 401087, т.е. выход из программы, а если иначе, то переходим к выполнению следующих инструкций. А следующая инструкция у нас - mov [ebp+var_1A0], 0. Это присвоение некой ячейке памяти (переменной) нулевого значения. Хм… Опустим глаза ниже… Мы видим, что происходит увеличение нашей этой переменной на 1. Сомнений не остаётся: [ebp+var_1A0] – это j, индекс столбца. Значит теперь мы находимся уже в теле второго, вложенного цикла. А теперь джимп на 401056… Переходим туда…
.text:00401047 ; ---------------------------------------------------------------------------
.text:00401047
.text:00401047 loc_401047:
.text:00401047 mov ecx, [ebp+var_1A0]
.text:0040104D add ecx, 1
.text:00401050 mov [ebp+var_1A0], ecx
;В данном блоке инкременируется значение j и программа выполняется дальше, т.е. переходит управление на 401056))
.text:00401056 loc_401056:
.text:00401056 cmp [ebp+var_1A0], 0Ah
.text:0040105D jge short loc_401085
.text:0040105F mov edx, [ebp+var_19C]
.text:00401065 imul edx, 50h
.text:00401068 lea eax, [ebp+edx+var_198]
.text:0040106F mov ecx, [ebp+var_1A0]
.text:00401075 lea edx, [eax+ecx*8]
; Здесь идёт проверка условия внутреннего цикла, а именно проверка j на 10, т.е. сравнение с COLUMN. Если j больше или равно 10, то на 401085(где нас опять пошлют на 401023, где на единицу увеличивается i), в противном случае заносим в edx индекс i, после чего умножаем его на 50 с занесением результата в edx (imul edx, 50h). Так, а тут (lea eax, [ebp+edx+var_198]) в eax кладём смещение по указателю [ebp+edx – 198]. Теперь в ecx мы копируем j (mov ecx, [ebp+var_1A0]), после чего мы кладём в edx смещение [eax+ecx*8] – это как раз тот адрес, который ниже кладётся в стек как параметр к функции ввода. Это и есть буфер для ввода, в котором окажется число, которое мы введём. Этот буфер будет всегда уникален благодаря блокам 401023 и 401047, которые инкременируют значение индексов j и i, по содержанию которых как раз – таки и высчитывается ячейка памяти, где будет храниться введённое нами число.
.text:00401078 push edx
.text:00401079 mov ecx, offset dword_4207D4
.text:0040107E call sub_401090
.text:00401083 jmp short loc_401047
;Кладём в стек буфер для ввода и вызываем функцию ввода. Напомню, что массив является дробного типа, поэтому каждое введённое нами число будет ещё и в регистре сопроцессора ST7 и в памяти. Но при вводе нового числа в ST7 оно попадает на место старого, но старое так же и остаётся в памяти. Почему же, вы спросите, тогда нет команд fst, fstp и прочих команд сопроцессора, хоть и регистры ST задействованы? Потому, что мы вводим эти числа, но никаких операций над ними не выполняем. Так… Ну более менее разъяснил всё.. Теперь переходим по jmp short loc_401047…
.text:00401085 ; ---------------------------------------------------------------------------
.text:00401085
.text:00401085 loc_401085:
.text:00401085 jmp short loc_401023
; Сюда программа получает управление всегда, когда отработает вложенный цикл, а от сюда мы перейдём на инкремент j….
.text:00401087 ; ---------------------------------------------------------------------------
.text:00401087
.text:00401087 loc_401087:
.text:00401087 xor eax, eax
.text:00401089 mov esp, ebp
.text:0040108B pop ebp
;Так, вот и всё) Сюда программа переходит тогда, когда по 401039 мы не проходим проверку j < ROW, оно же j < 5, оно же cmp [ebp+var_19C], 5/ jge short loc_401087.
Ещё раз немного повторюсь по логике работы этой программы. Главный цикл крутит свою работу 5 раз. В нём есть вложенный цикл, который выполняется 10 раз, запрашивая каждый раз новое число, а когда мы ввели 10 чисел, управление переходит на родительский цикл(как-никак условие вложенного цикла не истинно, потому что i будет равно 10), который увеличивает значение j на единицу, и выполнение вложенного цикла повторяется ещё раз, в памяти оказывается ещё 10 чисел, и управление переходит опять на родительский цикл, где j снова увеличивается. Получается, что когда условие главного цикла станет ложью (j = 5), в памяти будет 50 чисел.
Ну, думаю немного разобрались… Правда единственный минус, что мы не сделали цикл вывода введённого массива. Про это я напишу следующую статью, в которой мы будем через OllyDbg лезть в откомпилированный файл этой нашей программы, и вписывать прямо в отладчике код нашего цикла вывода чисел.