서비스 · 프로세스 관리 05 / 50 · Part 1. 프로세스 기초
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정 analyst)

1. 들어가며

ps, top, pgrep, lsof는 서로 다른 도구처럼 보이지만 정보의 출처는 같다. 바로 /proc 가상 파일 시스템 이다. 도구가 없는 최소 설치 서버나 도구 자체가 변조됐을 가능성이 있는 침해 서버에서는 /proc을 직접 읽는 능력이 결정적인 차이를 만든다.

이번 글에서는 /proc/PID 아래의 주요 항목을 하나씩 읽어 보고, 다른 사용자의 프로세스는 어디까지 보이는지 권한 경계를 Rocky와 Ubuntu에서 확인한다.

「리눅스 시스템 기초 10편」에서 /proc/cpuinfo, /proc/meminfo 같은 시스템 정보를 봤다. 이번 글은 프로세스별 디렉터리 에 집중한다.


2. 핵심 개념

2-1. /proc은 파일이 아니다

/proc은 디스크에 저장된 파일이 아니라, 커널 자료구조를 파일 형태로 보여 주는 창 이다. 파일을 cat하는 순간 커널이 현재 값을 만들어 돌려준다. 그래서 ls -l에서 크기가 0으로 보여도 내용이 있고, 프로세스가 종료되면 해당 디렉터리도 즉시 사라진다.

2-2. 주요 항목

항목종류내용관제 활용
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) 소속인가

3. 동작 원리

/proc/PID — 커널이 보여 주는 프로세스의 모든 것

사용자:  cat /proc/454/status
   ↓  open/read 시스템 콜
커널:    procfs 드라이버가 PID 454의 task_struct를 찾음
   ↓  접근 권한 검사 (소유자 / ptrace 접근 모드)
커널:    현재 값으로 텍스트 생성 → 사용자에게 반환

권한 검사는 항목마다 다르다. status, cmdline, stat은 누구나 읽을 수 있지만, exe, cwd, fd, environ, maps처럼 민감한 항목은 ptrace 접근 권한 (대략 "같은 UID이고 특권 프로그램이 아닐 것" 또는 root)이 있어야 읽을 수 있다.


4. 명령어 실습

# 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

5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · analyst@rocky9-lab — /proc/PID 에서 프로세스 정보 읽기

실제 실행 결과 — Ubuntu 24.04.5 · analyst@ubuntu-lab — 다른 사용자의 /proc 은 어디까지 보이나

텍스트 원본(실제 출력):

[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

6. 결과 해석

관찰의미
/proc/454 아래 50여 개 항목한 프로세스에 대한 정보가 이만큼 공개되어 있다. 디스크 공간은 전혀 쓰지 않는다
cmdline → sleep 500실행 인자 전체. tr '\0' ' '가 없으면 인자가 붙어서 보인다
cwd -> /home/analyst (셸은 이미 /tmp로 이동)sleep은 cd 이전에 fork 되었기 때문에 당시 디렉터리를 가진다. 작업 디렉터리는 fork 시점에 복사된 뒤 각자 관리된다
exe -> /usr/bin/sleep커널이 기록한 실제 실행 파일
Uid: 1000 1000 1000 1000real / 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: 00000025402800cfjournald가 가진 Linux Capability 비트마스크. root라도 필요한 권한만 남겨 실행한다 (30편)

7. 보안 관점

주제내용
인자 노출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_PRELOADenviron에 LD_PRELOAD=/tmp/x.so가 있으면 라이브러리 주입을 의심한다

8. 보안관제 관점

[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의 비정상 경로 라이브러리

9. 실무에서 자주 발생하는 실수

실수결과예방
cat /proc/PID/cmdline만 실행인자가 붙어서 보여 오판tr '\0' ' '로 변환
일반 계정으로 점검 스크립트 실행exe, fd가 Permission denied로 빠진다점검은 root(또는 sudo)로, 결과에 권한 오류를 남긴다
프로세스 종료 후 /proc 확인디렉터리가 이미 사라짐수집 → 종료 순서
comm을 실행 파일 이름으로 신뢰15자 잘림·위장에 속는다exe 링크와 비교
/proc/PID/mem을 직접 dd권한·오프셋 오류, 대상 불안정메모리 수집은 검증된 도구(gcore, AVML 등)로

10. 실습 체크리스트

[ ] /proc/PID 아래 항목 목록을 확인했다
[ ] cmdline 의 NUL 구분자를 tr 로 변환해 읽었다
[ ] exe, cwd 링크로 실행 파일과 작업 디렉터리를 확인했다
[ ] cwd 가 fork 시점 값이라는 것을 확인했다
[ ] status 의 Uid 4개 값과 VmRSS 를 읽었다
[ ] fd/ 와 maps 로 열린 파일과 메모리 매핑을 확인했다
[ ] 다른 사용자 프로세스의 environ·exe·fd 가 차단되는 것을 확인했다

11. 핵심 정리

  • /proc/PID는 커널이 실시간으로 만들어 주는 프로세스 정보 창 이며, ps·top·lsof의 원천이다.
  • exe, cwd, cmdline, status, fd, maps, environ만 알아도 프로세스 분석의 대부분이 가능하다.
  • cmdline, status는 누구나 보지만, exe, fd, environ, maps는 소유자와 root만 본다.
  • 명령 인자에 비밀번호를 넣으면 모든 사용자에게 노출된다. hidepid로 가시성을 제한할 수 있다.
  • /proc의 정보는 휘발성 증거 다. 프로세스를 종료하기 전에 수집한다.

12. 다음 편 예고

다음 글 「06. ps 옵션 완전 정리 (aux·-ef·-o)」 에서는 BSD 스타일(aux)과 UNIX 스타일(-ef)의 차이를 정리하고, -o로 관제에 필요한 열만 골라 출력하는 방법, 정렬(--sort)과 필터링 조합을 실습한다.


참고 자료


시리즈 이동

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글