> 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** байт.

![](/files/-LjqtHgLMh9TXMNF2aqv)

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

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

![](/files/-LjqtHgN7t9MdcuAUfH3)

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

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

![](/files/-Ljttz2C8Yy2mXnrhnC9)

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

![](/files/-LjqtHgRFLP13NuijepP)

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

![](/files/-Ljttz2YoLcUCOvp1nEN)

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

![](/files/-LjqtHgVhNMWM15AcT2j)

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

![](/files/-LjqtHgXANakKThlFLmX)

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

![](/files/-LjqtHgZVSEPI8xAzJEK)

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

![](/files/-LjqtHgaURhe6qqRtVj3)

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

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

![](/files/-LjqtHgcIS7zdnnc-8cy)

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

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

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

![](/files/-Ljttz3Q08loKxdAWVS8)

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

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

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

![](/files/-LjqtHggJQ1qQGRVjjr1)

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

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

![](/files/-LjqtHgid7dEjfl5RYgn)

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

![](/files/-LjqtHgkkcsbmzjqx781)

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

![](/files/-Ljttz42fesb5gDl9GyV)

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

![](/files/-Ljttz4BfwtZzyGYoX-3)

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

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

![](/files/-LjqtHgqBgn3vkczjK9h)

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

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

![](/files/-Ljttz4WCjN_vzFlHaw2)

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

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

![](/files/-Ljttz4fMPgdJGvJ7y8h)

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

![](/files/-Ljttz4qFHWkxURitlIC)

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

![](/files/-Ljttz52mVFFoxE_sEmn)

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

![](/files/-LjqtHh-o1eIjewgOUvB)

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

![](/files/-Ljttz5JU2knguKh1ELJ)

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

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

![](/files/-LjqtHh3E_Htx9LII18o)

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

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

![](/files/-LjqtHh5O28hpst7tzMm)

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

![](/files/-LjqtHh7b6T0csNGipoR)

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

![](/files/-Ljttz5teTN0pKvGNu8q)

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

![](/files/-Ljttz64h1Urxzej8FVv)

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

![](/files/-Ljttz6EgvX9Gp3EQXno)

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

![](/files/-LjqtHhFAKCoX1RHRx7v)

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

![](/files/-LjqtHhHqBrIFN1lj2EB)

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

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

![](/files/-LjqtHhJKH8SQdJfj3GD)

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

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

![](/files/-LjqtHhLta6rtvrwvgp2)

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

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

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

![](/files/-Ljttz6xLQZEgH2Yzb_Q)

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

![](/files/-LjqtHhPGPDV43M2NFQe)

Было бы неплохо проанализировать программу, чтобы убедиться, что это решение делает её неуязвимой или оставляет её такой же уязвимой. Эта программа называется **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)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
