뭉근 : 느긋하게 타는 불

이번 포스트는 Windbg에서 프로세스 리스트를 볼 때 왜 유휴 프로세스(Idle Process)는 없는가? 에 대한 내용이다. Process Explorer로 보면 아래와 같이 Idle Process가 나오는데, 왜 프로세스 리스트를 직접 출력해보면 유휴 프로세스를 찾을 수 없는 것일까? 궁금하면 아래의 글을 읽어보기 바란다.



들어가기

윈도우는 시스템 프로세스 중 커널 영역에서만 동작하는 프로세스로 Idle Process(유휴 프로세스)와 System Process(시스템 프로세스)를 사용한다. 이 두 프로세스들은 유저 모드에서 실행되지 않기 때문에 커널 영역에서만 존재한다.   

System Process는 커널 모드 시스템 스레드(kernel-mode system thread)들을 포함한다. 이 스레드들은 오직 커널 주소 공간의 코드들만을 실행하여 커널 레벨에서만 동작한다.

Idle Process는 CPU의 각 프로세서의 유휴 상태를 나타내기 위한 Idle Thread(유휴 스레드)를 포함한다. 프로세서에 더 이상 실행 가능한 스레드가 없을 때, 윈도우는 해당 프로세서의 유휴 스레드를 실행한다. 이는 멀티프로세서 시스템에서 하나의 CPU가 실행될 때, 다른 CPU들이 실행할 스레드들을 가지지 않는 상황이 있기 때문이다. 따라서 Idle Thread는 프로세서의 수만큼 생성된다.

Idle Process와 Idle Thread들은 특수한 케이스로 EPROCESS / ETHREAD 구조체로 표현되지만 익스큐티브 오브젝트 관리자에 의해 생성되는 프로세스와 스레드 오브젝트들이 아니기 때문에 시스템의 프로세스 리스트에 포함되지 않는다[0].


---------------------------------------------------------------------------------

0: kd> !process 0 0

**** NT ACTIVE PROCESS DUMP ****

PROCESS fffffa800ccc2990

SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000

DirBase: 00187000 ObjectTable: fffff8a000001740 HandleCount: 479.

Image: System

   

PROCESS fffffa800d2fcb30

SessionId: none Cid: 00e8 Peb: 7fffffde000 ParentCid: 0004

DirBase: 2495c000 ObjectTable: fffff8a0005c07d0 HandleCount: 30.

Image: smss.exe

   

… (생략)

   

PROCESS fffffa800f2b3b30

SessionId: 1 Cid: 0948 Peb: 7fffffd4000 ParentCid: 08d8

DirBase: 3091b000 ObjectTable: fffff8a0017fdea0 HandleCount: 134.

Image: vmtoolsd.exe

   

PROCESS fffffa800f2e6b30

SessionId: 1 Cid: 0954 Peb: 7efdf000 ParentCid: 08d8

DirBase: 30cce000 ObjectTable: fffff8a0018be990 HandleCount: 41.

Image: runonce.exe

---------------------------------------------------------------------------------


위의 결과를 보면 프로세스 리스트가 System Process부터 시작한다는 것을 알 수 있다. 물론 언급했던 것과 같이 Idle Process는 나오지 않는다.

커널은 프로세스 리스트를 유지하기 위해 이중 연결 리스트(double-linked list)를 사용한다. 프로세스들은 생성된 순서대로 이 이중 연결 리스트에 들어가고, 프로세스가 종료된다면 역시 이중 연결 리스트에서 제거된다.

따라서 !process 0 0 명령어를 사용하지 않고도 직접 이 이중 연결 리스트를 따라가 리스트에 있는 각각의 프로세스를 추출할 수 있다. (* 아마도 !process 명령어도 이 리스트를 따라가지 않나 싶다. 실제로 확인해보지는 않았다 :) )   

윈도우 커널은 프로세스 리스트의 헤드를 전역 변수로 관리하며, 이에 대한 심볼 정보를 PsActiveProcessHead로 공개하고 있다. 간단히 말해서 전역 변수인 PsActiveProcessHead를 시작으로 실행 중인 프로세스들의 정보에 접근한다.   

Windbg의 dq 명령을 통해 직접 순회하여 볼 수도 있지만, !list 명령을 통해 다음과 같이 각각의 리스트 요소에 대해 정보를 일괄적으로 얻을 수 있다. 아래의 !list 명령어는 리스트의 각 요소인 ActiveProcessLinks를 참조한다.


---------------------------------------------------------------------------------

0: kd> !list "-t nt!_EPROCESS.ActiveProcessLinks.Flink -e -x \"dd @$extret L1; dt nt!_EPROCESS @$extret ImageFileName\" poi(nt!PsActiveProcessHead)-0x188"

dd @$extret L1; dt nt!_EPROCESS @$extret ImageFileName

fffffa80`0ccc2990 00580003

+0x2e0 ImageFileName : [15] "System"

   

dd @$extret L1; dt nt!_EPROCESS @$extret ImageFileName

fffffa80`0d2fcb30 00580003

+0x2e0 ImageFileName : [15] "smss.exe"

   

… (생략)

   

dd @$extret L1; dt nt!_EPROCESS @$extret ImageFileName

fffffa80`0f2b3b30 00580003

+0x2e0 ImageFileName : [15] "vmtoolsd.exe"

   

dd @$extret L1; dt nt!_EPROCESS @$extret ImageFileName

fffffa80`0f2e6b30 00580003

+0x2e0 ImageFileName : [15] "runonce.exe"

---------------------------------------------------------------------------------

* 0x188은 Windows 7 x64 시스템의 ActiveProcessLinks 오프셋이다. 이는 버전마다 다를 수 있다.

  

!process 명령어를 사용했을 때와 마찬가지로 System Process는 나타나지만, Idle Process는 나타나지 않는다. 즉 Idle 프로세스는 프로세스의 이중 연결 리스트에 포함되지 않는다.

그렇다면 Idle Process는 어떻게 생성되길래 System Process도 포함되어 있는 프로세스 리스트에는 존재하지 않는건가?

   

Idle Process 찾아가기

일단 Idle Process의 존재부터 알아본다. 윈도우 인터널스[0]에 Idle Process로 접근하는 방법이 나와있지만, Windbg로 디버깅시에는 전역 심볼인 PsIdleProcess을 이용하여 더 간단하게 접근할 수 있다.

   

---------------------------------------------------------------------------------

0: kd> dt _EPROCESS poi(PsIdleProcess) ImageFileName ActiveProcessLinks

nt!_EPROCESS

+0x188 ActiveProcessLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]

+0x2e0 ImageFileName : [15] "Idle"

---------------------------------------------------------------------------------

   

이 Idle Process의 ActiveProcessLinks 필드를 살펴보면 Flink와 Blink가 모두 NULL로 처리되어 있음을 확인할 수 있다. 즉 Idle Process는 다른 프로세스들을 가리키지 않는다.

* KiInitialProcess 로도 접근할 수 있다. PsIdleProcess는 EPROCESS 구조체의 주소에 대한 주소를 가진 반면 KiInitialProcess는 _EPROCESS 구조체의 주소를 가진다.


Idle Process는 시스템에서 반드시 생성하는 정적인 데이터이기 때문에 전역 심볼로 관리된다. 생성 및 파괴를 반복하는 일반적인 프로세스와 다르게 한번 생성되면 시스템이 실행되는 동안 파괴되지 않는다. 그렇다면 System 프로세스는 어떨까? System 프로세스 또한 아래와 같이 전역 변수인 PsInitialSystemProcess로 관리된다.

   

---------------------------------------------------------------------------------

0: kd> dt _EPROCESS poi(PsInitialSystemProcess) ImageFileName

nt!_EPROCESS

+0x2e0 ImageFileName : [15] "System"

---------------------------------------------------------------------------------

   

사실 커널 디버깅이 아닌, 실제 커널 코드 코딩(ex. 드라이버)를 할 때에 PsInitialSystemProcess는 전역 변수로 export[1]되어 있어 프로그래머가 접근할 수 있다. 하지만 Idle Process의 디버깅 심볼인 PsIdleProcess는 MS에서 심볼 정보만 제공하기 때문에 커널 코드 작성 시에 사용할 수 없다. 사실 생각해보면 유휴 프로세스에 드라이버가 접근할 필요가 없다. 인터널스에서도 언급한대로, 유휴 프로세스는 유휴 스레드를 위한 프로세스로 내부 객체를 살펴보면 일반적인 프로세스들과 달리 대부분의 필드들이 사용할 필요가 없기 때문에 NULL(0)으로 채워져 있다.

또한 구현 측면에서 보면 인터널스에 업근된 대로, 초기 유휴 스레드(PsInitialSystemThread)와 유휴 프로세스(PsInitialSystemProcess) 구조체는 정적으로 할당돼 프로세스 관리자와 객체 관리자가 초기화되기 전에 시스템 부팅을 위해 사용된다. 그 이후 유휴 스레드 구조체는 추가 프로세서가 동작할 때 동적으로 할당된다.

그렇다면 위의 말이 진짜일까? 궁금해진다.

   

Idle Process 생성

디버깅 심볼 정보인 PsIdleProcess와 PsInitialSystemProcess가 사용되는 부분을 찾으면 아래와 같이 PspInitPhase0 함수에서 Idle Process와 System Process의 EPROCESS.ImageFileName에 각 프로세스 이름을 쓰는 것을 볼 수 있다. 다른 프로세스들은 파일 이름을 기반으로 프로세스 이름이 정해지는데 비해, 이 두 프로세스들은 특수한 프로세스로서 프로세스 이름이 정해진 커널 코드에 의해 정해진다.

   

* 0x16c는 윈도우 7 32비트 상에서 _EPROCESS.ImageFileName 필드의 오프셋이다.

   

PspInitPhase0 함수는 커널을 초기화할 때 실행되는 함수 중 하나로, 커널은 KiSystemStartup 함수를 시작으로 다음과 초기화 함수를 호출한다. PsActiveProcessHead를 참조하는 모든 코드를 살펴보면 이 PspInitPhase0 함수에서 아래와 같이 PsActiveProcessHead를 초기화하는 것을 볼 수 있다.

   

   

코드를 살펴보면 KeGetCurrentThread 함수를 이용하여 현재 쓰레드가 속한 프로세스를 얻는다. 프로세스의 몇몇 필드를 초기화한 후 전역 변수 PsIdleProcess에 해당 프로세스의 주소를 대입한다. EPROCESS 구조체 필드는 이게 다가 아닌데 나머지는 어디서 초기화하는 걸까? 또한 KeGetCurrentThread는 어떤 쓰레드를 얻어오는건가?

   

커널 초기 쓰레드의 생성

KiSystemStartup 함수부터 살펴보면 다음과 같은 순서로 커널 초기화 함수가 실행된다.

   

KiSystemStartup()

  - KiInitialThread를 스택에 저장

  - KiInitilizePcr()

  - KiIntializeKernel()

    - KiInitializeProcess()

    - KiInitializeThread()

    - InitBootProcessor()

      - PsInitSystem()

      - PspInitPhase0()

   

KiSystemStartup 함수는 전역 변수 KiInitialThread를 스택에 저장한다.

   

   

이후 코드에서 스택에 저장한 값을 커널 프로세서 관리 구조체(Kernel Processor Control Region)인 KPCR의 IdleThread에 그 값을 등록한다.

   

   

   

이후 KiInitializeKernel 함수를 실행한다. 이 때 스레드 초기화를 위해 앞서 스택에 저장한 KiInitialThread를 호출 인자로 넘겨준다. KiInitilizeKernel을 호출할 때 KiInitialProcess 또한 인자로 같이 넘긴다.

   


KiInitializeProcess 함수를 먼저 실행하여 Idle Process의 나머지 필드들을 초기화한다. 사실 이 함수의 내부를 보면 복잡한 초기화가 아니라 앞에서 0으로 채웠던 것처럼 다른 필드들도 NULL 값으로 채운다.

   

   

Idle Process의 필드를 초기화한 후, 시스템 스레드를 초기화하는 KiInitializeThread 함수를 실행한다.

   

   

이 함수에서는 크게 KeInitThread 함수와 KeStartThread 함수를 호출한다. KeInitThread 함수를 호출하는 코드를 찾아보면 일반적인 스레드를 생성할 때 호출하는 용도로도 사용되는 것을 알 수 있는데, 이 때 입력받는 인자가 다르다. 실제로 PspAllocateThread를 찾아가보면 아래와 같이 인자가 일반적인 스레드와 다르게 들어간다는 것을 알 수 있다.

   

   

   

다시 KiInitilizeThread 함수로 돌아와 생각해보면 이 함수가 호출될 때 입력 받는 인자는 Idle Thread와 Idle Process이다. KiInitializeThread 함수에서 실행되는 KeInitThread 함수는 다음 코드와 같이 Idle Thread가 속한 프로세스를 Idle Process라고 명시한다.

   

   

앞서 커널의 첫 쓰레드는 언제 어떻게 생성되어 이 스레드로부터 Idle Process를 어떻게 얻어올 수 있는지 궁금했었는데 이제 해결되었다. KeGetCurrentThread 함수를 호출하기 전에 첫번째로 KPCR.Prcb.CurrentThread에KiInitialThread (Idle Thread)를 등록하고 KiInitializeThread 함수에서 Idle Thread가 속한 프로세스로 Idle Process를 등록하는 것이다.

   

프로세스 리스트에 Idle Process가 포함되지 않는 이유

IdleProcess는 PspCreateProcess에 의해 생성되지 않기 때문에 PsActiveProcessHead의 리스트에 등록되지 않는다. 물론 다음 코드처럼 System 프로세스부터는 PspCreateProcess에 의해 생성되고, 이 PspCreateProcess 함수 내부에서 PsActiveProcessHead를 참조하여 프로세스 리스트에 새로 생성되는 프로세스를 등록한다.

   

   

PspCreateProcess 함수의 내부에서 호출하는 PspInsertProcess 함수는 다음 코드와 같이 이중 연결 리스트의 마지막에 새로운 프로세스를 추가한다.

   

   

이 때까지의 Idle Process를 초기화하는 과정을 정리하면 아래와 같다.

   

   

결론

Idle Process는 커널에 _EPROCESS 구조체 자체, 즉 C언어의 변수 선언으로 "EPROCESS KiInitialProcess"처럼 정적으로 존재하기 때문에 PspCreateProcess와 같은 프로세스 할당 함수를 사용하지 않는다. 따라서 PsActiveProcessHead로부터 얻을 수 있는 프로세스 리스트에는 Idle Process가 없다. 더욱이 Idle Process는 다른 여타 프로세스와 달리 실행 파일이 없기 때문에 커널 코드 내에서 프로세스 이름이 정해진다. (System Process 또한 커널 코드 내에서 프로세스 이름이 정해진다. )

   

참조

[0] Windows Internals, 마크 러시노비치 등, 5장, p616

[1] https://msdn.microsoft.com/en-us/library/windows/hardware/ff559943(v=vs.85).aspx

   

커널 함수에서 사용하는 구조체 분석에 사용된 문서들

http://www.nirsoft.net/kernel_struct/vista/LOADER_PARAMETER_BLOCK.html

https://translate.google.co.kr/translate?hl=ko&sl=ru&tl=en&u=http%3A%2F%2Fhex.pp.ua%2Fntstatus%2Fpage024.php

http://www.osronline.com/showThread.cfm?link=3787

http://doxygen.reactos.org/d3/d0c/ntoskrnl_2include_2internal_2powerpc_2ke_8h_a88a282c034ffdbc59b89bab1b67676bb.html

http://www.osronline.com/showThread.cfm?link=72000

   

이외에 커널 분석에 도움될만한 내용

이번에 커널 코드를 살펴보면서 _LIST_ENTRY 구조체의 초기화 방식을 알게 되었다. 대부분이 자기 자신을 가리키도록 해놓았다. 이렇게 해놓으면 리스트 엔트리가 초기화 되었는지 되지 않았는지를 바로 파악할 수 있구나 생각하였다.

   

   

따라서 커널 구조체 분석시 LIST_ENTRY 구조체로 이루어진 필드 멤버가 자기 자신을 가리킨다면 별 다른 의미를 가진 것이 아니라 단순히 초기화된 상태라는 것이다.

'디버깅' 카테고리의 다른 글

MS 심볼 구성에 관하여  (0) 2014.12.17
Windows 10 _KPCR and _KPRCB  (0) 2014.11.14
8바이트 변수 초기화 문제  (0) 2014.11.14
Windbg에서 KDBG 찾기  (0) 2014.06.19
Window8 Remote Kernel Debugging on Vmware using WinDbg  (0) 2014.04.11

들어가며

그냥 MS 심볼 정보를 얻어야 될 일이 있어서 여차저차 알아보다 보니까 그 동안 모르고 넘겼던 MS 심볼 디렉터리에 생기는 폴더 이름들에 대해 알게되었다. 알고보니까 직접 배포할 때도 이런 형식을 맞춰야 하는거 같다.

   

MS 심볼 폴더 구성

MS 심볼 폴더 구성은 크게 PE 파일에 대한 폴더pdb 파일에 대한 폴더 두 가지로 나뉜다. 우선 PE 파일 폴더 구성은 아래와 같다.

   

"%s\%s\%s%s\%s" % (serverName, peName, timeStamp, imageSize, peName)

   

딱 보면 이게 무슨 말인지 아시는 분들도 있고 모르시는 분들도 있겠다. 일단 아래의 사례를 통해 이해해보도록 하겠다.

   

   

위 심볼 정보는 MS 심볼 패키지 모음에서 다운받은 Windows 8.1 심볼 패키지 중 하나이다. ntoskrnl.exe는 심볼 서버의 저장된 PE 파일이다. 경로를 그려보면

"~\WebSymbols\ntoskrnl.exe\52341CF4781000\ntoskrnl.exe" 라 할 수 있다.

여기서 serverName은 WebSymbols로 심볼 정보를 설치한 폴더 이름이고, peName은 그대로 ntoskrnl.exe이다. 그다음 timeStamp + imageSize가 섞인 것을 볼 수 있는데 이는 해당 헥스값을 문자열로 표현한 것이다. timeStamp + imageSize 폴더 하위에는 다시 peName으로 PE 파일이 있다. 이 timeStamp와 imageSize 값은 dumpbin같은 PE 도구를 확인하여 확인할 수 있다.

   

   

자동화를 한다면 하드 코딩하거나 아래 정도로 사용하면 될 거 같다.

   

   

(텍스트)

~\WebSymbols\ntoskrnl.exe\52341CF4781000>dumpbin.exe ntoskrnl.exe /headers | grep "date stamp\|size of image"

52341CF4 time date stamp Sat Sep 14 17:23:16 2013

781000 size of image

   

   

확인하면 폴더명이 [52341CF4 + 781000] 인 것을 확인할 수 있다.

   

다음으로 pdb 파일에 대한 폴더인데 이 pdb 파일은 PE 파일에 대한 디버깅 정보를 포함하는 파일이다. pdb 파일은 아래와 같은 구성을 따른다.

   

"%s\%s\%s%s\%s" % (serverPath, pdbName, guid, age, pdbName)

   

여기서 guid는 PE 파일의 Directory 중 Debug Directory에서 찾아볼 수 있으나 왠만한 PE 도구에서는 GUID까지 보여주지 않는다. VC에서 포한하는 도구인 dumpbin을 이용하여 찾아볼 수 있다.

   

   

(텍스트)

~\WebSymbols\ntoskrnl.exe\52341CF4781000>dumpbin.exe ntoskrnl.exe /headers | grep "Format:"

52341CF4 cv 25 0001AFF0 1A5F0 Format: RSDS, {FD3D00D2-8EDC-4527-BB92-2BCC0509D285}, 1, ntkrnlmp.pdb

   

RSDS 다음에 나오는 헥스 문자열과 그 뒤에 오는 숫자(여기선 1)이 바로 GUID와 age이다. 또한 PDB 파일의 이름이 ntkrnlmp.pdb라고 친절하게 나와있다. 위의 폴더 구성을 통해 파일의 위치를 찾아보면 아래와 같은 폴더를 찾을 수 있다. 여기서 age란 incremental build를 사용하여 컴파일하면 초기값 1부터 시작하여 증가한다고 한다.

   

~\WebSymbols\ntkrnlmp.pdb\FD3D00D28EDC4527BB922BCC0509D2851\ntkrnlmp.pdb

   

   

근데 왜 PE 파일과 pdb 파일명이 서로 다를까? 생각해봤지만. 음… 뭐 MS의 마음이니까 라고 결론을 내렸다. 뭐 위와 같은 심볼 폴더 구성을 알면 뭐가 좋냐 하면 PE 파일명과 PDB 파일명이 달라 디버깅하지 못하는 이런 사태를 피할 수 있다.

   

마치며

내가 하고 싶은건 모든 ntoskrnl.exe의 모든 버전에 대한 심볼 정보를 얻어오는 것인데.. :(

뭐 이런 것도 정리해 놓으면 도움이 되겠지 생각한다.

참고

<Symbols the Microsoft Way, https://randomascii.wordpress.com/2013/03/09/symbols-the-microsoft-way/>

   

2015.03.13 추가.

ntoskrnl.exe는 윈도우 업데이트 폴더(ex. WinSxS)에서 구할 수 있다. 이 폴더는 그 동안 업데이트된 파일들을 저장한다.

그러나 모든 버전의 ntoskrl.exe는 구할 수 없다. 아마도 언어마다 업데이트가 다른 것도 있어서? 인거 같다. GRR 도구 참고.

Connected to Windows 8 9841 x86 compatible target at (Fri Nov 14 08:06:42.382 2014 (UTC + 9:00)), ptr64 FALSE
Kernel Debugger connection established.

************* Symbol Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       srv*D:\WebSymbols*http://msdl.microsoft.com/download/symbols
Symbol search path is: srv*D:\WebSymbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
Windows 8 Kernel Version 9841 MP (1 procs) Free x86 compatible
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 9841.0.x86fre.fbl_release.140912-1613
Machine Name:
Kernel base = 0x81276000 PsLoadedModuleList = 0x8148d6d8
Debug session time: Fri Nov 14 08:06:36.550 2014 (UTC + 9:00)
System Uptime: 0 days 0:05:40.451
Break instruction exception - code 80000003 (first chance)

// 기존 8.1의 경우 9600임

 

kd> !pcr
KPCR for Processor 0 at 814a0000:
    Major 1 Minor 1
 NtTib.ExceptionList: 827e6f6c
     NtTib.StackBase: 00000000
    NtTib.StackLimit: 00001f80
  NtTib.SubSystemTib: 807d5000
       NtTib.Version: 0002e7cc
   NtTib.UserPointer: 00000001
       NtTib.SelfTib: 00000000

             SelfPcr: 814a0000
                Prcb: 814a0120
                Irql: 0000001f
                 IRR: 00000000
                 IDR: 00000000
       InterruptMode: 00000000
                 IDT: 807e1400
                 GDT: 807e1000
                 TSS: 807d5000

       CurrentThread: 814be200
          NextThread: 00000000
          IdleThread: 814be200

           DpcQueue: Unable to read nt!_KDPC_DATA.DpcListHead.Flink @ 814a2300

kd> dd nt!KiInitialPCR
814a0000  827e6f6c 00000000 00001f80 807d5000
814a0010  0002e7cc 00000001 00000000 814a0000
814a0020  814a0120 0000001f 00000000 00000000
814a0030  00000000 81473d40 807e1400 807e1000
814a0040  807d5000 00010001 00000001 00000d40
814a0050  00000000 00000000 00000000 00000000
814a0060  00000000 00000000 00000000 00000000
814a0070  00000000 00000000 00000000 00000000

kd> dt _KPCR 814a0000
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 Used_ExceptionList : 0x827e6f6c _EXCEPTION_REGISTRATION_RECORD
   +0x004 Used_StackBase   : (null)
   +0x008 MxCsr            : 0x1f80
   +0x00c TssCopy          : 0x807d5000 Void
   +0x010 ContextSwitches  : 0x2e7cc
   +0x014 SetMemberCopy    : 1
   +0x018 Used_Self        : (null)
   +0x01c SelfPcr          : 0x814a0000 _KPCR
   +0x020 Prcb             : 0x814a0120 _KPRCB
   +0x024 Irql             : 0x1f ''
   +0x028 IRR              : 0
   +0x02c IrrActive        : 0
   +0x030 IDR              : 0
   +0x034 KdVersionBlock   : 0x81473d40 Void
   +0x038 IDT              : 0x807e1400 _KIDTENTRY
   +0x03c GDT              : 0x807e1000 _KGDTENTRY
   +0x040 TSS              : 0x807d5000 _KTSS
   +0x044 MajorVersion     : 1
   +0x046 MinorVersion     : 1
   +0x048 SetMember        : 1
   +0x04c StallScaleFactor : 0xd40
   +0x050 SpareUnused      : 0 ''
   +0x051 Number           : 0 ''
   +0x052 Spare0           : 0 ''
   +0x053 SecondLevelCacheAssociativity : 0 ''
   +0x054 VdmAlert         : 0
   +0x058 KernelReserved   : [14] 0
   +0x090 SecondLevelCacheSize : 0
   +0x094 HalReserved      : [16] 0x1000000
   +0x0d4 InterruptMode    : 0
   +0x0d8 Spare1           : 0 ''
   +0x0dc KernelReserved2  : [17] 0
   +0x120 PrcbData         : _KPRCB

 

// KPCR 크기는 역시나 계속 동일함


kd> dt _KPRCB 814a0000+120
nt!_KPRCB
   +0x000 MinorVersion     : 1
   +0x002 MajorVersion     : 1
   +0x004 CurrentThread    : 0x814be200 _KTHREAD
   +0x008 NextThread       : (null)
   +0x00c IdleThread       : 0x814be200 _KTHREAD
   +0x010 LegacyNumber     : 0 ''
   +0x011 NestingLevel     : 0x1 ''
   +0x012 BuildType        : 0
   +0x014 CpuType          : 6 ''
   +0x015 CpuID            : 1 ''
   +0x016 CpuStep          : 0x3a09
   +0x016 CpuStepping      : 0x9 ''
   +0x017 CpuModel         : 0x3a ':'
   +0x018 ProcessorState   : _KPROCESSOR_STATE
   +0x338 ParentNode       : 0x8147d080 _KNODE
   +0x33c PriorityState    : 0x814a4948  ""
   +0x340 KernelReserved   : [14] 0
   +0x378 HalReserved      : [16] 0xa6a600
   +0x3b8 CFlushSize       : 0x40
   +0x3bc CoresPerPhysicalProcessor : 0x1 ''
   +0x3bd LogicalProcessorsPerCore : 0x1 ''
   +0x3be CpuVendor        : 0x1 ''
   +0x3bf PrcbPad0         : [1]  ""
   +0x3c0 MHz              : 0xd40
   +0x3c4 GroupIndex       : 0 ''
   +0x3c5 Group            : 0 ''
   +0x3c6 PrcbPad05        : [2]  ""
   +0x3c8 GroupSetMember   : 1
   +0x3cc Number           : 0
   +0x3d0 ClockOwner       : 0x1 ''
   +0x3d1 PendingTickFlags : 0x1 ''
   +0x3d1 PendingTick      : 0y1
   +0x3d1 PendingBackupTick : 0y0
   +0x3d2 PrcbPad10        : [70]  ""
   +0x418 LockQueue        : [17] _KSPIN_LOCK_QUEUE
   +0x4a0 InterruptCount   : 0xa968
   +0x4a4 KernelTime       : 0x5314
   +0x4a8 UserTime         : 0x208
   +0x4ac DpcTime          : 0x37
   +0x4b0 DpcTimeCount     : 0
   +0x4b4 InterruptTime    : 0x12
   +0x4b8 AdjustDpcThreshold : 2
   +0x4bc PageColor        : 0x320d
   +0x4c0 DebuggerSavedIRQL : 0x1c ''
   +0x4c1 NodeColor        : 0 ''
   +0x4c2 PrcbPad20        : [6]  ""
   +0x4c8 NodeShiftedColor : 0
   +0x4cc SecondaryColorMask : 0x3f
   +0x4d0 DpcTimeLimit     : 0
   +0x4d4 DeviceInterruptCount : 0x524c
   +0x4d8 PrcbPad21        : [2] 0
   +0x4e0 CcFastReadNoWait : 0
   +0x4e4 CcFastReadWait   : 0x1464
   +0x4e8 CcFastReadNotPossible : 0
   +0x4ec CcCopyReadNoWait : 0
   +0x4f0 CcCopyReadWait   : 0x1913
   +0x4f4 CcCopyReadNoWaitMiss : 0
   +0x4f8 MmSpinLockOrdering : 0n0
   +0x4fc IoReadOperationCount : 0n7105
   +0x500 IoWriteOperationCount : 0n5147
   +0x504 IoOtherOperationCount : 0n104510
   +0x508 IoReadTransferCount : _LARGE_INTEGER 0xbd1ff8a
   +0x510 IoWriteTransferCount : _LARGE_INTEGER 0x19bb1be
   +0x518 IoOtherTransferCount : _LARGE_INTEGER 0x55e32e
   +0x520 CcFastMdlReadNoWait : 0
   +0x524 CcFastMdlReadWait : 0
   +0x528 CcFastMdlReadNotPossible : 0
   +0x52c CcMapDataNoWait  : 0
   +0x530 CcMapDataWait    : 0xed39
   +0x534 CcPinMappedDataCount : 0xfa2
   +0x538 CcPinReadNoWait  : 0
   +0x53c CcPinReadWait    : 0x647
   +0x540 CcMdlReadNoWait  : 0
   +0x544 CcMdlReadWait    : 6
   +0x548 CcLazyWriteHotSpots : 0x2f
   +0x54c CcLazyWriteIos   : 0x211
   +0x550 CcLazyWritePages : 0x61c
   +0x554 CcDataFlushes    : 0x4b3
   +0x558 CcDataPages      : 0x16bd
   +0x55c CcLostDelayedWrites : 0
   +0x560 CcFastReadResourceMiss : 0
   +0x564 CcCopyReadWaitMiss : 0x944d
   +0x568 CcFastMdlReadResourceMiss : 0
   +0x56c CcMapDataNoWaitMiss : 0
   +0x570 CcMapDataWaitMiss : 0xe61
   +0x574 CcPinReadNoWaitMiss : 0
   +0x578 CcPinReadWaitMiss : 0x3a
   +0x57c CcMdlReadNoWaitMiss : 0
   +0x580 CcMdlReadWaitMiss : 0
   +0x584 CcReadAheadIos   : 0x851
   +0x588 KeAlignmentFixupCount : 0
   +0x58c KeExceptionDispatchCount : 0x2dc
   +0x590 KeSystemCalls    : 0x1e6ee1
   +0x594 AvailableTime    : 0x84
   +0x598 PrcbPad22        : [2] 0
   +0x5a0 PPLookasideList  : [16] _PP_LOOKASIDE_LIST
   +0x620 PPNxPagedLookasideList : [32] _GENERAL_LOOKASIDE_POOL
   +0xf20 PPNPagedLookasideList : [32] _GENERAL_LOOKASIDE_POOL
   +0x1820 PPPagedLookasideList : [32] _GENERAL_LOOKASIDE_POOL
   +0x2120 PacketBarrier    : 0
   +0x2124 ReverseStall     : 0n2
   +0x2128 IpiFrame         : (null)
   +0x212c PrcbPad3         : [52]  ""
   +0x2160 CurrentPacket    : [3] (null)
   +0x216c TargetSet        : 0
   +0x2170 WorkerRoutine    : (null)
   +0x2174 IpiFrozen        : 0
   +0x2178 PrcbPad4         : [40]  ""
   +0x21a0 RequestSummary   : 0
   +0x21a4 SignalDone       : (null)
   +0x21a8 PrcbPad50        : [40]  ""
   +0x21d0 InterruptLastCount : 0xa968
   +0x21d4 InterruptRate    : 4
   +0x21d8 DeviceInterrupts : 0x30
   +0x21dc IsrDpcStats      : 0x00000001 Void
   +0x21e0 DpcData          : [2] _KDPC_DATA
   +0x2210 DpcStack         : 0x827ec000 Void
   +0x2214 MaximumDpcQueueDepth : 0n4
   +0x2218 DpcRequestRate   : 4
   +0x221c MinimumDpcRate   : 3
   +0x2220 DpcLastCount     : 0x9dfd
   +0x2224 PrcbLock         : 0
   +0x2228 DpcGate          : _KGATE
   +0x2238 IdleState        : 0 ''
   +0x2239 QuantumEnd       : 0 ''
   +0x223a DpcRoutineActive : 0 ''
   +0x223b IdleSchedule     : 0 ''
   +0x223c DpcRequestSummary : 0n8
   +0x223c DpcRequestSlot   : [2] 0n8
   +0x223c NormalDpcState   : 0n8
   +0x223e ThreadDpcState   : 0n0
   +0x223c DpcNormalProcessingActive : 0y0
   +0x223c DpcNormalProcessingRequested : 0y0
   +0x223c DpcNormalThreadSignal : 0y0
   +0x223c DpcNormalTimerExpiration : 0y1
   +0x223c DpcNormalDpcPresent : 0y0
   +0x223c DpcNormalLocalInterrupt : 0y0
   +0x223c DpcNormalSpare   : 0y0000000000 (0)
   +0x223c DpcThreadActive  : 0y0
   +0x223c DpcThreadRequested : 0y0
   +0x223c DpcThreadSpare   : 0y00000000000000 (0)
   +0x2240 LastTimerHand    : 0x32ba
   +0x2244 LastTick         : 0x551c
   +0x2248 PeriodicCount    : 0
   +0x224c PeriodicBias     : 0
   +0x2250 ClockInterrupts  : 0x57bc
   +0x2254 ReadyScanTick    : 0x5565
   +0x2258 GroupSchedulingOverQuota : 0 ''
   +0x2259 ThreadDpcEnable  : 0x1 ''
   +0x225a PrcbPad41        : [2]  ""
   +0x2260 TimerTable       : _KTIMER_TABLE
   +0x3aa0 CallDpc          : _KDPC
   +0x3ac0 ClockKeepAlive   : 0n1
   +0x3ac4 PrcbPad6         : [4]  ""
   +0x3ac8 DpcWatchdogPeriod : 0n0
   +0x3acc DpcWatchdogCount : 0n1
   +0x3ad0 KeSpinLockOrdering : 0n0
   +0x3ad4 PrcbPad70        : [1] 0
   +0x3ad8 QueueIndex       : 1
   +0x3adc DeferredReadyListHead : _SINGLE_LIST_ENTRY
   +0x3ae0 ReadySummary     : 0
   +0x3ae4 AffinitizedSelectionMask : 0n262135
   +0x3ae8 WaitLock         : 0
   +0x3aec WaitListHead     : _LIST_ENTRY [ 0x9c6980dc - 0x97ac749c ]
   +0x3af4 ScbOffset        : 0x40
   +0x3af8 StartCycles      : 0x000002b9`cd5fac50
   +0x3b00 TaggedCyclesStart : 0
   +0x3b08 TaggedCycles     : [2] 0
   +0x3b18 GenerationTarget : 0x5538
   +0x3b20 CycleTime        : 0x00000009`eabff3e4
   +0x3b28 AffinitizedCycles : 0
   +0x3b30 HighCycleTime    : 9
   +0x3b34 PrcbPad71        : [11] 0
   +0x3b60 DispatcherReadyListHead : [32] _LIST_ENTRY [ 0x814a3c80 - 0x814a3c80 ]
   +0x3c60 ChainedInterruptList : (null)
   +0x3c64 LookasideIrpFloat : 0n2147483647
   +0x3c68 ScbQueue         : _RTL_RB_TREE
   +0x3c70 ScbList          : _LIST_ENTRY [ 0x89ac7100 - 0x89ac7100 ]
   +0x3c78 MmPageFaultCount : 0n612791
   +0x3c7c MmCopyOnWriteCount : 0n11038
   +0x3c80 MmTransitionCount : 0n184606
   +0x3c84 MmCacheTransitionCount : 0n0
   +0x3c88 MmDemandZeroCount : 0n368214
   +0x3c8c MmPageReadCount  : 0n102675
   +0x3c90 MmPageReadIoCount : 0n13991
   +0x3c94 MmCacheReadCount : 0n0
   +0x3c98 MmCacheIoCount   : 0n0
   +0x3c9c MmDirtyPagesWriteCount : 0n61586
   +0x3ca0 MmDirtyWriteIoCount : 0n421
   +0x3ca4 MmMappedPagesWriteCount : 0n0
   +0x3ca8 MmMappedWriteIoCount : 0n0
   +0x3cac CachedCommit     : 0x100
   +0x3cb0 CachedResidentAvailable : 0x79
   +0x3cb4 HyperPte         : 0x8001000f Void
   +0x3cb8 PrcbPad8         : [4]  ""
   +0x3cbc VendorString     : [13]  "GenuineIntel"
   +0x3cc9 InitialApicId    : 0 ''
   +0x3cca LogicalProcessorsPerPhysicalProcessor : 0x1 ''
   +0x3ccb PrcbPad9         : [1]  ""
   +0x3cd0 FeatureBits      : 0xa3cf3fff
   +0x3cd8 UpdateSignature  : _LARGE_INTEGER 0x00000017`00000000
   +0x3ce0 IsrTime          : 0
   +0x3ce8 PrcbPad90        : [2] 0
   +0x3cf0 PowerState       : _PROCESSOR_POWER_STATE
   +0x3e70 PrcbPad91        : [18] 0
   +0x3eb8 DpcWatchdogDpc   : _KDPC
   +0x3ed8 DpcWatchdogTimer : _KTIMER
   +0x3f00 HypercallPageList : _SLIST_HEADER
   +0x3f08 HypercallPageVirtual : 0xffd06000 Void
   +0x3f0c VirtualApicAssist : (null)
   +0x3f10 StatisticsPage   : (null)
   +0x3f14 Cache            : [5] _CACHE_DESCRIPTOR
   +0x3f50 CacheCount       : 4
   +0x3f54 PackageProcessorSet : _KAFFINITY_EX
   +0x3f60 SharedReadyQueueMask : 0
   +0x3f64 SharedReadyQueue : 0x814a4840 _KSHARED_READY_QUEUE
   +0x3f68 CoreProcessorSet : 1
   +0x3f6c ScanSiblingMask  : 0
   +0x3f70 LLCMask          : 1
   +0x3f74 CacheProcessorMask : [5] 1
   +0x3f88 ScanSiblingIndex : 0
   +0x3f8c WheaInfo         : 0x8035a170 Void
   +0x3f90 EtwSupport       : 0x803258c0 Void
   +0x3f98 InterruptObjectPool : _SLIST_HEADER
   +0x3fa0 PrcbPad92        : [3] 0
   +0x3fac PteBitCache      : 0x3fffff
   +0x3fb0 PteBitOffset     : 0x19b00
   +0x3fb4 PrcbPad93        : 0
   +0x3fb8 ProcessorProfileControlArea : (null)
   +0x3fbc ProfileEventIndexAddress : 0x814a40dc Void
   +0x3fc0 TimerExpirationDpc : _KDPC
   +0x3fe0 SynchCounters    : _SYNCH_COUNTERS
   +0x4098 FsCounters       : _FILESYSTEM_DISK_COUNTERS
   +0x40a8 Context          : 0x81a44340 _CONTEXT
   +0x40ac ContextFlagsInit : 0x1006f
   +0x40b0 ExtendedState    : 0x81a44000 _XSAVE_AREA
   +0x40b4 EntropyTimingState : _KENTROPY_TIMING_STATE
   +0x41dc IsrStack         : 0x827f0000 Void
   +0x41e0 VectorToInterruptObject : [208] (null)
   +0x4520 AbSelfIoBoostsList : _SINGLE_LIST_ENTRY
   +0x4524 AbPropagateBoostsList : _SINGLE_LIST_ENTRY
   +0x4528 AbDpc            : _KDPC
   +0x4548 IoIrpStackProfilerCurrent : _IOP_IRP_STACK_PROFILER
   +0x459c IoIrpStackProfilerPrevious : _IOP_IRP_STACK_PROFILER
   +0x45f0 TimerExpirationTrace : [16] _KTIMER_EXPIRATION_TRACE
   +0x46f0 TimerExpirationTraceCount : 5
   +0x46f4 ExSaPageArray    : 0x80250718 Void
   +0x46f8 PrcbPad100       : [10] 0
   +0x4720 LocalSharedReadyQueue : _KSHARED_READY_QUEUE

// 멤버 추가 됨.. 천천히 수정

 

 

이번 포스트의 시작과 함께…

이런 경우는 도대체 언제 발생하는가 생각이 든다…

왜 8바이트를 컴파일러에서는 이런 식으로 처리하게 되었는가? 부터 시작해서

VC 컴파일러는 믿을만 한가? 라는 생각도 들게하는 문제이다.

일단 문제는 잘 사용되던 코드에서 갑자기 에러가 나면서 시작됐다.

에러가 난 코드는 다음 그림과 같다.

VC를 조금이라도 다뤄본 사람은 0xCC로 채워진 메모리는 초기화되지 않은 영역을 의미한다는 것을 알것이다. 근데 나는 분명히 0으로 초기화를 했으며… 8바이트 중 4바이트만 일부러 0xcc를 채우지도 않았다.

아래는 위 상태의 this 메모리 영역이다. (m_nDTB를 채우기전)

 

첫 4바이트는 vftable을 나타낸다. 문제는 그 뒤 메모리를 어떻게 사용하느냐인데… 아래를 디스어셈블 내용을 보면 알 수 있다.

 

디스어셈블을 보면 전달 인자인 _nDTB를 스택에 하위 4바이트를 넣고 edx에 상위 4바이트를 전달하여 처리한다. 처리할 때 메모리 영역을 보면 this + 0x08 부터 4바이트, 4바이트 단위로 8바이트 변수인 m_nDTB 변수에 채워넣는 것을 볼 수 있다. edx는 당연히 0이다.

이쯤 되면 코드에는 아무 문제가 없어보인다. 그런데 다시 맨 위 m_nDTB 값을 보면 이상하다는 것을 느낄 수 있을 것이다. 위 코드로 볼 때 m_nDTB가 메모리 상에서는 0으로 채워져있다는 것을… 하지만 실제 m_nDTB 값은 아래와 같이 값을 채운 후에도 이상하다는 것을…

 

위 그림은 Address 부분에  &m_nDTB를 입력했을 때 나온 결과이다. 즉, 실제 8 바이트 m_nDTB 변수 사용은 this + 0x04 인 영역부터 8바이트인 것이다. 왜 이런 상황이 발생했을까?

  1. 단순히 디버깅 정보 표시 잘못이다. m_nDTB는 0x0000000000185000으로 사용된다.
  2. 컴파일 시 무언가 잘못되었다.

1번인지 알고 release 모드로 실행했지만 m_nDTB가 쓰레기 값으로 사용되는 것을 확인하였다. 그럼 2번인데… 컴파일러가 왜 이걸 햇갈렸을까 아무리 생각해봤자 답이 안나오니…

컴파일러야 햇갈리지마 T^T 라는 생각으로 메모리 정렬 단위를 명시해주었다.

#pragma pack(4)

적어주니 아래와 같이 값이 컴파일러는 잘 판단해주었다. ㅡㅡ...

 

32비트 컴파일 시 기본 컴파일 단위가 4바이트라고 알고 있는데… 무엇이 잘못되었을까….

결론 : VC 2010 버전에서는 메모리 정렬 단위 선언으로 컴파일러에 확실히 명시해주자

   

'디버깅' 카테고리의 다른 글

MS 심볼 구성에 관하여  (0) 2014.12.17
Windows 10 _KPCR and _KPRCB  (0) 2014.11.14
Windbg에서 KDBG 찾기  (0) 2014.06.19
Window8 Remote Kernel Debugging on Vmware using WinDbg  (0) 2014.04.11
_DRIVER_OBJECT.DriverSection 정보  (0) 2013.10.05

  Windbg의 타겟 시스템은 wdbgexts.h에 정의되어 있는 _KDDEBUGGER_DATA64 구조체(이하 KDBG)를 이용하여 그 디버깅 정보를 유지한다. 또한 Windbg는 심볼 정보가 담긴 pdb 파일을 이용하여 구조체들의 필드 구성과 해당 값을 볼 수 있다. 이런 구조체 조사를 아래와 같이 "dt" 명령어를 통해 지원한다.


kd> dt _EPROCESS

nt!_EPROCESS

+0x000 Pcb : _KPROCESS

+0x2c8 ProcessLock : _EX_PUSH_LOCK

+0x2d0 CreateTime : _LARGE_INTEGER

+0x2d8 RundownProtect : _EX_RUNDOWN_REF

+0x2e0 UniqueProcessId : Ptr64 Void

+0x2e8 ActiveProcessLinks : _LIST_ENTRY

+0x2f8 Flags2 : Uint4B

+0x2f8 JobNotReallyActive : Pos 0, 1 Bit

(생략)


  하지만 타겟 시스템(Debuggee) 내에서도 유용하게 사용되는 KDBG 구조체 정보는 dt 명령어를 통해 한눈에 수가 없는데, 이유는 KDBG 구조체에 대한 심볼 정보가 없기 때문이다. 사실 KDBG 구조체를 누가 자주 찾아보겠는가 하지만… 가끔씩 KDBG 구조체를 봐야할 때가 있는데 때마다 아래와 같은 방법으로 KDBG 구조체를 찾았다.


Version 정보

Microsoft (R) Windows Debugger Version 6.3.9600.16384 AMD64

Copyright (c) Microsoft Corporation. All rights reserved.

   

Opened \\.\pipe\com_8_64

Waiting to reconnect...

Connected to Windows 8 9200 x64 target at (Tue Jun 17 19:40:42.866 2014 (UTC + 9:00)), ptr64 TRUE

Kernel Debugger connection established.

   

Windbg에서 KDBG 찾기

dq KdDebuggerDataBlock 

하면 된다. ^_^ 아 괜히 부끄러워지네... 

아래는 뭐.. 남겨둔다. 소스 코드도 수정해서 다시 올려야겠다.

  1. KdVersionBlock의 주소를 얻는다.

kd> dq KdVersionBlock

fffff803`5f2fddf0 00070206`23f0000f 00000031`030c8664

fffff803`5f2fde00 fffff803`5f08a000 fffff803`5f354a60

fffff803`5f2fde10 fffff803`5f30a8d0 ffffffff`ffffffff

fffff803`5f2fde20 00000000`00140014 00000000`00000000

fffff803`5f2fde30 00000000`00000000 00000000`00000000

fffff803`5f2fde40 00000000`00000000 00000000`00000000

fffff803`5f2fde50 00000000`00000000 00000000`00000000

fffff803`5f2fde60 00000000`00000000 00000000`00000000

   

  이 KdVersionBlock은 Windbg의 커널의 전역 변수로 디버깅 시스템에 대한 버전 정보를 지닌 _DBGKD_GET_VERSION64 구조체의 주소를 가지고 있다. 또한 이 구조체의 DebuggerDataList 필드는 KDBG 리스트의 주소를 가지고 있다. 리스트라고 했지만 사실 KDBG는 하나만 존재한다. 리스트는 확장성을 위해서 그런 형태로 남겨둔 것 같다.

   

kd> dt nt!_DBGKD_GET_VERSION64 fffff803`5f2fddf0

+0x000 MajorVersion : 0xf

+0x002 MinorVersion : 0x23f0

+0x004 ProtocolVersion : 0x6 ''

+0x005 KdSecondaryVersion : 0x2 ''

+0x006 Flags : 7

+0x008 MachineType : 0x8664

+0x00a MaxPacketType : 0xc ''

+0x00b MaxStateChange : 0x3 ''

+0x00c MaxManipulate : 0x31 '1'

+0x00d Simulation : 0 ''

+0x00e Unused : [1] 0

+0x010 KernBase : 0xfffff803`5f08a000

+0x018 PsLoadedModuleList : 0xfffff803`5f354a60

+0x020 DebuggerDataList : 0xfffff803`5f30a8d0 // -> _PKDDEBUGGER_DATA64

   

  1. DebuggerDataList를 통해 KDBG의 주소를 얻는다.

       

kd> dq 0xfffff803`5f30a8d0

fffff803`5f30a8d0 fffff803`5f2fda90 fffff803`5f2fda90

fffff803`5f30a8e0 00001666`754785db 00001666`754785db

fffff803`5f30a8f0 00000000`00000000 00000000`00da7a64

fffff803`5f30a900 526d6574`7379535c 74737973`5c746f6f

fffff803`5f30a910 6f746e5c`32336d65 78652e6c`6e726b73

fffff803`5f30a920 00000000`00000065 00000000`00000000

fffff803`5f30a930 00000000`00000000 00000000`00000000

fffff803`5f30a940 00000000`00000000 00000000`00000000


 KDBG의 주소는 fffff803`5f2fda90로 LIST_ENTRY64의 형태로 생각하면 FLINK 부분이라고 보면 된다. 물론 바로 옆 8바이트를 보면 BLINK 부분도 같은 값으로 채워져 있는 것을 확인할 수 있다.

   

kd> dc fffff803`5f2fda90

fffff803`5f2fda90 5f30a8d0 fffff803 5f30a8d0 fffff803 ..0_......0_....

fffff803`5f2fdaa0 4742444b 00000360 5f08a000 fffff803 KDBG`......_....

fffff803`5f2fdab0 5f0ff930 fffff803 00000000 00000000 0.._............

fffff803`5f2fdac0 00000000 00010000 5f0ff510 fffff803 ..........._....

fffff803`5f2fdad0 00000000 00000000 5f354a60 fffff803 ........`J5_....

fffff803`5f2fdae0 5f320c10 fffff803 5f3e0188 fffff803 ..2_......>_....

fffff803`5f2fdaf0 5f319ac0 fffff803 5f3e21c0 fffff803 ..1_.....!>_....

fffff803`5f2fdb00 5f3e00a8 fffff803 5f3e00bc fffff803 ..>_......>_....

   

 Tag(_KDDEBUGGER_DATA64.Header.Tag)값을 확인하여 KDBG 구조체를 확인할 수 있다. 이렇게 KDBG를 찾으면 또 문제가 생기는데 구조체 심볼 정보가 없기 때문에 원하는 필드의 값을 일일이 오프셋을 계산하여 알아내야 한다. 너무 귀찮다… 그래서 공부도 할 겸 Windbg Extesion 형태로 kdbgext.dll 플러그인을 만들었다.

   

KDBG EXTENSION Plugin

  최대한 dt와 비슷하게 나오게 할려고 했지만 아직 부족한건 사실이다. 백문이 불여일견이라고 아래를 보면 어떻게 동작하는지 대충 알 수 있다.

   

kd> .load kdbgext.dll

kd> !kdbg

KDBG Windbg Extension v0.1 by ozdang

> PlatformId : 2 / Win32 : 6.2 / KD : 15.9200

> KdVersionBlock        : 0xfffff80355af9df0

> DebugDataList        : 0xfffff80355b068d0

> KDBG's Address        : 0xfffff80355af9a90

dt _KDDEBUGGER_DATA64 0xfffff80355af9a90

+00000 Header.List.Flink        : 0xfffff80355b068d0

+0x008 Header.List.Blink        : 0xfffff80355b068d0

+0x010 Header.OwnerTag        : 0x4742444b 'KDBG'

+0x014 Header.Size        : 0x360

+0x018 KernBase        : 0xfffff80355886000

+0x020 BreakpointWithStatus        : 0xfffff803558fb930

+0x028 SavedContext        : 0

+0x030 ThCallbackStack        : 0

+0x032 NextCallback        : 0

+0x034 FramePointer        : 0

+0x036 Header.Size        : 0x1

+0x038 KiCallUserMode        : 0xfffff803558fb510

+0x040 KeUserCallbackDispatcher        : 0

+0x048 PsLoadedModuleList        : 0xfffff80355b50a60

+0x050 PsActiveProcessHead        : 0xfffff80355b1cc10

(생략)

   

  Windbg x86 버전에서만 동작한다. 세팅 방법은 Windbg Extension 폴더에 넣으면 된다. WDK 8.1 버전의 경우 "C:\Program Files (x86)\Windows Kits\8.1\Debuggers\x86\winext" 이다. 이후 위와 같이 .load kdbgext.dll로 플러그인을 불러오고 !kdbg 명령어를 통해 KDBG 구조체의 정보를 볼 수 있다.

  본 플러그인의 소스는 https://code.google.com/p/kdbg-extension/ 에서 볼 수 있으며 다운로드도 가능하다.

   

Download : 

kdbgext.dll


*여담이지만 처음만들 때는 pdb 파일을 만들어서 심볼 정보만 얻을려고 했는데 뭔가 잘 되지 않았다… 이 방법으로 될 것 같기도 하고 아닌 것 같기도 하고… 해서 일단 확실한 플러그인 형태로 제작하였다.

'디버깅' 카테고리의 다른 글

MS 심볼 구성에 관하여  (0) 2014.12.17
Windows 10 _KPCR and _KPRCB  (0) 2014.11.14
8바이트 변수 초기화 문제  (0) 2014.11.14
Window8 Remote Kernel Debugging on Vmware using WinDbg  (0) 2014.04.11
_DRIVER_OBJECT.DriverSection 정보  (0) 2013.10.05

윈도우 8이 새로이 출시되면서 윈도우 8의 커널을 분석해야 되는 분들이 있을거라 생각한다. 따라서 이번 포스트에서는 Vmware로 윈도우 8을 동작 중인 가상 머신에 Windbg를 사용하여 리모트 커널 디버깅을 설정하는 방법을 설명한다.

   

소개

윈도우 8이 공개되고 이에 맞는 WDK 8이 공개되었으며 이 WDK 8에 윈도우 8을 디버깅하기 위한 새 버전의 WinDbg 또한 포함되어 있다. 구버전의 WinDbg를 사용할 경우 디버깅 연결은 되지만 윈도우 8 디버깅 방법을 모르기 때문에 명령어 실행 단계에서 에러가 발생한다. 따라서 새 버전(Windbg:6.3.9600.*)의 WinDbg를 설치해야 하며, 나머지 부분은 윈도우 7 때와과 동일하다.

   

WinDbg 설치

WinDbg의 새 버전은 MSDN 공식 사이트에서 다운로드 할 수 있다. 이 사이트는 WinDbg를 새로 설치하기 위해 두 가지 방법을 제공하는데 첫번째로 WDK 8.1을 설치하여 그 중에 포함되어 있는 Windbg를 사용하는 방법과 Standalone형으로 나온 WinDbg를 사용하는 방법이 있다. 전자의 경우 아래 그림에 나와있듯이 Visual Studio 2013이 미리 설치되어 있어야 한다.

   

   

후자인 Standalone 형의 경우 설치만 하면 된다.

   

   

WinDbg를 설치 후 실행하여 반드시 버전을 확인한다.

  • Windbg:6.3.9600.*

   

이전에 설치했던 WinDbg의 경우 버전 호환성을 위해 그대로 남아있는 것 같으므로

반드시 설치 폴더에 들어가서 실행해 보아야 한다.

WDK 8.1 설치 경로는 다음과 같다.

   

  • C:\Program Files (x86)\Windows Kits\8.1\bin\x86
  • C:\Program Files (x86)\Windows Kits\8.1\bin\x64

   

Vmware 세팅

가상 머신을 리모트 디버깅하기 위해 윈도우 7에서 했던 방식과 달라진 점은 없지만 한번 더 짚고 넘어가기로 한다.

윈도우 8을 설치한 가상 머신에서 아래 작업을 순서대로 수행한다.

   

  • 실행(Ctrl +D) - msconfig 실행
  • 부팅 탭에서 고급 옵션 선택
  • 디버그 모드 체크

   

   

위 작업은 윈도우 8.1에서 디버깅을 가능하게 한 것이고 외부에서 디버깅을 위해 접근하려면 COM1 포트를 사용해야 한다는 설정해주는 것이다.

(* 이 작업은 bcdedit을 이용하여 디버깅용 부팅 옵션을 따로 만들 수도 있다.)

   

본래 COM1, COM2 포트 등은 컴퓨터의 본체에 다른 컴퓨터와 연결하기 위하여 존재하는 것이지만 가상 머신의 경우 실제 포트가 존재하지 않으므로 이름있는 파이프를 활용하여 가상머신을 실행 중인 호스트 PC와 연결해준다.

   

  • 윈도우 8 가상 머신 종료
  • Vmware 가상 머신 옵션
  • Hardware 탭에서 Add 버튼 클릭
  • Serial Port를 추가

 

   

뒤에서도 한번 더 말하겠지만 호스트 PC에 프린터가 잡혀있다면 Vmware가 이를 인식하여 가상 머신과 연동하기 위해 COM1 포트를 프린터 포트로 사용한다. 따라서 부팅 옵션에서 COM1 포트를 사용할 것이지 COM2 포트를 사용할 것인지 잘 정해야 한다.

   

  • Next 후 Output to named pipe 선택

   

   

  • 파이프의 이름을 정한다. 여기서는 com_8_64로 하였다.

   

   

Finish를 하면 새로운 포트가 추가되는데 앞서 말했듯이 Printer가 잡혀있다면 Serial Port 2로 생성이 된다. 물론 Printer가 잡혀있지 않다면 Serial Port 1으로 생성이되며, 이후 Windbg를 실행하여 리모트 커널 디버깅을 진행하면 된다. Serial Port 2일 경우 이를 해결하기 위한 방법으로 두 가지가 있는데 첫번째는 Printer 하드웨어를 제거하고 다시 시리얼 포트를 추가하는 방법이며 두번째는 부팅 옵션의 디버깅용 포트를 변경하는 방법이 있다.

   

   

여기선 가상 머신에서 프린터를 사용할 때를 대비하여 부팅 옵션의 디버깅용 포트를 변경하기로 한다.

   

  • 다시 윈도우 8 가상머신을 키고 부팅 옵션에서 COM2 로 설정해준다.

   

   

* 프린터가 Serial Port 1을 사용하고 있다는 내용은 이 블로그를 통해 알게 되었다.

   

이후 가상 머신을 다시 재부팅한다.

   

Windbg 커널 디버거 연결

앞에서 Vmware의 가상 머신이 리모트 디버깅을 받아들 일 수 있도록 하는 설정을 마쳤다. 이제 남은 일은 Windbg를 실행하여 리모트 커널 디버거를 가상 머신과 연결하는 것이다.

   

  • File - Kernel Debug - Com 탭 에서 아래와 같이 설정해준다.

   

   

포트는 아까 이름있는 파이프의 이름을 설정한 걸 사용하면 된다.(\\.\pipe\com_8_64)

   

*다른 블로그에서 볼 수 있는 단축 아이콘의 실행 인자 정보를 변경하여 실행하는 것이 위와 같은 과정이다.

   

OK를 누르면 아래와 같이 창이 뜨는데

   

   

계속 멈춰 있으면 한번 Ctrl + Break 를 눌러보면 연결이 된다.

(이 문제는 윈도우7 디버깅할 때도 있었는데 무슨 문제인가 싶다.)

   

이후 심볼 설정을 해주면 되는데

Windbg 메뉴에서 File - Symbol File Path에 아래와 같이 입력한다.

SRV*d:\websymbol*http://msdl.microsoft.com/download/symbols;

   

   

"d:\websymbol"은 심볼을 넣을 폴더 경로로 디버깅하다가 필요한 심볼 정보가 있으면 마이크로소프트사에서 제공하는 디버깅 데이터 서버를 통해 다운받을 폴더이다. 반드시 사용 전에 폴더를 미리 만들어주어야 한다. Reload 체크 박스를 설정하면 OK를 누르자마자 심볼 정보를 다시 리로드하는 것을 볼 수 있다.

간혹 가다가 한번씩 심볼 정보가 일치하지 않을 때가 있는데 이 때는 심볼 정보를 다시 리로드하는 명령어 .reload를 실행주면 된다.

   

   

이상 Window8 Remote Kernel Debugging on Vmware using WinDbg 글을 마친다.

'디버깅' 카테고리의 다른 글

MS 심볼 구성에 관하여  (0) 2014.12.17
Windows 10 _KPCR and _KPRCB  (0) 2014.11.14
8바이트 변수 초기화 문제  (0) 2014.11.14
Windbg에서 KDBG 찾기  (0) 2014.06.19
_DRIVER_OBJECT.DriverSection 정보  (0) 2013.10.05

윈도우 커널에서 드라이버에 대한 정보를 관리하는 오브젝트가 있는데 이를 드라이버 오브젝트(_DRIVER_OBJECT)라고 한다. 이 오브젝트란 것이 프로그래밍상의 오브젝트가 아니라 말 그대로 '드라이버 객체'를 의미한다. 프로그래밍상으로 보면 구조체라고 보면 된다. 아래는 _DRIVER_OBJECT 구조체 정의이다.

   

kd> dt _DRIVER_OBJECT

nt!_DRIVER_OBJECT

+0x000 Type : Int2B

+0x002 Size : Int2B

+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT

+0x008 Flags : Uint4B

+0x00c DriverStart : Ptr32 Void

+0x010 DriverSize : Uint4B

+0x014 DriverSection : Ptr32 Void

+0x018 DriverExtension : Ptr32 _DRIVER_EXTENSION

+0x01c DriverName : _UNICODE_STRING

+0x024 HardwareDatabase : Ptr32 _UNICODE_STRING

+0x028 FastIoDispatch : Ptr32 _FAST_IO_DISPATCH

+0x02c DriverInit : Ptr32 long

+0x030 DriverStartIo : Ptr32 void

+0x034 DriverUnload : Ptr32 void

+0x038 MajorFunction : [28] Ptr32 long

   

(Windows 7 SP0 x86 from Windbg)

   

이 중에 DriverSection이 Void형 포인터란 것을 알 수 있다. 즉 이 부분을 커널 디버깅하는 유저들에게 알려주지 않겠다는 뜻인데, 리버서들은 아래와 같이 이 영역이 사용된다는 것을 알아내었다. ㅡㅡa

   

typedef struct _KLDR_DATA_TABLE_ENTRY {

    /*+0x000*/ LIST_ENTRY InLoadOrderLinks;

    /*+0x008*/ PVOID ExceptionTable;

    /*+0x00c*/ ULONG ExceptionTableSize;

    // ULONG padding on IA64

    /*+0x010*/ PVOID GpValue;

    /*+0x014*/ PNON_PAGED_DEBUG_INFO NonPagedDebugInfo;

    /*+0x018*/ PVOID DllBase;

    /*+0x01c*/ PVOID EntryPoint;

    /*+0x020*/ ULONG SizeOfImage;

    /*+0x024*/ UNICODE_STRING FullDllName;

    /*+0x02c*/ UNICODE_STRING BaseDllName;

    /*+0x034*/ ULONG Flags;

    /*+0x038*/ USHORT LoadCount;

    /*+0x03c*/ USHORT __Unused5;

    /*+0x03e*/ PVOID SectionPointer;

    /*+0x040*/ ULONG CheckSum;

    // ULONG padding on IA64

    /*+0x044*/ PVOID LoadedImports;

    /*+0x048*/ PVOID PatchInformation;

} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;

   

출처: <http://www.kernelmode.info/forum/viewtopic.php?f=10&t=1206>

   

뭐 사실 위 구조체에서 명확히 얻을 수 있는 정보는 드라이버 오브젝트(_DRVIER_OBJECT)와 커널 모듈 엔트리(_LDR_DATA_TABLE_ENTRY)의 base(MZ~) 주소를 비교하여 서로 얻을 수 있지만 위처럼 문서화되지 않은 구조체를 사용하여 편하게 구할 수도 있다.

   

한번 실제로 이런지 살펴봐야겠다.

   

kd> !drvobj \Driver\rspndr

Driver object (867f2628) is for:

\Driver\rspndr

Driver Extension List: (id , addr)

   

Device Object list:

867f3be8

kd> dt _DRIVER_OBJECT 867f2628

nt!_DRIVER_OBJECT

+0x000 Type : 0n4

+0x002 Size : 0n168

+0x004 DeviceObject : 0x867f3be8 _DEVICE_OBJECT

+0x008 Flags : 0x12

+0x00c DriverStart : 0x8923b000 Void

+0x010 DriverSize : 0x13000

+0x014 DriverSection : 0x867f2848 Void // -> _KLDR_DATA_TABLE_ENTRY

+0x018 DriverExtension : 0x867f26d0 _DRIVER_EXTENSION

+0x01c DriverName : _UNICODE_STRING "\Driver\rspndr"

+0x024 HardwareDatabase : 0x82fa9250 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"

+0x028 FastIoDispatch : (null)

+0x02c DriverInit : 0x8924a450 long +ffffffff8924a450

+0x030 DriverStartIo : (null)

+0x034 DriverUnload : 0x89248422 void +ffffffff89248422

+0x038 MajorFunction : [28] 0x82cf2da3 long nt!IopInvalidDeviceRequest+0

   

kd> dc 0x867f2848

867f2848 86416198 867ecd28 ffffffff ffffffff .aA.(.~.........

867f2858 00000000 00000000 8923b000 8924a450 ..........#.P.$.

867f2868 00013000 004e004e 8a46fc00 00140014 .0..N.N...F.....

867f2878 867f28a4 49104000 00000001 00000000 .(...@.I........

867f2888 00017a94 00000000 00000000 8a47aa38 .z..........8.G.

867f2898 00000000 00013000 4a5bc8f0 00730072 .....0....[Jr.s.

867f28a8 006e0070 00720064 0073002e 00730079 p.n.d.r...s.y.s.

867f28b8 ffff0000 7fffffff 04030010 69536d4d ............MmSi

   

아 근데 이거 비교해보니까.. 그냥 _LDR_DATA_TABLE_ENTRY를 가리키는거 같다. --a

   

kd> dt _LDR_DATA_TABLE_ENTRY 0x867f2848

nt!_LDR_DATA_TABLE_ENTRY

+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x86416198 - 0x867ecd28 ]

+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xffffffff - 0xffffffff ]

+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]

+0x018 DllBase : 0x8923b000 Void

+0x01c EntryPoint : 0x8924a450 Void

+0x020 SizeOfImage : 0x13000

+0x024 FullDllName : _UNICODE_STRING "\SystemRoot\system32\DRIVERS\rspndr.sys"

+0x02c BaseDllName : _UNICODE_STRING "rspndr.sys"

+0x034 Flags : 0x49104000

+0x038 LoadCount : 1

+0x03a TlsIndex : 0

+0x03c HashLinks : _LIST_ENTRY [ 0x0 - 0x17a94 ]

+0x03c SectionPointer : (null)

+0x040 CheckSum : 0x17a94

+0x044 TimeDateStamp : 0

+0x044 LoadedImports : (null)

+0x048 EntryPointActivationContext : (null)

+0x04c PatchInformation : 0x8a47aa38 Void

+0x050 ForwarderLinks : _LIST_ENTRY [ 0x0 - 0x13000 ] // 여기 아래부터 필드 사용 안함

+0x058 ServiceTagLinks : _LIST_ENTRY [ 0x4a5bc8f0 - 0x730072 ]

+0x060 StaticLinks : _LIST_ENTRY [ 0x6e0070 - 0x720064 ]

+0x068 ContextInformation : 0x0073002e Void

+0x06c OriginalBase : 0x730079

+0x070 LoadTime : _LARGE_INTEGER 0x7fffffff`ffff0000

   

LoadedImports 구조체와 완전 유사하다. PatchInformation 쯤 부터 오프셋 차이가 나는데 위 리버싱이 잘못된 걸까 싶기도 하다.

   

ForwarderLinks 부터는 필드 값이 정상적이지 않아서 사용하지 않는 걸로 판단하였었는데, 뭐 다른걸로 사용될 수도?

   

실제로 모듈의 풀 헤더(_POOL_HEADER)를 살펴보면

아래와 같이 _POOL_HEADER의 크기(0x08) + _LDR_DATA_TABLE_ENTRY(0x78)의 크기로 구성된 것을 알 수 있다.

   

kd> dt _POOL_HEADER 0x867f2848-8

nt!_POOL_HEADER

+0x000 PreviousSize : 0y000000110 (0x6)

+0x000 PoolIndex : 0y0000000 (0)

+0x002 BlockSize : 0y000010000 (0x10) // 0x10 * 0x08(x86 pool alignment) = 0x80

+0x002 PoolType : 0y0000010 (0x2)

+0x000 Ulong1 : 0x4100006

+0x004 PoolTag : 0x644c6d4d

+0x004 AllocatorBackTraceIndex : 0x6d4d

+0x006 PoolTagHash : 0x644c

   

   

바이너리도 살펴보면

   

kd> dc 0x867f2848 L24

867f2848 86416198 867ecd28 ffffffff ffffffff .aA.(.~.........

867f2858 00000000 00000000 8923b000 8924a450 ..........#.P.$.

867f2868 00013000 004e004e 8a46fc00 00140014 .0..N.N...F.....

867f2878 867f28a4 49104000 00000001 00000000 .(...@.I........

867f2888 00017a94 00000000 00000000 8a47aa38 .z..........8.G.

867f2898 00000000 00013000 4a5bc8f0 00730072 .....0....[Jr.s.

867f28a8 006e0070 00720064 0073002e 00730079 p.n.d.r...s.y.s.

867f28b8 ffff0000 7fffffff 04030010 69536d4d ............MmSi // 아래부터 blocksize를 벗어남

867f28c8 00000000 829028f0 00000001 867f28d4 .....(.......(..

   

실제로 MmSi 시그너처가 있는 것으로 보아 다른 영역으로 사용되는 것을 추측할 수 있다.


결론을 내리자면 _DRIVER_OBJECT.DriverSection은 _LDR_DATA_TABLE_ENTRY(또는 _KLDR_DATA_TABLE_ENTRY)를 가리킨다. 정도?

항상 커널 분석은 미궁이다. --a

'디버깅' 카테고리의 다른 글

MS 심볼 구성에 관하여  (0) 2014.12.17
Windows 10 _KPCR and _KPRCB  (0) 2014.11.14
8바이트 변수 초기화 문제  (0) 2014.11.14
Windbg에서 KDBG 찾기  (0) 2014.06.19
Window8 Remote Kernel Debugging on Vmware using WinDbg  (0) 2014.04.11