Автоматическая запутывание инструкций x86 - PullRequest
7 голосов
/ 30 октября 2011

Я работаю над x86 asm-обфускатором, который принимает синтаксический код Intel в виде строки и выводит эквивалентный набор кодов операций, которые запутаны.

Вот пример:

mov eax, 0x5523
or eax, [ebx]
push eax
call someAPI

Становится что-то вроде:

mov eax, 0xFFFFFFFF ; mov eax, 0x5523
and eax, 0x5523     ;
push [ebx]          ; xor eax, [ebx]
or [esp], eax       ;
pop eax             ;
push 12345h         ; push eax
mov [esp], eax      ;
call getEIP         ; call someAPI
getEIP:             ;
add [esp], 9        ;
jmp someAPI         ;

Это всего лишь пример, я не проверял, что это не портит флаги (возможно, так и есть).

Сейчас у меня есть XML-документ, в котором перечислены шаблоны команд (например, push e*x) и список инструкций по замене, которые можно использовать.

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

1 Ответ

15 голосов
/ 31 октября 2011

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

Затем для каждой инструкции вы просматриваете ее алгебраическое описание (для примера,

 XOR  eax,mem[ecx]

с алгебраическим эквивалентом

 eax exclusive_or mem[ecx]

перечислить алгебраические эквивалентности, используя такие алгебраические эквиваленты, как:

 a exclusive_or b ==> (a and not b) or (b and not a)

для генерации эквивалентного алгебраического оператора для вашей инструкции XOR

 eax exclusive_or mem[ecx] ==> (eax and not mem[ecx]) or (mem[ecx] and not eax)

Вы можете применить к этому более алгебраические законы, например, теорема де Моргана:

 a or b ==> not (not a and not b)

чтобы получить

(not (not (eax and not mem[ecx])) and (not (mem[ecx] and not eax)))

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

Теперь вы должны «скомпилировать» это в машинные инструкции, сопоставляя какие инструкции будет делать то, что это говорит. Как и любой компилятор, вы, вероятно, хотите оптимизировать сгенерированный код (нет смысла извлекать mem [ecx] дважды). (Все это сложно ... это генератор кода!) Результирующая последовательность кода будет выглядеть примерно так:

mov ebx, mem[ecx]
mov edx, ebx
not edx
and edx, eax
not eax
and eax, ebx
not eax
or eax, edx

Это много машин для сборки вручную.

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

Одним из этих инструментов является наш набор инструментов для реинжиниринга программного обеспечения DMS .

DMS принимает определение языка (по сути, как EBNF), автоматически реализует синтаксический анализатор, сборщик AST и prettyprinter (антипарсер, возвращающий AST обратно в допустимый исходный текст). [DMS в настоящее время не имеет EBNF для ASM86, но десятки EBNF для различных сложные языки были созданы для DMS, включая несколько для разных ассемблеров не x86 Таким образом, вы должны определить ASM86 EBNF для DMS. Это довольно просто; DMS имеет действительно сильный генератор парсеров].

Используя это, DMS позволит вам писать исходные преобразования непосредственно в коде. Вы можете написать следующие преобразования, которые напрямую реализуют эквивалент XOR и закон Деморгана:

  domain ASM86;

  rule obfuscate_XOR(r: register, m: memory_access):instruction:instruction
  =  " XOR \r, \m " 
      rewrites to
     " MOV \free_register\(\),\m
       NOT \free_register\(\)
       AND \free_register\(\),\r 
       NOT \r
       AND \r,\m
       OR \r,\free_register\(\)";

 rule obfuscate_OR(r1: register, r2: register):instruction:instruction
 = " OR \r1, \r2"
     rewrites to
    " MOV \free_register\(\),\r1
      NOT \free_register\(\)
      AND \free_register\(\),\r2
      NOT \r2
      AND \r1,\r2
      NOT \r1";

с некоторой дополнительной магией в мета-процедуре под названием "free_register", которая определяет, какие регистры свободны в этот момент (от совпадения AST) в коде. (Если вы не хотите этого делать, используйте вершину стека так же временно, как и в вашем примере).

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

Затем можно попросить механизм преобразования применить эти преобразования случайным образом один или несколько раз в каждой точке кода для его шифрования.

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

...