Речь пойдёт о том, как можно спрятать «лишние» ассемблерные команды в обычном коде. Данный метод полезен для усложнения дизассемблирования кода, особенно, если генерацию «скрытых» команд автоматизировать.
Инструментарий: отладчик OllyDbg.
Странный странный код
Взглянем на следующий код, в котором скрыто намного больше команд, чем видно на первый згляд:
MOV EAX,1EBC031
MOV EBX,90DB3190
CMP EAX,EBX
JNE SHORT 0000009E
NOP
(возможно, будет трудно вставить данный фрагмент, кода с помощью ассемблера в OllyDbg, поэтому советую воспользоваться встроенным HEX-редактором: “B8 31 C0 EB 01 BB 90 31 DB 90 39 D8 75 F3 90” )
Как вы думаете, данный код будет выполняться бесконечно, так как EAX и EBX не равны, а команда JNE будет делать переход в район первой команды пока эти два регистра не будут одинаковы?
Давайте поставим break-point на последний оператор NOP, запустим и посмотрим результат.
Ставим break-point (адрес подсвечен красным, позиция выполнения — чёрным), нажимаем кнопку запуска.
После запуска процесс выполнения остановился в нужной точке.
Как ни странно, но программа не зависла и значение регистра EAX и EBX равны нулю. Но как же это могло произойти?
Посмотрим на код повнимательнее. Первая команда помещает в регистр EAX значение 1EBC031, вторая команда помещает в EBX значение 90DB3190. CMP сравнивает два регистра. JNE делает переход, если значения регистров не совпадают. А вот тут самое интересное — переход делается не в начало первой команды, а на второй байт первой команды. Давайте будем трассировать код по одной команде.
Ставим на первую позицию.
Выполняются команды, как обычно.
… и так доходим до перехода.
Выполнился короткий переход, и что же мы видим? Две спрятанные команды — XOR EAX,EAX выполняет обнуление регистра EAX.
И короткий переход, который передаёт управление внутрь другого MOV-а.
Ещё место осталось :)
Обнуляем второй регистр
Доходим до проверки. В это время регистры EAX и EBX равны нулю.
Успешно проходит проверка.
Дело в том, что в регистр EAX записано не простое число, а машинный код команды. Из-за таких «накладок» код практически не возможно представить на языке ассемблера. Ассемблерные команды имеют разный размер. Команда «MOV EAX,1EBC031» занимает 5 байт, когда как команда XOR EAX,EAX — 2 байта. С помощью команд бинарного сдвига и, используя полу-регистры AL и AH, можно такими «прыжками» набрать обработку и ввод целого регистра EAX.
Хочу отметить, что при вставке машинных команд, как параметр MOV, нужно менять последовательность байтов, так например команда 31CO(XOR EAX,EAX), помещённая в команду MOV будет выглядеть как «MOV EAX,C031».
Данный метод применяется во многих системах защиты программного обеспечения, затрудняя поиски перехода. Так же, значения, занесённые в регистры можно применять для простого шифрования методом XOR. Думаю, можно создать программу, автоматизирующую процесс перекомпилирования куска ассемблерного кода на такие небольшие фрагменты, что точно затруднит процесс обратной инженерии.
Кроме 32-х разрядных регистров, на 64-х битных системах есть 64-х разрядные регистры, это: RAX,RBX,RCX,RDX. В них можно поместить в 2 раза больше команд.
Вставка ассемблерного кода в приложение
Можно воспользоваться стандартными средствами среды разработки высокого уровня: для C++ — этот код будет выглядеть так:
int someint=123;
__asm {
MOV EAX, someint
INC EAX
// ...
MOV someint,EAX
}
для Delphi:
Var someint:Integer; somelong:Longint;
begin
asm
mov ax,i
mov ebx,somelong
// ...
end;
end.
Можно воспользоваться и OllyDbg, предварительно отведя место для кода.
При этом не стоит забывать, что программы, в которые вы вставляете кусок ассемблерного кода, сами используют некоторые регистры, так что не забывайте сохранять значения регистров до их изменения (PUSH) и восстанавливать их по завершению (POP).
Автор: godAlex