В одной из своих разработак столкнулся с проблемкой - необходимо подгрузить определенную DLL к эксплореру. Сложность задачи была в том, что необходимо было это всё сделать из драйвера. Реализация вышла геморной и через жопу, но работает нормально на Win 2000,XP,2003 на остальных нет возможностей потестить. И так алгоритм таков: 1) попасть в контекст нужного процесса (эксплорера) 2) выделить в нем память в его юзермодной части адресов 3) записать в эту память шелкод который подгрузит нашу dll 4) выполнить шелкод 5) каким то образом узнать о том, что код выполнился. С виду всё просто, но на практике столкнулся с несколькими жопными моментами - таких как попадание в контекст процесса, и запуск нужного нам кода. А теперь по парядку как всё было реализовано 1) Попадание в контекст процесса Т.к. эксплорер ничего не знает о нашем драйвере, то и обращаться к нему не станет, по этому нам нужно чтото похукать чтобы попасть в контекст эксплорера. По логике вещей хукать нужно то, что часто вызывается. Сразу в голову пришло хукать SYSENTER и в добавок для win2k еще и INT2E. Но метод отпал быстро из-за очень геморной реализации хука SYSENTER в плане работы внутри хука. Можно было еще похукать какойнить драйвер и в перехватчике IRP_MJ_*** выполнять наши данные. Но в виду хз каких обстоятельств сразу недодумался до этого и пошел другим путём. А именно - хук какойнить часто используемой функции из ntoskrnl.exe. Хукал через SDT одну фунцию работы с реестром, но это не помогло т.к. не иногда ждать приходилось пару минут в виду того что эксплорер редко работал с реестром в неактивном состоянии. По этому пришлось хукать функции win32k.sys отвечающие за работу с графической подсистемой винды. И тут появились первые ступоры - хукать нужно не в ServiceDescriptorTable а в ShadowServiceDescriptorTable которая в отличии от ServiceDescriptorTable напрямую не экспортируется. Для поиска ShadowSDT был заюзан способ поиска через KeAddSystemServiceTable. Code: typedef struct _SYSTEM_SERVICE_TABLE { PNTPROC ServiceTable; PDWORD CounterTable; ULONG ServiceLimit; PBYTE ArgumentTable; } SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE; typedef struct _SERVICE_DESCRIPTOR_TABLE { SYSTEM_SERVICE_TABLE ntoskrnl; //SST для ntoskrnl.exe SYSTEM_SERVICE_TABLE win32k; //SST для win32k.sys SYSTEM_SERVICE_TABLE unused1; SYSTEM_SERVICE_TABLE unused2; } SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE; PSERVICE_DESCRIPTOR_TABLE ShadowTable; extern SYSTEM_SERVICE_TABLE * KeServiceDescriptorTable; __declspec(dllimport) KeAddSystemServiceTable(ULONG,ULONG,ULONG,ULONG,ULONG); SYSTEM_SERVICE_TABLE * GetShadowSDT() { unsigned char *check = (unsigned char*)KeAddSystemServiceTable; int i; SYSTEM_SERVICE_TABLE *rc=0; for (i=0; i<=99; i++) { __try { rc=*(SYSTEM_SERVICE_TABLE**)check; if (!MmIsAddressValid(rc)||(rc==KeServiceDescriptorTable)||(memcmp(rc,KeServiceDescriptorTable,sizeof(*rc))!=0)) { check++; rc = 0; } } __except (EXCEPTION_EXECUTE_HANDLER) {rc=0;} if (rc) break; } return rc; } Теперь через функцию GetShadowSDT мы сможет получить нужным нам адрес. Установка хука происходит точно также как и в SDT но с учетом того, что мы должны предварительно приаттачится к нужному нам процессу через KeAttachProcess. Чтобы приаттачиться нам необходимо знать PEPROCESS процесса. Его мы будет получать через PsLookupProcessByProcessId. И по этому у нас появляется необходимость узнать PID эксплорера. Делаем так: Code: #define SystemProcessInformation 5 int GetPidByName(WCHAR * name) { PSYSTEM_PROCESSES_INFORMATION pSystemProcessesInfo, pInfo; NTSTATUS ns; ULONG dSize=4096; ULONG dData=0; PVOID shi; do { shi=ExAllocatePool(NonPagedPool,dSize); if (shi==NULL) return 0; ns=ZwQuerySystemInformation(SystemProcessInformation,shi,dSize,&dData); if (ns==STATUS_INFO_LENGTH_MISMATCH) { ExFreePool(shi); dSize*=2; } } while (ns!=0); pSystemProcessesInfo=pInfo=(PSYSTEM_PROCESSES_INFORMATION)shi; ns=0; if (pSystemProcessesInfo) { while (1) { if (pSystemProcessesInfo->ProcessName.Buffer) { if (!wcscmp(pSystemProcessesInfo->ProcessName.Buffer, name)) { ns=pSystemProcessesInfo->ProcessId; break; } } if (!pSystemProcessesInfo->NextEntryDelta) break; pSystemProcessesInfo = (PSYSTEM_PROCESSES_INFORMATION)((ULONG)pSystemProcessesInfo + pSystemProcessesInfo->NextEntryDelta); } } ExFreePool(pInfo); return ns; } Теперь вызов pid=GetPidByName(L"explorer.exe"); даст нам нужный PID. При выборе функции для хука взгляд пал на NtUserGetMessage т.к. она очень активно используется процессами у которых есть окна. Получаем ID этой функции в таблице с учетом ОС Code: switch (*NtBuildNumber) { case 2195:NtUserGetMessageID=0x19A; break; // win 2k case 2600:NtUserGetMessageID=0x1A5; break; // win XP case 3790:NtUserGetMessageID=0x1A4; break; // win 2k3 default: return -1; } Для того чтобы ставить хук и снимать его была написана спец функция Code: // NewADDR - адрес нового обработчике // ID - номер сервиса в таблице // type - // 0 - установить перехват // 1 - снять перехват из-за таймаута // 2 - снять перехват в случие удачи BOOLEAN HOOK_SHADOW(PVOID NewADDR,ULONG ID,ULONG type) { NTSTATUS status; PEPROCESS EProc; // если type=2 то ном не нужно аттачитсья к процессу, т.к. мы уже приаттачины к ниму. if (type<2) // Если хукаем или снимаем хук по таймауту { // pid - глобальная переменная, хранит PID эксплорера status=PsLookupProcessByProcessId(pid, &EProc); // получаем PEPROCESS эксплорера if (!NT_SUCCESS(status)) return FALSE; KeAttachProcess(EProc); } if (type==0) // если ставим хук { // сохраним в глобальную переменную настоящий адрес функции TrueNtUserGetMessage=ShadowTable->win32k.ServiceTable[ID]; } // отключам защиту памяти __asm { CLI MOV EAX,CR0 AND EAX,NOT 10000H MOV CR0,EAX } ShadowTable->win32k.ServiceTable[ID]=NewADDR; //хукаем // включаем защиту памяти __asm { MOV EAX,CR0 OR EAX,10000H MOV CR0,EAX STI } if (type<2) KeDetachProcess(); // деаттачимся от процесса если надо. return TRUE; } Чтобы поставить хук нужно выполнить: HOOK_SHADOW(MyNtUserGetMessage,NtUserGetMessageID,0) Чтобы снять HOOK_SHADOW(TrueNtUserGetMessage,NtUserGetMessageID,1); // по таймауту или HOOK_SHADOW(TrueNtUserGetMessage,NtUserGetMessageID,2); Обработчик хука будет выглядеть так: Code: BOOLEAN MyNtUserGetMessage(OUT ULONG pmsg,IN ULONG hwnd,IN ULONG wMsgFilterMin,IN ULONG wMsgFilterMax) { // inhook - шлобальная переменнах хранящая инфу о хуке // 0 - снят. // 1 - установлен if (inhook!=0&&(ULONG)PsGetCurrentProcessId()==pid) // если хук поставлен и нужный нам PID { // проверли inhook чтобы сделать простенькую синхронизацию HOOK_SHADOW(TrueNtUserGetMessage,NtUserGetMessageID,2); // снимаем хук inhook=0; DLLINJECTPROC(); // вызываем нашу процедуру инжекта DLL // Для уведомления об результате KeSetEvent(&event,0,FALSE); KeClearEvent(&event); } // вызываем настоящий обработчик return TrueNtUserGetMessage(pmsg,hwnd,wMsgFilterMin,wMsgFilterMax); 2) Выделяем память, кидаем туда шелкод и выполняем его Вся работа по выделению памяти и запуску шелкода была переложена на DLLINJECTPROC. вызов юзермодного когда будет осущствляться через KeUserModeCallback (Огромное спасибо Twister за метод) Code: typedef struct _CMD_PARAMS { PCHAR dll; } CMD_PARAMS, * PCMD_PARAMS; VOID DLLINJECTPROC(VOID) { PROCESS_BASIC_INFORMATION pbi; NTSTATUS status; int ret; PPEB Peb=NULL; PVOID pMem = NULL, OutputBuffer,CmdAddr; ULONG OutputLength; ULONG dwSize = PAGE_SIZE; // 4 Кб ULONG KernelCallbackTable = 0; ULONG TableIndex, dwUserCodeSize; GLobalStatus=-1; // получаем PEB процесса status=ZwQueryInformationProcess((HANDLE)-1, ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL); if (NT_SUCCESS(status)) { Peb = pbi.PebBaseAddress; } if (Peb) { KernelCallbackTable=*(ULONG*)((ULONG)Peb + 0x2C); // вычесляем адрес таблицы // выделям память в юзермодном пространстве status=ZwAllocateVirtualMemory((HANDLE)-1, &pMem, 0, &dwSize, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); if (NT_SUCCESS(status)) { *(ULONG*)pMem=(ULONG)pMem+4; TableIndex=((ULONG)pMem-KernelCallbackTable)/sizeof(ULONG); dwUserCodeSize=(ULONG)SHELLCODE_END-(ULONG)SHELLCODE_START; CmdAddr = (PVOID)((ULONG)pMem+4+dwUserCodeSize); __try { // копируем в буфер шелкод и имя dll RtlCopyMemory((PVOID)((ULONG)pMem + 4), SHELLCODE_START, dwUserCodeSize); RtlCopyMemory(CmdAddr, StackParams.dll, strlen(StackParams.dll)); StackParams.dll=CmdAddr; // вызываме наш код status = KeUserModeCallback(TableIndex,&StackParams,sizeof(CMD_PARAMS),&OutputBuffer,&OutputLength ); // наш код должен вернуть 0xDEADC0DE // GLobalStatus - глобальная переменнах хранящая инфу о результате выполнения if (status==0xDEADC0DE) GLobalStatus=0; } __except (EXCEPTION_EXECUTE_HANDLER) { } // удаляем память ZwFreeVirtualMemory((HANDLE)-1, &pMem, &dwSize, MEM_DECOMMIT); } } return; } 3) Собственно говоря шелкод Алгоритм работы шелкода: Поиск адреса Kernel32.dll -> поиск адреса функции LoadLibraryA по её хешу -> LoadLibrary нашу DLL Code: ULONG SHELLCODE_START(PCMD_PARAMS lpData,ULONG dwDataSize) { __asm { xor eax,eax add eax,fs:[eax+0x30] mov eax,[eax+0x0c] mov esi,[eax+0x1c] lodsd mov eax,[eax+0x08] // eax = адрес KERNEL32.DLL mov ebx,eax add eax,[eax+0x3C] // PE заголовок mov edx,[eax+0x78]// Таблица экспорта add edx,ebx xor ecx,ecx mov edi,[edx+0x20]// Таблица экспортируемых имен add edi,ebx getproc_scan: mov esi,ebx add esi,[edi+(ecx)*4] //Адрес имени функции xor eax,eax getproc_hash:// считаем хеш rol eax,7 xor al,[esi] inc esi cmp byte ptr [esi],0 jnz getproc_hash xor eax,0xC8AC8026 // сравниваем с нашим хешем для LoadLibraryA jz getproc_found inc ecx cmp ecx,[edx+0x18] jne getproc_scan xor eax, eax jmp getproc_quit // если не нашли нужную апишку getproc_found: // если нашли mov eax,[edx+0x24] add eax,ebx mov ax,[eax+ecx*2] and eax,0x0000FFFF shl eax,2 mov edx,[edx+0x1C] add edx,ebx mov eax,[edx+eax] add eax,ebx // eax = Адрес LoadLibraryA getproc_quit: mov ebx,[ebp+8] push [ebx] // занесем в стек имя нашел dll call eax mov eax,0xDEADC0DE // вернем наше магическое число } } VOID SHELLCODE_END(){} 4) Уведомление о выполнении и таймаут С учетом всего вышеизложенного пишем функцию которая будет заниматься непосредственно подготовкой к подгрузке DLL Code: int InjectDLL(char* dllname) { LARGE_INTEGER timeout; pid=GetPidByName(L"explorer.exe"); // получаем PID эксплорера if (pid==0) return -1; // если не смогли получить то выходим inhook=0; StackParams.dll=dllname; // указатель на имя нашел DLL KeInitializeEvent(&event, NotificationEvent, FALSE); // Создаем событие для уведобления // Получаем ID сервиса для хука switch (*NtBuildNumber) { case 2195:NtUserGetMessageID=0x19A; break; case 2600:NtUserGetMessageID=0x1A5; break; case 3790:NtUserGetMessageID=0x1A4; break; default: return -1; } // находим адрес Shadow SDT ShadowTable=(PSERVICE_DESCRIPTOR_TABLE)GetShadowSDT(); if (!ShadowTable) return -1; // если не нашли // пытаемся хукать if (!HOOK_SHADOW(MyNtUserGetMessage,NtUserGetMessageID,0)) return -1; // если хук установлен inhook=1; timeout.QuadPart=RELATIVE(SECONDS(100)); // 100 секунд даем для подгрузки DLL // ждем положенное время if (KeWaitForSingleObject(&event, Executive, KernelMode,FALSE, &timeout) != STATUS_TIMEOUT) { // подгрузили удачно return GLobalStatus; } else { // если врмя истекло if (inhook!=0) // если хук до сих пор еще установлен { // снимаем хук по таймауту HOOK_SHADOW(TrueNtUserGetMessage,NtUserGetMessageID,1); inhook=0; } return -1; } } Теперь когда нам нужно подгрузить dll мы выполним код: InjectDLL("MyDLL.dll"); P.S. dll желательно чтобы находилась в SYSTEM32, в противном случае лучше указать тогда полный путь до неё. В заключение Чтобы не было глюков связанных с тем, что хук установлен и драйвер выгружается из системы, то напишим функцию которую необходимо будет вызвать из анлоада драйвера. Функция в случае наличия перехвата снимит его Code: VOID StopInject(VOID) { if (inhook!=0) { HOOK_SHADOW(TrueNtUserGetMessage,NtUserGetMessageID,1); inhook=0; } return; } Ну вот и всё!!! Садомазо удалось! (C) SLESH 2009
Молодец! Как всякому труЪ-кодеру достаточно знать, что нечто теоретически возможно, чтобы сотворить это если приспичит! PS while (inhook!=0), while (ns!=0) - непохек)
интересно, даже очень интересно, только вот проблема в том что С/С++ не знаю(... Попробую позже разобраться +
спосибо, статйа интересна. Ещё можно захватить PeekMessageW в импортах эксплорера, и выполнять в шелкоде загрузку своей dll. Не совсем понял. Разве обработчик IRP быдет выполняться в контексте explorer.exe?