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

# Часть 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 и отрегулировать.
