Содержание статьи
Что за зверь LLVM
Проект LLVM стартовал в 2000 году и получил распространение в начале десятых. Изначальная расшифровка — low level virtual machine, хотя сейчас она не отражает суть проекта. LLVM — фреймворк с открытым исходным кодом для создания компиляторов. На базе LLVM можно собрать компилятор для собственного языка программирования. Или «улучшить» существующий, чем мы сегодня и займемся.
Архитектурно LLVM разделен на три части: фронтенд, оптимизация и бэкенд. Фронтенд преобразует исходный код в промежуточное представление (intermediate representation) — универсальный промежуточный код, который используется на следующих этапах для оптимизации кода и сборки файла. Оптимизация удаляет неиспользуемый код, сворачивает арифметические вычисления в готовые константы и заменяет неэффективные конструкции более быстрыми. Бэкенд превращает IR в машинный код.
Если ты создаешь новый язык, достаточно написать фронтенд, генерирующий IR, а остальную работу LLVM возьмет на себя. Точно так же можно работать с кодом (изначально написанным на любом поддерживаемом языке) на уровне IR, меняя его под свои нужды на этапе оптимизации. Или написать свой бэкенд, чтобы собирать старый код под неизвестную процессорную архитектуру.
Необязательно писать фронтенд с нуля. Достаточно модифицировать исходный код LLVM. Оптимизация — более гибкий инструмент, она поддерживает загружаемые плагины, добавляющие проходы — специальные функции, обрабатывающие IR на уровне модуля, функции, цикла или базового блока. Для обфускации кода мы будем использовать именно их.
Сборка LLVM под Windows
Для начала установим CMake и поместим утилиту ninja.exe в C:\
, тем самым сделав ее видимой для CMake при сборке.
mkdir C:\LLVM && cd C:\LLVM
"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
mkdir build && cd build
cmake -G "Ninja" ^
-DLLVM_ENABLE_PROJECTS="clang" ^
-DLLVM_ENABLE_ASSERTIONS=ON ^
-DCMAKE_BUILD_TYPE=Release ^
-DLLVM_BUILD_LLVM_DYLIB=ON ^
-DLLVM_ENABLE_RTTI=ON ^
-DCMAKE_INSTALL_PREFIX=C:/llvm/custom ^
../llvm
ninja
ninja install
Создаем папку в корне диска и запускаем vcvars64.
. Он установит необходимые для сборки переменные окружения, такие как путь до компилятора и подключаемых файлов MSVC. Далее клонируем исходный код LLVM и запускаем сборку. Ждем около часа окончания сборки и получаем новые файлы в C:\
. Среди них утилиты, компиляторы и подключаемые файлы .lib. Они пригодятся при создании собственных проходов для оптимизации.
Сборка проходов
Создаем проект в Visual Studio и меняем его настройки:
Configuration Type = Dynamic Library (.dll)
Additional Include Directories = C:\LLVM\include
Additional Library Directories = C:\LLVM\lib
C++ Langauge Standart = ISO C++17 Standard (/std:c++17)
Code Generation → RuntimeLibrary = /MT
Также добавляем список .lib в Additional
:
LLVMCore.lib
LLVMSupport.lib
LLVMBitReader.lib
LLVMIRReader.lib
LLVMAnalysis.lib
LLVMPasses.lib
LLVMFrontendOpenMP.lib
LLVMTargetParser.lib
LLVMRemarks.lib
LLVMProfileData.lib
LLVMBinaryFormat.lib
LLVMDemangle.lib
LLVMBitstreamReader.lib
Таким образом собираются все наши проходы.
Проход для анализа покрытия
Создадим простой плагин для оптимизатора. Первое, что приходит на ум, — добавить логирование на вход каждой функции. AFL использует похожий подход для определения покрытия — он добавляет свой код в исследуемые исходники, чтобы отслеживать поток управления.
#pragma warning(disable : 4146)#pragma comment(linker, "/export:llvmGetPassPluginInfo")#define _CRT_SECURE_NO_WARNINGS#include "llvm/Passes/PassBuilder.h"#include "llvm/Passes/PassPlugin.h"#include "llvm/IR/PassManager.h"#include "llvm/Pass.h"#include "llvm/IR/Function.h"#include "llvm/IR/Module.h"#include "llvm/IR/IRBuilder.h"#include "llvm/IR/Type.h"#include "llvm/IR/Instructions.h"#include "llvm/IR/Constants.h"#include "llvm/IR/DerivedTypes.h"#include "llvm/Support/raw_ostream.h"using namespace llvm;struct DebugTracePass : PassInfoMixin<DebugTracePass> { PreservedAnalyses run(Module& M, ModuleAnalysisManager&) { LLVMContext& Ctx = M.getContext(); // Получаем i8* тип (char*) Type* i8Ty = Type::getInt8Ty(Ctx); PointerType* i8PtrTy = PointerType::get(i8Ty, 0); // Тип функции: void OutputDebugStringA(char*) FunctionType* debugFnTy = FunctionType::get(Type::getVoidTy(Ctx), { i8PtrTy }, false); FunctionCallee debugFn = M.getOrInsertFunction("OutputDebugStringA", debugFnTy); for (Function& F : M) { // Пропуск, если это объявление функции if (F.isDeclaration()) continue; // Строим IR перед первой инструкцией Instruction* insertPt = &*F.getEntryBlock().getFirstInsertionPt(); IRBuilder<> builder(insertPt); // Формируем строку std::string msg = "Enter: " + F.getName().str(); Value* msgStr = builder.CreateGlobalString(msg); // Вставляем вызов OutputDebugStringA builder.CreateCall(debugFn, msgStr); } return PreservedAnalyses::none(); }};extern "C"llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return { LLVM_PLUGIN_API_VERSION, // Версия API плагинов "InjectFunctionCallPass", // Название плагина "v0.1", // Версия [](llvm::PassBuilder& PB) { PB.registerPipelineParsingCallback( [](llvm::StringRef Name, llvm::ModulePassManager& MPM, llvm::ArrayRef<llvm::PassBuilder::PipelineElement>) { if (Name == "debug-trace") { MPM.addPass(DebugTracePass()); return true; } return false; }); } };}
Здесь я сначала отключаю раздражающее предупреждение о неверной типизации внутри кода LLVM. Затем прошу экспортировать функцию llvmGetPassPluginInfo
. Она должна быть в экспорте плагина, чтобы сообщить при загрузке его название и добавить новый проход debug-trace
в список доступных проходов. Проход располагается в методе run
класса DebugTracePass
.
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее