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

# Часть 20

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

## Уязвимости.

В этой главе, мы поговорим об уязвимоcтях и как анализировать самые простые из них.

## Что же такое уязвимость ?

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

Уязвимости - это результат ошибок или просчётов в дизайне системы. Хотя, в более широком смысле, они также могут быть результатом самих технологических ограничений, потому что, в принципе, не существует 100% безопасной системы. Таким образом, существуют теоретические и настоящие уязвимости.

То же самое правило применяется и к программам. Уязвимая программа - это такая программа, которая имеет ошибки или изъяны в программном коде и в зависимости от типа ошибок, эти ошибки могут быть эксплуатированы. Из-за этих ошибок может быть запущен вредоносный код в вышеупомянутой программе, но также и аутентификация может завершиться ошибкой и позволит совершать действия, которые не позволены пользователю, провоцировать сбои, поднимать привилегии, и т.д.

Конечно, на уровне ошибок повреждения памяти самые простые - это переполняющиеся буфера.

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

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

В исходном коде простой программы на **C**, буфер может быть представлен так:

**char buf\[xxx];**

Где **XXX** - это размер буфера. В нашем случае, это буфер в стеке длиной **0x300** байт.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgLMh9TXMNF2aqv%2F20-01.png?generation=1563215346968619\&alt=media)

Очевидно, эта программа ничего не делает, но мы всё равно компилируем её и загружаем в **IDA**.

Она приложена к этому туториалу, как файл **COMPILADO\_1.EXE**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgN7t9MdcuAUfH3%2F20-02.png?generation=1563215346176085\&alt=media)

Уже знаем, что нам нужно искать ссылки на аргументы **ARGC** или **ARGV**. Так мы можем попасть в функцию **MAIN** в консольной программе.

Делая двойной щелчок на них, я прибываю сюда:

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz2C8Yy2mXnrhnC9%2F03.png?generation=1563265873656877\&alt=media)

Теперь поищем перекрёстные ссылки с помощью клавиши **X**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgRFLP13NuijepP%2F20-04.png?generation=1563215346271990\&alt=media)

Ища перекрёстные ссылки, попадаем в известный блок, который вызывает функцию **MAIN**. В нашем случае, давайте дадим ему другое имя. Может быть такое, потому что блок ничего не делает.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz2YoLcUCOvp1nEN%2F05.png?generation=1563265870090881\&alt=media)

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

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgVhNMWM15AcT2j%2F20-06.png?generation=1563215346868256\&alt=media)

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

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgXANakKThlFLmX%2F20-07.png?generation=1563215346949625\&alt=media)

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

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgZVSEPI8xAzJEK%2F20-08.png?generation=1563215348400216\&alt=media)

Видим, что вышеупомянутая функция имеет два аргумента - буфер и максимальный размер того, что Вы можете ввести, для того, чтобы буфер не переполнился. Очевидно, в примере нет переполнения, потому что размер, который копируется, не превышает размера созданного буфера в **0x300** байт.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgaURhe6qqRtVj3%2F20-09.png?generation=1563215349046384\&alt=media)

Видно, что не существует возможности переполнить буфер, так как то, что я буду вводить будет меньше или равно **0x300** байт.

Давайте посмотрим на эту программу в **IDA**. Файл приложен к данному туториалу с именем **COMPILADO**\_**2.EXE**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgcIS7zdnnc-8cy%2F20-10.png?generation=1563215348813459\&alt=media)

Я компилировал файл с символами, и **IDA** также нашла символы на моей машине.

Так выглядит намного лучше. Уже появилась функция **MAIN** c её аргументами и переменными.

Давайте немного проанализируем этот код в **IDA**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz3Q08loKxdAWVS8%2F11.png?generation=1563265865935186\&alt=media)

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

Здесь у нас есть три аргумента - **ENVP**, **ARGV** и **ARGC**, которые программа не использует внутри функции **MAIN**.

Они помещаются в стек перед вызовом **MAIN**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHggJQ1qQGRVjjr1%2F20-12.png?generation=1563215346702357\&alt=media)

Программа помещает три аргумента в стек перед тем как сделать **ВЫЗОВ** функции **MAIN**.

Программа сохраняет **АДРЕС ВОЗВРАТА**, который является адресом, который знает куда нужно вернуться программе после выхода из **CALL**. В нашем случае, адрес возврата имеет значение **0x401200**, если не будет рандомизации.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgid7dEjfl5RYgn%2F20-13.png?generation=1563215346494427\&alt=media)

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

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgkkcsbmzjqx781%2F20-14.png?generation=1563215346767610\&alt=media)

Затем программа начинает выполнять функцию **MAIN**. Первое, что делает программа — это исполняет инструкцию **PUSH EBP**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz42fesb5gDl9GyV%2F15.png?generation=1563265870302170\&alt=media)

Эта инструкция сохраняет в стеке значение **EBP**, которое использовалось функцией, которая вызвала функцию **MAIN**, чуть выше **АДРЕСА ВОЗВРАТА 0x401200**. Мы не знаем, какое значение оно может иметь, потому что оно меняется при каждом запуске программы, но сохраненный **EBP** или **STORED EBP** - это отец этой функции.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz4BfwtZzyGYoX-3%2F16.png?generation=1563265866253916\&alt=media)

Здесь, **EBP** будет сохраняться в стек, выше адреса возврата.

Следующая инструкция, которая исполняется - такая.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHgqBgn3vkczjK9h%2F20-17.png?generation=1563215349045082\&alt=media)

Она устанавливает **EBP** как базу в этой функции и выравнивает его с **ESP**. Это одинокая инструкция **MOV**. Она меняет значение **EBP**, а не стек.

Затем, следующая инструкция **SUB ESP**, **0x304** передвигает указатель **ESP** вверх памяти, резервируя место для локальных переменных и буферов в стеке, которые расположены выше **STORED EBP** и **ESP** будет работать в функции основанной на **EBP**. Чуть выше резервируется место.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz4WCjN_vzFlHaw2%2F18.png?generation=1563265872090313\&alt=media)

Здесь, мы видим зарезервированное пространство для переменных и буферов. Чуть ниже находится **S** (**STORED EBP**).

Первая переменная, которая почти всегда находится сразу после **S** - это **CANARY**, которая нужна для защиты стека. В нашем случае переменная называется **VAR\_4**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz4fMPgdJGvJ7y8h%2F19.png?generation=1563265868653421\&alt=media)

Здесь видим, что программа читает значение **\_\_SECURITY\_COOKIE**, которое является случайным значением, которое создаётся каждый раз, когда запускается программа. Это значение **XOR**ится с помощью **EBP** и сохраняется в переменную **VAR\_4** как мы уже видели ранее. Переименовываем её в **CANARY**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz4qFHWkxURitlIC%2F20.png?generation=1563265866263639\&alt=media)

Выше **CANARY** находится буфер **BUF**. Давайте посмотрим его в статическом представлении стека.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz52mVFFoxE_sEmn%2F21.png?generation=1563265866306902\&alt=media)

Когда я вижу пустое пространство в статическом представлении стека, я предполагаю, что это может быть буфер. Поэтому мы делаем правый щелчок на этих байтах **BUF** и выбираем **ARRAY**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHh-o1eIjewgOUvB%2F20-22.png?generation=1563215346187044\&alt=media)

Видно, что его размер равен **768** \* **1** байт, т.к. это длина каждого элемента. Следовательно, размер буфера равен **768**, который в **HEX** представлении равен **0x300**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz5JU2knguKh1ELJ%2F23.png?generation=1563265873689400\&alt=media)

Поэтому, мы соглашаемся с **IDA** и получается буфер **BUF**, который уже определен как буфер из **0x300** байт в **HEX** или **768** в десятичном виде.

Здесь, есть вызов функции **GETS\_S** и два её аргумента: максимальный размер **0x300** и другой её аргумент - адрес буфера, который получается через инструкцию **LEA**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHh3E_Htx9LII18o%2F20-24.png?generation=1563215346234215\&alt=media)

Поэтому, мы проверяем, что размер буфера **BUF** равен **0x300** байт и он вмещает максимум **0x300** байт, который мы вводим через функцию **GETS\_S**.

Очевидно, что если бы мы могли переполнить буфер, копируя больше чем **0x300** байт, мы переписали бы переменные **CANARY, STORED EBP** и **АДРЕС ВОЗВРАТА**, которые находятся чуть ниже **БУФЕРА**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHh5O28hpst7tzMm%2F20-25.png?generation=1563215346592039\&alt=media)

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

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHh7b6T0csNGipoR%2F20-26.png?generation=1563215346681499\&alt=media)

Очевидно, часто пользователь не знает размер данных, которые будут скопированы. Если это так, размер должен быть хорошо проверен, что этот **SIZE** не будет больше, чем размер буфера.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz5teTN0pKvGNu8q%2F27.png?generation=1563265866841069\&alt=media)

Здесь видим буфер из **0x10** байт или **16** байт в десятичном виде и пользователь имеет возможность ввести размер буфера через функцию **GETS\_S**. Очевидно, не существует никакой проверки этого максимального значения, поэтому, если я компилирую и запускаю этот файл (**COMPILADO\_3.EXE**).

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz64h1Urxzej8FVv%2F28.png?generation=1563265868099336\&alt=media)

Видно, что теперь в программе есть **БАГ** и она стала уязвима. Если откроем её в **IDA**, даже если мы не имеем исходного кода, то увидим.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz6EgvX9Gp3EQXno%2F29.png?generation=1563265872430414\&alt=media)

Как и раньше это переменная **CANARY**. Чуть выше есть буфер **BUF**. Давайте посмотри его длину в статическом представление стека.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHhFAKCoX1RHRx7v%2F20-30.png?generation=1563215347618823\&alt=media)

Размер буфера равен **16** байт \* **1**, так как это размер одного элемента. Поэтому размер буфера в **HEX** равен **0x10**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHhHqBrIFN1lj2EB%2F20-31.png?generation=1563215346821013\&alt=media)

Другими словами, если бы мы смогли скопировать более **16** байтов в буфер, произошло бы переполнение и перезапись переменных **CANARY**, **STORED EBP** и **АДРЕС ВОЗВРАТА**.

Посмотрим размер переменной.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHhJKH8SQdJfj3GD%2F20-32.png?generation=1563215347671230\&alt=media)

Видно, что после вывода с помощью функции **PRINTF** сообщения **PLEASE ENTER YOUR NUMBER**, вызывается функция **SCANF\_S** для ввода значений с клавиатуры, которые сохраняется в переменной, размер которой равен **DWORD** и передает размер переменной с помощью инструкции **LEA** в регистр **EAX**.

Давайте посмотрим описание функции **SCANF\_S**.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHhLta6rtvrwvgp2%2F20-33.png?generation=1563215346872120\&alt=media)

Функция **scanf\_s** читает данные из стандартного потока **stdin**, и пишет данные по адресу, который указан в **аргументе**. Каждый **аргумент** должен быть указателем на переменную типа, который соответствует спецификатору типа в **формате**. Если копирование происходит между строками, которые перекрываются, поведение не определено.

Другими словами, это как противоположность функции **PRINTF**. Только вместо печати с форматом, формат вводится с консоли с форматом в буфер. В нашем случае, формат **%d** интерпретирует данные как десятичное число.

Таким образом, когда Вы вызываете функцию **GETS**\_**S** использую этот размер, который я напечатал, программа будет копировать заданное количество байт и если размер больше чем **0x10**, буфер будет переполнен.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjttkJvbq8Uocl4jWiJ%2F-Ljttz6xLQZEgH2Yzb_Q%2F34.png?generation=1563265873695732\&alt=media)

Возможным решением этой проблемы будет проверять размер введённых данных перед тем как копировать ввод пользователя в буфер.

![](https://333516620-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Ljp8Kl9OQ2AqdU_D0G8%2F-LjqtEP2kV1qNtJHVrED%2F-LjqtHhPGPDV43M2NFQe%2F20-35.png?generation=1563215347017043\&alt=media)

Было бы неплохо проанализировать программу, чтобы убедиться, что это решение делает её неуязвимой или оставляет её такой же уязвимой. Эта программа называется **VULNERABLE\_O\_NO.EXE**, и мы будем обсуждать её в следующей главе.

До встрече в следующей **21**-й главе друзья.

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

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

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

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

28.10.2017

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