3장
프로세스 분석 기초
WinDbg로 시작하는 Windows 내부 분석 입문
| 학습 목표 프로세스의 개념과 Windows 내부 구조를 이해하고, WinDbg의 !process/!thread 명령으로 프로세스 정보를 확인하며, 프로세스 간의 관계를 추적하는 능력을 기른다. |
3장 프로세스 분석 기초
1장에서 WinDbg 설치와 커널 디버깅 환경을 구성했고, 2장에서 레지스터와 어셈블리 기초를 익혔다면, 이제 Windows 내부의 핵심 구조체인 프로세스를 본격적으로 분석해 볼 차례다.
프로세스는 운영체제가 프로그램을 실행할 때 생성하는 기본 단위다. 각 프로세스는 고유한 가상 메모리 공간과 시스템 자원을 가지며, WinDbg에서는 이 프로세스들의 상태를 실시간으로 관찰하고 분석할 수 있다.
처음에는 구조체와 필드 이름이 낯설 수 있다. 하지만 PID, 부모 PID, 스레드 목록, 핸들 테이블처럼 질문을 작게 나누어 따라가면 Windows 내부 흐름을 훨씬 선명하게 볼 수 있다.
3.1 프로세스란?
프로세스(Process)는 실행 중인 프로그램의 인스턴스다. 단순히 실행 파일이 메모리에 올라간 상태를 넘어, 운영체제가 권한, 주소 공간, 핸들, 스레드, 실행 상태를 묶어서 관리하는 자원 단위다.

[그림 3-1] 프로세스의 구성 요소
| 특징 | 설명 |
| 고유성 | 각 프로세스는 고유한 PID(Process ID)를 가진다. |
| 메모리 격리 | 각 프로세스는 독립적인 가상 메모리 주소 공간을 사용한다. |
| 자원 소유 | 파일 핸들, 레지스트리 키, 이벤트, 네트워크 소켓 같은 시스템 자원을 소유한다. |
| 실행 단위 | 실제 CPU에서 실행되는 흐름은 프로세스 내부의 스레드가 담당한다. |
| 생애 주기 | 생성, 실행, 대기, 종료 상태를 거치며 커널 객체로 관리된다. |
| 분석 관점 프로세스를 볼 때는 '이 프로세스가 누구인지', '누가 만들었는지', '어떤 스레드가 실행 중인지', '어떤 자원을 잡고 있는지'를 차례로 확인하면 된다. |
3.2 Windows 프로세스 내부 구조
Windows 커널은 프로세스를 관리하기 위해 여러 구조체를 사용한다. 입문 단계에서는 _EPROCESS와 _KPROCESS의 관계를 먼저 이해하면 충분하다. _EPROCESS는 프로세스 객체의 큰 틀을 담고, 그 내부 첫 필드인 Pcb가 _KPROCESS를 포함한다.

[그림 3-2] EPROCESS와 KPROCESS의 관계
3.2.1 _EPROCESS 구조체
_EPROCESS(Executive Process)는 실행 중인 프로세스에 대한 고수준 정보를 담는다. 이미지 이름, PID, 부모 PID, 활성 프로세스 링크, 핸들 테이블, 토큰 같은 분석에 중요한 필드가 여기에 포함된다.
kd> dt nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x440 UniqueProcessId : Ptr64 Void
+0x448 ActiveProcessLinks : _LIST_ENTRY
+0x570 ObjectTable : Ptr64 _HANDLE_TABLE
+0x5a8 ImageFileName : [15] UChar
+0x5e0 InheritedFromUniqueProcessId: Ptr64 Void
+0x4b8 Token : _EX_FAST_REF
| 버전 차이 주의 _EPROCESS와 _KPROCESS의 오프셋은 Windows 버전과 빌드에 따라 달라진다. 책이나 예제의 오프셋을 외우기보다 dt nt!_EPROCESS 명령으로 현재 대상 시스템의 실제 레이아웃을 확인하는 습관이 중요하다. |
| 필드 | 의미 | 분석 포인트 |
| Pcb | 내부에 포함된 _KPROCESS | 스케줄링과 스레드 관련 정보로 이어진다. |
| UniqueProcessId | 프로세스 PID | !process 출력의 Cid와 연결해 확인한다. |
| ActiveProcessLinks | 활성 프로세스 리스트 | 전체 프로세스 순회와 은닉 프로세스 분석의 출발점이다. |
| ObjectTable | 핸들 테이블 포인터 | !handle 분석과 연결된다. |
| ImageFileName | 실행 파일명 | 짧은 이미지 이름이므로 전체 경로는 별도 확인이 필요하다. |
| InheritedFromUniqueProcessId | 부모 프로세스 PID | 부모-자식 관계 추적에 사용한다. |
| Token | 보안 토큰 | 권한, 사용자, 무결성 수준 분석의 시작점이다. |
3.2.2 _KPROCESS 구조체
_KPROCESS(Kernel Process)는 스케줄링과 CPU 시간 관리에 필요한 저수준 정보를 담는다. EPROCESS가 프로세스 객체 전체를 설명한다면, KPROCESS는 커널 스케줄러가 실제로 참고하는 핵심 정보에 가깝다.
kd> dt nt!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x028 DirectoryTableBase : Uint8B
+0x030 ThreadListHead : _LIST_ENTRY
+0x080 ProcessLock : Uint4B
+0x438 KernelTime : Uint4B
+0x43c UserTime : Uint4B
| 필드 | 의미 |
| DirectoryTableBase | 프로세스 주소 변환에 쓰이는 페이지 테이블 기준 값이다. |
| ThreadListHead | 프로세스에 속한 스레드 목록의 리스트 헤더다. |
| KernelTime | 커널 모드에서 사용한 CPU 시간이다. |
| UserTime | 유저 모드에서 사용한 CPU 시간이다. |
3.3 WinDbg에서 프로세스 분석하기
프로세스 분석의 중심 명령은 !process다. 가장 먼저 전체 목록을 보고, 대상 프로세스의 EPROCESS 주소를 확인한 뒤, 주소를 기준으로 상세 정보와 스레드를 확인한다.

[그림 3-4] WinDbg 프로세스 분석 기본 흐름
3.3.1 !process 명령어
| 명령 | 설명 |
| !process 0 0 | 모든 활성 프로세스의 기본 정보를 표시한다. |
| !process 0 1 | 각 프로세스의 스레드 요약까지 함께 표시한다. |
| !process <EPROCESS주소> 0 | 특정 프로세스의 기본 정보를 표시한다. |
| !process <EPROCESS주소> 1 | 특정 프로세스의 스레드 정보를 포함해 표시한다. |
| dt nt!_EPROCESS <주소> | EPROCESS 구조체를 직접 해석한다. |
| PID로 바로 찾고 싶을 때 !process는 보통 EPROCESS 주소를 기준으로 상세 조회한다. PID만 알고 있다면 !process 0 0 출력에서 Cid를 찾거나, WinDbg Preview의 검색/필터, findstr, dx 명령을 함께 사용해 EPROCESS 주소를 먼저 확보한다. |
실습 1. 모든 프로세스 목록 확인
// 모든 프로세스 목록 출력
1: kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS fffff8a0`c2e4b900 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: fffff8a0`c3d4e800 HandleCount: 52.
Image: System
PROCESS fffff8a0`c3b9ad40 SessionId: none Cid: 0114 Peb: 7ffdd000 ParentCid: 0004
DirBase: 3eba9020 ObjectTable: fffff8a0`c3be0e80 HandleCount: 29.
Image: smss.exe
PROCESS fffff8a0`c6b13a80 SessionId: 1 Cid: 02a4 Peb: 7ffd9000 ParentCid: 01e0
DirBase: 3eba9060 ObjectTable: fffff8a0`c6b2bcf0 HandleCount: 156.
Image: explorer.exe
| 필드 | 설명 |
| PROCESS 주소 | EPROCESS 구조체의 메모리 주소다. |
| SessionId | 프로세스가 속한 세션 ID다. 사용자 로그인 세션 분석에 중요하다. |
| Cid | Client ID이며 여기서는 PID로 보면 된다. |
| Peb | Process Environment Block 주소다. 유저 모드 프로세스 정보로 이어진다. |
| ParentCid | 부모 프로세스의 PID다. |
| DirBase | 주소 변환에 쓰이는 페이지 디렉터리/테이블 기준 값이다. |
| ObjectTable | 핸들 테이블 포인터다. |
| HandleCount | 열린 핸들 수다. |
| Image | 실행 파일 이미지 이름이다. |
3.3.2 특정 프로세스 상세 정보 확인
목록에서 explorer.exe의 EPROCESS 주소가 fffff8a0`c6b13a80이라고 확인했다면, 이 주소를 기준으로 상세 정보를 조회한다.
// explorer.exe 프로세스 상세 정보
1: kd> !process fffff8a0`c6b13a80 0
PROCESS fffff8a0`c6b13a80 SessionId: 1 Cid: 02a4 Peb: 7ffd9000 ParentCid: 01e0
Image: explorer.exe
ObjectTable: fffff8a0`c6b2bcf0
HandleCount: 156.
VadRoot fffff8a0`c6b0d000 Vads 1234 Clone 0 Private 14567. Modified 320. Locked 0.
// EPROCESS 구조체 직접 보기
1: kd> dt nt!_EPROCESS fffff8a0`c6b13a80 UniqueProcessId ImageFileName ObjectTable InheritedFromUniqueProcessId
+0x440 UniqueProcessId : 0x00000000`000002a4 Void
+0x570 ObjectTable : 0xfffff8a0`c6b2bcf0 _HANDLE_TABLE
+0x5a8 ImageFileName : [15] "explorer.exe"
+0x5e0 InheritedFromUniqueProcessId : 0x00000000`000001e0 Void
| 해석 포인트 !process 출력은 빠른 요약이고, dt nt!_EPROCESS는 구조체 필드를 직접 확인하는 방식이다. 둘을 함께 보면 출력 필드와 실제 커널 구조체가 어떻게 연결되는지 익힐 수 있다. |
3.4 프로세스 관계 추적하기
3.4.1 부모-자식 관계 이해
Windows에서 프로세스는 보통 자신을 만든 부모 프로세스의 PID를 기록한다. 이 값은 _EPROCESS의 InheritedFromUniqueProcessId와 !process 출력의 ParentCid에서 확인할 수 있다.

[그림 3-3] 프로세스 계층 구조 예시
| 주의할 점 ParentCid는 생성 당시 부모 PID를 기록한 값이다. 부모 프로세스가 이미 종료되었거나 PID가 재사용된 경우 현재 실행 중인 프로세스 트리와 완전히 일치하지 않을 수 있다. |
실습 2. 프로세스 계층 구조 추적
// explorer.exe의 부모 PID 확인
1: kd> !process fffff8a0`c6b13a80 0
PROCESS fffff8a0`c6b13a80 SessionId: 1 Cid: 02a4 Peb: 7ffd9000 ParentCid: 01e0
Image: explorer.exe
// 전체 목록에서 Cid 01e0인 프로세스를 찾아 부모 확인
1: kd> !process 0 0
PROCESS fffff8a0`c5b13a80 SessionId: 0 Cid: 01e0 Peb: 7ffda000 ParentCid: 0198
Image: services.exe
// 다시 ParentCid 0198을 찾아 상위 프로세스로 이동
PROCESS fffff8a0`c4e113a8 SessionId: 0 Cid: 0198 Peb: 7ffd9000 ParentCid: 0164
Image: wininit.exe
// 최종적으로 System 또는 세션 초기화 프로세스까지 추적
PROCESS fffff8a0`c2e4b900 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
Image: System
3.4.2 스레드 분석 (!thread 명령)
프로세스는 자원 컨테이너이고, 실제 실행 흐름은 스레드(Thread)가 담당한다. 하나의 프로세스에는 하나 이상의 스레드가 있으며, 각 스레드는 실행 상태, 우선순위, 대기 이유, 커널 스택 정보를 가진다.
// 특정 프로세스의 스레드 정보 포함 출력
1: kd> !process fffff8a0`c6b13a80 1
PROCESS fffff8a0`c6b13a80 SessionId: 1 Cid: 02a4 Peb: 7ffd9000 ParentCid: 01e0
Image: explorer.exe
THREAD fffff8a0`c6d12345 Cid 02a4.0b10 Teb: 00000000`001652f0 Win32Thread: 00000000`00000000 RUNNING on processor 1
Not impersonating
Owning Process fffff8a0`c6b13a80 Image: explorer.exe
Wait Start TickCount 0
// 스레드 주소로 상세 정보 확인
1: kd> !thread fffff8a0`c6d12345
THREAD fffff8a0`c6d12345 Cid 02a4.0b10 Teb: 00000000`001652f0 Win32Thread: 00000000`00000000
THREAD_STATE: Running
Kernel stack not resident.
| 스레드 상태 | 의미 |
| Running | 현재 CPU에서 실행 중이다. |
| Ready | 실행 준비가 되었고 CPU 할당을 기다린다. |
| Waiting | 이벤트, I/O, 락, 타이머 같은 조건을 기다린다. |
| Terminated | 종료되었거나 종료 과정에 있다. |
3.5 핸들 테이블 분석
프로세스는 파일, 레지스트리 키, 이벤트, 섹션, 토큰 같은 커널 객체에 접근하기 위해 핸들(Handle)을 사용한다. 핸들은 실제 커널 객체 주소를 직접 노출하지 않는 간접 참조 값이며, 접근 권한과 함께 관리된다.
| 개념 | 설명 |
| Handle | 프로세스가 커널 객체를 참조하기 위해 사용하는 값이다. |
| Object | 파일, 이벤트, 뮤텍스, 토큰 등 커널이 관리하는 실제 객체다. |
| GrantedAccess | 해당 핸들로 허용된 접근 권한이다. |
| Handle Table | 프로세스별 핸들 목록과 권한 정보를 담는 테이블이다. |
실습 3. 핸들 테이블 확인
// 프로세스 주소를 지정해 핸들 목록 확인
1: kd> !handle 0 0 fffff8a0`c6b13a80
Handle table at fffff8a0`c6b2bcf0 with 156 entries in use
0014: Object: fffff8a0`c3d4e800 GrantedAccess: 00120019 Entry: fffff8a0`c6c00050
Object: fffff8a0`c3d4e800 Type: (fffff8a0`c1d2e010) Directory
ObjectHeader: fffff8a0`c3d4e7d0
HandleCount: 5 PointerCount: 6
Directory Object: 00000000 Name: KnownDlls
0018: Object: fffff8a0`c4d5f200 GrantedAccess: 00120089 Entry: fffff8a0`c6c00060
Object: fffff8a0`c4d5f200 Type: (fffff8a0`c1d2e5a0) File
Name: \Device\HarddiskVolume1\Windows\System32\kernel32.dll
| 명령 형식 메모 !handle 명령의 인자 순서는 WinDbg 버전과 확장 도움말에 따라 표현이 다르게 보일 수 있다. 헷갈리면 !handle -?로 현재 환경의 형식을 먼저 확인한다. |
3.6 실습: 실제 프로세스 분석 연습
다음 실습은 Target VM에서 notepad.exe를 실행한 뒤 WinDbg 커널 세션에서 프로세스, 부모 관계, 스레드, 핸들 정보를 차례로 확인하는 종합 연습이다.
실습 4. 메모장 프로세스 추적
// 1. Target VM에서 notepad.exe 실행
// 2. WinDbg에서 Ctrl+Break로 커널 디버깅 세션 중단
// 3. 프로세스 목록에서 notepad 찾기
1: kd> !process 0 0
PROCESS fffff8a0`d7b13a80 SessionId: 1 Cid: 03c4 Peb: 7ffda000 ParentCid: 02a4
Image: notepad.exe
// 출력이 길면 WinDbg 명령 창 또는 로그에서 notepad.exe를 검색한다.
// 콘솔형 kd/cdb 환경에서는 아래처럼 필터링할 수 있다.
kd> !process 0 0 | findstr /i notepad
// 4. 상세 정보와 스레드 확인
1: kd> !process fffff8a0`d7b13a80 1
// 5. 부모 프로세스 확인
1: kd> !process fffff8a0`c6b13a80 0
PROCESS fffff8a0`c6b13a80 SessionId: 1 Cid: 02a4 Peb: 7ffd9000 ParentCid: 01e0
Image: explorer.exe
// 6. 스레드 상세 정보 확인
1: kd> !thread fffff8a0`d7d12345
// 7. 핸들 요약 확인
1: kd> !handle 0 0 fffff8a0`d7b13a80
| 체크 항목 | 확인 방법 |
| notepad 프로세스의 PID를 찾았는가? | !process 0 0 출력의 Cid를 확인한다. |
| 부모 프로세스가 explorer.exe임을 확인했는가? | ParentCid와 explorer.exe의 Cid를 비교한다. |
| 핸들 개수를 확인했는가? | !process 출력의 HandleCount 또는 !handle 출력을 본다. |
| 스레드가 몇 개 있는지 확인했는가? | !process <EPROCESS> 1 출력의 THREAD 항목을 센다. |
| 대기 중인 스레드의 WaitReason을 확인했는가? | !thread 출력에서 상태와 대기 이유를 확인한다. |
3.7 자주 쓰는 WinDbg 명령어 정리
| 명령어 | 설명 | 예시 |
| !process 0 0 | 모든 프로세스 목록 | !process 0 0 |
| !process 0 1 | 모든 프로세스와 스레드 요약 | !process 0 1 |
| !process <주소> 1 | 특정 프로세스 상세 정보와 스레드 | !process fffff8a0`c6b13a80 1 |
| !thread <주소> | 스레드 상세 정보 | !thread fffff8a0`c6d12345 |
| !handle 0 0 <주소> | 프로세스 핸들 목록 | !handle 0 0 fffff8a0`c6b13a80 |
| dt nt!_EPROCESS <주소> | 프로세스 구조체 직접 확인 | dt nt!_EPROCESS fffff8a0`c6b13a80 |
| dt nt!_KPROCESS <주소> | 스케줄링 관련 구조체 확인 | dt nt!_KPROCESS fffff8a0`c6b13a80 |
| lm | 로드된 모듈 목록 | lm |
| !peb | 현재 프로세스 PEB 확인 | !peb |
이것이 궁금하다
| Q. PID가 4인 System 프로세스는 왜 항상 존재하나요? System 프로세스는 Windows 커널이 사용하는 기본 실행 컨텍스트다. 부팅 직후 생성되어 시스템 종료까지 존재하며, 커널 모드 드라이버와 시스템 스레드가 이 컨텍스트에서 실행될 수 있다. |
| Q. 핸들과 포인터의 차이는 무엇인가요? 핸들은 운영체제가 관리하는 간접 참조 값이고, 포인터는 메모리 주소다. 핸들은 권한 검사와 객체 수명 관리를 거치므로 보안과 안정성 측면에서 중요하다. |
| Q. 스레드가 Waiting 상태라면 무엇을 기다리나요? I/O 완료, 이벤트 신호, 락 해제, 타이머 만료 같은 조건을 기다릴 수 있다. !thread 출력의 WaitReason과 호출 스택을 함께 보면 대기 원인을 더 구체적으로 좁힐 수 있다. |
| Q. ParentCid만 보면 완전한 프로세스 트리를 알 수 있나요? 항상 그렇지는 않다. 부모가 종료되었거나 PID가 재사용되었을 수 있다. 라이브 시스템에서는 ParentCid, 생성 시간, 이벤트 로그, EDR/ETW 로그를 함께 보는 것이 더 정확하다. |
| Q. 프로세스 메모리 덤프를 어떻게 생성하나요? 사용자 모드 디버깅에서는 .dump /ma C:\dump\process.dmp 명령으로 현재 프로세스의 전체 메모리 덤프를 만들 수 있다. 커널 디버깅 중에는 대상 컨텍스트와 덤프 목적을 먼저 확인해야 한다. |
3장 요약
| 핵심 내용 | 설명 |
| 프로세스 | 실행 중인 프로그램의 인스턴스로, 고유한 주소 공간과 시스템 자원을 가진다. |
| _EPROCESS | 고수준 프로세스 정보를 담는 구조체로, PID, 이미지 이름, 핸들 테이블, 부모 PID 등을 포함한다. |
| _KPROCESS | 저수준 스케줄링 정보를 담는 구조체로, 스레드 리스트와 CPU 시간 관리에 사용된다. |
| !process | 프로세스 목록과 상세 정보를 출력하는 핵심 명령어다. |
| 부모-자식 관계 | ParentCid와 InheritedFromUniqueProcessId를 통해 생성 관계를 추적한다. |
| 스레드 | 프로세스 내부에서 실제로 실행되는 흐름이며 !thread로 상태를 확인한다. |
| 핸들 테이블 | 프로세스가 소유한 파일, 이벤트, 레지스트리 키 같은 커널 객체 참조를 관리한다. |
다음 장 미리보기
4장에서는 프로세스 내부에서 실제 실행 흐름을 만드는 스레드와 스택을 더 깊게 살펴본다. 호출 스택, 대기 원인, 커널 스택을 함께 읽으면 장애 분석과 악성 행위 추적에서 훨씬 더 많은 단서를 얻을 수 있다.
'Windows' 카테고리의 다른 글
| Windows - Bat 배치 파일 프로세스 2개 이상 동시에 실행시키기 (0) | 2024.06.21 |
|---|---|
| 코파일럿 2024년 4월 한글 지원 예정 (0) | 2024.03.06 |
| 윈도우 - 블루투스 안되고, 알 수 없는 USB 장치 해결 방법 (0) | 2024.03.02 |
| Visual Studio - 전체 검색 Ctrl+T,Ctrl+Q (0) | 2024.02.28 |
| Visual Studio 2022 - 자동 줄 정렬 단축키 (0) | 2024.02.28 |