Следующая новость
Предыдущая новость

Виртуалка своими руками. Как обфусцировать код при помощи виртуализации и что делать с такой защитой

20.02.2019 16:23
Виртуалка своими руками. Как обфусцировать код при помощи виртуализации и что делать с такой защитой

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

INFO

Читай другие статьи автора по теме дебага и защиты от него.

  • Тайный WinAPI. Как обфусцировать вызовы WinAPI в своем приложении
  • Детект песочницы. Учимся определять, работает ли приложение в sandbox-изоляции
  • Антиотладка. Теория и практика защиты приложений от дебага
  • Набор программ для взлома программ. Выбираем инструменты для реверса

Здесь я имею в виду не виртуальные машины вроде VirtualBox или VMware, а те, при помощи которых запутывают исполняемый код, чтобы затруднить анализ программной логики. В этой статье мы коснемся принципов работы виртуальных машин, компиляторов, трансляции кода, а также напишем свою виртуальную машину, которая будет понимать наш собственный язык программирования.

Итак, виртуальные машины, предназначенные для запутывания кода, основаны на идее замены «обычного» байт-кода, который, например, используется в архитектуре x86-64, на тот байт-код, который мы изобретем сами. Чтобы реконструировать поток управления в программе, подвергшейся виртуализации, необходимо проанализировать каждый опкод и разобраться, что он делает. Чтобы понимать, что происходит, нужно немного коснуться работы процессора — ведь, по сути, перед нами стоит задача «написать процессор».

Нам предстоит написать некое подобие транслятора-интерпретатора кода — чтобы исходный код, который мы будем писать, начал обрабатываться внутри нашей виртуальной машины. Можно провести аналогию с процессорами: современные процессоры представляют собой сложные устройства, которые управляются микрокодом. Многие наборы инструкций, особенно современные, типа Advanced Vector Extensions (AVX), — это, по сути, подпрограммы на микрокоде процессора, который, в свою очередь, напрямую взаимодействует с железом процессора.

Получается, что современные процессоры похожи больше на софт, а не на железо: сложные инструкции типа VBROADCASTSS, VINSERTF128, VMASKMOVPS реализованы исключительно «софтверно» при помощи программ, состоящих из микрокодов. А таких наборов инструкций, как ты, возможно, знаешь, много — достаточно открыть техническое описание какого-нибудь Skylake и посмотреть на поддерживаемые наборы инструкций.

Виртуалка своими руками. Как обфусцировать код при помощи виртуализации и что делать с такой защитой

INFO

Микропрограммы процессора состоят из микроинструкций, а они, в свою очередь, реализуют элементарные операции процессора — операции, которые уже нельзя разделить на более мелкие, например работа с арифметико-логическим устройством (АЛУ) процессора: подсоединение регистров к входам АЛУ, обновление кодов состояния АЛУ, настройка АЛУ на выполнение математических операций.

Стековая виртуальная машина

Нам необходимо будет эмулировать, помимо работы процессора, работу памяти (RAM). Для этого мы воспользуемся реализацией собственного стека, который будет работать по принципу LIFO.

Виртуалка своими руками. Как обфусцировать код при помощи виртуализации и что делать с такой защитой

INFO

LIFO (last in, first out) — способ организации хранения данных, который похож на стопку журналов на столе: если нужный журнал лежит в середине стопки, нельзя его просто вытащить, можно только поочередно убирать журналы сверху и так до него добраться. Получается, мы всегда работаем только с верхушкой этой стопки.

В этом нет ничего сложного — по сути, это просто массив данных с указателем на них. Для наглядности код:

// Размер памяти VM const int MAXMEM = 5; // Массив памяти, который состоит из элементов типа int int stack[MAXMEM]; // Указатель на положение данных в стеке, сейчас стек не инициализирован int sp = -1; 

Этот стек станет оперативной памятью нашей виртуальной машины. Чтобы путешествовать по нему, достаточно обычных операций с массивами:

stack[++sp] = data1;      // Положим данные int data2 = stack[--sp];  // Возьмем данные 

Далее, чтобы наша память не «сломалась», нам необходимо позаботиться о проверках, чтобы не срабатывали попытки взять данные, когда память пуста, либо положить больше данных, чем она может вместить.

// Проверка стека на пустоту // Функция вернет TRUE (1), если стек пуст, // и FALSE (0), если данные есть int empty_sp() {   return sp == -1 ? 1 : 0; }  // Проверка стека на заполненность // Функция вернет TRUE (1), если стек полон, // и FALSE (0), если место еще есть int full_sp() {   return sp == MAXMEM ? 1 : 0; } 

Как видишь, никакой магии нет! Мы успешно запрограммировали память для нашей будущей VM. Далее переходим к командам. Создадим перечисление под названием mnemonics и заполним его инструкциями для нашей VM (читай комментарии):

Продолжение доступно только подписчикам

Материалы из последних выпусков можно покупать отдельно только через два месяца после публикации. Чтобы продолжить чтение, необходимо купить подписку.

Подпишись на «Хакер» по выгодной цене!

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

1 год

7190 р.

Экономия 1400 рублей!

1 месяц

720 р.

25-30 статей в месяц

Уже подписан?

Источник

Последние новости