Есть некий процесс A.EXE, который импортирует и использует функцию msvcrt!memset - Мне нужно перехватить вызов этой функции и специальным образом заполнять буффер. Решил воспользоватся методом перехвата IAT, описанным в книжке "Rootkits:subverting the Windows Kernel" - смешанный подход к захвату: Код (Text): NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath) { NTSTATUS ntStatus; gb_Hooked = FALSE; // We have not hooked yet ntStatus = PsSetLoadImageNotifyRoutine(MyImageLoadNotify); return ntStatus; } ///////////////////////////////////////////////////////// // MyImageLoadNotify gets called when an image is loaded // into kernel or user space. At this point, you could // filter your hook based on ProcessId or on the name of // of the image. VOID MyImageLoadNotify(IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId, // Process where image is mapped IN PIMAGE_INFO ImageInfo) { // UNICODE_STRING u_targetDLL; DbgPrint("Image name: %ws\n", FullImageName->Buffer); // Setup the name of the DLL to target // RtlInitUnicodeString(&u_targetDLL, L"\\WINDOWS\\system32\\kernel32.dll"); // if (RtlCompareUnicodeString(FullImageName, &u_targetDLL, TRUE) == 0) // { HookImportsOfImage(ImageInfo->ImageBase, ProcessId); // } } NTSTATUS HookImportsOfImage(PIMAGE_DOS_HEADER image_addr, HANDLE h_proc) { PIMAGE_DOS_HEADER dosHeader; PIMAGE_NT_HEADERS pNTHeader; PIMAGE_IMPORT_DESCRIPTOR importDesc; PIMAGE_IMPORT_BY_NAME p_ibn; DWORD importsStartRVA; PDWORD pd_IAT, pd_INTO; int count, index; char *dll_name = NULL; char *pc_dlltar = "kernel32.dll"; char *pc_fnctar = "GetProcAddress"; PMDL p_mdl; PDWORD MappedImTable; DWORD d_sharedM = 0x7ffe0800; DWORD d_sharedK = 0xffdf0800; // Little detour unsigned char new_code[] = { 0x90, // NOP make INT 3 to see 0xb8, 0xff, 0xff, 0xff, 0xff, // mov eax, 0xffffffff 0xff, 0xe0 // jmp eax }; dosHeader = (PIMAGE_DOS_HEADER) image_addr; pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader, dosHeader->e_lfanew ); // First, verify that the e_lfanew field gave us a reasonable // pointer, then verify the PE signature. if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE ) return STATUS_INVALID_IMAGE_FORMAT; importsStartRVA = pNTHeader->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; if (!importsStartRVA) return STATUS_INVALID_IMAGE_FORMAT; importDesc = (PIMAGE_IMPORT_DESCRIPTOR) (importsStartRVA + (DWORD) dosHeader); for (count = 0; importDesc[count].Characteristics != 0; count++) { dll_name = (char*) (importDesc[count].Name + (DWORD) dosHeader); pd_IAT = (PDWORD)(((DWORD) dosHeader) + (DWORD)importDesc[count].FirstThunk); pd_INTO = (PDWORD)(((DWORD) dosHeader) + (DWORD)importDesc[count].OriginalFirstThunk); for (index = 0; pd_IAT[index] != 0; index++) { // If this is an import by ordinal the high // bit is set if ((pd_INTO[index] & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG) { p_ibn = (PIMAGE_IMPORT_BY_NAME)(pd_INTO[index]+((DWORD) dosHeader)); if ((_stricmp(dll_name, pc_dlltar) == 0) && \ (strcmp(p_ibn->Name, pc_fnctar) == 0)) { //DbgPrint("Imports from DLL: %s", dll_name); //DbgPrint(" Name: %s Address: %x\n", p_ibn->Name, pd_IAT[index]); // Use the trick you already learned to map a different // virtual address to the same physical page so no // permission problems. // // Map the memory into our domain so we can change the permissions on the MDL p_mdl = MmCreateMdl(NULL, &pd_IAT[index], 4); if(!p_mdl) return STATUS_UNSUCCESSFUL; MmBuildMdlForNonPagedPool(p_mdl); // Change the flags of the MDL p_mdl->MdlFlags = p_mdl->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA; MappedImTable = MmMapLockedPages(p_mdl, KernelMode); if (!gb_Hooked) { // Writing the raw opcodes to memory // used a kernel address that gets mapped // into the address space of all processes // thanks to Barnaby Jack RtlCopyMemory((PVOID)d_sharedK, new_code, 8); RtlCopyMemory((PVOID)(d_sharedK+2),(PVOID)&pd_IAT[index], 4); gb_Hooked = TRUE; } // Offset to the "new function" *MappedImTable = d_sharedM; // Free MDL MmUnmapLockedPages(MappedImTable, p_mdl); IoFreeMdl(p_mdl); } } } } return STATUS_SUCCESS; } Проблема в том, что система настраивает импорты уже ПОСЛЕ того как моя функция отработала - соответсвтенно я ставлю хуки, а система потом их затирает. Кто нибудь сталкивался с подобной задачей, если да, то как решили ее?
как вариант подготовить все вещи(в плане права доступа к памяти), затем перебить в PE заголовке EntryPoint для DLL. И как тока система всё загрузит и передаст управление на точку входа в DLL то ты поставишь нужные хуки куда надо, ну и потом передашь управление на оригинальную точку входа
ntcdm Импорт пользовательских модулей настраивается юзермодным лодером. При нотификации вы имеете только проекцию модуля. Все остальные действия с этой проекцией выполнит поток вернувшись в U-mode. Это очень плохой способ. Темболее патч. Решением может стать настройка импорта вручную(правка проекции эквивалентна изменению файла), таким образом, дабы после отработки загрузчика в импорте были адреса ваших хэндлеров.
Спасибо за подсказки. Я кстати тем временем пробовал похукать функцию memset через сплайс, так модуль msvcrt.dll во всех процессах поменялся (не на диске), естественно меня это не устраивает slesh да, это вариант, я до такого не додумался, правда конечно придется городить огород, ведь не так-то просто скопировать свой кодес из кернел мода в юзер мод так, чтобы обрабатывались корректно допустимы ссылки на данные или импорты библиотек. Пока в юзермоде я реализовал только свою функцию-трамплин. Clerk Скажи, а как можно настроить импорт вручную при загрузке чтобы там оказались адреса моих обработчиков? разве до этого я не делал то же самое? И что значит правка проекции эквивалентна изменению файла? что, файл на диске будет меняться если я в проекцию пишу? Может быть более подробно просветишь меня.
ntcdm Нужно делать это также как с файлом. Есть ли возможность изменить модуль на диске таким образом, дабы после загрузки его в IAT оказался ваш адрес ? Если биндинг то да, в остальных типах импорта это весьма проблемно.
самое простое что приходит в голову - изготовить мою Дллку в которой сделать функции-переходники кроме одной нужной мне, которую выполнить как фильтр. Затем при загрузке файла менять имя Длл-ки на мое. Но может быть есть какой-то простой способ, например чтобы лоадер не затирал импорт адресом импортирвемой функции при загрузке?
ntcdm Есть эффективные способы связанные с исключениями. Но их не хотят юзать, так как для получения некоторых переменных необходимо выполнить парсинг ядра, который якобы считается не стабильным, вдобавок написать трап-процессинг для многих не представляется возможным, хотя там ничего сложного нет. Если разбить модуль(сделать инвалидными какието ссылки в нём), то при использовании их будут возникать исключения которые нужно откатать. При этом будут доступен целевой системный рантайм. Тоесть останов в теле лодера. Этого можно достичь также различными манипуляциями с загрузчиком(например фильтрация отладочного вывода http://www.wasm.ru/forum/viewtopic.php?id=38310, при этом отладочный стаб получает инфы более чем достаточно. Либо это перехват любой точки близко расположенной к коду настраивающему импорт(LdrpWalkImportDescriptor). upd.: Посмотрел. Происходит следующее: Код (Text): typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; typedef struct _IMAGE_THUNK_DATA { union { PBYTE ForwarderString; PDWORD Function; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; } u1; } IMAGE_THUNK_DATA; typedef IMAGE_THUNK_DATA * PIMAGE_THUNK_DATA; Код (Text): NTSTATUS LdrpSnapThunk ( IN PVOID DllBase, IN PVOID ImageBase, IN PIMAGE_THUNK_DATA OriginalThunk, IN OUT PIMAGE_THUNK_DATA Thunk, IN PIMAGE_EXPORT_DIRECTORY ExportDirectory, IN ULONG ExportSize, IN BOOLEAN StaticSnap, IN PSZ DllName ) /*++ Routine Description: This function snaps a thunk using the specified Export Section data. If the section data does not support the thunk, then the thunk is partially snapped (Dll field is still non-null, but snap address is set). Arguments: DllBase - Base of Dll. ImageBase - Base of image that contains the thunks to snap. Thunk - On input, supplies the thunk to snap. When successfully snapped, the function field is set to point to the address in the DLL, and the DLL field is set to NULL. ExportDirectory - Supplies the Export Section data from a DLL. StaticSnap - If TRUE, then loader is attempting a static snap, and any ordinal/name lookup failure will be reported. Return Value: STATUS_SUCCESS or STATUS_PROCEDURE_NOT_FOUND --*/ { BOOLEAN Ordinal; USHORT OrdinalNumber; ULONG OriginalOrdinalNumber; PIMAGE_IMPORT_BY_NAME AddressOfData; PULONG NameTableBase; PUSHORT NameOrdinalTableBase; PULONG Addr; USHORT HintIndex; NTSTATUS st; PSZ ImportString; // // Determine if snap is by name, or by ordinal // Ordinal = (BOOLEAN)IMAGE_SNAP_BY_ORDINAL(OriginalThunk->u1.Ordinal); if (Ordinal) { OriginalOrdinalNumber = (ULONG)IMAGE_ORDINAL(OriginalThunk->u1.Ordinal); OrdinalNumber = (USHORT)(OriginalOrdinalNumber - ExportDirectory->Base); } else { // // Change AddressOfData from an RVA to a VA. // AddressOfData = (PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)ImageBase + ((ULONG_PTR)OriginalThunk->u1.AddressOfData & 0xffffffff)); ImportString = (PSZ)AddressOfData->Name; // // Lookup Name in NameTable // NameTableBase = (PULONG)((ULONG_PTR)DllBase + (ULONG)ExportDirectory->AddressOfNames); NameOrdinalTableBase = (PUSHORT)((ULONG_PTR)DllBase + (ULONG)ExportDirectory->AddressOfNameOrdinals); // // Before dropping into binary search, see if // the hint index results in a successful // match. If the hint index is zero, then // drop into binary search. // HintIndex = AddressOfData->Hint; Делаем инвалидным RVA описателя импортируемой функции в IMAGE_THUNK_DATA. Тогда при обращении к описателю IMAGE_IMPORT_BY_NAME будет возникать #AV: Код (Text): HintIndex = AddressOfData->Hint ну или в коде: Код (Text): $ ==> 7C917BD1 _LdrpSnapThunk@32 mov edi,edi $+2 7C917BD3 push ebp $+3 7C917BD4 mov ebp,esp $+5 7C917BD6 sub esp,28 $+8 7C917BD9 mov eax,dword ptr ss:[ebp+10] $+B 7C917BDC mov eax,dword ptr ds:[eax] $+D 7C917BDE mov edx,eax $+F 7C917BE0 push ebx $+10 7C917BE1 shr edx,1F $+13 7C917BE4 push esi $+14 7C917BE5 and dl,1 $+17 7C917BE8 push edi $+18 7C917BE9 mov byte ptr ss:[ebp+13],dl $+1B 7C917BEC jnz ntdll.7C917EA4 $+21 7C917BF2 mov ecx,dword ptr ss:[ebp+C] $+24 7C917BF5 test ecx,ecx $+26 7C917BF7 je ntdll.7C93FEFC $+2C 7C917BFD mov esi,dword ptr ss:[ebp+18] $+2F 7C917C00 mov edi,dword ptr ds:[esi+24] $+32 7C917C03 add edi,dword ptr ss:[ebp+8] $+35 7C917C06 add ecx,eax $+37 7C917C08 mov eax,dword ptr ds:[esi+20] $+3A 7C917C0B add eax,dword ptr ss:[ebp+8] $+3D 7C917C0E lea ebx,dword ptr ds:[ecx+2] $+40 7C917C11 movzx ecx,word ptr ds:[ecx] ; * #AV (Ecx:PIMAGE_IMPORT_BY_NAME, AddressOfData). Тогда можно указать свою ссылку, либо перехватить адрес возврата из функции и изменить IAT.
Както так(диспетчер исключений имеет контекст и выполняет примерно такой код, до этого хэндлер NtMapViewOfSection проецирует секцию и разбивает OriginalThunk, загружая туда инвалидное значение THUNK_SIGNATURE): Код (Text): ; typedef struct _IMAGE_IMPORT_BY_NAME { ; WORD Hint; ; BYTE Name[1]; ; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; ; typedef struct _IMAGE_THUNK_DATA { ; union { ; PBYTE ForwarderString; ; PDWORD Function; ; DWORD Ordinal; ; PIMAGE_IMPORT_BY_NAME AddressOfData; ; } u1; ; } IMAGE_THUNK_DATA; ; typedef IMAGE_THUNK_DATA * PIMAGE_THUNK_DATA; ; def. ldrsnap.c/ldrp.h ; ; NTSTATUS ; LdrpSnapThunk ( ; IN PVOID DllBase, ; IN PVOID ImageBase, ; IN PIMAGE_THUNK_DATA OriginalThunk, ; IN OUT PIMAGE_THUNK_DATA Thunk, ; IN PIMAGE_EXPORT_DIRECTORY ExportDirectory, ; IN ULONG ExportSize, ; IN BOOLEAN StaticSnap, ; IN PSZ DllName ; ); ; SFC ; STACK_FRAME struct Next PVOID ? ; PSTACK_FRAME Ip PVOID ? STACK_FRAME ends PSTACK_FRAME typedef ptr STACK_FRAME ; ~ LdrpSnapThunk() ; LDRP_SNAP_THUNK_FRAME struct Sfc STACK_FRAME <> ; ~ LdrpSnapIAT() DllBase PVOID ? ImageBase PVOID ? OriginalThunk PIMAGE_THUNK_DATA ? Thunk PIMAGE_THUNK_DATA ? ExportDirectory PIMAGE_EXPORT_DIRECTORY ? ExportSize ULONG ? StaticSnap BOOLEAN ? DllName PSTR ? LDRP_SNAP_THUNK_FRAME ends PLDRP_SNAP_THUNK_FRAME typedef ptr LDRP_SNAP_THUNK_FRAME ; PCR PCR equ fs: PcPrcbData equ 00120H ; PRCB PbCurrentThread equ 00004H ; TEB TeStackBase equ 00004H TeStackLimit equ 00008H ; CONTEXT CsEbp equ 000B4H CsEsp equ 000C4H CsEip equ 000B8H CsEax equ 000B0H ; o IF, PASSIVE_LEVEL ; XcptDispatch proc uses ebx esi edi Context:PCONTEXT, DllBase:PVOID %SEH_Prolog mov ebx,Context assume ebx:PCONTEXT invoke PsGetCurrentThread ; PETHREAD ; mov eax,PCR[PcPrcbData + PbCurrentThread] invoke PsGetThreadTeb, Eax ; PTEB mov ecx,dword ptr [ebx + CsEsp] ; Esp mov esi,dword ptr [ebx + CsEbp] ; Ebp cmp ecx,dword ptr [eax + TeStackLimit] ; Esp >= StackLimit mov edx,esi jb Error sub edx,ecx ; Var's: (Ebp - Esp) jna Error ; Ebp > Esp ; Def. 0x28 cmp edx,10H ; Min Var's: Ebp >= Esp + 10H jb Error lea ecx,[esi + sizeof(LDRP_SNAP_THUNK_FRAME)] cmp edx,40H ; Max Var's: Ebp <= Esp + 40H ja Error cmp ecx,dword ptr [eax + TeStackBase] ; Ebp + sizeof(Arg's) < StackBase jnb Error assume esi:PLDRP_SNAP_THUNK_FRAME mov eax,DllBase ; msvcrt.dll cmp [esi].DllBase,eax jne Error cmp [esi].OriginalThunk,THUNK_SIGNATURE ; Magic jne Error invoke RtlImageNtHeader, [esi].ImageBase test eax,eax ; PIMAGE_NT_HEADERS jz Error mov eax,[esi].ImageBase add eax,[esi].Thunk ; PIMAGE_IMPORT_BY_NAME add eax,2 ; IMAGE_IMPORT_BY_NAME.Name invoke ValidateImportByName, Eax test eax,eax ; PNEW_ENTRY(Handler) mov ecx,[esi].Thunk jz Error mov dword ptr [ecx],eax mov edx,[esi].Sfc.Ip ; Eip mov ecx,[esi].Sfc.Next ; Ebp add esi,sizeof(LDRP_SNAP_THUNK_FRAME) ; Esp mov dword ptr [ebx + CsEax],STATUS_SUCCESS mov dword ptr [ebx + CsEip],edx mov dword ptr [ebx + CsEbp],ecx mov dword ptr [ebx + CsEsp],esi ; Нужно восстановить регистры Ebx, Esi и Edi, тут упрощённо. xor eax,eax Exit: %SEH_Epilog ret Error: mov eax,STATUS_UNSUCCESSFUL jmp Exit XcptDispatch endp В теории должно работать.
dfrsa APC механизм асинхронный. Тоесть она будет доставлена при возврате из сервиса(NtMapViewOfSection), либо после того как загрузчик отработает http://www.wasm.ru/forum/viewtopic.php?id=38310. Если при возврате из сервиса, то это ничего не даёт. А если при возврате из загрузчика, то тут и APC не нужна - всеголишь изменить ссылку на стартап код в контексте текущего потока(Eip/Eax). APC использовать в данном случае не приемлимо. Вобще наверно можно использовать решение "в лоб" - создать свой дескриптор(IMAGE_IMPORT_DESCRIPTOR) и инициализировать TimeDateStamp, дабы адрес не менялся.
Я просто ищу наиболее простой способ, так чтобы он в будущем был переносим и на платформу x64. Пока что мне нравится идея с Bound Import, но как же мне похукать функцию через него - пока не сообразил.
Перенастроить импорт. Тоесть например может не хватать места для нового дескриптора. Он будет описывать один вход в IAT. Проверки принадлежности ссылок модулю сведены к минимому, адрес таблицы импорта вроде не проверяется, тогда её можно переместить за пределы модуля, например в приватную память. Попробую в юзермоде сделать.
Может лучше изменить адрес функции в таблице экспорта ? Тогда при импорте будет загружаться ссылка на хэндлер. При динамическом импорте также.
Я до этого хукал сплайсом код функции самой ДЛЛки - так он менялся во всех процессах. А как же мне изменить экспорт для этой ДЛЛки только в данном процессе?
Файловые проекции не разделяемы. Находите таблицу экспорта, в ней адрес своей функции и изменяете его.
На данный момент экспериментирую со способом изменения экспорта функции в ДЛЛ-ке. Почему-то при попытке вызова моего кода выпадает эксепшен. т.е. в дебагере я вижу что управление передается на мой код - 001b:00030004 8bec mov ebp,esp но при нажатии F11 (шаг) выпадает в ntdll!KiUserExceptionDispatcher С чем это может быть связано? Может быть с тем что страница памяти не помечена как выполянемая? Память в процессе выделяю след. образом: Код (Text): PMDL pMdl = NULL; BYTE *pVirtualMemory = NULL; PHYSICAL_ADDRESS phBegin,phEnd,phSkip; phBegin.QuadPart = 0; phEnd.QuadPart = (LONGLONG)-1; phSkip.QuadPart = 0; DWORD dwAllocationSize; __try { pMdl = MmAllocatePagesForMdl(phBegin, phEnd, phSkip, 0x1000); DBGPRINT(("ImageLoadNotifyRoutine MmAllocatePagesForMdl %X\r\n", pMdl)); if (pMdl) { dwAllocationSize = MmGetMdlByteCount(pMdl); if (dwAllocationSize >= 0x60) { pVirtualMemory = (BYTE *)MmMapLockedPagesSpecifyCache(pMdl, UserMode, MmCached, 0, FALSE, NormalPagePriority); DBGPRINT(("ImageLoadNotifyRoutine MmMapLockedPagesSpecifyCache %X\r\n", pVirtualMemory)); if (pVirtualMemory) { И у меня память стабильно выделяется по виртуальному адресу 0x30000 Может быть еще что то надо?
В том то и дело что я не знаю... А как это узнать из WinDbg? Пробовал через "!address" но это расширение под Win7 не работает. Как можно узнать стоит атрибут на исполнение или нет?
ziral2088 Не вводите людей в заблуждение. Доступ на исполнение задаётся в дескрипторе сегмента, а не PTE/PDE. В дефолтных кодовых сегментах разрешено исполнение, в сегментах данных запрещено. Поэтому и не работает в NT атрибут PAGE_EXECUTE. ntcdm Контекст покажите.