# Часть 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yutewiyof.gitbook.io/intro-rev-ida-pro/chast-12.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
