Динамическое изменение кода программы
Данный метод является основным методом противодействия дизассемблированию
программы, трудно представить себе защиту от дизассемблирования, не использующую этот метод. Метод основан на том, что код программы, за исключением небольшой части — распаковщика, хранится в исполняемом файле в искаженном виде, а преобразуется к нормальному виду лишь в оперативной памяти, в ходе выполнения программы. При попытке дизассемблировать программу с динамически изменяемым кодом дизассемблер правильно дизассемблирует только распаковщик (если в отношении распаковщика
не применены другие методы защиты от анализа), а большую часть кода программы интерпретирует как данные. Суть преобразования кода может быть различной. В простейшем случае используются стандартные программы-упаковщики наподобие
UPX, которые, помимо защиты от дизассемблирования, дают еще один приятный побочный эффект — исполняемый файл программы занимает в 1,5—4 раза меньше, чем до упаковки, впрочем, это преимущество в значительной степени компенсируется увеличивающимся временем загрузки упакованного программного модуля. В более
сложных защитах динамическое архивирование кода дополняется шифрованием. Ключ шифрования может быть жестко фиксированным либо поступать из какого-то внешнего источника. Например, если программа защищена от копирования с помощью внешнего
аппаратного устройства, поставляемого вместе с программой и подключаемого к одному из портов компьютера перед запуском программы, ключ, необходимый для распаковки кода программы, может считываться с этого устройства. Если распаковка кода выполняется только один раз при запуске программы, аналитик все-таки может дизассемблировать программу взяв в качестве входных данных содержимое оперативной памяти в тот момент, когда программа полностью распакована. Для повышения эффективности защиты кода от анализа преобразование кода выполняют по частям. В распакованном состоянии находятся лишь те фрагменты кода программы, которые выполняются в данный момент либо будут выполняться в ближайшем будущем, а все фрагменты кода, выполнение которых в ближайшее время не планируется, снова преобразуются в упакованный вид либо вообще вытесняются из памяти. Если в программе реализована данная схема, дизассемблирование оперативной памяти, занимаемой программой, не приводит к успеху ни в какой момент времени. Динамическое изменение кода программы, выполняемое не одномоментно, а регулярно, фактически делает программу оверлейной — и каждый момент времени большая часть кода программы недоступна для отладочных средств. Поэтому данный способ динамического
изменения кода позволяет обеспечить защиту не только от статического, но и от динамического метода анализа кода. Особенно мощная защита обеспечивается в том случае, если в программе поддерживается несколько различных алгоритмов модификации кода, и при каждой упаковке фрагмента кода алгоритм упаковки выбирается случайным образом (при распаковке кода выбор алгоритма однозначен).
Также повышает эффективность защиты от анализа случайный выбор адресов оперативной памяти, по которым размещаются распакованные фрагменты кода.
Пожалуй, наиболее мощной модификацией данного метода является применение полиморфных преобразований кода, когда преобразование модифицируемого кода не является взаимно однозначным, т. е. после упаковки и последующей распаковки кода получается код, не идентичный оригиналу, но выполняющий те же самые действия.
Перечислим некоторые наиболее простые полиморфные преобразования:
• «засеивание» кода «пустышками» — командами или наборами
команд, не выполняющими никаких действий, например:
nор
или
xchg eax, ebx
xchg ebx, eax
или
pushf
add eax, ebx
pub eax, ebx
popf
• вставка в код команд условных переходов на случайные адреса по
тождественно ложным условиям:
pushf
хоr еах, еах
jnz RandomAddress
popf
• замена команд синонимами, например замена
mov еах, ebx
на
pushf
push ebx
sub ebx, eax
add eax, ebx
pop ebx
popf
• замена регистров и (или) локальных переменных, используемых
командами, например замена
mov еах, [ebp - 4]
mov ebx,[еах + 4]
mov [ebp-8], ebx
на
mov ebx, [ebp - 8]
mov eax, [ebx + 4]
mov [ebp-4], eax
Существуют и другие, более сложные полиморфные преобразования, но они применяются очень редко, поскольку аккуратная и безошибочная реализация даже простейших полиморфных преобразований, перечисленных выше, является серьезным испытанием для программиста. Даже самая мелкая и незначительная ошибка в алгоритме преобразования кода рано или поздно приводит к краху защищаемой программы, причем локализовать место ошибки, как правило, весьма сложно.