# Часть 67

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

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

Мой друг попросил у меня несколько разъяснений о методе, используемом для обхода **COOKIE** на **32**-битных машинах, когда у нас есть переполнение стека в ядре, и мы можем перезаписать адрес возврата, но есть **COOKIE**, который мешает нам завершить выполнение кода.

Очевидно, что если у нас есть другая уязвимость, которая допускает утечку данных, мы могли бы прочитать значение **COOKIE** и затем использовать его для отправки наших данных, для перезаписи, но есть метод, который я никогда не использовал на практике, который немного стар и в системах, отличных от **WINDOWS 7 32** бит не будет работать, но было бы хорошо взглянуть на него, чтобы уточнить для моего друга и того, кто читает меня (и для меня самого).

Мы уже знаем, что уязвимый драйвер можно скачать отсюда.

<https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/releases/download/v1.20/HEVD.1.20.zip>

А сам инструмент для загрузки драйвера, можно загрузить отсюда.

<http://www.osronline.com/OsrDown.cfm/osrloaderv30.zip?name=osrloaderv30.zip&id=157>

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

Внутри **ZIP** архива находятся такие файлы.

> **HEVD.1.20\DRV\VULNERABLE\I386**

![](/files/-LjtduQ72XPIt0wCJlFA)

Это драйвер и его символы.

![](/files/-LjtduQLYeAH-ioyQ0FO)

При открытии драйвера в **IDA**, мы видим функцию **DRIVERENTRY**, первым аргументом которой всегда является указатель на объект **\_DRIVER\_OBJECT**.

![](/files/-Ljtu4v7RyIa2e6gZJrn)

Регистр **ESI** это указатель на объект **\_DRIVER\_OBJECT**.

Если мы перейдём во вкладку **LOCAL TYPES**.

![](/files/-Ljtu4vIbday_Y2uO2BM)

Мы видим, что эта структура. Если мы поищем структуру **\_IRP**, мы находим наиболее часто используемым для обращения структуры.

![](/files/-LjtduQfvFaXCylTfMm2)

Мы будем отмечать структуры **\_DRIVER\_OBJECT**, **\_IRP**, **\_DEVICE\_OBJECT**, **\_IO\_STACK\_LOCATION** и **PIRP** и синхронизировать их, потому что именно их мы будем использовать чаще всего.

То что нам нужно сделать - это добавить структуру **MAJORFUNCTION**, которой нигде нет.

Напомним, что мы можем добавить структуру через вкладку **LOCAL TYPES**, используя правую кнопку мыши, пункт **INSERT** и вставить этот код.

```cpp
struct __MajorFunction
{
    SIZE_T _MJ_CREATE;
    SIZE_T _MJ_CREATE_NAMED_PIPE;
    SIZE_T _MJ_CLOSE;
    SIZE_T _MJ_READ;
    SIZE_T _MJ_WRITE;
    SIZE_T _MJ_QUERY_INFORMATION;
    SIZE_T _MJ_SET_INFORMATION;
    SIZE_T _MJ_QUERY_EA;
    SIZE_T _MJ_SET_EA;
    SIZE_T _MJ_FLUSH_BUFFERS;
    SIZE_T _MJ_QUERY_VOLUME_INFORMATION;
    SIZE_T _MJ_SET_VOLUME_INFORMATION;
    SIZE_T _MJ_DIRECTORY_CONTROL;
    SIZE_T _MJ_FILE_SYSTEM_CONTROL;
    SIZE_T _MJ_DEVICE_CONTROL;
    SIZE_T _MJ_INTERNAL_DEVICE_CONTROL;
    SIZE_T _MJ_SCSI;
    SIZE_T _MJ_SHUTDOWN;
    SIZE_T _MJ_LOCK_CONTROL;
    SIZE_T _MJ_CLEANUP;
    SIZE_T _MJ_CREATE_MAILSLOT;
    SIZE_T _MJ_QUERY_SECURITY;
    SIZE_T _MJ_SET_SECURITY;
    SIZE_T _MJ_POWER;
    SIZE_T _MJ_SYSTEM_CONTROL;
    SIZE_T _MJ_DEVICE_CHANGE;
    SIZE_T _MJ_QUERY_QUOTA;
    SIZE_T _MJ_SET_QUOTA;
    SIZE_T _MJ_PNP;
    SIZE_T _MJ_PNP_POWER;
    SIZE_T _MJ_MAXIMUM_FUNCTION;
};
```

![](/files/-LjtduQrH0zjL3gT7z30)

Мы добавляем и синхронизируем структуру, и последнее что мы делаем – открываем во вкладке **LOCAL TYPES** структуру **\_DRIVER\_OBJECT** и изменяем ее так, чтобы последнее поле представляло собой структуру типа **\_MAJORFUNCTION**.

![](/files/-Ljtu4vqibc_2WMeqxTF)

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

![](/files/-Ljtu4w06kcI7FdmZBoS)

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

Мы видим, что регистр **ESI** сохраняет значение указателя на объект **\_DRIVER\_OBJECT** в этой области, а по адресу **0x000160СF** и это значение указателя затирается.

![](/files/-LjtduR9AVQ3NHsT4GAZ)

Таким образом, мы помечаем эту зону (это можно сделать с помощью **ALT + L**, спустится стрелкой курсора и затем снова нажать **ALT + L** для завершения) или если это небольшая область сделать это той же мышью.

![](/files/-Ljtu4wQeftQJftQw1mh)

Как только зона отмечена, мы нажимаем **T**.

![](/files/-LjtduRQ_rtUdQnkaDFz)

Конечно, мы выбираем регистр **ESI** в качестве базового регистра структуры и смещение, которое мы устанавливаем в нуль, потому что оно указывает на начало структуры, и мы выбираем структуру **\_DRIVER\_OBJECT**, и **IDA** обнаруживает **4** поля которые используются.

![](/files/-LjtduRZyJMFQoRD84by)

Для нас важно то поле, которое обрабатывает **IOCTL**, т.е. **\_MJ\_DEVICE\_CONTROL**.

Если бы у нас не было символов, такая же работа выполнялась бы путем импорта файла **.H** с **32**-битными структурами для реверсинга драйверов.

Файл **.H** с 32-битными структурами находится здесь.

<https://drive.google.com/file/d/1VXwR45uvw1FtvzW2b9eNO1DLid9CIdx8/view?usp=sharing>

И он импортируется в **IDA** отсюда.

![](/files/-Ljtu4wv_SaDmnprv1QH)

При этом мы увидим необходимые структуры **DRIVER\_OBJECT**, **\_IRP**, **\_DEVICE\_OBJECT**, **\_IO\_STACK\_LOCATION** и **PIRP** и **\_MAJORFUNCTION** в **LOCAL TYPES**. Мы синхронизируем их и получим таким же образом возможность распознавать функцию, которая обрабатывает **IOCTL**.

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

![](/files/-Ljtu4x5Nw1g0WAqFeZp)

Внутри этой функции, которая обрабатывает **IOCTL**, находятся различные уязвимые функции. В этом случае мы будем пытаться эксплуатировать функцию **STACKOVERFLOWG**.

![](/files/-Ljtu4xGMnS_3SR7fxlU)

Вызов идет сюда.

![](/files/-Ljtu4xREp5nLdmoTs38)

И затем сюда.

Мы видим, что есть функция **MEMCPY**, которая копирует буфер в стек. **MAXCOUNT** - количество байтов. Полностью отреверсив функцию мы увидим, что у неё есть **COOKIE**. Хотя мы это уже видели до адреса возврата, который в отличие от другого переполнения стека, который мы уже эксплуатировали.

![](/files/-Ljtu4xb2l-TXixuZGIH)

Перед инструкцией **RETN** есть такой вызов.

![](/files/-LjtduS4MV7rqkPVNXSo)

Эта проверка и в начале функции она то же есть.

![](/files/-LjtduSAw172vMQhykRO)

Возвращаясь к началу функции с именем **IRPDEVICEIOCTLHANDLER**, которая обрабатывает **IOCTL**, в регистре **EDI** передается указатель на структуру **IRP**. Мы уже видели в предыдущих туториалах, что в **32х** битных системах по смещению **0x60** это был указатель на структуру **IO\_STACK\_LOCATION**, которая останется в регистре **ESI**.

И я нажимаю **T** на **ESI** + **0xC**.

![](/files/-LjtduSGIcnA10n7OtRL)

Как мы уже видели, что структура **IO\_STACK\_LOCATION** варьируется в зависимости от функции, в которой она используется , поскольку здесь мы используем её в случае функции, которая обрабатывает **IOCTL**. Мы должны выбрать **DEVICEIOCONTROL**.

![](/files/-Ljtu4yU5aqZw-1RVueH)

Я оставлю всё это так. В этой функции, регистр **ESI** имеет указатель на **IO\_STACK\_LOCATION**, регистр **EDX** – это код **IOCTL** (**IOCONTROLCODE**) и **EDI** указатель на **\_IRP**.

![](/files/-LjtduSS9x_GVJP8dIt2)

Регистр **ESI** и регистр **EDI –** это два аргумента вызова функции **STACKOVERFLOWGSIOCTLHANDLER**.

![](/files/-LjtduS_X8ci9BdsTz_5)

Конечно, поскольку у нас есть символы, мы можем видеть эти два аргумента в определении функции. Первый - это указатель на **\_IRP**, а второй - указатель на **IO\_STACK\_LOCATION**.

![](/files/-LjtduSiMxAuGIinh-O_)

Опять же, пытаясь определить поле **\_IO\_STACK\_LOCATION**, мы должны выбрать случай, когда используется **DEVICEIOCONTROL**.

![](/files/-LjtduSnXwYYlQ7ydyLR)

Мы видим, что то, что мы определили при реверсинге, совпадает с тем, что нам показывают символы.

![](/files/-Ljtu4zOcndE1EteDy7V)

Поле **INPUTBUFFERLENGHT** - это размер входного буфера из режима пользователя, а **TYPE3INPUTBUFFER** - указатель на этот пользовательский входной буфер, который мы также передаем.

Переименуем эти два аргумента. Помните, что та переменная, которую мы называем **SIZE\_BUFFER\_USER**, является произвольным переданным нами числом, которое должно быть размером буфера, но это может быть любое значение, так как не видно никакой проверки.

![](/files/-LjtduSz3Vc4v-O5GZno)

Мы видим, что обе переменные используются без проверки или изменения в функции **MEMCPY**.

![](/files/-Ljtu4zlO6GaKadC2DOu)

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

![](/files/-LjtduT9vVo_IY0sXWpx)

![](/files/-Ljtu5-CXB1-UaXdBCoa)

Инициализируется только **0x1FF** байт буфера. Также нет проблем, что некоторые байты ещё остались нетронутыми.

![](/files/-Ljtu5-Rl7DXsADau9gq)

В начале функции мы видим, что выше адреса возврата есть структура **CPPEH\_RECORD**.

![](/files/-LjtduTU5HrgNzosD08H)

Она находится чуть ниже буфера и поверх адреса возврата.

![](/files/-Ljtu5-q6n-NVxTjuKIs)

Мы видим, что в стек помещаются два аргумента. Константа **0x210** и указатель на структуру. Константа **0x210**, так как это первый **PUSH**, будет чуть выше адреса возврата **R**, который я сохранил при входе в эту же функцию пролога.

![](/files/-LjtduTih27bBnyILkBf)

Однако мы видим, что **IDA** показывает нам, что чуть выше "**R**" находится сохраненный регистр **EBP** т.е. "**S**".

![](/files/-Ljtu50BUTgfeNPWRCyg)

Также, если мы войдем в функцию **\_\_SEH\_PROLOG4\_GS**.

![](/files/-Ljtu50MA11NTXexaAWa)

Мы видим, что после помещения в регистр **EAX** значения переменной **CONS\_0x210**, программа сохраняет здесь регистр **EBP**, так что на самом деле выше "**R**" , наконец, остается "**S**" или сохраненный регистр **EBP**.

Затем, над сохраненным регистром **EBP**, помещается указатель на эту структуру которая передается сразу после инструкции **PUSH 0x210**.

![](/files/-LjtduTwPZbhfapIgWIc)

По этому адресу находится структура **STRU\_0x12218**. Программа сохраняет её чуть выше "**S**".

![](/files/-Ljtu50h-3lCw39WcYu-)

И чуть выше "**S**" находится переменная **MS\_EXC**, которая является структурой типа **CPPEH\_RECORD**, так что этот адрес будет последним полем этой структуры, и мы увидим это.

![](/files/-Ljtu50rO1yxkkFaAEy4)

Здесь после сохранения сохраненного регистра **EBP** в переименую **CONST\_0X210** программа помещает адрес указанной переменной в регистр **EBP**. Это будет более или менее похоже на начало функции **PUSH EBP**, **MOV EBP**, **ESP**.

Оба должны сохранить значение регистра **EBP** родительской функции **TRIGGERSTACKOVERFLOWGS** и установить новый регистр **EBP** для неё через инструкцию **LEA**.

![](/files/-LjtduUGRtd-W50nEyLR)

Затем программа освобождает место для переменных, выполняя инструкцию **SUB ESP**, **EAX**.

И также мы видим, что по адресу **EBP-4 XOR**ится значение, которое было там с **COOKIE**, которое читается из секции данных.

Напомним, что по адресу **EBP-4** находится значение **0x12218**. С этим значением **XOR**ится **COOKIE** и сохраняется по этому адресу.

![](/files/-Ljtu51FIlXIRTE9u3sM)

Кроме того, **XOR**ится **COOKIE** с регистром **EBP** и полученное значение сохраняется по адресу **EBP-1C**.

![](/files/-Ljtu51STS2hWZmgjZnJ)

Это то, что программа будет проверять в эпилоге.

![](/files/-LjtduUaJoY_yZ7HCEsQ)

И внутри функции **\_\_SECURITY\_CHECK\_COOKIE**.

![](/files/-LjtduUioKZU3LxY5Zmi)

Программа сравнивает значения. Если они одинаковы, то всё хорошо, а если нет мне выкидывает **BSOD**.

Мы создадим стек с самого начала в соответствии с порядком, с которым программа размещает значения перед входом в пролог:

```
PUSH 0x210
PUSH 0x12218
```

Затем программа входит в пролог, который заставляет её сохранять адрес возврата в стеке, куда она будет возвращаться. Это будет адрес **0x000148E9**, поскольку при выходе из пролога программа вернется туда.

![](/files/-Ljtu521jecaTwfoF1QA)

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

![](/files/-Ljtu52AHMrqp6PKc_Re)

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

Затем есть еще две инструкции **PUSH** - адрес функции **EXCEPTION\_HANDLER4** и значение, которое содержит регистр **FS:0**.

![](/files/-LjtduV3BhCv5FLyqE_E)

Над обратным адресом тогда будут эти два значения.

![](/files/-Ljtu52RWPIUoysBdYdS)

Затем **0x210** перезаписывается **STORED\_EBP**.

![](/files/-Ljtu52cWkVyTWmqj-Qg)

Мы знаем, что под сохраненным регистром **EBP** был адрес возврата в функцию **TRIGGERSTACKOVERFLOWGS**. Мы добавили его в наше представление стека.

![](/files/-LjtduVMpiywADYqGrU-)

Текущий регистр **EBP** остается с адресом **STORE\_EBP** (смотри на адрес, а не значение)

Из регистра **ESP** вычитается значение **0x210** для пространства переменных, другими словами над адресом **FS:0** - **0x210** останется регистр **ESP**.

![](/files/-Ljtu52ywEHvR3ueXl6Y)

Затем выше есть еще три **PUSH** - **EBX**, **ESI** и **EDI**.

![](/files/-LjtduVbfe67TdOpHx50)

![](/files/-LjtduVj9IC2wQX1KPT4)

Затем содержимое **EBP-4 XOR**ится с **COOKIE**.

Поскольку **ТЕКУЩИЙ EBP** продолжает указывать на адрес **STORE\_EBP**, **EBP-4** указывает на значение **0x12218**, это значение **XOR**ится с **COOKIE**.

```
ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
…
…
…
FS:0
__EXCEPT_HANDLER4
0x148E9 <---- АДРЕС ВОЗВРАТА В ПРОЛОГ
0x12218 <--------XORится с COOKIE
STORED_EBP <----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TriggerStackOverflowGS
```

Если мы сделаем так, чтобы уточнить первый столбец, с адресами ссылающимися на значение **ТЕКУЩЕГО EBP**.

```
ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
…
…
…
EBP-10 FS:0
EBP-C __EXCEPT_HANDLER4
EBP-8 0x148E9 <---- АДРЕС ВОЗВРАТА В ПРОЛОГ
EBP-4 0x12218 <--------XORИТСЯ С COOKIE
EBP STORED_EBP <----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TRIGGERSTACKOVERFLOWGS
```

Одна из проблем здесь заключается в том, что это не нормальная функция, которая при входе и выходе из **ESP** остается такой же, как и до помещения аргументов. Ваши аргументы хорошо сбалансированы. Это функция, которая является прологом функции **TRIGGERSTACKOVERFLOWGS**. Этот код должен быть частью той же функции и не должет быть идти отдельным **CALL**.

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

В обычной функции регистр **ESP** при возврате равен тому же значения, что и перед передачей аргументов.

![](/files/-LjtduVtoRUUIWprMb-o)

Но в данном конкретном случае эта специальная функция похожа на часть функции **TRIGGERSTACKOVERFLOWGS**, выполняемой в отдельном **CALL**.

Если принимать регистр **ESP** в качестве нуля в начале функции, я вижу, что при возврате из **CALL** регистр увеличивается на **0x234** байт, потому что внутри функции пролога было сделано несколько инструкций **PUSH**, была выполнена инструкция **SUB ESP**, **0x210**, и был осуществлен возврат из функции без восстановления регистра **ESP**.

![](/files/-Ljtu53k52bBMIVHamzm)

Многие скажут, но если регистр **ESP** не восстановлен, как найти адрес возврата в стеке, который намного ниже значения регистра **ESP**.

Мы говорили, что адрес **EBP-8** указывает на адрес возврата, чтобы вернуться из функции пролога в **TRIGGERSTACKOVERFLOWGS** и **ТЕКУЩИЙ ESP** после того, как три **PUSH** из **EBX**, **ESI** и **EDI**остались выше.

![](/files/-Ljtu53sHd4sfhAFYMq2)

Если мы посмотрим в функции пролога, увидим, что она возвращает адрес возврата с помощью инструкции **PUSH** - **RET**.

![](/files/-Ljtu540CxaGgPXU1kMU)

Помещенное в стек значение, указанное через адрес **EBP-8**, является адресом возврата. Программа помещает значение обратно в стек и затем выполняет инструкцию **RET**, и программа возвращается к функции **TRIGGERSTACKOVERFLOWGS**, не восстанавливая **ESP** и оставляя весь стек целым, как это было в прологе.

Между инструкцией **PUSH** и **RET** есть только инструкции **MOV** и **LEA**, поэтому стек не затрагивается, и это аналогично **PUSH-RET**.

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

Мы создали стек здесь.

![](/files/-Ljtu54Ch4H05eR66EdV)

До этого момента он был создан так.

```
ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
…
…
…
EBP-10 FS:0
EBP-C __EXCEPT_HANDLER4
EBP-8 0x148E9 &lt;---- АДРЕС ВОЗВРАТА В ФУНКЦИЮ ПРОЛОГ
EBP-4 0x12218 &lt;--------XORED COOKIE
EBP STORED_EBP &lt;----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TRIGGERSTACKOVERFLOWGS
```

Мы уже знаем, что ничего из этого не будет потеряно, все, что я добавлю или изменю в прологе в стеке, не будет удалено, поскольку **PUSH-RET** покинет стек, как это было для функции **TRIGGERSTACKOVERFLOWGS**.

Еще одна вещь, которая уже настроена для функции **TRIGGERSTACKOVERFLOWGS**, это регистр **EBP**.

![](/files/-Ljtu54PujpLt6HAfSAe)

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

Я смотрю функцию **TRIGGERSTACKOVERFLOWGS**, чтобы попытаться увидеть, где это соответствует адресу **EBP-1C**, где программа хранит **COOKIE**.

![](/files/-Ljtu54dwWY0dMluUZcE)

Мы видим, что переменная **MS\_EXC** находится по адрес **EBP-0x18**. Другими словами место, где программа хранит **COOKIE**, которое вы собираетесь проверить, находится чуть выше структуры **MS\_EXC**.

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

![](/files/-LjtduWiNHIWiKH3Z8DI)

Здесь я назначаю новый размер и у меня остается четыре пустых байта между ними. Я нажимаю **D**, пока я не изменю на **DWORD** (**DD**), и переименую переменную в **COOKIE**.

![](/files/-Ljtu55FC_pbR_uPUPz7)

Я вижу, что это по адресу **EBP-1C** (слева от названия есть позиция относительно **EBP** т.е. **0x0000001C**).

Затем идет инструкция **PUSH EAX** и сохраняется текущее значение регистра **ESP** по адресу **EBP-18**, которое было внутри структуры **MS\_EXC**, которая начинается здесь. Это первое поле той же структуры.

![](/files/-LjtduWvrfuwzLvJhUpb)

Если мы заглянем внутрь структуры, первым полем будет **OLD ESP**

![](/files/-Ljtu55fHt5UTCQV2Msk)

Так что стек стал таким

```
ЗНАЧЕНИЕ EAX
ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
…
EBP-10 FS:0
EBP-C __EXCEPT_HANDLER4
EBP-8 0x148E9 <---- АДРЕС ВОЗВРАТА В ПРОЛОГ
EBP-4 0x12218 <--------XORится с COOKIE
EBP STORED_EBP <----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TRIGGERSTACKOVERFLOWGS
```

Поскольку теперь обе функции совместно используют стек, если мы сравним их, мы увидим, что над **STORED\_EBP** находится значение **MS\_EXC**, поэтому внутри пролога чуть выше переменной "**S**" байты также являются полями указанной структуры.

![](/files/-LjtduX9boLoJwu9N9NZ)

Эти **4 DWORDS** являются **4** нижними полями структуры **MS\_EXC**.

![](/files/-LjtduXH-duvZ_KE64JO)

Помните, что последние **4** поля структуры - это другая структура размером **0x10** байтов, т.е. **16** в десятичной системе (**4 DWORDS**), поэтому на изображении отмечены только те **4 DWORDS**.

![](/files/-LjtduXP3iSG3MTxrEYY)

Двумя важными переменными являются **NEXT** и **EXCEPTION HANDLER**. Мы уже знаем их положение в стеке. Мы видим, что переменная **NEXT** в структуре имеет значение **FS:0**, а **EXCEPTIONHANDLER** на данный момент имеет значение **\_\_EXCEPT\_HANDLER4**, хотя они еще не добавлены в цепочку **SEH**.

```
ЗНАЧЕНИЕ EAX
ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
…
…
…
EBP-10 FS:0 - (NEXT)
EBP-C __EXCEPT_HANDLER4 - (EXCEPTION_HANDLER)
EBP-8 0x148E9 <---- АДРЕС ВОЗВРАТА В ПРОЛОГ -(SCOPETABLE)
EBP-4 0x12218 <--------XORится с COOKIE (TRYLEVEL)
EBP STORED_EBP <----- ТЕКУЩИЙ EBP - АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TRIGGERSTACKOVERFLOWGS
```

Хорошо. У нас создан стек, и мы видим справа синие поля структуры.

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

![](/files/-LjtduXaL2MpinQmIzNq)

Мы видим, что в по адресу **EBP-8** (**SCOPETABLE**) программа сохраняет значение **COOKIE, XOR**ит его со значением **0x12218**, которое было в **EBP-4**, а затем в том же **EBP-4**, что является **TRYLEVEL**, сохраняет значение **0xFFFFFFFE**.

В конце программа сохраняет адрес **EBP-10 - NEXT** в регистр **FS:0** с настроенным обработчиком исключений.

Мы знаем, что регистр **FS:0** указывает на последний элемент в списке цепочки исключений, т.е на верхнюю часть всей цепочки.

Помните, что добавление нового элемента в список осуществляется с помощью этого кода

```
PUSH OFFSET HANDLER
PUSH FS:[0]
MOV FS:[0], ESP
```

Т.е. поскольку здесь выполняется следующая инструкция **MOV LARGE FS:0, EAX**

Этот регистр **EAX** является адресом стека, где находится новый **NEXT** и ниже **SEH**.

Так как регистр **EAX** является адресом **EBP-10**, здесь будет переменная **NEXT** и чуть ниже **SEH**, как мы уже говорили.

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

![](/files/-LjtduXhSeF_vUII1eH-)

Я вижу, что регистр **FS:0** указывает на верхний элемент цепочки **SEH**. В моем случае он равен **9CCEFCC0**. Если я посмотрю, здесь должны быть переменные **NEXT** и **SEH**. Переменная **NEXT** равна **0xFFFFFFFF**, потому что это последний **NEXT** в цепочке исключений.

![](/files/-Ljtu56nOlZWa0s_bU1w)

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

![](/files/-LjtduXw7Vu0dWtDIz6Q)

Если я продолжу трассировать пролог, я попадаю туда, где регистр **EAX** сохранится в регистр **FS:0**.

![](/files/-LjtduY3ApsvtKTJBbNl)

Здесь мы видим новый драйвер, добавленный в цепочку.

![](/files/-LjtduYAeYN8tmS5PNa4)

Как мы уже рассматривали, адрес **EBP-10** будет новым **NEXT**, а ниже находится **SEH**, который будет являться **\_EXCEPT\_HANDLER4**. Это то значение, которое мы должны будем переписать для эксплуатации

![](/files/-Ljtu57chJurzLbiZtLL)

Хорошо. У нас уже все хорошо расположено. Пора начинать писать эксплойт.

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

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

Метод работает, но эксплойт делаем сбой и вызывает **BSOD**. Так что нам нужно будет увидеть, где произошел сбой. Наверняка есть что-то что мы не видим.

Основное объяснение этого метода находится здесь:

<http://poppopret.blogspot.com/2011/07/windows-kernel-exploitation-basics-part_16.html>

И исходный код публичного эксплойта находится здесь:

<https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/tree/master/Exploit>

Я не собираюсь делать все это также на **PYTHON**, потому что это того не стоит, но давайте посмотрим, как автор это объясняет и исправим, то что не работает.

Прежде всего, если бы мы сделали это в **PYTHON**, у нас была бы проблема, которую можно решить, но, компилируя его в **C++**, у нас уже есть модуль, который помимо запуска и экслуатации повышения привилегий, мы можем скомпилировать его по своему вкусу, например, без **SAFESEH**, или **DEP** или **ASLR**. В опциях **VISUAL STUDIO** позволят нам выбрать то что нужно, поэтому, если кто-то загружает решение, т.е. файл **SLN** в **VISUAL STUDIO**, вам придется изменить параметры по умолчанию.

![](/files/-LjtduYReyV47T-TCO7c)

Чуть выше находится это.

![](/files/-LjtduYWQ2yXdNp8HNEA)

Хорошо. Я приложу скомпилированный файл с его символами **HACKSYSEVDEXPLOIT.EXE** и **HACKSYSEVDEXPLOIT.PDB**, чтобы его было легко увидеть в **IDA**.

Исполняемый файл скомпилирован для всех уязвимостей, которые есть у драйвера, и его выполнение в консоли в **WINDOWS 7 32** с аргументами **-G -C XXX.EXE** достаточно, так как я уже добавляю в конце этого метода выполнение калькулятора c правами **SYSTEM** после поднятия прав. В остальных вместо **XXX.EXE** придется подставить **CALC.EXE** или **CMD.EXE**

Хорошо. Мы переходим к функции, которая использует эту уязвимость. В данном случае - **STACKOVERFLOWGSTHREAD**.

![](/files/-LjtduYamrUYHKggDpMG)

После исправления некоторых проблем консоли, которые не имеют отношения, давайте проанализируем эксплойт, который мы открываем в **IDA**, и видим, что эксплойт начинается здесь:

![](/files/-Ljtu58bPGcxCtRzdtM6)

Внутри мы видим вызов функции **CREATEFILE** для получения дескриптора драйвера.

![](/files/-Ljtu58n0u357MlKCSwv)

Все это так же, как и случаи, которые мы видели в предыдущих ядрах.

Регистр **EBX** остается с дескриптором драйвера, он используется только при вызове **DEVICEIOCONTROL** ниже.

![](/files/-Ljtu5900xvkU_K2KOF4)

Хорошо. Затем идет вызов функции **CREATEFILEMAPPING**, что является пространством виртуальной памяти, которое будет связано с содержимым файла. (Функция не резервирует память, только создает объект и возвращает дескриптор)

<https://docs.microsoft.com/en-us/windows/desktop/memory/file-mapping>

![](/files/-LjtduYzHROPALqs4xbn)

Но если мы посмотрим в описание функции **CREATEFILEMAPPING**, мы увидим, что первым аргументом является дескриптором файла, но нам также говорится, что может быть передан аргумент **INVALID\_HANDLE\_VALUE**. В этом случае, программа создаст отображение файла, не связывая его с файлом, и это будет общая анонимная память.

![](/files/-LjtduZ5fAa8e_pmBHB_)

![](/files/-Ljtu59ey_m2NQZV_7MW)

Хорошо. Это тот случай, поэтому мы видим, что когда вызывается эта **API**, вы передаете значение **0xFFFFFFFF**, которое является **INVALID\_HANDLE\_VALUE**.

![](/files/-Ljtu59q0kElWJakECbq)

В исходном коде, под названием **SHARED MEMORY**, созданном здесь, мы видим, что функции передаются разрешение на выполнение, чтение и запись.

![](/files/-LjtduZMHYcl64mnYWOY)

Хорошо. Она возвращает нам дескриптор файлового отображения.

![](/files/-LjtduZTKhP0oqOtMLWA)

Затем программа вызывает функцию **MAPVIEWOFFILE**, которая отображает объект в памяти, зарезервировав необходимое для него пространство.

![](/files/-LjtduZ_jWhLOy3b0CHM)

![](/files/-Ljtu5AczajcROa_UKRg)

Хорошо. Функция возвращает адрес начала секции, созданного для отображения файлов.

Чтобы отладить эксплойт в режиме пользователе, несмотря на драйвер, я копирую сервер **IDA** - **WIN32\_REMOTE.EXE** в целевую машину и запускаю его.

![](/files/-Ljtu5AnG6VpcLP_F1NP)

Я запускаю его на сервере с правами администратора в целевой системе, а на машине, где я реверсил эксплойт, меняю отладчик на удаленный отладчик **WINDOWS**. В **PROCESS OPTIONS** я указываю **IP**-адрес и порт.

![](/files/-LjtduZxzsVzSBvMKtbb)

![](/files/-Ljtdu_5gVOHrgmW8T3n)

Напомним, что мы можем отлично отладить этот эксплойт в пользовательском режиме, но на шелл-код, который вызывается из ядра, мы не сможем поставить **BP** или что-либо еще, потому что это вызовет исключение **INT3** в ядре, которое не обрабатывается как в пользовательском режиме, и будет создан **BSOD**.

Если мы запустим файл без аргументов, он покажет нам опции.

![](/files/-Ljtdu_EIzMdFPnEeVVW)

Я запускаю эксплойт с аргументами **-G**, чтобы задействовать уязвимость **STACK OVERFLOW GS**,

![](/files/-Ljtdu_LKdvuRVhD334j)

Теперь я ожидаю.

![](/files/-Ljtu5BqqgGoo57b68IO)

Так что я могу теперь прикрепить **IDA**, ту где я реверсил эксплойт (не ту, которой был проанализирован драйвер)

![](/files/-Ljtu5C0f94QX498aCAl)

Нажав клавишу в целевой машине, чтобы пропустить паузу, мы останавливаемся на **BP**, который я ставлю после паузы.

![](/files/-Ljtu5CGN9S96-Vqp5v2)

Я дохожу до функции **CREATEFILEMAPPING**.

![](/files/-Ljtu5CTfvfCzlA-NhaB)

Пройдя вызов с помощью **F8**, мне возвращается дескриптор файла.

![](/files/-Ljtdu_zXrgeJ6Ymx57p)

Как мы уже говорили, этот дескриптор передается в регистре **ESI**.

![](/files/-Ljtu5CwlYbIOx_ioKHU)

Здесь нам вернется адрес отображаемого файла.

![0.png](/files/-Ljtdua7T9aJTUiwQiGe)

Это секция будет из **0x1000** байт.

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

Я поставлю эту **IDA** на паузу на одну минуту и открою другую, где у меня есть драйвер.

Одна вещь, которую я не видел и допустил ошибку - это то, что буфер назначения

![](/files/-Ljtu5DC7aPpAh3dVJp9)

Мы видим, что он инициализируется байтами **0x1FF**, но непосредственно перед обнулением байта, который находится чуть выше, и место назначения начинается с **VAR\_21C**, поэтому вам нужно исправить буфер назначения, чтобы иметь возможность хорошо вычислять размер, и теперь начинать с **VAR\_0x21C**, и он будет равен **0x200** байт.

![](/files/-Ljtu5DRiQZgA5g3qR_J)

Теперь, если всё в порядке, я переименую его в **BUFFER\_DESTINO**.

Это выглядит хорошо. Здесь прямо в функции **MEMCPY**, и мы можем скопировать количество байтов, которые нам нужны.

![](/files/-LjtduaNFlneG1_wo-Ii)

Очевидно, что мы не должны копировать с начала секции файлового отображения, потому что данные должны быть копированы только до **SEH**. Я должен увидеть, сколько байтов я должен скопировать.

Мы должны скопировать **0x200** байт, чтобы заполнить буфер. Еще **4**, чтобы перезаписать **COOKIE**, а затем ещё идёт структура **MS\_EXC**.

Внутри структуры есть **8** байтов, а затем **NEXT** и **SEH**, поэтому было бы так.

Всего cкопировано = **0x200 + 4 + 8 + NEXT + SEH**.

![](/files/-LjtduaSHhBpvgnY1wlt)

Т.е. **0x214** байтами мы перезаписываем **SEH**.

![](/files/-Ljtu5E0CZ1UJCEckarw)

Поскольку длина секции составляет **0x1000** байт, чтобы узнать, какое адрес передать, чтобы начать копирование, в начальный адрес секции я добавляю **0x1000**, а затем вычитаю **0x214**. При этом программа будет использовать этот новый адрес в качестве входного буфера, просто, чтобы перезаписать **SEH** и вызвать сбой при чтении.

Давайте посмотрим на это в отладчике, который был остановлен на эксплойте.

![](/files/-Ljtu5EEXl0lmAti7cnI)

Мы видим, что адрес файлового отображения, хранится в регистре **ESI** и к нему добавляется значение **0xDEC**. Т.е. **0x1000** – **0x214 = 0xDEC**.

![](/files/-LjtduadUTbr-7Vpno77)

![](/files/-Ljtu5E_h1OsdGJpJouy)

На моей машине **0x2C0DEC** будет адресом, с которого начнется копирование **0x214** байт. Источник функции **MEMCPY** в стек.

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

С помощью функции **MEMSET** заполняется вcя секция буквами **A** (**0x41**)

![](/files/-Ljtu5Eq6-D2MWdwATo6)

Здесь заполняется входной буфер.

![](/files/-Ljtu5F1g_sPJ88Wk22y)

Мы видим, что в позицию **0x0204** записывается значение **0x42424242**. Это предположительно говорит о том, что программа перезаписала **COOKIE**, так как буфер занимает **0x200**, а **COOKIE**находится ниже. Для меня, так как это значение **0x204**, перезаписывается **DWORD** чуть ниже **COOKIE** - первое поле структуры **MS\_EXC**.

Затем программа записывает по адресу **ESI** + **4** значение **0x43434343**.

![](/files/-Ljtu5FDlY65MqbsAohz)

Затем добавляет значение **8** к исходному регистру **ESI** и записывает **NEXT** и **SEH**.

![](/files/-Ljtu5FO633LVi2IKnYD)

Мы видим, что, как я уже сказал, **COOKIE** не перезаписался значением **0x41414141**, и я затер чуть ниже **4 DWORD** структуры **MS\_EXC**.

![](/files/-Ljtdubt-e6FSnMLnm4p)

Это **4 DWORD** которые я перезаписал, поэтому последний **DWORD -** это **SEH**.

![](/files/-Ljtu5Fn1derBggbX6Uw)

Чуть ниже **SEH** заканчивается секция. Поскольку мы хотим вызвать сбой при чтении мы продолжим чтение данных. Мы передадим размер немного больше, чем **0x214**.

Я устанавливаю **BP** перед функцией **DEVICEIOCONTROL**, и когда я нажимаю **RUN**, мне нужно нажать клавишу в целевой маишине, чтобы пройти следующую системную паузу.

![](/files/-LjtdubAFSPplZ0udQx9)

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

![](/files/-Ljtu5G9M7Oa2cWWiwi-)

Указатель на возвращенные байты передаются с помощью инструкции **LEA**. Затем идет **PUSH 0** и **PUSH 0** для выходного буфера. Его размер не имеет значения. Затем мы видим, что количество байтов, которые мы передает функции, чтобы она скопировала из входного буфера, равно **0x218** или на **4** байт больше, чем длина входного буфера, который был равен **0x214**. Это приведет к сбою при чтении в конце источника.

Адрес входного буфера был оставлен в регистре **EDI**.

![](/files/-Ljtu5GLjwD-9Q5E8m_V)

И затем передается код **IOCTL 0x222007** от этой ошибки **STACK OVERFLOW GS** и дескриптор устройства, который находится в регистре **EBX**.

Мы уже проанализировали эксплойт. Поэтому теперь мы можем закрыть его и присоединить **IDA** с анализом драйвера к **ЯДРУ** и посмотреть, как копируются данные.

Прежде чем прикрепить **IDA**, я поставлю **BP** в начале уязвимой функции.

![](/files/-LjtdubNgb9T0vqRYjE7)

![](/files/-Ljtu5GePJpeJcC7Htbd)

Готово. **IDA** уже обнаружила, что это тот же файл, который я проанализировал, и если я соглашусь и перемещу базу секции, **IDA** скажет, что это тот же файл.

Я запускаю эксплойт на целевой машине.

![](/files/-LjtdubUQoEemPL0bfzH)

![](/files/-Ljtu5Gy9I0OyCqRtJyo)

Мы останавливаемся на **BP**.

![](/files/-Ljtdub_BHG186c3CCHW)

Как мы уже говорили, будут скопированы **NEXT** и **SEH**, которые мы должны будем перезаписать позже в функции **MEMCPY**.

![](/files/-Ljtu5HLulodtimOkwQZ)

Потому что, когда мы нажимаем **RUN**, мы будем копировать данные и продолжим, не имея возможности остановить этот процесс. Мы можем установить **BPM ON WRITE**, чтобы остановить отладчик, когда скопируется **NEXT** прямо перед сбоем. Поэтому мы посмотрим, все ли в порядке.

![](/files/-Ljtu5HXmKRRoWqTKAUF)

Я вставил это в панель **WINDBG**.

![](/files/-LjtdubkhipWpaS_rQ5y)

Я создаю сегмент и преобразовываю его в код.

![](/files/-Ljtdubnb3v6fZFJeXI2)

Или если мне хочется посмотреть на сегмент в панели **WINDBG**, я делаю так.

![](/files/-Ljtu5I1gtFKvBPviAeh)

Напомним, что когда я armada источник мы помещаем значение **0x44444444**, чтобы перезаписать значение **NEXT**.

![](/files/-MX3mI8YnhfUsSyYDycY)

Это изображение выше было источником. Давайте посмотрим, хорошо ли оно перезаписано в стеке.

![](/files/-LjtdubwRDvp34BY4fMK)

Вот оно. Я останавливаюсь на **BPM ON WRITE** сразу после копирования **NEXT**, и теперь я скопирую **SEH**. Я нажимаю **F7**.

![](/files/-Ljtu5IXmrPADCVx2YfA)

Сюда я скопирую наш **SEH**.

Также я могу посмотреть на него в **IDA**.

![](/files/-Ljtduc1ZIqxUiCO2VKo)

Конечно, модуль эксплойта, куда будет переходить отладчик, скомпилирован без **DEP** и без **SAFE SEH**, поскольку это является частью эксплуатации. Если бы он был создан с **PYTHON**, проблем не было бы. Вам нужно было бы создать область памяти, которая позволила бы ему работать с функцией **VIRTUALALLOC**, скопировать туда код и указать адрес этой области как **SEH**.

Давайте не будем забывать, что это **PRIVILEGE ESCALATION**, поэтому у нас уже есть выполнение кода на машине, но с обычным пользователем. Идея состоит в том, чтобы масштабироваться до прав **SYSTEM**, чтобы мы могли выполнять действия на компьютере, ограниченные нашими привилегиями, но запускать **EXE** с теми же привилегией. Также, если бы это был **PYTHON**, мы должны установить **PYTHON** для запуска **.PY**.

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

![](/files/-Ljtduc40nM4RxOdUIcv)

Я уже создал сегмент, я преобразовал его в код с **C**, и я создал функцию.

![](/files/-Ljtu5J2U-OUgRhUWLrc)

Здесь я не могу поставить **BP** в отладчике в пользовательском режиме на целевой машине, но могу сделать так.

![](/files/-LjtducAzU-0qSrFWBmi)

Я помещаю эту команду в **WINDBG**. Затем я нажимаю **RUN**.

Отладчик остановился здесь. Наш обработчик исключений работает.

![](/files/-Ljtu5JOjDewwIjEfCYF)

Конечно, этот код - это шелл-код который называется как **TOKEN STEALER**, который уже был проанализирован в предыдущих туториалах.

Ошибка в исходном коде была в отмеченной области после возврата, чтобы украсть системный токен и сохранить его, чтобы повысить привилегии нашему процессу. Есть инструкция **POPAD**, которая восстанавливает регистры, сохраненные в начале с помощью инструкции **PUSHAD**, И потом не так легко вернуться из ядра исключения к режиму пользователю без прерывания, поэтому мы следовали советам, используя инструкцию **SYSEXIT**.

![](/files/-LjtducESM5Z8BBLOTHt)

В регистр **EDX** вы должны поместить регистр **EIP**, куда программа вернетесь, когда вернетесь в пользовательский режим, а в регистр **ECX** - регистр **ESP**. В моем случае, поскольку файл компилирован без **ASLR**, я помещаю в регистр **EDX** адрес чуть ниже вызова **DEVICEIOCONTROL**, а в регистр **ESP** адрес основания стека **0x12FF00**, который не совпадает со значением, которое выполнялось до вызова функции **DEVICEIOCONTROL**, но, поскольку я сохранил адрес в секции данных в регистре **ESP**, которое у меня был, когда я вернусь, я смогу восстановить правильный регистр **ESP**.

В другой **IDA** с анализом модуля эксплойта я вижу адрес, куда я вернусь.

![](/files/-LjtducH7ImCIDvwa98D)

В текущей **IDA**, в которой я отлаживаю, я вижу ту же часть кода.

![](/files/-LjtducJxb0ZjMBE1OcP)

Я могу поставить здесь **BP**.

![](/files/-LjtducLiQPOz9b03x_q)

Я удаляю предыдущие **BP**.

![](/files/-LjtducNWeXKSq_7qW6X)

И нажимаю **RUN**.

![](/files/-Ljtu5KKPivE9SnNcVPy)

Я вижу, что программа вернулась в режим пользователя с регистрами **EIP** и **ESP**, которые я установил до вызова **SYSEXIT**.

Здесь восстанавливается регистр **ESP**. Идет чтение с того места, где вы его сохранили, в секции данных **0x4212B0**.

![](/files/-LjtducRpmGxus04-a_R)

Теперь я буду выполнять калькулятор или код, который я хочу, с привилегиями **SYSTEM**. Я мог бы, например, инжектировать код в какой-нибудь процесс **SYSTEM**, и выйти.

![](/files/-Ljtu5KdZ_1m9kAfn1Fh)

Я удаляю все **BP** и нажимаю **RUN**.

![](/files/-Ljtu5KnoXx9jm0DJh23)

![](/files/-Ljtu5Kwxm3SnoTLQuEB)

Давайте посмотрим, какой пользователь является владельцем.

![](/files/-LjtducZNTmgCmZyXJxn)

Готово. Я закончил. Я могу повышать привилегии до **SYSTEM**.

Я приложил **ZIP** файл с измененным исходным кодом и скомпилированным исполняемым файлом. Помните, что если вы скомпилируете его самостоятельно, вы должны сделать это без **DEP**, без **SAFESEH** и без **ASLR**, и он будет работать только в **32**-разрядной версии **WINDOWS** 7 и отрегулировать.


---

# Agent Instructions: 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:

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

The question should be specific, self-contained, and written in natural language.
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.
