Итак, как это можно использовать в своих целях, ну например для антиотладки и запутывания логики программы. Для начала небольшая теория:
В Windows существует механизм структурной обработки исключений(Structured Exception Handling) сокращенно SEH. Он обрабатывает возникшие исключения в программе и позволяет сделать попытку исправить ошибку возникшую в ходе выполнения программы.
SEH сам по себе выглядит в форме цепочки, где каждое следующее звено указывает на предыдущее. Структура выглядит приблизительно так:
EXCEPTION_REGISTRATION struc prev dd ? handler dd ? EXCEPTION_REGISTRATION ends
Где prev - это адрес предыдущего исключения, а handler - обработчик исключения.
Чтобы прервать цепочку и дальше она не продолжалась необходимо в prev записать -1.
Адрес на конец цепочки по умолчанию находиться в регистре FS(хотя бывают рукотворные исключения), тоесть для того, чтобы посмотреть на конец цепочки можно просто сделать следующее:
assume fs:nothing mov eax, DWORD PTR fs:[0]
или если не хочется лезть напрямую к регистру, то можно воспользоваться функцией GetThreadContext, вот так:
invoke GetThreadContext, NULL, ADDR contexts mov ecx, [ecx]В ecx сообственно будет адрес обработчика исключений.
Итак, что мы можем с этим сделать, для начала мы можем добавить свое звено в цепочку ну или просто перезаписать стандартный обработчик(что в принципе не очень рекомендуется). Для чего нужны звенья и т.п. будет чуть ниже.
Добавляем:
assume fs:nothing push offset except_one push DWORD PTR fs:[0] mov DWORD PTR fs:[0], esp
Или более грубо ломимся и перезаписываем:
assume fs:nothing mov eax, DWORD PTR fs:[0] mov ecx, offset except add eax, 4h mov [eax], ecx
Итак вопрос и нахрена все это надо? Ну для начала мы можем запутать логику программы, например вызывая необходимые процедуры методом деления на ноль. Бывает крышу рвет у антивирусов и отладчиков и они не знают, что делать и как. Конечно для опытного крякера это не помеха, но поломать бошку придется, особенно если например захучить SetUnhandledExceptionFilter и сделать свой обработчик BasepCurrentTopLevelFilter. Можно сорвать стек у одной программы и например внедрить свой код при помощи исключения. Ну и главное можно сделать целую логику программы на исключениях. Например делаем деление на ноль - выполняется одна процедура, а если при расшифровке данных сломали джамп и не ввели правильный ключ, то получаем другую процедуру. Ну и небольшое колдунство которое я написал для примера, как это может работать:
.386 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\macros\macros.asm uselib kernel32, user32, masm32 .data textMSG1 db "Exception divine by ZERO!",0 ; сообщение для деления на ноль textMSG2 db "Exception ACCESS violation!",0 ; сообщение для нет доступа к памяти header db "ALL OK!",0 ; заголовок pContext equ [esp+0Ch] ; смещение где лежит структура EXCEPTION_RECORD .data? .code start: assume fs:nothing push offset except_one ; пишем адрес процедуры обрабатывающей деление на ноль push DWORD PTR fs:[0] ; создаем второе звено(первое - дефолтный обработчик) mov DWORD PTR fs:[0], esp ; заносим ее в регистр fs push offset except_two ; пишем адрес процедуры обрабатывающей доступ к памяти push DWORD PTR fs:[0] ; создаем третье звено SEH mov DWORD PTR fs:[0], esp ; заносим адрес в регистр fs xor eax, eax ; обнуляем eax div eax ; делим на eax где сейчас лежит 0 и вызываем обработчик исключения except_one fixed_one: xor eax, eax ; обнуляем eax mov [eax], eax ; пытаемся записать по адресу 0 и вызываем исключение except_two pop fs:[0] ; востанавливаем стек чтобы ничего не сломалось pop eax ; востанавливаем стек чтобы ничего не сломалось pop fs:[0] ; востанавливаем стек чтобы ничего не сломалось pop eax ; востанавливаем стек чтобы ничего не сломалось jmp prog_exit ; выход из программы fixed_two: pop fs:[0] ; востанавливаем стек чтобы ничего не сломалось pop eax ; востанавливаем стек чтобы ничего не сломалось pop fs:[0] ; востанавливаем стек чтобы ничего не сломалось pop eax ; востанавливаем стек чтобы ничего не сломалось jmp prog_exit prog_exit ; выход из программы prog_exit: invoke ExitProcess, NULL ; выход из программы except_one proc mov eax,DWORD PTR [esp+4h] ; заносим в eax адрес структуры EXCEPTION_RECORD mov eax, (EXCEPTION_RECORD PTR [eax]).ExceptionCode ; считываем код исключения cmp eax, 0C0000094h ; если код исключения не равено "STATUS_INTEGER_DIVIDE_BY_ZERO" jnz nextExcpOne ; то прыгаем на nextExcpOne invoke MessageBox, NULL, ADDR textMSG1, ADDR header,MB_OK ; иначе выводим сообщение 1 mov eax, pContext ; в eax запишем адрес на структуру pContext mov (CONTEXT PTR [eax]).regEip, offset fixed_one ; правим eip на продолжение программы после ошибки mov eax,ExceptionContinueExecution ; возвращаем флаг, что ошибку починили и необходимо продолжить выполнение ret nextExcpOne: mov eax,ExceptionContinueSearch ; этот обработчик не подошел, требуем продолжение поиска в другом обработчике Ret except_one EndP except_two proc mov eax,DWORD PTR [esp+4h] ; заносим в eax адрес структуры EXCEPTION_RECORD mov eax, (EXCEPTION_RECORD PTR [eax]).ExceptionCode ; считываем код исключения cmp eax, 0C0000005h ; если код исключения не равено "STATUS_ACCESS_VIOLATION" jnz nextExcpTwo ; то прыгаем на nextExcpTwo invoke MessageBox, NULL, ADDR textMSG2, ADDR header,MB_OK ; иначе выводим сообщение 2 mov eax, pContext в eax запишем адрес на структуру pContext mov (CONTEXT PTR [eax]).regEip, offset fixed_two ; правим eip на продолжение программы после ошибки mov eax,ExceptionContinueExecution ; возвращаем флаг, что ошибку починили и необходимо продолжить выполнение Ret nextExcpTwo: mov eax,ExceptionContinueSearch ; этот обработчик не подошел, требуем продолжение поиска в другом обработчике Ret except_two EndP end startСмысл сего колдунства следующий если программа встретила деление на ноль то срабатывает один обработчик исключения, если же нет доступа к записи в память, то второй, такую цепочку можно продолжать сколько угодно, на ходу меняя адреса обработчиков и более запутывая программу. Единственный минус, что через некоторое время можно самому запутаться.
А теперь бонус трек. Метод стырин вот от сюда. В IDA нельзя посмотреть SEH Chain как в OllyDbg и это есть прискорбно да и в стеке там обычно некоторая каша, которая не дает подсказки как OllyDbg. Можно воспользоваться Task Information Block сокращенно TIB. Для этого запускаем программу в отладчике IDA. И нажимаем CTRL+S должно получиться, что то типа такого экрана где ищем упоминания TIB:
Встаем на TIB и тыкаем 2 раза на нее и попадаем на конец цепочки SEH, нажимем "o", получается более удобоваримый вид, както так:
Если еще раз тыкнуть на получившийся адрес, то перейдем на следующее звено и там уже можно(опять же нажав кнопку "o" для удосбтва) увидеть какой адрес вызывается в случаее исключения первым. Както так:
А дальше еще раз тыкаем 2 раза и оказываемся в процедурке которую вызывает исключение:
Таким образом обойдя всю цепочку SEH можно прокоментировать и описать все исключения, что поможет в дальнейшем разобраться как работает программа. Ну вот в принципе на сегодня и все...
Комментариев нет:
Отправить комментарий