Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
книги хакеры / Майкл_Сикорски,_Эндрю_Хониг_Вскрытие_покажет!_Практический_анализ.pdf
Скачиваний:
18
Добавлен:
19.04.2024
Размер:
17.17 Mб
Скачать

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

450  Часть VI  •  Специальные темы

w Click

 

 

 

 

 

 

 

 

 

 

 

o

m

 

w

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Эта функция вычисляет для своего строкового аргумента 32-битный хеш типа DWORD. Регистр EDI хранит значение текущего хеша и после инициализации равен 0. Каждый байт входящей строки загружается с помощью инструкции lodsb . Если байт не равен NULL, текущий хеш поворачивается вправо на 13 (0x0d) в строке , а текущий байт добавляется в хеш. Результат возвращается в регистр EAX, чтобы вызывающий код мог сравнить его с вкомпилированным значением.

ПРИМЕЧАНИЕ

Популярность алгоритма из листинга 19.5 обусловлена тем, что он включен в пакет Metasploit. Вы можете также встретить разные его вариации с другим поворотом или размером хеша.

Окончательная версия программы Hello World

В листинге 19.6 представлена полная реализация функции findSymbolByHash, с помощью которой можно искать экспортные символы в загруженных динамических библиотеках­.

Листинг 19.6. Реализация функции findSymbolByHash

; __stdcall DWORD findSymbolByHash(DWORD dllBase, DWORD symHash);

findSymbolByHash:

 

pushad

 

 

mov

ebp, [esp + 0x24]

; загружаем 1-й аргумент dllBase

mov

eax, [ebp + 0x3c]

; получаем сдвиг PE-сигнатуры

; загружаем массив DataDirectories в edx: рассчитано на PE32

mov

edx, [ebp + eax + 4+20+96]

add

edx, ebp ; edx:= addr IMAGE_EXPORT_DIRECTORY

mov

ecx, [edx + 0x18]

; ecx:= NumberOfNames

mov

ebx, [edx + 0x20]

; ebx:= ОВА массива AddressOfNames

add

ebx, ebp

; rva->va

.search_loop:

 

jecxz

.error_done

; если это конец массива, переходим к done

dec

ecx

; dec: счетчик цикла

; esi:= следующее имя, использует ecx*4, так как каждый указатель занимает 4 байта

mov

esi, [ebx+ecx*4]

 

add

esi, ebp

; rva->va

push

esi

 

call

hashString

; хешируем текущую строку

; сравниваем результат со вторым аргументом в стеке: symHash

cmp

eax, [esp + 0x28]

 

jnz

.search_loop

 

; на этом этапе мы нашли строку в AddressOfNames

mov

ebx, [edx+0x24]

; ebx:= ОВА таблицы порядковых номеров

add

ebx, ebp

; rva->va

;переводим cx в порядковый номер по имени-индексу

;используем еcx*2: каждое значение занимает 2 байта

mov

cx, [ebx+ecx*2]

 

mov

ebx, [edx+0x1c]

; ebx:= ОВА мaссива AddressOfFunctions

add

ebx, ebp

; rva->va

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

Глава 19. Анализ кода командной оболочки   451

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

; eax:= ОВА экспортной функции. Используем ecx*4: каждое значение занимает 4 байта

mov

eax, [ebx+ecx*4]

 

add

eax, ebp

; rva->va

jmp

near .done

 

.error_done:

 

 

xor

eax, eax

; очищаем eax при ошибке

.done:

 

 

mov

[esp + 0x1c], eax

; перезаписываем eax, сохраненный в стеке

popad

 

 

retn 8

 

 

Вкачестве аргументов эта функция принимает указатель на базовый адрес DLL

и32-битный хеш, который соответствует искомому символу. В результате в регистре EAX возвращается указатель на запрашиваемую функцию. Помните, что все виртуальные адреса в PE-файле являются относительными, поэтому, чтобы создать указатели, которые можно использовать, коду приходится добавлять значение dllBase (в этом примере оно хранится в регистре EBP) к каждому полученному ОВА.

Код начинает разбор PE-файла в строке , пытаясь получить указатель на PE-сигнатуру. В строке путем добавления подходящего сдвига создается указатель на IMAGE_EXPORT_DIRECTORY (предполагается, что файл 32-битный). Разбор структуры IMAGE_EXPORT_DIRECTORY начинается в строке ; для этого загружаются значение NumberOfNames и указатель AddressOfNames. Каждый указатель на строку в AddressOfNames передается в функцию hashString , а результат вычисления сравнивается со значением, переданным в качестве аргумента .

Обнаружив подходящий элемент внутри AddressOfNames, код использует его как указатель для массива AddressOfNameOrdinals , чтобы получить соответствующий порядковый номер. Затем этот номер послужит индексом для массива AddressOfFunctions . Это то значение, которое нужно пользователю, поэтому оно сохраняется в стек , перезаписывая содержимое EAX, которое было создано инструкцией pushad. Следующая инструкция popad оставит это значение без изменений.

В листинге 19.7 показана полная версия примера Hello World, которая использует определенные выше функции findKernel32Base и findSymbolByHash, не полагаясь на заранее встроенные адреса API-вызовов.

Листинг 19.7. Пример Hello World, не зависящий от размещения

mov

ebp, esp

 

sub

esp, 24h

 

call

sub_A0

; вызываем настоящее начало кода

db 'user32',0

 

db 'Hello World!!!!',0

 

sub_A0:

 

 

pop

ebx

; ebx получает указатель на данные

call

findKernel32Base

 

mov

[ebp-4], eax

; сохраняем базовый адрес kernel32

push

0EC0E4E8Eh

; хеш функции LoadLibraryA

push

dword ptr [ebp-4]

 

call

findSymbolByHash

 

mov

[ebp-14h], eax

; сохраняем адрес LoadLibraryA

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

 

F

 

 

 

 

 

 

t

 

 

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

 

r

 

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

w

 

 

to

 

 

452   

Часть VI  •  Специальные темы

w Click

 

 

 

 

 

 

 

 

 

 

 

 

 

o

m

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

.c

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

lea

eax, [ebx]

; eax указывает на "user32"

 

 

 

 

 

 

 

 

 

 

push

eax

 

 

 

 

 

 

 

 

 

 

 

call

dword ptr [ebp-14h]

; LoadLibraryA

 

 

 

 

 

 

 

 

 

 

mov

[ebp-8], eax

; сохраняем базовый адрес user32

 

 

 

 

 

 

 

 

 

 

push

0BC4DA2A8h

; хеш функции MessageBoxA

 

 

 

 

 

 

 

 

 

 

push

dword ptr [ebp-8]

; местоположение библиотеки user32

 

 

 

 

 

 

 

 

 

 

call

findSymbolByHash

 

 

 

 

 

 

 

 

 

 

 

mov

[ebp-0Ch], eax

; сохраняем адрес MessageBoxA

 

 

 

 

 

 

 

 

 

 

push

73E2D87Eh

; хеш функции ExitProcess

 

 

 

 

 

 

 

 

 

 

push

dword ptr [ebp-4]

; местоположение библиотеки kernel32

 

 

 

 

 

 

 

 

 

 

call

findSymbolByHash

 

 

 

 

 

 

 

 

 

 

 

mov

[ebp-10h], eax

; сохраняем адрес ExitProcess

 

 

 

 

 

 

 

 

 

 

xor

eax, eax

 

 

 

 

 

 

 

 

 

 

 

lea

edi, [ebx+7]

; edi:= указатель на "Hello World!!!!"

 

 

 

 

 

 

 

 

 

 

push

eax

; uType: MB_OK

 

 

 

 

 

 

 

 

 

 

push

edi

; lpCaption

 

 

 

 

 

 

 

 

 

 

push

edi

; lpText

 

 

 

 

 

 

 

 

 

 

push

eax

; hWnd: NULL

 

 

 

 

 

 

 

 

 

 

call

dword ptr [ebp-0Ch]

; вызываем MessageBoxA

 

 

 

 

 

 

 

 

 

 

xor

eax, eax

 

 

 

 

 

 

 

 

 

 

 

push

eax

; uExitCode

 

 

 

 

 

 

 

 

 

 

call

dword ptr [ebp-10h]

; вызываем ExitProcess

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Код начинается с использования инструкций call/pop для получения указателя на данные . Затем вызываются функции findKernel32Base и findSymbolByHash , чтобы найти библиотеку kernel32.dll и получить из нее экспортный символ с хешем 0xEC0E4E8E. Этот добавочный хеш с поворотом на 13 соответствует строке LoadLibraryA. Результат, который эта функция записывает в EAX, будет указывать на реальный адрес LoadLibraryA.

Код загружает указатель на строку "user32" и вызывает функцию LoadLibraryA. После этого он находит и вызывает функцию MessageBoxA , чтобы вывести сообщение Hello World!!!!. В конце происходит корректное завершение работы с помощью вызова ExitProcess.

ПРИМЕЧАНИЕ

Разбор PE-файла с помощью встроенных возможностей shell-кoда вместо вызова GetProcAddress имеет еще одно преимущество: это усложняет обратное проектирование. При поверхностном анализе значения хешей скрывают API-вызовы.

Кодировки кода командной оболочки

Чтобы выполнить код командной оболочки, его нужно поместить в адресное пространство программы, из которого он будет вызван. Он должен находиться перед эксплойтом или передаваться вместе с ним. Например, если программа выполняет какую-нибудь простую фильтрацию входящих данных, shell-код должен миновать этот фильтр, иначе он не сможет попасть на уязвимый участок процесса.

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

Глава 19. Анализ кода командной оболочки   453

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Это означает, что многие уязвимые программы принимают shell-код только в том случае, если он похож на обычные данные.

Вкачестве примера можно привести программу, использующую небезопасные строковые функции strcpy и strcat, которые не устанавливают максимальный размер записываемых ими данных. Если программа считывает или копирует вредоносную информацию в буфер фиксированной длины, используя любую из этих функций, это может легко привести к атаке на основе переполнения буфера. Эти функции работают со строками как с массивами символов, в конце которых находится нулевой байт (0x00). Код командной оболочки, который злоумышленник хочет скопировать в этот буфер, должен выглядеть как обычные данные. Это означает, что посреди него не должно находиться нулевых байтов, иначе операция копирования строки завершится преждевременно.

Влистинге 19.8 показан небольшой фрагмент ассемблерного кода для доступа

креестру. Внутри него вы можете видеть несколько нулевых байтов, поэтому в таком виде он, скорее всего, не подойдет для использования в качестве shell-кода.

Листинг 19.8. Типичный код с выделенными нулевыми байтами

57

 

 

 

 

 

push

edi

 

50

 

 

 

 

 

push

eax

; phkResult

6A

01

 

 

 

 

push

1

; samDesired

8D

8B

D0

13

00

00

lea

ecx, [ebx+13D0h]

 

6A

00

 

 

 

 

push

0

; ulOptions

51

 

 

 

 

 

push

ecx

; lpSubKey

68

02

00

00

80

 

push

80000002h

; hKey: HKEY_LOCAL_MACHINE

FF 15

20

00

42

00

call

ds:RegOpenKeyExA

 

Иногда код командной оболочки попадает под дополнительные проверки, которые устраивают программы. Например:

все байты должны быть печатными символами в формате ASCII (быть меньше 0x80);

все байты должны быть буквами или цифрами (от A до Z, от а до z и от 0 до 9).

Чтобы обойти ограничения фильтрации со стороны уязвимой программы, почти любой shell-код шифрует свою основную часть и вставляет декодер, который превращает зашифрованные данные в исполняемые байты. Для удовлетворения строгим условиям фильтров нужно тщательно написать лишь сам декодер, а остальной код пройдет фильтрацию за счет кодирования, которое можно выполнять на этапе компиляции. Процедура, когда код командной оболочки записывает декодированные байты поверх закодированных (что обычно и происходит), называется саморедактированием. Когда она завершится, декодер передаст управление основному вредоносному коду.

Ниже приводятся распространенные методы кодирования.

Применить ко всем байтам исключающее ИЛИ с постоянной байтовой маской. Помните, что, если значения a и b имеют одинаковый размер, для них справедливо уравнение (a XOR b)XOR b == a.