Данный механизм можно успешно использовать при эскалациях. Если вы когдато использовали их, то должны хорошо понимать проблему отката состояния потока и ресурсов. Не факт что вы вобще сплоет можите заюзать, именно изза сложности отката состояния системы. Вы уже всюду видите вирмейкерские техники и аверские засады, это паранойя. Уже множество задач было описано, последний не описанный пример тут http://www.wasm.ru/forum/viewtopic.php?pid=400991#p400991. Опишу матчасть. Не следует понимать как конечное и частное решение, дальнейший пример призван показать основные принципы. Последовательность процедур при вызове InitRoutine() из CreateProcessA(): Код (Text): CreateProcessA CreateProcessInternalA CreateProcessInternalW GetProcAddress("CreateProcessAsUserSecure") LdrGetProcedureAddress LdrpGetProcedureAddress LdrpSnapThunk if !(LDR_DATA_TABLE_ENTRY.Flags & LDR_ENTRY_PROCESSED) LdrpRunInitializeRoutines LdrpCallInitRoutine InitRoutine() Причём NL(GetProcAddress) > NL(CreateProcessInternalW) и (NL(InitRoutine) - NL(CreateProcessA)) = Const. Для того чтобы создать поток остановленным и выполнить некоторые действия с процессом необходимо установить флажёк CREATE_SUSPENDED во входном параметре процедуры в которой находится вызов NtResumeThread, в данной версии это 7-й параметр CreateProcessInternalW(): Код (Text): $ test byte ptr ss:[ebp + 20],4 $+4 jnz short $+19 $+6 lea eax,dword ptr ss:[ebp-6AC] $+C push eax $+D push dword ptr ss:[ebp-67C] $+13 call NtResumeThread $+19 mov eax,dword ptr ss:[ebp-674] Так как функция может измениться и номер параметра также(например изза появления промежуточных процедур), то необходимо вычислять номер параметра и функцию динамически. Целью определения функции является определение стекового фрейма из InitRoutine() принадлежащего данной функции, в нём находятся её параметры, в частности флажки. Во первых определение смещения флажков(номер параметра функции). Эта часть кода в графе будет описана 5-ю входами(при оптимизации): Код (Text): 1 [$] 2 [$ + 4] 3 [$ + 6,C,D] 4 [$ + 13] 5 [$ + 19] Так эта ветвь не завершается, то все эти описатели будут соеденены между собой прямыми и обратными ссылками. Второй описатель будет иметь ссылку на 5-й, так он описывает ветвление. Благодаря обратным ссылкам можно трассировать граф в обратном направлении, таким образом раскрыв три обратные ссылки мы окажемся на инструкции test byte ptr ss:[ebp + 20],4 Точнее её можно представить так: test byte(dword) ptr ss:[ebp + ArgId*4 + 4],CREATE_SUSPENDED Из этой инструкции извлекаем номер параметра. Псевдокод следующий(логично и понятно): Код (Text): RE:PGRAPH_ENTRY GE = RE GE:PGRAPH_ENTRY if GE.Type = TYPE_CALL if GE.BranchAddress = @NtResumeThread GE = GE.Link.Blink if GE.Type = TYPE_LINE GE = GE.Link.Blink if GE.Type = TYPE_JXX ; if GE.BranchLink = RE.Link.Flink GE = GE.Link.Blink if Opcode(GE.Address) = (test byte(dword) ptr ss:[ebp + Disp],CREATE_SUSPENDED) ArgId = Disp/4 - 1 Теперь необходимо определить саму процедуру, тоесть фрейм принадлежащий ей. Для этого можно использовать два различных метода, статический и динамический. При статическом способе выполняется трассировка уже созданного графа и поиск в нём инструкции принадлежащей процедуре. Также можно определить адрес возврата и искать его в SFC. GPE экспортирует необходимый для этого сервис, это псевдофункция FindCallerBelongToGraph(разворачивает цепочку стековых фреймов и проверяет вхождение в граф каждого адреса возврата). Динамический способ позволяет определить начало функции на этапе создания графа. Предыдущая версия движка это не поддерживала. Для каждой инструкции ведётся список процедур в порядке их вызова. Тоесть мы извлекаем ссылку из начала списка. Затем при первом вызове InitRoutine() ищем необходимый фрейм и сохраняем его NL(он является константой). Для последующих калбэков просто извлекаем фрейм с ранее определённым NL(выполняем бактрейс из NL итераций). После определения необходимого фрейма взводим бит CREATE_SUSPENDED по смещению [ArgId*4 + 1] в фрейме и заменяем в нём адрес возврата на хэндлер(2ndDispatch()).
Изменил на пару байт. Удалил один параметр из калбэка(мешал) и добавил флажёк определяющий тип списка. Теперь задача выше решается полноценно, тоесть двиг уже полноценный фактически.
Опишу семпл. Непосредственно процедура Main: Код (Text): $PsName CHAR "d:\windows\system32\calc.exe",0 $Fn CHAR "Ip: 0x%p, Flg: 0x%x", CR, LF, 0 Ep proc Local Snapshot:GP_SNAPSHOT Local GpSize:ULONG Local OldProtect:ULONG Local StartupInfo:STARTUPINFO Local ProcessInfo:PROCESS_INFORMATION mov Snapshot.GpBase,NULL mov GpSize,1000H * X86_PAGE_SIZE invoke ZwAllocateVirtualMemory, NtCurrentProcess, addr Snapshot.GpBase, 0, addr GpSize, MEM_COMMIT, PAGE_READWRITE mov ebx,Snapshot.GpBase %NTERR add Snapshot.GpBase,0FFFH * X86_PAGE_SIZE mov GpSize,X86_PAGE_SIZE invoke ZwProtectVirtualMemory, NtCurrentProcess, addr Snapshot.GpBase, addr GpSize, PAGE_NOACCESS, addr OldProtect %NTERR mov Snapshot.GpLimit,ebx mov Snapshot.GpBase,ebx lea ecx,Snapshot.GpLimit push eax push eax push 1234H push offset PARSE_CALLBACK_ROUTINE push eax push 8 push GCBE_PARSE_DISCLOSURE push ecx push dword ptr [_imp__CreateProcessA] %GPCALL GP_PARSE .if !Eax mov eax,STATUS_NOT_FOUND Int 3 .elseif Eax != STATUS_WAIT_1 Int 3 .endif .if !StAddress Int 3 .endif invoke DbgPrint, addr $Fn, StAddress, StFlagsOffset invoke LdrSetDllManifestProber, offset LdrpManifestProberRoutine invoke GetStartupInfo, addr StartupInfo ; !CREATE_SUSPENDED invoke CreateProcess, addr $PsName, NULL, NULL, NULL, FALSE, 0, NULL, NULL, addr StartupInfo, addr ProcessInfo %APIERR invoke Sleep, 3000 invoke ZwResumeThread, ProcessInfo.ThreadHandle, NULL %NTERR ret Ep endp Вместо InitRoutine() используется LdrpManifestProberRoutine, по сути разницы нет. Вызывается конструктор, затем регается калбэк и создаётся процесс, причём не с остановленным потоком и должен сразу начать исполняться. На построение и разбор графа уходит малое время, не измеримое через системные тики(t < 64ms), так как построение завершается принудительно. Его цель - определение адреса возврата(var StAddress) из внутренней функции в которой находится ZwResumeThread и смещение(var StFlagsOffset) флажков в фрейме принадлежащем процедуре(номер параметра). Конструктор на каждой итерации вызывает калбэк PARSE_CALLBACK_ROUTINE, вот он: Код (Text): ; o !GCBE_PARSE_SEPARATE ; o !GCBE_PARSE_OPENLIST ; PARSE_CALLBACK_ROUTINE proc uses ebx Graph:PVOID, ; Ссылка на граф. GraphEntry:PVOID, ; Ссылка на описатель инструкции. SubsList:PVOID, ; Список описателей входов процедур в порядке вызова. SubsCount:ULONG, ; Число процедур в списке является уровнем вложенности(NL). PreOrPost:BOOLEAN, ; Тип вызова. Context:PVOID ; Def. Flags offset. mov ebx,GraphEntry mov eax,dword ptr [ebx + EhEntryType] and eax,TYPE_MASK cmp eax,HEADER_TYPE_CALL jne @f ; (!BRANCH_DEFINED_FLAG) mov eax,dword ptr [ebx + EhAddress] cmp word ptr [eax],15FFH jne @f mov eax,dword ptr [eax + 2] mov eax,dword ptr [eax] cmp dword ptr [_imp__ZwResumeThread],eax jne @f mov ebx,dword ptr [ebx + EhBlink] and ebx,NOT(TYPE_MASK) jz @f test dword ptr [ebx + EhEntryType],TYPE_MASK jnz @f mov ebx,dword ptr [ebx + EhBlink] and ebx,NOT(TYPE_MASK) jz @f mov eax,dword ptr [ebx + EhEntryType] and eax,TYPE_MASK cmp eax,HEADER_TYPE_JXX jne @f mov ebx,dword ptr [ebx + EhBlink] and ebx,NOT(TYPE_MASK) jz @f mov ebx,dword ptr [ebx + EhAddress] cmp byte ptr [ebx + 1],45H jne @f movzx eax,byte ptr [ebx] sub eax,0F6H jb @f .if Zero? movzx eax,byte ptr [ebx + 3] .else dec eax jnz @f mov eax,dword ptr [ebx + 3] .endif cmp eax,CREATE_SUSPENDED ; 4 jne @f movzx eax,byte ptr [ebx + 2] cmp eax,4 jna @f mov StFlagsOffset,eax ; Def. STACK_FRAME.Ip mov ecx,SubsList cmp SubsCount,2 jb @f mov ecx,dword ptr [ecx] ; PCALL_HEADER mov ecx,dword ptr [ecx + EhFlink] and ecx,NOT(TYPE_MASK) jz @f mov ecx,dword ptr [ecx + EhAddress] mov eax,STATUS_WAIT_1 mov StAddress,ecx jmp Exit @@: xor eax,eax Exit: ret PARSE_CALLBACK_ROUTINE endp Псевдокод его в #9. Он находит StAddress и StFlagsOffset, после чего прерывает дальнейший анализ. Далее граф не нужен и может быть удалён. Вторичный хэндлер: Код (Text): .data StFlagsOffset ULONG ? StAddress PVOID ? .code $Ch CHAR "2nd'f called..", CR, LF, 0 Fn2ndDispatch proc C pushad invoke DbgPrint, addr $Ch popad jmp StAddress Fn2ndDispatch endp TbStackBase equ 4 TbStackLimit equ 8 $Ld CHAR "SFC Frame: 0x%p", CR, LF, 0 assume fs:nothing LdrpManifestProberRoutine proc DllBase:PVOID, FullDllPath:PCWSTR, ActivationContext:PVOID mov eax,STACK_FRAME.Next[ebp] mov ecx,StAddress assume eax:PSTACK_FRAME @@: cmp eax,-1 je @f cmp fs:[TbStackBase],eax jna @f cmp fs:[TbStackLimit],eax ja @f cmp [eax].Ip,ecx je Load mov eax,[eax].Next jmp @b Load: mov ecx,StFlagsOffset mov [eax].Ip,offset Fn2ndDispatch or dword ptr [eax + ecx],CREATE_SUSPENDED invoke DbgPrint, addr $Ld, Eax @@: xor eax,eax ret LdrpManifestProberRoutine endp Гдето в глубине целевой функции вызывается этот загрузочный калбэк, оттуда он вызывается не имеет значения. Он выполняет бактрейс и находит фрейм с известным адресом возврата StAddress найденным ранее. После этого загружает в фрейм адрес возврата на Fn2ndDispatch, это стаб и взводит в фрейме по определённому ранее смещению(StFlagsOffset) флажёк CREATE_SUSPENDED. Далее управление отдаётся, при возврате из целевой функции получает управление вторичный хэндлер. Он заглушка и ничего не делает кроме вывода сообщения. Доп. макро: Код (Text): %NTERR macro .if Eax Int 3 .endif endm %APIERR macro .if !Eax Int 3 .endif endm CR equ 13 LF equ 10 После отработки CreateProcess() поток будет остановлен, выполняется задержка и его ресум. В результате лог:
Clerk, поясните, пожалуйста, мне, как нубу и школоте, что дает такая технология (построение графа инструкций) и как ее можно использовать (сейчас и в перспективе)? Спасибо. ps. На Вашем сайте log.txt отдает 404
user1 Это мощная технология, в дальнейшем позволит вам решать задачи, которые никто решить не может, тоесть иного оптимального пути решения не существует. Например: o Поиск процедуры по имени сообщения. Код (Text): PARSE_CALLBACK_ROUTINE(GE:PGRAPH_ENTRY, CallList:PCALL_ENTRY): if GE.Type = TYPE_CALL if GE.BranchAddress = @DbgPrint() GE = GE.Blink if GE.Type = TYPE_LINE if BYTE[GE.Address] = 0x68 if StrCmp(TargetMessage, DWORD[GE.Address + 1]) Routine = CallList[0] Напр.(используется только часть строки): Код (Text): "LDR: Tls Found in %wZ at %p" -> LdrpInitializeTls() "[%x,%x] LDR: Real INIT LIST for process %wZ pid %u 0x%x" -> LdrpRunInitializeRoutines() "LDR: Tls Callbacks Found. Imagebase %p Tls %p CallBacks %p" -> LdrpCallTlsInitializers() "Failed to initialize a new segment (%x)" -> RtlpInitializeHeapSegment() Это всевозможные перехваты(не то фуфло с патчами, а нормальные способы), поиск переменных, анализ кода и пр. Весь васм должен признать что задачи подобные описанным выше решить не способен. С моим сайтом сейчас траблы, точнее с хостом. Они не могут исправить проблемы, либо не хотят.
Clerk, спасибо за ответ. Тем не менее, много непонятного. Исходники красивые, везде комменты, тем не менее, читать асм тяжелее, чем VC. вопрос, почему Вы не пишите сразу все на VC со всеми настройками по уменьшению размера файла, в итоге получится тоже небольшие файлики, зато повысится удобство чтения (и редактирования) кода плюс переносимость. Вы меня конечно извините, но Вы не могли бы добавить более полную документацию к Вашей технологии. То, что сейчас есть в архиве (файл help.txt) достаточно сложен для понимания, если не вникал до этого в исходники, либо предполагается, что надо садиться и внимательно читать все исходники. Из представленной документации мне, как профессиональному нубу, не совсем понятны исчерпывающие возможности движка. Из названия технологии (построение графа) я сначала думал, Вы берете прогу, и запускаете под своим отладчиком, потом все значения регистров в каждом eip сохраняете в список, которым можете манипулировать в последствии. Но, насколько я понял, речь идет о более серьезной технологии. Был бы Вам очень признателен за более подробную документацию по возможностям движка, тк направление очень интересное, в котором Вы работаете. В любом случае, спасибо, что проводите исследования в таких направлениях, в которые не многие работают, и уже тем более спасибо, что делитесь своими проектами (исходниками) и идеями. Вы упомянули перехваты, если речь идет не о сплайсинге кода, значит Вы подменяете какие-то элементы в секции данных? (или что Вы имели ввиду?). Спасибо.
user1 У вас очень обширные вопросы, я не могу кратко ответить. Если есть вопросы по движку, то я обьясню.
Видимо, движок настолько безапеляционно крут, что даже сам клерк не знает, для чего он нужен и что он умеет Сосредоточьтесь
MSoft Уже много описал, вы не я и если не знаете, то читайте. Кстате вы тоже там про крипторы в теме отписались вроде, так мб скажите как найти две функи чтоб тлс заюзать - LdrpInitializeTls() & LdrpAllocateTls() ? У меня очень просто(опорная LdrInitializeThunk()), сейчас проверил: Код (Text): .data NtBase PVOID ? NtLimit PVOID ? gRef1 PCALL_HEADER ? gRef2 PCALL_HEADER ? .code $Fn1 CHAR "Tls Found in %wZ",0 ; LdrpInitializeTls() $Fn2 CHAR "TlsVector %x",0 ; LdrpAllocateTls() ; o GCBE_PARSE_OPENLIST ; PARSE_CALLBACK_ROUTINE proc uses ebx Graph:PVOID, GraphEntry:PVOID, SubsList:PVOID, SubsCount:ULONG, PreOrPost:BOOLEAN, Context:PVOID mov ebx,GraphEntry mov eax,dword ptr [ebx + EhEntryType] and eax,TYPE_MASK cmp eax,HEADER_TYPE_CALL jne @f ; (!BRANCH_DEFINED_FLAG) mov ecx,dword ptr [ebx + EhAddress] cmp byte ptr [ecx],0E8H jne @f mov eax,dword ptr [ebx + EhBranchAddress] cmp dword ptr [_imp__DbgPrint],eax jne @f cmp byte ptr [ecx - 5],68H jne @f mov ebx,dword ptr [ecx - 4] cmp NtBase,ebx jnb @f cmp NtLimit,ebx jbe @f cmp gRef1,NULL jne Check2 invoke InString, 1, Ebx, addr $Fn1 test eax,eax jle Check2 mov eax,SubsList mov eax,dword ptr [eax] mov gRef1,eax @@: xor eax,eax Exit: ret xBreak: mov eax,STATUS_WAIT_1 jmp Exit Check2: cmp gRef2,NULL jne xBreak invoke InString, 1, Ebx, addr $Fn2 test eax,eax jle @b mov eax,SubsList mov eax,dword ptr [eax] mov gRef2,eax jmp @b PARSE_CALLBACK_ROUTINE endp
Т.е. твой движок умеет пролистывать список адресов возврата, анализировать вызванные функции и по тем строкам, к которым идет обращение, движок может найти адрес нужной функции? Т.е. суть - найти строку, характерную для определенной функции?
Да было б чудно, если б кто-то из форумчан, кто применил сие чудо, написал бы статейку по использованию, полученному результату и т.д. И да, на си для ленивых было б классно
spa Не юзал бы ты си, то мог бы решать такие задачи http://www.wasm.ru/forum/viewtopic.php?pid=403376, а так..
spa Не всё бред, что вам не понятно. Вот простая задача http://www.wasm.ru/forum/viewtopic.php?pid=403996, собственно нужно сформировать трап фрейм. Как решать будем ? Отношение к сабжу имеет прямое - необходимо скопировать часть кода начиная с хэндлера сискала до инструкции размаскирующей прерывания. Все паразитные ветвления за пределы кода присекаются если обнаружена инструкция перезагружающая селекторы. В остальном тривиально, граф компилится. Ну разумеется нужно немного допилить, введя описатели ветвлений, дабы они стали псевдокодом и конструктор не использовал непосредственно их опкоды: Код (Text): JCC_SHORT_OPCODE_BASE equ 70H JCC_NEAR_OPCODE_BASE equ 80H JCC_O equ 0 ; OF JCC_NO equ 1 ; !OF JCC_C equ 2 ; CF JCC_B equ 2 ; CF JCC_NAE equ 2 ; CF JCC_NC equ 3 ; !CF JCC_NB equ 3 ; !CF JCC_AE equ 3 ; !CF JCC_Z equ 4 ; ZF JCC_E equ 4 ; ZF JCC_NZ equ 5 ; !ZF JCC_NE equ 5 ; !ZF JCC_NA equ 6 ; CF | ZF JCC_BE equ 6 ; CF | ZF JCC_A equ 7 ; !CF & !ZF JCC_NBE equ 7 ; !CF & !ZF JCC_S equ 8 ; SF JCC_NS equ 9 ; !SF JCC_P equ 0AH ; PF JCC_PE equ 0AH ; PF JCC_NP equ 0BH ; !PF JCC_PO equ 0BH ; !PF JCC_L equ 0CH ; SF != OF JCC_NGE equ 0CH ; SF != OF JCC_NL equ 0DH ; SF = OF JCC_GE equ 0DH ; SF = OF JCC_NG equ 0EH ; ZF | (SF != OF) JCC_LE equ 0EH ; ZF | (SF != OF) JCC_G equ 0FH ; !ZF & (SF = OF) JCC_NLE equ 0FH ; !ZF & (SF = OF) ; o Jump short: 0x70 + JCC_* ; o Jump near: 0x0F 0x80 + JCC_* Без оптимизации собирается на коленке за пять минут. Предложите своё решение или снова идите лесом.