메모리의 실체, 공포의 파란 화면

윈도우 구조는 크게 유저모드와 커널모드로 메모리 영역이 나누어져 있다. 여기에서는 커널 메모리 영역과 윈도우에서 메모리를 관리하는 방식에 대해 정리해 보도록 하겠다. 메모리 부분은 역분석에서 주요 분석 부분이므로, 이 구조 이해도 필요하다 할 수 있다.

윈도우는 실제 메모리보다 많은 공간을 사용할 수 있도록 가상 메모리 공간을 만들어 사용한다.

가상 메모리는 메모리상의 일부 영역을 디스크로 페이징시켜 메모리 공간처럼 사용한다.

이러한 가상 메모리 관리를 메모리 관리자가 진행하는데, 가상 메모리 할당과 해제, 프로세스간 메모리 공유, 파일과 메모리 맵핑등 메모리 관련 전반 작업을 진행한다.

이로 인해 각 프로세스는 독립적인 가상 메모리를 메모리 관리자로부터 제공받게 된다.

윈도우에서는 각 프레세스가 사용할 수 있는 가상메모리를 생성, 관리하는데, 이는 각 프로세스들이 메모리를 신경쓰지 않고 사용할 수 있도록 도와준다. 윈도우는 물리 메모리를 절약하기 위해 Copy-on-wirte라는 기술을 사용하여 프로세스가 공유하여 사용되는 메모리를 효율적으로관리한다.

Copy-on-wirte는 프로세스에서 쓰기 행위가 일어날때까지 복사를 지연시키다, 공유 페이지에 쓰기 행위가 일어나면 해당 페이지를 새로운 메모리 공간에 복사하여 해당 프로세스에 제공해, 변경된 내용을 반영하게 된다. Copy-on-wirte 행위가 얼마나 일어나는지는 성능 모니터에서 Write Copies/sec를 통해 확인 할 수 있다.

[그림] 성능 모니터에서 확인한 Copy-on-wirte

 

프로세스 메모리 맵 보기

앞서 애기 했듯 윈도우에서는 메모리 매핑한 맵 파일을 관리하는 해당 맵 파일을 통해 해당 프로세스에서 연결중인 파일들을 확인 할 수 있다. 아래 그림과 같이 Prcoess expoler 를 통해 해당 프로세스 메모리에 매핑된 파일들(DLL포함)을 확인 할 수 있다. 이는 차후 역분석에서도 유용하게 사용된다.

[그림] Process explorer를 이용하여 매핑된 파일들을 확인할 수 있다.

 

그럼 커널 메모리 구조에 대해 애기해 보도록 하겠다.

[그림] 커널 메모리 구조

 

각 영역들에 대해서도 간단히 알아보자.

System Code: NTLDR에 의해 HAL, 커널, 부팅 관련 디바이스 드라이버를 이곳에 로딩되게 된다.

System mapped view, Session space: Win32k.sys가 매핑되는 공간으로, 커널 모드 그래픽 드라이버가 이를 사용한다. Session space는 Desktop heap하고 관계 가 있다. 이부분은 이후 다시 다룰것이다.

Process page tables and page directory: 가상 메모리 매핑 정보를 가지고 있다.

Hyperspace: Process Working set list 정보(EPROCESS.Vm.VmWorkingSetList)가 여기 매핑된다.

System working set list: System working set 정보가 여기 위치한다.

System cache: 가상 메모리 공간에서 사용되는 파일이 여기 매핑된다.

Paged pool: 페이지된 시스템 메모리 힙이 여기 위치 한다. 가상메모리 공간에 위치하는 영역으로, 페이지 아웃이나 페이지 인 될수 있는 가상 메모리 영역이다. DPC/Dispatch 이상의 IRQL이 사사용되 않을 때 사용된다.

System page table entries (PTEs): I/O 정보, Kernel stracks과 메모리 관련 정보등 시스템 정보들이 매핑된다.

Nonpaged pool: 페이징되지 않는 시스템 메모리 풀이 여기 위치 한다. DPC/Dispatch 이상의 IRQL은 페이지 폴트를 처리를 처리할 수 없기 때문에 이 영역을 이용하게 된다.항상 물리 메모리 내에 있도록 보장되는 메모리 영역으로 페이지 폴트 없이 접근이 가능하게 된다.

Crash dump information: 시스템 크래쉬시 관련 정보를 저장하기 위해 예약된 공간

HAL 예약 공간: HAL 용도로 사용되기 위해 예약한 공간

 

메모리 풀(Memory pool)

Ntoskrnl.exe가 커널 영역을 초기화 할 때 시스템 메모리 할당을 위해 사용되는 2개의 메모리 풀을 생성하는데, Nonpaged pool, Paged pool을 생성한다.

두 메모리 풀 모두 시스템 영역의 주소 공간에 위치해 모든 프로세스의 가상 주소에 맵핑되게 된다.(이를 위해 제공하는 함수가 ExAllocatePool( http://msdn.microsoft.com/en-us/library/windows/hardware/ff544501(v=vs.85).aspx )을 통해 할당 할 수 있다.)

 

시스템은 4개의 Page pool과 1개의 Nonpaged pool을 가지고 있는데, Nonpaged pool은 32bit시스템에서 최대 물리 메모리의 75%나 2GB중 작은값을 쓰고, Paged pool은 2GB를 사용하게 된다.

(이는 비스타 이후 변경된것이며 Windows 2003시절은 Nonpaged pool은 최대 256MB, Paged pool의 경우 491MB를 넘을 수 없었다.)

Nonpaged pool 메모리는 물리 메모리에만 할당 되는 것으로, 페이지 폴트가 없이 접근할 수 있는 메모리 영역이기도 하다. 우리가 사용하는 가상 메모리, 즉 디스크 공간을 스왑하여 사용되는 공간은 무수히 많은 페이지 폴트가 일어난다.(이것이 정상이다.) 그리고 접근 속도 역시 메모리보다 매우 느리다.(약1000배) 따라서 Nonpaged pool은 접근 속도도 역시 향상된게 된다. 이러한 장점으로 인해 고레벨의 IRQL 접근들도 오류가 없이 가능해진다.

1: kd> !vm

*** Virtual Memory Usage ***

    Physical Memory: 849790 ( 3399160 Kb)

    Page File: \??\C:\pagefile.sys

     Current: 3848972 Kb Free Space: 2205520 Kb

     Minimum: 3399160 Kb Maximum: 10197480 Kb

    Available Pages: 355709 ( 1422836 Kb)

    ResAvail Pages: 755369 ( 3021476 Kb)

    Locked IO Pages: 0 ( 0 Kb)

    Free System PTEs: 57708 ( 230832 Kb)

    Modified Pages: 3880 ( 15520 Kb)

    Modified PF Pages: 3813 ( 15252 Kb)

    NonPagedPool Usage: 0 ( 0 Kb)

    NonPagedPoolNx Usage: 15495 ( 61980 Kb)

    NonPagedPool Max: 523082 ( 2092328 Kb)

    PagedPool 0 Usage: 48738 ( 194952 Kb)

    PagedPool 1 Usage: 6753 ( 27012 Kb)

    PagedPool 2 Usage: 3540 ( 14160 Kb)

    PagedPool 3 Usage: 3512 ( 14048 Kb)

    PagedPool 4 Usage: 3559 ( 14236 Kb)

    PagedPool Usage: 66102 ( 264408 Kb)

    PagedPool Maximum: 523264 ( 2093056 Kb)

    Session Commit: 36302 ( 145208 Kb)

    Shared Commit: 90359 ( 361436 Kb)

    Special Pool: 0 ( 0 Kb)

    Shared Process: 6291 ( 25164 Kb)

    PagedPool Commit: 66136 ( 264544 Kb)

    Driver Commit: 9281 ( 37124 Kb)

    Committed pages: 862691 ( 3450764 Kb)

    Commit limit: 1811594 ( 7246376 Kb)

[내용] Windbg로 확인한 Windows 7의 메모리 현황

 

위와 같이 Windows XP 시절 Nonpaged pool와 Paged pool이 운영체제에 따라 크게 차이가 나게 된다. 과거 Windows 2003(Windows 2003 커널 구조는 XP를 계승하였다.)을 주로 사용하던 시전

Nonpaged pool이 가득차 시스템이 크래쉬나는 경우가 종종 발생 하였는데(커널모드에서 발생하는 오류는 대부분 크래쉬로 이여진다.), 프로그램에서 소켓을 생성할 때 이 Nonpaged pool 공간을 약간 소모하게 된다.(bind/connect/WSASend/WSARecv) 그리고 Nonpaged pool이 고갈 되었을때 Bugcheck 0x50 PAGE_FAULT_IN_NONPAGED_AREA라는 BSOD를 화면에 표시하고 운영체제는 동작을 멈추게 된다. 이 오류는 많이 발생하는 오류중 하나로, 아는 사람도 일 부 있을거라 판단된다..(이전 BSOD에서도 이 Bugcheck에 대해 애기 하였다.) 또 paged Pool 역시 작아(개수도 작다.) Paged pool Memory leak 메모리가 부족하여 할당하지 못했다는 메시지를 시스템 이벤트 ID 2019, 2020을 통해 많이 경험 했을 것이다.(시스템 크래쉬는 나지 않는다.) 대부분의 오류 원인은 메모리 풀을 비정상적으로 사용한다거나 너무 작은 메모리 풀을 할당하여 발생하는데, 이 오류를 해결하기 위해 시스템 메모리를 설정하는 레지스트리에 다음 레지스트리 설정을 변경하여 조치가 가능하다.(Windows 7의 경우 이 옵션을 사용할 일은 없을 것이다.)

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Memory Management

 

NonPagedPoolSize : 0xFFFFFFFF 데이터 형식: REG_DWORD

PagedPoolSize : 0xFFFFFFFF 데이터 형식: REG_DWORD

PoolUsageMaximum : 0x3c(10진수로 표기시 60 백분율방식 사용) 데이터 형식: REG_DWORD

[내용] 페이지풀 관련 설정

 

위 레지스트리 내용대로 설정하게 되면, Nonpaged pool과 Paged pool 사이즈를 최우선으로 할당하게 된다.(다른 영역보다) 그리고 PoolUsageMaximum는 해당을 퍼센트로 인식하여, 60% 도달하면, 사용하지 않는 Page pool을 정리하게 되는데, 값이 없을 경우 기본값인 80%로
동작하게 된다. 이 값을 너무 낮추게 된다면, 당연히 자주 Memory pool을 정리하여 성능이 느려지게 된다.

시스템에 맞는 설정을 찾아 적용하기를 권장한다.

추가로 Pool 내용 확인을 위해 Windbg에서 확장 명령으로 !pool과 !poolused(Gflag에서 Pooltag활성화 필요하다. Gflag는 Windbg와 함께 제공된다.) 혹은 Poolmon이라는 툴을 통해 Pool관련 정보를 확인 할 수 있는데 자세한 정보는 아래 링크를 확인하기 바란다.

http://support.microsoft.com/kb/177415

그럼 계속 메모리에 대해 알아보자.

 

페이지 테이블 엔트리(PTE)

IO공간, 커널스택, 메모리 디스크럽터 리스트와 같은 시스템 페이지들은 동적으로 시스템 페이지 테이블 엔트리에 할다하여 사용되는데, 가상 주소와 맵핑된 물리 메모리 주소가 이 PTE에 저장된다. 이 공간 역시 커널에 존재 하며, 무한히 제공되지 않는다.

기존 !vm이나 !sysptes 명령을 통해 현재 어느정도 시스템 PTE가 사용되는지 확인 할 수 있다.

1: kd> !sysptes

System PTE Information

Total System Ptes 60384

unable to get nt!MiPteBinMax

Unable to get syspte index array ― skipping bins

starting PTE: c0400000

 

free blocks: 70 total free: 3436 largest free block: 352

Kernel Stack PTE Information

Unable to get syspte index array ― skipping bins

starting PTE: c0400000

 

free blocks: 326 total free: 1854 largest free block: 40

[내용] Windbg를 이용하여 현재 PTE 사용현황을 확인

 

비스타 이후부터 MiSystemPteInfo라는 전역 변수를 통해서도 정보를 확인할 수 있는데,

이를 통해 실패 현황에 대해서도 확인 할 수 있어 PTE 누수 문제점에 대해 알 수 있다.

//MiSystemPteInfo 위치를 확인하자.

1: kd> ? nt!MiSystemPteInfo

Evaluate expression: -2078747392 = 8418d500

//PTE 할당 실패에 대한 자세한 정보를 확인할 수 있다.

1: kd> dt _MI_SYSTEM_PTE_TYPE 8418d500

nt!_MI_SYSTEM_PTE_TYPE

+0x000 Bitmap : _RTL_BITMAP

+0x008 Flags : 3

+0x00c Hint : 0x10d5d

+0x010 BasePte : 0xc0400000 _MMPTE

+0x014 FailureCount : 0x8418d528 -> 0

+0x018 Vm : 0x841b02c0 _MMSUPPORT

…중략

[내용] Windbg에서 MiSystemPteInfo를 이용하여 PTE 실패 현황을 확인할 수 있다

 

만약 PTE 할당에 실패가 많다면 PTE에 대한 트래킹 옵션을 활성화(HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\ 하위에 DWORD로 TrackPtes를 생성후 값 1을 설정하면 된다.)하여 !sysptes 4 명령을 통해 문제점에 대해 확인 할 수 있다.

PTE는 3부분으로 나누어 볼수 있는데, 페이지 디렉토리 인덱스, 페이지 테이블 인덱스, 바이트 인텍스로 구분 된다.

이중 바이트 인덱스에 크기는 페이지 크기에 따라 정해지는데, 페이지 크기가 4096(2의12승)바이트로 바이트 인덱스는 12bit가 된다.

각 프로세스는 하나의 페이지 디렉토리를 가지고 있는데, 메모리 관리자는 해당 프로세스의 모든 페이지 테이블에 대한 위치를 해당 프로세스 커널 프로세스 블록(KPROCESS)에 저장한다.(cr3과 KPROCESS의 DirBase는 페이지 디렉토리 인덱스 0을 가리킨다.)

[그림] Windbg로 확인한 프로그램의 DirBase

 

PTE의 10bit에 PDE 페이지 디렉토리 인덱스로, 해당 가상 주소에 대한 페이지 테이블을 가르키는 PDE가 들어 있다. 이는 cr3레지스터에는 현재 처리중인 프로세스의 물리적 주소값을 가지고 있다.

이는 PDE의 기준이 되고, X86 시스템에서는 가상 주소 0xC0300000에 맵핑된다.(메모리 확장 옵션인 PAE가 활성화 되어 있으면, 0xC0600000에 맵핑됨) 커널모드에서 동작중인 대부분이 물리 주소가 아닌 가상 주소를 참조한다.

1: kd> !pte 0

VA 00000000

PDE at C0600000 PTE at C0000000

contains 0000000022822867 contains 0000000000000000

pfn 22822 —DA—UWEV not valid

[내용] Windbg로 확인한 PTE

 

PTE 주소는 가상 주소로 이진수 형태로 변환하여 세개의 필드로 나누어 사용된다.

PTE가 0x80021인 경우 이진수로 아래와 같이 변환된다. (계산기를 이용해도 좋다.)

[그림] PTE 필드 구조

 

변환 작업을 위해 페이지 디렉토리 시작 주소가 필요 한데 이 값은 프로세스 내 스레드가 동작하는 동안 CR3 레지스터에 저장돼 있다. 이를 기준으로 PTE의 페이지 디렉토리 인덱스 값에 해당하는 위치를 찾는다. 해당 위치의 PDE에는 페이지 테이블의 페이지 프레임 번호(PFN)을 저장 되어 있는데 PFN을 통해 페이지 테이블의 시작 주소를 확인하게 된다, 이후 PTE의 페이지 테이블 인덱스를 통해 해당 PTE를 찾게 된다. 해당 PTE에는 요청한 페이지의 시작 페이지 프레임 번호(PFN)가 저장되어 있어 요청된 페이지의 기준이 되는 주소값으로부터, 바이트 인덱스 값을 통해 요청된 페이지를 찾게 된다.

아래 그림은 위 세값이 가상 주소에서 물리 주소로 변환이 어떻게 사용되는지 보여준다.

[그림] PTE를 이용한 물리 주소 변환 과정

 

//!pte 명령을 통해 페이지 테이블 엔트리 내용을 확인하자. 페이지 테이블 페이지의 pfn은 22822, 테이터 페이지는 92b36h에 위치함을 확인할 있다.

1: kd> !pte 80021

VA 00080021

PDE at C0600000 PTE at C0000400

contains 0000000022822867 contains 8000000092B36025

pfn 22822 —DA—UWEV pfn 92b36 —-A—UR-V

//각 PFN의 세부정보를 확인 할 수 있다.

1: kd> !pfn 22822

PFN 00022822 at address 855C63B8

flink 00000029 blink / share count 0000007B pteaddress C0600000

reference count 0001 Cached color 0 Priority 5

restore pte 00000080 containing page 0AEADA Active M

Modified

1: kd> !pfn 92b36

PFN 00092B36 at address 8620B9E8

flink 0000063C blink / share count 00000007 pteaddress 8DCDE470

reference count 0001 Cached color 0 Priority 6

restore pte 87DEDC18000004C0 containing page 06FB71 Active P

Shared

//그럼 이제 lmvm 명령을 통해 현재 로드한 가상 메모리 위치를 확인할 수 있다.

kd> lmvm RDPCDD

start end module name

f8a6e000 f8a6f080 RDPCDD (deferred)

Image path: RDPCDD.sys

Image name: RDPCDD.sys

Timestamp: Sat Aug 18 05:46:56 2001 (3B7D82C0)

CheckSum: 0000E2B7

…중략

//!pte 명령을 통해 물리 메모리 위치를 확인하자. pfn 9b6c000h를 가르키고 있음을 확인할 수 있다.

kd> !pte f8a6e000

VA f8a6e000

PDE at C0300F88 PTE at C03E29B8

contains 01019163 contains 09B6C163

pfn 1019 -G-DA—KWEV pfn 9b6c -G-DA—KWEV

//가상 메모리 데이터 확인

kd> dc f8a6e000

f8a6e000 00905a4d 00000003 00000004 0000ffff MZ…………..

f8a6e010 000000b8 00000000 00000040 00000000 ……..@…….

f8a6e020 00000000 00000000 00000000 00000000 …………….

f8a6e030 00000000 00000000 00000000 000000d0 …………….

f8a6e040 0eba1f0e cd09b400 4c01b821 685421cd ……..!..L.!Th

f8a6e050 70207369 72676f72 63206d61 6f6e6e61 is program canno

f8a6e060 65622074 6e757220 206e6920 20534f44 t be run in DOS

f8a6e070 65646f6d 0a0d0d2e 00000024 00000000 mode….$…….

//물리 메모리 데이터 확인 /p 옵션을 사용하거나, !dc 명령을 통해 물리메모리를 확인할 수 있다.

kd> dc /p 09B6C000

09b6c000 00905a4d 00000003 00000004 0000ffff MZ…………..

09b6c010 000000b8 00000000 00000040 00000000 ……..@…….

09b6c020 00000000 00000000 00000000 00000000 …………….

09b6c030 00000000 00000000 00000000 000000d0 …………….

09b6c040 0eba1f0e cd09b400 4c01b821 685421cd ……..!..L.!Th

09b6c050 70207369 72676f72 63206d61 6f6e6e61 is program canno

09b6c060 65622074 6e757220 206e6920 20534f44 t be run in DOS

09b6c070 65646f6d 0a0d0d2e 00000024 00000000 mode….$…….

[내용] PTE를 이용한 물리 메모리 확인

 

공포의 파란화면

이 파 란화면의 문제 해결에 대한 내용을 넣어야 할지 넣지 않는게 나을지 고민을 많이 했다. 문제 해결은 난이도가 높고 넓어서, 몇 페이지로 애기한다는게 어렵기 때문에, 여러 차례 고심을 하였다. 하지만 운영중 발생하는 문제점들을 해결하는 것 역시 관리의 중요 이슈이기에, 여기서 다루어야 한다 판단하여, 여러분들에게 윈도우의 대표적인 오류인 블루스크린에 대해 애기하고, 이 블루스크린을 해결할 수 있는 접근 방법에 대해 애기해 주어, 엔지니어로써 이를 분석하여, 해결할 수 있는 방안으로 제공하고자 한다. 아마 문제 해결을 원활히 하기 위해서는 커널 분석 뿐만아니라 시스템 영역을 전반적으로 이해하고, Windbg가 숙달 되었을때, 원활한 분석을 진행할 수 있을것이다. 여기의 몇장되는 페이지로 장애시 커널 분석을 원활히 진행하기는 어려울 것이지만, 블루스크린의 처리 프로세스에 대해 이해하고, 메모리 덤프를 생성하는것과 일반적인 분석 방법론에 대해 이해할 수 있도록 하여, 여러분들이 더 큰 그림을 볼수 있는 안목을 만들어, 앞으로 나아갈수 있게 하고자 함이 목표임을 애기하고 진행하도록 하겠다.

 

파란화면 BSOD(Blue Screen of Death)

시스템을 더 이상 유지할 수 없을 정도의 예외가 발생하였을 때, 블루스크린이 발생하는데 이 역시 문제를 해결할 수 있는 코드를 제공하는 예외 상황이라 할 수 있다. (몇몇 오류는, 블루 스크린을 볼수 없다. 이와 같은 경우를 Durty Shutdown이라 부른다.) 대부분이 CPU 혹은 커널 모드에서 동작하는 드라이버들의 예외 상황이 발생시 블루 스크린이 발생하게 되며 (유저모드 예외 상황시에는 Dr. Watson이나 Windows Error Reporting에 의해 좀더 정교한 작업이 진행된다. 이에 대해서는 2장에서 설명하도록 하겠다.) 이는 윈도우 시스템을 보호 하기 위한 기능으로 커널에서 시스템 오류를 감지하면, CPU 프로세서는 실행을 멈추고 Trap Frame을 생성후 KeBugCheckEX 함수를 실행하여 오류 코드와 4개의 파라메터를 생성하고 아래와 같이 블루스크린 화면을 표시하고 메모리 덤프를 레지스티리 설정에 따라 생성 혹은 비 생성후 운영제체를 재시작 하게 된다.

이 오류 코드와 4개의 파라메터를 문제점을 분석할 수 있는 단서가 되며, 이를 통해 생성된 메모리 덤프르 분석하여 원인을 찾을 수 있게 된다.

 

오류 처리 – KeBugCheckEx

윈도우에서는 IDT(2장 내부구조 이해에서 다룬다.)를 이용해 인터럽트 및 예외(Exception)를 처리하는데, BSOD와 같은 예외 상황 역시 IDT 테이블 통해 처리되며, 최종 KeBugCheckEx를 통해 오류상황에 대처한다.

그럼 KeBugCheckEx의 구조체를 알아보자.

VOID KeBugCheckEx(

__in ULONG BugCheckCode,

__in ULONG_PTR BugCheckParameter1,

__in ULONG_PTR BugCheckParameter2,

__in ULONG_PTR BugCheckParameter3,

__in ULONG_PTR BugCheckParameter4

);

[내용] KeBugCheckEx 구조체

 

첫번째 값은 버그 체크 코드(Bug check code) 코드이며, 그외 4가지 값은 해당 버그 체크 코드의 파라메터가 표시되는데 이 파라메터는, 버그 체크 코드별로 해석이 달리 된다.

그럼 커널 모드 에러가 발생하면 어떻게 진행되는 것일까?

 

  1. 커널 디버거로 처리를 넘긴다.(커널 디버거를 연결한 경우 chance 3)
  2. 블루스크린을 표시하며, 해당 버그 체크 코드를 화면에 표시함
  3. 메모리 덤프 파일을 생성한다.(가능한 경우)
  4. 시스템을 재부팅한다.(재부팅 설정이 되었을 경우)

     

    1번의 상황이 제일 기본적인 상황으로 많이 경험해 봤을 것이며, 2번의 경우 앞서 진행한 실시간 커널 디버깅 상태에서 이루어 진다. 그 외 3, 4번의 경우 상황이 맞을 경우에 한하여 구현되게 된다. 그럼 이 장애 처리 흐름을 조금 더 자세히 애기해보자.

     

    KeBugCheckEx 처리 흐름 분석

    그럼 BSOD는 어떻게 발생하는지 처리 흐름에 대해 확인해 보도록하자.

    우리는 앞서 부팅 과정을 통해 시스템 프로세스에 대해 공부했었다. 이 시스템 프로세스들이 종료되었을 때 역시 BSOD상황이 발생하게 된다는걸 알것이다.

    그럼 여기서는 이를 이용해서 BSOD 처리 흐름을 알아보자.

    앞서 애기 했듯이 시스템 프로세스는 기본적으로 작업관리자에서 종료할 수 없다.

    Sysinternals에서 제공하는 Process Expolerer로 Csrss를 종료할 수 있으며, 여기서는 C# 프로그램을 통해 Csrss.exe를 종료해 보도록 하자.

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

     

    namespace BSOD

    {

    class Program

    {

    static void Main()

    {

    System.Diagnostics.Process.GetProcessesByName(“csrss”)[0].Kill();

    }

    }

    }

    [내용] Csrss 시스템 프로세스를 종료시켜 BSOD를 유발하는 C#코드

     

    위 코드를 컴파일한후 실행하면, 아래와 같이 Bugcheck 0xF4를 나타내며, 블루스크린이 발생하고, 메모리 덤프가 생성되게 된다.

    [그림] 시스템 프로세스를 종료하여 BSOD 발생

     

    위그림과 같은 BSOD는 다음과 같은 내부 프로세스를 통해 발생하게 된다.

    그럼 오류 내용을 Windbg를 이용하여 분석해 보자.

    !analyze -v는 누차 애기하지만 각 오류코드 상황에 맞게 오류를 자동으로 검색하여주는 편리한 확장 명령어 이다. 이 결과를 100% 신뢰할 수는 없지만, 참고용도로 유용하다. 즉 자동 분석 결과라 생각하면 편리할 것이다.

    이제 analyze 분석 결과를 살펴 보기전에 해당 Bugchek 코드를 Windbg 도움말에서 찾아 해당 문제점이 무엇을 의미하고 파라메터 값들이 무엇을 의미하는지 확인해 보도록 하자.

     

    [그림] Windbg 도움말을 통해 해당 오류에 대한 정보를 얻는다.

     

    Windbg 도움말에 의하면, 시스템 운영 프로세스 혹은 스레드가 강제 종료 되었다고 나온다.

    이제 해당 오류코드와 파라메터의 의미를 알았으니 현재 메모리 덤프의 오류 상황에 대입해 보도록 하자.

    파라메터 1: 0x3 à 프로세스의 의해 발생되었다

    파라메터 2: 82103020 à 0x82103020 주소의 오브젝트임을 알린다.

    파라메터 3: 82103194 à 파라메터 2에서 가르키는 오브젝트의 이름값은 0x82103194 주소

    파라메터 4: 8060777e à ASCII로 관련 메시지가 8060777e에 존재함

    그럼 이제부터 Windbg를 이용해서 분석을 진행해 보자.

    //!process 명령을 통해 파라메터 2: 82103020의 오브젝트를 확인하자 3옵션을 사용하면 세부정보까지 확인가능하다.

    0: kd> !process 82103020 3

    PROCESS 82103020 SessionId: 0 Cid: 0320 Peb: 7ffdb000 ParentCid: 0274

    DirBase: 07028000 ObjectTable: e165c7a0 HandleCount: 411.

    Image: csrss.exe

    VadRoot 81c56818 Vads 112 Clone 0 Private 404. Modified 570. Locked 0.

    DeviceMap e10001b8

    Token e16b6908

    ElapsedTime 00:00:49.631

    UserTime 00:00:03.156

    KernelTime 00:00:02.296

    QuotaPoolUsage[PagedPool] 80148

    QuotaPoolUsage[NonPagedPool] 6848

    Working Set Sizes (now,min,max) (1377, 50, 345) (5508KB, 200KB, 1380KB)

    …중략

    // 파라메터 3: 82103194의 위치를 dc명령을 통해 Double-word 방식으로 확인해 보자.

    0: kd> dc 82103194

    82103194 73727363 78652e73 00000065 00000000 csrss.exe

    //파라메터 4: 8060777e의 위치를 ASCII 방식으로 표시하는 da 명령을 통해 확인해보자.

    0: kd> da 8060777e

    8060777e “Terminating critical process 0x%”

    8060779e “p (%s).”

    [내용]Windbg를 이용하여, 해당 오류 코드의 파라메터 값 확인

     

    결과 Csrss.exe 프로세스가 종료되었음을 할 수 있다. 그럼 어떻게 종료된 것 인지를 조사해야한다.

    이를 위해 예외 상황 발생 당시 저장해 놓은 프레임을 이용하여 해당 포인터로 이동하여 문제점 발생 당시의 상황을 확인할 수 있다.

    //현재 처리중인 스레드를 확인하자.

    0: kd> .trap b1a16d64

    ErrCode = 00000000

    eax=0012f3b0 ebx=00160050 ecx=00000001 edx=001771b0 esi=0012f428 edi=0012f688

    eip=7c93e514 esp=0012f3d4 ebp=0012f3e4 iopl=0 nv up ei pl nz na pe nc

    cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206

    001b:7c93e514 ?? ???

    0: kd> !thread -1

    THREAD 81c10da8 Cid 0538.0578 Teb: 7ffdd000 Win32Thread: e2027eb0 RUNNING on processor 0

    Not impersonating

    DeviceMap e1e308f8

    Owning Process 0 Image: <Unknown>

    Attached Process 81c10020 Image: BSOD.exe

    Wait Start TickCount 4969 Ticks: 0

    Context Switch Count 510 LargeStack

    UserTime 00:00:00.000

    KernelTime 00:00:00.093

    Win32 Start Address 0x79004ddb

    Start Address 0x7c7e0705

    Stack Init b1a17000 Current b1a1677c Base b1a17000 Limit b1a13000 Call 0

    Priority 10 BasePriority 8 PriorityDecrement 2 DecrementCount 16

    ChildEBP RetAddr Args to Child

    b1a16d00 80637e9d 000000f4 00000003 82103020 nt!KeBugCheckEx+0x1b (FPO: [5,0,0])

    b1a16d24 8060773c 8060777e 82103020 82103194 nt!PspCatchCriticalBreak+0x75 (FPO: [3,0,0])

    b1a16d54 804df99f 82103268 ffffffff 0012f3e4 nt!NtTerminateProcess+0x7d (FPO: [2,4,4])

    b1a16d54 7c93e514 82103268 ffffffff 0012f3e4 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ b1a16d64)

    WARNING: Frame IP not in any known module. Following frames may be wrong.

    0012f3e4 00000000 00000000 00000000 00000000 0x7c93e514

    //사용된 스레드 프로세스가 BSOD.exe임을 확인하였다. 이 정보를 통해 해당 프로세스정보와 스레드정보를 확인하자.

    0: kd> !process 81c10020 3

    PROCESS 81c10020 SessionId: 0 Cid: 0538 Peb: 7ffde000 ParentCid: 02a8

    DirBase: 14964000 ObjectTable: e1dc2950 HandleCount: 83.

    Image: BSOD.exe

    VadRoot 81f7f1d0 Vads 80 Clone 0 Private 277. Modified 5. Locked 0.

    DeviceMap e1e308f8

    …중략

    CommitCharge 2227

    //해당 프로세스에서 실행중이던 스레드들을 확인할 수 있다.

    THREAD 81c10da8 Cid 0538.0578 Teb: 7ffdd000 Win32Thread: e2027eb0 RUNNING on processor 0

    THREAD 81f7a140 Cid 0538.03d4 Teb: 7ffdc000 Win32Thread: 00000000 WAIT: (UserRequest) UserMode Non-Alertable

    81d14ff0 SynchronizationEvent

    81d14f90 SynchronizationEvent

    81d14b50 SynchronizationEvent

     

    //동작중이던 스레드인 81c10da8를 확인해 보면, 크래쉬 상황으로 연결됨을 확인할 수 있다.

    0: kd> !thread 81c10da8

    THREAD 81c10da8 Cid 0538.0578 Teb: 7ffdd000 Win32Thread: e2027eb0 RUNNING on processor 0

    Not impersonating

    DeviceMap e1e308f8

    Owning Process 0 Image: <Unknown>

    Attached Process 81c10020 Image: BSOD.exe

    Wait Start TickCount 4969 Ticks: 0

    …중략

    //해당 스레드에서 문제가 발생하였음을 알 수 있다.

    b1a16d00 80637e9d 000000f4 00000003 82103020 nt!KeBugCheckEx+0x1b (FPO: [5,0,0])

    b1a16d24 8060773c 8060777e 82103020 82103194 nt!PspCatchCriticalBreak+0x75 (FPO: [3,0,0])

    b1a16d54 804df99f 82103268 ffffffff 0012f3e4 nt!NtTerminateProcess+0x7d (FPO: [2,4,4])

    b1a16d54 7c93e514 82103268 ffffffff 0012f3e4 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ b1a16d64)

    WARNING: Frame IP not in any known module. Following frames may be wrong.

    0012f3e4 00000000 00000000 00000000 00000000 0x7c93e514

    //현재 메모리 덤프가 커널메모리덤프로써 전체 영역을 담고있지않아 출돌 당시의 상황을 확인하기는 어려움이 있음을 확인할 수 있다. 따라서 문제점 분석을 진행할때는 전체 메모리 덤프를 생생성하는 것 습관화하자 J

    0: kd> dds esp

    0012f3d4 ????????

    0012f3d8 ????????

    0012f3dc ????????

    …중략

    [내용] Windbg를 이용한 오류 원인 확인

     

    적은 분량에 큰 내용을 애기하려다 보니 여러분이 이해하기 어려운 부분들이 많았을 거란 생각이 든다.

    그래도 여러분들이 이글을 통해 커널에 관심을 가지고 공부를 시작할 수 있었다면 그것으로 만으로도 필자는 만족한다.

    그리고 앞으로도 더 높은 산을 바라보는 사람이 되기를 여러분과 나 자신에게 바란다.

Facebook Comments

Leave A Reply

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.