Добрый день. Помогите, пожалуйста, разрешить следующую проблему: Сейчас по нуждам работы пишу пакер PE-файлов. Возникла потребность реализовать нечто вроде CopyMem-а в армадилле. Поступаю следующим образом: 1) Перед передачей управления на OEP проставляю всей секции кода PAGE_NOACCESS и регистрирую свой обработчик через Код (Text): push offset MyHandler push dword ptr fs:[0000h] mov dword ptr fs:[0000h],esp Далее передаю управление на OEP (А там PAGE_NOACCESS). Вылетает исключение и я попадаю в свой обработчик. Там определяю страницу, на которой произошло исключение (страницу определяю через EIP, на котором оно произошло), ставлю ей атрибут 00000040h и расшифровываю ее в нужное место. Затем делаю Код (Text): mov eax, ExceptionContinueExecution ret Соответственно, на всякий случай, перед этим восстанавливаю значение всех регистров (хотя, как понял, значение имеют только esp и ebp). Смысл проблемы заключается в том, что несколько раз этот трюк срабатывает, т.е. страница выполнится, дальше пытается выполниться либо следующая, либо страница через CALL или Jxx или еще че нить... А вот дальше, при очередном доступе к NOACCESS-овской памяти мой обработчик уже не вызывается и прога просто беззвучно вылетает. Например, тестил пакер на LordPE, так он только окно главное на долю секунды показывает и вылетает без воплей. Другие програмы ведут себя примерно таким же образом. Другие потоки вроде не заводятся. Потом пробовал через тупую подмену обработчика, дающегося при регистрации процесса. Эффект примерно тот же. Через SetUnhandledExceptionFilter вообще маразм какой-то. Что можно попробовать? Если есть такие сорцы или статьи по этому поводу, поделитесь пожалуйста.
> А можно про это подробнее, откуда и как они восстанавливаются? IMHO нужно просто вернуть в EAX 0 (ExceptionContinueExecution) и диспетчер сам перезагрузит регистры из структуры CONTEXT.
Ну насчет всех я, естественно, загнул=) Из контекста диспетчер их сам восстановит. Имел в виду, что по незнанию восстанавливал все регистры перед ExceptionContinueExecution, хотя значение для нормального дальнейшего функционирования имеют только esp и ebp. Но суть не в этом... Смысл в том, что в некоторых мерзких прогах заводятся свои SEH-фреймы, которые, помимо всего прочего, сами пытаются обработать EXCEPTION_ACCESS_VIOLATION, а обработка его заключается в том, что они просто угробят процесс и не будут думать, что делать, куда передавать управление, т.е. даже не вернут EXCEPTION_CONTINUE_SEARCH. Меня же интересует вопрос, можно ли сделать так, чтобы все исключения, связанные с EXCEPTION_ACCESS_VIOLATION в итоге доходили до моего обработчика, а не обрабатывались где-то в цепочке установленных самой пакуемой прогой SEH-фреймов. Или же какие еще могут быть пути решения этой проблемы? Заводить второй процесс, который будет отлаживать другой как в армадилле я не могу, да и неэстетично это.
Меня же интересует вопрос, можно ли сделать так, чтобы все исключения, связанные с EXCEPTION_ACCESS_VIOLATION в итоге доходили до моего обработчика, а не обрабатывались где-то в цепочке установленных самой пакуемой прогой SEH-фреймов. А-то! Можно. Лезешь сам в ту цепочку SEH-ов программы и переориентируешь адреса обработчиков на свой
volodya А-то! Можно. Лезешь сам в ту цепочку SEH-ов программы и переориентируешь адреса обработчиков на свой хмм... а в какой момент в неё лезть ? тоесть допустим во время выполнения прога ставит свой обработчик, потом происходит исключение, после которого данный обработчик прибъёт прогу. Получается, что нужно отлавливать установку каждого нового обработчика и перенаправлять его на свой... но вот как такое сделать в протекторе ? (в смысле отлов установки обработчиков)
volodya, именно в том, что сказал Mario555 и заключается проблема. Я могу отлавливать создание новых фреймов и перенаправлять их на свой только в момент выполнения моего обработчика. Но до моего дело в определенный момент просто не дойдет, если на очередной расшифрованной странице прога установит свой SEH-фрейм, который при последующем EXCEPTION_ACCESS_VIOLATION, связанным с переходом на нерасшифрованную и NOACCESS-овскую страницу, просто молча пришибет прогу, не пытаясь вернуть EXCEPTION_CONTINUE_SEARCH...
..Вот поэтому в XP+ и появился VEH.. Может быть пропатчить ExecuteHandler2 ? Его адрес можно получить так - вызвать exeption когда установлен только свой SEH и посмотреть, что на стеке.
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 возвращает ошибку, окно не создается, процесс завершается.
Хмм... А чем же тогда можно объяснить достаточно устойчивое функционирование армадиллы? Может быть (и скорее всего я не догоняю, но чем это можно объяснить?
volodya но ведь по словам RobinFood: RegisterClassEx вызывает с помощью int 2e недокументированную функцию, которая пытается читать память процесса, к которой я ранее запретил доступ с помощью PAGE_NOACCESS - арма ставит BreakOnAccess на секцию кода... её отладчик не сможет поймать обращение к памяти процесса из ринг0... но ведь почему-то она работает. Например, отлавливая обращения к FS:0 с помощью DR-регистров. С двумя процессами - это понятно, но вот в условиях одного процесса ???
Вы создаете проблемы там, где их, в принципе, нет. В случае процесса-отладчика и отлаживаемого процесса имеет место быть следующая картина (положим, оба процесса в юзер-моде): исключение в кольце-3 -> вектор в IDT (кольцо-0) -> LPC сообщение, отправляемое на порт процесса из кольца-0 в кольцо-3 В случае отладчика "отправляемое на порт процесса" подменяется на фразу "отправляемое на порт отладчика". Т.е. о возникновении исключительных ситуаций первым узнает отладчик. А уж потом решает - либо спустить исключение программе, либо обработать самому. В случае MoonShiner ему охота все делать самому. Что ж. Достойная проблема. В этом случае, очевидно, все сообщения отправляются уже не отладчику (его нет!), а самому процессу. Т.е., в конечном итоге, управление передается на SEH-кадр. Вывод: перехватывать SEH на себя. Т.е. все адреса в цепочке SEH должны вести в ТВОЙ код, где ты обрабатываешь одно-единственное исключение - PAGE_NOACCESS. При этом, надо убедиться, что исключение действительно вызвано тобой, а не кем-то другим. Словом, работайте, господа...
Mario555 Получается, что нужно отлавливать установку каждого нового обработчика и перенаправлять его на свой... но вот как такое сделать в протекторе ? (в смысле отлов установки обработчиков) намного проще пропачить KiUserExceptionDispatcher. там есть call rel32. впатчиваешь rel32 на себя, потом jmp на старый rel32 - всегда будешь обрабатывать ексепшен первым. RobinFood RegisterClassEx вызывает с помощью int 2e недокументированную функцию, которая пытается читать память процесса речь идет о том, что ринг0 лезет к секции данных, или к секции кода? если ограничиться установкой PAGE_NOACCESS только на секцию кода, это поможет? Хотя тут тоже есть неприятные функции (типа CreateThread). Вполне возможно, что в них тоже возможна ситуация обращения из ринг0 в ринг3 к странице с PAGE_NOACCESS. Ты не проверял?
Что-то всё сложно так, мне до DR регистров далеко ещё, поэтому я просто менял эти 5 байтов из ntdll.dll на call my_handler. SEH цепочка при этом остаётся неизменной, а my_handler смотрит, нужно ли вызывать обработчики из неё или нет. Возможно у этого способа есть куча недостатков, я так делал тока под XP SP2, под другими ОС адреса ессно другие (если они вообще есть =) Код (Text): 7C9037BA 8B4D 18 mov ecx, [dword ss:ebp+18] 7C9037BD FFD1 call near ecx ЗЫ Долго я думаю, Max уже про подобное написал.
volodya, я о том и говорил... Но тогда возникает вопрос, как отлавливать создание новых фреймов. Но, думаю, эта проблема разрешится, так как защищать нужно конкретный софт, который разрабатывает моя же контора (т.е. сорцы все есть Просто хотелось написать автоматическую тулзу, которая сама и на любом скомпиленном файле все это делала. Max, S_T_A_S_, мне нужно, чтобы работало и в 9х... А KiUserExceptionDispatcher, как мне казалось, в 9х отсутствует Но для 2к пропатчу... А вот альтернативная замена этого действия для 9х есть? С тредами проблем пока не возникнет, приложения пока однопоточные. Это дальше придется думать с потоками. В общем, многое прояснилось, появились кое-какие идейки. Mario555, Max, S_T_A_S_, volodya, огромное спасибо вам за ответы. ЗЫ Тема не закрыта, дополнительным идеям буду очень рад.
Но тогда возникает вопрос, как отлавливать создание новых фреймов Ну... В принципе, неуниверсальное решение - это перехват соответствующих функций ntdll.dll. Никаких DR-регистров, ничего такого использовать не надо.
MoonShiner KiUserExceptionDispatcher нет в мастае (а у меня мастдая нет, что бы посмотреть как там всё), но я предлагаю падчить не непосредственно его, а конечную точку, непосредственно вызывающую твой SE Handler - этот фрагмент ExecuteHandler2 я привёл. как обстоят дела на других системах я не знаю, но что-то мне подсказывает, что адрес вызываемого SEH всё так же будет лежать в [dword ss:ebp+18] - нужно тока убедиться в этом.
Max Не проверял. Но ведь мы же хотим сделать "универсальный пакер" (а я хотел тогда сделать универсальный API-монитор). Для меня хватило того, что я понял, что такое может быть в нестандартных ситуациях (ReadFile в секцию кода). И потом, кто сказал что нельзя передавать управление на данные? XP SP2? Вот на него и будем ориентироваться. Только я спешить не буду, а подожду (пусть даже пару лет), пока AMD-64 подешевеет - и тогда смогу пользоваться NoExecute-флагом в свое удовольствие S_T_A_S_ Это ты просто не пробовал ba (break-on-access) в отладичках использовал? Вот он как раз и реализован через DR-регистры. Всего-то делов, вызвать SetThreadContext для их изменения...
Бредовая идея: Если речь идет об "универсальном пакере", то не стоит исключать непростой, но "изящно-эстетичный" вариант - патчить саму прогу, т.е. вылавливать установку SEH-ов по обращению к fs:[0] и подменять push оригинального обработчика на санки по типу переадресации таблицы импорта при отладке под 9x. Наверное это можно делать не только в процессе распаковки в runtime, но и до или в процессе упаковки. Думаю вариаций установки SEH-ов по жизни не более двух-трех, а в языках высокого уровня наверняка используется один из излюбленных вариантов (например в дельфях это всегда xor eax,eax + push fs:[eax] + mov fs:[eax],esp). PS: чувствую будут бить, возможно ногами
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'а вылетит искючение, надо только обработать его первым...