Windows OS2011. 8. 30. 23:00
반응형
개발중 아래와 같은 레지스트리 키를 열고 닫았더니 BSOD가 발생하는 황당한 일이 있었다.

\REGISTRY\MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009


원인을 추적하다가 재미있는 사실을 몇 가지 알게 되어 기록해 놓을까 한다.

소스코드는 대략 다음과 같다.

RtlInitUnicodeString(&usKeyName,
L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows
NT\\CurrentVersion\\Perflib\\009");
 
InitializeObjectAttributes(&oa, &usKeyName, OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, NULL, NULL);

status = ZwOpenKey(&handle, KEY_ALL_ACCESS, &oa);
if (NT_SUCCESS(status))
{
     ZwClose(handle);
}

ZwClose()를 호출하면 BSOD가 발생하고 만다.


1. status와 handle


status는 분명히 STATUS_SUCCESS다.

handle도 분명히 0x80000050 값이 들어 있었다.

하지만 ZwClose()를 호출하면 BugCheck 0x93이 발생한다.
BugCheck 0x93은 INVALID_KERNEL_HANDLE 이다.

분명히 ZwOpenKey() 함수가 성공했고 핸들도 리턴했는데 Invalid Handle 이라니 기가 막힐 노릇이다.

일단 STATUS_SUCCESS는 더 볼게 없으니 handle 값으로 부터 정리하기 시작했다.
정말 지푸라기를 잡는 심정으로 0x80000050를 검색해 봤는데 여기서 의외의 수확을 올리게 되었다. 

0x80000050은 실제 핸들이 아니라 HKEY_LOCAL_MACHINE 같은 predefined 핸들이었다. 

#define HKEY_PERFORMANCE_TEXT          (0x80000050)
#define HKEY_PERFORMANCE_NLSTEXT    (0x80000060)

HKEY_PERFORMANCE_TEXT로 검색 해보니 더 가관이다.
MS KB에 설명이 되어 있는 것이었다.

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

마지막 멘트에 다시 한번 기가 찬다. -_-
"이것은 의도적으로 설계된 동작입니다."


2.  실제 키 핸들 얻기

Zw* 시리즈 함수로 별짓을 다 해 봤지만 항상 0x80000050이 튀어 나와 원하는 개발을 할 수 없었다.
OBJ_KERNEL_HANDLE 플래그를 빼도 항상 0x80000050이 튀어 나온다.
이것이 핸들 테이블에서 나온 것이 아니라 일종의 상수라는 것을 명확히 보여주는 것이다.

실제 레지스트리 오브젝트에 대한 핸들 테이블의 핸들값을 얻어야 내가 원하는 작업을 할 수 있기 때문에...
삽질 끝에 레지스트리도 일종의 오브젝트가 아니겠냐는 생각에 Ob* 시리즈 함수로 가 보기로 했다.

NTSTATUS

ObOpenObjectByName(
                  POBJECT_ATTRIBUTES ObjectAttributes,

  POBJECT_TYPE ObjectType,

  PVOID ParseContext,

  KPROCESSOR_MODE AccessMode,

  ACCESS_MASK DesiredAccess,

  PACCESS_STATE PassedAccessState,

  PHANDLE Handle);

 
이것을 사용하려면 ObjectType을 알아야 하는데 알려진 ObjectType은 IoDriverObjectType, PsProcessObjectType, PsThreadObjectType 이런거 밖에 없어서 레지스트리를 열지 못했다.

결국 찾아낸 레지스트리를 위한 ObjectType은 CmpKeyObjectType 이었다.
하지만 이것은 Undocumented라서 코딩을 할 때 직접 사용할 수가 없다.
커널 내부를 뒤져서 값을 얻어내야 하나 어쩌나 고민을 많이 했는데 결국 이것도 구글사마가 해결해 주었다.

다음은 CmpKeyObjectType을 구하는 코드 예제다.

http://read.pudn.com/downloads159/sourcecode/windows/vxd/716440/RegMon_Checked/RegMon.c__.htm

GetCmpKeyObjectTypeByInstance() 함수에서 우리가 원하는 작업을 해주고 있다.


3. 코드 수정

다음과 같이 수정해 작업을 완료했다. 만세!!!

RtlInitUnicodeString(&usKeyName,
L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows
NT\\CurrentVersion\\Perflib\\009");
 
InitializeObjectAttributes(&oa, &usKeyName, OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, NULL, NULL);

status = ObOpenObjectByName(&oa, CmpKeyObjectType, NULL, KernelMode, DesiredAccess, NULL, &handle);
if (NT_SUCCESS(status))
{
     ZwClose(handle);
}

주의할 점은 status는 STATUS_PREDEFINED_HANDLE 로 나온다는 점이다.
#define 
STATUS_PREDEFINED_HANDLE 0x40000016
으로 정의되어 있어 NT_SUCCESS()에서 성공으로 처리된다.

반응형
Posted by GreeMate
Windows OS2011. 6. 20. 00:16
반응형
얼마전 경험한 황당한 일입니다.

파일시스템 필터 드라이버에서 IRP_MJ_CLEANUP 시점에 Irp->RequestorMode를 확인하는데 항상 KernelMode만 들어있는 것이었습니다. (Windows XP부터 Windows 7까지 모두 확인했는데도...)

콜스택은 아래와 같이 틀림없이 유저모드에서 CloseHandle()이 호출된 상황입니다.

1: kd> k
ChildEBP RetAddr  
f70d59f8 f73ef888 MyFilter!PreCleanup 
f70d5a58 f73f12a0 fltmgr!FltpPerformPreCallbacks+0x2d4
f70d5a6c f73f1c48 fltmgr!FltpPassThroughInternal+0x32
f70d5a88 f73f2059 fltmgr!FltpPassThrough+0x1c2
f70d5ab8 804f118f fltmgr!FltpDispatch+0x10d
f70d5ac8 f52b72b1 nt!IopfCallDriver+0x31
f70d5b48 805bea0a nt!IopCloseFile+0x26b
f70d5b7c 805be333 nt!ObpDecrementHandleCount+0xd8
f70d5ba4 805be3d1 nt!ObpCloseHandleTableEntry+0x14d
f70d5bec 805be509 nt!ObpCloseHandle+0x87
f70d5c00 f7772d8c nt!NtClose+0x1d
f70d5d58 7c93e4f4 nt!KiFastCallEntry+0xfc
00129638 7c93cfdc ntdll!KiFastSystemCallRet
0012963c 7c809c1b ntdll!ZwClose+0xc
00129648 01cf4ec8 kernel32!CloseHandle+0x51


전달된 Irp 내부를 들여다 봐도 명확히 RequestorMode가 0 (KernelMode)로 되어 있습니다.

1: kd> !irp 8621fa68 3
Irp is active with 10 stacks 10 is current (= 0x8621fc1c)
 No Mdl: No System Buffer: Thread 862e3650:  Irp stack trace.  
Flags = 00000404
ThreadListEntry.Flink = 862e3860
ThreadListEntry.Blink = 862e3860
IoStatus.Status = 00000000
IoStatus.Information = 00000000
RequestorMode = 00000000
...


어째서 RequestorMode가 KernelMode인 걸까요?

실제로 커널에서 어떤 요청이 있었던걸까요?
하지만 이때 ExGetPreviousMode()를 호출해 보면 UserMode가 나옵니다.

ExGetPreviousMode()함수의 구현을 보면 스레드 정보에서 PreviousMode를 구해 오는 것을 볼 수 있습니다.

1: kd> u ExGetPreviousMode
nt!ExGetPreviousMode:
8052d324 64a124010000    mov     eax,dword ptr fs:[00000124h]
8052d32a 8a8040010000    mov     al,byte ptr [eax+140h]
8052d330 c3              ret


그래서 현재 스레드를 구해서 실제로 확인해 보면

1: kd> .thread
Implicit thread is now 862e3650
 
1: kd> dt _KTHREAD PreviousMode 862e3650
ntdll!_KTHREAD
   +0x140 PreviousMode : 1 ''


0x140 옵셋에 있는 PreviousMode가 정확히 1 (UserMode)로 되어 있음을 확인할 수 있습니다.

도대체 이 무슨 해괴망측한 경우란 말입니까?

스레드에 저장된 PreviousMode는 UserMode인데 이 스레드에서 첫 번째로 처리되고 있는 Irp->RequestorMode는 KernelMode라니...

유저모드에서 CreateFile()을 호출해 필터 드라이버에서 IRP_MJ_CREATE를 받은 경우는 PreviousMode, Irp->RequestorMode 모두 UserMode로 정확히 나옵니다.
 
일단 여기서 알아둬야 할 점은 Thread의 PreviousMode와 Irp의 RequestorMode가 우리의 예상과 다를 수 있다는 점입니다.

IRP_MJ_CLEANUP(CLOSE)시의 Irp->RequestorMode는 항상 KernelMode라는 것은 이번에 확실히 알았으니 앞으로 주의해서 사용해야 하겠습니다.

반응형
Posted by GreeMate
Windows OS2010. 5. 4. 02:04
반응형
KiDeliverApc 내부 동작에 대한 설명입니다.
물론 심심해서 이런 분석을 하게 된 것은 아닙니다.
하도 문제가 안풀리다 보니 답답해서 커널이라도 분석해 보자 하다가 이렇게 되었습니다. ^^

얼마전 시스템 행이 걸리는 문제를 만났는데 아래와 같은 콜스택만 보일 뿐 어떻게 원인을 찾아야 할지 난감한 상태였습니다.

THREAD 8956e660  Cid 0c18.0bcc  Teb: 7ffdd000 Win32Thread: e2975360 WAIT: (Suspended) KernelMode Non-Alertable
SuspendCount 1
...
    Owning Process            899cd5a8       Image:      taskmgr.exe
    Attached Process          N/A            Image:         N/A
...
    ChildEBP RetAddr  
    aad52c18 80504d50 nt!KiSwapContext+0x2f (FPO: [Uses EBP] [0,0,4])
    aad52c24 804fcf40 nt!KiSwapThread+0x8a (FPO: [0,0,0])
    aad52c4c 8050448c nt!KeWaitForSingleObject+0x1c2 (FPO: [5,5,4])
    aad52c64 80500dfa nt!KiSuspendThread+0x18 (FPO: [3,0,0])
    aad52cac 80504d6e nt!KiDeliverApc+0x124 (FPO: [3,10,0])
    aad52cc4 804fcf40 nt!KiSwapThread+0xa8 (FPO: [0,0,0])
    aad52cec 805c108c nt!KeWaitForSingleObject+0x1c2 (FPO: [5,5,4])
    aad52d50 8054289c nt!NtWaitForSingleObject+0x9a (FPO: [Non-Fpo])
    aad52d50 7c93e514 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ aad52d64)
    00dbff84 7c802532 ntdll!KiFastSystemCallRet
    00dbff98 0100d90d kernel32!WaitForSingleObject+0x12 (FPO: [2,0,0])
    00dbffb4 7c80b699 taskmgr!WorkerThread+0x3a (FPO: [1,0,4])
    00dbffec 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])


콜스택의 마지막이 KiSwapContext라 처음엔 그냥 컨텍스트 스위칭인가보다 생각했는데 자세히 보니 KiSwapThread가 콜스택 상에서 두 번 보이는 상태였습니다.

중간에 보이는 KiSwapThread가 일반적인 스케쥴링에 의한 호출이구요.
그 위로 보이는 KiDeliverApc부터가 약간 생소한 부분이었는데요.
KiSuspendThread를 호출하여 다시 대기 상태로 들어가는 흐름입니다.

프로세스는 taskmgr.exe인데 이런 상태가 되면 작업관리자가 뜨다가 말고 행이 걸려 버립니다. 콜스택으로 보면 KiSuspendThread에 의한 결과로 Suspend가 걸려버리는 것 같네요.

문제는 누가 KiSuspendThread를 호출했느냐는 것입니다.

콜스택에서는 KiDeliverApc가 호출한 것으로 보입니다.
여기서 DeliverApc란 뜻을 좀 이해해야 하는데요. (음냐... 복잡해 지는데...)

윈도우에서 스레드 스케쥴링을 할 때는 DISPATCH_LEVEL이라는 우선 순위 개념(IRQL)을 두어서 스케쥴러가 사용하구요.
실행할 스레드가 정해지면 APC_LEVEL로 내려서 이 스레드에 큐잉된 APC(Asynchronous Procedure Call)를 수행하게 합니다.
그리고 나서 PASSIVE_LEVEL로 내려줘서 원래 스레드 코드가 실행되게 하지요.

KiDeliverApc는 큐잉된 APC 들을 실행해 주는 역할을 합니다.
KiDeliverApc의 파라미터로 APC를 전달할 거라고 상상해 봤는데 함수 선언을 보니 파라미터가 매우 단순하므로 그건 아니구요.

VOID
NTAPI
KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode,
             IN PKEXCEPTION_FRAME ExceptionFrame,
             IN PKTRAP_FRAME TrapFrame)


그래서 KiDeliverApc를 분석했는데 요약하면 다음과 같습니다.

KiDeliverApc()
{
    PKTHREAD Thread = KeGetCurrentThread();

    //
    // 현재 스레드Thread의 ApcState.ApcListHead에 달려있는 APC가 있으면 
    // 꺼내서 실행하고 리스트를 계속 확인하여 모두 없어질 때까지 실행한다.

    //
    while (!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
    {
        ApcListEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
        Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);

        KernelRoutine = Apc->KernelRoutine;
        NormalRoutine = Apc->NormalRoutine;

        KernelRoutine(Apc, ...);
        NormalRoutine(...);
    }
}


핵심은 Thread->ApcState.ApcListHead에 어디선가 APC를 추가한다는 것입니다.
KiSuspendThread가 NormalRoutine으로 등록되어 실행된 것이었구요.

APC를 추가한 녀석을 찾기 위해 Thread->ApcState.ApcListHead에 메모리 브레이크 포인트를 걸어서 List에 뭔가 들어오자마자 브레이크 포인트가 걸리게 해 봅니다.
ApcState는 KTHREAD 구조체에서 0x34 옵셋에 위치하므로 아래와 같이 설정합니다.

kd> dt _KTHREAD ApcState.
nt!_KTHREAD
   +0x034 ApcState  :
      +0x000 ApcListHead : [2] _LIST_ENTRY
      +0x010 Process   : Ptr32 _KPROCESS
      +0x014 KernelApcInProgress : UChar
      +0x015 KernelApcPending : UChar
      +0x016 UserApcPending : UChar
   +0x138 ApcStatePointer : [2]
   +0x165 ApcStateIndex : UChar

kd> ba w4 (Thread주소+0x34)


브레이크 포인트가 걸렸을 때 콜스택과 코드를 보면 아래와 같은데요.
이건 어떤 프로세스가 최초 생성될 때 SuspendThread가 호출되는 상황이네요.

kd> k
ChildEBP RetAddr 
f9b9bb60 804fdaa3 nt!KiInsertQueueApc+0x79
f9b9bb80 805c7436 nt!KeSuspendThread+0x67
f9b9bcc4 805c7f02 nt!PspCreateThread+0x570
f9b9bd3c 8053ea48 nt!NtCreateThread+0xfc
f9b9bd3c 7c93e514 nt!KiFastCallEntry+0xf8

kd> ub nt!KiInsertQueueApc+0x79
nt!KiInsertQueueApc+0x64:
804ff374 eb3f            jmp     nt!KiInsertQueueApc+0xa5 (804ff3b5)
804ff376 0fbeda          movsx   ebx,dl
804ff379 8d3cdf          lea     edi,[edi+ebx*8]
804ff37c 8b5f04          mov     ebx,dword ptr [edi+4]
804ff37f 8d700c          lea     esi,[eax+0Ch]       ; ApcListEntry
804ff382 893e            mov     dword ptr [esi],edi
804ff384 895e04          mov     dword ptr [esi+4],ebx 
804ff387 8933            mov     dword ptr [ebx],esi ; 리스트에 추가


코드의 마지막 줄에서 리스트에 추가한 것은 KAPC 구조체의 ApcListEntry 필드의 주소입니다. ApcListEntry의 옵셋이 0Ch이므로 위 코드에서 eax는 KAPC 주소라는 것을 알 수 있습니다.

kd> dt _KAPC @eax
nt!_KAPC
   +0x000 Type             : 18
   +0x002 Size             : 48
   +0x004 Spare0           : 0
   +0x008 Thread           : 0x812f1020 _KTHREAD
   +0x00c ApcListEntry     : _LIST_ENTRY [ 0x812f1054 - 0x812f1054 ]
   +0x014 KernelRoutine    : 0x80501ed4     void  nt!KiSuspendNop+0
   +0x018 RundownRoutine   : 0x805277ba     void  nt!PopAttribNop+0
   +0x01c NormalRoutine    : 0x8050230a     void  nt!KiSuspendThread+0
   +0x020 NormalContext    : (null)
   +0x024 SystemArgument1  : (null)
   +0x028 SystemArgument2  : (null)
   +0x02c ApcStateIndex    : 0 ''
   +0x02d ApcMode          : 0 ''
   +0x02e Inserted         : 0 ''


0x01c옵셋의 NormalRoutine에 KiSuspendThread가 보이네요.

하지만 APC는 이 목적 이외에 다른 목적으로도 다양하게 사용되기 때문에 KTHREAD의 ApcState에 메모리 브레이크를 걸어 놓으면 KernelRoutine과 NormalRoutine에 여러가지 다양한 함수가 전달되는 것을 보게 됩니다.

이것을 피하기 위해 804ff37f 주소(즉, nt!KiInsertQueueApc+79)에 브레이크 포인트를 걸되 NormalRoutine이 KiSuspendThread인 경우만 잡도록 조건을 줬습니다. 
 
kd> u nt!KiSuspendThread L1
nt!KiSuspendThread:
8050230a 64a124010000    mov     eax,dword ptr fs:[00000124h]

kd> bp nt!KiInsertQueueApc+79 ".if (poi(@eax+1c)==8050230a) {} .else {gc}"

그러면 KiSuspendThread를 위한 KAPC만 잡히게 됩니다.
잡혔을 때의 콜스택은 다음과 같구요.

kd> k
ChildEBP RetAddr 
f766ecc8 804fdaa3 nt!KiInsertQueueApc+0x79
f766ece8 805cb671 nt!KeSuspendThread+0x67
f766ed28 805cb706 nt!PsSuspendThread+0x6f
f766ed44 805cb8fe nt!PsSuspendProcess+0x28
f766ed58 8053ea48 nt!NtSuspendProcess+0x40
f766ed58 7c93e514 nt!KiFastCallEntry+0xf8
WARNING: Stack unwind information not available. Following frames may be wrong.
00000000 00000000 ntdll!KiFastSystemCallRet


어떤 프로세스가 유저모드에서부터 SuspendProcess를 호출한 것이었고 결국 KeSuspendThread에서 KiInsertQueueApc를 호출해서 APC가 전달된 것이었습니다.

[부록]

KiDeliverApc 분석 내용을 C언어 버전과 Disassembly 버전으로 첨부했습니다.

반응형
Posted by GreeMate
Windows OS2009. 11. 18. 22:30
반응형

Win2k에서 커널 핸들 테이블을 사용해야 하는데 이게 XP와 달라서 삽질한 내용이다.
요즘 세상에 Win2k를 위한 정보가 쓸모 있을까 싶지만 다른 데는 없는 정보이니 그냥 적어둔다. ^^

[XP의 커널 핸들 테이블 주소]

XP에서는 커널 핸들 테이블은 시스템 프로세스의 핸들 테이블을 의미한다.
다음과 같이 확인 할수 있다.

kd> dd nt!ObpKernelHandleTable L1
80561e38  e1001d28

ObpKernelHandleTable 내부 변수에 저장된 e1001d28이 커널 핸들 테이블 주소다.

이제 시스템 프로세스의 핸들 테이블을 찾아 보자.

kd> !process 0 0 System
PROCESS 8133e7f8  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00039000  ObjectTable: e1001d28  HandleCount: 230.
    Image: System

kd> dt 8133e7f8 _EPROCESS
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x0
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x00000004
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x81193b48 - 0x80562758 ]
   +0x090 QuotaUsage       : [3] 0
   +0x09c QuotaPeak        : [3] 0
   +0x0a8 CommitCharge     : 7
   +0x0ac PeakVirtualSize  : 0x371000
   +0x0b0 VirtualSize      : 0x1d2000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x0bc DebugPort        : (null)
   +0x0c0 ExceptionPort    : (null)
   +0x0c4 ObjectTable      : 0xe1001d28 _HANDLE_TABLE
    ...

시스템 프로세스의 ObjectTable 필드에 같은 값이 들어 있는 것을 볼 수 있다.
따라서 코드를 작성할 때는 간단히 다음과 같은 형식으로 구해도 된다.

MyObpKernelHandleTable = PsInitialSystemProcess->ObjectTable;

[2K의 커널 핸들 테이블 주소]

2K에서는 커널 핸들 테이블은 시스템 프로세스의 핸들 테이블을 의미하지 않는다.
위과 같은 방법으로 확인 할 수 있다.

kd> dd nt!ObpKernelHandleTable L1
8046e438  820b7608

ObpKernelHandleTable 내부 변수에 저장된 820b7608이 커널 핸들 테이블 주소다.

이제 시스템 프로세스의 핸들 테이블을 찾아 보자.

kd> !process 0 0 System
PROCESS 82083be0  SessionId: 0  Cid: 0008    Peb: 00000000  ParentCid: 0000
    DirBase: 00030000  ObjectTable: 820b7688  TableSize: 132.
    Image: System
kd> dt 82083be0 _EPROCESS
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ExitStatus       : 0x103
   +0x070 LockEvent        : _KEVENT
   +0x080 LockCount        : 1
   +0x088 CreateTime       : _LARGE_INTEGER 0x0
   +0x090 ExitTime         : _LARGE_INTEGER 0x0
   +0x098 LockOwner        : (null)
   +0x09c UniqueProcessId  : 0x00000008
   +0x0a0 ActiveProcessLinks : _LIST_ENTRY [ 0x81f96780 - 0x8046e460 ]
   +0x0a8 QuotaPeakPoolUsage : [2] 0
   +0x0b0 QuotaPoolUsage   : [2] 0
   +0x0b8 PagefileUsage    : 6
   +0x0bc CommitCharge     : 6
   +0x0c0 PeakPagefileUsage : 0x27
   +0x0c4 PeakVirtualSize  : 0x1d3000
   +0x0c8 VirtualSize      : 0x1b0000
   +0x0d0 Vm               : _MMSUPPORT
   +0x118 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x120 DebugPort        : (null)
   +0x124 ExceptionPort    : (null)
   +0x128 ObjectTable      : 0x820b7688 _HANDLE_TABLE
    ...

시스템 프로세스의 ObjectTable 필드에 다른 값이 들어 있는 것을 볼 수 있다.
이것이 의미하는 바는...

2K에서는 시스템 프로세스의 핸들 테이블은 그냥 시스템 프로세스의 핸들 테이블이고,
커널 핸들 테이블은 시스템 프로세스와 직접 연결되어 있지 않다는 것이다.

[2K 에서 커널 핸들 테이블을 사용하려면 어떻게 해야 하나?]

몇 시간의 구글링 끝에 단서를 하나 찾을 수 있었다.
커널 내부 변수중 하나인 핸들 테이블 리스트였다.

nt!HandleTableListHead

프로세스마다 존재하는 핸들 테이블은 리스트로 연결되어 있다.
커널 핸들 테이블이 직접 시스템 프로세스에 존재하지는 않지만 이것도 핸들 테이블이므로 저 리스트에 연결되어 있을 것이라는 생각이 들었다.

핸들 테이블을 리스트를 쭉 살펴보면 다음과 같다.

kd> dl nt!HandleTableListHead
8046dda8  820b76dc 81f11b1c 00000000 00000000
820b76dc  820b765c 8046dda8 00040000 00000000
820b765c  81f96a5c 820b76dc 00040000 00000000
81f96a5c  81f66f1c 820b765c 00040000 00000000
81f66f1c  81f667fc 81f96a5c 00040000 00000000
...

이 리스트는 HANDLE_TABLE 구조체에서 HandleTableList 로 연결된 리스트이다.

kd> dt _HANDLE_TABLE
nt!_HANDLE_TABLE
   +0x000 Flags            : Uint4B
   +0x004 HandleCount      : Int4B
   +0x008 Table            : Ptr32 Ptr32 Ptr32 _HANDLE_TABLE_ENTRY
   +0x00c QuotaProcess     : Ptr32 _EPROCESS
   +0x010 UniqueProcessId  : Ptr32 Void
   +0x014 FirstFreeTableEntry : Int4B
   +0x018 NextIndexNeedingPool : Int4B
   +0x01c HandleTableLock  : _ERESOURCE
   +0x054 HandleTableList  : _LIST_ENTRY
   +0x05c HandleContentionEvent : _KEVENT

따라서 핸들 테이블의 주소는 (리스트주소 - 54) 이다.
그러면 핸들 테이블들을 하나씩 살펴보자.

첫번째 핸들 테이블의 주소는 820b76dc - 54 이다.
이것은 누구의 핸들 테이블인지 보자.

kd> dt 820b7688 _HANDLE_TABLE
nt!_HANDLE_TABLE
   +0x000 Flags            : 0
   +0x004 HandleCount      : 0x84
   +0x008 Table            : 0xe1002000  -> 0xe1002400  -> 0xe1002800 _HANDLE_TABLE_ENTRY
   +0x00c QuotaProcess     : (null)
   +0x010 UniqueProcessId  : 0x00000008
   +0x014 FirstFreeTableEntry : 0x85
   +0x018 NextIndexNeedingPool : 0x100
   +0x01c HandleTableLock  : _ERESOURCE
   +0x054 HandleTableList  : _LIST_ENTRY [ 0x820b765c - 0x8046dda8 ]
   +0x05c HandleContentionEvent : _KEVENT

ProcessId 가 8 인걸 보니 시스템 프로세스 같다.
그러고 보니 위에서 찾았던 시스템 프로세스의 핸들 테이블 주소와 일치한다.

두번째 핸들 테이블의 주소는 820b765c - 54 이다.

kd> dt 820b7608 _HANDLE_TABLE
nt!_HANDLE_TABLE
   +0x000 Flags            : 0
   +0x004 HandleCount      : 0x24
   +0x008 Table            : 0xe1003000  -> 0xe1003400  -> 0xe1003800 _HANDLE_TABLE_ENTRY
   +0x00c QuotaProcess     : (null)
   +0x010 UniqueProcessId  : (null)
   +0x014 FirstFreeTableEntry : 0x29
   +0x018 NextIndexNeedingPool : 0x100
   +0x01c HandleTableLock  : _ERESOURCE
   +0x054 HandleTableList  : _LIST_ENTRY [ 0x81f96a5c - 0x820b76dc ]
   +0x05c HandleContentionEvent : _KEVENT

오... 주소가 위에서 봤던 커널 핸들 테이블 주소와 일치한다.
ProcessId 가 0으로 설정되어 있는 핸들 테이블이 커널 핸들 테이블인 것이다.

세번째 핸들 테이블의 주소는 81f96a5c -54 이다.

kd> dt 81f96a08 _HANDLE_TABLE
nt!_HANDLE_TABLE
   +0x000 Flags            : 0
   +0x004 HandleCount      : 0x21
   +0x008 Table            : 0xe1364000  -> 0xe1364400  -> 0xe1364800 _HANDLE_TABLE_ENTRY
   +0x00c QuotaProcess     : 0x81f966e0 _EPROCESS
   +0x010 UniqueProcessId  : 0x0000008c
   +0x014 FirstFreeTableEntry : 0x23
   +0x018 NextIndexNeedingPool : 0x100
   +0x01c HandleTableLock  : _ERESOURCE
   +0x054 HandleTableList  : _LIST_ENTRY [ 0x81f66f1c - 0x820b765c ]
   +0x05c HandleContentionEvent : _KEVENT

이제 OuotaProcess와 UniqueProcessId가 나타난다.

kd> !process 0x81f966e0 0
PROCESS 81f966e0  SessionId: 0  Cid: 008c    Peb: 7ffdf000  ParentCid: 0008
    DirBase: 034a4000  ObjectTable: 81f96a08  TableSize:  33.
    Image: smss.exe

이것은 smss.exe 프로세스의 핸들 테이블이다.
이후로는 실행중인 프로세스들의 핸들 테이블이 이어진다.

다행히 핸들 테이블이 리스트로 엮여 있어서 프로세스와 연결되어 있지 않은 ObpKernelHandleTable을 찾을 수 있었다. ^^b

반응형
Posted by GreeMate
Windows OS2009. 5. 16. 00:23
반응형
Driver Verifier의 Pool Tracking이 어떤 방식으로 동작하는지 조금 살펴 보았다.

ExFreePoolWithTag() 에 전달된 해제할 메모리 주소를 분석하여 확인한다. 
Verifier 검사 함수인 ViReleasePoolAllocation() 까지 이 메모리 주소가 전달된다.

0: kd> kb
ChildEBP RetAddr  Args to Child              
f7426ad4 806590b2 815d5f00 81cd8fa8 00000068 nt!ViReleasePoolAllocation+0x6
f7426af8 8066dc72 81cd8fa8 00000058 00000001 nt!ViFreeTrackedPool+0x8c
f7426b40 8054c32a 81cd8fa8 81105ad0 810ffce8 nt!MmFreeSpecialPool+0x1d2
f7426b80 8065a169 81cd8fa8 00000000 f7426b9c nt!ExFreePoolWithTag+0x4a
f7426b90 f706a8a6 81cd8fa8 f7426bb4 f706aac8 nt!VerifierFreePool+0x1f
...

ViReleasePoolAllocation() 의 두번째 파라미터(ebp+0x0c)가 메모리 주소이다.

0: kd> dd ebp
f7426ad4  f7426af8 806590b2 815d5f00 81cd8fa8
f7426ae4  00000068 00000058 81cd8000 00000058
f7426af4  c040e6c0 f7426b40 8066dc72 81cd8fa8
f7426b04  00000058 00000001 00000068 81105ad0

메모리 내용은 아래와 같이 우리가 사용하던 메모리이다.

0: kd> db 81cd8fa8
81cd8fa8  48 00 4b 00 4c 00 4d 00-5c 00 53 00 79 00 73 00  H.K.L.M.\.S.y.s.
81cd8fb8  74 00 65 00 6d 00 5c 00-43 00 6f 00 6e 00 74 00  t.e.m.\.C.o.n.t.
81cd8fc8  72 00 6f 00 6c 00 53 00-65 00 74 00 30 00 30 00  r.o.l.S.e.t.0.0.
81cd8fd8  32 00 5c 00 53 00 65 00-72 00 76 00 69 00 63 00  2.\.S.e.r.v.i.c.

이 메모리 속성을 보면 Special pool 임을 알 수 있다.

0: kd> !pool 81cd8fa8
Pool page 81cd8fa8 region is Special pool
*81cd8fb0 size:   50 pagable special pool, Tag is RMBN
Owning component : Unknown (update pooltag.txt)

Speical Pool 이기 때문에 페이지 맨 앞쪽에서 헤더를 확인한다.

0: kd> db 81cd8000
81cd8000  58 c0 e1 00 52 4d 42 4e-68 00 00 00 00 5f 5d 81  X...RMBNh...._].
81cd8010  e1 e1 e1 e1 e1 e1 e1 e1-e1 e1 e1 e1 e1 e1 e1 e1  ................
81cd8020  e1 e1 e1 e1 e1 e1 e1 e1-e1 e1 e1 e1 e1 e1 e1 e1  ................
81cd8030  e1 e1 e1 e1 e1 e1 e1 e1-e1 e1 e1 e1 e1 e1 e1 e1  ................

앞부분 8 바이트는 할당크기,상태,태그 정보가 있고 뒷부분에 관리 정보가 있다.
뒤부분 8바이트에 포함된 815d5f00 는 Verifier 관리 정보이다.

메모리 속성을 보면

0: kd> !pool 815d5f00
Pool page 815d5f00 region is Nonpaged pool
 815d5000 size:   20 previous size:    0  (Allocated)  NBqh
815d5dc0 size:   20 previous size:   30  (Allocated)  VfGo
 815d5de0 size:  118 previous size:   20  (Allocated)  Dlck
*815d5ef8 size:   88 previous size:  118  (Allocated) *MmLd
Pooltag MmLd : Mm load module database, Binary : nt!mm

MemoryManager 의 load module database 라는 것을 알 수 있다.

내용을 보면 드라이버 이름이 보인다.

0: kd> db 815d5f00
815d5f00  48 2a 56 80 48 2a 56 80-03 00 00 00 01 00 00 00  H*V.H*V.........
815d5f10  18 00 1a 00 60 5f 5d 81-00 30 e5 f6 00 c0 e6 f6  ....`_]..0......
815d5f20  01 00 00 00 40 19 76 98-00 00 00 00 01 00 00 00  ....@.v.........
815d5f30  d0 bd 10 81 10 00 00 00-00 00 00 00 00 00 00 00  ................
815d5f40  00 00 00 00 08 00 00 00-02 00 00 00 09 00 00 00  ................
815d5f50  00 00 00 00 7c 01 00 00-00 90 3f 00 00 10 01 00  ....|.....?.....
815d5f60  41 00 79 00 44 00 6d 00-76 00 4e 00 73 00 79 00  M.y.D.r.v...s.y.
815d5f70  73 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  s...............

메모리 할당/해제시에 815d5f30 영역을 참조하여 메모리를 점검하는데 여기에 저장된 메모리 주소 8110bdd0 는 무엇인지 확인해 보자.

0: kd> !pool 8110bdd0
Pool page 8110bdd0 region is Nonpaged pool
 8110b000 size:  108 previous size:    0  (Allocated)  Driv (Protected)
 8110bcb0 size:  118 previous size:   40  (Allocated)  Devi (Protected)
*8110bdc8 size:  108 previous size:  118  (Allocated) *Vepp
Pooltag Vepp : Verifier Pool Tracking information, Binary : nt!Vf

Verifier의 Pool Tracking information 임을 알 수 있다.

내용을 보면

0: kd> db 8110bdd0
8110bdd0  09 00 00 00 6a b9 e5 f6-24 06 00 00 53 49 4f 00  ....j...$...SIO.
8110bde0  d0 ef d2 81 2c b6 e5 f6-30 00 00 00 53 49 4f 00  ....,...0...SIO.
8110bdf0  f0 0f 91 81 97 91 e5 f6-10 00 00 00 49 66 73 20  ............Ifs
8110be00  80 4f 69 82 ec 91 e5 f6-7c 00 00 00 49 66 73 20  .Oi.....|...Ifs
8110be10  d0 2f b3 81 94 92 e5 f6-2c 00 00 00 49 66 73 20  ./......,...Ifs
8110be20  f0 ef 4c 82 85 93 e5 f6-0c 00 00 00 49 66 73 20  ..L.........Ifs
8110be30  f0 0f 94 82 97 5b e5 f6-0c 00 00 00 49 66 73 20  .....[......Ifs
8110be40  b0 af 8b 81 ec 5b e5 f6-50 00 00 00 49 66 73 20  .....[..P...Ifs

일정한 형태가 보이므로 dd  로 다시 보면

0: kd> dd 8110bdd0
8110bdd0  00000009 f6e5b96a 00000624 004f4953
8110bde0  81d2efd0 f6e5b62c 00000030 004f4953
8110bdf0  81910ff0 f6e59197 00000010 20736649
8110be00  82694f80 f6e591ec 0000007c 20736649
8110be10  81b32fd0 f6e59294 0000002c 20736649
8110be20  824ceff0 f6e59385 0000000c 20736649
8110be30  82940ff0 f6e55b97 0000000c 20736649
8110be40  818bafb0 f6e55bec 00000050 20736649

한 부분만 선택해서 분석해 보니

81910ff0 는 할당된 메모리 주소
F6e59197 은 할당한 코드 주소
00000010 은 메모리 크기 임을 알 수 있다.

0: kd> db 81910ff0
81910ff0  00 00 00 00 80 4f 69 82-d0 2f b3 81 f0 ef 4c 82  .....Oi../....L.
81911000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
81911010  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
81911020  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

0: kd> u f6e59197
MyDrv!AllocList+0x17
6e59197 8945f8          mov     dword ptr [ebp-8],eax
f6e5919a 837df800        cmp     dword ptr [ebp-8],0
f6e5919e 7507            jne     MyDrv!AllocList+0x27 (f6e591a7)
f6e591a0 33c0            xor     eax,eax
f6e591a2 e963020000      jmp     MyDrv!AllocList+0x28a (f6e5940a)
f6e591a7 8b4508          mov     eax,dword ptr [ebp+8]
f6e591aa 50              push    eax
f6e591ab ff15a02be6f6    call    dword ptr [MyDrv!_imp__wcslen (f6e62ba0)]

Verifier의 Pool Tracking information 에 메모리 할당정보가 저장되어 있는 것을 알 수 있다.

 
반응형
Posted by GreeMate
Windows OS2009. 3. 30. 23:53
반응형

유저모드 프로그램과 커널모드 드라이버간에 동기화가 필요할 때 막연히 Named Mutex 를 쓰면 된다고 생각했는데 막상 하려고 하니 막막하다.

Named Event 는 IoCreateNotificationEvent() 나 IoCreateSynchronizationEvent() 를 사용하면 되는데 커널모드에서 Named Mutex 를 오픈하는 커널 API 는 없는 것이었다.

한참을 헤메고 물어물어 발견한 방법은 ObOpenObjectByName() 으로 여는 방법이다. 다음과 같이 사용하면 된다. 유저모드 에서는 CreateMutex() 로 만들고 커널모드에서는 아래와 같이 만든다.
( 유저모드에서 사용한 Mutex 이름은 Global\\MyLockName 이다. )

NTSTATUS ObOpenObjectByName(POBJECT_ATTRIBUTES ObjectAttributes, POBJECT_TYPE ObjectType, PVOID ParseContext, KPROCESSOR_MODE AccessMode, ACCESS_MASK DesiredAccess, PACCESS_STATE PassedAccessState, PHANDLE Handle);

//
// 뮤텍스 초기화
//

UNICODE_STRING usLockName;
OBJECT_ATTRIBUTES stObjAttr;
HANDLE hLock = NULL;
PVOID pObject = NULL;

RtlInitUnicodeString( &usLockName, L"\\BaseNamedObjects\\Global\\MyLockName" );
InitializeObjectAttributes( &stObjAttr, &usLockName, OBJ_CASE_INSENSITIVE, NULL, NULL );

// Named Object 를 연다.
ntStatus = ObOpenObjectByName( &stObjAttr, NULL, NULL, KernelMode, 0, NULL, &hLock );
if (FALSE == NT_SUCCESS(ntStatus))
{
    return ntStatus;
}

// 핸들로부터 오브젝트 포인터를 구한다.
ntStatus = ObReferenceObjectByHandle( hLock, SYNCHRONIZE, NULL, KernelMode, &pObject, NULL );
if (FALSE == NT_SUCCESS(ntStatus))
{
   ZwClose( hLock );
   return ntStatus;
}

// 이제 핸들은 필요없으므로 닫는다.
ZwClose( hLock );


//
// 뮤텍스 사용
//

#define TIMER_100NANO_BASE 10000 // 1ms 계산용

LARGE_INTEGER lnTimeout;

// 타임아웃 시간설정
lnTimeout.QuadPart = dwMilliseconds * TIMER_100NANO_BASE;
lnTimeout.QuadPart = -lnTimeout.QuadPart;

// 동기화 뮤텍스를 얻기 위해 대기한다.
ntStatus = KeWaitForSingleObject( pObject, Executive, KernelMode, TRUE, &lnTimeout );
if (STATUS_SUCCESS != ntStatus)
{
  return ntStatus;
}

// 동기화가 필요한 작업을 여기서 한다.
// do something

// 뮤텍스의 소유를 해제한다.
KeReleaseMutex( pObject, FALSE );


//
// 뮤텍스 해제
//

ObDereferenceObject( pObject );

반응형
Posted by GreeMate
Windows OS2009. 2. 23. 23:51
반응형

유저모드와 커널모드의 공유메모리를 구현할 때 ZwOpenSection() + ZwMapViewOfSection() 조합으로 사용하는 경우가 많다. 이 때 ZwMapViewOfSection() 에서 얻은 공유메모리에 대한 가상주소는 이 함수를 호출한 현재 프로세스 컨텍스트의 유저모드 주소이다. (0x00160000 같은...)

만약 멀티 프로세스 컨텍스트를 지원하는 드라이버를 개발하고 있다면 ZwMapViewOfSection() 에서 얻은 가상주소는 사용할 수가 없게 된다. 왜냐하면 ZwMapViewOfSection() 을 호출한 프로세스에서  다른 프로세스 컨텍스트로 스위칭되어 버리면 이 가상주소는 유효하지 않은 메모리가 될테니 말이다.

이럴 때는 ZwOpenSection() +  ObReferenceObjectByHandle() + MmMapViewInSystemSpace() 조합을 사용하면 된다. MmMapViewInSystemSpace() 는 커널모드 가상주소를 얻어주기 때문에 아무때나 아무 프로세스 컨텍스트에서나 사용할 수 있다. (0xbb000000 같은...)

NTSTATUS MmMapViewInSystemSpace(PVOID Section , PVOID *
MappedBase , PSIZE_T ViewSize ) ;

param[IN] Section : Section 포인터
param[OUT] MappedBase : 맵핑된 가상주소
param[OUT] ViewSize : 맵핑된 메모리 크기

NTSTATUS MmUnmapViewInSystemSpace(PVOID MappedBase ) ;

param[IN] MappedBase : MmMapViewInSystemSpace() 에서 얻었던 가상주소

반응형
Posted by GreeMate
Windows OS2008. 12. 17. 23:01
반응형
NT 커널모드 드라이버에서 locale 을 구하고 싶을 때 사용할 수 있는 함수를 찾았다. 
사실 커널모드 드라이버에서 locale 을 알아야 할 일이 그렇게 많지는 않기 때문에 얼마나 써먹을지는모르겠지만... ^^ 

ntddk 헤더에는 들어있지 않아서 다음과 같이 선언하고 사용해야 한다. 

NTSYSAPI 
NTSTATUS 
NTAPI 
ZwQueryDefaultLocale(
    IN BOOLEAN ThreadOrSystem, 
    OUT PLCID Locale 
    ); 

놀랍게도 NT4 부터 NT kernel 에서 익스포트하던 함수였다. 
Native API Reference 책에도 이미 나와있는 함수이다. 

시험삼아 호출해 보니 Locale 에 0x412(1042) 가 나왔다. 
Korean locale ID 가 맞다. ^^
반응형
Posted by GreeMate
Windows OS2008. 11. 1. 01:59
반응형
오늘 IA64 (Windows Server 2003 for Itanium)에서 작업하다가 황당한 일을 당했다.

IA64 는 x86 계열과 달라서 메모리를 읽을 때 아무렇게나 읽으면 misalignment exception 이 발생한다. 예를 들어 다음과 같은 코드가 있다면

ULONG_PTR *pData = e0000008`8054a494;
ULONG_PTR ulValue;

ulValue = *pData;

세번째 라인이 실행될 때 data misalinment exception 이 발생하고 이것이 커널모드 드라이버라면 바로 블루스크린이 뜨고 만다.

8 바이트를 읽어서 ulValue 에 넣어주는 작업이기 때문에 pData 가 e0000008`8054a490 이나 e0000008`8054a498 이었다면 문제가 없지만 e0000008`8054a494 부터 8 바이트를 한번에 읽는 저런 작업은 허용되지 않는다.

이 사실을 알고 작업을 해 왔는데 오늘은 저런 상황에서도 블루스크린이 뜨지 않고 정상적으로 수행하는 경우를 당한것이다. 기존의 지식체제가 무너지면서 잠시동안 엄청난 혼란에 빠지고 말았다.

잠시 후 정신을 가다듬고 죽을 때와 죽지 않을 때의 차이점이 무엇인지 생각해 보니 프로세스가 64비트이냐 32비트이냐의 차이가 있다는 사실을 깨닫게 되었다. 유저모드의 프로세스가 64비트인 경우 여기서 I/O 를 요청한 것이 커널로 넘어와 저런 상황을 만들면 바로 블루스크린, 프로세스가 32비트인 경우 요청한 I/O 가 커널로 넘어와서 저런 상황을 만들면 정상 실행되는 것이다.

이렇게 되면 32비트 프로세스인경우 WOW64 가 관여한다는 점에 초점을 맞출 수 있을 것 같다. 주변 사람들과 의견을 나눠본 후 가장 유력한 가정은 32비트 프로세스가 동작하는 상황은 이미 에뮬레이션 모드이고 이 상태에서 커널에 들어오면 OS 가 misalignment exception 에 대하여 exception handling 을 해 준다는 것이었다. IA64 CPU 자체는 구조상 exception 이 발생할 수 밖에 없을테니 OS 가 핸들링을 해준다는 것은 나름대로 설득력이 있어 보인다.

한가지 문제는 왜 32비트 프로세스 환경에서만 핸들링을 해 주냐는 것이다. 64비트 프로세스 환경에서는 왜 핸들링을 안해줘서 죽게 만드는지 모르겠다. 그것도 좀 해주지. 그냥 확 내가 핸들링 해 줄까? 오... 그거 재미있겠다. 내가 예외 핸들러를 만들어 보자!


반응형
Posted by GreeMate
Windows OS2008. 7. 3. 22:49
반응형

프로세스의 시작과 종료 시점을 알려주는 함수로 PsSetCreateProcessNotifyRoutine() 이 있다.
다음과 같이 설정하면 프로세스 시작/종료 시에 CreateProcessNotifyRoutine() 이 호출된다.

PsSetCreateProcessNotifyRoutine( CreateProcessNotifyRoutine, FALSE );

콜백함수는 아래와 같은 프로토타입을 가지는데....

VOID
CreateProcessNotifyRoutine(
    IN HANDLE  ParentId,
    IN HANDLE  ProcessId,
    IN BOOLEAN  Create
    )
{
...
}

이 함수가 호출되었을 때 프로세스 컨텍스트가 MSDN 에 잘 나와 있지 않다.
궁금해서 확인해 보니

Create == TRUE 인 프로세스 시작 시점에는 Parent 프로세스 컨텍스트에서 호출되고
Create == FALSE 인 프로세스 종료 시점에는 자신의 프로세스 컨텍스트에서 호출되더라.

기억해 두는 것이 좋겠다.

반응형
Posted by GreeMate