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

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

444  Часть 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

 

 

 

 

Инструкция fldz помещает вещественное число 0,0 в стек сопроцессора, который обновляет значение fpu_instruction_pointer, чтобы оно указывало на fldz.

Врезультате выполнения операции fnstenv структура FpuSaveState попадает

встек по адресу [esp-0ch], что позволяет коду командной оболочки загрузить значение fpu_instruction_pointer в регистр EBX, используя инструкцию pop. Когда pop завершится, EBX будет содержать указатель на местоположение инструкции fldz. Затем shell-код начнет использовать EBX в качестве базового регистра для доступа к данным, встроенным в процесс.

Как и в предыдущем примере на основе операций call/pop, мы вызываем функции MessageBoxA и ExitProcess, используя заранее заданные адреса. Однако теперь эти адреса хранятся в виде данных вместе со строкой в формате ASCII, которую

нужно вывести. Инструкция lea загружает адрес строки Hello World!, вычитая 0x0d из местоположения инструкции fldz, хранящегося в регистре EBX. Инструк-

ции mov в строках и загружают местоположение функций MessageBoxA и соответственно ExitProcess.

ПРИМЕЧАНИЕ

Пример в листинге 19.3 немного надуманный, но такой подход часто используется в shell-коде для хранения или создания массивов с указателями на функции. Мы выбрали инструкцию fldz, но здесь подошла бы любая неуправляющая операция математического сопроцессора.

Чтобы запустить этот пример с помощью утилиты shellcode_launcher.exe, введите следующую команду:

shellcode_launcher.exe -i hellofstenv.bin -bp -L user32

Поиск символов вручную

Код командной оболочки существует в виде набора двоичных данных, которые можно выполнить. При запуске он должен делать что-то осмысленное. Обычно это подразумевает взаимодействие с системой посредством API-вызовов.

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

LoadLibraryA загружает определенную библиотеку и возвращает ее дескриптор. GetProcAddress ищет в библиотеке экспортные вызовы для символа с заданным именем или порядковым номером. Если 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. Анализ кода командной оболочки   445

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

он может загрузить любую библиотеку в системе и найти символы, которые она экспортирует, что дает ему полный доступ к API.

Обе функции экспортируются из файла kernel32.dll, поэтому код командной оболочки должен сделать следующее.

1.Найти kernel32.dll в памяти.

2.Разобрать PE-заголовок kernel32.dll и найти в нем адреса функций LoadLibraryA

и GetProcAddress.

Поиск kernel32.dll в памяти

Чтобы найти kernel32.dll, нужно пройтись по цепочке из недокументированных системных структур, одна из которых и будет содержать загрузочный адрес нужного файла.

ПРИМЕЧАНИЕ

Большинство системных структур Windows указаны на сайте Microsoft Developer network (www.msdn.microsoft.com), но вы ненайдете там их полной документации. Многие из них содержат массивы байтов, помеченные как Reserved и содержащие следующее предупреждение: «Эта структура может измениться в будущих версиях Windows». Полный список этих структур можно найти по адресу www.undocumented.ntinternals.net.

На рис. 19.1 показаны структуры данных, по которым обычно определяют базовый адрес библиотеки kernel32.dll (в каждом случае показаны только важные для нас поля и сдвиги).

Рис. 19.1. Обход структур для поиска DllBase в kernel32.dll

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

446  Часть 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

 

 

 

 

Процедура обхода начинается со структуры TEB, доступной из сегментного регистра FS. Сдвиг 0x30 внутри TEB указывает на PEB. Сдвиг 0xc внутри PEB ведет к структуре PEB_LDR_DATA, которая содержит три списка с двойным связыванием. Их элементы представляют собой структуры LDR_DATA_TABLE — по одной для каждого загруженного модуля. Поле DllBase на входе в kernel32.dll — то значение, которое мы искали.

Три структуры LIST_ENTRY связывают по имени, но в разном порядке элементы LDR_DATA_TABLE. Код командной оболочки обычно идет за элементом InInitializationOrderLinks. В версиях Windows с 2000 по Vista среди всех библиотек kernel32.dll инициализируется второй, вслед за ntdll.dll: это означает, что второй элемент в списке InInitializationOrderLinks должен принадлежать kernel32.dll. Но, начиная с Windows 7, модуль kernel32.dll больше не инициализируется вторым по счету, в связи с чем этот простой алгоритм не работает. Переносимый shell-код должен проверить поле FullDllName типа UNICODE_STRING, чтобы подтвердить, что это kernel32.dll.

При обходе структур LIST_ENTRY важно понимать, что указатели Flink и Blink ссылаются на аналогичные поля LIST_ENTRY в следующей и предыдущей структурах типа LDR_ DATA_TABLE. Это означает, что при переходе к элементу InInitializationOrderLinks для получения из kernel32.dll записи LDR_DATA_TABLE_ENTRY (и затем DllBase) вам нужно добавить к указателю значение 8, а не 0x18, как если бы указатель ссылался на начало структуры.

Листинг 19.4 содержит пример ассемблерного кода, который находит базовый адрес kernel32.dll.

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

; __stdcall

DWORD findKernel32Base(void);

findKernel32Base:

 

push

esi

 

xor

eax, eax

 

mov

eax, [fs:eax+0x30]

; eax получает указатель на PEB

test

eax, eax

; если старший бит установлен: Win9x

js

.kernel32_9x

 

mov

eax, [eax + 0x0c]

; еах получает указатель на PEB_LDR_DATA

;esi gets pointer to 1st ;LDR_DATA_TABLE_ENTRY.InInitializationOrderLinks.Flink

mov

esi, [eax + 0x1c]

 

;eax gets pointer to 2nd

 

;LDR_DATA_TABLE_ENTRY.InInitializationOrderLinks.Flink

lodsd

 

 

mov

eax, [eax + 8]

; eax получает LDR_DATA_TABLE_ENTRY.DllBase

jmp

near .finished

 

.kernel32_9x:

 

jmp

near .kernel32_9x

; Win9x не поддерживается: бесконечный цикл

.finished:

 

 

Pop

esi

 

ret

 

 

Чтобы получить указатель на структуру PEB, этот код обращается к TEB, используя сегментный регистр FS . Инструкция js (переход со знаком) в строке

 

 

 

 

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. Анализ кода командной оболочки   447

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

проверяет, установлен ли старший бит в указателе на PEB: это делается для того, чтобы отличить систему Win9x от WinNT. В WinNT (включая Windows 2000, XP и Vista) старший бит в указателе на PEB обычно не устанавливается, поскольку верхние адреса зарезервированы для ОС. Определение семейства операционной системы с помощью знакового бита не работает, если ОС загружена с параметром /3GB, из-за которого разделение между пространствами пользователя и ядра происходит по адресу 0xC0000000 вместо 0x8000000. Но для простоты мы решили этим пренебречь. Данный shell-код намеренно не поддерживает Win9x, поэтому при обнаружении ОС этого семейства он входит в бесконечный цикл .

Дальше код командной оболочки переходит к PEB_LDR_DATA . Предполагается, что он работает в системе не новее Windows Vista, поэтому он может просто извлечь из связного списка InInitializationOrderLinks второй элемент LDR_DATA_TABLE_ ENTRY и вернуть его поле DllBase.

Разбор экспортных данных в PE-файле

Получив базовый адрес библиотеки kernel32.dll, вы должны найти символы, которые она экспортирует. Как и в предыдущем случае, этот процесс заключается в переборе цепочки из нескольких структур, загруженных в память.

При определении местоположения файла формат PE использует относительные виртуальные адреса (ОВА). Эти адреса можно рассматривать как сдвиги в образе PE-файла, поэтому, чтобы получить корректный указатель, к каждому ОВА следует добавлять базовый адрес образа.

Экспортные данные содержатся в структуре IMAGE_EXPORT_DIRECTORY, ОВА которой хранится в массиве элементов IMAGE_DATA_DIRECTORY в конце IMAGE_OPTIONAL_ HEADER. Местоположение массива зависит от разрядности PE-файла. Код командной оболочки обычно рассчитан на работу в 32-битной системе, поэтому на этапе компиляции он может вычислить расстояние между PE-сигнатурой и массивом элементов

IMAGE_DATA_DIRECTORY:

sizeof(PE_Signature) + sizeof(IMAGE_FILE_HEADER) + sizeof(IMAGE_OPTIONAL_HEADER) = 120 bytes

На рис. 19.2 показаны поля структуры IMAGE_EXPORT_DIRECTORY, которые нас интересуют. AddressOfFunctions — это массив ОВА, который ведет к реальным экспортным функциям. В качестве индекса в нем используется экспортный порядковый номер (альтернативный способ обращения к экспортным символам).

Чтобы использовать этот массив, shell-код должен привязать экспортное имя к порядковому номеру. Для этого он использует массивы AddressOfNames и Ad­ dressOfNameOrdinals, которые существуют параллельно. Они имеют одинаковое количество элементов, а их индексы напрямую связаны между собой. AddressOfNames содержит 32-битные ОВА, которые указывают на строки с именами символов. AddressOfNameOrdinals содержит 16-битные порядковые номера. Если взять idx за индекс, то символ по адресу AddressOfNames[idx] имеет экспортный порядковый номер AddressOfNameOrdinals[idx]. Массив AddressOfNames отсортирован в алфавитном порядке, поэтому мы можем быстро найти нужную строку, используя

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

w

 

 

to

 

 

448  Часть 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

 

 

 

 

двоичный поиск (хотя в большинстве случаев это делается простым последовательным перебором массива).

Рис. 19.2. Структура IMAGE_EXPORT_DIRECTORY в kernel32.dll

Чтобы найти экспортный адрес символа, выполните следующие шаги.

1.Пройдитесь по массиву AddressOfNames, сравнивая каждую запись char* с нужным вам символом, пока не найдете совпадение. Воспользуйтесь соответству­ ющим индексом, чтобы получить имя iName из AddressOfNames.

2.Возьмите из массива AddressOfNameOrdinals элемент по индексу iName. Это будет порядковый номер iOrdinal.

3.Используйте iOrdinal в качестве индекса в массиве AddressOfFunctions. Полученное значение будет содержать ОВА экспортного символа. Верните его запрашивающему коду.

4.Реализация этого алгоритма будет показана в этой главе при демонстрации пол-

ной версии примера Hello World.

 

 

 

 

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. Анализ кода командной оболочки   449

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Как только код командной оболочки найдет функцию LoadLibraryA, он сможет загружать произвольные библиотеки. Значение, возвращаемое из LoadLibraryA, имеет в Win32 API тип HANDLE. Изучив значения этого типа, вы увидите, что на самом деле это 32-битный указатель на функцию dllBase в загруженной библиотеке. Это означает, что shell-код может обойтись без вызова GetProcAddress и дальше использовать свой собственный код в сочетании с указателями на dllBase, полученными из функции LoadLibraryA. В следующем разделе вы увидите, что это полезно в случае использования хешированных имен.

Использование хешированных экспортных имен

Алгоритм, который мы только что рассмотрели, имеет один недостаток: он выполняет операцию strcmp для каждого экспортного имени, пока не найдет подходящее. Для этого имя каждой API-функции, используемой в shell-коде, должно храниться

вформате ASCII. Однако это может сделать размер кода непозволительно большим.

Распространенное решение этой проблемы состоит в вычислении хеша строки каждого символа и сравнении результата с заранее подготовленным значением, хранящимся в коде командной оболочки. Хеш-функция не должна быть слишком сложной — достаточно, чтобы она гарантировала уникальность хешей в пределах отдельной DLL. Коллизии между хешами символов, которые не используются

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

Самой распространенной функцией хеширования является добавочный алгоритм с вращением вправо, представленный в листинге 19.5.

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

; __stdcall

DWORD hashString(char* symbol);

hashString:

 

 

push

esi

 

push

edi

 

mov

esi, dword [esp+0x0c]

; загружаем аргумент функции в esi

.calc_hash:

 

 

xor

edi, edi

 

cld

 

 

.hash_iter:

 

 

xor

eax, eax

 

lodsb

 

; загружаем следующий байт входящей строки

cmp

al, ah

 

je

.hash_done

; проверяем, последний ли это символ

ror

edi, 0x0d

; поворачиваем вправо на 13 (0x0d)

add

edi, eax

 

jmp

near .hash_iter

 

.hash_done:

 

 

mov

eax, edi

 

pop

edi

 

pop

esi

 

retn

4