3879095_Otvetnik_po_Zolotovux

advertisement
1. Основные подходы к восстановлению алгоритмов функционирования программных
модулей

метод черного ящика;

метод непосредственного анализа кода:
o дизассемблирование,
o отладка.
2. Метод черного ящика
При исследовании по методу «черного ящика» на вход выполняемой программы подаются
различные данные. При таком тестировании требуется только запуск программы и не проводится
никакого анализа исходного кода. С точки зрения безопасности на вход программы могут
подаваться вредоносные данные с целью вызывать сбой в работе программы. Если программа дает
сбой при выполнении какого-то теста, то считается, что выявлена проблема безопасности.
Анализ по методу «черного ящика» возможен даже без доступа к двоичному коду. Таким
образом, программа может быть проанализирована по сети. Все, что требуется, - это наличие
запущенной программы, которая способна принимать входные данные, т.е. если исследователь
способен отправить входные данные, которые принимает программа, и способен получить
результат обработки этих данных, значит, возможно тестирование по методу «черного ящика».
Анализ программы по методу «черного ящика» не так эффективен, как при использовании
метода непосредственного анализа кода, но этот метод намного проще для реализации и не
требует высокого уровня квалификации. В ходе тестирования по методу черного ящика,
специалист, воздействуя на программу, по выдаваемым результатам пытается максимально точно
определить пути исполнения кода в программе. При этом невозможно проверить действительное
место ввода пользовательских данных в коде программы.
3. Восстановление структуры и параметров информационных контейнеров с
использованием методов черного ящика.
Этот метод является действенным, только если нет доступа к самому коду программы.
Можно лишь проследить зависимость выходной информации от входных данных и на основе
этого сделать некие предположения об исследуемом алгоритме. При этом нельзя быть уверенным
наверняка в своих предположениях.
4. Методы прямого анализа кода исполняемых модулей
Проанализировать доступный код программы можно двумя способами:
 дизассемблирование;
 отладка.
Дизассемблирование – это статический анализ программы, представляющий код в виде текста
на некотором мнемоническом языке (чаще – на языке Ассемблера).
Отладчик позволяет выполнять пошаговую трассировку, отслеживать, устанавливать или
изменять значения переменных в процессе выполнения кода, устанавливать и удалять
контрольные точки или условия останова и т.д.
В дизассемблере (IDA) код программы представлен в виде графа, можно проанализировать
каждую ветку алгоритма, что дает лучшее, по сравнению с отладчиком, представление об
алгоритме программы. Однако, здесь требуется больше умственной работы, нежели при
использовании отладчика.
5. Формат исполняемого PE-файла.
Portable Executable — (PE, переносимый исполняемый) — формат исполняемых файлов,
объектного кода и динамических библиотек, используемый в 32- и 64-битных версиях
операционной системы Microsoft Windows. Формат PE представляет собой структуру данных,
содержащую всю информацию, необходимую PE загрузчику для проецирования файла в память.
Файл в таком формате начинается со стандартного DOS-кого заголовка:
0х0
WORD
Magic “MZ”
0x3C – смещение, по которому начинается заголовок PE-файла.
Заголовок PE-файла определяется следующей структурой:
struct_IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
};
1я часть – сигнатура PE\0\0
2я часть – обязательный заголовок
3я часть – дополнительный заголовок.
Обязательный заголовок:
struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
//Определяет, для какого процесса была скомпилирована
//данная программа (0х14С - 386)
//Файл, кроме заголовка, имеет различные секции
//Время, когда программа была скомпилирована
//Указатель на таблицу символов
//и число значений в ней
//Размер дополнительного заголовка
//Битовое поле, содержащее информацию о том, что это
//за файл, например:
//0x0002 IMAGE_FILE_EXECUTABLE
//0x1000 IMAGE_FILE_SYSTEM
//0x2000 IMAGE_FILE_DLL
};
Структура дополнительного заголовка:
struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
//10 либо 20 в зависимости от того, 32-разрядный
//или 64-разрядный заголовок
//Старшая и младшая часть версии линковщика
//соответственно
//Суммарный размер кода и данных:
//инициализированный и
//неинициализированных
//Адрес точки входа в программу
//База области кода
//База области данных
//Адрес, по которому желательно загрузить
//программу в память (по умолчанию 0х0400000)
DWORD SectionAlignment;
//Определяет границу выравнивания секций
DWORD FileAlignment;
//Выравнивание при хранении файла
WORD MajorOperatingSystemVersion; //Старшая и младшая части версии ОС
WORD MinorOperatingSystemVersion; //соответственно
WORD MajorImageVersion;
//Старшая и младшая части версии программы
WORD MinorImageVersion;
//в файле
WORD MajorSubsystemVersion;
//Старшая и младшая части версии
WORD MinorSubsystemVersion;
//подсистемы
DWORD Win32VersionValue;
//Это поле зарезервировано нулем
DWORD SizeOfImage;
//Задает размер всего файла, включая заголовок,
//уже после того, как он будет загружен в память
DWORD SizeOfHeaders;
//Суммарный размер всех заголовков
DWORD CheckSum;
//Контрольная сумма
WORD Subsystem;
//Непосредственно задает подсистему, для которой
//создан заданный файл. Возможные варианты:
//0 – неизвестная подсистема,
//1 – NATIVE (драйвера),
//2 – GUI (программы с графическим интерфейсом),
//3 – CUI (консольное приложение) и т.д.
WORD DllCharacteristics;
//Описывает характеристики Dll, если этот файл
//является динамической библиотекой
DWORD SizeOfStackReserve;
//Эти поля определяют память,
DWORD SizeOfStackCommit;
//которую необходимо зарезервировать
DWORD SizeOfHeapReserve;
//под стек
DWORD SizeOfHeapCommit;
//и под кучу
DWORD LoaderFlags;
//Зарезервировано нулем
DWORD NumberOfRVAandSizes;
//Определяет количество элементов в массиве,
//который содержит структуры, указывающие
//на местоположение и размер возможных таблиц
//PE-файла. Массив структур:
//IMAGE_DATA_DIRECTORY DataDirectory [].
//Основные таблицы – импорта и экспорта.
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
};
По окончании заголовка в файле располагается таблица секций:
struct _IMAGE_SECTION_HEADER {
BYTE Name [ 8 ];
DWORD VirtualSize;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLineNumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
//Имя секции
//Описывают размер секции и ее местоположение,
//когда она загружена в память
//Описывают размер и местоположение секции в
//файле, когда он находится на диске
//Описывают местоположение
//и размер структур,
//описывающих Relocations
//и номера строк
//флаговое поле из 3х флагов:
//0х80000000 Write
//0х40000000 Read
//0х20000000 Execute
};
Список секций:







.text – секция содержит исполняемый код;
.data – инициализированные глобальные переменные;
.bss – все неинициализированные данные;
.rdata – данные, доступные только для чтения (константы, отладочная информация);
.rsrc – секция содержит информацию о ресурсах;
.edata – информация об экспортируемых функциях;
.idata – информация об импортируемых функциях;
Таблица экспорта:
struct _IMAGE_EXPORT_DIRECTORY {
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunction;
DWORD NumberOfNames;
DWORD AddresOfFunctions;
DWORD AddressOfNames;
DWORD AddressOfNameOrdinals;
//Дата и время, когда структура экспорта была
// создана
//Старшая и младшая части версии
//таблицы экспорта соответственно
//Указатель на строку, содержащую имя секции
//Базовые значения для ординалов
//Количество элементов в таблице адресов
//экспортируемых функций
//Число имен (обычно равно числу функций)
//Адреса таблиц функций,
//имен
//и имен ординалов
};
Таблица импорта:
struct _IMAGE_IMPORT_DIRECTORY {
DWORD OrdinalFirstThunk;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
};
//Относительный адрес таблицы, описывающей
//непосредственное поле имен ординалов
//Дата и время создания (равна нулю до того, как
//загрузка состоится)
//Это поле связано с возможностью передачи
//экспорта в другие библиотеки
//Содержит адрес таблицы, которая аналогична
//таблице в 1-ом параметре до загрузки в память,
//после загрузки содержит адреса тех функций
//библиотек, которые импортируются
6. Процесс запуска исполняемого файла в ОС семейства Windows NT.
Windows проверяет наличие DOS-сигнатуры “MZ”, потом по смещению 0x3C берется
заголовок PE, проверяется наличие PE-сигнатуры и начинается загрузка файлов в память.
По таблице секций по очереди выбираются секции в память.
После того, как все секции загружены в память, начинается разбор таблиц импорта. Это самый
простой способ обратиться к функциям из библиотек. Происходит поиск библиотеки и загрузка ее
в память как обычного PE-файла; вызывается функция с нулевым значением, производящая
инициализацию библиотеки; в ней будут найдены необходимые функции.
7. Основные программные конструкции, используемые в коде программных модулей.
1)
if (выражение)
then { .
.
}
else { .
.
}
Пример реализации на ассемблере следующей проверки:
if ( ( (x > y ) && ( z < t ) ) || ( A != B ) ) then ... else…
mov ax, A
cmp ax, B
jne _then
mov ax, x
cmp ax, y
jng _endif
mov ax, z
cmp ax, t
jnl _endif
_then:
…
_endif:
…
2)
switch [ ]
case : …
...
case : …
default :
Можно switch преобразовать в последовательность if ’ов.
Если проверяемое значение в switch небольшое, то может использоваться таблица переходов,
где будут записаны адреса блоков. Проверяемое значение будет умножено на размер ячейки (как
правило, 4 байта), получится адрес перехода, и дальше будет выполнен jmp:
shl eax, 2
jmp [eax]
8. Использование функций. Структура функций. Правила передачи параметров и
возврата результата.
Если в программе возникает необходимость частого обращения к некоторой группе
операторов, то рационально сгруппировать такую группу операторов в самостоятельный блок, к
которому можно обращаться, указывая его имя. Такие разработанные программистом
самостоятельные программные блоки называются подпрограммами пользователя.
При вызове подпрограммы (процедуры или функции), определенной программистом, работа
главной программы на некоторое время приостанавливается и начинает выполняться вызванная
подпрограмма. Она обрабатывает данные, переданные ей из главной программы. По завершении
выполнения подпрограмма-функция возвращает главной программе результат (подпрограммапроцедура не возвращает явно результирующего значения).
Передача данных из главной программы в подпрограмму и возврат результата выполнения
функции осуществляются с помощью параметров.
Функция, определенная пользователем, состоит из заголовка и тела функции. Обращение к
функции осуществляется по имени с необязательным указанием списка аргументов. Каждый
аргумент должен соответствовать формальным параметрам, указанным в заголовке, и иметь тот
же тип.
Возвращаемое значение функции хранится в регистре eax. Если его размер слишком велик для
размещения в регистре, то оно размещается на верхушке стека, а значение в регистре eax будет
указывать на него.
Правила передачи параметров в функцию:

cdecl – основной способ вызова для С. Аргументы передаются в обратном порядке.
Очистку стека производит вызывающая программа. Это основной способ вызова
функций с переменным числом аргументов (например, printf ()).

pascal – основной способ вызова для Паскаля. Аргументы передаются через стек, в
прямом порядке. Указатель стека на исходную позицию возвращает подпрограмма.
Причём, изменяемые параметры передаются только по ссылке, а у функций неявно
создаётся дополнительный первый изменяемый параметр Result, через который и
возвращается значение. Передача параметра по ссылке означает, что функция или
процедура сможет изменить полученные значения параметров.

stdcall – применяется при вызове функций WinAPI. Аргументы передаются через стек в
обратном порядке. Очисткой стека занимается вызываемая подпрограмма.

fastcall – передача параметров через регистры; обычно самая быстрая. Если все
параметры и промежуточные результаты умещаются в регистрах, манипуляции со
стеком вообще не нужны. Fastcall не стандартизирован, поэтому используется только в
функциях, которые программа не экспортирует наружу. Например, у Borland
параметры передаются слева направо в eax, edx, ecx и, если параметров больше трёх, –
в стеке. Указатель стека на исходное значение возвращает подпрограмма.
9. Способы обращения программы к функциям динамических библиотек в ОС Windows.
Существуют два способа обращения к DLL:


динамический (LoadLibrary, GetProcAddress);
статический (добавление необходимых библиотек в список линкуемых).
С помощью LoadLibrary подгружаем необходимую библиотеку (аргументом LoadLibrary
является имя необходимой DLL). Затем, чтобы вызвать какую-либо функцию из данной
библиотеки, необходимо сначала определить адрес этой функции с помощью GetProcAddress, a
после вызывать функцию через полученный адрес.
Есть программы с пустой таблицей импорта. Соответственно, им не известны адреса
библиотек. Тем не менее, они также могут использовать DLL. Для этого можно воспользоваться
тремя способами:

способ 1. Идти с верхних адресов вниз, ища сигнатуру исполняемого файла (т.к.
библиотека – тот же PE-файл, по сути).

способ 2. Анализ последнего SEH-обработчика (Structured Exception Handling –
структурная обработка исключений), который опять же указывает в kernel32.dll. В
структуре TIB (Thread Information Block) есть поле ExceptionList с линейным
односвязанным списоком обработчиков SEH. Последний обработчик обязательно
укажет в kernel32.dll, т.к. если никто исключение не перехватит, то управление пойдет
в этот обработчик и он выдаст до боли знакомое окно "someprog.exe has encountered a
problem and needs to close. We are sorry for the inconvenience."

способ 3. Подавляющее большинство программ импортируют kernel32.dll, и ее базу
можно найти в списке модулей текущего процесса, который можно взять из Process
Environment Block (PEB), адрес которого, в свою очередь, можно узнать из структуры
Thread Environment Block (TEB), расположенной по адресу FS:[0].
Итак, начиная с FS:[0], располагается структура TEB. В поле PPEB Peb этой структуры
(по адресу fs:[30]) лежит адрес структуры PEB в памяти. В PEB обращаемся к полю со
смещением 0Ch – LoaderData. Это адрес структуры PEB_LDR_DATA. В ней есть три
кольцевых двусвязных списка структур LDR_MODULE:
 InLoadOrderModuleList - список модулей в порядке загрузки;
 InMemoryOrderModuleList - список модулей в порядке расположения в памяти;
 InInitializationOrderModuleList - список модулей в порядке инициализации. Онто нам и нужен, т.к. первые два элемента в нем ntdll.dll и kernel32.dll
Теперь нас интересует поле BaseAddress последнего списка. В списке первой будет
стоять ntdll.dll, а за ней будет kernel32.dll. Поэтому мы получаем голову списка через
PEB_LDR_DATA.InInitializationOrderModuleList.Flink и делаем еще раз переход по
Flink к следующему элементу списка. Преобразовав указатель к типу LDR_MODULE*,
мы получим указатель на структуру описания kernel32.dll, где и будет ее база.
А дальше, получив Loadlibrary и GetProcAddress, можно грузить любые остальные функции.
10. Методы динамического анализа алгоритмов функционирования программных
модулей.
К методам динамического анализа программ относится отладка. Выделяют:


отладчики пользовательского режима (OllyDbg);
отладчики режима ядра (SoftIce).
Используются: трассировка (F8), трассировка с заходом в функции (F7) и исполнение до точки
останова (F9).
Точки останова бывают:
 программные (F2) – используются специально для остановки процессора при
выполнении инструкций, самый частый вид точек останова. Когда отладчик говорит
установить точку останова в желаемый адрес, он сначала читает первый байт опкода
(кода операции) по запрошенному адресу и сохраняет его. Затем отладчик записывает
CC байт по этому адресу. Когда точка останова, или INT 3, срабатывает при
интерпретации процессором опкода CC, отладчик перехватывает это. Затем отладчик
проверяет указывает ли указатель инструкций (регистр EIP) на адрес, на который
предварительно была установлена точка останова. Если адрес находится во внутреннем
списке точек останова, он записывает обратно сохраненный байт по этому адресу,
чтобы опкод мог выполниться правильно, после продолжения выполнения процесса.
Однако программные точки останова имеют одно ограничение: когда вы меняете байт
исполняемого в памяти, вы изменяете контрольную сумму циклического избыточного
кода (Cyclic redundancy code, CRC) выполняемого приложения. CRC - это тип функции,
которая используется для определения изменения данных каким либо способом, и она
может быть применена к файлам, памяти, тексту, сетевым пакетам, или чего-нибудь
еще, за изменением данных которого вам надо наблюдать. CRC возьмет диапазон
данных, в данном случае память выполняемого процесса, и получит хэш содержимого.
Затем она сравнивает хеш с контрольной суммой для определения были ли изменены
данные. Если контрольная сумма отличается от контрольной суммы, которая хранится
для подтверждения, проверка CRC собьется. Важно заметить, как часто вредоносное
ПО будет проверять свой исполняемый код в памяти для любых изменений CRC и
убьёт себя, если обнаружится сбой. Это очень эффективная техника для замедления
реверс-инженерии, таким образом, предотвращается использование программных
точек останова, ограничивая динамический анализ его поведения. Для того чтобы
обойти эти особенности используются аппаратные точки останова.

аппаратные (hardware breakpoints) – полезны, когда нужно установить небольшое число
точек останова, и отлаживаемая программа не может быть модифицирована. Этот тип
точек устанавливается на уровне процессора, в специальных регистрах, называемых
регистрами отладки. Типичный процессор имеет 8 регистров отладки (по порядку с
DR0 до DR7 соответственно), которые используются для установки и управлением
аппаратных точек. Регистры отладки с DR0 до DR3 зарезервированы для адресов точек
останова. Это означает, что вы можете использовать лишь 4 аппаратных точки
одновременно. Регистры DR4 и DR5 зарезервированы, а регистр DR6 используется, как
регистр статуса, который определяет тип события отладки, вызванного встречей точки
останова. Регистр отладки DR7 по существу является выключателем (вкл/выкл)
аппаратных точек останова, а так же хранит разные состояния точек останова. При
установке специальных флагов в регистр DR7, вы можете создать точки останова в
следующих состояниях:



Останов, когда инструкция выполняется по определенному адресу.
Останов, когда данные записываются по адресу.
Останов на чтение или запись, но не выполнение.
В отличие от программных точек останова, которые используют событие INT 3,
аппаратные используют прерывание INT 1.
11. Способы, используемые программными модулями для противодействия отладке.
Способов противодействия отладке существует не меньше, чем отладчиков. Это именно
способы противодействия, поскольку основная их задача сделать работу отладчика либо совсем
невозможной, либо максимально трудоемкой. Опишем основные способы противодействия:
Замусоривание кода программы (обфускация). Способ, при котором в программу вносятся
специальные функции и вызовы, которые выполняют сложные действия, обращаются к
накопителям, но по факту ничего не делают. Типичный способ обмана. Хакера нужно отвлечь,
создав ответвление, которое и будет привлекать внимание сложными вызовами, и содержать в
себе сложные и большие вычисления. Хакер рано или поздно поймет, что его обманывают, но
время будет потеряно.
Использование многопоточности. Тоже эффективный способ защиты, использующий
возможности Windows по параллельному исполнению функций. Любое приложение может идти
как линейно, то есть инструкция за инструкцией, и легко читаться отладчиком, а может
разбиваться на несколько потоков, исполняемых одновременно, естественно, в этом случае, нет
никакого разговора о линейности кода, а раз нет линейности, то анализ здесь трудноосуществим.
Как правило, создание 5-6 и более потоков существенно усложняет жизнь хакеру. А если потоки
еще и шифруются, то хакер надолго завязнет, пытаясь вскрыть приложение.
Подавление изменения операционной среды. Программа сама несколько раз перенастраивает
среду окружения, либо вообще отказывается работать в измененной среде. Не все отладчики
способны на 100% имитировать среду системы, и если "подопытное" приложение будет менять
настройки среды, то рано или поздно "неправильный" отладчик может дать сбой.
Противодействие постановке контрольных точек. Специальный механизм, поддерживаемый
микропроцессором, при помощи которого можно исследовать не всю программу сначала, а,
например, только начиная с середины. Для этого в середине программы ставят специальный вызов
(контрольную точку - Breakpoint), который передает управление отладчику. Недостаток способа
кроется в том, что для осуществления прерывания в код исследуемого приложения надо внести
изменение. А если приложение время от времени проверяет себя на наличие контрольных точек,
то сделать подобное будет весьма и весьма непросто.
Изменение определенных регистров процессора, на которые отладчики неадекватно
реагируют. Также как и со средой. Отладчик тоже программа и тоже пользуется и операционной
системой и процессором, который один на всех. Так, если менять определенные регистры
микропроцессора, которые отладчик не может эмулировать, то можно существенно "подорвать"
его здоровье.
Также используют: блокировку прерываний и устройств, работу с контроллерами через
порты, подсчеты контрольных сумм для выявления контрольных точек, контроль стека,
временные метки и т.д.
12. Способы обнаружения отладчиков.
Основной принцип действия: программа пытается выявить разницу при обычном исполнении
и исполнении под отладчиком. Для этого используются следующие средства:






Выявление флага трассировки (TF, Trap Flag) – пошаговое выполнение.
Выявление точек останова (байт СС).
Регистр tsc – количество тактов процессора от начала работы (значение разное при
отладке).
Использование временных меток. GetTickCount, которая возвращает количество
миллисекунд, прошедших с момента запуска.
Контроль целостности.
IsDebuggerPresent ().Функция IsDebuggerPresent показывает, запущен ли вызывающий
ее процесс в контексте отладчика. Эта функция экспортируется из kernel32.dll. У этой
функции нет аргументов.
Возвращаемое значение:
o Если текущий процесс запущен в контексте отладчика, возвращаемое значение
не равно нулю.
o Если текущий процесс не запущен в контексте отладчика, возвращаемое
значение равно нулю.
Избежать такого простого обхода проверки можно, если разобраться в том, откуда
IsDebuggerPresent берет информацию о наличии отладчика. Лезем дизассемблером в
библиотеку и видим ее код:
77E72740 MOV EAX,DWORD PTR FS:[18] ; TEB
77E72746 MOV EAX,DWORD PTR [EAX+30] ; EAX <- адрес PEB из TEB
77E72749 MOVZX EAX,BYTE PTR [EAX+2] ; EAX <- BeingDebugged
77E7274D RETN
PEB содержит информацию о некоторых параметрах процесса. Если посмотреть
описание этой структуры, можно заметить, что третий байт в ней - это BeingDebugged,
то есть флаг присутствия отладчика. Это значит, что для обнаружения отладчика
можно не вызывать API, а просто где угодно в коде проверять значение BeginDebugged.
Обход этой штуки напрашивается сам собой: сразу после загрузки в отладчик обнулить
флаг BeingDebugged.

Поиск окна (или класса окна) отладчика. FindWindow вернет хэндл окна, если оно
найдено, либо null, если оно не найдено. С помощью этой функции можно искать,
естественно, не только отладчик, но и любую оконную программу, которой пользуются
при взломе (это могут быть, к примеру, мониторы FileMon и RegMon). Соответственно,
обходом этого защитного приема будет подмена результата вызова FindWindow, либо,
более удобный вариант - замена имени и класса окна отладчика.

Поиск по имени процесса. Производится с помощью ToolHlp API, функций
CreateToolhelp32Snapshot, Process32First и Process32Next, которые перечисляют все
доступные процессы и получают для них структуру PROCESSENTRY32, содержащую
полезную информацию о процессе. Получив список можно, например, закрыть
непонравившийся процесс (скажем, если его имя равно имени процесса какого-нибудь
известного отладчика). Еще есть параноидальная проверка у ACProtect: он считает
допустимым свой запуск только от explorer.exe и еще пары учтенных программ.
Проверка работает элементарно. В структуре PROCESSENTRY32 есть поле DWORD
th32ParentProcessID, в котором указан PID процесса-родителя. Если вдруг это поле
равно идентификатору неучтенного протектором процесса, то защита просто-напросто
убивает своего родителя (печальная история, особенно когда хочешь запустить
программу из-под какого-нибудь не очень популярного файлового менеджера). В
последних версиях ACProtect такую проверку убрали, однако ACPR - не единственный
протектор, в котором она была. Обход - переименовать отладчик, к примеру,
OllyDbg.exe в explorer.exe. Правда, тут возникает одна проблемка: плагины будут
искать именно OllyDbg.exe, так как там находятся функции, которые они импортируют,
поэтому в папке должен остаться OllyDbg.exe, будем переименовывать и запускать его
копию.

Поиск сигнатур в памяти процесса. Фактически это модификация предыдущего
способа, только хитрее - тут уже переименование exe'шника отладчика не поможет. Так
же, как и в предыдущем способе, перечисляются процессы с помощью ToolHlp API,
потом каждый открывают с помощью OpenProcess и по некоторым адресам ищут
сигнатуры неугодных программ, используя ReadProcessMemory. Чтобы защититься от
такого обнаружения, можно либо отлавливать вызовы вышеназванных API и
подменять значения, которые они возвращают, либо попробовать убрать из exe'шника
сигнатуры, по которым его могут найти. Последнее позволит запускать целевую
программу (например дампер или импрек), даже если ее поиск осуществляется в
отдельном трейде. Применяется в Execryptor2, ActiveMark.

Баг в OllyDbg при работе с OutputDebugString. В Windows есть несколько функций API,
предназначенных для взаимодействия с отладчиком, одна из них - OutputDebugString.
Она посылает отладчику строку, которую тот в свою очередь показывает
пользователю. В отсутствие отладчика OutputDebugString ничего не делает. Баг
заключается в неправильной обработке посылаемой строки в OllyDbg. Если эта строка
будет вида "%s%s%s%s...", то Olly упадет с ошибкой чтения по адресу 00000001h.
Чтобы не дать уронить Olly, нужно не дать выполниться OutputDebugString с такой
строкой либо пропатчить Olly, чтобы он вообще не реагировал на посылаемые строки.
13. Методы статического анализа алгоритмов функционирования программных модулей.
Статический анализ кода – анализ ПО, проводимый без реального выполнения исследуемых
процедур.
Так дизассемблирование – это статический анализ программы, представляющий код в виде
текста на некотором мнемоническом языке (чаще – на языке Ассемблера).
Наиболее известный дизассемблер – IDA Pro. Позволяет отслеживать переменные, давать им
осмысленные имена, отслеживать ссылки на функции, точки входа.
У IDA есть возможность написания скриптов, что упрощает выполнение однотипных участков
кода.
14. Способы, используемые программными модулями для противодействия
дизассемблированию.
Существует несколько методов противодействия дизассемблированию:

Шифрование. Зашифрованную программу невозможно дизассемблировать без
расшифрования. Зашифрование (расшифрование) программ может осуществляться
аппаратными средствами или отдельными программами. Такое шифрование
используется перед передачей программы по каналам связи или при хранении ее на
ВЗУ. Дизассемблирование программ в этом случае возможно только при получении
доступа к расшифрованной программе, находящейся в ОП перед ее выполнением.
Другой подход к защите от дизассемблирования связан с совмещением процесса
расшифрования с процессом выполнения программ. Если расшифрование всей
программы осуществляется блоком, получающим управление первым, то такую
программу расшифровать довольно просто. Гораздо сложнее расшифровать и
дизассемблировать программу, которая поэтапно расшифровывает информацию, а
этапы разнесены по ходу выполнения программы. Задача становится еще более
сложной, если процесс расшифрования разнесен по тексту программы.

Использование самогенерируемых кодов – способ написания программы таким
образом, что исполняемые коды программы получаются самой программой в процессе
ее выполнения. Самогенерируемые коды получаются в результате определенных
действий над специально выбранным массивом данных. В качестве исходных данных
могут использоваться исполняемые коды самой программы или специально
подготовленный массив данных. Данный метод показал свою высокую эффективность,
но он сложен в реализации.

«Обман» дизассемблера – стиль программирования, который вызывает нарушение
правильной работы стандартного дизассемблера за счет нестандартных приемов
использования отдельных команд, нарушения общепринятых соглашений. «Обман»
дизассемблера осуществляется следующими способами:
o нестандартная структура программы;
o скрытые переходы, вызовы процедур, возвраты из них и из прерываний;
o переходы и вызовы подпрограмм по динамически изменяемым адресам;
o модификация исполняемых кодов.
Для дезориентации дизассемблера часто используются скрытые переходы, вызовы и
возвраты за счет применения нестандартных возможностей команд. Маскировка
скрытых действий часто осуществляется с применением стеков.
15. Программы-упаковщики. Принципы работы. Методы снятия упаковки с
программных модулей.
Упаковщик - программа для сжатия исполняемого файла, которая отличается от архиватора
лишь тем, что добавляет код распаковщика в тело программы. При запуске управление передается
этому коду вплоть до полной распаковки программы в память. Изначальное предназначение –
уменьшение размера программ.
Средства распаковки:

Статические. Программы, знающие алгоритм упаковки/распаковки, выцепляют данные
исходной программы. Надежны, но часто меняются программы-упаковщики.
Преимущество – сама программа не получает управления. Следовательно, можно
полностью контролировать процесс.

Динамические. Запускают программу под собственной отладкой, дают выполняться
распаковочному коду внутри нее, затем сохраняют в виде исполняемого файла. Так как
код исследуемой программы получает право на управление, то он может выйти за
пределы распаковщика и выполниться в том объеме, в котором посчитает нужным.

Ручной способ. Программа запускается под отладчиком. Обход защиты. Получение в
памяти исходного кода. После запуска программы с физического носителя
операционная система проецирует образ программы в оперативную память (точнее, в
определенное адресное пространство памяти). В этом случае сам код располагается в
памяти с началом по адресу так называемого значения Image base (то есть Image Base адрес в памяти, начиная с которого идет код программы). Однако начало проекции
совсем не означает начало исполняемого кода. И вот уже в проекции существует Точка
входа (EP - Entry Point) в программу - то место, откуда начинается выполнение
исходника. Упаковщик добавляет свой код в тело программы, поэтому на сцене
появляется понятие Оригинальная точка входа (OEP). Как уже можно было догадаться,
это точка входа в изначальную программу, если бы она не была запакована.
Естественно, нам необходимо найти это место, снять дамп памяти исходной
программы и сохранить его на диск.
Нахождение OEP: при стандартном начале Windows-программ указатель на верхнюю
часть стека один и тот же и равен значению регистра esp. Когда упаковщик передает
управление первоначальной программе, указатель восстанавливается, но перед этим,
как правило, упаковщик считывает из стека значение esp-4. Соответственно, за этой
командой, вероятно, будет следовать OEP.
Для снятие дампа существует различный софт. Например, PETools и LordPE.
Нам предстоит еще восстановить таблицу импорта.
Импорт можно восстановить с помощью еще Import Recontructor. Запустим
упакованную программу, затем найдем ее в листе процессов ImpRec'а. Теперь нам
необходимо указать RVA OEP (в ImpRec он просто OEP). "RVA OEP = VA OEP ImageBase". Image Base мы сможем найти, нажав на кнопку PE Editor в LordPE. Вводим
значение RVA в поле OEP и жмем автопоиск, то есть IAT AutoSearch. После этого
нажимаем Get Imports. Мы должны увидеть строки с функциями и надписью YES
напротив. Останется только указать наш дамп и после нажатия на ОК получить
распакованную программу.
Можно восстанавливать таблицу импорта вручную. Как известно, функции в
исполняемом файле находятся в виде адресов. Упаковщик сохраняет всю таблицу.
Следовательно, по адресам в простом HEX-редакторе мы сможем обнаружить таблицу
импорта в упакованном файле.
Download