← 목록으로

코드 난독화 해제: Control Flow Flattening 분석하기

[Rev] 리버싱 · 작성: 2026-07-20 03:10:34 · 수정: 2026-07-20 03:11:15 · 조회 5

안티 디버깅·패킹 글에서 실행을 방해하는 기법들을 다뤘는데, 코드 자체를 읽기 어렵게 비트는 난독화(Obfuscation)는 또 다른 층위의 방해 기법이다. 그중 가장 흔하고 강력한 게 Control Flow Flattening(제어 흐름 평탄화)이다.

평탄화 전/후 비교

정상 코드는 if/else, 반복문이 자연스러운 트리·그래프 구조로 이어진다. 평탄화된 코드는 이 구조를 다 부수고, 모든 블록을 하나의 거대한 switch문 케이스들로 흩어놓은 뒤, 다음에 실행할 케이스 번호를 담은 변수 하나(state variable)로 흐름을 제어한다.

// 원본
if (a > 0) { foo(); } else { bar(); }
baz();

// 평탄화 후 (개념적 예시)
int state = 0;
while (1) {
  switch (state) {
    case 0: state = (a > 0) ? 1 : 2; break;
    case 1: foo(); state = 3; break;
    case 2: bar(); state = 3; break;
    case 3: baz(); return;
  }
}

디스어셈블리 상에서는 분기가 전부 이 switch문 하나로 수렴해버리기 때문에, 그래프 뷰(IDA/Ghidra의 CFG)가 원래 로직과 전혀 다른 스파게티 모양이 되어버린다. LLVM 기반의 OLLVM(Obfuscator-LLVM) 같은 도구가 이 기법을 컴파일 단계에서 자동 적용해준다.

해제 접근법

  1. state variable 찾기: switch문 진입 직전에 계속 갱신되는 변수를 특정한다
  2. 각 case의 실제 동작과 다음 state 계산 로직을 분리해서 기록한다
  3. 심볼릭 실행·테인트 분석으로 state 값의 전이 그래프를 복원한다 — 어떤 조건에서 어떤 state로 넘어가는지를 자동으로 추적한다
  4. Ghidra의 P-code나 angr 같은 프레임워크로 이 과정을 스크립트화하면 수작업보다 훨씬 빠르다

실전 팁

리버싱 카테고리의 글 (11/11)

  1. 리버싱이란 무엇인가: 정적 분석 기초
  2. 어셈블리어 읽기 기초 (x86-64)
  3. 동적 분석: 디버거로 실행 흐름 추적하기
  4. 안티 디버깅 / 안티 리버싱 기법과 우회
  5. 패킹과 언패킹: 실행 파일을 압축/암호화해서 숨기기
  6. Frida로 동적 계측/후킹하기
  7. 안드로이드 앱 리버싱: APK 분석 기초
  8. iOS 앱 리버싱 기초
  9. 크랙미(Crackme) 실습 방법론
  10. PE 포맷 내부 구조: Windows 실행 파일 뜯어보기
  11. 코드 난독화 해제: Control Flow Flattening 분석하기
← PE 포맷 내부 구조: Windows 실행 파일 뜯어보기