Virtualize Analysis LLVM Pass

안상준·2025년 8월 19일

LLVM

목록 보기
10/12

Introduction

현재 연구중인 가상화 난독화 분석 LLM 학습에 사용할 feature를 생성하는 LLVM Pass 제작에 대하여 고민해 보았다.
Tigress로 난독화 한 코드를 대상으로 진행할 예정이며, Tigress는 C code -> C code로 난독화가 가능하므로 LLVM IR 생성이 수월하다.(VMP의 경우 .exe를 IR로 lift 필요)
먼저 LLM 학습에 사용할 feature 생성을 위한 LLVM Pass에 대하여 여러가지 생각해 보았다.

Switch 구조

LLVM Pass를 통해 switch 구조를 찾고 case문을 pass를 통해 뽑아올 수 있다. case문을 분석한다면 코드의 전체 흐름을 바로 알 수 있고 핸들러 분석에 용이할 것이라 생각한다.
https://velog.io/@sangjun19/Switch-IRLLVM-Pass

Fan-in / Fan-out

가상화 난독화의 경우 FDE 구조를 사용하며 loop-switch 구조로 인해 특정 블럭의 많은 Fan-in/Fan-out 이 발생할 수 있다.

위 사진은 Tigress로 c코드를 가상화난독화 한 코드인데 내부에 while-switch 구조를 볼 수 있다. 진입과 진출이 많은 블럭을 찾는다면 가상화 분석에 유용하게 사용할 수 있을 것 같다.
하지만 요즘 가상화난독화의 경우 핸들러 진입 후 switch 로 복귀하는 것이 아닌 해당 위치에서 다시 새로운 블럭으로 분기하여 fetch 하는 로직을 사용하기도 한다고 한다.

명령어 군집화

일반 코드와 가상화된 코드를 구분하기 위하여 블럭 단위로 명령어의 빈도를 측정한다. 예를 들어 dispatcher 부분에서는 반복문과 간접 분기가 주를 이룰 것이고, handler 에는 연산자와 직접 분기를 이용할 것이라 생각한다.
각 블럭 단위로 명령어의 빈도를 확인하여 해당 블럭의 예상되는 역할을 LLM이 식별할 수 있게 한다면 좋을 것 같다.
생각되는 단점으로는 junk code로 인해 정확한 분석과 군집화의 어려움이 있을 것 같다. 그래서 빈도수만 구하고 이를 feature로 하여 LLM 에게 맡기는 방법도 괜찮을 것 같다.

Pass

진입점이 가장 많은 basic block을 찾는 pass를 제작해 보았다.

Code

#include "llvm/IR/PassManager.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Passes/PassPlugin.h"
#include <map>

using namespace llvm;

namespace {

class FindMostFrequentBlockPass : public PassInfoMixin<FindMostFrequentBlockPass> {
public:
    PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
        // BasicBlock의 진입 횟수를 저장할 맵
        std::map<const BasicBlock*, unsigned> blockPredecessorCounts;

        // 함수의 모든 BB 순회
        for (auto &BB : F) {
            // 현재 블록의 마지막 명령어를 가져오기(br, ret)
            const Instruction *terminator = BB.getTerminator();

            // 분기할 수 있는 모든 후속 블록을 순회
            for (unsigned i = 0; i < terminator->getNumSuccessors(); ++i) {
                const BasicBlock *successor = terminator->getSuccessor(i);
                
                // 후속 블록의 카운트를 1 증가
                blockPredecessorCounts[successor]++;
            }
        }

        // 가장 많이 참조된 블록 찾기
        const BasicBlock *mostFrequentBlock = nullptr;
        unsigned maxCount = 0;

        for (auto const& [block, count] : blockPredecessorCounts) {
            if (count > maxCount) {
                maxCount = count;
                mostFrequentBlock = block;
            }
        }

        // 최종 결과
        if (mostFrequentBlock) {
            errs() << "[*] 함수 '" << F.getName() << "' 분석 결과:\n";
            errs() << "    가장 많은 진입점을 가진 블록: ";
            if (mostFrequentBlock->hasName()) {
                errs() << mostFrequentBlock->getName() << "\n";
            } else {
                errs() << mostFrequentBlock << "\n";
            }
            errs() << "    진입 횟수: " << maxCount << "\n";
        }

        return PreservedAnalyses::all();
    }
};

}

// Plugin registration
extern "C" ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
    return {
        LLVM_PLUGIN_API_VERSION,
        "FindMostFrequentBlockPass", "v0.1",
        [](PassBuilder &PB) {
            PB.registerPipelineParsingCallback(
                [](StringRef Name, FunctionPassManager &FPM,
                   ArrayRef<PassBuilder::PipelineElement>) {
                    if (Name == "find-most-frequent-block") {
                        FPM.addPass(FindMostFrequentBlockPass());
                        return true;
                    }
                    return false;
                });
        }
    };
}

각 basic block의 후속 block을 탐색하며 map에 block과 진입 횟수를 저장한다.

; test.ll
define i32 @main() {
entry:
  br label %loop

loop:
  %i = phi i32 [ 0, %entry ], [ %next, %loop_exit ]
  %cond = icmp slt i32 %i, 10
  br i1 %cond, label %loop_body, label %loop_exit

loop_body:
  br label %loop_exit

loop_exit:
  %next = add nsw i32 %i, 1
  br label %loop

end:
  ret i32 0
}

위 IR은 테스트를 위해 사용한 코드로 코드의 의미는 아래와 같다.
for (int i=0; i<10; i++) {}

Result


맨 처음 entry에서 한 번, loop_exit에서 한 번 총 2번 loop로 진입하게 된다.

0개의 댓글