The one above it is the previous EBP (0012FF80
). The value above the prev-EBP is always the return address.
(This obviously assumes a non-FPO binary and 32bit Windows)1.
If you recall, the prologue looks like:
push ebp ; back up the previous ebp on the stack
mov ebp, esp ; set up the new frame pointer
and when a function is called, e.g.
call 0x00401000
The current EIP is pushed on the stack (used as the return address), so the stack after the prologue looks like:
[ebp+0xc] ; contains parameter 1, etc
[ebp+0x8] ; contains parameter 0
[ebp+0x4] ; contains return address
[ebp] ; contains prev-EBP
So for each %p
, printf uses the next 4 bytes starting from [ebp+0xc]
(the first %p
parameter). Eventually you hit the previous EBP value stored on the stack, which is (0012FF80
) and then the next one is the Return Address.
Note these addresses must ‘make sense’, which they clearly do here (though it may not be ‘clear’ for all).
Re Q2) The stack grows down. So when you push eax
, 4 is subtracted from esp
, then the value of eax is moved to [esp], equivalently in code:
push eax
; <=>
sub esp, 4
mov [esp], eax
- The book is Writing Secure Code, yes?
Эти функции могут быть использованы для получения информации об абонентах функции.
- Встроенная функция: void * __builtin_return_address (беззнаковый level int )
-
Эта функция возвращает адрес возврата текущей функции или одного из ее вызывающих. level аргумент число кадров для сканирования стеки вызовов. Значение
0
дает адрес возврата текущей функции, значение1
дает адрес возврата вызывающей стороны текущей функции и так далее. Ожидаемое поведение при встраивании состоит в том, что функция возвращает адрес возвращаемой функции. Чтобы обойти это поведение, используйтеnoinline
функции noinline .level аргумент должен быть постоянным целым числом.
На некоторых машинах может быть невозможно определить адрес возврата какой-либо функции, кроме текущей; в таких случаях или при достижении вершины стека эта функция возвращает неопределенное значение. Кроме того,
__builtin_frame_address
может использоваться, чтобы определить, достигнута ли вершина стека.Может потребоваться дополнительная постобработка возвращаемого значения, см.
__builtin_extract_return_addr
.Сохраненное представление адреса возврата в памяти может отличаться от адреса, возвращаемого
__builtin_return_address
. Например, в AArch64 сохраненный адрес может быть изменен с помощью подписи обратного адреса, тогда как адрес, возвращаемый__builtin_return_address
, — нет.Вызов этой функции с ненулевым аргументом может привести к непредсказуемым последствиям,в том числе к аварийному завершению работы вызывающей программы.В результате,вызовы,считающиеся небезопасными,диагностируются при помощи функции-Wframe-addressопция в действии.Такие вызовы должны выполняться только в отладочных ситуациях.
На целевых объектах, где кодовые адреса представлены как
void *
,void *addr = __builtin_extract_return_addr (__builtin_return_address (0));
дает адрес кода, по которому будет возвращаться текущая функция. Например, такой адрес может использоваться с
dladdr
или другими интерфейсами, которые работают с кодовыми адресами.
- Встроенная функция: void * (void * addr )
-
Адрес, возвращаемый
__builtin_return_address
, может потребоваться передать через эту функцию, чтобы получить фактический закодированный адрес. Например, на 31-битной платформе S / 390 старший бит должен быть замаскирован, или на платформах SPARC необходимо добавить смещение для выполнения истинной следующей инструкции.Если исправление не требуется, эта функция просто проходит через addr .
- Встроенная функция: void * __builtin_frob_return_addr (void * addr )
-
Эта функция делает обратное
__builtin_extract_return_addr
.
- Встроенная функция: void * __builtin_frame_address (беззнаковый level int )
-
Эта функция похожа на
__builtin_return_address
, но возвращает адрес фрейма функции, а не адрес возврата функции. Вызов__builtin_frame_address
со значением0
дает адрес кадра текущей функции, значение1
дает адрес кадра вызывающего текущую функцию и так далее.Фрейм — это область в стеке, в которой хранятся локальные переменные и сохраненные регистры. Адрес кадра обычно является адресом первого слова, помещенного функцией в стек. Однако точное определение зависит от процессора и соглашения о вызовах. Если процессор имеет специальный регистр указателя кадра, а функция имеет кадр, то
__builtin_frame_address
возвращает значение регистра указателя кадра.На некоторых машинах может быть невозможно определить адрес кадра какой-либо функции, кроме текущей; в таких случаях или при достижении вершины стека эта функция возвращает
0
, если указатель первого кадра правильно инициализирован кодом запуска.Вызов этой функции с ненулевым аргументом может привести к непредсказуемым последствиям,в том числе к аварийному завершению работы вызывающей программы.В результате,вызовы,считающиеся небезопасными,диагностируются при помощи функции-Wframe-addressопция в действии.Такие вызовы должны выполняться только в отладочных ситуациях.
Далее: Векторные расширения , Предыдущий: Имена функций , Вверх: Расширения C [ Содержание ][ Индекс ]
GCC
12.2
-
6.60.13.4 Необработанные функции чтения/записи
В этом разделе описаны встроенные функции,связанные с доступом к памяти инструкций чтения и записи.
-
7.2 Ограничение псевдослучайности указателя
Как и в случае с C,G++понимает особенность C99-ограниченные указатели,указанные __restrict__,или квалификатор типа.
-
6.60.28 Встроенные функции RISC-V
Эти встроенные функции доступны для процессоров семейства RISC-V.
-
6.33.27 Атрибуты функций RISC-V
Эти атрибуты функций поддерживаются бэкэндом RISC-V:Этот атрибут позволяет компилятору построить требуемое объявление функции,при этом позволяя
Введение в реверсинг с нуля, используя IDA PRO. Часть 67. Часть 1.
| Редактировалось 4 июл 2019
Метод, который мы не рассмотрели, чтобы эксплуатировать одну из уязвимостей драйвера 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.20DRVVULNERABLEI386
Это драйвер и его символы.
При открытии драйвера в IDA, мы видим функцию DRIVERENTRY, первым аргументом которой всегда является указатель на объект _DRIVER_OBJECT.
Регистр ESI это указатель на объект _DRIVER_OBJECT.
Если мы перейдём во вкладку LOCAL TYPES.
Мы видим, что эта структура. Если мы поищем структуру _IRP, мы находим наиболее часто используемым для обращения структуры.
Мы будем отмечать структуры _DRIVER_OBJECT, _IRP, _DEVICE_OBJECT, _IO_STACK_LOCATION и PIRP и синхронизировать их, потому что именно их мы будем использовать чаще всего.
То что нам нужно сделать — это добавить структуру MAJORFUNCTION, которой нигде нет.
Напомним, что мы можем добавить структуру через вкладку LOCAL TYPES, используя правую кнопку мыши, пункт INSERT и вставить этот код.
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;
};
Мы добавляем и синхронизируем структуру, и последнее что мы делаем – открываем во вкладке LOCAL TYPES структуру _DRIVER_OBJECT и изменяем ее так, чтобы последнее поле представляло собой структуру типа _MAJORFUNCTION.
Мы видим, что изначально это массив указателей на функции, но если мы изменим его на структуру с указателями на известные функции с их именами, это будет проще и будет работать так же, и это даст нам информацию, которая нам необходима в более четкой форме.
Таким образом, вместо того, чтобы был массивом указателей на функции, про которые мы ничего не знаем, что делает каждая из них, это будет структура той же длины, что и массив, но с указателями на функции, уже известные в соответствии со спецификацией.
Мы видим, что регистр ESI сохраняет значение указателя на объект _DRIVER_OBJECT в этой области, а по адресу 0x000160СF и это значение указателя затирается.
Таким образом, мы помечаем эту зону (это можно сделать с помощью ALT + L, спустится стрелкой курсора и затем снова нажать ALT + L для завершения) или если это небольшая область сделать это той же мышью.
Как только зона отмечена, мы нажимаем T.
Конечно, мы выбираем регистр ESI в качестве базового регистра структуры и смещение, которое мы устанавливаем в нуль, потому что оно указывает на начало структуры, и мы выбираем структуру _DRIVER_OBJECT, и IDA обнаруживает 4 поля которые используются.
Для нас важно то поле, которое обрабатывает IOCTL, т.е. _MJ_DEVICE_CONTROL.
Если бы у нас не было символов, такая же работа выполнялась бы путем импорта файла .H с 32-битными структурами для реверсинга драйверов.
Файл .H с 32-битными структурами находится здесь.
https://drive.google.com/file/d/1VXwR45uvw1FtvzW2b9eNO1DLid9CIdx8/view?usp=sharing
И он импортируется в IDA отсюда.
При этом мы увидим необходимые структуры DRIVER_OBJECT, _IRP, _DEVICE_OBJECT, _IO_STACK_LOCATION и PIRP и _MAJORFUNCTION в LOCAL TYPES. Мы синхронизируем их и получим таким же образом возможность распознавать функцию, которая обрабатывает IOCTL.
В этом случае, наличие символов для этой функции уже имело имя, что дало нам представление о том, что это искомая функция, но по мере того, как мы учимся, полезно знать это всё, чтобы найти ее для всех случаев реверсинга, либо с символами, либо без символов.
Внутри этой функции, которая обрабатывает IOCTL, находятся различные уязвимые функции. В этом случае мы будем пытаться эксплуатировать функцию STACKOVERFLOWG.
Вызов идет сюда.
И затем сюда.
Мы видим, что есть функция MEMCPY, которая копирует буфер в стек. MAXCOUNT — количество байтов. Полностью отреверсив функцию мы увидим, что у неё есть COOKIE. Хотя мы это уже видели до адреса возврата, который в отличие от другого переполнения стека, который мы уже эксплуатировали.
Перед инструкцией RETN есть такой вызов.
Эта проверка и в начале функции она то же есть.
Возвращаясь к началу функции с именем IRPDEVICEIOCTLHANDLER, которая обрабатывает IOCTL, в регистре EDI передается указатель на структуру IRP. Мы уже видели в предыдущих туториалах, что в 32х битных системах по смещению 0x60 это был указатель на структуру IO_STACK_LOCATION, которая останется в регистре ESI.
И я нажимаю T на ESI + 0xC.
Как мы уже видели, что структура IO_STACK_LOCATION варьируется в зависимости от функции, в которой она используется , поскольку здесь мы используем её в случае функции, которая обрабатывает IOCTL. Мы должны выбрать DEVICEIOCONTROL.
Я оставлю всё это так. В этой функции, регистр ESI имеет указатель на IO_STACK_LOCATION, регистр EDX – это код IOCTL (IOCONTROLCODE) и EDI указатель на _IRP.
Регистр ESI и регистр EDI – это два аргумента вызова функции STACKOVERFLOWGSIOCTLHANDLER.
Конечно, поскольку у нас есть символы, мы можем видеть эти два аргумента в определении функции. Первый — это указатель на _IRP, а второй — указатель на IO_STACK_LOCATION.
Опять же, пытаясь определить поле _IO_STACK_LOCATION, мы должны выбрать случай, когда используется DEVICEIOCONTROL.
Мы видим, что то, что мы определили при реверсинге, совпадает с тем, что нам показывают символы.
Поле INPUTBUFFERLENGHT — это размер входного буфера из режима пользователя, а TYPE3INPUTBUFFER — указатель на этот пользовательский входной буфер, который мы также передаем.
Переименуем эти два аргумента. Помните, что та переменная, которую мы называем SIZE_BUFFER_USER, является произвольным переданным нами числом, которое должно быть размером буфера, но это может быть любое значение, так как не видно никакой проверки.
Мы видим, что обе переменные используются без проверки или изменения в функции MEMCPY.
Назначением функции MEMCPY является буфер в стеке, который мы можем перезаписать. Проблема в том, что здесь функция не помогает нам перезаписать весь стек, пока он не закончится, потому что в этом случае SEH не вызывается так, как в пользовательском режиме, и создается BSOD, поэтому необходимо использовать другую технику.
Инициализируется только 0x1FF байт буфера. Также нет проблем, что некоторые байты ещё остались нетронутыми.
В начале функции мы видим, что выше адреса возврата есть структура CPPEH_RECORD.
Она находится чуть ниже буфера и поверх адреса возврата.
Мы видим, что в стек помещаются два аргумента. Константа 0x210 и указатель на структуру. Константа 0x210, так как это первый PUSH, будет чуть выше адреса возврата R, который я сохранил при входе в эту же функцию пролога.
Однако мы видим, что IDA показывает нам, что чуть выше «R» находится сохраненный регистр EBP т.е. «S«.
Также, если мы войдем в функцию __SEH_PROLOG4_GS.
Мы видим, что после помещения в регистр EAX значения переменной CONS_0x210, программа сохраняет здесь регистр EBP, так что на самом деле выше «R» , наконец, остается «S» или сохраненный регистр EBP.
Затем, над сохраненным регистром EBP, помещается указатель на эту структуру которая передается сразу после инструкции PUSH 0x210.
По этому адресу находится структура STRU_0x12218. Программа сохраняет её чуть выше «S«.
И чуть выше “S” находится переменная MS_EXC, которая является структурой типа CPPEH_RECORD, так что этот адрес будет последним полем этой структуры, и мы увидим это.
Здесь после сохранения сохраненного регистра EBP в переименую CONST_0X210 программа помещает адрес указанной переменной в регистр EBP. Это будет более или менее похоже на начало функции PUSH EBP, MOV EBP, ESP.
Оба должны сохранить значение регистра EBP родительской функции TRIGGERSTACKOVERFLOWGS и установить новый регистр EBP для неё через инструкцию LEA.
Затем программа освобождает место для переменных, выполняя инструкцию SUB ESP, EAX.
И также мы видим, что по адресу EBP-4 XORится значение, которое было там с COOKIE, которое читается из секции данных.Напомним, что по адресу EBP-4 находится значение 0x12218. С этим значением XORится COOKIE и сохраняется по этому адресу.
Кроме того, XORится COOKIE с регистром EBP и полученное значение сохраняется по адресу EBP-1C.
Это то, что программа будет проверять в эпилоге.
И внутри функции __SECURITY_CHECK_COOKIE.
Программа сравнивает значения. Если они одинаковы, то всё хорошо, а если нет мне выкидывает BSOD.
Мы создадим стек с самого начала в соответствии с порядком, с которым программа размещает значения перед входом в пролог:
PUSH 0x210
PUSH 0x12218
Затем программа входит в пролог, который заставляет её сохранять адрес возврата в стеке, куда она будет возвращаться. Это будет адрес 0x000148E9, поскольку при выходе из пролога программа вернется туда.
Таким образом, при входе в пролог мы имеем в стеке два аргумента и адрес возврата, куда вернется программа.
Давайте продолжим смотреть на то, как аргументы помещаются в стек.
Затем есть еще две инструкции PUSH — адрес функции EXCEPTION_HANDLER4 и значение, которое содержит регистр FS:0.
Над обратным адресом тогда будут эти два значения.
Затем 0x210 перезаписывается STORED_EBP.
Мы знаем, что под сохраненным регистром EBP был адрес возврата в функцию TRIGGERSTACKOVERFLOWGS. Мы добавили его в наше представление стека.
Текущий регистр EBP остается с адресом STORE_EBP (смотри на адрес, а не значение)
Из регистра ESP вычитается значение 0x210 для пространства переменных, другими словами над адресом FS:0 — 0x210 останется регистр ESP.
Затем выше есть еще три PUSH — EBX, ESI и EDI.
Затем содержимое 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 при возврате равен тому же значения, что и перед передачей аргументов.
Но в данном конкретном случае эта специальная функция похожа на часть функции TRIGGERSTACKOVERFLOWGS, выполняемой в отдельном CALL.
Если принимать регистр ESP в качестве нуля в начале функции, я вижу, что при возврате из CALL регистр увеличивается на 0x234 байт, потому что внутри функции пролога было сделано несколько инструкций PUSH, была выполнена инструкция SUB ESP, 0x210, и был осуществлен возврат из функции без восстановления регистра ESP.
Многие скажут, но если регистр ESP не восстановлен, как найти адрес возврата в стеке, который намного ниже значения регистра ESP.
Мы говорили, что адрес EBP-8 указывает на адрес возврата, чтобы вернуться из функции пролога в TRIGGERSTACKOVERFLOWGS и ТЕКУЩИЙ ESP после того, как три PUSH из EBX, ESI и EDI остались выше.
Если мы посмотрим в функции пролога, увидим, что она возвращает адрес возврата с помощью инструкции PUSH — RET.
Помещенное в стек значение, указанное через адрес EBP-8, является адресом возврата. Программа помещает значение обратно в стек и затем выполняет инструкцию RET, и программа возвращается к функции TRIGGERSTACKOVERFLOWGS, не восстанавливая ESP и оставляя весь стек целым, как это было в прологе.
Между инструкцией PUSH и RET есть только инструкции MOV и LEA, поэтому стек не затрагивается, и это аналогично PUSH—RET.
Мы уже знаем, как функция начинается, как все устроено в стеке и как функция возвращается, у нас есть некоторые вещи которые находятся в середине после трех PUSH перед возвратом.
Мы создали стек здесь.
До этого момента он был создан так.
ЗНАЧЕНИЕ EBX
ЗНАЧЕНИЕ ESI
ЗНАЧЕНИЕ EDI
…
…
…
EBP-10 FS:0
EBP-C __EXCEPT_HANDLER4
EBP-8 0x148E9 <—- АДРЕС ВОЗВРАТА В ФУНКЦИЮ ПРОЛОГ
EBP-4 0x12218 <———XORED COOKIE
EBP STORED_EBP <—— ТЕКУЩИЙ EBP — АДРЕС STORED_EBP
АДРЕС ВОЗВРАТА В ФУНКЦИЮ TRIGGERSTACKOVERFLOWGS
Мы уже знаем, что ничего из этого не будет потеряно, все, что я добавлю или изменю в прологе в стеке, не будет удалено, поскольку PUSH-RET покинет стек, как это было для функции TRIGGERSTACKOVERFLOWGS.Еще одна вещь, которая уже настроена для функции TRIGGERSTACKOVERFLOWGS, это регистр EBP.
С помощью LEA вычисляется база для переменных и аргументов не только пролога, но и функции TRIGGERSTACKOVERFLOWGS, поскольку начиная с этого момента её значение остается постоянным, даже после возвращения.
Я смотрю функцию TRIGGERSTACKOVERFLOWGS, чтобы попытаться увидеть, где это соответствует адресу EBP-1C, где программа хранит COOKIE.
Мы видим, что переменная MS_EXC находится по адрес EBP-0x18. Другими словами место, где программа хранит COOKIE, которое вы собираетесь проверить, находится чуть выше структуры MS_EXC.
Напомним, что буфер DST был инициализирован только с 0x1FF байтами, и мы сказали, что осталось несколько байтов чуть ниже него, поэтому, если мы поправим размер DST на 0x1FF, у нас будет переменная, в которой сохраняется COOKIE в стеке.
Здесь я назначаю новый размер и у меня остается четыре пустых байта между ними. Я нажимаю D, пока я не изменю на DWORD (DD), и переименую переменную в COOKIE.
Я вижу, что это по адресу EBP-1C (слева от названия есть позиция относительно EBP т.е. 0x0000001C).
Затем идет инструкция PUSH EAX и сохраняется текущее значение регистра ESP по адресу EBP-18, которое было внутри структуры MS_EXC, которая начинается здесь. Это первое поле той же структуры.
Если мы заглянем внутрь структуры, первым полем будет OLD ESP
Так что стек стал таким
ЗНАЧЕНИЕ 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» байты также являются полями указанной структуры.
Эти 4 DWORDS являются 4 нижними полями структуры MS_EXC.
Помните, что последние 4 поля структуры — это другая структура размером 0x10 байтов, т.е. 16 в десятичной системе (4 DWORDS), поэтому на изображении отмечены только те 4 DWORDS.
Двумя важными переменными являются NEXT и EXCEPTION HANDLER. Мы уже знаем их положение в стеке. Мы видим, что переменная NEXT в структуре имеет значение FS:0, а EXCEPTION HANDLER на данный момент имеет значение __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
Хорошо. У нас создан стек, и мы видим справа синие поля структуры.Поскольку адрес возврата уже помещен в стек, изменение значения сохраненной переменной не имеет значения.
Мы видим, что в по адресу 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 для достижения уязвимой функции (Позже мы увидим, как это сделать. Сейчас же это просто для проверки).
Я вижу, что регистр FS:0 указывает на верхний элемент цепочки SEH. В моем случае он равен 9CCEFCC0. Если я посмотрю, здесь должны быть переменные NEXT и SEH. Переменная NEXT равна 0xFFFFFFFF, потому что это последний NEXT в цепочке исключений.
Функция является типичным универсальным обработчиком. Если я собираюсь увидеть, что это за байты, то нажимаю C чтобы создать функцию.
Если я продолжу трассировать пролог, я попадаю туда, где регистр EAX сохранится в регистр FS:0.
Здесь мы видим новый драйвер, добавленный в цепочку.
Как мы уже рассматривали, адрес EBP-10 будет новым NEXT, а ниже находится SEH, который будет являться _EXCEPT_HANDLER4. Это то значение, которое мы должны будем переписать для эксплуатации
Хорошо. У нас уже все хорошо расположено. Пора начинать писать эксплойт.
Метод заключается в том, что, когда мы копируем из пользовательского буфера, который является источником, и мы предоставляем, вместо того, чтобы сломать стек, заполняя его полностью, мы должны вычислить, какой источник копирует 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, вам придется изменить параметры по умолчанию.
Чуть выше находится это.
Хорошо. Я приложу скомпилированный файл с его символами HACKSYSEVDEXPLOIT.EXE и HACKSYSEVDEXPLOIT.PDB, чтобы его было легко увидеть в IDA.
Исполняемый файл скомпилирован для всех уязвимостей, которые есть у драйвера, и его выполнение в консоли в WINDOWS 7 32 с аргументами -G -C XXX.EXE достаточно, так как я уже добавляю в конце этого метода выполнение калькулятора c правами SYSTEM после поднятия прав. В остальных вместо XXX.EXE придется подставить CALC.EXE или CMD.EXE
Хорошо. Мы переходим к функции, которая использует эту уязвимость. В данном случае — STACKOVERFLOWGSTHREAD.
После исправления некоторых проблем консоли, которые не имеют отношения, давайте проанализируем эксплойт, который мы открываем в IDA, и видим, что эксплойт начинается здесь:
Внутри мы видим вызов функции CREATEFILE для получения дескриптора драйвера.
Все это так же, как и случаи, которые мы видели в предыдущих ядрах.
Регистр EBX остается с дескриптором драйвера, он используется только при вызове DEVICEIOCONTROL ниже.
Хорошо. Затем идет вызов функции CREATEFILEMAPPING, что является пространством виртуальной памяти, которое будет связано с содержимым файла. (Функция не резервирует память, только создает объект и возвращает дескриптор)
https://docs.microsoft.com/en-us/windows/desktop/memory/file-mapping
Но если мы посмотрим в описание функции CREATEFILEMAPPING, мы увидим, что первым аргументом является дескриптором файла, но нам также говорится, что может быть передан аргумент INVALID_HANDLE_VALUE. В этом случае, программа создаст отображение файла, не связывая его с файлом, и это будет общая анонимная память.
Хорошо. Это тот случай, поэтому мы видим, что когда вызывается эта API, вы передаете значение 0xFFFFFFFF, которое является INVALID_HANDLE_VALUE.
В исходном коде, под названием SHARED MEMORY, созданном здесь, мы видим, что функции передаются разрешение на выполнение, чтение и запись.
Хорошо. Она возвращает нам дескриптор файлового отображения.
Затем программа вызывает функцию MAPVIEWOFFILE, которая отображает объект в памяти, зарезервировав необходимое для него пространство.
Хорошо. Функция возвращает адрес начала секции, созданного для отображения файлов.
Чтобы отладить эксплойт в режиме пользователе, несмотря на драйвер, я копирую сервер IDA — WIN32_REMOTE.EXE в целевую машину и запускаю его.
Я запускаю его на сервере с правами администратора в целевой системе, а на машине, где я реверсил эксплойт, меняю отладчик на удаленный отладчик WINDOWS. В PROCESS OPTIONS я указываю IP-адрес и порт.
Напомним, что мы можем отлично отладить этот эксплойт в пользовательском режиме, но на шелл-код, который вызывается из ядра, мы не сможем поставить BP или что-либо еще, потому что это вызовет исключение INT3 в ядре, которое не обрабатывается как в пользовательском режиме, и будет создан BSOD.
Если мы запустим файл без аргументов, он покажет нам опции.
Я запускаю эксплойт с аргументами -G, чтобы задействовать уязвимость STACK OVERFLOW GS,
Теперь я ожидаю.
Так что я могу теперь прикрепить IDA, ту где я реверсил эксплойт (не ту, которой был проанализирован драйвер)
Нажав клавишу в целевой машине, чтобы пропустить паузу, мы останавливаемся на BP, который я ставлю после паузы.
Я дохожу до функции CREATEFILEMAPPING.
Пройдя вызов с помощью F8, мне возвращается дескриптор файла.
Как мы уже говорили, этот дескриптор передается в регистре ESI.
Здесь нам вернется адрес отображаемого файла.
Это секция будет из 0x1000 байт.
yashechka
Ростовский фанат Нарвахи
- Регистрация:
- 2 янв 2012
- Публикаций:
- 90
Есть такой код на MASM32:
.386
.model flat, stdcall
option casemap:none
includelib masm32libkernel32.lib
ExitProcess proto: DWORD
.code
start:
push DWORD PTR 2
push DWORD PTR 3
call AddDigs
mov ecx, [esp - 4];
invoke ExitProcess, 0
AddDigs proc
mov eax, [esp + 8]
add eax, [esp + 4]
ret 8
AddDigs endp
end start
Я хочу разобраться, что происходит со стеком при вызове этой процедуры и хочу получить адрес возврата, который хранился в стеке.
Сначала мы кладем на стек 2, регистр ESP явно указывает на это число. Затем кладем туда число 3, оно ложится на вершину, и теперь двойка находится по адресу [esp — 4]. После выполнения инструкции call на стек помещается адрес возврата 0040100F, а числа 2 и 3 находятся теперь по адресам [esp — 8] и [esp — 4] соответственно. Будем считать, что в процедуре вместо ret 8 написано ret, т.е. мы просто снимаем со стека адрес возврата и переходим в отладчике на следующую строчку после call, а мусор остается в стеке. Как в этом месте получить значение адреса возврата?
Если сразу после вызова call выполнить инструкцию mov ecx, [esp — 4], то в регистре ecx окажется число 2, а инструкция mov ecx, [esp — 8] помещает туда число 3. Почему так? Ведь двойка находится дальше от вершины стека, чем тройка, и поэтому чтобы добраться до нее, надо отнять от вершины стека не 4, а 8.
Когда мы снимаем со стека адрес возврата, регистр esp увеличивается на 4, потому что стек растет в сторону уменьшения адресов. Значит, чтобы добраться до адреса возврата, который только что лежал на вершине, от адреса вершины надо отнять 4. Почему же вместо адреса возврата оттуда вытаскиваются числа?
На второй картинке программа, где в процедуре вместо ret 8 написано просто ret. В окне стека видно, как в сторону увеличения адресов лежат последовательно числа 3 и 2. То есть адрес возврата находится выше и не виден в этом окне? Кажется, получить его обратно все-таки удалось, но все равно расскажите побольше теории обо всем этом.
0 / 0 / 0 Регистрация: 24.10.2013 Сообщений: 89 |
|
1 |
|
Переполнение буфера, адрес возврата23.03.2015, 18:39. Показов 8978. Ответов 26
Подскажите пожалуйста, где можно найти адрес возврата при переполнении буфера. Visual Studio 2012 windows 8.1
0 |
Programming Эксперт 94731 / 64177 / 26122 Регистрация: 12.04.2006 Сообщений: 116,782 |
23.03.2015, 18:39 |
Ответы с готовыми решениями: Переполнение буфера возникло переполнение… Переполнение буфера Переполнение буфера Переполнение буфера 26 |
Ушел с форума 16458 / 7422 / 1186 Регистрация: 02.05.2013 Сообщений: 11,617 Записей в блоге: 1 |
|
23.03.2015, 18:51 |
2 |
Нужно, чтобы буфер был переполнен настолько, что перезаписал то
1 |
0 / 0 / 0 Регистрация: 24.10.2013 Сообщений: 89 |
|
23.03.2015, 19:21 [ТС] |
3 |
у меня и просходит аварийное завершение, я просто думал может в исходниках какого нибуть kernel32.dll или самого exe найти его
0 |
Ушел с форума 16458 / 7422 / 1186 Регистрация: 02.05.2013 Сообщений: 11,617 Записей в блоге: 1 |
|
23.03.2015, 21:36 |
4 |
Можно и самому найти. Регистр RSP/ESP указывает на верхушку стека,
1 |
0 / 0 / 0 Регистрация: 24.10.2013 Сообщений: 89 |
|
24.03.2015, 15:07 [ТС] |
5 |
А как такое можно объяснить?) Если добавить еще один А то будет аварийное завершение…
0 |
Ушел с форума 16458 / 7422 / 1186 Регистрация: 02.05.2013 Сообщений: 11,617 Записей в блоге: 1 |
|
24.03.2015, 15:28 |
6 |
А что удивляет ?
если Такое да, можно эксплуатировать. В других случаях под большим вопросом.
1 |
0 / 0 / 0 Регистрация: 24.10.2013 Сообщений: 89 |
|
24.03.2015, 15:55 [ТС] |
7 |
так, то есть если я правильно понял это то самое количество А, когда можно добавлять адрес возврата, это адрес по которому находится sub esp 44h? то есть strcpy(str,»x33x18x41x00″);?
0 |
Ушел с форума 16458 / 7422 / 1186 Регистрация: 02.05.2013 Сообщений: 11,617 Записей в блоге: 1 |
|
24.03.2015, 16:36 |
8 |
Решение Вы не туда смотрите. Адрес, по которому лежит инструкция «sub esp, 44h»,
1 |
Модератор 8806 / 6589 / 894 Регистрация: 14.02.2011 Сообщений: 23,165 |
|
24.03.2015, 16:48 |
9 |
Вы не туда смотрите. Адрес, по которому лежит инструкция «sub esp, 44h», я так понял что ему нужно так переполнить стек, чтобы отправить выполнение по своей ветке
0 |
Ушел с форума 16458 / 7422 / 1186 Регистрация: 02.05.2013 Сообщений: 11,617 Записей в блоге: 1 |
|
24.03.2015, 17:12 |
10 |
И какой же это взлом ? А знать, как устроено переполнение буфера и его эксплуатация, полезно.
0 |
Модератор 8806 / 6589 / 894 Регистрация: 14.02.2011 Сообщений: 23,165 |
|
24.03.2015, 17:20 |
11 |
И какой же это взлом ? классический, «атака через переполнения буфера»
А знать, как устроено переполнение буфера и его эксплуатация, полезно. так это не ко мне а к правилам
находит адрес возврата в стеке и во первых находит а не втупую забивает буфер своим адресом
0 |
0 / 0 / 0 Регистрация: 24.10.2013 Сообщений: 89 |
|
29.03.2015, 03:58 [ТС] |
12 |
Нашел, оказалось вот в чем была проблема:почему то открыв kernel32.dll через ida я там не смог найти call esp, а открыв его через ollydbg(посмотрев связанные с этим exe длл) смог, типо потому что диначисекая библиотека? Добавлено через 1 минуту Добавлено через 1 минуту
классический, «атака через переполнения буфера» так это не ко мне а к правилам Думаю вы приувиличиваете, это у нас лаба такая на 3 курсе=)
0 |
Неэпический 17813 / 10585 / 2043 Регистрация: 27.09.2012 Сообщений: 26,625 Записей в блоге: 1 |
|
29.03.2015, 11:56 |
13 |
Почитайте книгу Эриксона «Хакинг — искусство эксплойта.». В ней всё подробно расписано. Правда там всё заточено под Ubuntu, но я когда читал сидел на винде. Будет разница в адресах, в стратегиях распределения памяти операционной системой, в устройстве стека и кучи и т.д. На первых порах это мешает, но зато думаешь самостоятельно и быстро привыкаешь.
0 |
Ушел с форума 16458 / 7422 / 1186 Регистрация: 02.05.2013 Сообщений: 11,617 Записей в блоге: 1 |
|
29.03.2015, 11:57 |
14 |
открыв kernel32.dll через ida я там не смог найти call esp call esp — бессмысленная инструкция.
0 |
0 / 0 / 0 Регистрация: 24.10.2013 Сообщений: 89 |
|
29.03.2015, 12:00 [ТС] |
15 |
Ну не знаю) я в общеи в ntldll нашел jmp esp, вот с ним все норм, затираем main а дльше jmp esp и шел код, все работает
0 |
Ушел с форума 16458 / 7422 / 1186 Регистрация: 02.05.2013 Сообщений: 11,617 Записей в блоге: 1 |
|
29.03.2015, 12:04 |
16 |
в общеи в ntldll нашел jmp esp Где ? В какой функции (и версию ntdll, пожалуйста) ?
0 |
0 / 0 / 0 Регистрация: 24.10.2013 Сообщений: 89 |
|
29.03.2015, 12:21 [ТС] |
17 |
Где ? В какой функции (и версию ntdll, пожалуйста) ? Еще, после исполнения шел кода происходит аварийное завершение программы, еще приходтся переодически менять адрес jmp esp и адреса в shellcode и кстати у друга не получается так сделать типо нет доступа при вызове.
0 |
Убежденный Ушел с форума 16458 / 7422 / 1186 Регистрация: 02.05.2013 Сообщений: 11,617 Записей в блоге: 1 |
||||
29.03.2015, 12:29 |
18 |
|||
Я вам продемонстрирую сейчас всю бессмысленность затеи с jmp esp:
Что такое 0xC0000005, думаю, объяснять не надо ?
1 |
rjrf 0 / 0 / 0 Регистрация: 24.10.2013 Сообщений: 89 |
||||
29.03.2015, 13:00 [ТС] |
19 |
|||
Я вам продемонстрирую сейчас всю бессмысленность затеи с jmp esp:
Вложение 507273 Что такое 0xC0000005, думаю, объяснять не надо ? Да, при отладке после шел кода как раз таки эта ошибка вылезает… спрпить с вами не могу, но могу предположить что это как то связано с тем что vs юзает ntldll Добавлено через 16 минут
Я вам продемонстрирую сейчас всю бессмысленность затеи с jmp esp: https://vk.com/im?act=browse_images&id=107465
0 |
Ушел с форума 16458 / 7422 / 1186 Регистрация: 02.05.2013 Сообщений: 11,617 Записей в блоге: 1 |
|
29.03.2015, 13:02 |
20 |
И зачем скрины ? Лучше опишите в деталях, как ваш эксплойт/шелл-код работает
0 |