Низкоуровневое программирование для Дzenствующих
Вид материала | Документы |
СодержаниеVolodya/HI-TECH, NEOx/UINC.RU Об упаковщиках в последний раз (часть 2.2) А запакован ли файл? DWORD i = 0 PE Sniffer |
- Низкоуровневое программирование, 108.99kb.
- Курс является базовым как для изучения других математических дисциплин, так и для более, 36.89kb.
- 1 Обобщенное программирование. Обобщенное программирование это еще одна парадигма программирования,, 55.18kb.
- Введение в линейное программирование линейное программирование (ЛП), 139.72kb.
- Учебно-методический комплекс для студентов заочного обучения специальности Прикладная, 63.23kb.
- Аттестационное тестирование в сфере профессионального образования, 72.49kb.
- Лекции по дисциплине «Социальное моделирование и программирование», 44.69kb.
- Программа дисциплины Линейное программирование Семестр, 17.93kb.
- Программа дисциплины "Программирование" для направления, 488.76kb.
- Рабочая программа по дисциплине Программирование на языке высокого уровня для специальности, 182.97kb.
# Эпилог
; ######################################################################### include cstr.inc ;; ;; А на закуску, небольшая функция из ULIB, ;; над которой можно помедитировать :) ;; Edmond / HI-TECH ;; ;; Функция возвращает длинну C-строки ;; eax - ;; размер строки ;; ;; Параметры:: ;; esi - указатель на строку cstr@@@len macro string:REQ IFDIFI mov esi,string ENDIF call cstr@len ENDM ; ######################################################################### .code cstr@len proc mov ecx,esi test ecx,11b je short main_loop ;; Цикл учитывающий невыровненность данных aligned: mov al,byte ptr [ecx] inc ecx test al,al je short byte_3 test ecx,11b jne short aligned align 16 cstr@len_align:: ; Метка, для обращения к процедуре ; если известно, что строка выровнена, ; но в этом случае ecx = esi comment /************************************************************ Проверка байта на содержание нуля происходит при помощи учёта переноса бита из соседнего байта, когда складываются числа X с числом 7efefeffh 7efefeffh = 01111110111111101111111011111111b Если какой-то байт НЕ РАВЕН нулю, происходит перенос в младший бит соседнего байта. Обратите внимание на ноль в старшем бите. Если бы его не было, то переполнение могло бы произойти из старшего байта. *********************************************************************/ main_loop: mov eax,dword ptr [ecx] mov edx,7efefeffh add edx,eax xor eax,-1 xor eax,edx add ecx,4 test eax,81010100h je short main_loop ;; Теперь нужно определить в каких из 4 байт был ноль? ;; Если мы здесь ;; нам нужно обратить знак адреса ecx neg ecx test al,al ;0 je short byte_0 test ah,ah ;1 je short byte_1 test eax,00ff0000h ;2 je short byte_2 ;; Обратите внимание ВЫЧИТАНИЕ ЗАМЕНЕНО СЛОЖЕНИЕМ!!! ;; Должно было бы быть lea eax,[ecx-esi], но такой команды нет byte_3: lea eax,[esi + ecx] neg eax ret byte_2: lea eax,[esi + ecx + 1] neg eax ret byte_1: lea eax,[esi + ecx + 2] neg eax ret byte_0: lea eax,[esi + ecx + 3] neg eax ret cstr@len endp end |
Volodya/HI-TECH, NEOx/UINC.RU
Об упаковщиках в последний раз
(часть 2.2)
(продолжение)
“That people seeking education should have the opportunity to find it.”
Nick Parlante
“Binary Trees”
Рецензент:
Dr.Golova/UINC.RU
Сорецензенты:
Four-F/HI-TECH, Quantum, Sten, Fixer
4. А запакован ли файл?
5. Тонкости PE-формата
6. OEP и иже с ним
7. Дампер процессов
8. Практический пример: UPX
9. Практический пример: Aspack
А запакован ли файл?
написано совместно с Fixer
В виду того, что эта статья позиционируется для взломщика с некоторым опытом, кажется немного наивным поднимать здесь такой вопрос. И в самом деле, для того, чтобы определить, а запакован ли файл, нужен только опыт. Дизассемблируется мусор, редакторы ресурсов отказываются работать и т.д., и т.п. Словом, все симптомы налицо и не о чем тут говорить. Это так. Возражений нет. Однако в рамках этой главы нам бы хотелось показать довольно оригинальный прием программной детекции упакованного файла. Для этого определим понятие "энтропия".
Энторпия |
Боже, как только это слово в термодинамике не обругали. И мера порядка системы, и мера рассеивания энергии, уж чего только там не было! Без сомнения, настоящего физика от нашего определения покоробит, а настоящий математик искренне возмутится. Тем не менее, по дилетантски определим слово "энтропия" как некоторую меру эффективности хранения информации. Для иллюстрирования понятия продемонстрируем следующий пример. Условимся считать файл всего лишь строкой байт, конец которой определяется каким-то загадочным образом самой операционной системой. Положим, у нас есть строка: abcdcdaad Подсчитаем количество вхождений каждого байта. Вот так: a = 3 b = 1 c = 2 d = 3 ------ 9 total Таким образом, мы можем сказать, что частота (т.е. вероятность - мы лезем в статистику) появления данного байта в этой конкретной последовательности составляет: a = 3/9 = 0.33... b = 1/9 = 0.11... c = 2/9 = 0.22... d = 3/9 = 0.33..., где 9 - общая длина последовательности. Определим теперь энтропию каждого символа по формуле: entropy = |log2(frequency_of_given_byte)|, где log2 - логарифм по основанию 2. Таким образом, имеем: a: |log2(3/9)| = 1,5849625007211561814537389439478 b: |log2(1/9)| = 3,1699250014423123629074778878956 c: |log2(2/9)| = 2,1699250014423123629074778878956 d: |log2(3/9)| = 1,5849625007211561814537389439478, Просуммировав сумму всех энтропий, получаем: 1,5849625007211561814537389439478 A 1,5849625007211561814537389439478 A 1,5849625007211561814537389439478 A 3,1699250014423123629074778878956 B 2,1699250014423123629074778878956 C 2,1699250014423123629074778878956 C 1,5849625007211561814537389439478 D 1,5849625007211561814537389439478 D 1,5849625007211561814537389439478 D ---------------------------------------------- 17,019550008653874177444867327367 бит информации Теоретически, это означает, что данную строку мы могли бы хранить в компьютерной памяти, используя лишь 17 бит информации. Реально же используется 72 - т.е. символов у нас 9, каждый символ - это байт, а байт - 8 бит. Итого 72 = 8*9. Остается сделать последний штрих - подсчитать остаток от деления общего количества бит на "энтропийные" биты (72/17 = 4,23). Выполнив его, мы увидим, что эффективность хранения информации невысока - фактически, имеем разницу в ЧЕТЫРЕ раза. Отношение к упаковке это имеет самое прямое. Когда секция кода файла сжата, то суммарная энтропия при подсчете возрастет, т.е. приблизится к стандартной сумме всех бит - это будет говорить о повышенной эффективности хранения информации, т.е. возможном сжатии секции кода (остаток от деления уменьшится). |
Алгоритм может выглядеть так:
long *ArrFreq;
double *aEntropy;
double msgEntropy=0.0;
ArrFreq=new long[256];
aEntropy=new double[256];
ZeroMemory(ArrFreq,256*sizeof(long));
ZeroMemory(aEntropy,256*sizeof(double));
BYTE *pBuff=(BYTE*)Offset;
long Max=0;
DWORD i = 0;
// подсчитаем каждый байт в сегменте (кода, данных и т.п.)
for (;i
Size;i++)
{
ArrFreq[pBuff[i]]++;
}
BYTE OpCode=0xff;
for (i=0;i<255;i++)
{
if (ArrFreq[i]>Max)
{
Max = ArrFreq[i];
OpCode=(BYTE)i;
}
// хранит вероятность появления символа
double prob=(double)ArrFreq[i]/(double)pSegment->Size;
if (prob)
{
//подсчитаем энтропию для i-го байта
aEntropy[i]=(-log(prob)/log(2))*(double)ArrFreq[i];
// и в общую сумму!
msgEntropy+=aEntropy[i];
}
}
// теперь в битах
double DataSize=(double)pSegment->Size*8.0;
// теперь делим, для вычисления остатка
double CompressionRatio=DataSize/msgEntropy;
Данный кусочек кода был любезно предоставлен Manuel Jimenez - автором BDASM (ссылка скрыта) - очень перспективного и быстрого дизассемблера, из которого в будущем может получится достойный соперник IDA! Разумеется, код нельзя просто скомпилировать, однако общий подход он даст.
А особо любознательным расскажем, что идея эта, естественно, отнюдь не нова. Давным-давно криптографы определили понятие "гаммирование" - т.е. наложение какой-либо последовательности байт на текст, чтобы исказить его до неудобочитаемости. Шифр Вернама, перестановки Цезаря - все это призвано было защитить файл от чужих глаз. Однако криптоаналитики придумали подход, который можно назвать "частотным анализом". Т.е., делается предположение о том, что зашифрованный файл содержит в себе осмысленные предложения из такого-то или такого-то языка. И зная вероятности появления символов алфавита в тексте (кстати, как вы думаете, а какая самая часто встречающаяся буква в русском алфавите?), можно попытаться примерно таким же алгоритмом угадать, а что же спрятано за маской? Доказано, что гаммирование принципиально нельзя сломать при условии равенства (и достаточно скрупулезного подбора!) длины последовательности (гаммы), длине шифруемой последовательности. Но даже если это и не так, то, ответьте, что мне мешает заархивировать файл, а уж потом наложить гамму, пусть и неустойчивую? Архивация полностью уничтожит вероятностные распределения букв под маской, делая дешифровку невозможной. Так? А вот и не так! Популярных архиваторов не так уж и много! Опытный криптоаналитик, увидев подобную белиберду, первым делом попробует сжать файл. Как, не сжимается? Ах так! Ну мы тогда...
Впрочем, кажется, мы увлеклись. Итак, определение того, что файл упакован, не займет много времени. Теперь зададим вопрос: "А ЧЕМ упакован файл?". Ответ на подобный вопрос нужен не одним нам с вами. Мировым стандартом (не побоимся этой фразы) считается составление сигнатуры и поиска этой сигнатуры в файле. С точки зрения алгоритмики имеем поиск подстроки в строке.
Приготовление сигнатуры – вещь не сложная и во многом должна определяться квалификацией того человека, который эту сигнатуру составляет. В Pe Tools для этой цели разработана утилита SignMan, следующая версия которой будет основана на очень простом принципе: побайтовом сравнении файлов, запакованных ОДНИМ И ТЕМ ЖЕ упаковщиком с разными опциями:
SetFilePointer(на оффсет, введенный пользователем,
т.к. отсчитывать от точки входа – неверное решение!);
... //отвести буферы и т.д., и т.п.
for(int i = 0; i < до какого-то значения, заданного пользователем; i++)
{
if(byFromFile1[i] == byFromFile2[i])
{
/*хорошо – в отчет*/
}
}
Сигнатуры хранятся в текстовой форме в файле Sign.txt. Практика показала, что это решение удачно. У многих людей, заинтересованных в судьбе утилиты, нашлось свободное время, чтобы отправить баг-репорт с правками сигнатур и, можно заявить, что файл, на сегодняшний день является достаточно тщательно проверенным не только авторами утилиты, но и многочисленными пользователями. Хотя, разумеется, это не означает, что багов там нет...
Словом, примем сигнатуру для поиска достаточно надежным средством. Осталось определится с тем, как ее искать. Мы трактуем файл как последовательность байт. PE Sniffer, по версию 1.5.х. включительно, является пока еще утилитой-ребенком и от него, ни в коем случае, пока нельзя ожидать многого. Поэтому утилита должна быть переписана с учетом быстрого и в достаточной степени надежного поиска. Итак, поиск может быть разделен на две категории:
1) Точный поиск подстроки в строке –
Байт-в-байт в точке входа – тривиальное strcmp с минимальными трюками по пропуску плавающих байт. Медленно, наивно, не всегда работает. Скажем, ничто не мешает переписать точку входа PE-файла на свой, достаточно безобидный код, который, скажем, делает не больше, чем xchg eax,eax – и этого хватит, чтобы сигнатура не была опознана. Учтите, что автоматический алгоритм детекции секции кода может быть неверным (причины см. в первой части), поэтому надежнее сначала просто спросить у пользователя, какую секцию PE он считает секцией кода. Что мы имеем здесь в более продвинутом плане? Первое - поиск по принципу бинарного дерева – реализован в IDA во FLIRT-алгоритме. Более подробно об этом можно почитать в статье Ильфака Гильфанова о FLIRT. Второе - поиск Бойера-Мура в файле. Рекомендуется прочесть некоторые документы с ссылка скрыта или ознакомиться со статьей ссылка скрыта на RSDN. Хочется сказать спасибо автору статьи - Андрею Боровскому – за такие объяснения, какими они должны быть. Понять алгоритм можно только посидев с карандашом над ним, разрисовав палочки и черточки. Эта статья относится именно к такому классу. Приведенные в ней алгоритмы в несколько более эффективном варианте, переписанные на С, будут использованы в PE Sniffer. В этом случае с файлом работают при помощи MMF-функций.
2) Неточный поиск подстроки в строке –
Поиск Бойера-Мура достаточно быстр за счет построения таблицы смещений и делает меньше сравнений, чем тривиальное strcmp. Однако он подходит лишь для точного поиска образца (или, в улучшенных вариантах, допускает лишь минимальные отклонения, согласно простейшим регулярным выражениям). Здесь же мы ни в коей мере не можем быть уверены, что подстрока (сигнатура) не будет самым злостным образом искажена. Давайте рассмотрим пару примеров и на их основе попытаемся сформулировать ряд правил для написания движка.
Пример: libc.lib – стандартная библиотека языка С. Известно, что код программы начинается не с main, а с *mainCRTStartup (одной из четырех). Поэтому ничто не препятствует поменять код процедуры с такого, например:
posvi = (OSVERSIONINFOA *)_alloca(sizeof(OSVERSIONINFOA));
posvi->dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
(void)GetVersionExA(posvi);
_osplatform = posvi->dwPlatformId;
_winmajor = posvi->dwMajorVersion;
_winminor = posvi->dwMinorVersion;
на вот такой:
posvi = (OSVERSIONINFOA *)_alloca(sizeof(OSVERSIONINFOA));
goto here;
now_here:
_osplatform = posvi->dwPlatformId;
_winmajor = posvi->dwMajorVersion;
_winminor = posvi->dwMinorVersion;
goto keep_on;
here:
posvi->dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
(void)GetVersionExA(posvi);
goto now_here;
keep_on:
...
Выражено коряво, но смысл вы схватить должны. Это полностью исказит сигнатуру и мы более не сможем сказать, что же это за компилятор. Причем и противопоставить-то такому приему практически нечего... К счастью, такое практически невозможно в общем случае проделать с исполняемым файлом, поэтому рассмотренный код относится, скорее, к патологии.
Реально же имеем следующее:
1) Для каждого пакера существует устойчивая кодовая последовательность именуемая в дальнейшем сигнатурой для которой действуют следующие правила (здесь под элементом сигнатуры подразумевается байт)
a) Элементы сигнатуры не могут меняться местами
b) Элементы сигнатуры не могут быть заменены на другие
2) Между отдельными элементами может присутствовать "шумовой" код (если мы для сигнатуры выберем 1, 5, 10 байты сгенерированные пакером, для уменьшения количества ложных срабатываний, необходимо учесть минимальное расстояние на котором могут встретиться эти элементы сигнатуры друг от друга), а между некоторыми такой код появиться не может (двух и более байтные команды).
Что-то наподобие такого и будет реализовано в новой, уже не совсем детской версии PE Sniffer. Хотя и здесь решение для общего случая, скорее всего, НЕ существует. Уж слишком злопакостно можно исказить сигнатуру при желании, и ни CRC, ни xor-сумма строки, ни побайтовое сравнение по хитрым правилам не помогут. Скажем, пункты 1.a/1.b явно дискуссионны, чего только стоят т.н. stolen bytes в Asprotect. Поэтому если имеете собственное суждение – не стесняйтесь его высказать.
Возможно, самые отчаянные захотят обсудить экзотические методы детекции – применение нейронных сетей, генетические алгоритмы, fuzzy logic и т.п. – это прекрасно. Дерзайте! Учтите, что и OEP (см. главу об OEP) можно искать в памяти по такому же принципу – поиском сигнатур компилятора. Так, к примеру, поступает PEiD в своем genoep.dll – просто-напросто в дампе программы выполняется поиск компиляторных сигнатур, при этом, кстати, опять таки, не учитывается понятие «украденные байты» - stolen bytes – защитный прием некоторых пакеров (Asprotect), при котором куски кода из OEP нагло утаскиваются.
В заключение было бы интерестно рассмотреть – а как же действуют профессиональные антивирусы? Ведь как-то же они предполагают наличие вируса, хотя, зачастую и неверно, давая ложные срабатывания или просто не узнавая его. Однако мир антивирусов практически закрыт от конечного пользователя. И кто-то еще ругает Windows за закрытость кода?
Как правило, в открытую печать не попадает сколь-нибудь ценной технической информации... Что тут говорить. Тем не менее, мы предложим пару линков:
- «Heuristic Techniques in AV Solutions: An Overview»
ссылка скрыта - в качестве вступления смотрися неплохо.
2. «Stripping Down An Av Engine»
- ссылка скрыта – в качестве вступления к следующему линку тоже ничего.
- ссылка скрыта - попытка антивируса с открытым исходым кодом (язык С). Функция эвристики (пока только ребенок) находится в файле matcher.c и использует обобщение алгоритма Кнута-Морриса-Пратта в поиске в односвязном списке – функция cl_scanbuff. Есть и еще несколько подобных антивирусов, но у их основателей хватило ума писать их на Java… Что тут сказать... И это там, где важна скорость и низкоуровневые трюки...
- ссылка скрыта - на удивление приличные статьи о детекции метаморфов, пара статей о Win32 высокого класса. Это достаточно хороший уровень. Очень рекомендуется!