Clerk а вы задавайте задачки иногда. вроде как блэк миррор. интересное развлечение иногда подумать как в старые добрые времена.
Clerk Выполнение кода на эмуляторе с синхронизацией исполнения в исследуемом процессе. Все попытки установки callback'ов переставляются на свои процедурки и уже в них управление передается на настоящие колбэки. Если честно, я так и не смог въехать в точную формулировку задачи Можно простейший пример (в виде схемы) - в чем конкретно сложность и чего именно нужно перехватить? Если исследуемый код просто обфусцирован (те просто попытка скрыть функциональность) и не напичкан системными вызовами - то я к примеру трассировал куски sys-драйвера через SEH в usermode а потом писал фильтры для лога.
PSR1257 Кодом не совсем корректно описывать задачу. Доступ к ресурсу зависит от SFN. Тоесть: AcquireResource() Callback() ReleaseResource() Необходимо выполнить некоторые действия при освобождении ресурса.
Clerk Попробую пересказать как я понимаю это (выполнение приведено как это выглядит во времени): Variant I. 1. AcquireResource() 2. return from AcquireResource; 3. Callback(&ProcX); // Инициализация callback?! 4. return from Callback(& ... (Сейчас в любой момент управление может быть передано на ProcX асинхронно) 5. ... выполнение некоего кода сразу после Callback(&... 6. ... Асинхронный вызов ProcX, возврат ... 7. ReleaseResource() Variant II. ReleaseResource() находится внтури колбэка и вызывается асинхронно. 1. AcquireResource() 2. return from AcquireResource; 3. Callback(&ProcX); // Инициализация callback?! 4. return from Callback(& ... (Сейчас в любой момент управление может быть передано на ProcX асинхронно) 5. ... выполнение некоего кода сразу после Callback(&... 6. ... Асинхронный вызов ProcX, ReleaseResource() ВНУТРИ, возврат ... 7. ... Какая схема является актуальной? Все происходит в SYS, и нельзя (нежелательно) прибегать к аппаратным методам отладки: любая возня с IDT (int 1,3 included) - лучше избежать, помечать страницы как выгруженные и поставить свой обработчик (врезка в старый тоже) PageFault - нельзя - ... - какие еще ограничения? Так или иначе, а по условию нельзя патчить ReleaseResource (саму) - ?, нельзя сажать в систему свой SYS чтобы ловить патченный импорт приложения - ReleaseResourceXXX. Все это в реальном времени или речь идет о де-обфускации (можно трейсить код часами и медитировать)? Ну и собственно главное - задача получить управление СРАЗУ ПОСЛЕ ReleaseRSRC?
PSR1257 Не рассматривайте AcquireResource() как просто процедуру. Это может быть очень большой код, сотни вложенных процедур. Как например любой системный сервис, где управление начинается от входа в ядро и путь через множество процедур вглубь. Просто это механизм, который не позволяет выполнить некоторые действия в калбэке, они возможны только после возврата и освобождения ресурса. Незачем использовать всякий изврат с сепшенами и пр. Нужно просто спустится по стеку вызовов. Допустим ресурс свободен при SFN1, калбэк имеет SFN2 = SFN1 + M, он освобождается когда SFN <= SFN1.
Clerk Опять непонятно Есть некий SYS, он вызывает AcquireResource тем самым где-то в глубине системы захватывая этот ресурс, далее все это когда-то вернется назад, будет выполнены другие команды этого же SYS и в самом конце - ReleaseResource. Callback-процедура же установлена Вами (?) самим приложением (?) и переиодически получая управление (или один раз - что за условие?) должна выполнить некое действие но при этом вначале нужно как-то узнать достигло ли управление ReleaseResource() (строго после возврата или допустимо выполнение следующих команд?). Callback-процедуре дозволяется (?) перехват перед Acquire, она может (?) в этот момент проводить анализ кода, стека... - ?
PSR1257 Проще показать пример. Код (Text): NtCreateProcessEx PspCreateProcess MmInitializeProcessAddressSpace PsMapSystemDll MmMapViewOfSection LOCK_ADDRESS_SPACE MiMapViewOfImageSection PsCallImageNotifyRoutines Notify() UNLOCK_ADDRESS_SPACE Это вызов LoadImage нотификатора при создании процесса. АП не доступно и пр. необходимый функционал. Нужно получить управление при возврате из PspCreateProcess(), тогда процесс инициализирован и можно выполнить необходимые действия. Код (Text): LdrInitializeThunk LdrpInitialize _LdrpInitialize LOCK_LOADER_LOCK LdrpInitializeThread LdrpCallInitRoutine InitRoutine() UNLOCK_LOADER_LOCK Вызов "DllMain" при создании потока. Загрузчик залочен, необходимо выполнить некоторые действия после его освобождения и вобще по возврете из LdrpInitialize(). Код (Text): NtReadVirtualMemory MmCopyVirtualMemory MiDoMappedCopy MmProbeAndLockPages LOCK_PFN MmAccessFault RAISE_IRQL LOCK_WORKING_SET MmPageFaultNotifyRoutine Notify() UNLOCK_WORKING_SET LOWER_IRQL UNLOCK_PFN PageFault нотификатор, нужно выполнить некоторые действия при возврате из MmAccessFault().
Вот скрин отлично отражающий суть из топика про IDP: Большая часть сделанных реализаций этой техники требует понижение SFN для получения контроля на необходимой точке.
PSR1257 Вот общая модель механизма: ; Routine() -> ... -> Notify(). Initialize: Graph = CreateGraph(@Routine, NL = 1) Notify: Frame:PSTACK_FRAME Frame = rEbp Do if CheckIpBelongToGraph(Graph, Frame.Ip) Tls = AllocateTls() Tls.Ip = Frame.Ip Frame.Ip = @2ndDispatch() End endif Frame = Frame.Next Loop END_OF_CHAIN 2ndDispatch: Payload() Jmp Tls.Ip
Clerk О, спасибо - я наверное почти все понял. Код (Text): NtCreateProcessEx PspCreateProcess MmInitializeProcessAddressSpace PsMapSystemDll MmMapViewOfSection LOCK_ADDRESS_SPACE MiMapViewOfImageSection PsCallImageNotifyRoutines Notify(&OurCallBack) UNLOCK_ADDRESS_SPACE ... где-то в нашем SYS ... OurCallBack proc ... В данном примере мы получаем управление внутри вызываемой OurCallBack и мы видим (?) куда будет выполнен возврат (а именно - в следующую инструкцию после Notify(&OurCallBack)), нам нужно каким-то образом поймать завершение процедуры в UNLOCK_ADDRESS_SPACE. Верно? Допустим мы точно знаем этот адрес - что далее? Мы будет патчить это место создавая переходник на втрой обработчик?
PSR1257 Мы ищем в SFC необходимый фрейм с адресом возврата принадлежащим функции PspCreateProcess(). Если этот адрес найден, мы его(или следующий фрейм, это адрес возврата из PspCreateProcess()) заменяем на OurCallBack(у меня это 2ndDispatch()). После возврата выполняем необходимые действия и возвращаемся обратно на оригинальный адрес возврата. Проблема в определении необходимого фрейма, о чём и топик.
Clerk Может быть прокатит идейка с эмулятором? Найденный фрейм PspCreateProcess() -> ссылка на системный модуль -> считываем его в user32 и выполняем на эмуляторе -> естественно после эмуляции мы знаем когда и как произошел возврат. Также можно предложить сигнатурный метод. Прогоняем для каждого конкретнго перехвата все это под отладчиком и накапливаем фреймы со стеков (может быть в разных версиях win тоже). То есть например с точностью до локации модулей адреса и данные конкретного фрейма будут выглядеть так: FRAME_BEGIN: RET_ADDRESS // Always belong to current IB of system module - то, чего нам нужно но мы точно его не знаем. // Но мы знаем что он ВСЕГДА +1000h...1800h от EP / Code section begin / etc. SAVED_EBP // Мы опять не знаем точного значения но мы знаем что это будет в пределах current_stack_space LOCAL_VAR1 // Какая-то локальная переменная, она всегда 0xC??????? ... SOME_CONST1 // Допустим тут всегда ref to str - имя для AllocatePoolWithTag. ...
PSR1257 Как найдём, если адрес возврата не известен ? Это никакой не перехват, а отложенный способ вызова кода на подобие DPC.
Clerk Насчет сигнатуры фрейма. Имелось в виду ЗАРАНЕЕ составляем сигнатурки и потом используем их (уже в реалтайм) для скана стека в поисках нужного фрейма. Никакой трассировки.
PSR1257 В #36 семпл http://www.wasm.ru/forum/viewtopic.php?pid=400318#p400318, покажите свой или опишите действия подробно.
Clerk Мне кажется, Ваше решение не универсальное. Или чтобы быть таковым, потребует реализации анализатора кода, не уступающего по сложности движку IDA. Например, как Вы предлагаете строить граф в случае, если ф-ция содержит chunks, связанные косвенными переходами?
green Оно не нужно, очень редко это может понадобиться. Этого достаточно почти для всего системного кода.
Clerk Попробовал быстро вникнуть в Ваш сэмпл - навскидку нет - мне нужно время. Поэтому попробую опейсать идейку - это очень тупо и вроде бы очень просто реализуемо. Part I. "Дома" Берем отладчик, ida, все что нужно для анализа. Запускаем код и останавливаемся внутри нашей CallBack которую позвал Notify(). Поднимаясь вверх по стеку находим нужный фрейм и адрес возврата. Дампим фрейм в файл. Далее пытаемся составить СИГНАТУРУ (гибкую). В фрейме не только адрес возврата но и еще другие адреса и переменные и константы - все это ХАРАКТЕРНО для данного модуля и данной версии винды и конкретного порядка и расположения сисмодулей в памяти. Пытаемся абстрагироваться от конкретики - допустим (грубо) мы нашли что по смещению RET_ADDR-50h ВСЕГДА есть какая-то константа - например это было: push "MTag" ... call AllocatePoolWithTag В данном случае в фрейме ВСЕГДА есть "MTag". Также можно маскировать и адреса, допустим IB может быть разная но (например) разница элментов RET_ADDR-34h и RET_ADDR-48h всегда равна 120h. Даже если эта разница плавает от версии к версии винды, то мы все равно можем фильтровать: разница от 120h (min) до 180h (max). Можно продолжать долго но думаю что 4-8 фильтров должно хватить. Плюс проверки типа адрес принадлежит данному SYS, etc, etc. Part II. "В поле" В реальных условиях мы просто сканируем из CallBack стек вверх применяя сигны. Никой отладки и вообще участия человека уже не нужно, это все идентично распознаванию LibraryCode через сигны Flirt в IDA. Если мы нашли что данная сигна подошла мы делаем практически 100% заключение где адрес возврата и меняем его.