> For the complete documentation index, see [llms.txt](https://yutewiyof.gitbook.io/intro-rev-ida-pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yutewiyof.gitbook.io/intro-rev-ida-pro/chast-12.md).

# Часть 12

[\[Используемые материалы\]](https://github.com/yutewiyof/intro-rev-ida-pro/tree/e1367e11cc661f3d69c02ae5f733b7dd168bc5ab/.gitbook/assets/files/12.zip)

Хорошо, чтобы не было скучно, мы будем объединять теорию и некоторые упражнения, в этом случае, это другая программа скомпилированная мной, которая называется **TEST\_REVERSER.EXE** и которая очень простая, но она поможет нам увидеть некоторые новые вещи в статическом реверсинге и которые мы проверим в отладчике.

Если мы запустим программу вне **IDA**, то мы увидим.

![](/files/-Ljtu9BHOqhwj4qv3ThX)

Нас просят ввести имя пользователя и затем пароль, а потом программа говорит нам, что мы проиграли, и она смеётся над нами.

Давайте откроем её в **IDA**, чтобы увидеть программу в статическом виде.

Поскольку я не использую символы, всё оказывается слегка уродливым.

![](/files/-Ljtu9BiV4RJJaPn2CeV)

Очевидно, она не открывается в функции **MAIN**, зато открывается в **EP**, но хорошо, действительность почти всегда такова и мы разберемся с этой проблемой.

Один из способов, который мы уже видели, чтобы попасть в "горячую часть программы" - найти строки, мы уже знаем как это делать, также в этих консольных программах на **C++**, это один из способов найти **MAIN**, который почти всегда работает.

Мы знаем, что в функцию передаются аргументы **ARGC**, **ARGV** и т.д. Это аргументы консоли.

**INT MAIN(INT ARGC, CHAR \*ARGV\[])**

Мы уже видели в предыдущем примере, что даже если аргументы не используются, они всё равно **ПОМЕЩАЮТСЯ** в стек, иногда это действует по умолчанию для консольных исполняемых файлов, поэтому мы можем искать их во вкладке **NAMES**, чтобы увидеть, есть ли они там или нет.

![](/files/-Ljpc9ZVtxLSJHayOt2b)

Отсюда и далее, когда я говорю - "На вкладке **XXX**", Вы уже должны знать, что это открывается в меню **VIEW → OPEN SUBVIEW → XXX**, чтобы не повторять это более.

Здесь с помощью комбинации **CTRL + F** мы фильтруем ввод, введём например **ARG** и мы увидим записи, давайте сделаем двойной щелчок по **\_P\_ARGC**.

![](/files/-Ljtu9CKWer7T6CJqrS0)

Ища ссылки с помощью **X** мы находим

![](/files/-Ljtu9CVeriawh4hK5xI)

Здесь мы видим как программа вызывает функции **\_P\_ARGV** и **\_P\_ARGC** и то, что она передает содержимое результата в функцию **MAIN**, адрес которой в этом случае - **0x401070**.

Если я посмотрю ту же самую функцию в моей **IDA**, в версии с символами.

![](/files/-Ljpc9ZajYSvzNl1TNpp)

И мы видим ссылку.

![](/files/-Ljpc9ZcQpZRIvvfRL2R)

Очевидно это не хорошая идея использовать такие читы, так можно только проверить, что метод для нахождения **MAIN** работает и здесь - это полностью верно, ища ссылки аргументов, которые передаются консолью, мы прибываем к **MAIN**.

Давайте переименуем этот вызов в **MAIN**.

![](/files/-Ljpc9Zex1pApYkrZraI)

**IDA** автоматически переименовывает **АРГУМЕНТЫ** узнав, что вышеупомянутая функция – это **MAIN**.

![](/files/-Ljtu9DLfu2MFW8J_LxI)

Сейчас, это больше похоже на версию с символами.

![](/files/-Ljtu9Dd-TA9-Z8A_krg)

Мы видим в этом случае, что переменные и аргументы более нумерованы чем в предыдущем примере.

Если мы сделаем двойной щелчок на любой переменной или аргументу, мы увидим статическое представление стека.

![](/files/-Ljtu9DsxR_f9qmsw-GV)

Мы смотрим снизу вверх и видим, что логически первыми идут аргументы функции, которые будут всегда под адресом возврата **R**, так как они передаются через **PUSH** и они сохраняются в стек перед вызовом функции с помощью **CALL**, которая затем сохраняет адрес возврата в стек.

Затем у нас идёт **S** или что, то же самое, что и **STORED EBP**, который является **EBP** функции, которая вызывается функцией **MAIN**, он сохраняется в стек, когда начинает выполняться функция с помощью инструкции **PUSH EBP**.

![](/files/-Ljtu9E5twoT8jnx7dCG)

Затем программа помещает **ESP** в **EBP**, помещая значение в **EBP**, которое оно будет иметь в этой функции, чтобы быть **БАЗОЙ** откуда берутся аргументы, которые ниже **БАЗЫ** и локальные переменные, которые выше неё, и наконец инструкция **SUB ESP, 0x94** сдвигает **ESP** выделяя место для переменных и локальных буферов, поэтому они выше, в этом случае размер для буфера будет равен **0x94**, потому что компилятор вычисляет, размер который ему нужен, чтобы зарезервировать место для переменных и буферов, в соответствии с тем, как мы запрограммировали нашу функцию.

**ESP** остается со значением выше этого зарезервированного пространства для локальных переменных и **EBP** указывает на **БАЗУ** или **ГОРИЗОНТ**, который делит стек на локальные переменные, которые выше и **СОХРАНЕННЫЙ EBP**, **АДРЕС ВОЗВРАТА** и **АРГУМЕНТЫ**, которые ниже него.

![](/files/-Ljtu9EHT1Xy5eB0PlPh)

Вот почему в функциях основанных на **EBP**, как только программа вызывает функцию, она сохраняет с помощью инструкции **PUSH EBP** значение **EBP** функции, которую вызывает программа, затем программа помещает **ESP** в **EBP** и это считается теперь как горизонт, вот почему в статическом представлении стека, **IDA** показывает **000000000** как горизонт, а выше видно знаки минус (**-**), а ниже знаки плюс (**+**).

Вот почему **VAR\_4** имеет значение **- 00000004**, потому что переменная берет **EBP** как **БАЗУ** или как **0** и математическим адресом переменной будет **EBP - 4**.

И ниже **ARGC** будет равен **EBP + 8**, это видно по колонки слева.

![](/files/-Ljtu9EYRzoI0WINqMMc)

Это можно проверить это в листинге, внутри функции **MAIN** где программа использует переменную **VAR\_4**, если мы сделаем правый клик на ней.

![](/files/-Ljtu9Ei3IDYieiGfPAI)

Вернемся к статическому представлению стека.

Когда мы видим здесь пустое пространство где нет смежных переменных, возможно это потому что выше есть **БУФЕР** (позже мы увидим случаи, в котором пустое пространство является структурой). Сейчас давайте немного поднимемся.

![](/files/-Ljpc9ZuYSLirEALOxLy)

Здесь мы видим переменную **BUF**, которая является первой переменной над пустой зоной, мы делаем правый клик и выбираем **ARRAY**.

![](/files/-Ljpc9Zw5ikFy3SjuxHr)

Мы видим, что размер **МАССИВА** равен **120** байт, потому что он состоит из **120** элементов по **1** байту.

![](/files/-Ljpc9Zyvpswo3_TrFKf)

Сейчас представление стека стало лучше.

![](/files/-Ljpc9_-qBQ9ozag0xJU)

Мы видим базу **EBP** и мы помним, что как только **EBP** и **ESP** равны это равносильно инструкции **MOV EBP, ESP**, затем программа вычитает из **ESP** значение **0x94** и **ESP** теперь начинает работать выше зоны для локальных переменных.

![](/files/-Ljpc9_1V98ktN1sHNi0)

Здесь мы видим область в которой **ESP** будет после инструкции **SUB ESP, 0x94**.

Здесь в левой стороне видно значение **-00000094** или что также равно **ESP = EBP – 094**, очевидно затем значение будет продолжать расти по мере работы программы, между другими подпрограммы и т.д., но всегда пока значение находится внутри этой функции **MAIN** и пока значение не выйдет из этой области, оно будет продолжать работать в области не выше **0x94** зарезервированную часть для переменных, чтобы не наступить на них.

Хорошо, однажды мы уже видели статическое представления стека, давайте отреверсим переменные, поскольку аргументы нам известны (**ARGC**, **ARGV**, и т.д.)

![](/files/-Ljpc9_30XQVoZhudr8a)

Мы уже видели, что **VAR\_4** это переменная **COOKIE\_SEGURIDAD** или **CANARY**, мы видим, что инструкция считывает это значение, затем **XORит** его с помощью **EBP** и сохраняет его в стек, чтобы защитить стек от **ПЕРЕПОЛНЕНИЯ**, поэтому давайте переименуем эту переменную.

![](/files/-Ljpc9_5LKOtDlWkUwf_)

Равно как и в предыдущем примере **API** функция **PRINTF** не имеет символов и она не отображается, но я наблюдаю строки для неё, которые она печатает в консоли и мы видим эту функцию по адресу **0x4011B0**.

![](/files/-Ljpc9_7dV4vINCSElr2)

И здесь внутри по адресу **0x401040** мы видим.

![](/files/-Ljtu9GS0x0cPsZ06okG)

Так что, давайте переименуем функцию по адресу **0x4011B0** в функцию с именем **PRINTF**.

![](/files/-Ljtu9GdRhQtfk7ptfg4)

Давайте идти дальше.

![](/files/-Ljpc9_DXQp_hldUCn4n)

Мы видим, что размер переменной инициализируется с помощью числа **8** и больше это значение никогда не изменяется, есть только два чтения среди следующих ссылок, поэтому мы переименуем размер этой переменной в имя **\_CONST\_8**.

![](/files/-Ljtu9H2ctOvfhPZgE19)

Затем мы видим вызов функции **GETS\_S**, которая является эволюцией функции **GETS**, но с ограничение по количеству вводимых символов, которые мы можем ввести(это такая защита), в этом случае максимальным значением будет **8**, которое помещается в **EAX** и передается как аргумент с помощью **PUSH EAX**, а затем **LEA** получает адрес переменной **BUF** или **BUFFER**.

![](/files/-Ljpc9_HHnaoYsYRo7w2)

Конечно, если мы введём больше символом чем **8** и нажмём **ENTER**, функция также будет обрезать ввод и случится возврат.

Так что мы знаем, что в **BUF** будет помещаться имя **ПОЛЬЗОВАТЕЛЯ**, которое мы набрали и что оно будет иметь максимум **8** символов.

![](/files/-Ljtu9HSSd4lyzTDzAJZ)

Здесь мы видим, что потом программа передаёт с помощью **PUSH EDX** адрес буфера снова, как аргумент к **API** функции **STRLEN**, чтобы получить длину строки, которая сейчас находится в **BUF** и соответствует введенному **ПОЛЬЗОВАТЕЛЮ**, и сохраняет длину в переменной **VAR\_90** через регистр-результат **EAX**, так что мы переименовываем **VAR\_90** в **LEN\_USER**.

![](/files/-Ljtu9Hbh8jSIrjHgDzG)

![](/files/-Ljtu9HolwI7_gEgHRVD)

Синяя стрелка всегда указывает на переход назад, который может быть **ЦИКЛОМ**, по адресу **0x4010CE** программа инициализирует счетчик **LOOP VAR\_84**, мы также видим, что по адресу **0x4010F5**находится условный переход, который оценивает условие выхода из цикла, счетчик начинается с **НУЛЯ**, и он будет увеличиваться в каждом цикле, и программа выйдет из цикла, когда счетчик будет больше или равен длине, которую мы ввели в **LEN\_USER**.

![](/files/-Ljpc9_P-dl8Q5qXDPIm)

Счетчик увеличивается к концу **ЦИКЛА** здесь.

![](/files/-Ljtu9IChYTegX63inbG)

Здесь он помещает значение **СЧЁТЧИКА** в **EAX** увеличивая его и затем снова сохраняет.

![](/files/-Ljtu9IKhME3Xjav-Lwx)

Здесь программа помещает первый байт **БУФЕРА** из **EBP + EDX + BUF** в **EAX**, поскольку **EBP + BUF** складывается со **СЧЕТЧИКОМ**, который сейчас равен нуль, то он будет увеличиваться пока работает **ЦИКЛ**, мы видим, что здесь будет складываться все значения символов, которые я набираю, поэтому переменная **VAR\_88** которая начинается с нуля, будет складываться в каждом цикле **HEX**значения каждого символа строки **БУФЕРА**.

Мы видим инструкцию, которую мы до этого даже ещё не видели - **MOVSX**.

**MOVSX** И **MOVZX**.

Обе инструкции берут байт и помещают его в регистр, в случае с **MOVZX** заполняются с помощью нулей старшие байты, в то время как в случае с **MOVSX** учитывается знак байта, если он положительный или меньше или равен **0x7F** он заполняется с помощью нулей и если он отрицательный или равен **0x80** или больше заполняется с помощью **0xFF**.

**MOVZX EAX, \[XXXX]**

Если содержимое **XXXX** будет равно **0x40, EAX** будет равен **0x00000040**.

Также например существует инструкция **MOVZX EAX, CL**.

Этот случай похожий, инструкция будет брать значение байта и заполнит с помощью нулей старшие байты.

**MOVSX EAX, CL**

Инструкция принимает во внимание знак байта, если **CL** - например равен **0x40**, **EAX** будет равен **0x00000040** и если бы он был бы равен **0x85**, в этом случае, поскольку у него отрицательный знак и это значение отрицательное **EAX** будет равен **0xFFFFFF85**.

Также, так как мы вводим символы букв и чисел через консоль, эти символы являются положительные **HEX** значениями, так что у нас не будет никаких проблем, программа будет складывать значения один за одним и сохранять.

![](/files/-Ljtu9Ia3sTjc-4ur5To)

Мы видим, что **ЦИКЛ** - это сложение символов, мы покрасим их тем же цветом.

![](/files/-Ljpc9_XH-PMUMnf9eB0)

Также я немного увеличил их, перетащив и отпустив нижний блок немного выше.

Есть люди, которые, чтобы убрать блоки с экрана, если они хотят, чтобы они их не беспокоили, группируют их, с помощью нажатия **CTRL** и делая клик в верхнюю панель каждого блока.

![](/files/-Ljtu9J2k9Yb7yhE5f-D)

Делаем правый клик и выбираем **GROUP NODES**, а затем вводим имя, например **LOOP**.

![](/files/-Ljpc9_a5BMc2cFjPqCR)

![](/files/-Ljpc9_cNj3MlSOFSvZQ)

Последнее, если Вы хотите увидеть блоки, которые спрятаны, то это можно сделать через **UNGROUP NODES**.

Затем программа выводит слово **ПОЛЬЗОВАТЕЛЬ** и говорит, что нужно ввести **ПАРОЛЬ**.

![](/files/-Ljtu9JbnUsypVcLlxMe)

Затем программа вызовет снова функцию **GET\_S** используя тот же самый буфер и тоже максимальное значение вводимых символов.

![](/files/-Ljpc9_gFjuCfJ5LqGjH)

Программа может повторно использовать тот же **БУФЕР** для **ПАРОЛЯ**, в любом случае программа полностью рассчитала **СУММУ HEX** значений символов **ПОЛЬЗОВАТЕЛЯ** и она больше не будет использовать строку **ПОЛЬЗОВАТЕЛЬ**.

![](/files/-Ljtu9K2rG5o5Kcy6Urg)

Сейчас она возьмёт **ПАРОЛЬ** и преобразует его в **HEX** как в предыдущем примере используя **ATOI**.

![](/files/-Ljtu9KF-lPobZ3Omrtz)

Здесь передаётся значение **VALOR\_PASSWORD** с помощью инструкции **PUSH EDX** и суммируется с помощью **PUSH EAX**, это будут два аргумента, которые передаются в функцию по адресу **0x401010**, давайте введём их.

![](/files/-Ljtu9KQsVeGyqXbFpii)

Здесь мы видим два аргумента, очевидно, что тот, который ниже будет **VALOR\_PASSWORD** так как передается с помощью **PUSH** в стек первым и второй, который кладется следующим, будет суммой, и он будет выше.

![](/files/-Ljtu9KZFAL9A2EAOHkA)

Я переименовываю их согласно этому, и затем, чтобы проверить нормально ли всё, сделайте правый щелчок по адресу **SUB\_0x401010** и выберите **SET\_TYPE**.

![](/files/-Ljpc9_qMKoVoGPwEPi6)

При этом **IDA** попытается объявить функцию со своими аргументами, чтобы показать их в ссылке и мы переименуем также это в функцию **CHECK**.

![](/files/-Ljtu9KrLsbC-snKPFlK)

И если мы пойдём к ссылке.

![](/files/-Ljpc9_uwPzEsgs-CIPI)

Мы видим, что **IDA** распространят имена и говорит мне, что у **EAX** есть **СЛОЖЕНИЕ** и **EDX** - это **VALOR\_PASSWORD**.

И что сделает функция **CHECK** с этими двумя аргументами?

Мы видим, что она сравнивает их, но сначала она берет значение **PASSWORD** и делает с ним операцию **SHL EAX, 1**

![](/files/-Ljtu9L9srLivJ9NPyNJ)

Мы знаем, что **SHL** сдвигает биты влево, заполняя нулями те, которые исчезают на другой стороне, но в частности **SHL REG, 1 -** это равносильно умножению на **2**.

![](/files/-Ljpc9_yY_86NRFZScUy)

Программа берет значение пароля, умножает его на **2** и сравнивает его с суммой символов слова **ПОЛЬЗОВАТЕЛЬ**.

![](/files/-Ljtu9Lb8dNyDbXmNQqT)

При этом мы получаем числовое значение символа, мы можем сделать формулу, которая суммирует все символы строки **pepe**, которую я использую как **ПОЛЬЗОВАТЕЛЬ**.

![](/files/-Ljpc9a1ob9ZY_jr1lSZ)

Мы видим, что сумма равна **0x1AA**, с другой стороны значение, которые мы ввели как пароль будет умножено на **2** перед сравнением с этим **0x1AA**, поэтому правильный пароль должен быть значением, которое при умножении на два даёт нам **0x1AA**.

**X\*2 = 0x1AA**

Давайте проясним, что эта программа имеет ограничения, если сложение даёт нечетное число, то невозможно, чтобы существовало значение x, которое при умножении на 2 даёт нам результат нечетное число, поэтому эти имена пользователей не имеют решений , в этой программе имеют решения только имена пользователей, чья сумма при сложении чётная.

Мы очищаем строку и вводим.

**X = 0x1AA / 2** и ответ поступает в десятичной системе счисления, очевидно, что с помощью **ATOI** он переводится из десятичной системы счисления в **HEX**.

![](/files/-Ljpc9a3slRnFZjfaPm6)

Если я введу **ИМЯ ПОЛЬЗОВАТЕЛЯ** как **pepe**, а **ПАРОЛЬ** как **213**, что произойдёт?

![](/files/-Ljtu9MH-4EnCJXh0Z6A)

Конечно, я вижу, когда сравнение одинаково внутри функции проверки.

![](/files/-Ljtu9MWKc4_gBzx6iTs)

Если они не равны, программа идёт в красный блок и возвращает **НУЛЬ**, а если они равны программа идёт в зелёный блок и возвращает **ЕДИНИЦУ**, давайте посмотрим, что произойдёт с этим возвращаемым значением.

![](/files/-Ljpc9a96qehk0-WkMJJ)

Программа сохраняет его здесь, давайте переименуем это значение в **FLAG\_EXITO**.

![](/files/-Ljpc9aBTNt5EH18YqGv)

Так что, поскольку, если мы видим **0**, то тогда пойдём к **BAD REVERSER**, а если **AL** равно **1**, то идём к **GOOD BOY** как это и случается у нас здесь.

Мне бы очень понравилось, если бы вы отладили этот крэкми и проверили всё, что мы вместе реверсили, установив **BPs** и увидев значения в каждом случае до окончательной проверки.

До встречи в **13**-й главе.

Автор оригинального текста — Рикардо Нарваха.

Перевод и адаптация на английский язык — IvinsonCLS.

Перевод и адаптация на русский язык — Яша Яшечкин.

Перевод специально для форума системного и низкоуровневого программирования - WASM.IN

02.09.2017

[**Источник: ricardonarvaja.info**](http://ricardonarvaja.info/WEB/IDA%20DESDE%20CERO/CURSO%20DE%20IDA%20TUTES/12-INTRODUCCION%20AL%20REVERSING%20CON%20IDA%20PRO%20DESDE%20CERO.docx)
