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

# Часть 23

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

Осталось решить незаконченное упражнение **IDA1.EXE**.

![](/files/-Ljttxmsm6DnA_OdCle-)

Функция основана на **EBP** как предыдущие упражнения. Разница заключается в том, что этот пример скомпилирован с помощью **DEVC**++, который имеет особый способ компиляции вызовов **API**, отличный от обычного способа и это может вызвать головокружение, у тех, кто никогда такого не видел.

Хорошо, "**VAMOS POR PARTES**", как сказал **Джек**. Первое, что мы видим - это функция **MAIN**. Если она не появляется, Вы также можете перейти к важной функции, просто посмотрев на строки.

![](/files/-Ljttxn1MH5cc1UM7siT)

Сделаем двойной щелчок по строке "**You are a winner man je**" и мы попадём на этот адрес:

![](/files/-LjttxnFSm0G6ny6UNvr)

Я помещаю курсор мышки над стрелкой перекрёстной ссылки. Мы можем видеть куда ведёт эта перекрёстная ссылка.

![](/files/-LjttxnUgKx5UPNOi7yG)

Но лучше, конечно, нажать **CTRL** + **X**. Так мы попадём туда, где находится данный код.

![](/files/-LjttxndB2f-GVe9EcKc)

Переходим в этот код, щелкая по этой ссылке.

![](/files/-LjqtGZ2JwfTrULfW0lW)

Я меняю в зеленый цвет блок кода, который является **ХОРОШИМ СООБЩЕНИЕМ**. Нам нужно попасть в него.

Мы видим, что перед ним идёт сравнение переменной **VAR**\_**C** с константой **0x51525354**.

Если мы сделаем правый щелчок на этом значение **0x51525354**, появятся альтернативные значения, как можно представить это значение.

![](/files/-LjqtGZ4iHdqCR8E-KCR)

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

Мы также можем видеть, что компилятор не использует **CANARY** защиту, потому что в начале функции программа должна считать переменную из секции **DATA** и **XOR**'ить эту переменную с помощью регистра **EBP** и сохранить её чуть выше **STORED EBP** в стек, а также, считать её снова, непосредственно перед выходом из функции, чтобы вызвать **CALL**, который проверит это значение переменной. Ничего из этого не происходит здесь. Если мы посмотрим статический анализ стека, сделав двойной щелчок на любой переменной.

![](/files/-Ljttxo7NiEpLFZjKdcw)

Мы видим, что единственное, что есть в стеке, чуть выше **STORED EBP**, это переменная **VAR**\_**C**, которая сравнивается со строкой **QRST**, чтобы идти к хорошему сообщению, что исключает, что это переменная **CANARY**, так как в этом случае это проверка, которая принадлежит исходному коду программы. **CANARY** не подмешивается к решениям исходного кода программы. Это что-то добавленное самим компилятором, внешнее по отношению к исходному коду и это не защита.

Мы можем переименовать эту переменную, чтобы не путать её с **CANARY**. Назовём её как **VAR\_DECISION**.

![](/files/-LjqtGZ8FxXqG8EVsU8v)

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

Те, кто смотрит на этот код впервые, обратят внимание, что существуют переменные и аргументы связанные с **EBP**, но есть и другие, которые отсчитываются от **ESP**.

![](/files/-LjttxoWBSPNoQ3YkGq1)

Весь этот начальный код, добавлен компилятором, чтобы установить **ESP** чуть выше переменных и локальных буферов. После исполнения **SUB ESP**, **0B8** программа подготовлена к этому. Хотя программа никогда не изменяет намного больше памяти чем выравнять и округлить, мы могли бы делать математические расчёты. Но если мы не хотим осложнять себе жизнь, мы можем отладить программу, чтобы увидеть какое значение осталось в **ESP**, чтобы закончить всё это и начать с оригинального кода функции.

Тот, кто захочет отладить это место, может сделать это, но у нас есть другая помощь от **IDA**, которая очень полезна. Это расчёт отклонения **ESP** в стеке начиная с самого начала стека функции, что принимается за нуль.

![](/files/-LjqtGZDc4mKxJdPx2sL)

Если посмотрим на код сейчас, после установки этой опции.

![](/files/-LjttxooF-gXewI-A6sk)

Мы видим влияние каждой инструкции на стек начиная с нуля, что является началом стека. После выполнения инструкции **PUSH EBP**, стек уменьшается на **4**, поэтому у второй строки справа написано **004**.

Вторая инструкция - это **MOV EBP**, **ESP**, которая не изменяет стек, потому что это только одинокая инструкция **MOV** и поэтому регистр **ESP** не меняется, а изменяется только регистр **EBP**.

![](/files/-LjttxouCASRL1Vm9nH3)

Вот почему третья строка имеет то же самое значение **004**, потому что инструкция не изменила значение **ESP**.

Давайте запомним, что **ESP** и **EBP** остаются одинаковыми и **EBP**, начиная отсюда, будет ссылкой отстающей на **4** байта от начала **ESP**.

Следующая строка вычитает значение **0xB8** из **ESP**, поэтому **ESP** будет равен **0xBC** от начала отсчёта в стеке и поскольку **EBP** был равен **4**, разница между **EBP** и **ESP** будет только **0xB8** байт.

![](/files/-Ljttxp0ACe_n5Tkpoej)

Мы видим, что потом расстояние не меняется, даже при вызове функций \_\_**ALLOCA** и \_\_\_**MAIN**, которые добавлены компилятором. Поэтому, мы можем заключить, что всё это не влияёт ни на что. Расстояние между **EBP** и **ESP** всё также равно **0xB8** и это пространство для локальных переменных и буферов.

![](/files/-LjttxpB3CISfTq5XALH)

Здесь уже начинается код самой функции и расстояние от **ESP** до начала стека осталось равным **0xBC** и **0xB8** - это область, зарезервированная для локальных переменных и буферов.

Поскольку переменная **VAR\_9C** является временной переменной, созданной компилятором и не используется больше, то мы переименуем её в **TEMP**.

![](/files/-LjttxpLiGsEAdinJmM3)

Ниже переменной **TEMP** у нас есть буфер. Я делаю правый щелчок и выбираю – **ARRAY**. Мы видим, что размер буфера равен **140** байт в десятичной системе, так как каждый элемента равен **1** байт, поэтому и размер буфера равен **140** байт в десятичной системе.

![](/files/-LjqtGZP4pkHRclO6O7x)

![](/files/-LjttxpbTqSyQDuTVTin)

Если бы нам удалось переполнить буфер, скопировав в него больше **140** байт, он бы имел уязвимость типа **BUFFER OVERFLOW**, эксплуатируя которую, можно было бы перезаписать переменную **VAR**\_**DECISION** и если бы могли писать дальше вниз, нам удалось бы дойти до **STORED EBP** и **RETURN** и перезаписать их, в зависимости от того, сколько байт мы скопировали.

Давайте продолжим реверсить. Самая высокая точка компиляции - это способ, как Вы передаёте аргументы в функции. Вместо того, чтобы использовать **PUSH** для сохранения аргументов в стек, сохраняйте их непосредственно с помощью инструкции **MOV**. Мы увидим скоро это пример.

Здесь всё становится ясно. В этом случае, **PRINTF** имеет единственный аргумент, который является адресом строки "**You are a winner je**" и больше не имеет никаких других аргументов.

![](/files/-LjqtGZT6XEFWeCL7BtK)

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

![](/files/-LjqtGZVXTdV1uu2Lz7K)

Если мы изменим представление на этот пункт.

![](/files/-Ljttxq6u-LZXWwvnuxE)

Мы видим, что то, что действительно происходит, состоит в том, что адрес строки помещается в содержимое **ESP** (самое верхнее положение стека), чтобы использовать его как аргумент. Другими словами, это программа вместо того, чтобы исполнить **PUSH** на вершине стека, помещает аргументы в положение стека с помощью инструкции **MOV** и логически **PUSH** изменяет значение **ESP**, в то время как инструкция **MOV** – нет. Это можно увидеть по значению **BC**, которое расположено непосредственно перед функцией.

![](/files/-LjqtGZZMqs9dt45D8Fr)

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

![](/files/-LjttxqOjrbWY2cyu7FA)

Мы видим, что первый **PRINTF** имеет **3** аргумента, потому что он заменяет форматную строку в строке `buf : %08x cookie : %08x\n`. Для двух верхних аргументов, будет так.

![](/files/-LjttxqVTU_YpO0LjJjb)

Первый аргумент - это адрес переменной **VAR**\_**DECISION**, который получен с помощью инструкции **LEA**. Инструкция помещает адрес в **EAX** и сохраняет его в содержимое **ESP + 8**.

Второй аргумент - это адрес переменного буфера, который получен также с помощью **LEA**. Инструкция помещает его в **EAX** и сохраняет адрес в содержимое **ESP+4** как следующий аргумент. Третий аргумент сохраняет адрес в содержимое **ESP** и это будет адресом строки. В нашем случае, это строка с форматом.

Если Вы запустите программу вне **IDA**, мы увидим, что программа печатает в консоли адреса обоих переменных, так как программа заменяет исходную строку на форматную, на **HEX** значения обоих адресов, потому что используется последовательность **%x**, которая является преобразованием вывода строки в **HEX** представление.

![](/files/-LjqtGZecWsN-nQ5wPaR)

![](/files/-Ljttxqlxg3r47Z0HH6z)

Затем программа продолжает вызывать функцию **GETS**, которая принимает символы с клавиатуры, без каких-либо ограничений, из-за чего Вы можете написать без проблем более **140** символов.

![](/files/-LjqtGZiYHRhR4-BeZ87)

Здесь также программа передаёт адрес буфера, в который будут поступать символы с клавиатуры как аргумент, в содержимое **ESP**

Так что мы знаем, что если я напишу например **140** символов **A** и затем символы **TSQR**, так как они должны идти наоборот из-за режима **LITTLE ENDIAN**, программа должна показать мне хорошее сообщение, так как мы будем перезаписывать переменную **VAR**\_**DECISION** значением **QRST**, которая находится чуть ниже буфера. Давайте сначала сделаем всё это вручную, перед тем как сделать скрипт.

![](/files/-LjqtGZkzZvqI9fHZ5GV)

Я печатаю строку в **IDA**, и копирую её в буфер обмена, и вставляю её здесь.

`AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATSRQ`

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

Когда программа ожидаем ввода я вставляю подготовленную строку и ...

![](/files/-LjqtGZmzGqf3byjCIHW)

Скрипт очень простой. Он также совпадает с предыдущим. В нашем случае он не имеет двух потоков ввода для **STDIN**, а имеет только один.

> **from** subprocess **import** \*
>
> p = Popen(\[**r'C:\Users\ricna\Desktop\New folder (6)\IDA1.exe'**, **'f'**], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
>
> **print "ATACHEA EL DEBUGGER Y APRETA ENTER\n"**
>
> raw\_input()
>
> primera=**"A"** \*140 + **"TSRQ\n"**
>
> p.stdin.write(primera)
>
> testresult = p.communicate()\[0]
>
> **print** primera
>
> **print**(testresult)

![](/files/-LjqtGZo19lVsguYySw6)

Здесь Вы видите результат. Многие не смогли бы это сделать из-за способа передачи аргументов. Но сейчас будет проще сделать упражнение №**2**, так как оно очень похожее и скомпилировано таким же образом, и Вы уже знаете про этот трюк.

Следующее упражнение называется **IDA2.EXE**.

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

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

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

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

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

27.11.2017

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