환경은 24.04 우분투 서버에서 진행하였다.
sudo apt update
sudo apt install -y build-essential cmake ninja-build git
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
mkdir build && cd build
cmake -G Ninja ../llvm \
-DLLVM_ENABLE_PROJECTS="clang;lld" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local/llvm
ninja
sudo ninja install
echo 'export PATH="/usr/local/llvm/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
clang --version
llvm-config --version

정상적으로 설치된 화면이다
mkdir -p ~/llvm-project/llvm/lib/Transforms/MyPass
cd ~/llvm-project/llvm/lib/Transforms
vi CMakeLists.txt
맨 아래줄에 add_subdirectory(MyPass) 추가
cd ~/llvm-project/llvm/lib/Transforms/MyPass
cat > CMakeLists.txt << 'EOF'
add_llvm_library(MyPass MODULE
MyPass.cpp
PLUGIN_TOOL
opt
)
EOF
같은 디렉토리에 cpp 파일을 작성해 준다.
cat > MyPass.cpp << 'EOF'
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
struct MyPass : public PassInfoMixin<MyPass> {
// optnone 속성이 있는 함수에서도 실행되도록 설정
static bool isRequired() { return true; }
PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
errs() << "=== Running MyPass on function: " << F.getName() << " ===\n";
return PreservedAnalyses::all();
}
};
}
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return {
LLVM_PLUGIN_API_VERSION, "MyPass", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "mypass") {
FPM.addPass(MyPass());
return true;
}
return false;
});
}
};
}
EOF
cd ~/llvm-project/build
ninja
빌드가 성공하게 되면

다음과 같이 뜨게 된다.
홈 디렉토리에 새로운 디렉토리를 만들어 진행해 주었다.
mkdir -p ~/experiment
cd ~/experiment
다음과 같은 C 파일을 만들어 주었다.
#include <stdio.h>
int calc(int a, int b) {
return a + b;
}
int main() {
int a = 1, b = 2;
printf("%d", calc(a, b));
return 0;
}
clang -S -emit-llvm test.c -o test.ll
/home/$(whoami)/llvm-project/build/bin/opt -load-pass-plugin=/home/$(whoami)/llvm-project/build/lib/MyPass.so -passes="mypass" test.ll -o test_out.ll
결과는 다음과 같이 나오게 된다.

위의 명령어를 실행해보면 알 수 있듯이 매우 길어 입력하기가 불편하다. 그래서 스크립트와 환경변수를 이용해 수고를 덜도록 하자.
mkdir -p ~/bin
cat > ~/bin/llvm_pass << 'EOF'
#!/bin/bash
LLVM_BUILD=~/llvm-project/build
LLVM_PASS=$LLVM_BUILD/lib/MyPass.so
$LLVM_BUILD/bin/opt -load-pass-plugin=$LLVM_PASS -passes="mypass" "$1" -o "${1%.ll}_out.ll"
EOF
chmod +x ~/bin/llvm_pass
bin 파일이 있는지 확인하고 없다면 생성 하여 환경변수 설정
if [[ ":$PATH:" != *":$HOME/bin:"* ]]; then
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
fi
cd ~/experiment
llvm_pass test.ll
결과 화면

LLVM Build 하는 방법에 대해 알아 보았으며, LLVM Pass를 이용하여 명령어 이름을 뽑아 보았다. 하지만 이 외에 다양한 기능을 할 수 있다.
분석
변환
찾아본 결과 llvm pass 를 이용하여 루프 분석, 스위치 패턴 분석, 제어 흐름 분석 등이 탐지가 가능할 것 같으며,
llvm pass 를 이용하여 난독화 해제도 일부 가능할 것 같다.
중복 변수 제거 및 단순화, 불필요한 분기 제거, 조건부 분기 단순화, 상태변수 추적 및 단순화 등등...
난독화 된 코드를 분석할 필요가 있을 것 같다.