SEH и PAGE_NOACCESS

Тема в разделе "WASM.ASSEMBLER", создана пользователем MoonShiner, 7 ноя 2004.

  1. MoonShiner

    MoonShiner New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    44
    Добрый день. Помогите, пожалуйста, разрешить следующую проблему:

    Сейчас по нуждам работы пишу пакер PE-файлов. Возникла потребность реализовать нечто вроде CopyMem-а в армадилле. Поступаю следующим образом:

    1) Перед передачей управления на OEP проставляю всей секции кода PAGE_NOACCESS и регистрирую свой обработчик через
    Код (Text):
    1.  
    2. push offset MyHandler
    3. push dword ptr fs:[0000h]
    4. mov dword ptr fs:[0000h],esp
    5.  


    Далее передаю управление на OEP (А там PAGE_NOACCESS). Вылетает исключение и я попадаю в свой обработчик. Там определяю страницу, на которой произошло исключение (страницу определяю через EIP, на котором оно произошло), ставлю ей атрибут 00000040h и расшифровываю ее в нужное место. Затем делаю
    Код (Text):
    1.  
    2. mov eax, ExceptionContinueExecution
    3. ret
    4.  


    Соответственно, на всякий случай, перед этим восстанавливаю значение всех регистров (хотя, как понял, значение имеют только esp и ebp). Смысл проблемы заключается в том, что несколько раз этот трюк срабатывает, т.е. страница выполнится, дальше пытается выполниться либо следующая, либо страница через CALL или Jxx или еще че нить... А вот дальше, при очередном доступе к NOACCESS-овской памяти мой обработчик уже не вызывается и прога просто беззвучно вылетает. Например, тестил пакер на LordPE, так он только окно главное на долю секунды показывает и вылетает без воплей. Другие програмы ведут себя примерно таким же образом. Другие потоки вроде не заводятся.

    Потом пробовал через тупую подмену обработчика, дающегося при регистрации процесса. Эффект примерно тот же. Через SetUnhandledExceptionFilter вообще маразм какой-то. Что можно попробовать? Если есть такие сорцы или статьи по этому поводу, поделитесь пожалуйста.
     
  2. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    >




    А можно про это подробнее, откуда и как они восстанавливаются?

    IMHO нужно просто вернуть в EAX 0 (ExceptionContinueExecution) и диспетчер сам перезагрузит регистры из структуры CONTEXT.
     
  3. MoonShiner

    MoonShiner New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    44
    Ну насчет всех я, естественно, загнул=) Из контекста диспетчер их сам восстановит. Имел в виду, что по незнанию восстанавливал все регистры перед ExceptionContinueExecution, хотя значение для нормального дальнейшего функционирования имеют только esp и ebp.



    Но суть не в этом... Смысл в том, что в некоторых мерзких прогах заводятся свои SEH-фреймы, которые, помимо всего прочего, сами пытаются обработать EXCEPTION_ACCESS_VIOLATION, а обработка его заключается в том, что они просто угробят процесс и не будут думать, что делать, куда передавать управление, т.е. даже не вернут EXCEPTION_CONTINUE_SEARCH. Меня же интересует вопрос, можно ли сделать так, чтобы все исключения, связанные с EXCEPTION_ACCESS_VIOLATION в итоге доходили до моего обработчика, а не обрабатывались где-то в цепочке установленных самой пакуемой прогой SEH-фреймов. Или же какие еще могут быть пути решения этой проблемы? Заводить второй процесс, который будет отлаживать другой как в армадилле я не могу, да и неэстетично это.
     
  4. volodya

    volodya wasm.ru

    Публикаций:
    0
    Регистрация:
    22 апр 2003
    Сообщения:
    1.169
    Меня же интересует вопрос, можно ли сделать так, чтобы все исключения, связанные с EXCEPTION_ACCESS_VIOLATION в итоге доходили до моего обработчика, а не обрабатывались где-то в цепочке установленных самой пакуемой прогой SEH-фреймов.



    А-то! Можно. Лезешь сам в ту цепочку SEH-ов программы и переориентируешь адреса обработчиков на свой ;)
     
  5. Mario555

    Mario555 New Member

    Публикаций:
    0
    Регистрация:
    9 июл 2004
    Сообщения:
    21
    volodya

    А-то! Можно. Лезешь сам в ту цепочку SEH-ов программы и переориентируешь адреса обработчиков на свой ;)

    хмм... а в какой момент в неё лезть ?

    тоесть допустим во время выполнения прога ставит свой обработчик, потом происходит исключение, после которого данный обработчик прибъёт прогу. Получается, что нужно отлавливать установку каждого нового обработчика и перенаправлять его на свой... но вот как такое сделать в протекторе ? (в смысле отлов установки обработчиков)
     
  6. MoonShiner

    MoonShiner New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    44
    volodya, именно в том, что сказал Mario555 и заключается проблема. Я могу отлавливать создание новых фреймов и перенаправлять их на свой только в момент выполнения моего обработчика. Но до моего дело в определенный момент просто не дойдет, если на очередной расшифрованной странице прога установит свой SEH-фрейм, который при последующем EXCEPTION_ACCESS_VIOLATION, связанным с переходом на нерасшифрованную и NOACCESS-овскую страницу, просто молча пришибет прогу, не пытаясь вернуть EXCEPTION_CONTINUE_SEARCH...
     
  7. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    ..Вот поэтому в XP+ и появился VEH..

    Может быть пропатчить ExecuteHandler2 ?

    Его адрес можно получить так - вызвать exeption когда установлен только свой SEH и посмотреть, что на стеке.
     
  8. RobinFood

    RobinFood New Member

    Публикаций:
    0
    Регистрация:
    6 апр 2004
    Сообщения:
    45
    Адрес:
    Ukraine
    Mario555

    Решить проблему с установкой обработчика можно. Например, отлавливая обращения к FS:0 с помощью DR-регистров. PAGE_NOACCESS, увы, не подойдет - VirtualProtect на FS:0 тупо не сработает, это я проверял.



    Мелкие подводные камни (вроде необходимости отслеживать создание всех потоков) тоже можно (и нужно) учесть. Но, как я уже где-то писал, есть гораздо бОльшая проблема. При обращении к блоку памяти с атрибутом PAGE_NOACCESS из ring0, т.е. из большинства API-функций (например, ReadProcessMemory, ReadFile, и т.д.) ring3-обработчик вызван не будет. А API-функция сработает не так, как должна была сработать в нормальных условиях. После чего на нормальную работу программы можно даже не надеяться.



    У меня в такой ситуации даже такой простой подопытный кролик, как calc.exe, отказывался запускаться - как выяснилось, RegisterClassEx вызывает с помощью int 2e недокументированную функцию, которая пытается читать память процесса, к которой я ранее запретил доступ с помощью PAGE_NOACCESS, после чего RegisterClassEx возвращает ошибку, окно не создается, процесс завершается.
     
  9. MoonShiner

    MoonShiner New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    44
    Хмм... А чем же тогда можно объяснить достаточно устойчивое функционирование армадиллы? Может быть (и скорее всего:) я не догоняю, но чем это можно объяснить?
     
  10. volodya

    volodya wasm.ru

    Публикаций:
    0
    Регистрация:
    22 апр 2003
    Сообщения:
    1.169
    Потому как в Армадилле есть поток-отладчик и отладочные сообщения передаются ему.
     
  11. Mario555

    Mario555 New Member

    Публикаций:
    0
    Регистрация:
    9 июл 2004
    Сообщения:
    21
    volodya

    но ведь по словам RobinFood:

    RegisterClassEx вызывает с помощью int 2e недокументированную функцию, которая пытается читать память процесса, к которой я ранее запретил доступ с помощью PAGE_NOACCESS -



    арма ставит BreakOnAccess на секцию кода... её отладчик не сможет поймать обращение к памяти процесса из ринг0... но ведь почему-то она работает.



    Например, отлавливая обращения к FS:0 с помощью DR-регистров.

    С двумя процессами - это понятно, но вот в условиях одного процесса ???
     
  12. volodya

    volodya wasm.ru

    Публикаций:
    0
    Регистрация:
    22 апр 2003
    Сообщения:
    1.169
    Вы создаете проблемы там, где их, в принципе, нет.

    В случае процесса-отладчика и отлаживаемого процесса имеет место быть следующая картина (положим, оба процесса в юзер-моде):



    исключение в кольце-3 -> вектор в IDT (кольцо-0) -> LPC сообщение, отправляемое на порт процесса из кольца-0 в кольцо-3



    В случае отладчика "отправляемое на порт процесса" подменяется на фразу "отправляемое на порт отладчика". Т.е. о возникновении исключительных ситуаций первым узнает отладчик. А уж потом решает - либо спустить исключение программе, либо обработать самому.



    В случае MoonShiner ему охота все делать самому. Что ж. Достойная проблема. В этом случае, очевидно, все сообщения отправляются уже не отладчику (его нет!), а самому процессу. Т.е., в конечном итоге, управление передается на SEH-кадр. Вывод: перехватывать SEH на себя. Т.е. все адреса в цепочке SEH должны вести в ТВОЙ код, где ты обрабатываешь одно-единственное исключение - PAGE_NOACCESS. При этом, надо убедиться, что исключение действительно вызвано тобой, а не кем-то другим. Словом, работайте, господа...
     
  13. Max

    Max Member

    Публикаций:
    0
    Регистрация:
    22 май 2003
    Сообщения:
    192
    Mario555

    Получается, что нужно отлавливать установку каждого нового обработчика и перенаправлять его на свой... но вот как такое сделать в протекторе ? (в смысле отлов установки обработчиков)



    намного проще пропачить KiUserExceptionDispatcher.

    там есть call rel32.

    впатчиваешь rel32 на себя, потом jmp на старый rel32 - всегда будешь обрабатывать ексепшен первым.



    RobinFood

    RegisterClassEx вызывает с помощью int 2e недокументированную функцию, которая пытается читать память процесса



    речь идет о том, что ринг0 лезет к секции данных, или к секции кода?

    если ограничиться установкой PAGE_NOACCESS только на секцию кода, это поможет?

    Хотя тут тоже есть неприятные функции (типа CreateThread).

    Вполне возможно, что в них тоже возможна ситуация обращения из ринг0 в ринг3 к странице с PAGE_NOACCESS.

    Ты не проверял?
     
  14. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    Что-то всё сложно так, мне до DR регистров далеко ещё, поэтому я просто менял эти 5 байтов из ntdll.dll на call my_handler. SEH цепочка при этом остаётся неизменной, а my_handler смотрит, нужно ли вызывать обработчики из неё или нет. Возможно у этого способа есть куча недостатков, я так делал тока под XP SP2, под другими ОС адреса ессно другие (если они вообще есть =)
    Код (Text):
    1.  
    2. 7C9037BA    8B4D 18         mov     ecx, [dword ss:ebp+18]
    3. 7C9037BD    FFD1            call    near ecx
    4.  


    ЗЫ Долго я думаю, Max уже про подобное написал.
     
  15. MoonShiner

    MoonShiner New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    44
    volodya, я о том и говорил... Но тогда возникает вопрос, как отлавливать создание новых фреймов. Но, думаю, эта проблема разрешится, так как защищать нужно конкретный софт, который разрабатывает моя же контора (т.е. сорцы все есть:) Просто хотелось написать автоматическую тулзу, которая сама и на любом скомпиленном файле все это делала.

    Max, S_T_A_S_, мне нужно, чтобы работало и в 9х... А KiUserExceptionDispatcher, как мне казалось, в 9х отсутствует:) Но для 2к пропатчу... А вот альтернативная замена этого действия для 9х есть?

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

    В общем, многое прояснилось, появились кое-какие идейки.

    Mario555, Max, S_T_A_S_, volodya, огромное спасибо вам за ответы.



    ЗЫ Тема не закрыта, дополнительным идеям буду очень рад.
     
  16. volodya

    volodya wasm.ru

    Публикаций:
    0
    Регистрация:
    22 апр 2003
    Сообщения:
    1.169
    Но тогда возникает вопрос, как отлавливать создание новых фреймов



    Ну... В принципе, неуниверсальное решение - это перехват соответствующих функций ntdll.dll. Никаких DR-регистров, ничего такого использовать не надо.
     
  17. S_T_A_S_

    S_T_A_S_ New Member

    Публикаций:
    0
    Регистрация:
    27 окт 2003
    Сообщения:
    1.754
    MoonShiner



    KiUserExceptionDispatcher нет в мастае (а у меня мастдая нет, что бы посмотреть как там всё), но я предлагаю падчить не непосредственно его, а конечную точку, непосредственно вызывающую твой SE Handler - этот фрагмент ExecuteHandler2 я привёл. как обстоят дела на других системах я не знаю, но что-то мне подсказывает, что адрес вызываемого SEH всё так же будет лежать в [dword ss:ebp+18] - нужно тока убедиться в этом.
     
  18. RobinFood

    RobinFood New Member

    Публикаций:
    0
    Регистрация:
    6 апр 2004
    Сообщения:
    45
    Адрес:
    Ukraine
    Max







    Не проверял. Но ведь мы же хотим сделать "универсальный пакер" (а я хотел тогда сделать универсальный API-монитор). Для меня хватило того, что я понял, что такое может быть в нестандартных ситуациях (ReadFile в секцию кода).



    И потом, кто сказал что нельзя передавать управление на данные? XP SP2? Вот на него и будем ориентироваться.

    Только я спешить не буду, а подожду (пусть даже пару лет), пока AMD-64 подешевеет - и тогда смогу пользоваться NoExecute-флагом в свое удовольствие :)



    S_T_A_S_





    Это ты просто не пробовал :)

    ba (break-on-access) в отладичках использовал? Вот он как раз и реализован через DR-регистры. Всего-то делов, вызвать SetThreadContext для их изменения...
     
  19. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Бредовая идея:

    Если речь идет об "универсальном пакере", то не стоит исключать непростой, но "изящно-эстетичный" вариант - патчить саму прогу, т.е. вылавливать установку SEH-ов по обращению к fs:[0] и подменять push оригинального обработчика на санки по типу переадресации таблицы импорта при отладке под 9x. Наверное это можно делать не только в процессе распаковки в runtime, но и до или в процессе упаковки.

    Думаю вариаций установки SEH-ов по жизни не более двух-трех, а в языках высокого уровня наверняка используется один из излюбленных вариантов (например в дельфях это всегда xor eax,eax + push fs:[eax] + mov fs:[eax],esp).



    PS: чувствую будут бить, возможно ногами :)
     
  20. Max

    Max Member

    Публикаций:
    0
    Регистрация:
    22 май 2003
    Сообщения:
    192
    MoonShiner

    Заводить второй процесс, который будет отлаживать другой как в армадилле я не могу



    интересно, а почему арма нормально работает в таких случаях?

    допустим, есть процесс-отладчик.

    отлаживаемый процесс вызывает RegisterClassEx, управление уходит в ринг0, там возникает исключение на PAGE_NOACCESS.

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

    интересно было бы проверить, да вот как?



    RobinFood

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



    нуу, ReadFile в секцию кода (которая у всех нормальных людей real-only) может сделать только настоящий оптимизатор :))

    а вот то, что HLL компилятор засовывает в секцию кода данные (строки) - сплошь и рядом.

    ты не мог бы припомнить, какие именно параметры при вызове RegisterClassEx указывали на PAGE_NOACCESS?

    хотца востановить эту ситуацию да поэкспериментировать.



    leo

    патчить саму прогу, т.е. вылавливать установку SEH-ов по обращению к fs:[0] и подменять push оригинального обработчика



    чиста теоретически, можно перевести fs:[0] в линейный адрес через GetThreadSelectorEntry и поставить DR регистр на этот адрес по записи.

    или попробовать поставить page_readonly на тот-же адрес.

    в результате, при попытке установки seh'а вылетит искючение, надо только обработать его первым...