Об одном методе маскировки программ

       

Генерация несущественного кода


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

Этап генерации несущественного кода состоит из следующих подэтапов:

  • Генерация пула типов.

  • Генерация пула переменных.

    Генерация несущественного кода.

Генерация пула типов. Подготавливаются определения типов, которые затем будут использоваться в холостом коде. Типы для холостого кода строятся одним из следующих способов:

Непосредственно используются типы, определённые в маскируемой программе.

Используются встроенные типы.

Из встроенных типов и типов, определённых в маскируемой программе, путём встраивания в шаблонные типы маскирующего компилятора. Например, из типа t, определённого в маскируемой программе, могут быть построены типы массива элементов типа t, списки, деревья с элементами типа t.

Модификацией структурных типов, определённых в маскируемой программе.

Для каждого типа, добавляемого в маскируемую программу, в маскирующем компиляторе строится класс-реализатор, на который возлагаются все функции по генерации инструкций для манипуляций с этим типом. Класс-реализатор должен удовлетворять интерфейсу TypeImplementer, который приведён на рис. 6.

Каждая несущественная переменная, добавленная в маскируемую функцию, в маскирующем компиляторе представлена классом, реализующим интерфейс VarImplementer, который показан на рис. 7.

Генерация пула переменных. Для генерации пула "холостых" переменных используются типы, построенные на предыдущей стадии. Переменные размещаются как на уровне локальных переменных функции, так и на уровне глобальных переменных.

Генерация несущественного кода. Для генерации несущественного кода используются множества операций, которые были получены с помощью методов getBinaryOps, getUnaryOps, getAssignOps интерфейса TypeImplementer.
Инструкции несущественного кода размещаются в базовых блоках вперемешку с инструкциями исходной функции и управляющими инструкциями.

interface TypeImplementer { public boolean requiresGlobalInit(); public boolean isSimple(); public boolean isUsable(); public MIFInstr emitType(MIFInstr p); public VarImplementer newVar(); public Set getBinaryOps(); public Set getUnaryOps(); public Set getAssignOps(); }

Рис. 6. Интерфейс для реализатора типов

interface VarImplementer { public TypeImplementer getType(); public MIFElem emitGlobalDecl(MIFElem p); public MIFElem emitLocalDecl(MIFElem p); public MIFElem emitInit(MIFElem p); public MIFElem emitFini(MIFElem p); }

Рис. 7 Интерфейс для реализатора переменных

"Перемешивание" управляющих инструкций. Управляющими инструкциями назовём инструкции функции, выполняющиеся при вычислении возвращающих предикатов и счётчиков, то есть инструкции, необходимые для того, чтобы расширенный граф потока управления замаскированной программы всегда выполнялся как граф потока управления исходной функции. На этом шаге все управляющие инструкции передвигаются на как можно большее расстояние от точки в программе, в которую они были изначально помещены. Границы, в пределах которых можно двигать инструкции, определяются зависимостями по данным управляющих инструкций друг от друга и зависимостями по управлению.


Содержание раздела