хай всем! помогите плз кароч реализовал движок для сплайсинга с помощью дизассемблера длин от z0mbie. в общем делаю так из драйвера: __asm { cli mov eax,cr0 mov CR0Reg,eax and eax,0xFFFEFFFF mov cr0,eax } тут делаю патчинг первых N байт функции (как правило 12-17 байт, тк я ещё передаю в стек адрес функции оригинала), которые переношу в отдельный буфер, после них jmp на продолжение фунции-оригинала. __asm { mov eax,CR0Reg mov cr0,eax sti } перехватываю функции работы с реестром. в вмвари и на системниках где тестил - всё окей. НО, обнаружил что на довольно многих машинах где реестр активно используется происходит бсод. в минидампах я вижу CODE_CORRUPTION, разрушенный стек и тп. читал тут на форуме что каким-то образом можно обезопасить себя от патчинга выполняемого в данный момент кода, но найденные ссылки на инфу оказались дохлыми. подскажите плиз где об этом можно почитать подробнее. сенкс
сплайсинг, вообще, штука очень опасная. если у тебя выполнение кода прервется в момент входа в пролог перехватываемой функций, произойдет переключение потоков и твой поток изуродует пролог, то когда первый поток восстановится, будет БСоД. Самое безопасное будет ставить сплайсинг 1 раз при загрузке компа и не снимать его потом вообще. Тогда вероятность падения минимальна. Это так, к слову. А по теме - ну покажи анализ хотя бы одного бсода.
ну собственно я один раз и ставлю при загрузке. вот пример анализа (тут test.sys мой модуль, к explorer-у никакого отношения не имеет): UNEXPECTED_KERNEL_MODE_TRAP_M (1000007f) ... Arguments: Arg1: 00000008, EXCEPTION_DOUBLE_FAULT Arg2: 80042000 Arg3: 00000000 Arg4: 00000000 Debugging Details: ------------------ BUGCHECK_STR: 0x7f_8 DEFAULT_BUCKET_ID: CODE_CORRUPTION PROCESS_NAME: explorer.exe LAST_CONTROL_TRANSFER: from 00000000 to 804dd9a8 STACK_TEXT: a9d60000 00000000 00000000 00000000 00000000 nt!KiFastCallEntry+0x105 STACK_COMMAND: kb CHKIMG_EXTENSION: !chkimg -lo 50 -d !nt !chkimg -lo 50 -d !nt 8058f60d-8058f613 7 bytes - nt!NtEnumerateValueKey [ 6a 54 68 68 c7 4f 80:e9 8a 65 d5 29 90 90 ] 7 errors : !nt (8058f60d-8058f613) MODULE_NAME: test IMAGE_NAME: test.sys DEBUG_FLR_IMAGE_TIMESTAMP: 462ca159 FOLLOWUP_NAME: MachineOwner MEMORY_CORRUPTOR: PATCH_test FAILURE_BUCKET_ID: MEMORY_CORRUPTION_PATCH_test BUCKET_ID: MEMORY_CORRUPTION_PATCH_test Followup: MachineOwner --------- eax=00000000 ebx=8058f60d ecx=f8e77b98 edx=00000000 esi=00d0f4b0 edi=a9d67d64 eip=804dd9a8 esp=a9d60000 ebp=a9d60000 iopl=0 nv up ei ng nz na pe nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010286 nt!KiFastCallEntry+0x105: 804dd9a8 8b553c mov edx,dword ptr [ebp+3Ch] ss:0010:a9d6003c=???????? ChildEBP RetAddr Args to Child a9d60000 00000000 00000000 00000000 00000000 nt!KiFastCallEntry+0x105 (FPO: [0,0] TrapFrame-EDITED @ 00000000) -------------------------------- по большей части дампы получаются разные, MEMORY_CORRUPTION_PATCH есть везде.
gilg по этому поводу к сожалению ниче не могу сказать. Great вот код, по большей части взят был из blackrevealer-a by Cr4sh, немного модифицирован: void GenJmp(ULONG To, ULONG From) { InsertDword(From + 1, To - From - 5); // dst - src - 5 InsertByte(From, ASMJMP); // jmp ... } void GenJmpParam( ULONG To, ULONG From, ULONG Param) { InsertByte(From, 0x58); /* pop eax */ InsertByte(From+1, 0x68); /* push pchars */ InsertDword(From+2,(ULONG)Param); InsertByte(From+6, 0x50); /* push eax */ InsertByte(From+7, 0xe9); /* jmp new_fn */ InsertDword(From+8, To - From - 12); } // set hook // OrigFuncInParam - если TRUE, то для NewFunc в виде первого параметра будет передаваться // указатель на оригинальную функцию, это не годится для FASTCALL функций VOID Hook(PVOID Addr, PVOID NewFunc, BOOL OrigFuncInParam, PVOID *OutCallGate) { ULONG CR0Reg; PVOID CallGate, Inst = Addr; ULONG Size = 0, CollectedSpace = 0, CallGateSize = 0; ULONG NeedSpace; if (Addr == NULL) { DbgPrint("BUG, tried to HOOK 0 addr"); return 0; } NeedSpace = (OrigFuncInParam) ? SIZEOFJUMPPARAM : SIZEOFJUMP; __asm { cli mov eax,cr0 mov CR0Reg,eax and eax,0xFFFEFFFF mov cr0,eax } while (CollectedSpace < NeedSpace) { GetInstLenght(Inst, &Size); (ULONG)Inst += Size; CollectedSpace += Size; } CallGateSize = CollectedSpace + SIZEOFJUMP; CallGate = (PVOID)ExAllocatePool(NonPagedPool, CallGateSize); if ( OutCallGate) { *OutCallGate = CallGate; } DbgPrint("Function addr : 0x%.8x\n", Addr); DbgPrint("Handler addr : 0x%.8x\n", NewFunc); DbgPrint("CallGate at : 0x%.8x; size : %d\n", CallGate, CallGateSize); memset(CallGate, ASMNOP, CallGateSize); memcpy(CallGate, Addr, CollectedSpace); memset(Addr, ASMNOP, CollectedSpace); GenJmp((ULONG)Addr + NeedSpace, (ULONG)CallGate + CollectedSpace); if ( OrigFuncInParam ) { GenJmpParam((ULONG)NewFunc, (ULONG)Addr, CallGate); } else { GenJmp((ULONG)NewFunc, (ULONG)Addr); } __asm { mov eax,CR0Reg mov cr0,eax sti } return CallGate; }
Или HyperThreading или многоядерность. БСОД происходит не сразу и хук успевать поработать? в cr0 ты только отрубаешь защиту от записи. Хук часто снимается?
im1111 бсод похоже что происходит сразу, по крайней мере я это наблюдал один раз у себя на вмвари на двух ядерном проце (два ядра на вмварь выделено). в cr0 снимаю только защиту от записи. хук ставится при загрузке и не снимается более до выгрузки. но есть ещё одно но, похоже что на некоторых машинах бсод происходит не сразу, драйвер успевает поработать несколько минут. опять же в минидампах вижу то что я выше писал. этому объяснение найти пока что не могу.
Исходник дать можешь? Вот еще момент: То есть по смещению? "To - From - 5" может "From - To - 5" (извините если гоню, не спал долго =) ) Может использовать прямую адресацию?
Попробуй так (сместив сброс WP и cli после ExAllocatePool): __asm { cli mov eax,cr0 mov CR0Reg,eax and eax,0xFFFEFFFF mov cr0,eax } memset(CallGate, ASMNOP, CallGateSize); memcpy(CallGate, Addr, CollectedSpace); memset(Addr, ASMNOP, CollectedSpace); GenJmp((ULONG)Addr + NeedSpace, (ULONG)CallGate + CollectedSpace); if ( OrigFuncInParam ) { GenJmpParam((ULONG)NewFunc, (ULONG)Addr, CallGate); } else { GenJmp((ULONG)NewFunc, (ULONG)Addr); } __asm { mov eax,CR0Reg mov cr0,eax sti }
в общем реализовал я остановку всех процессоров с помощью DPC. стало гораздо лучше, но на отдельных машинах всё равно вылазит эта хрень с MEMORY_CORRUPTION.
vlaman В большинстве случаев полезно штудировать матчасть и думать головой, интернет тут нужен постольку-поскольку. Если это гипертрейдинг то заблокировать все процессоры поднявшись на dispatch уровень там и делать своё дело. Кроме этого неплохо было бы изьять контексты потоков и проверить их на опасное место.
k3internal ну вот я сейчас перехожу в dispatch_level, делаю dpc на всех процессорах кроме текущего которые гоняют nop пока я не закончу патчинг. вот насчет контекстов надо поглядеть, сенкс.
Код (Text): #pragma pack (push, 1) typedef struct _FAR_JMP{ UCHAR Opcode1; PVOID Address; UCHAR Opcode2; UCHAR Opcode3; UCHAR Opcode4; } FAR_JMP, *PFAR_JMP; #pragma pack (pop) Код (Text): void __declspec(naked) OrigCode(void) //резервируем место для замененного участка оригинального кода (чтобы не трогать пул) { __asm { mov eax, eax mov eax, eax mov eax, eax mov eax, eax mov eax, eax mov eax, eax mov eax, eax mov eax, eax mov eax, eax mov eax, eax } } //============ поскипано ============ кусок функции Код (Text): PFAR_JMP Fjmp; FAR_JMP FarJmpCode; //Length - длина участка кода, который будет заменен переходником, его следует определять дизассемблером длин //Length должно быть меньше, чем длина функции OrigCode UCHAR i; __asm { mov eax, cr0 mov CR0Reg,eax and eax,0xFFFEFFFF // сбросить WP bit mov cr0, eax } for(i=0; i<Length; i++) //копируем начальный участок кода функции в зарезервированное место нашего драйвера (void __declspec(naked) OrigCode(void)) { *(PUCHAR)((ULONG)OrigCode+i) = *(PUCHAR)((ULONG)OrigFuncAddress+i); } Fjmp = (PFAR_JMP)((ULONG)OrigCode + i); // делаем переходник на продолжение в оригинальную функцию Fjmp->Opcode1 = 0x68; Fjmp->Address = pNextOpcode; // pNextOpcode - указатель на следующую инструкцию, после участка длиной Length Fjmp->Opcode2 = 0xC3; Fjmp = &FarJmpCode; // далее формируем 8байт кода, который впишется в начало перехватываемой функции (Relative Jmp) RtlCopyMemory(Fjmp, OrigFuncAddress, sizeof(FAR_JMP)); Fjmp->Opcode1 = 0xE9; Fjmp->Address = (PVOID)((ULONG)NewFunction-(ULONG)OrigFuncAddress-5); //на этот момент времени, если наш поток прервется ничего страшного не произойдет __asm { mov esi, OrigFuncAddress mov edi, Fjmp mov eax, [esi] mov edx, [esi+4] mov ebx, [edi] mov ecx, [edi+4] lock CMPXCHG8B [esi] // здесь происходит вписывание джампа в одну инструкицю с залочиванием шины процессора mov eax, CR0Reg mov cr0, eax // востановить содержимое CR0 } И не забываем в обработчике перехваченной функции делать джамп на сохраненный участок начала функции, и еще не забываем про возможные команды с Relative Offset, это просто для примера я привел
Deyton ну а как быть если один из потоков в момент патчинга находится гденить в середине кода, который патчим?
vlaman это недостаток сплайсинга, как вариант можно сплайсить инструкцию длиной от 5 байт, ну или парсить контексты на предмет наличия EIP в этом месте. Но могу заверить, что вероятность этого ничтожно мала, во всяком случае мне не удалось получить BSOD именно по этой приячине, сколько я не пробовал. Кстати в начале функций частенько стоит 5ти байтный push хххххххх
Deyton ну, допустим, есть атомарность патчинга, но где гарантия что в 8 байт укладывается целое число команд?
asmfan 1) Из 8 байт реально патчится только 5 (посмотри внимательно) 2) дизассемблером длин ты получаешь указатель на _начало_ следующей инструкции после джампа до того как его вписываешь. если там останется мусор от какой-то инструкции на него управление не попадет после джампа.
т.е. Length должна быть больше либо равно 8 иначе Код (Text): lock CMPXCHG8B [esi] затирается безвозвратно (8 - Length) байт или я не прав?
НЕТ Например, было: -------------------------------- 80000000 mov edi, edi (8B FF) 80000002 push ebp (55) 80000003 mov ebp, esp (8B EC) 80000005 sub esp, 48 (83 EC 48) ..... -------------------------------- станет 80000000 jmp MyFunc 80000005 sub esp, 48 -------------------------------- MyFunc: ...... ...... jmp OrigCode -------------------------------- OrigCode: mov edi, edi push ebp mov ebp, esp push 80000005 ret -------------------------------- pNextOpcode = 80000005 В эти 8 байт копируется начало функции, до патча, а потом только первые 5 байт заменяются джампом, и вписывается в начало через lock CMPXCHG8B [esi].