
서비스 · 프로세스 관리 05 / 50 · Part 1. 프로세스 기초
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
ps, top, pgrep, lsof는 서로 다른 도구처럼 보이지만 정보의 출처는 같다. 바로 /proc 가상 파일 시스템 이다. 도구가 없는 최소 설치 서버나 도구 자체가 변조됐을 가능성이 있는 침해 서버에서는 /proc을 직접 읽는 능력이 결정적인 차이를 만든다.
이번 글에서는 /proc/PID 아래의 주요 항목을 하나씩 읽어 보고, 다른 사용자의 프로세스는 어디까지 보이는지 권한 경계를 Rocky와 Ubuntu에서 확인한다.
「리눅스 시스템 기초 10편」에서
/proc/cpuinfo,/proc/meminfo같은 시스템 정보를 봤다. 이번 글은 프로세스별 디렉터리 에 집중한다.
/proc은 디스크에 저장된 파일이 아니라, 커널 자료구조를 파일 형태로 보여 주는 창 이다. 파일을 cat하는 순간 커널이 현재 값을 만들어 돌려준다. 그래서 ls -l에서 크기가 0으로 보여도 내용이 있고, 프로세스가 종료되면 해당 디렉터리도 즉시 사라진다.
| 항목 | 종류 | 내용 | 관제 활용 |
|---|---|---|---|
cmdline | 파일 | 실행 인자 (NUL \0 구분) | 실행 명령 전체 확인 |
comm | 파일 | 프로세스 이름 (최대 15자) | 위장 이름 확인 |
exe | 링크 | 실제 실행 파일 경로 | (deleted) 여부, 위장 탐지 |
cwd | 링크 | 현재 작업 디렉터리 | /tmp, /dev/shm에서 동작하는가 |
root | 링크 | 프로세스가 보는 / | chroot·컨테이너 여부 |
fd/ | 디렉터리 | 열린 파일 디스크립터 | 열린 파일·소켓·삭제된 파일 |
environ | 파일 | 환경변수 (NUL 구분) | LD_PRELOAD 등 주입 흔적 |
maps | 파일 | 메모리 매핑 영역 | 로드된 라이브러리, 삭제된 매핑 |
status | 파일 | 이름·상태·PPid·Uid·메모리·Capability | 사람이 읽기 좋은 요약 |
stat | 파일 | 한 줄 숫자 나열 | ps가 파싱하는 원본 |
limits | 파일 | 자원 제한 | 17편 ulimit |
cgroup | 파일 | 소속 cgroup | 어느 서비스(unit) 소속인가 |

사용자: cat /proc/454/status
↓ open/read 시스템 콜
커널: procfs 드라이버가 PID 454의 task_struct를 찾음
↓ 접근 권한 검사 (소유자 / ptrace 접근 모드)
커널: 현재 값으로 텍스트 생성 → 사용자에게 반환
권한 검사는 항목마다 다르다. status, cmdline, stat은 누구나 읽을 수 있지만, exe, cwd, fd, environ, maps처럼 민감한 항목은 ptrace 접근 권한 (대략 "같은 UID이고 특권 프로그램이 아닐 것" 또는 root)이 있어야 읽을 수 있다.
# 1) 테스트 프로세스와 /proc/PID 목록
sleep 500 & P=$!; cd /tmp; echo "PID=$P"
ls /proc/$P | column -c 110
# 2) 무엇을 실행했나
tr '\0' ' ' < /proc/$P/cmdline; echo # NUL 구분자를 공백으로 바꿔 출력
ls -l /proc/$P/exe /proc/$P/cwd
# 3) 상태·소유자·메모리
grep -E '^(Name|State|PPid|Uid|Gid|Threads|VmRSS)' /proc/$P/status
# 4) 열린 파일과 메모리 매핑
ls -l /proc/$P/fd
head -5 /proc/$P/maps
# 5) (Ubuntu, analyst) root 소유 프로세스는 어디까지 보이나
J=$(pgrep -x systemd-journal)
tr '\0' ' ' < /proc/$J/cmdline; echo
cat /proc/$J/environ
ls -l /proc/$J/exe
ls /proc/$J/fd
grep -E '^(Name|Uid|CapEff)' /proc/$J/status


텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ sleep 500 & P=$!; cd /tmp; echo "PID=$P"
PID=454
[analyst@rocky9-lab tmp]$ ls /proc/$P | head -60 | column -c 110
arch_status fdinfo ns smaps_rollup
attr gid_map numa_maps stack
autogroup io oom_adj stat
auxv ksm_merging_pages oom_score statm
cgroup ksm_stat oom_score_adj status
clear_refs limits pagemap syscall
cmdline loginuid personality task
comm map_files projid_map timens_offsets
coredump_filter maps root timerslack_ns
cpuset mem sched uid_map
cwd mountinfo schedstat wchan
environ mounts sessionid
exe mountstats setgroups
fd net smaps
[analyst@rocky9-lab tmp]$ tr '\0' ' ' < /proc/$P/cmdline; echo
sleep 500
[analyst@rocky9-lab tmp]$ ls -l /proc/$P/exe /proc/$P/cwd
lrwxrwxrwx 1 analyst analyst 0 Sep 24 09:38 /proc/454/cwd -> /home/analyst
lrwxrwxrwx 1 analyst analyst 0 Sep 24 09:38 /proc/454/exe -> /usr/bin/sleep
[analyst@rocky9-lab tmp]$ grep -E '^(Name|State|PPid|Uid|Gid|Threads|VmRSS)' /proc/$P/status
Name: sleep
State: S (sleeping)
PPid: 431
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
VmRSS: 1940 kB
Threads: 1
[analyst@rocky9-lab tmp]$ ls -l /proc/$P/fd
total 0
lr-x------ 1 analyst analyst 64 Sep 24 09:38 0 -> /dev/null
lrwx------ 1 analyst analyst 64 Sep 24 09:38 1 -> /dev/pts/0
lrwx------ 1 analyst analyst 64 Sep 24 09:38 2 -> /dev/pts/0
[analyst@rocky9-lab tmp]$ head -5 /proc/$P/maps
5603b0640000-5603b0642000 r--p 00000000 00:2a 983733 /usr/bin/sleep
5603b0642000-5603b0645000 r-xp 00002000 00:2a 983733 /usr/bin/sleep
5603b0645000-5603b0646000 r--p 00005000 00:2a 983733 /usr/bin/sleep
5603b0646000-5603b0647000 r--p 00006000 00:2a 983733 /usr/bin/sleep
5603b0647000-5603b0648000 rw-p 00007000 00:2a 983733 /usr/bin/sleep
[analyst@rocky9-lab tmp]$ kill $P
analyst@ubuntu-lab:~$ J=$(pgrep -x systemd-journal); echo "journald PID=$J owner=$(stat -c %U /proc/$J)"
journald PID=24 owner=root
analyst@ubuntu-lab:~$ tr '\0' ' ' < /proc/$J/cmdline; echo
/usr/lib/systemd/systemd-journald
analyst@ubuntu-lab:~$ cat /proc/$J/environ
cat: /proc/24/environ: Permission denied
analyst@ubuntu-lab:~$ ls -l /proc/$J/exe
ls: cannot read symbolic link '/proc/24/exe': Permission denied
lrwxrwxrwx 1 root root 0 Sep 24 09:37 /proc/24/exe
analyst@ubuntu-lab:~$ ls /proc/$J/fd
ls: cannot open directory '/proc/24/fd': Permission denied
analyst@ubuntu-lab:~$ grep -E '^(Name|Uid|CapEff)' /proc/$J/status
Name: systemd-journal
Uid: 0 0 0 0
CapEff: 00000025402800cf
| 관찰 | 의미 |
|---|---|
/proc/454 아래 50여 개 항목 | 한 프로세스에 대한 정보가 이만큼 공개되어 있다. 디스크 공간은 전혀 쓰지 않는다 |
cmdline → sleep 500 | 실행 인자 전체. tr '\0' ' '가 없으면 인자가 붙어서 보인다 |
cwd -> /home/analyst (셸은 이미 /tmp로 이동) | sleep은 cd 이전에 fork 되었기 때문에 당시 디렉터리를 가진다. 작업 디렉터리는 fork 시점에 복사된 뒤 각자 관리된다 |
exe -> /usr/bin/sleep | 커널이 기록한 실제 실행 파일 |
Uid: 1000 1000 1000 1000 | real / effective / saved / filesystem UID. 네 값이 다르면 SUID 실행이나 권한 변경이 있었다는 뜻이다 |
VmRSS: 1940 kB | 실제 물리 메모리 사용량 |
fd 0 -> /dev/null, 1·2 -> /dev/pts/0 | 백그라운드 작업은 표준입력이 /dev/null로 연결되고, 출력은 터미널(pts/0)로 간다 |
maps에 /usr/bin/sleep이 5줄 (r--p, r-xp, rw-p) | 실행 파일이 읽기 전용·실행·쓰기 영역으로 나뉘어 매핑되었다. r-xp가 코드 영역이다 |
Ubuntu: journald cmdline, status 읽기 성공 | 다른 사용자(root)의 프로세스라도 이름·상태·UID는 보인다 |
environ, exe, fd → Permission denied | 민감 정보는 소유자·root만 볼 수 있다. 관제 스크립트를 root로 실행해야 하는 이유 다 |
CapEff: 00000025402800cf | journald가 가진 Linux Capability 비트마스크. root라도 필요한 권한만 남겨 실행한다 (30편) |
| 주제 | 내용 |
|---|---|
| 인자 노출 | mysql -uroot -pP@ssw0rd처럼 비밀번호를 인자로 넘기면 같은 서버의 모든 사용자 가 ps나 /proc/PID/cmdline으로 볼 수 있다. 설정 파일이나 환경변수(소유자만 읽기)로 넘긴다 |
| hidepid | /proc을 hidepid=2(또는 hidepid=invisible) 옵션으로 마운트하면 일반 사용자는 자기 프로세스만 볼 수 있다. 공용 서버의 정보 노출을 줄인다 |
| 도구 변조 대응 | 루트킷이 ps를 바꿔치기해도 /proc을 직접 읽으면 숨긴 프로세스를 찾을 수 있는 경우가 많다. ls /proc의 숫자 디렉터리와 ps 결과를 비교한다 |
| 증거 수집 | /proc/PID/exe, maps, environ, fd는 프로세스가 살아 있을 때만 읽을 수 있는 휘발성 증거다. 종료 전에 수집한다 |
| LD_PRELOAD | environ에 LD_PRELOAD=/tmp/x.so가 있으면 라이브러리 주입을 의심한다 |
[Detection] 의심 PID 3391 보고 (CPU 과다 사용)
↓
[휘발성 증거 수집 — 종료 전에]
ls -l /proc/3391/exe /proc/3391/cwd
tr '\0' ' ' < /proc/3391/cmdline
tr '\0' '\n' < /proc/3391/environ | grep -E 'LD_|PATH|HOME'
ls -l /proc/3391/fd
grep -v -e '\.so' /proc/3391/maps | head
cp /proc/3391/exe /evidence/3391.bin && sha256sum /evidence/3391.bin
↓
[판단] cwd=/dev/shm, exe=(deleted), fd에 외부 소켓 → 악성 가능성 높음
↓
[Response] 프로세스 정지(kill -STOP) → 추가 수집 → 종료
| 질문 | /proc에서 답을 찾는 곳 |
|---|---|
| 진짜 실행 파일은? | exe |
| 어디서 실행됐나? | cwd |
| 누가 실행했나? | status의 Uid, PPid |
| 무엇과 통신하나? | fd/의 socket:[inode] → /proc/net/tcp (16편) |
| 무엇이 주입됐나? | environ의 LD_PRELOAD, maps의 비정상 경로 라이브러리 |
| 실수 | 결과 | 예방 |
|---|---|---|
cat /proc/PID/cmdline만 실행 | 인자가 붙어서 보여 오판 | tr '\0' ' '로 변환 |
| 일반 계정으로 점검 스크립트 실행 | exe, fd가 Permission denied로 빠진다 | 점검은 root(또는 sudo)로, 결과에 권한 오류를 남긴다 |
프로세스 종료 후 /proc 확인 | 디렉터리가 이미 사라짐 | 수집 → 종료 순서 |
comm을 실행 파일 이름으로 신뢰 | 15자 잘림·위장에 속는다 | exe 링크와 비교 |
/proc/PID/mem을 직접 dd | 권한·오프셋 오류, 대상 불안정 | 메모리 수집은 검증된 도구(gcore, AVML 등)로 |
[ ] /proc/PID 아래 항목 목록을 확인했다
[ ] cmdline 의 NUL 구분자를 tr 로 변환해 읽었다
[ ] exe, cwd 링크로 실행 파일과 작업 디렉터리를 확인했다
[ ] cwd 가 fork 시점 값이라는 것을 확인했다
[ ] status 의 Uid 4개 값과 VmRSS 를 읽었다
[ ] fd/ 와 maps 로 열린 파일과 메모리 매핑을 확인했다
[ ] 다른 사용자 프로세스의 environ·exe·fd 가 차단되는 것을 확인했다
/proc/PID는 커널이 실시간으로 만들어 주는 프로세스 정보 창 이며, ps·top·lsof의 원천이다.exe, cwd, cmdline, status, fd, maps, environ만 알아도 프로세스 분석의 대부분이 가능하다.cmdline, status는 누구나 보지만, exe, fd, environ, maps는 소유자와 root만 본다.hidepid로 가시성을 제한할 수 있다./proc의 정보는 휘발성 증거 다. 프로세스를 종료하기 전에 수집한다.다음 글 「06. ps 옵션 완전 정리 (aux·-ef·-o)」 에서는 BSD 스타일(aux)과 UNIX 스타일(-ef)의 차이를 정리하고, -o로 관제에 필요한 열만 골라 출력하는 방법, 정렬(--sort)과 필터링 조합을 실습한다.