ANTICHAT.XYZ    VIDEO.ANTICHAT.XYZ    НОВЫЕ СООБЩЕНИЯ    ФОРУМ  
Баннер 1   Баннер 2
Antichat снова доступен.
Форум Antichat (Античат) возвращается и снова открыт для пользователей. Здесь обсуждаются безопасность, программирование, технологии и многое другое. Сообщество снова собирается вместе.
Новый адрес: forum.antichat.xyz
Вернуться   Форум АНТИЧАТ > ИНФО > Статьи > Авторские статьи
   
 
 
Опции темы Поиск в этой теме Опции просмотра

Inject DLL в процесс из kernel-mode
  #1  
Старый 28.02.2009, 22:45
Аватар для slesh
slesh
Reservists Of Antichat - Level 6
Регистрация: 05.03.2007
Сообщений: 1,985
Провел на форуме:
3288241

Репутация: 3349


Отправить сообщение для slesh с помощью ICQ
По умолчанию 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
 
Ответить с цитированием
 



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Процесс создания программного обеспечения для распределенных вычислений (С++). c0n Difesa Авторские статьи 9 09.06.2009 16:33
Установка и настройка Tor&Privoxy d3ath *nix 2 24.10.2006 22:02
Многопоточность в Unix KEZ С/С++, C#, Delphi, .NET, Asm 11 10.08.2005 18:29



Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT.XYZ