코드 난독화 도구 제작: LLVM

손지웅·2025년 8월 18일

이전 코드 난독화 제작에서는, 소스 코드 레벨에서의 난독화를 진행하였다.

하지만 최근 LLM의 발전으로, 평이하거나 전통적인 난독화 코드에 대하여 역난독화가 가능해졌다. 소스 코드 수준에서의 난독화만으로는 보안 유지가 어려워지고 있는 현실이다.

위의 예시와 같이 소스 코드 난독화는 언어적인 변형이다. 즉 언어의 문법과 논리 구조는 유지되므로 LLM은 텍스트 패턴 기반으로 쉽게 추론할 수 있다.

코드의 동작 흐름은 다음과 같다.
소스코드 ──> 중간코드(IR) ──> 기계어/바이너리

소스코드 난독화 적용 위치는 소스코드 단계, 변수명 변경·난해한 코드 삽입 등으로 소스코드 문법과 논리 구조는 유지된다.

LLVM 난독화 적용 위치는 중간코드 IR 단계, Control Flow Flattening, Dead Code Insertion 등이 있다.

이렇듯 적용 위치가 달라지게 되면, 즉 IR이나 바이너리 단계에서는 정보를 파악하여 역난독화 할 수 있는 맥락이 많이 사라지게 된다. 그리고 최적화 과정으로 인하여 원래 구조가 완전히 변경되게 된다. 구체적으로 함수나 변수명, 주석 등 자연어 기반의 정보가 전혀 남아있지 않아 LLM이 사용할만한 단서가 많이 사라지게 되고, 반복적이고 패턴화된 기계어 수준의 명령들이 많아 분석의 난이도가 비약적으로 상승하게 된다.

예시

원본 코드

int max(int a, int b) {
  if(a > b) {
    return a;
  } else {
    return b;
  }
}
define i32 @max(i32 %a, i32 %b) {
entry:
  %cmp = icmp sgt i32 %a, %b
  br i1 %cmp, label %if.true, label %if.false

if.true:
  ret i32 %a

if.false:
  ret i32 %b
}

소스코드 수준 난독화 - 변수/함수명 변경

int Qz_1(int kM, int pn) {
  if(kM > pn) {
    return kM;
  } else {
    return pn;
  }
}

IR수준 난독화 - Control Flow Fattening

define i32 @max(i32 %a, i32 %b) {
entry:
  br label %loop

loop:
  %state = phi i32 [0, %entry], [1, %block1], [2, %block2]
  switch i32 %state, label %end [
    i32 0, label %block0
    i32 1, label %block1
    i32 2, label %block2
  ]

block0:
  %cmp = icmp sgt i32 %a, %b
  br i1 %cmp, label %block1, label %block2

block1:
  %r1 = phi i32 [ %a, %block0 ]
  br label %end

block2:
  %r2 = phi i32 [ %b, %block0 ]
  br label %end

end:
  %result = phi i32 [ %r1, %block1 ], [ %r2, %block2 ]
  ret i32 %result
}

Obfuscator-LLVM

이전 공부했던 LLVM은 오픈소스 컴파일러 프레임워크로, C,C++의 코드를 IR로 변환하여 최적화 및 기계어로 변환하는 도구였다. OLLVM은 LLVM을 기반으로 만든 파생 프로젝트로, LLVM의 코어구조를 그대로 사용하되 IR 수준에서의 난독화 패스가 추가되어 있다.

https://github.com/obfuscator-llvm/obfuscator

해당 부분을 빌드하여 Tigress와 동일하게 난독화를 먼저 진행해보고자 한다.

IR수준의 난독화 기법

  1. Control Flow Flattening (제어 흐름 평탄화)
    if/else, loop 등 고수준 제어문을 switch-case 같은 복잡한 구조로 꼬아 논리 흐름을 숨기는 방식

  2. Instruction Substitution (명령어 치환)
    단순 명령/연산을 동작상 등가이지만 더 복잡한 명령어 시퀀스로 교체하여 패턴 분석을 방해
    예: a = b + c → a = b - (-c) 등

  3. Bogus Control Flow & Dead Code Insertion (가짜 제어 흐름/데드 코드 주입)
    실제 실행되지 않는 분기, 조건문, 의미 없는 명령어 등 가짜 흐름을 주입해 분석을 어렵게 만듦

4. Basic Block Transformation (블록 분할/병합/조합)
실제 로직이 담긴 블록을 쪼개거나, 여러 블록을 합치거나, 타 코드와 물리적으로 뒤섞어 블록 경계와 실행 흐름 분석을 복잡하게 함

5. Virtualization-based Obfuscation (가상 머신 난독화)
IR 코드 전체 또는 일부를 자체 정의 가상 명령어셋 바이트코드로 변환, 가상머신 해석기에서만 실행되도록 변환하여 리버싱·분석 난이도를 극대화

  1. Data & Object Obfuscation (데이터/객체/타입 난독화)
    데이터 흐름에 불필요한 변수·더미 연산 추가, 객체/구조체 레이아웃 변형(패딩, 가짜 필드, 오프셋 동적 계산) 등으로 분석을 어렵게 함

  2. Interprocedural Control Flow Obfuscation (함수 호출/리턴 흐름 난독화)
    함수포인터, 오프셋 테이블 등 간접적인 호출 구조 도입으로 제어 흐름 추적을 어렵게 만듦

8. Delayed Computation & Dynamic Code (연산 지연/동적 패치/자체 수정 코드)
계산을 실제 사용 직전까지 미루거나, 코드 자체를 실행 도중에 동적으로 수정 또는 외부에서 패치·주입받아야만 동작하도록 설계

  1. Logging/Exception Obfuscation (로깅/예외처리 연동)
    실제 로직 주변에 가짜 로깅, 예외처리 루틴 등 혼선 유발 코드를 추가

  2. Encrypted Literals (암호화된 상수)
    코드 내 상수, 문자열, 테이블 등을 암호화하여 저장하고, 실행 중 복호화해서만 사용

0개의 댓글