Тут уже была похожая тема, однако я столкнулся с такой проблемой, я пытаюсь статический TLS перенести из загружаемой проги в свой загрузчик, делаю так: Код (C): BOOL HandleTls(DWORD dwImageBase) { PIMAGE_DOS_HEADER pTargetDosHeader = (PIMAGE_DOS_HEADER)dwImageBase; PIMAGE_NT_HEADERS pTargetNtHeaders = (PIMAGE_NT_HEADERS)(dwImageBase + pTargetDosHeader->e_lfanew); if (!pTargetNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress) return FALSE; PIMAGE_TLS_DIRECTORY pTargetTlsDirectory = (PIMAGE_TLS_DIRECTORY)(dwImageBase + pTargetNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress); DWORD dwSize = pTargetTlsDirectory->EndAddressOfRawData - pTargetTlsDirectory->StartAddressOfRawData; PVOID lpTls = VirtualAlloc(NULL, dwSize, MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE); DWORD dwStartAddr = pTargetTlsDirectory->StartAddressOfRawData; memcpy(lpTls, (PVOID)dwStartAddr, dwSize); DWORD dwAddr = pTargetTlsDirectory->AddressOfIndex; DWORD dwArraySize = *(DWORD*)dwAddr; DWORD * dwArray = (DWORD*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (1 + size) * sizeof(DWORD*)); dwArray[0] = (DWORD)lpTls; _asm { mov eax, dwArray mov fs:[0x2C], eax } } Tls обрабатываю после релокации, копирую данные из адреса в поле StartAddressOfRawData в свой выделенный кусок памяти Код (C): PVOID lpTls = VirtualAlloc(NULL, dwSize, MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE); Индекс tls модуля всегда нулевой, поэтому дальше выделяю память под массив DWORD'ов, Код (C): DWORD * dwArray = (DWORD*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (1 + size) * sizeof(DWORD*)); В нулевой элемент копирую адрес lpTls , дальше меняю массив в teb->ThreadLocalStoragePointer через: Код (ASM): mov eax, dwArray mov fs:[0x2C], eax Далее запускаю цель, в ней в main вывожу через MessageBox переменную из TLS, всё нормально. Но вот когда я создаю поток в целевом приложении и в нём вывожу эту же переменную там мусор, вот код тестируемого приложения: Код (C): __declspec(thread) int var = 777; void NTAPI tls_callback(PVOID DllHandle, DWORD dwReason, PVOID) { return; } #ifdef _WIN64 #pragma comment (linker, "/INCLUDE:_tls_used") #pragma comment (linker, "/INCLUDE:tls_callback_func") #else #pragma comment (linker, "/INCLUDE:__tls_used") #pragma comment (linker, "/INCLUDE:_tls_callback_func") #endif #ifdef _WIN64 #pragma const_seg(".CRT$XLF") EXTERN_C const #else #pragma data_seg(".CRT$XLF") EXTERN_C #endif PIMAGE_TLS_CALLBACK tls_callback_func = tls_callback; #ifdef _WIN64 #pragma const_seg() #else #pragma data_seg() #endif DWORD WINAPI ThreadProc(CONST LPVOID lpParam) { char buf[16] = ""; itoa(var, buf, 10); MessageBoxA(0, buf, buf, 0); ExitThread(0); } int main(void) { char buf[16] = ""; itoa(var, buf, 10); MessageBoxA(0, buf, buf, 0); CreateThread(NULL, 0, &ThreadProc, 0, 0, NULL); Sleep(3000); return 0; } Я посмотрел в WinDbg, по бряку на запись в fs:[0x2C], когда создаётся поток функция ntdll!LdrpAllocateTls пишет в fs:[0x2C], расширяя массив, но данные с TLS туда не копируются почему-то, в итоге в переменных мусор. Как-то можно решить эту проблему? Ну кроме хука создания потока и правки руками fs:[0x2c]
Потестировал ещё, создал аналогичного размера tls секцию в лоадере, положил в переменную по тому же смещению другое число, в итоге в мейне выводит как надо, в новом потоке выводятся данные из tls секции лоадера. Откуда загрузчик заполняет данными teb->ThreadLocalStoragePointer ? Я думал сначала что он лезет в tls директорию у лоадера, но подменив в ней адреса StartAddressOfRawData/ EndAddressOfRawData я вижу что при создании потока системные функции всё равно заполняют teb->ThreadLocalStoragePointer данными с tls секции лоадера. Я так понимаю нужно где-то что-то поравить в TEB/ntdll ещё, но я в упор этого не вижу. Пока что решил проблему просто созданием в лоадере секции такого же размера как и в цели и копированием туда всего содержимого. Наверное это лучше чем хуки CreateThread всё-таки
PaperMoon, Я уже писал на кл: Код (Text): LdrpMapDllWithSectionHandle -> LdrpMapAndSnapDependency -> LdrpSnapModule -> LdrpDoPostSnapWork -> LdrpHandleTlsData -> LdrpAllocateTlsEntry Это значит что тлс настраивается при загрузке модуля.
Я понял, получается нужно будет составлять паттерн для каждой ос и искать в памяти нтдлл LdrpHandleTlsData, которая перенесёт тлс из модуля в переменную соотвествующую и настроит fs:2ch. Жаль что нельзя без этого обойтись, P.S. Я большой фанат ваших работа по антиэмуляции, могу я вам задать технический вопрос и если да где это лучше сделать в личке или прямо здесь тему создать?
PaperMoon, Нет смысла какие то переменные искать, есть общие решения. Эмулировать механизм (тлс) - зачем его исполнять непосредственно, если загрузка имитируется и не совместима с нтлдр > могу я вам задать технический вопрос и если да где это лучше сделать в личке или прямо здесь тему создать? Спросите тут, я не использую лк.
Хорошо я тут спрошу, немного не по теме конечно: 1) Я читал ваши статьи про детект атомов через Data Fetch. Правильно ли я понял, что при эмуляции некоего приложения avvm эмулирует функции используемые в импортах, меняя вызов апи на некий свой обработчик который эмулирует вызов апи, если допустим импорты отсутствуют то как я понял, по паттернам в коде можно задетекить использование той или иной апи, так как аввм имеет куски кода функций системных длл. Исходя из всего этого я накидал такое: Код (C): PCHAR lpStr = "test"; BOOL bFlag = FALSE; LONG NTAPI OnHit(PEXCEPTION_POINTERS ExceptionInfo) { UINT_PTR uBase = GetVirtualBaseByAddress(hNtdllAddr); // адрес по которому загружена ntdll DWORD dwModuleSize = GetModuleSize(uBase); CONTEXT ctx; if (ExceptionInfo->ExceptionRecord->ExceptionAddress >= uBase && ExceptionInfo->ExceptionRecord->ExceptionAddress < uBase + dwModuleSize) { GetThreadContext(hThread, &ctx); ctx.Dr0 = 0 ctx.Dr7 = 0; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; SetThreadContext(hThread, &ctx); RemoveVectoredExceptionHandler(handler); } else { GetThreadContext(hThread, &ctx); ctx.Dr0 = 0; ctx.Dr7 = 0; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; SetThreadContext(hThread, &ctx); RemoveVectoredExceptionHandler(handler); lpStr[0] = '\0'; bFlag = TRUE; } return EXCEPTION_CONTINUE_EXECUTION; } PCHAR lpStr = "test"; BOOL bFlag = FALSE; DWORD Test(PVOID pv) { HANDLE hFile = 0; hFile = OpenEvent(0,0,str); ExitThread(0); } void Check() { if (handler = AddVectoredExceptionHandler(TRUE, OnHit)) { if (hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)Test, 0, CREATE_SUSPENDED, 0)) { CONTEXT ctx; GetThreadContext(hThread, &ctx); ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; ctx.Dr3 = (ULONG_PTR)str; ctx.Dr7 = 0xF0000040; SetThreadContext(hThread, &ctx); ResumeThread(hThread); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); } } } int main() { Check(); if(bFlag){ //атом? } } Ставится хардвейр бряк на чтение из переменной lpStr, устанавливается хэндлер эксепшонов OnHit. Далее запускается поток в котором вызывается OpenEvent. Я это понял так, при нормальных условиях, я получу бряк в ntdll, при вызове OpenEvent. При выполнении OpenEvent, срабатывает бряк, вызывается эксепшон. Я проверяю в каком модуле бряк произошёл, если он в пределах Ntdll, значит всё ок. Но если оно будет проэмулировано, то бряк будет в некоем другом модуле аввм? Правильно ли я понял вашу концепцию? 2) Заодно уже) Вы упоминали на rohitab в теме http://www.rohitab.com/discuss/topic/42697-the-best-ever-method-keylogger/?p=10106007 Что есть RIT/rit-apc? Я знаю про Raw Input NtUserRegisterRawInputDevices/NtUserGetRawInputData, но что есть Rit-Apc? Я не вижу в win32k.sys Apc апи связанных с Raw Input'ом
PaperMoon, RIT - поток сырого ввода, это рекурсивный цикл ожидания сообщений от устройств ввода через APC. Это базовый цикл, который принимает запросы от мыша етц и передаёт запросы в тень(shadow). Именно там можно хорошо всториться кейлоггеру, так что не обнаружить. Но помимо этого важен есчо и виртуальный ввод данных, он на этом уровне не присутствует, например посылка сообщения окну. > Правильно ли я понял, что при эмуляции некоего приложения avvm эмулирует функции используемые в импортах Нет. Та серия статей не закончена, но они малое отношение к вм аверской имеют. Атомы это концепт, общий принцип. То что их использует вм лишь частный случай. Вы не поняли суть к сожалению, наверно я плохо умею описывать, хотя будет конечная статья обьединяющая это всё. Идея весьма хорошо проработана, если кратко - приложение запускается под dye(реалтайм разновидность визора), всё адр пространство не исполняемо. Отслеживаются все выборки данных(DF), формируется полный DFG через маркеры в памяти. Таким образом наследуются указатели и вероятные указатели(так как иногда данные не отличимы от указателя). Это делает невозможным передачу управления на произвольный адрес(классический инжект невозможен, так как ап NX) и из за валидации DFG делает практически невозможным OP-инжект(ROP/COP etc). Как следствие любой атом обнаруживается. > Правильно ли я понял, что при эмуляции некоего приложения avvm эмулирует функции используемые в импортах, меняя вызов апи на некий свой обработчик который эмулирует вызов Вы не правильно представляете всё это. Замены функций нет, просто системный код иной, чем в вм. В ней сложная обработка выделяется как атом, спец вызов, который обрабатывается не вм. При этом не происходит выборка данных по адресам аргументов, что нарушает поток данных(DFG). На счёт обработки hwb - не знаю что вы хотите сделать, но это приведёт к ошибке в вм, для этого нужно множество серьёзных тестов с аналитикой, разобрать как оно работает. Иначе поведение вм не предсказуемо.
Код весьма старый, но он мало изменялся и главное суть: Код (Text): /***************************************************************************\ * RawInputThread (RIT) * * This is the RIT. It gets low-level/raw input from the device drivers * and posts messages the appropriate queue. It gets the input via APC * calls requested by calling NtReadFile() for the keyboard and mouse * drivers. Basically it makes the first calls to Start*Read() and then * sits in an NtWaitForSingleObject() loop which allows the APC calls to * occur. * * All functions called exclusively on the RIT will have (RIT) next to * the name in the header. * * History: * 10-18-90 DavidPe Created. * 11-26-90 DavidPe Rewrote to stop using POS layer. \***************************************************************************/