На главную | Содержание | Назад | Вперёд
Наши друзья

 

 

Использование жестко закодированных вызовов функций

Использование в коде любых динамических данных приводит к увеличению его размера. Чем больше значений жестко закодировано, тем меньше программный код. Функции по сути являются внешними областями памяти. Поэтому вызов функции означает переход к их адресу — легко и просто. Если заранее знать адрес используемой функции, то нет необходимости добавлять код для ее поиска.
Хотя жесткое кодирование предоставляет преимущество уменьшения размера полезной нагрузки, но необходимо учесть и следующий недостаток — наша полезная нагрузка может оказаться бесполезной, если искомая функция будет перемещена. Даже для одинакового программного обеспечения на двух различных компьютерах могут использоваться различные адреса вызова функций. Это очень серьезная проблема, из-за которой жестко закодированные адреса редко приносят успех. Лучше всего избежать жесткого кодирования, кроме тех случаев, когда без сокращения кода обойтись невозможно.

Использование динамических таблиц переходов
В большинстве случаев состояние атакуемой системы предсказать очень сложно. Это считается серьезным препятствием для жесткого кодирования адресов. Однако есть несколько интересных методов "изучения" того, где могут находиться функции. Созданы таблицы соответствий (lookup table), в которых содержатся каталоги функций. Определив такую таблицу, можно найти функцию. Если в полезной нагрузке требуется использовать несколько функций (что чаще всего и наблюдается), все адреса этих функций можно будет найти "одним махом" и разместить результаты в таблицу переходов. В дальнейшем для вызова функции вполне реально просто использовать ссылку на создаваемую таблицу переходов.
Удобным способом создания таблицы переходов является загрузка базового адреса таблицы переходов в регистр центрального процессора. В центральном процессоре обычно есть несколько регистров, которые можно безопасно использовать во время выполнения других задач. Удобным для этой цели является регистр ЕВР — регистр указателя базы кадра, обычно используемый для хранения адреса стекового фрейма (ЕВР содержит адрес, начиная с которого в стек вносится информация или копируется из него). Однако вызовы функций могут быть закодированы как смещения относительно указателя базы10.
К de f i ne
GET PROC ADDRESS
[ebp]

#define
LOAD LIBRARY
[ebp
-
4]

((define
GLOBAL ALLOC
[ebp
T
8]

#define
WRITE FILE
[ebp

12]

idefine
SLEEP
[ebp
+
16]

((define
READ FILE
[ebp

20!

((define
PEEK NAMED PIPE
[ebp
■f
24]

((define
CREATE PROC
[ebp
+
28]

"define
GET START INFO
[ebp

32 j

С помощью этих удобных операторов вполне реально ссылаться на функции в таблице переходов. Например, используем следующий простой код для вызова внешней функции GlobalAlloc ().
call GLOBAL_ALLOC
В действительности это означает, что call [ebp+8]
Регистр ebp указывает на начало нашей таблицы переходов и каждая запись в этой таблице является указателем (размером 4 байт). Таким образом, строка [ebp+8 ] указывает на третий указатель в нашей таблице.
Инициализация таблицы переходов с помощью относительных значений может оказаться проблематичной. Существует множество способов для определения адреса функции в памяти. В некоторых случаях поиск может быть выполнен по имени функции. Код для управления таблицей переходов может осуществлять повторяющиеся вызовы функций LoadLibary () и GetProcAddress () для загрузки указа­телей функций. Безусловно, при таком методе требуется добавление имен функций
в полезную нагрузку (вот здесь и пригодится раздел "Данные"). В нашем примере код по управлению таблицей переходов способен выполнять поиск функций по имени. При этом раздел данных должен иметь следующий формат.
OxFFFFFFFF
DLL NAME 0x00 Function Name 0x00 Function Name 0x00 0x00
DLL NAME 0x00 Function Name 0x00 0x00
0x00
Наиболее важным моментом в этом примере является наличие байтов NULL (0x00). Два символа NULL завершают цикл загрузки библиотеки DLL, а три символа NULL завершают весь процесс загрузки. Например, чтобы заполнить таблицу переходов, воспользуемся следующим блоком данных.
char data[] = "kernel32.dll\0" \
"GlobalAlloc\0WriteFile\0Sleep\0ReadFile\0PeekNamedPipe\0" \ "CreateProcessA\OGetStartupInfoA\OCreatePipe\0\0";
Также обратите внимание, что мы разместили четырехбайтовую последовательность символов OxFF перед форматом раздела данных. Здесь в качестве подсказки можно использовать любое значение. Ниже мы покажем, как находить раздел данных в полезной нагрузке.
Определение раздела данных
Чтобы определить месторасположение раздела данных, достаточно просто выполнить поиск (вперед с текущей позиции) значения-подсказки. Мы уже узнали текущее значение на первом этапе "рекогносцировки". Реализовать поиск достаточно просто.
GET_DATA_SECTION:
inc edi // наша точка рекогносцировки
cmp dword ptr [edi], -1
jne GET_DATA_SECTION
add edi, 4 // мы сделали это, получив подсказку
Не забывайте, что в регистре EDI содержится значение указателя на текущую позицию в памяти. Мы увеличиваем это значение, пока не находим -1 (OxFFFFFFFF). Увеличиваем значение указателя еще на 4 байт и регистр EDI не будет указывать на начало раздела данных.
При использовании строк возникает проблема большого размера данных, который требуется для сохранения строк в полезной нагрузке. Кроме того, возникает необходимость использования строк, завершающихся символом NULL. В большинстве случаев символ NULL не годится для использования в векторе вторжения, т.е. эти символы полностью исключаются при атаке. Безусловно, мы можем воспользоваться операцией XOR для защиты части строки нашей полезной нагрузки. Это не так сложно, но возникают издержки относительно создания процедур кодирования/декодирования XOR.
Защита с помощью XOR
Это очень распространенная хитрость. Можно создать небольшую процедуру для XOR-кодирования данных до их использования в программе. Использовав при операции XOR какое-то значение, можно полностью избавиться от символов NULL

в данных. Ниже приведен пример циклического кода для декодирования данных полезной нагрузки, закодированных с помощью операции XOR по байту ОхАА
mov eax, ebp
add eax, OFFSET (см. Смещение ниже)
хог есх, есх
mov ex, SIZE
LOOPA: хог [еах] , ОхАА
inc eax loop LOOPA
В этом небольшом фрагменте кода берется только несколько байтов нашей полезной нагрузки, а в качестве стартовой точки используется значение регистра ebp. Смещение для нашей строки данных вычисляется по базовому указателю (ebp), после чего начинается цикл выполнения операции XOR для строки байтов (в качестве второго аргумента используется значение ОхАА). Это преобразование позволяет исключить все "ненужные" символы NULL. Однако для полной уверенности лучше проверить свою строку. При операции XOR некоторые символы могут быть преобразованы в нежелательные символы с той же простотой, с которой эта операция позволяет от них избавиться.
Использование контрольных сумм
Еще один метод при работе со строками заключается в размещении в полезной нагрузке контрольной суммы для строки. Оказавшись в пространстве искомого процесса, можно разместить таблицу функций и выполнить хэширование имени каждой функции. Вычисленные контрольные суммы можно сравнить с сохраненной контрольной суммой. Совпадение, как правило, свидетельствует о том, что найдена нужная функция. "Берем" адрес совпадающей функции и заносим его в таблицу переходов. Преимущество состоит в том, что размер контрольных сумм может составлять 4 байт и адрес функции может иметь такой же размер, т.е. при выявлении совпадения вполне реально просто заменить контрольную сумму адресом функции. Это позволяет сэкономить место и сделать все более элегантно (плюс отсутствие символов NULL).
хог есх, есх
_F1 :
хог cl, byte ptr [ebx]
rol есх, 8
inc ebx
cmp byte ptr [ebx], 0
jne _F1
cmp ecx, edi // сравниваем конечную контрольную сумму
В этом коде предполагается, что регистр ЕВХ указывает на строку, по отношению к которой мы хотим выполнить операцию хэширования. Контрольная сумма вычисляется до выявления символа NULL. Подсчитанная контрольная сумма сохраняется в ЕСХ. Если искомая контрольная сумма хранится в EDI, то контрольные суммы сравниваются. При обнаружении совпадения можно потом внести исправления в таблицу переходов с помощью установленного указателя функции.
Безусловно, создание полезной нагрузки нельзя назвать легким занятием. Наиболее важно воспрепятствовать появлению символов NULL, оставить нагрузку небольшой по объему и отслеживать положение в памяти.

 

На главную | Содержание | Назад | Вперёд
 
Яндекс.Метрика