Множество вопросов при захвате кода, в личку часто пишут про это, но лишь не многие используют обработку исключений. Это определяется самим подходом к захвату. Опишу только общий принцип. Жёсткая модификация кода далее не рассматривается изза своей не актуальности и простоты. Одним из основных альтернативных(продвинутых) способов контроля исполнения кода является совокупность из трёх базовых техник: o Обработка(отлов) исключения. o Трассировка. o Бактрейс(развёртка/парсинг цепочки стековых вреймов). Принцип заключается в следующем. Допусти в какомто месте кода используется обращение к адресу, ссылка на который определена в глобальной(фиксированной) переменной. Для отлова этого обращения значение этой переменной(указатель который она содержит) делается не валидным, указывающем на не валидный адрес. Далее при обращении к этой переменной возникнет исключение. Обработчик исключения имеет контекст потока в котором это исключение возникло. Этот момент является основой для дальнейшей обработки кода до необходимого(целевого) места. Отсюда начинается трассировка кода. Целевой код может находится на значительном удалении от текущего, поэтому полная трассировка не применима. Используется трассировка до процедурного ветвления, которое загружает в стек адрес возврата. Этот адрес делается не валидным и на этом трассировка завершается. При возврате из процедуры возникнет исключение, контекст должен быть восстановлен(в частности Eip). Если целевой код находится выше по цепочке стековых фреймов, то выполняется развёртка(бактрейс) их и замена указателей на не валидные. Это позволит получать управление при возврате из процедур. Условием необходимым для реализации этой техники является наличие фиксированной глобальной переменной содержащей указатель и отсутствие проверки указателя(например обращение к базе данных загрузчика, ссылка на которую находится в PEB отслеживается, но в User32.dll есть проверки и это место требует специальной обработки). Это могут быть внутренние структуры, указатели на структуры, строки и тп. Также для генерации исключения может быть использована подмена смещений, индексов и тп., которые используются при адресации. Конечной целью такой манипуляции является разрушение указателя, что приведёт к генерации исключения. В юзермоде как пример: указатели в TEB, PEB, списки модулей и пр., указатели в секции данных модулей на внутренние структуры, строки и тп. Частным случаем является перемещение структуры. Структура перемещается в конец страницы, следующая страница не выделена и при обращении к полям следующим за последним будет возникать исключение. Часто эта техника является единственно возможной при захвате кода. Для шадова(GUI) может успешно использоваться, так как там активно используются глобальные указатели. При выборе сопсоба захвата следует использовать следующее: 1. Поиск непосредственно указателя на обработчик в структуре или секции данных. Это могут быть калбэки, как частный случай нотификаторы. Сюда можно отнести подмену ссылки на список(структуру). Может использоваться совокупность теневых калбэков с бактрейсом. 2. Поиск калбэка выше либо ниже по последовательности вызовов процедур. Если такой калбэк существует, то он будет использовать бактрейс(развёртку цепочки стековых фреймов) с подменой адреса возврата в одном из фреймов на свой обработчик. 3. Если имеется возможность обработки исключения, то ищется способ генерации исключения как можно ближе к целевому коду. Основным способом является разрушение указателей. В ядре на высоких IRQL возможности не большие, обычно выполняется замена шлюза в IDT, на низких IRQL обработка исключений в KiDebugRoutine. В юзермоде удалённо исключение обрабатывается посредством подключения отладчика к целевому процессу и обработки отладочных сообщений. Если отладчик не может быть применён, возможна регистрация векторного обработчика исключений. Здесь есть один подводный камень - указатель на VEH в системном списке зашифрован, ключём являются куки, значение уникальное для процесса и оно не может быть получено в юзермоде удалённо. Следует выполнить код в контексте целевого процесса, который установит обработчик. 4. Жёсткая модификация кода. Атомарный сплайсинг/хотпатч. Пример реализации. Допустим имеется код: Код (Text): MainRoutine: [...] Call SubRoutine1 [...] Call SubRoutine2 [...] [Целевой код] SubRoutine1: [...] MessageBox(GlobalNameRef, ..) ; Указатель на строку. [...] ret SubRoutine2: [...] ret Разрушаем указатель в GlobalNameRef, предварительно сохранив его для последующего восстановления. При обращении к указателю исключение возникнет гдето внутри MessageBox(). Обработчик исключений выполнит бактрейс, найдёт необходимый стековый фрейм с адресом возврата в MainRoutine(), сохранит этот указатель и сделает его не валидным. После чего восстановит в контексте указатель [GlobalNameRef], либо указатель в GlobalNameRef(в этом случае выполняется трассировка инструкции, после её исполнения указатель снова затирается) и выполнит рестарт инструкции. Тред отработает и при возврате из SubRoutine() будет сгенерировано исключение. Так как целевой код находится на некотором удалении от процедурного ветвления на SubRoutine1(), и между ними есть есчо ветвление, а его нужно пропустить, то контекст восстанавливается и выполняется трассировка до входа в SubRoutine2(). После этого как и в предыдущем случае делается не валидным адрес возврата в стеке и тред отпускается. После отработки SubRoutine2() при возврате из неё возникает исключение. Контекст восстанавливается и отсюда начинается трассировка до целевого кода. Эта техника эффективна для ядра. Разумеется не всюду это может применяться, но это должно быть основным способом захвата, если не возможна установка калбэков.
На вскидку кодес. Поиск LdrpInitializeTls(). Допустим гдето внутри процедуры имеется ссылка на строку. Соответственно для этой ссылки определён фиксап. o Ищем строку в памяти. o Ищем фиксап для ссылки на строку. o В графе ищем процедуру с инструкцией загружающей ссылку на строку, исходя из адреса найденого анализом фиксапов. Код (Text): ; \IDP\Public\User\Bin\Graph\Dasm\Relocs\Fix.asm ; .686 .model flat, stdcall option casemap :none include \masm32\include\ntdll.inc GET_CURRENT_GRAPH_ENTRY macro Call _$_GetCallbackReference endm .code _$_GetCallbackReference:: pop eax ret SEH_Prolog proc C pop ecx push ebp push eax Call SEH_GetRef push eax assume fs:nothing push dword ptr fs:[TEB.Tib.ExceptionList] mov dword ptr fs:[TEB.Tib.ExceptionList],esp jmp ecx SEH_Prolog endp SEH_Epilog proc C pop ecx pop dword ptr fs:[TEB.Tib.ExceptionList] lea esp,[esp + 3*4] jmp ecx SEH_Epilog endp SEH_GetRef proc C GET_CURRENT_GRAPH_ENTRY mov eax,dword ptr [esp + 4] mov esp,dword ptr [esp + 2*4] ; (esp) -> ExceptionList mov eax,EXCEPTION_RECORD.ExceptionCode[eax] mov ebp,dword ptr [esp + 3*4] jmp dword ptr [esp + 2*4] SEH_GetRef endp LdrImageNtHeader proc ImageBase:PVOID, ImageHeader:PIMAGE_NT_HEADERS mov edx,ImageBase mov eax,STATUS_INVALID_IMAGE_FORMAT assume edx:PIMAGE_DOS_HEADER cmp [edx].e_magic,'ZM' jne @f add edx,[edx].e_lfanew assume edx:PIMAGE_NT_HEADERS cmp [edx].Signature,'EP' jne @f cmp [edx].FileHeader.SizeOfOptionalHeader,sizeof(IMAGE_OPTIONAL_HEADER32) jne @f cmp [edx].FileHeader.Machine,IMAGE_FILE_MACHINE_I386 jne @f test [edx].FileHeader.Characteristics,IMAGE_FILE_32BIT_MACHINE je @f mov ecx,ImageHeader xor eax,eax mov dword ptr [ecx],edx @@: ret LdrImageNtHeader endp ; + ; Перечисление фиксапов для ссылки. ; LdrEnumerateFixups proc uses ebx esi edi ImageBase:PVOID, Ip:PVOID, CallbackRoutine:PVOID, CallbackParameter:PVOID Local ExitFlag:BOOLEAN Call SEH_Epilog_Reference Call SEH_Prolog invoke LdrImageNtHeader, ImageBase, addr ExitFlag test eax,eax mov ecx,ExitFlag jnz Exit mov esi,IMAGE_NT_HEADERS.OptionalHeader.DataDirectory.VirtualAddress[ecx + IMAGE_DIRECTORY_ENTRY_BASERELOC*sizeof(IMAGE_DATA_DIRECTORY)] mov edi,IMAGE_NT_HEADERS.OptionalHeader.DataDirectory._Size[ecx + IMAGE_DIRECTORY_ENTRY_BASERELOC*sizeof(IMAGE_DATA_DIRECTORY)] test esi,esi mov edx,Ip mov ecx,IMAGE_NT_HEADERS.OptionalHeader.SizeOfImage[ecx] jz Error sub edx,ImageBase jbe Error cmp edx,ecx jnb Error test edi,edi jz Error add esi,ImageBase add edi,esi ; Limit assume esi:PIMAGE_BASE_RELOCATION Scan: mov ebx,[esi].SizeOfBlock sub ebx,sizeof(IMAGE_BASE_RELOCATION) jbe Error ; .. shr ebx,1 @@: movzx eax,word ptr [esi + ebx*2 + sizeof(IMAGE_BASE_RELOCATION) - 2] ; push eax ; and eax,NOT(0FFFH) ; cmp eax,IMAGE_REL_BASED_HIGHLOW ; pop eax ; jne Error and eax,0FFFH add eax,[esi].VirtualAddress mov ecx,Ip add eax,ImageBase .if dword ptr [eax] == ecx lea edx,ExitFlag mov ExitFlag,FALSE push edx push CallbackParameter push eax push ImageBase Call CallbackRoutine cmp ExitFlag,FALSE jne Exit .endif dec ebx jnz @b Next: add esi,[esi].SizeOfBlock cmp esi,edi jb Scan xor eax,eax jmp Exit SEH_Epilog_Reference: GET_CURRENT_GRAPH_ENTRY Exit: Call SEH_Epilog ret Error: mov eax,STATUS_UNSUCCESSFUL jmp Exit LdrEnumerateFixups endp CALLBACK_DATA struct Routine PVOID ? Parameter PVOID ? CALLBACK_DATA ends PCALLBACK_DATA typedef ptr CALLBACK_DATA _$_SearchCallback: GET_CURRENT_GRAPH_ENTRY SearchCallbackInternal proc ImageBase:PVOID, Fixup:PVOID, CallbackData:PCALLBACK_DATA, ExitFlags:PBOOLEAN mov eax,Fixup mov ecx,CallbackData dec eax cmp byte ptr [eax],68H jne @f push ExitFlags push CALLBACK_DATA.Parameter[ecx] push eax push ImageBase Call CALLBACK_DATA.Routine[ecx] @@: ret SearchCallbackInternal endp ; + ; Ищет инструкцию Push XXXX для ссылки сканируя таблицу базовых поправок. ; LdrSearchPushReferenceInRelocationTable proc ImageBase:PVOID, Ip:PVOID, CallbackRoutine:PVOID, CallbackParameter:PVOID lea ecx,CallbackRoutine Call _$_SearchCallback invoke LdrEnumerateFixups, ImageBase, Ip, Eax, Ecx ret LdrSearchPushReferenceInRelocationTable endp ; + ; Ищет строку ASCII "LDR: Tls Found in %wZ at %p",LF ; [Сообщение может отличаться в версиях.] ; LgSearchMessage proc uses edi ImageBase:PVOID, Message:PVOID Local ImageHeader:PIMAGE_NT_HEADERS mov edi,ImageBase invoke LdrImageNtHeader, Edi, addr ImageHeader test eax,eax mov ecx,ImageHeader jnz Exit add edi,IMAGE_NT_HEADERS.OptionalHeader.BaseOfCode[ecx] mov ecx,IMAGE_NT_HEADERS.OptionalHeader.SizeOfCode[ecx] cld mov al,'L' mov edx,Message @@: repne scasb jne @f cmp dword ptr [edi]," :RD" jne @b cmp dword ptr [edi + 4]," slT" jne @b cmp dword ptr [edi + 2*4],"nuoF" jne @b dec edi xor eax,eax mov dword ptr [edx],edi jmp Exit @@: mov eax,STATUS_NOT_FOUND Exit: ret LgSearchMessage endp _$_LgSearchCallback: GET_CURRENT_GRAPH_ENTRY LgSearchCallback proc ImageBase:PVOID, Ip:PVOID, Message:PVOID, ExitFlag:PBOOLEAN mov eax,Message mov ecx,Ip mov edx,ExitFlag mov dword ptr [eax],ecx mov byte ptr [edx],1 xor eax,eax ret LgSearchCallback endp Entry proc Local Message:PVOID assume fs:nothing mov eax,fs:[TEB.Peb] mov eax,PEB.Ldr[eax] mov eax,PEB_LDR_DATA.InLoadOrderModuleList.Flink[eax] mov eax,LDR_DATA_TABLE_ENTRY.InLoadOrderModuleList.Flink[eax] mov ebx,LDR_DATA_TABLE_ENTRY.DllBase[eax] invoke LgSearchMessage, Ebx, addr Message test eax,eax lea ecx,Message .if Zero? Call _$_LgSearchCallback invoke LdrSearchPushReferenceInRelocationTable, Ebx, Message, Eax, Ecx .if !Eax int 3 ; В случае успеха Message будет содержать указатель в LdrpInitializeTls(). ; Далее можно найти её начало(..\Belong\Ip.asm, SearchRoutineEntry()). .endif .endif ret Entry endp end Entry Перекрёстный линк, должно быть интересно автору http://www.wasm.ru/forum/viewtopic.php?id=35894
Клерк рулит, впрочем, как и всегда. Все очень круто. > Разрушаем указатель в GlobalNameRef, предварительно > сохранив его для последующего восстановления. От себя добавлю как решил проблему восстановления указателей. Установка старшего бита заставляет систему лезть туда, куда она сама себя не пускает, выбрасывая исключение. А для восстановления оригинального значения мы просто тупо сбрасываем старший бит взад. Ничего запоминать нигде не надо. с глубоким почтением к клерку-сан мыщъх
kaspersky Выше очень обобщенное описание, подробности в аттаче. Восстанавливать не нужно, этим занимается двиг изменяя целевой сегмент посредством перезагрузки селекторов. Иначе будет не возможной обработка для нескольких потоков.
Clerk с потоками согласен. но там где их заведомо нет (или они заведомо не тронут данную ячейку) - такой способ вполне прокатывает. к тому же даже если мы не восстанавливаем ячейку, а "эмулируем" операции чтения/записи в движке, то нам надо где-то хранить оригинальное значение. почему бы не в самой переменной, установив старший бит в единицу? ЗЫ. аттач не смотрел. сейчас курить буду
kaspersky по моему слишком быстро сдался, как раз при наличие нескольких тредов его способ позволяет проще отвязаться от контекста, а для поддержки реентерабельности и подавно. С другой стороны, он не для ядра, и может быть эксплуатирован (теоретически). ЗЫ аттач тоже не смотрел. интересно для кого здесь пишет Clerk, посту более 4х месяцев и 40 скачиваний
Я уделил этой технике очень много времени и считаю её весьма кошерной. kaspersky Уже говорил что используется гибкая манипуляция селекторами, точнее подмена сегмента, база которого вычисляется как разность смещений двух сегментов. После записи в переменную никакие манипуляции с ней не выполняются. На вирустеке описан матан подробно. J0E Для ядра аналогично, там есть некоторые нюансы, но сути это не меняет. n0name Если вы поняли смысл тогда респект.
Замечание про ядро относится не оригинальной технике, а к установке старшего бита, но это мелочи. Кошерная, да, определение в точку. control flow меняется, а машинный код нет. Бороться с этим, как говорится, a real pain in the ass. Имплементация может оказаться довольно сложной не только для реализации (простите за тавтологию) но и для понимания, вот я и удивлен малым количеством скачиваний, может быть многие сами давно такое сделали и им уже не интересно?
Clerk ну когда разжевано, да еще и сорц есть, трудно не понять Немного напрягает термин "захват", всё же чаще юзается "перехват" имхо.
n0name Врятли J0E Для людей, я думаю. Clerk "Техника" весьма интересная, правда, можно еще развить. На первый взгляд - раскручивается эвристиком нода, это раз. Возможны(хотя еще проверю) всякие глюки в семерке, это два. Респект тебе, за труды. Но старайся быть впереди планеты всей, мой тебе совет - раскручивай х64 семерку, и не исчи зомбу. Того зомбы уже нет, а какой щаз - я думаю, ты не захотел бы его таким найти.
GriD К эмуляции это отношения не имеет. В w7 глюков нет. Двиг откатан на 7-ке. Могут быть проблемы с примерами, но только потому, что у меня XP и лень лезть олей в модуля от 7, в частности это касается текстовых строк.
У меня перед новым годом на свежую семерку залелез зверег, который не давал никакие дрова грузить, а еще вешал все при запуске скайп и айсику, ну т.е. даже мышь не шевелилась, буквально немного поковырялся, нашел в в одной из LoadImageNotifyRoutine что-то типа 00000001 или 00000010, и сразу вспоинил про этот пост =), а потом срочно понадобился скайп, и пришлось винду переустанавливать, жаль, что поковырять больше не удалось..
Для людей... и я думаю, кто по 2 раза качал, кто качал, но не читал, кто читал, но не понял. "пара удачных". Пол темы прально читать меж строк:-\ Суть басни не в НОДе а в концепте и подходе (:с И кстати, Зомба... 5 сек не хочу спрашивать, что с ним стало, и без того вижу, это рок >:
Виндбг не видел по ним ничего, это первое, что пришло в голову) ну всмысле в указателе на один из калбэков, или я чего-то не понял?
Velheart Я думал вы имели ввиду возвращаемое значение, а не ссылку на калбэк. У виндбг есть проблемы с отображением памяти, не удивительно если он криво чтото показал. J0E Иди флуди в другой топик. Заладил нод, нод.. да гуан ваш нод.
малява модераторам зачем клерка забанили?! выражаю решительный протест. интересный же человек. с кучей свежих идей. без него тут совсем скучно. не по дзенски это.