...

воскресенье, 23 февраля 2014 г.

Техники обфускации кода при помощи LLVM

image

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



Введение




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

Используемый софт:

GCC 4.8.2 (mingw64)

IDA DEMO

Clang 3.4

LLVM 3.4


Что возможно реализовать при помощи LLVM?

1) Cлучайный CFG

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


Примеры
Оригинал

#include <stdio.h>
#include <stdlib.h>
int rand_func()
{
return 5+rand();;
}
int main()
{
int a = rand_func();
goto test;
exit(0);
test:
int b = a+a;
}




Оригинальный граф

image

Обфускация 1 запуск, функция main

image

Обфускация 2 запуск, функция main

image

Обфускация 3 запуск, функция main

image




2) Вставка огромного количества базовых блоков в CFG, с необязательным исполнением.(Смотрите скриншоты с п.1)

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

3) Замусоривание кода.

Предположим, что у нас есть определенный код, он разбавляется мусорными инструкциями которые пытаются имитировать свою полезность. Они могут обращаться/изменять наши данные, не влияя на исполнение программы в целом. Цель максимально усложнить анализ нашего приложения, с минимальной потерей в производительности.

Один из множества доступных примеров


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void test()
{
int a = 32;
int b = time(0);
int c = a+a+b;
int d = a-b+c*2;
printf("%d",d);
}
int main()
{
test();
}




image




4) Скрытие констант, данных.

Предположим, что у нас есть константа 15h, мы делаем так, что в нативном коде константа будет формироваться во время исполнения и не встречается в открытом виде.Так же константные данные могут скрываться при помощи любого алгоритма шифрования.

Пример


#include <stdio.h>
#include <stdlib.h>

int main()
{
const char *habr = "habrahabr";
printf("%s",habr);
}




Находим константные данные, а именно habrahabr, и вставляем свой расшифровщик данных. На изображении пример с xor-ом, но можно добавить любой алгоритм шифрования (AES,RC4 и тд.) Данные после использования (printf) на стеке будут зашифрованы случайным ключем.

image

Предположим вы захотели добавить шифрование данных, как это сделать проще всего?

LLVM умеет генерировать из cpp файлов свой код, который вы можете вставить в свой проект.

Смотрите подсказку в разделе ответы на вопросы.




5) Клонирование функций и использование их в случайном порядке.

Одна и та же функция клонируются на множество(с возможными изменениями), на месте кода вызова вставляется свой обработчик, функции вызывается в случайном порядке.

6) Объединение функций.

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

7) Организация из кода конечного автомата или свитча.

Создается новый базовый блок, который становится точкой входа для main функции, от него создается ветвления (возможно на основе переменной) к другим базовым блокам.

Пример
Оригинальный код.

#include <stdio.h>
#include <stdlib.h>

int rand_func()
{
return 5+rand();;
}

int main()
{
const char *habr = "habrahabr";
printf("%s",habr);
int a = rand_func();
goto test;
exit(0);
test:
int b = a+a;
}




Первый раз.

image

Второй раз.

image




8) Создание из кода псевдоциклов.

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

9) Создается случайная виртуальная машина, весь существующий код трансформируется под нее, для меня этот пункт возможен пока что только в теории.

С чего начать изучение?




Посмотреть список доступных трехадресных команд
//===-- llvm/Instruction.def — File that describes Instructions -*- C++ -*-===//

//

// The LLVM Compiler Infrastructure

//

// This file is distributed under the University of Illinois Open Source

// License. See LICENSE.TXT for details.

//

//===----------------------------------------------------------------------===//

//

// This file contains descriptions of the various LLVM instructions. This is

// used as a central place for enumerating the different instructions and

// should eventually be the place to put comments about the instructions.

//

//===----------------------------------------------------------------------===//

FIRST_TERM_INST ( 1)

HANDLE_TERM_INST ( 1, Ret, ReturnInst)

HANDLE_TERM_INST ( 2, Br, BranchInst)

HANDLE_TERM_INST ( 3, Switch, SwitchInst)

HANDLE_TERM_INST ( 4, IndirectBr, IndirectBrInst)

HANDLE_TERM_INST ( 5, Invoke, InvokeInst)

HANDLE_TERM_INST ( 6, Resume, ResumeInst)

HANDLE_TERM_INST ( 7, Unreachable, UnreachableInst)

LAST_TERM_INST ( 7)


// Standard binary operators…

FIRST_BINARY_INST( 8)

HANDLE_BINARY_INST( 8, Add, BinaryOperator)

HANDLE_BINARY_INST( 9, FAdd, BinaryOperator)

HANDLE_BINARY_INST(10, Sub, BinaryOperator)

HANDLE_BINARY_INST(11, FSub, BinaryOperator)

HANDLE_BINARY_INST(12, Mul, BinaryOperator)

HANDLE_BINARY_INST(13, FMul, BinaryOperator)

HANDLE_BINARY_INST(14, UDiv, BinaryOperator)

HANDLE_BINARY_INST(15, SDiv, BinaryOperator)

HANDLE_BINARY_INST(16, FDiv, BinaryOperator)

HANDLE_BINARY_INST(17, URem, BinaryOperator)

HANDLE_BINARY_INST(18, SRem, BinaryOperator)

HANDLE_BINARY_INST(19, FRem, BinaryOperator)


// Logical operators (integer operands)

HANDLE_BINARY_INST(20, Shl, BinaryOperator) // Shift left (logical)

HANDLE_BINARY_INST(21, LShr, BinaryOperator) // Shift right (logical)

HANDLE_BINARY_INST(22, AShr, BinaryOperator) // Shift right (arithmetic)

HANDLE_BINARY_INST(23, And, BinaryOperator)

HANDLE_BINARY_INST(24, Or, BinaryOperator)

HANDLE_BINARY_INST(25, Xor, BinaryOperator)

LAST_BINARY_INST(25)


// Memory operators…

FIRST_MEMORY_INST(26)

HANDLE_MEMORY_INST(26, Alloca, AllocaInst) // Stack management

HANDLE_MEMORY_INST(27, Load, LoadInst ) // Memory manipulation instrs

HANDLE_MEMORY_INST(28, Store, StoreInst )

HANDLE_MEMORY_INST(29, GetElementPtr, GetElementPtrInst)

HANDLE_MEMORY_INST(30, Fence, FenceInst )

HANDLE_MEMORY_INST(31, AtomicCmpXchg, AtomicCmpXchgInst )

HANDLE_MEMORY_INST(32, AtomicRMW, AtomicRMWInst )

LAST_MEMORY_INST(32)


// Cast operators…

// NOTE: The order matters here because CastInst::isEliminableCastPair

// NOTE: (see Instructions.cpp) encodes a table based on this ordering.

FIRST_CAST_INST(33)

HANDLE_CAST_INST(33, Trunc, TruncInst ) // Truncate integers

HANDLE_CAST_INST(34, ZExt, ZExtInst ) // Zero extend integers

HANDLE_CAST_INST(35, SExt, SExtInst ) // Sign extend integers

HANDLE_CAST_INST(36, FPToUI, FPToUIInst ) // floating point -> UInt

HANDLE_CAST_INST(37, FPToSI, FPToSIInst ) // floating point -> SInt

HANDLE_CAST_INST(38, UIToFP, UIToFPInst ) // UInt -> floating point

HANDLE_CAST_INST(39, SIToFP, SIToFPInst ) // SInt -> floating point

HANDLE_CAST_INST(40, FPTrunc, FPTruncInst ) // Truncate floating point

HANDLE_CAST_INST(41, FPExt, FPExtInst ) // Extend floating point

HANDLE_CAST_INST(42, PtrToInt, PtrToIntInst) // Pointer -> Integer

HANDLE_CAST_INST(43, IntToPtr, IntToPtrInst) // Integer -> Pointer

HANDLE_CAST_INST(44, BitCast, BitCastInst ) // Type cast

LAST_CAST_INST(44)


// Other operators…

FIRST_OTHER_INST(45)

HANDLE_OTHER_INST(45, ICmp, ICmpInst ) // Integer comparison instruction

HANDLE_OTHER_INST(46, FCmp, FCmpInst ) // Floating point comparison instr.

HANDLE_OTHER_INST(47, PHI, PHINode ) // PHI node instruction

HANDLE_OTHER_INST(48, Call, CallInst ) // Call a function

HANDLE_OTHER_INST(49, Select, SelectInst ) // select instruction

HANDLE_OTHER_INST(50, UserOp1, Instruction) // May be used internally in a pass

HANDLE_OTHER_INST(51, UserOp2, Instruction) // Internal to passes only

HANDLE_OTHER_INST(52, VAArg, VAArgInst ) // vaarg instruction

HANDLE_OTHER_INST(53, ExtractElement, ExtractElementInst)// extract from vector

HANDLE_OTHER_INST(54, InsertElement, InsertElementInst) // insert into vector

HANDLE_OTHER_INST(55, ShuffleVector, ShuffleVectorInst) // shuffle two vectors.

HANDLE_OTHER_INST(56, ExtractValue, ExtractValueInst)// extract from aggregate

HANDLE_OTHER_INST(57, InsertValue, InsertValueInst) // insert into aggregate

HANDLE_OTHER_INST(58, LandingPad, LandingPadInst) // Landing pad instruction.

LAST_OTHER_INST(58)






Следует ознакомиться со следующей документацией:

LLVM-CheatSheet

LLVM Programmers Manual

LLVM-CheatSheet 2

LLVMBackendCPU

Obfuscating c++ programs via CFF

Cтоит посмотреть публичные реализации обфускации кода для ознакомления.

1) Obfuscator-llvm

Реализована замена инструкций, уплотнение графа исполнения.

2) Kryptonite

Реализована замена инструкций аналогами / разложение инструкций.


Сниппеты




Для того чтобы вставить асм инструкции можно использовать llvm::InlineAsm или в сторону MachinePass, через машинные проходы можно изменять, добавлять инструкции. Неплохой пример есть тут.
немного полезного кода для начала
Как прочесть байткод файл?

std::string file = "1.bc";
std::string ErrorInfo;
llvm::LLVMContext context;
llvm::MemoryBuffer::getFile(file.c_str(), bytecode);
llvm::Module *module = llvm::ParseBitcodeFile(bytecode.get(), context, &error);




Как сделать итерацию функций в модуле?

for (auto i = module->getFunctionList().begin(); i != module->getFunctionList().end(); ++i)
{
printf("Function %s",i->getName().str());
}




Как проверить на принадлежность к какой-то инструкции?

if (llvm::isa<llvm::BranchInst>(currentInstruction))
printf("BranchInst!");




Как заменить терминатор другой инструкцией?

llvm::BasicBlock *block = (инициализация)
block->replaceAllUsesWith(инструкция которой заменяем);




Как сделать приведение одной инструкции к другой?

llvm::Instruction* test = basicBlock->getTerminator();
llvm::BranchInst* branchInst = llvm::dyn_cast<llvm::BranchInst>(test)




Как получить первую не phi инструкцию в базовом блоке?

llvm::Instruction *inst = currentInstruction->getParent()->getFirstNonPHI()




Как итерировать инструкции в функции?

for(llvm::inst_iterator i = inst_begin(function); i != inst_end(function); i++)
{
llvm::Instruction* inst = &*i;
}




Как узнать используется ли инструкция где то еще?

bool IsUsedOutsideParentBlock(llvm::Instruction* inst)
{
for(llvm::inst::use_iterator i = inst->use_begin(); i != inst->use_end(); i++)
{
llvm::User* user = *i;
if(llvm::cast<llvm::Instruction>(user)->getParent() != inst->getParent())
return true;
}
return false;
}




Как получить/изменить базовые блоки на которые ссылается InvokeInst и другие?

invokeInst->getSuccessor(0); //получаем указатель на базовый блок.
invokeInst->setSuccessor(0,basicBlock); //устанавливаем.





Ответы на вопросы




Q: Что на счет деобфускации?

A: Все зависит от вас, есть проект базирующийся на LLVM для снятия обфускации.

Q: Как сгенерировать байт код файл из исходника?

A: clang -emit-llvm -o 1.bc -c 1.c

Q: Как скомпилировать байт код?

A: clang -o 1 1.bc

Q: Как сгенерировать asm файл из LLVM IR представления?

A: llc foo.ll

Q: Как сгенерировать IR файл из исходника?

A: clang -S -emit-llvm 1.c

Q: Как скомпилировать .s файл(ассемблер)?

A: gcc -o exe 1.s

Q: Как получить obj файл из байткода?

A: llc -filetype=obj 1.bc

Q: Как получить исходник с LLVM api для cpp файла?

A: clang++ -c -emit-llvm 1.cpp -o 1.ll затем llc -march=cpp -o 1.ll.cpp 1.ll

Q: Скомпилировал clang под windows, а он не может найти заголовочные файлы, как лечить?

A: Нужно найти InitHeaderSearch.cpp и добавить необходимые пути, смотрите в сторону AddMinGWCPlusPlusIncludePaths,AddMinGW64CXXPaths.

Q: Clang скомпилированный под визуал студией работает нормально?

A: На текущий момент нет, он может компилировать только самый простой C-код.

Q: GCC с режимом оптимизации вырезает нагенерированные инструкции/функции, что делать?

A: Ваши проходы нужно встроить в clang, в этот cpp файл. Так же можно заставить компилятор думать, что добавленный нами код нужный, для этого нужно чтобы этот код обязательно использовался, в случае с функциями они должны вызываться. Для тестов можете использовать режим -O0

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий