> 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-41.md).

# Часть 41

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

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

![](/files/-LjrE0paPBl7TkT9XiIC)

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

Здесь мы не ищем уязвимость или чего-то ещё. Мы только смотрим расположение данных.

Давайте откроем исполняемый файл в **ЗАГРУЗЧИКЕ**. Мы заставляем его загрузить символы. Теперь мы видим функцию **MAIN**.

![](/files/-Ljtu9GPYHauJ1wbJDgd)

Сейчас мы находимся в функции **MAIN**. У неё просто есть вызов функции **EJEMPLOS\_UBICACION**. И ничего больше. Давайте вызовем меню **DEMANGLE NAMES –> NAMES**.

![](/files/-LjrE0poX53Mk83OYhzI)

Вот этот вызов. Давайте посмотрим, что происходит внутри функции. Мы видим, что у неё нет аргументов, только переменные.

> **VOID \_\_CDECL EJEMPLOS\_UBICACION()**

![](/files/-LjrE0pu9pEDgcpYDS1Q)

Если бы у функции были бы аргументы, они были бы в круглых скобках. Также посмотрим статическое представление стека.

![](/files/-Ljtu9HJBZwTGZkE1FG2)

Если бы функция имела аргументы, то они должны были быть ниже адреса возврата (**R**). Но здесь ничего нет. Только переменные.

Здесь я вижу переменную **CANARY**, которая сохраняется в переменную **VAR\_4**.

![](/files/-Ljtu9HZjGAfYPPR9nlK)

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

![](/files/-Ljtu9HiF1oRPg1G8qJM)

Мы видим, что переменная **MENSAJE\_EN\_STACK** так и переменная **MENSAJE\_EN\_STACK\_SIN\_INICIALIZAR**, являются буферами в стеке. Одна из них имеет размер **100** байт, а другая **23** байта.

Здесь мы видим два случая.

```c
char mensaje_en_stack[]="hola reverser en stack"; // Инициализирован
char mensaje_en_stack_sin_inicializar[100]; // Не инициализирован
```

Мы видим, что во втором случае, программа резервирует **100** байт, потому что она не знает, что будет сохранено в эту переменную. Это может быть то, что вводит пользовать и не является постоянным значением. В то время как другая переменная резервирует пространство для строки **HOLA REVERSER EN STACK**. Переменная уже имеет фиксированную длину.

![](/files/-LjrE0qFju2wDa-83edJ)

Размер строки равен **22** байта + терминирующий нуль. Всего получается **23** байта.

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

Сначала программа получает исходный адрес строки с помощью **СМЕЩЕНИЯ** и помещает адрес в регистр **ESI**. Затем программа помещает адрес назначения в регистр **EDI**, который имеет адрес буфера в стеке.

![](/files/-Ljtu9ILpMo2y-h8dOFy)

**Что такое секция rdata?**

Объявляет инициализированную секцию данных, которая доступна для чтения, но не доступна для записи. Компиляторы Microsoft используют эту секцию для размещения в ней констант.

Мы видим, что в секцию **RDATA VISUAL STUDIO** при компиляции программы сохраняет константные данные, которые не будут меняться и в нашем случае строка сохраняется там. Поскольку секция недоступна для записи, то данные не меняется. Затем программа скопирует данные в стек.

![](/files/-Ljtu9IcJEVOWK2GY_Un)

Инструкция **LEA** помещает адрес буфера в регистр **EDI** и копирует строку помощью инструкции **REPS MOVS**, инициализирую переменную.

В первом случае, переменная была инициализирована, в том время как во втором случае - нет.

Логически, во втором случае этот неинициализированный буфер существует для чего-то, и программа будет использовать его и заполнять его в какой-то момент выполнения программы.

![](/files/-Ljtu9IqKr_AP-kFtbcV)

Программа будет делать это здесь. Это похоже на первый случае. Но теперь программа использует **API WINDOWS** для копирования строки. Она берет **СМЕЩЕНИЕ** строки из секции **RDATA** "**HOLAREVERSER SIN INICIALIZAR**" и копирует строку с помощью функции **STRCPY**.

> **STRCPY(MENSAJE\_EN\_STAQCK\_SIN\_INICIALIZAR, "HOLA REVERSER EN STACK SIN INICIALIZAR");**

Очевидно, что компилятор как в первом случае, не будет использовать **API WINDOWS**. Он будет использовать инструкции похожие на **REPS MOVS**, в то время как во втором случае это уже программный код, который может использовать **API**.

Совпадение состоит в том, что этот второй случай имеет определенную строку постоянной длины, но это может быть строка, введенная пользователем, и ее длина может измениться. Здесь нам нужно будет проверить, что строка не переполняет буфер длинной строкой.

Очевидно, что из этих двух строк мы получаем адреса строк с помощью инструкции **LEA**, и программа печатает эти адреса.

![](/files/-LjrE0qahQ1IOOY8hkiJ)

![](/files/-LjrE0qeTfMAFgAITiCM)

Если я помещу здесь **BP**, и выберу локальный отладчик, и запущу программу в режиме отладки.

![](/files/-Ljtu9JUvJ_ceGaA44Fb)

Я вижу, что адреса, которые печатает программа принадлежат стеку. Поэтому я могу пойти и посмотреть эти строки по этим адресах.

![](/files/-Ljtu9JiLePTWrKWxB8C)

Если я нажму **A**, чтобы сконвертировать строку в **ASCII** представление.

![](/files/-LjrE0qpQtjlMMbwZ3sX)

И другую.

![](/files/-LjrE0qxVHSmUCtEtkcH)

До этих пор все идёт нормально. Давайте посмотрим другие строки. Мы остановим отладчик и вернемся назад к загрузчику.

![](/files/-LjrE0r-Xlb42JHqxI5S)

Мы видим, что две другие переменные являются указателями (**OFFSET**) и занимают **4** байта (**DD**).

Давайте сначала посмотрим на указатель **MENSAJE\_EN\_DATA**.

![](/files/-Ljtu9L-5VjqTZD0o8YP)

Мы видим, что в этом случае, как мы сказали ранее, переменная является указателем, которая сохраняет адрес, который получен с помощью **СМЕЩЕНИЯ** в переменную стека **MENSAJE\_EN\_DATA**.

Строка находится в секции **RDATA** и сохраняется здесь. То, что мы обрабатываем в стеке является указателем на неё, в то время как в предыдущем случае, строка была полостью скопирована в стек заполняя сам буфер.

![](/files/-LjrE0r8YO1ezYqW1CyA)

Мы видим, что сейчас нам не нужна инструкция **LEA**, чтобы найти адрес, потому что значение переменной является указателем и это адрес, которые помещается в регистр **EAX** и помещается в стек для печати этого значения. Посмотрим, будем ли мы отлаживать код как раньше?

![](/files/-Ljtu9LSALMZ5UZRdL6N)

Если я пойду к этому адресу в секцию **RDATA**, строка будет находиться здесь.

![](/files/-LjrE0rEnuopIR4AiwU6)

И в переменной стека этот адрес сохраняется (**OFFSET**).

![](/files/-LjrE0rHEJEClM9_IGtg)

Здесь есть **СМЕЩЕНИЕ**. Если я хочу увидеть числовое значение, я нажимаю **D**.

![](/files/-LjrE0rL-SjdZxtvLPcy)

Если я нажму **D** несколько раз, когда число станет **DWORD**, **IDA** обнаружит, что оно указывает на строку и меняете его на **СМЕЩЕНИЕ**.

![](/files/-LjrE0rOslbcZE-gtCdG)

![](/files/-LjrE0rSnsNFl6T1JQ-4)

Здесь мы видим, что поместив мышь на переменную, **IDA** покажет нам, что переменная имеет сохранённый адрес строки(**OFFSET**), так как это переменная указатель, которая хранит адреса.

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

![](/files/-LjrE0rVSOsLkAy237or)

Сначала мы посмотрим **СТРОКУ1**, которая инициализируется и глобальную переменную **СТРОКУ2**.

Если я посмотрю код, я понимаю, что строка используется здесь.

![](/files/-Ljtu9NT1NV6_j-107de)

Мы видим, что в стек помещается строки. Если мы пойдем по этому адресу.

Мы видим, что-то непонятное. Сейчас мы находимся в секции данных, которая используется для глобальных переменных.

![](/files/-LjrE0rb1_1whVW6I2Qm)

У нас есть буфер **CHAR STRING** \[**18**]. **IDA** знает, что это строка такой-то длины, которая будет в этом буфере. Здесь это сохраненная строка "**DONDE SE UBICARA**". Если мы нажмём **D**, мы увидим байты. Если мы нажмем **A**, мы вернем представление назад, как и раньше.

![](/files/-LjrE0rgGiDma5TvfChk)

Обычно, когда есть второе определение, то это потому, что есть некоторая ссылка на некоторый **API**.

![](/files/-Ljtu9O5PuO-ZJxqRIZw)

Здесь мы видим, что есть ссылка.

![](/files/-Ljtu9OIxs2PJJ0Fneii)

Это аргумент к функции **PRINTF**. Вторым аргументом является адрес. Если мы сделаем правый щелчок, мы увидим, что это адрес, где существует строка.

![](/files/-Ljtu9OUjvW7sw-4nQeM)

Здесь, когда мы делаем правый щелчок, то заменяем смещения на строки прямым адресом, который будет напечатан. Мы видим, что обе строки находятся в секции **DATA**.

![](/files/-LjrE0rvyzi75e81NIKu)

Единственное различие заключается в том, что **STRING2** не инициализируется, поэтому строку нельзя вернуть в исходное состояние, потому что она заполнен нулями, т.е. буфер пуст. Поэтому сделаем правый щелчок и выберем пункт **ARRAY**.

![](/files/-LjrE0rznQmN3UXjmD4Q)

> **CHAR \* STRING2 DW 0Ah DUP(0)**

**DUP(0)** - означает, что нуль повторяется **DW**(**2** байта)\* **0xA** раз, т.е. **20** десятичных байт.

![](/files/-LjrE0s1fsUtI0PCKe0s)

Если я изменю буфер на байтовый тип, а не двойное слово, он будет более точным. Буфер будет равен **0x14** байт или **20** десятичных байт, заполненных нулями.

Давайте посмотрим ссылки, где буфер заполняется.

![](/files/-Ljtu9PNFEROJ5KBXLEE)

Я вернул **СМЕЩЕНИЕ**, которое передаётся как аргумент, сделав правый щелчок и выбрав **OFFSET \* CHAR** и, затем, с помощью клавиши **X** я могу увидеть ссылки. Первая ссылка, это где будет заполняться буфер.

![](/files/-LjrE0sEvlbl5Sj0b4uB)

Я вижу, что здесь программа копируют строку, которая поступает из переменной "**DONDE SE UBICARA**?" в буфер, который можно перезаписывать и можно модифицировать

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

Если мы попробуем это сделать, мы увидим.

![](/files/-Ljtu9PxsaxJ4zclx2L2)

Адреса строк **STRING** и **STRING2**, принадлежат секции **DATA**.

![](/files/-Ljtu9QN1RR1TGAakhGp)

![](/files/-LjrE0sRxlBYOMZgfh_b)

Единственное, что нам остается, это увидеть кучу. Это указатель в стеке, который хранит адрес строки, которая была размещена в куче.

![](/files/-Ljtu9QxsII69m0ZIWj-)

Мы видим, что здесь программа сохраняет **СМЕЩЕНИЕ**, т.е. адрес строки "**HOLA REVERSER EN DATA**", и затем передает адрес строки в функцию **STRLEN** для того, чтобы получить её длину

К результату добавляется число **1**, как и в исходном коде.

![](/files/-LjrE0sZfWWrBdCeIhTd)

![](/files/-Ljtu9ROeiZcx8D3vaCh)

И этот размер передается как размер в функцию **MALLOC**, чтобы зарезервировать в куче буфер длиной строки + **1**.

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

![](/files/-Ljtu9Ro5_7Oh_qb3-Lo)

Здесь программа сохраняет адрес этой кучи, который указывает на этот буфер, в переменную указатель стека под именем **MENSAJE\_EN\_HEAP**.

![](/files/-Ljtu9S5lfEreWEg6vjJ)

Затем программа скопирует строку **MENSAJE\_EN\_DATA** в буфер, созданный в куче.

![](/files/-LjrE0spdMS4YVkhth1k)

Теперь программа получает длину строки "**HOLA REVERSER EN**" и складывает с начальным адресом буфера в куче. Он просто указывает, чтобы заменить слово **DATA** на **HEAP**, так как она вызывает функцию **MEMCPY 4** байтов передавая слово "**HEAP**" как источник и как указатель назначение на слово **DATA**, которые находятся в буфере кучи.

В исходном коде мы видим следующее

**MEMCPY(MENSAJE\_EN\_HEAP+STRLEN("HOLA REVERSER EN "),"HEAP",4);**

Назначение, это адрес сообщения в кучи + размер строки "**HOLA REVERSER EN**". Это будет указывать на слово **DATA**, которое будут перезаписано **4** байтами, являющимися источником слова **HEAP**.

Давайте отлаживаем, чтобы убедиться, что всё это правда.

![](/files/-LjrE0svogGRjXHRCFlZ)

![](/files/-LjrE0szfh21MYDHUqTb)

Здесь я беру размер строки "**HOLA REVERSER EN DATA**" и он равен **15** байт вместе с нулем.

![](/files/-LjrE0t2gOPaHcHB-CYK)

Здесь программа добавляет **1** и передает значение в функцию **MALLOC**, чтобы зарезервировать **0x16** байт.

![](/files/-Ljtu9TSFsQcYs3YBH3g)

Теперь, у нас есть буфер куче. В моем случае он располагается по адресу **0x67D4F0**.

![](/files/-Ljtu9TgNFdRe81mjiNC)

Поскольку программа находится в режиме отладки при запуске отладчика, становится очень ясно, что **0x16** байт равно **22** десятичным байтам.

![](/files/-LjrE0tFcYkPFnhxa_tE)

Поскольку программа находится в режиме отладки (если это не так, то ничего не сработает) и пока ничего не написано, мы видим байты **BAAD F00D**.

Существует пять **DWORDS** т.е. буфер длиной в **20** байтов + два последних байта равны **0xFEEE**. Я спрашиваю у **IDA** сколько в десятичной системе равно **0x16** байт.

![](/files/-LkxkXWIz8XD5VtNyeNi)

Мы увидим уже больше информации о кучи. Сейчас же это буфер байтами (**BAAD F00D**).

Затем программа сохраняет адрес в переменную указателя стека.

![](/files/-Ljtu9UMNTZ_fowYtmPd)

Затем в функции **STRCPY**, программа копирует строку **HOLA REVERSER** в **DATA** в мои **BAAD F00D**.

![](/files/-LjrE0tR_yfs3Mk7qtfG)

Если я потрассирую программу с помощью **F8**.

![](/files/-Ljtu9Uv07njVx_g98mX)

В буфере кучи, я вижу скопированные байты. С помощью клавиши **A** я преобразую их в строку **ASCII**.

![](/files/-LjrE0tX2xrR6IMWUVo_)

Здесь программа получает длину строки "**HOLA REVERSER EN**".

![](/files/-Ljtu9VOEHvExhx3wy9C)

Длина строки равна **11**. Затем программа добавляет начальный адрес буфера в **КУЧИ**.

![](/files/-LjrE0teewmzdDnW0oIc)

Если я разложу исходную строку в байты, то увижу.

![](/files/-LjrE0tjvmdjfRm4_Ru-)

Затем, там я нажимаю клавишу **A**.

![](/files/-LjrE0toNZK1uYOC7-FH)

![](/files/-Ljtu9WZuq_M_FACXNm_)

Я вижу, что программа будет перезаписывать **4** байта слова "**DATA**" на слово "**HEAP**".

![](/files/-LjrE0txQ1GGpxcT7-uB)

Поэтому, я могу разложить слово **HEAP** и собрать целую строку.

![](/files/-Ljtu9X9A8Hzvx50mhVv)

Хорошо. Строка в куче уже собрана. Остаётся только напечатать её адрес.

![](/files/-Ljtu9XQz09mekGywwR7)

![](/files/-Ljtu9Xe-uyMb4W2MCWf)

Хорошо. На сейчас это всё. Постарайтесь хорошо разобраться в этом примере, как работают и как управлять строками.

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

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

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

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

21.04.2018

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