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

# Часть 46

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

Хорошо. Мы будем рассматривать упражнение из **44** главы.

<http://ricardonarvaja.info/WEB/INTRODUCCION%20AL%20REVERSING%20CON%20IDA%20PRO%20DESDE%20CERO/EJERCICIOS/PRACTICA_44.7z>

Прежде чем это сделать, давайте рассмотрим некоторые вещи.

Эксплоиты для кучи сильно зависят от программы и её уязвимости. Некоторое из уязвимостей эксплуатируются, а некоторые нет.

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

В основном, эксплоит для кучи работает хорошо, если планеты выровнены (is a comic expression for a very difficult task) и если автор эксплоита работает над ним хорошо. Он может получить более **80%** надежности. В случаях, когда всё идет не так как надо, потому что программа не позволяет управлять кучей, или автор эксплоита плохо рассчитал адреса, то он может получить менее **30%** или **60%** надежности.

Что я подразумеваю под манипуляцией кучей, или массажем кучи, или как бы это ни называлось? Чтобы заполнить пробелы, т.е. свободные блоки с помощью различных распределений разных размеров, прежде чем найти блок для переполнения, нужно чтобы он был помещен в позицию перед указателем, **VTABLE** или что-то, что мы можем перезаписать.

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

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

Очевидно это не просто и мы должны знать, что мы делаем. Мы ничего не может сделать вслепую.

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

Как мы видим сейчас, указателей заголовка **ШИФРУЮТСЯ(XOR)** со значениями, которые меняются, поэтому куча работает по другому и программа делает множество проверок в указателях. Поэтому эти методы не работают в наши дни, и сегодня их бесполезно изучать.

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

После этой части, я сделаю новую версию упражнения **44** с множеством выделений, как в реальном случае, чтобы вы могли попрактиковаться, чтобы заполнить все отверстия в сыре. Чтобы иметь больше возможностей эксплуатации и повысить надежность.

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

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

Я открываю исполняемый файл в загрузчике **IDA**.

![](/files/-LjrTBUdMN2P8aPPngNX)

Хорошо. После загрузки библиотеки **MYPEPE.DLL** программа печатает строку "**INGRESE UNA CANTIDAD DE NUMEROS ENTEROS**" и вызывает функцию **\_SCANF\_S**. Затем, с помощью инструкции **LEA**, программа получает адрес переменной **NUMERO**, чтобы сохранить в ней значение введенное в десятичном формате, так как формат равен **%D**.

![](/files/-LjrTBUrdn285ipalIoO)

Затем программа берет это число и умножает его на **4**. Программа использует это число как размер в функции **MALLOC**.

```
shl eax, 2; Равносильно to EAX * 4
```

И сохраняет адрес выделенного блока в переменную **DST**. Я переименую эту переменную в **P\_BLOQUE\_MI\_SIZE**.

![](/files/-Ljtu3w-1ZJds2VG2AWc)

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

![](/files/-LjrTBV7UHaeFuYTDMxF)

Программа сохраняет адрес функции **SYSTEM** в переменную **DST3**. Я изменяю её имя на понятное нам.

![](/files/-Ljtu3wZEKEVe3fOcUP3)

Теперь программа вызывает оператор **NEW**. Код будет выглядеть лучше, если я изменю имена через **DEMANGLE NAMES**.

![](/files/-LjrTBVTUWbPpN99wSJ0)

Размер аргумента **SIZE** равен **0x10** и это константа.

![](/files/-LjrTBV_dTfFa1Hy67AS)

Оператор **NEW** вызывает функцию **MALLOC** с тем же размером и если память может выделиться, то регистр **EAX** будет отличен от нуля и программа будет идти к блоку с инструкциями **POP EBP - RET**возвращая адрес выделенного блока в регистре **EAX**.

![](/files/-Ljtu3xAlVVtyR92p1ms)

Я добавил к переменной аббревиатуру. Переменная будет хранить адрес блока фиксированного размера равного **0x10**. Программа также сохранит этот же адрес в переменную **ARRAY**.

![](/files/-LjrTBVxnI9otQR8DUl5)

Затем программа сохранит адрес функции **PRINTF** в этом буфере, который указывает на **ARRAY**. Видно, что это массив указателей или **DWORDS**, потому что программа видимо индексирует значения группами по **4** байта. Программа просто заполняет первое поле массива адресом **PRINTF**.

Сумма регистров **ECX + EAX** равна регистру **ECX**, поскольку регистр **EAX** равен **0**. Т.е. программа будет сохранять адрес функции **PRINTF** в начальном адресе массива, т.е. в первом поле.

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

![](/files/-LjrTBWKMtHaPf-sDZFs)

Я определил тип **SYSTEM\_T**, и он является указателем на **SYSTEM**. Поэтому у нас есть **4** указателя по **4** байта каждый. Общая длина равна **0x10** байтов, т.е. **16** байтов в десятичной системе. Размер того, что будет выделять программа (массив указателей).

![](/files/-LjrTBWR1gRh8nq1eC3E)

Затем программа предупреждает печатью адреса блока с моим размером **P\_BLOQUE\_MI\_SIZE** и говорит мне, что туда я буду писать мои целые числа.

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

![](/files/-LjrTBWZBn6aJ0jU0h9_)

Затем программа просит нас ввести наше первое целое число и входит в цикл, который помечен зеленым. Программа помещает в переменную **I**, которая является счетчиком значение равное нулю. На выходе из цикла происходит сравнение счетчика со значением. Если счет больше чем значение, программа выходит из цикла.

Т.е. основная идея цикла состоит в том, чтобы записать целые числа, например если мы ввели в **NUMERO** значение **4** в десятичной системе, программа выделяет **4 \* 4** байт памяти, т.е. **16** десятичных байт т.е. **0x10** в шестнадцатеричной системе, и программа будет должна повторить цикл **4** раза, чтобы сохранить четырехбайтовый указатель и увеличить на **4** адрес в каждом цикле. Таким образом, программа будет повторять цикл **4** раза для сохранения **4** байтов. Каждый раз программа будет сохранять **16** байт в десятичной системе в блок из **16** байт и переполнения не будет.

Мы уже видели, что выход из цикла будет когда **I**, т.е. счетчик, будет больше или равен значение **NUMERO**, которое я ввел в начале.

Переполнение здесь происходит при умножении. Если моё первоначальное значение **NUMERO** равно, например **1073741825**, что соответствует **0x40000001**, то при умножении на **4**, значение переполнит возможный максимум в **32** бита. Результат будет **4**, и функция будет выделять только **4** байта.

![](/files/-LjrTBWnvXc3Cbg7goLI)

![](/files/-LjrTBWtm2h7wYM3x2qp)

Поэтому программа будет выделять только **4** байта и при записи в каждом цикле будет копировать только **4** байта и повторит цикл **1073741825** раз, так как это значение **NUMERO**, которое мы набрали и это то, что оценивается, и счетчик сравнивает с этим значением для выхода.

Очевидно, что программа работает хорошо до тех пор, пока как умножение на 4 введенного числа не превышает максимального **32** битного целого.

Многие спрашивают, как я получаю значение **0x40000001**. Легко. Я просто делю значение **0xFFFFFFFF** на **4** и это даёт мне результат **0x3FFFFFFF**.

![](/files/-LjrTBX1tnRRSWhn0VG5)

При умножении на **4** результат будет близок к значению **0xFFFFFFFF**. Я увеличиваю значение на один за раз, пока регистр не переполнится и результат будет маленьким.

![](/files/-LjrTBX7NX45hj_ilTwV)

Поэтому умножая значение **0x40000000** на **4** даёт мне результат нуль. И ничего не будет работать. Я добавлю ещё единицу к значению и получу уже результат **4**. Теперь всё работает. У меня есть диапазон значений от **0x40000001** и выше, где я произвожу переполнение и результатом которого является небольшое значение.

Очевидно, умножения на **0x40000000**, к значению которому я добавлю по одному, будет работать.

![](/files/-Ljtu3z7I7K6I9jBmNH3)

Поэтому, мы видим, что основная идея здесь состоит в том, чтобы переполнить блок в который я пишу целые числа, пытаясь добраться до блока массива указателей. Кроме того, в середине цикла программа использует сохраненный указатель для печати в первое поле массива.

![](/files/-Ljtu3zQW1AejuvRkfV_)

Программа в каждом цикле печатает слово **CORRECTO**, используя указатель на **PRINTF** сохраненный в массиве и добавив в качестве предыдущего значения регистра **ECX**, которое равно нулю, которое происходит от умножения на нуль. Поэтому, если мы не будем перезаписывать указатель, программа будет переходить на печать, но если мы сможем перезаписать массив, мы сможем перейти к выполнению кода.

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

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

Очевидно, это не зависит от нас в данном случае. И если ничего не получится, мы не сможем эксплуатировать программу. Если было бы множество распределений как в первом практическом занятии, мы могли бы заполнить кучу, выделив память в свободных блоках, чтобы заставить блок переполнить и пойти на более высокий адрес.

![](/files/-LjrTBXLyCHlYtVP3KA-)

Последнее вещь заключает в том, что целое число, которое мы ввели, программа сохраняет его во временной переменной и она копирует в блок число только с помощью функции **MEMCPY** с размером **4** байта, если оно больше чем **0x20**. Если значение меньше, программа пропустит копирование.

![](/files/-LjrTBXShvovoqyArdSr)

Адрес, где программа записывает значение увеличивается на **4** за раз. Другими словами, это массив целых чисел. Вот почему программа увеличивает значение на **4** за раз.

![](/files/-Ljtu4-G5EJs8G3I39xu)

Хорошо. Файл уже проанализирован. Посмотрим, что произойдет если я запущу его вне **IDA**.

![](/files/-Ljtu4-u71y32V_-6O72)

Давайте попробуем, чтобы наш блок выделял тот же размер, что и массив указателей т.е. **0x10**, который будет иметь определенную логику, поскольку сначала программа выделяет мой блок, а затем массив указателей. Оба с одинаковым размером. Мой блок остался с меньшим адресом для переполнения и переписал другой, но сначала я буду пробовать в **WINDOWS 7**, потому что эта система более дружелюбна для кучи.

![](/files/-Ljtu40EQRFevDTE_KRr)

Это выглядит хорошо. Блок для переполнения находится по адресу **0x612900**, а блок с массивом указателей по адресу **0x612918**. Я буду запускать его **10** раз, чтобы узнать, какой процент в порядке (Помните, что если вы изменили **PAGE HEAP** для этого процесса, вам придется установить его как **NORMAL HEAP**).

![](/files/-Ljtu40M3hoCS4NHB38C)

Расстояние всегда **18** байт, потому что будучи одинаковым размером и в **WINDOWS 7** которая лучше. Видно, что у нас всё хорошо. Сейчас я буду тестировать пример в **WINDOWS 10**.

![](/files/-Ljtu40YVy-8e5mFJjVx)

В **WINDOWS 10** это вещь более переменная. Иногда это хорошо, а иногда плохо.

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

![](/files/-Ljtu40m1GP5FB32OiZO)

Проблема заключается в том, что я не могу управлять выделением из кучи.Так что пока я буду использовать этот эксплойт только для **WINDOWS 7**. Следующее, что мы делаем, это мы увидим, что манипулирование помогает нам сделать версию для любой **WINDOWS**.

Хорошо. Сейчас я создал предварительный скрипт для тестирования.

![](/files/-Ljtu412tIhBQWb86DJS)

Я запускаю скрипт, выбираю в **IDA** отладчик **WINDBG** и присоединяюсь к процессу, который остановлен внутри функции **SCANF**.

![](/files/-LjrTBYLmjLq8cIc4XwL)

Поскольку я поместил **BP** по адресу **0x4010D0** перед первым вызовом функции **MALLOC**, программа будет останавливаться там когда я нажму **ENTER** в скрипте.

![](/files/-LjrTBYPtVK0ORBqyXSS)

Мы видим число, которое я ввел в регистр **EDX**. Оно равно **0x40000004**.

Инструкция **SHL** умножает число на 4. Результат будет равен 0x10, что является размером для выделения памяти.

![](/files/-Ljtu41iK_nbRqga21h5)

![](/files/-LjrTBYanQW9291FsbZr)

Давайте посмотрим, что говорит нам скрипт.

```
!HEAP -A 0x2C0000
!HEAP -A 0x510000
```

Я не буду приводить все результаты сюда, но сохраню их в файл **TXT**.

Сейчас я передаю в функцию **MALLOC** значение **0x10**.

![](/files/-LjrTBYg8GZZcpCexS0b)

Мой блок размером **0x10** байт будет располагаться по адресу **0x302FD0**.

![](/files/-LjrTBYnoD_F16J1GIt3)

Если я спрошу об этом адресе, я увижу, что он принадлежит куче **0x2C0000**.

![](/files/-Ljtu42ViRjvlC-N_B-a)

Его нет в списке для очень небольшого размера, потому что это очень маленький размер, но если я попрошу его отфильтровать блоки размером **0x10**

```
!HEAP -FLT S 0x10
```

Программа показывает мне их.

![](/files/-LjrTBZ3HBX_ODGENVVd)

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

![](/files/-LjrTBZArUEYkCkyrSr_)

Вы также можете искать по диапазону

```
!HEAP -FLT R 0x10 0x20
```

Но хорошо. Когда мы находим блоки с размером **0x10** и видим свободные, мы видим, что чуть ниже наших блоков есть ещё один свободный блок по адресу **0x302FE0** который является следующим свободным и который предположительно при выполнении функции **MALLOC** должен быть использован. Давайте пойдем в другую функции **MALLOC**.

![](/files/-LjrTBZFLK6YhKpkSL3E)

Программа выделила следующий, свободный и близкий.

![](/files/-LjrTBZOqF9HQeZZpzmD)

![](/files/-Ljtu43AX7_F6bN3s6kk)

Следующий блок размером **0x10** байт, по крайней мере в **WINDOWS 7**, достаточно предсказуем.

Более или менее у меня уже есть идея. Расстояние между ними блоками равно **0x302FE8** - **0x302FD0**

```
Python>hex\(0x302fe8 -0x302fd0\)
0x18
```

![](/files/-LjrTBZ_JGAkMfBgaG2-)

Здесь я помещаю **6** значений, которые дают мне **24** байт (**0x18**), а **7ой** будет равен **0x41424344**. Давайте посмотрим, что происходит.

![](/files/-LjrTBZjWhx-SriOhKC3)

Очевидно, это происходит, потому что **WINDOWS 7** более предсказуема.

![](/files/-LjrTBZrgdFjuzW3bLxm)

Хорошо. С этим гаджетом мы могли бы перейти, чтобы выполнить мой блок. Проблема состоит в том, что регистр **EDX** сейчас указывает на последние байта, которые существуют с тех пор как они были увеличены, что раздражает. Поэтому мы остановим его там. По крайней мере, мы показали, что мы соверщили переход и выполнили код. В следующем упражнении мы сможем обрабатывать больше различных распределений разных размеров и научимся бороться с ним как в **WINDOWS 7** так и в **WINDOWS 10**.

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

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

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

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

26.05.2018

[**Источник: ricardonarvaja.info**](http://ricardonarvaja.info/WEB/IDA%20DESDE%20CERO/CURSO%20DE%20IDA%20TUTES/46-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-46.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.
