Windows OS2014. 3. 13. 23:54
반응형

시스템 프로세스에 대해 ZwQueryInformationProcess(ProcessImageFileName)를 호출해 이미지 경로를 구하려고 하면 경로가 나오지 않는다. 


무심코 사용하다가 이런 상황이 발생하니 당황스러워 정리해 둔다.

(MSDN에 적혀 있지도 않아서...)


현재 프로세스는 시스템 프로세스다.


kd> !process 843bdd08 0

PROCESS 843bdd08  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000

    DirBase: 00185000  ObjectTable: 88001be0  HandleCount: 1153.

    Image: System



현재 콜스택은 MyDrv가 ZwQueryInformationProcess()를 호출하고 있는 상황이다.


kd> k

ChildEBP RetAddr  

920796ec 82c0dd87 nt!SeLocateProcessImageName+0x26

92079738 82c4375b nt!PsQueryFullProcessImageName+0x1d

92079a30 82a491ea nt!NtQueryInformationProcess+0x116

92079a30 82a479ad nt!KiFastCallEntry+0x12a

92079abc 876eb3c6 nt!ZwQueryInformationProcess+0x11

92079af8 876eb46c MyDrv!GetProcessImageName+0x86 

...

92079d90 82abc219 nt!PspSystemThreadStartup+0x9e

00000000 00000000 nt!KiThreadStartup+0x19



이미지 경로가 나오지 않는 이유를 알아보기 위해 커널 내부에서 어떻게 처리하고 있는지 살펴봤다.

콜스택에 보이는 마지막 함수(SeLocateProcessImageName)가 실제로 커널에서 이미지 경로를 얻어 주는 함수다.


kd> u 82c4c7e8 L12

nt!SeLocateProcessImageName:

82c4c7e8 8bff            mov     edi,edi

82c4c7ea 55              push    ebp

82c4c7eb 8bec            mov     ebp,esp

82c4c7ed 51              push    ecx

82c4c7ee 8b450c          mov     eax,dword ptr [ebp+0Ch]

82c4c7f1 832000          and     dword ptr [eax],0

82c4c7f4 53              push    ebx

82c4c7f5 8b5d08          mov     ebx,dword ptr [ebp+8]

82c4c7f8 56              push    esi

82c4c7f9 57              push    edi

82c4c7fa 8bc3            mov     eax,ebx

82c4c7fc e861000000      call    nt!SePopulateProcessImageName (82c4c862)

82c4c801 894508          mov     dword ptr [ebp+8],eax

82c4c804 85c0            test    eax,eax

82c4c806 7c4a            jl      nt!SeLocateProcessImageName+0x6a (82c4c852)

82c4c808 8b83ec010000    mov     eax,dword ptr [ebx+1ECh]

82c4c80e 0fb77002        movzx   esi,word ptr [eax+2]               <===



마지막 두 줄에서 이미지 경로를 얻어오기 시작한다.


ebx는 첫번째 파라미터인 [ebp+8]이고 프로세스 오브젝트 포인터다.

ebx+1EC는 뭔가 했더니 프로세스 생성 정보고 이미지 경로가 들어 있는 영역이다.

다음과 같이 확인이 가능하다.


kd> dt _EPROCESS

ntdll!_EPROCESS

   +0x000 Pcb              : _KPROCESS

   +0x098 ProcessLock      : _EX_PUSH_LOCK

   ...

   +0x1ec SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO



_SE_AUDIT_PROCESS_CREATION_INFO가 뭔지 확인해 보면 결국 UNICODE_STRING으로 밝혀진다.


kd> dt _SE_AUDIT_PROCESS_CREATION_INFO

ntdll!_SE_AUDIT_PROCESS_CREATION_INFO

   +0x000 ImageFileName    : Ptr32 _OBJECT_NAME_INFORMATION


kd> dt _OBJECT_NAME_INFORMATION

ntdll!_OBJECT_NAME_INFORMATION

   +0x000 Name             : _UNICODE_STRING



프로세스 포인터를 적용해 내용을 살펴보면 황당하게도 이름이 없다.


kd> dt _EPROCESS SeAuditProcessCreationInfo. 843bdd08 

ntdll!_EPROCESS

   +0x1ec SeAuditProcessCreationInfo  : 

      +0x000 ImageFileName               : 0x880010a0 _OBJECT_NAME_INFORMATION


kd> dt 0x880010a0 _OBJECT_NAME_INFORMATION

ntdll!_OBJECT_NAME_INFORMATION

   +0x000 Name             : _UNICODE_STRING ""


kd> dt 0x880010a0 _UNICODE_STRING

ntdll!_UNICODE_STRING ""

   +0x000 Length           : 0

   +0x002 MaximumLength    : 0

   +0x004 Buffer           : (null) 


kd> dd 0x880010a0 L2

880010a0  00000000 00000000



시스템 프로세스는  SeAuditProcessCreationInfo.ImageFileName이 비어 있음을 알 수 있다.


시스템 프로세스에 대해서는 ZwQueryInformationProcess(ProcessImageFileName)를 호출해도 얻어지는 데이터가 없으므로 호출하지 않는 것이 좋겠다.


반응형
Posted by GreeMate
Windows OS2013. 5. 19. 16:50
반응형

디스크 볼륨이 ReadOnly인지 확인할 필요가 있어 찾다보니 FltIsVolumeWritable()이라는 함수를 발견했다.

기쁨도 잠시...

Vista 이상만 지원하기 때문에 XP에서는 사용할 수가 없었다.

 

그래서...

XP에서도 동작하게 하기 위해 FltIsVolumeWritable() 함수를 구현하기로 했다.

FltIsVolumeWritable() 내부를 보니 생각보다 복잡하지 않았기 때문이다.


 

Windows 7 SP1


kd> uf fltmgr!FltIsVolumeWritable

fltmgr!FltIsVolumeWritable:

8489a398 8bff            mov     edi,edi

8489a39a 55              push    ebp

8489a39b 8bec            mov     ebp,esp

8489a39d 83ec18          sub     esp,18h

8489a3a0 53              push    ebx

8489a3a1 56              push    esi

8489a3a2 8b7508          mov     esi,dword ptr [ebp+8] ; FltObject(Volume)

8489a3a5 57              push    edi

8489a3a6 8b7d0c          mov     edi,dword ptr [ebp+0Ch] ; IsWritable(Ptr)

8489a3a9 c60701          mov     byte ptr [edi],1 ; IsWritable=1

8489a3ac 8b06            mov     eax,dword ptr [esi] ; Volume.Base.Flags

8489a3ae 8bc8            mov     ecx,eax

8489a3b0 c1e918          shr     ecx,18h ; Check bit 0x18

8489a3b3 f6c101          test    cl,1

8489a3b6 7405            je      fltmgr!FltIsVolumeWritable+0x25 (8489a3bd) ; No bit 0x18


fltmgr!FltIsVolumeWritable+0x20: ; bit 0x18

8489a3b8 8b7618          mov     esi,dword ptr [esi+18h]

8489a3bb eb07            jmp     fltmgr!FltIsVolumeWritable+0x2c (8489a3c4)


fltmgr!FltIsVolumeWritable+0x25:

8489a3bd c1e81a          shr     eax,1Ah ; Check bit 0x1A

8489a3c0 a801            test    al,1

8489a3c2 7470            je      fltmgr!FltIsVolumeWritable+0x9c (8489a434) ; No bit 0x1A 


fltmgr!FltIsVolumeWritable+0x2c: ; bit 0x1A

8489a3c4 33db            xor     ebx,ebx ; ebx=0

8489a3c6 395e20          cmp     dword ptr [esi+20h],ebx ; Volume.DiskDeviceObject

8489a3c9 7507            jne     fltmgr!FltIsVolumeWritable+0x3a (8489a3d2) ; jmp if exist


fltmgr!FltIsVolumeWritable+0x33:

8489a3cb b819001cc0      mov     eax,0C01C0019h ; STATUS_FLT_NO_DEVICE_OBJECT

8489a3d0 eb67            jmp     fltmgr!FltIsVolumeWritable+0xa1 (8489a439)


fltmgr!FltIsVolumeWritable+0x3a:

8489a3d2 53              push    ebx

8489a3d3 53              push    ebx

8489a3d4 8d45e8          lea     eax,[ebp-18h] ; KEVENT

8489a3d7 50              push    eax

8489a3d8 ff1528208884    call    dword ptr [fltmgr!_imp__KeInitializeEvent (84882028)]

8489a3de 8d45f8          lea     eax,[ebp-8]

8489a3e1 50              push    eax

8489a3e2 8d45e8          lea     eax,[ebp-18h] ; KEVENT

8489a3e5 50              push    eax

8489a3e6 53              push    ebx ; 0

8489a3e7 53              push    ebx ; 0

8489a3e8 53              push    ebx ; 0

8489a3e9 53              push    ebx ; 0

8489a3ea 53              push    ebx ; 0

8489a3eb ff7620          push    dword ptr [esi+20h] ; Volume.DiskDeviceObject

8489a3ee 6824000700      push    70024h ; IOCTL_DISK_IS_WRITABLE

8489a3f3 ff15b8238884    call    dword ptr [fltmgr!_imp__IoBuildDeviceIoControlRequest (848823b8)]

8489a3f9 3bc3            cmp     eax,ebx ; Irp

8489a3fb 7507            jne     fltmgr!FltIsVolumeWritable+0x6c (8489a404)


fltmgr!FltIsVolumeWritable+0x65:

8489a3fd b89a0000c0      mov     eax,0C000009Ah

8489a402 eb35            jmp     fltmgr!FltIsVolumeWritable+0xa1 (8489a439)


fltmgr!FltIsVolumeWritable+0x6c:

8489a404 8b4e20          mov     ecx,dword ptr [esi+20h] ; Volume.DiskDeviceObject

8489a407 8bd0            mov     edx,eax ; Irp

8489a409 ff15b4218884    call    dword ptr [fltmgr!_imp_IofCallDriver (848821b4)]

8489a40f 3d03010000      cmp     eax,103h ; STATUS_PENDING

8489a414 7511            jne     fltmgr!FltIsVolumeWritable+0x8f (8489a427) ; jmp if not


fltmgr!FltIsVolumeWritable+0x7e: ; STATUS_PENDING

8489a416 53              push    ebx ; 0

8489a417 53              push    ebx ; 0

8489a418 53              push    ebx ; 0

8489a419 53              push    ebx ; 0

8489a41a 8d45e8          lea     eax,[ebp-18h] ; KEVENT

8489a41d 50              push    eax

8489a41e ff1504248884    call    dword ptr [fltmgr!_imp__KeWaitForSingleObject (84882404)]

8489a424 8b45f8          mov     eax,dword ptr [ebp-8]


fltmgr!FltIsVolumeWritable+0x8f:

8489a427 3da20000c0      cmp     eax,0C00000A2h ; STATUS_MEDIA_WRITE_PROTECTED

8489a42c 7502            jne     fltmgr!FltIsVolumeWritable+0x98 (8489a430) ; jmp if not


fltmgr!FltIsVolumeWritable+0x96: ; STATUS_MEDIA_WRITE_PROTECTED

8489a42e 881f            mov     byte ptr [edi],bl ; IsWritable=0


fltmgr!FltIsVolumeWritable+0x98:

8489a430 33c0            xor     eax,eax ; STATUS_SUCCESS

8489a432 eb05            jmp     fltmgr!FltIsVolumeWritable+0xa1 (8489a439)


fltmgr!FltIsVolumeWritable+0x9c:

8489a434 b80d0000c0      mov     eax,0C000000Dh


fltmgr!FltIsVolumeWritable+0xa1:

8489a439 5f              pop     edi

8489a43a 5e              pop     esi

8489a43b 5b              pop     ebx

8489a43c c9              leave

8489a43d c20800          ret     8



 

이렇게 보고 나서 대충 비슷하게 C로 구현하니 다음과 같이 나왔다.

 

NTSTATUS
IsVolumeWritable(
IN   PVOID pFltVolume,
OUT  PBOOLEAN pbWritable
{

NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;

PDEVICE_OBJECT pDiskDevObj = NULL;
KEVENT kEvent;
PIRP pIrp = NULL;
IO_STATUS_BLOCK IoStatusBlock;

if (NULL == pFltVolume || NULL == pbWritable)
{
return STATUS_INVALID_PARAMETER;
}

 

*pbWritable = TRUE;

  

ntStatus = FltGetDiskDeviceObject( pFltVolume, &pDiskDevObj );
if (FALSE == NT_SUCCESS(ntStatus))
{
return ntStatus;
}

KeInitializeEvent( &kEvent, NotificationEvent, FALSE );

pIrp = IoBuildDeviceIoControlRequest( IOCTL_DISK_IS_WRITABLE,
                                                             pDiskDevObj,
                                                             NULL, 0, NULL, 0, FALSE,
                                                             &kEvent,
                                                             &IoStatusBlock );
if (NULL == pIrp)
{
ObDereferenceObject( pDiskDevObj );
return STATUS_INSUFFICIENT_RESOURCES;
}

ntStatus = IoCallDriver( pDiskDevObj, pIrp );
if (STATUS_PENDING == ntStatus)
{
KeWaitForSingleObject( &kEvent, Executive, KernelMode, FALSE, NULL );
ntStatus = IoStatusBlock.Status;
}

if (STATUS_MEDIA_WRITE_PROTECTED == ntStatus)
{
*pbWritable = FALSE;
}
 

ObDereferenceObject( pDiskDevObj );


return STATUS_SUCCESS;

구현 완료!
 


반응형
Posted by GreeMate
Windows OS2013. 4. 27. 22:18
반응형

[문제증상]

GetTempFileName()을 호출하면 시스템이 느려진다???

 

[분석]

시스템이 느려진다는 문의가 있어 Process Monitor로 살펴 봤다.

 

TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\myFFF.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\myFFF.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1000.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1000.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1001.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1001.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1002.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1002.tmp

 

이런 임시 파일 열기가 수두룩히 이어지고 있었다.

(ReadFile, CloseFile I/O는 생략했다.)

 

파일 이름 형식으로 보아 TEMP.EXE는 GetTempFileName()을 호출해 이름을 얻는 것으로 보인다.

 

문제는 GetTempFileName()에서 파일을 열어 본다는 것이다.

파일을 열어본 후에 이미 존재하면 이름의 숫자를 하나 올리고 다시 열어본다.

존재하면 또 올리고 또 열어보고... 존재하지 않는 파일이름이 나올 때까지... -_-

 

콜스택은 다음과 같이 확인된다.

 

Args to Child             
0012fcc4 80100080 0012fc64 ntdll!ZwCreateFile
00142ab0 80000000 00000000 kernel32!CreateFileW+0x35f
7ffdfc00 00142a90 00000000 kernel32!GetTempFileNameW+0x1fd
00422050 00422058 00000000 kernel32!GetTempFileNameA+0x75
00000001 00370d10 00370d88 TEMP!main+0x51

 

시스템 사용중에는 마지막 인덱스를 보관하고 사용하는 것 같은데...

부팅후 처음으로 GetTempFileName()을 호출하면 1번부터 마지막 인덱스를 찾기 위한 Open이 반복된다.

 

위 예제에서 이름을 보면 몇 번이나 열어보고 있는 것인지 확인이 가능하다.

myFFF.tmp로 보아 16진수로 올라가고 있음을 알 수 있으므로 my1000.tmp는 4096번째 파일이다.

tmp 파일이 이미 4096개 존재했다는 뜻이고 계속 올라가고 있다.

이 시스템에는 4000개 이상의 엄청나게 많은 tmp파일이 존재하고 있었다.

 

그냥 보면 왜 CreateFile이 2번씩 나오는지, 이게 왜 시스템을 느려지게 하는지 모르시겠지만...

사실 이 시스템에는 Anti-Virus가 설치되어 있기 때문에 다음과 같이 해석해야 한다.

 

TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\myFFF.tmp => GetTempFileName Open
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\myFFF.tmp => Anti-Virus 실시간감시 Open
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1000.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1000.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1001.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1001.tmp
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1002.tmp => GetTempFileName Open
TEMP.EXE 3568 CreateFile C:\Documents ... \Temp\my1002.tmp => Anti-Virus 실시간감시 Open

 

첫번째는 GetTempFileName()에서 수행하는 Open이고 두번째는 첫번째 Open을 검사하기 위해서 Anti-Virus 실시간감시에서 수행하는 Open이다.

 

실시간감시가 없었다면 GetTempFileName()에서 수행하는 CreateFile/CloseFile 때문에 느려지는 일은 없다.

CreateFile/CloseFile은 순식간에 끝나기 때문이다.

 

하지만 실시간감시가 있다면 tmp파일을 검사하기 위해 Anti-Virus에서 ReadFile이 추가로 발생한다.

ReadFile은 디스크에 있는 파일의 데이터를 실제로 읽는 작업을 하기 때문에 실제 데이터를 읽지 않는 CreateFile/CloseFile에 비해 속도가 느릴 수 밖에 없다.

 

위 예에서 tmp파일 하나 검사하는데 10ms 라고 가정하면 4000개를 검사하는 순간까지는 40초가 나온다.

GetTempFileName()을 호출했는데 리턴될 때까지 40초 이상 걸리는 것이다. -_-

 

 

[결론]

자신의 prefix를 가지는 tmp 파일이 많이 남아 있다면 정리해야 겠다.

부팅후 최초 GetTempFileName() 호출시 특정 prefix를 가지는 tmp 파일이 많이 존재한다면...

Anti-Virus가 모든 tmp 파일을 검사하게 돼 시스템이 느려질 수도 있다.

 

 

[부록]

GetTempFileName.txt

반응형
Posted by GreeMate
Windows OS2013. 4. 7. 21:24
반응형

집에 있는 PC가 이상해서 백신으로 검사도 해보고 뭐가 있나 이리 저리 살펴봤다. (Win7 SP1 x86)

하지만 특별히 문제있는 모듈은 발견되지 않았다.


여러가지 툴로 확인을 하다가 PCHunter를 사용해 보기로 했다. 

PCHunter를 실행해 보니 PCHunter가 제공하는 기능이 매우 수준이 높고 다양하다는 점에 감탄하고 있었다.


그러던 중 Ring0 Hooks-IDT에서 빨간색 라인이 튀어 나왔다.

오호... Unknown Image에 의한 후킹이다. 이거 루트킷인가?

 

 

IDT Index 몇 군데가 빨간색으로 나오고 있었다.

그중 Index 0x82를 살펴봤다.

 


오른쪽 클릭을 하면 "Disassembling original entry"라는 메뉴가 있다.

클릭해 봤다.

 

 

원래 있어야할 코드가 보인다.

오... 훌륭하다.

얼핏보니 인덱스를 주고 디폴트 핸들러로 넘기는 듯한 코드의 모양이 보인다.


오른쪽 클릭 메뉴에서 "Disassembling current entry"를 선택하니 다음과 같이 현재 상태가 나왔다.


 

이야... 이건 정말 어떤 함수잖아... 어떡해... -_-

85B5F558 이라는 주소도 Unknown이라고 나오는 것으로 보아 커널 코드가 아닌 어떤 메모리 영역인 것 같고...

뭔가 있는건가..?


이  Unknown 메모리가 뭔지 누가 이런 메모리에 코드를 올려놓았는지 찾아야 했다.

OS를 Debug 모드로 설정하고 재부팅해 WinDbg로 Local Kernel Debugging을 해 봤다. 

 

재부팅을 해서 주소가 일치하지 않을 수 있으므로 idt 명령으로 문제의 IDT를 다시 살펴봤다.

 

lkd> !idt

Dumping IDT:

37: 83238104 hal!PicSpuriousService37
62: 867c77d8 USBPORT!USBPORT_InterruptService (KINTERRUPT 867c7780)
72: 867c7cd8 USBPORT!USBPORT_InterruptService (KINTERRUPT 867c7c80)
             USBPORT!USBPORT_InterruptService (KINTERRUPT 867c7280)
82: 85266558 ataport!IdePortInterrupt (KINTERRUPT 85266500)
92: 852662d8 ataport!IdePortInterrupt (KINTERRUPT 85266280)
             ataport!IdePortInterrupt (KINTERRUPT 85266a00)
             USBPORT!USBPORT_InterruptService (KINTERRUPT 867c7a00)
b0: 85266058 ndis!ndisMiniportMessageIsr (KINTERRUPT 85266000)
b1: 85266cd8 ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 85266c80)
b2: 852667d8 HDAudBus!HdaController::Isr (KINTERRUPT 85266780)
             USBPORT!USBPORT_InterruptService (KINTERRUPT 867c7500)
             dxgkrnl!DpiFdoLineInterruptRoutine (KINTERRUPT 867c7000)
c1: 832383f4 hal!HalpBroadcastCallService
d1: 83220634 hal!HalpHpetClockInterrupt
d2: 83220898 hal!HalpHpetRolloverInterrupt
df: 832381dc hal!HalpApicRebootService
e1: 83238958 hal!HalpIpiHandler
e3: 832386f8 hal!HalpLocalApicErrorService
fd: 83238f2c hal!HalpProfileInterrupt
fe: 832391a8 hal!HalpPerfInterrupt


인덱스 82가 ataport!IdePortInterrupt 에 해당하는 것으로 나온다.

어... 이거 정상인가?


메모리 주소의 속성을 알아봤다.

 

lkd> !pool 85266558
Pool page 85266558 region is Nonpaged pool
*85260000 : large page allocation, Tag is KeIn, size is 0x1000 bytes
  Owning component : Unknown (update pooltag.txt)

 

lkd> !pte 85266558

               VA 85266558

PDE at 00000000C0602168    PTE at 00000000C042DB00
contains 000000007F556863  contains 000000007EB60963
pfn 7f556      ---DA--KWEV    pfn 7eb60      -G-DA--KWEV

 

KeIn이라는 태그를 가진 메모리다.

찝찝하게 소유자가 Unknown이다.

 

코드를 살펴봤다.

 

lkd> u 85266558 L40
85266558 54              push    esp
85266559 55              push    ebp
8526655a 53              push    ebx
8526655b 56              push    esi
8526655c 57              push    edi
8526655d 83ec54          sub     esp,54h
85266560 8bec            mov     ebp,esp
85266562 894544          mov     dword ptr [ebp+44h],eax
85266565 894d40          mov     dword ptr [ebp+40h],ecx
85266568 89553c          mov     dword ptr [ebp+3Ch],edx
8526656b f7457000000200  test    dword ptr [ebp+70h],20000h
85266572 0f8575010000    jne     852666ed
85266578 66837d6c08      cmp     word ptr [ebp+6Ch],8
8526657d 741f            je      8526659e
8526657f 8c6550          mov     word ptr [ebp+50h],fs
85266582 8c5d38          mov     word ptr [ebp+38h],ds
85266585 8c4534          mov     word ptr [ebp+34h],es
85266588 8c6d30          mov     word ptr [ebp+30h],gs
8526658b bb30000000      mov     ebx,30h
85266590 b823000000      mov     eax,23h
85266595 668ee3          mov     fs,bx
85266598 668ed8          mov     ds,ax
8526659b 668ec0          mov     es,ax
8526659e 648b1d00000000  mov     ebx,dword ptr fs:[0]
852665a5 64c70500000000ffffffff mov dword ptr fs:[0],0FFFFFFFFh
852665b0 895d4c          mov     dword ptr [ebp+4Ch],ebx
852665b3 81fc00000100    cmp     esp,10000h
852665b9 0f8202010000    jb      852666c1
852665bf c7456400000000  mov     dword ptr [ebp+64h],0
852665c6 648b0d24010000  mov     ecx,dword ptr fs:[124h]
852665cd fc              cld
852665ce 648b3d20000000  mov     edi,dword ptr fs:[20h]
852665d5 fe4711          inc     byte ptr [edi+11h]
852665d8 807f1101        cmp     byte ptr [edi+11h],1
852665dc 753b            jne     85266619
852665de 0f31            rdtsc
852665e0 2b87f8310000    sub     eax,dword ptr [edi+31F8h]
852665e6 1b97fc310000    sbb     edx,dword ptr [edi+31FCh]
852665ec 8b7110          mov     esi,dword ptr [ecx+10h]
852665ef 03f0            add     esi,eax
852665f1 115118          adc     dword ptr [ecx+18h],edx
852665f4 014110          add     dword ptr [ecx+10h],eax
852665f7 115114          adc     dword ptr [ecx+14h],edx
852665fa 0187f8310000    add     dword ptr [edi+31F8h],eax
85266600 1197fc310000    adc     dword ptr [edi+31FCh],edx
85266606 f6410201        test    byte ptr [ecx+2],1
8526660a 740d            je      85266619
8526660c 8bf1            mov     esi,ecx
8526660e 52              push    edx
8526660f 50              push    eax
85266610 57              push    edi
85266611 ff15b4ea3b83    call    dword ptr [nt!KeCpuAccountingCallback (833beab4)]
85266617 8bce            mov     ecx,esi
85266619 83652c00        and     dword ptr [ebp+2Ch],0
8526661d f64103df        test    byte ptr [ecx+3],0DFh
85266621 7522            jne     85266645
85266623 8b5d60          mov     ebx,dword ptr [ebp+60h]
85266626 8b7d68          mov     edi,dword ptr [ebp+68h]
85266629 89550c          mov     dword ptr [ebp+0Ch],edx
8526662c c74508000ddbba  mov     dword ptr [ebp+8],0BADB0D00h
85266633 895d00          mov     dword ptr [ebp],ebx
85266636 897d04          mov     dword ptr [ebp+4],edi
85266639 bf00652685      mov     edi,85266500h
8526663e e98d8702fe      jmp     nt!KiInterruptDispatch (8328edd0)

 

PCHunter에서 봤던 코드와 동일하다.

메모리에 올려진 코드라...


그런데 마지막을 보니 nt!KiInterruptDispatch로 점프하는 것이 보인다.

코드 중간을 대충 훓어보니 인터럽트 처리 정도로만 보인다.

딱히 이상한 짓을 하는 것 같지는 않다.


마지막 줄 위에 있는 명령의 값은

mov     edi,85266500h 

!idt 명령에서 KINTERRUPT라고 되어 있던 주소다.


KINTERRUPT 구조체를 확인해 보니

 

lkd> dt _KINTERRUPT 85266500
nt!_KINTERRUPT
   +0x000 Type             : 22
   +0x002 Size             : 632
   +0x004 InterruptListEntry : _LIST_ENTRY [ 0x85266504 - 0x85266504 ]
   +0x00c ServiceRoutine   : 0x891db48a     unsigned char  ataport!IdePortInterrupt+0
   +0x010 MessageServiceRoutine : (null)
   +0x014 MessageIndex     : 0
   +0x018 ServiceContext   : 0x852ad028
   +0x01c SpinLock         : 0
   +0x020 TickCount        : 0xffffffff
   +0x024 ActualLock       : 0x85b8d960  -> 0
   +0x028 DispatchAddress  : 0x8328edd0     void  nt!KiInterruptDispatch+0
   +0x02c Vector           : 0x82
   +0x030 Irql             : 0x7 ''
   +0x031 SynchronizeIrql  : 0x7 ''
   +0x032 FloatingSave     : 0 ''
   +0x033 Connected        : 0x1 ''
   +0x034 Number           : 0
   +0x038 ShareVector      : 0 ''
   +0x039 Pad              : [3]  ""
   +0x03c Mode             : 1 ( Latched )
   +0x040 Polarity         : 0 ( InterruptPolarityUnknown )
   +0x044 ServiceCount     : 0
   +0x048 DispatchCount    : 0xffffffff
   +0x050 Rsvd1            : 0
   +0x058 DispatchCode     : [135] 0x56535554


85266500는 인터럽트 오브젝트의 주소였던 것이다.

85266558는 KINTERRUPT+58에 위치하는 DispatchCode였던 것이고...

 

lkd> ? 85266500 + 58
Evaluate expression: -2061081256 = 85266558

 

Windows Internals를 보면 인터럽트 처리시 인터럽트 오브젝트를 전달하면서 nt!KiInterruptDispatch를 호출한다고 되어 있다.

인터럽트가 발생하면 인터럽트 오브젝트에 존재하는 정보로 다음과 같이 코드 실행이 이루어 지는 것이다.

 

1. +0x058 DispatchCode      : 0x85266558

2. +0x028 DispatchAddress  : 0x8328edd0     void  nt!KiInterruptDispatch+0
3. +0x00c ServiceRoutine   : 0x891db48a     unsigned char  ataport!IdePortInterrupt+0

 

정리하면 모든 것이 정상이었던 것이다.

허탈하다. -_-

 

[결론]

1. 왜 커널은 인터럽트 오브젝트에 코드를 넣어놓고 실행해 사람을 놀라게 하는가?

2. PCHunter 같은 정교한 툴이 이런 것을 정확히 처리하지 않고 빨간색을 보여주는지가 매우 안타깝다.

반응형
Posted by GreeMate
Windows OS2013. 1. 16. 22:44
반응형

XP 핸들 테이블을 추적해 주어진 핸들값에 해당하는 오브젝트를 찾아내는 과정을 정리해 본다.


특정 프로세스 정보를 보면 핸들 테이블에 해당하는 ObjectTable 주소를 찾을 수 있다. 


1: kd> !process 885fcba0

PROCESS 885fcba0  SessionId: 0  Cid: 04cc    Peb: 7ffd5000  ParentCid: 06d4

    DirBase: 0a380460  ObjectTable: e3297918  HandleCount: 321.

    Image: MyApp.exe

    ...


ObjectTable은 _HANDLE_TABLE 타입이므로 아래와 같이 확인해 볼 수 있다. 


1: kd> dt  _HANDLE_TABLE e3297918
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xe7794000
   +0x004 QuotaProcess     : 0x885fcba0 _EPROCESS
   +0x008 UniqueProcessId  : 0x000004cc Void
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY [ 0x80566ba8 - 0xe1141c8c ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : (null) 
   +0x02c ExtraInfoPages   : 0n0
   +0x030 FirstFree        : 0x544
   +0x034 LastFree         : 0x5d0
   +0x038 NextHandleNeedingPool : 0x800
   +0x03c HandleCount      : 0n321
   +0x040 Flags            : 0
   +0x040 StrictFIFO       : 0y0

이중에서 실제 핸들 테이블 내용에 해당하는 부분이 TableCode다.

1: kd> dc 0xe7794000
e7794000  00000000 fffffffe e1009529 000f0003  ........).......
e7794010  e19a61a9 00000003 87b87a31 00100020  .a......1z.. ...
e7794020  8878d951 00100020 e1aad391 000f000f  Q.x. ...........
e7794030  e6b9f4c9 021f0001 89af54f1 000f037f  .........T......
e7794040  87d192c1 00100020 87ce43a9 00100020  .... ....C.. ...
e7794050  87c6a171 021f0003 89b00601 000f01ff  q...............
e7794060  89af54f1 000f037f 890726d1 00100003  .T.......&......
e7794070  8852d911 00100003 e6197e81 020f003f  ..R......~..?...

잘 보면 8바이트 단위로 규칙적인 모양을 가지고 있는 것을 알 수 있다.
첫번째 8바이트가 핸들 0의 오브젝트, 두번째 8바이트가 핸들 4의 오브젝트 이런 식이다. (핸들값이 4씩 증가하므로...)

이 8바이트는 _HANDLE_TABLE_ENTRY이고 아래와 같은 구조다.

1: kd> dt _HANDLE_TABLE_ENTRY
nt!_HANDLE_TABLE_ENTRY
+0x000 Object : Ptr32 Void
+0x004 GrantedAccess : Uint4B
(사실 UNION 구조체인데 설명에 불필요한 필드는 생략했다)

따라서 이 프로세스 안에서의 핸들 180의 오브젝트 위치는 아래와 같이 구할 수 있다.

1: kd> dd 0xe7794000 + (180/4) * 8
e7794300  899fcac1 001f0003 8844c241 00100003
e7794310  8819b139 001f0001 884d5229 001f0003
e7794320  88675781 021f01ff 87cce699 00100003
e7794330  88104831 001f0001 87da1459 001f0001
e7794340  87cea2b9 00000a84 87cc5b51 001f03ff
e7794350  89a49199 001f0003 89b2e251 001f0003
e7794360  e30433d9 000f01ff 87cc5b51 001f03ff
e7794370  e7675639 0002001f 87d47961 021f0003

899fcac1가 오브젝트인데 딱 보니 최하위 비트를 어떤 플래그로 사용하고 있는 것 같다.
어떤 플래그인지는 찾아보지 않았는데 오브젝트 포인터를 사용하려면 이 플래그를 제거해서 899fcac0를 사용해야 한다.

이 포인터 주소는 사실 오브젝트 헤더의 포인터다.

1: kd> dt _OBJECT_HEADER

   +0x000 PointerCount : Int4B

   +0x004 HandleCount : Int4B

   +0x004 NextToFree : Ptr32 Void

   +0x008 Type : Ptr32 _OBJECT_TYPE

   +0x00c NameInfoOffset : UChar

   +0x00d HandleInfoOffset : UChar

   +0x00e QuotaInfoOffset : UChar

   +0x00f Flags : UChar

   +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION

   +0x010 QuotaBlockCharged : Ptr32 Void

   +0x014 SecurityDescriptor : Ptr32 Void

   +0x018 Body : _QUAD


오브젝트 헤더 + 18이 실제 오브젝트의 포인터이므로 아래와 같이 오브젝트를 확인할 수 있다.

1: kd> !object 899fcac0+18
Object: 899fcad8  Type: (89bea6f0) Event
    ObjectHeader: 899fcac0 (old version)
    HandleCount: 1  PointerCount: 2

MyApp.exe에서 핸들 0x180은 이벤트 오브젝트라는 것을 확인할 수 있다.

끝!

반응형
Posted by GreeMate
Windows OS2012. 2. 1. 00:15
반응형
앞에서 KeAcquireQueuedSpinLock() 함수에 대해 알아 봤다.

http://greemate.tistory.com/entry/KeAcquireQueuedSpinLock

이 좋은 것을 왜 서드파티 개발자들은 사용할 수 없을까?
그래서 마련된 것이 KeAcquireInStackQueuedSpinLock()이다.

KeAcquireQueuedSpinLock()은 PCR(Processor Control Region)에 락에 대한 변수를 선언해 놨기 때문에 서드 파티 개발자들이 사용할 수 없다는 것은 어찌보면 당연한 말이다.

그렇다면 KeAcquireInStackQueuedSpinLock()은 이 문제를 어떻게 해결한 것일까?
먼저 함수 프로토타입을 보자.

VOID 
KeAcquireInStackQueuedSpinLock(

  __inout  PKSPIN_LOCK SpinLock,

  __out    PKLOCK_QUEUE_HANDLE LockHandle

);

VOID 
KeReleaseInStackQueuedSpinLock(

  __in  PKLOCK_QUEUE_HANDLE LockHandle

); 


핵심은 KLOCK_QUEUE_HANDLE에 있다.
KLOCK_QUEUE_HANDLE이 PCR에 정의된 QueuedSpinLock 역할을 대신해 주면서 동일한 방식으로 동작한다.

KLOCK_QUEUE_HANDLE 구조는 공개되지 않았지만 분석해 보면 아래와 같은 필드를 가진다. (첨부파일 참조)

typedef struct _KLOCK_QUEUE_HANDLE  
{
    struct _KLOCK_QUEUE_HANDLE *pNext;
    PKSPIN_LOCK pLock;
    KIRQL OldIrql;
} KLOCK_QUEUE_HANDLE, *PKLOCK_QUEUE_HANDLE; 


QueuedSpinLock이 가지고 있던 pLock, pNext를 그대로 가지고 있다.
추가로 OldIrql 필드도 있다.

함수 프로토타입을 보면 KeAcquireInStackQueuedSpinLock() 함수는 IRQL을 리턴하는 부분이 없는데 Acquire하면서 OldIrql 필드에 직접 저장해 주기 때문에 굳이 함수의 리턴값일 필요가 없다. 
KeReleaseInStackQueuedSpinLock() 함수도 LockHandle만 넘겨주면 저장된 OldIrql을 사용하기 때문에 굳이 IRQL을 파라미터로 받지 않아도 된다.

여기서 주의해야 할 사항은 함수이름이 InStack인 이유가 있다는 것이다.
첫번째 파라미터인 SpinLock은 전역변수거나 지역변수거나 상관없지만 두번째 파라미터인 LockHandle은 반드시 지역변수로 선언해서 스택에 존재하게 해야 한다.

황당하게도 이 중요한 사실이 MSDN에는 나와있지 않다.
다행히도 Windows Internals에서는 잘 설명해 주고 있다.

KeAcquireQueuedSpinLock() 함수는 CPU마다의 PCR을 사용했기 때문에 CPU1의 PCR과 CPU2의 PCR 주소가 다르고, 서로 다른 메모리 영역이라서 pLock도 각각 가지고 있고, pNext에 주소를 기록해서 링크로 연결할 수 있었다.

KeAcquireInStackQueuedSpinLock() 함수에서는 LockHandle이 PCR 영역의 역할을 하는 것인데 만약 이것이 전역변수라면 CPU1과 CPU2가 사용하는 메모리가 같아진다는 뜻이다. 
CPU1과 CPU2가 pLock 변수를 같은 것을 쓰게 되어 각자 특정 비트를 셋팅할 수도 없고 pNext 링크도 자기 자신을 연결해 버리는 셈이 되니 엉망이 되고 말 것이다.

따라서 KeAcquireInStackQueuedSpinLock()를 호출하는 곳에서는 LockHandle을 지역변수로 선언해 현재 스레드 스택의 메모리가 사용되도록 하면 되는 것이다. (모든 스레드는 각자 고유의 스택영역을 가지고 있으니 말이다.)
설명하면서 생각해 보니 호출 직전에 할당한 메모리를 사용해도 문제가 없을 것 같다.
전역변수만 아니면 되겠다.

이제 개발자들도 QueuedSpinLock을 사용할 수 있다. ^^
아쉽게도 이 함수는 XP이상에서만 지원한다.

  

반응형
Posted by GreeMate
Windows OS2012. 1. 31. 00:14
반응형
디버깅 중에 KeAcquireQueuedSpinLock() 함수가 보이길래 이번 기회에 좀 더 자세히 알아봤다.
MSDN을 보면 시스템 전용이라고만 나와 있다.
개발자들은 사용하지 말라는 뜻으로 보인다.

그래도 궁금하므로 좀 더 알아봐야 겠다.
사실 Windows Internals 3장에도 이게 어떤 일을 하는지 나름대로 잘 설명되어 있다.
구글링해보면 더 자세한 설명들도 종종 나온다.

정리하자면...
일단 프로토타입은 아래와 같다.

KIRQL
FASTCALL
KeAcquireQueuedSpinLock (
    __in KSPIN_LOCK_QUEUE_NUMBER Number
    );

VOID
FASTCALL
KeReleaseQueuedSpinLock (
    __in KSPIN_LOCK_QUEUE_NUMBER Number,
    __in KIRQL OldIrql
    );



1. KSPIN_LOCK_QUEUE_NUMBER에 대해

가장 먼저 눈에 들어오는 파라미터 타입 KSPIN_LOCK_QUEUE_NUMBER를 먼저 알아보자.
이건 WDK에 포함된 헤더파일에 다음과 같이 정의되어 있다.

typedef enum _KSPIN_LOCK_QUEUE_NUMBER {
    LockQueueDispatcherLock,
    LockQueueUnusedSpare1,
    LockQueuePfnLock,
    LockQueueSystemSpaceLock,
    LockQueueVacbLock,
    LockQueueMasterLock,
    LockQueueNonPagedPoolLock,
    LockQueueIoCancelLock,
    LockQueueWorkQueueLock,
    LockQueueIoVpbLock,
    LockQueueIoDatabaseLock,
    LockQueueIoCompletionLock,
    LockQueueNtfsStructLock,
    LockQueueAfdWorkQueueLock,
    LockQueueBcbLock,
    LockQueueMmNonPagedPoolLock,
    LockQueueUnusedSpare16,
    LockQueueTimerTableLock,
    LockQueueMaximumLock = LockQueueTimerTableLock + LOCK_QUEUE_TIMER_TABLE_LOCKS
} KSPIN_LOCK_QUEUE_NUMBER, *PKSPIN_LOCK_QUEUE_NUMBER; 


정의된 이름들을 보면 어떤 경우에 사용하는 것인지 대충 감이 온다.
커널을 분석하다 보면 각각 상황에 맞게 위의 값들이 KeAcquireQueuedSpinLock() 함수의 파라미터로 전달되는 것을 볼 수 있다.

이것들은 enum으로 정의되어 있으므로 숫자로 된 인덱스다.
인덱스를 넘기는 이유는 커널이 이미 LockQueueMaimumLock만큼의 SpinLock을 들고 있기 때문이다.
SpinLock의 배열이므로 인덱스로 찾아서 사용하기만 하면 되는 것이다.

시스템 내부적인 목적으로 만들어 놓은 Lock들이므로 개발자들에게 사용하지 말라고 하는 것으로 보인다.


2. QueuedSpinLock에 대해

QueuedSpinLock은 SpinLock의 단점을 보완해 효율적으로 동작한다.

SpinLock의 단점이란 멀티 프로세서 환경에서 어떤 CPU가 SpinLock을 점유할 경우 다른 CPU들이 SpinLock을 체크하기 위해 동기적으로 특정 메모리(SpinLock)에 접근해 시스템 버스에 부하를 주는 현상을 말한다.

결론부터 간단히 말하면 QueuedSpinLock은 특정 메모리를 동기적으로 접근하지 않고 CPU가 각자 자기만의 영역을 체크하는 방식으로 동작하기 때문에 시스템 버스의 부하를 감소시킨다.

어떻게 이것이 가능한지 살펴보자.
QueuedSpinLock은 pLock, pNext라는 두 가지 속성을 가진다.
pLock은 SpinLock의 주소가 저장되고 pNext는 이 스핀락을 대기하는 다른  CPU의 QueuedSpinLock의 주소를 저장하는 링크를 의미한다.

다른 CPU의 QueuedSpinLock 주소라는 말을 이해하려면 CPU마다 각각 가지고 있는 PCR(Processor Control Region)에 LockQueueMaimumLock만큼의 QueuedSpinLock 배열이 정의되어 있다는 내용을 이해해야 한다.

[CPU1의 PCR 내부] 
...
QueuedSpinLock[LockQueuePfnLock].pLock = &PfnLock;
QueuedSpinLock[LockQueuePfnLock].pNext = NULL;
QueuedSpinLock[LockQueueSystemSpaceLock].pLock = &SystemSpaceLock;
QueuedSpinLock[LockQueueSystemSpaceLock].pNext = NULL;
...

[CPU2의 PCR 내부]
...
QueuedSpinLock[LockQueuePfnLock].pLock = &PfnLock;
QueuedSpinLock[LockQueuePfnLock].pNext = NULL;
QueuedSpinLock[LockQueueSystemSpaceLock].pLock = &SystemSpaceLock;
QueuedSpinLock[LockQueueSystemSpaceLock].pNext = NULL;
...


이와 같이 CPU 마다 QueuedSpinLock에 대한 정보를 가지고 있는 것이다.

이제 동작방식을 시나리오로 간단히 정리해 보자.
(설명의 편의를 위해 인덱스로 배열을 참조하는 부분은 생략한다)

1. CPU1이 KeAcquireQueuedSpinLock() 함수를 호출 
 - QueuedSpinLock 획득

2. CPU2가 KeAcquireQueuedSpinLock() 함수를 호출
 - 이미 스핀락이 점유되어 있으므로 대기를 위해
 - CPU2의 PCR->QueuedSpinLock[x].pLock 최하위 비트를 셋팅해 대기중임을 표시 (이 비트가 풀릴 때까지 무한 루프)
 - CPU1의 PCR->QueuedSpinLock[x].pNext에 CPU2의 PCR->QueuedSpinLock[x] 주소를 큐잉

3. CPU1이 KeReleaseQueuedSpinLock() 함수를 호출
 - 큐잉된 CPU2의 PCR->QueuedSpinLock[x].pLock을 찾아가 최하위 비트를 클리어(CPU2의 대기가 풀림)
 - CPU1의 PCR->QueuedSpinLock[x].pNext를 클리어


즉, 어떤 CPU라도 QueuedSpinLock을 대기하는 경우는 자신의 PCR에 저장된 QueuedSpinLock[x].pLock 필드의 특정 비트만 체크하면서 스핀하고 있는 것이므로 메모리를 동기적으로 접근할 필요가 없어진다.

그리고 여러 CPU가 QueuedSpinLock을 요청하는 경우라도 pNext 필드에 링크로 연결되어 있으므로 먼저 요청한 CPU가 먼저 받아가는 합리적인 배분(FIFO)도 이루어 진다.

알고보니 재미있는 트릭이다. ^^
 
반응형
Posted by GreeMate
Windows OS2011. 10. 10. 19:19
반응형
미니필터를 사용하다 보면 필터매니저의 프레임이라는 개념을 접하게 되는 경우가 있다.
프레임이 여러개 존재하는 상황에 대한 정리가 필요해 기록해 둔다.

1. Frame 0 하나인 상황
먼저 기본적인 상황을 살펴본다.
보통 필터 드라이버 디바이스 스택을 보면 다음과 같다.

kd> !devstack 859a8168  
  !DevObj   !DrvObj            !DevExt   ObjectName
  863d8108  \Driver\AhnFlt2K   863d81c0  
> 859a8168  \Driver\AFPAnsi    859a8220  
  8593c280  \FileSystem\FltMgr 8593c338  ===> Frame 0 
  8592d020  \FileSystem\Ntfs   8592d0d8  

아래에서 두번째 줄에 FltMgr이 보이는에 여기가 바로 필터매니저가 생성한 Frame 0 다.
Frame 0 위에 AFPAnsi와 AhnFlt2k가 있는데 둘 다 위에 있지만 이렇게 위치한 이유가 좀 다르다.

AFPAnsi는 부팅시에 로드하게 되어 있는데 Group이 Filter로 설정되어 있다.
필터 매니저가 관리하는 Altitude 중 가장 위에 Filter 그룹이 위치하므로 FltMgr 위로 올라간 것이다. 

필터 매니저의 Altitude 정보는 아래 링크를 참고하기 바란다.
http://msdn.microsoft.com/en-us/windows/hardware/gg462963.aspx

AhnFlt2k는 수동 로드이고 Group이 없기 때문에 부팅 후에 attach 하면서 맨 위에 붙어 버린 것이다.
이 부분은 나중에 좀 더 꼼꼼히 따져봐야 하는데 일단 현재까지는 이렇게 정리했다.

현재 상태의 프레임 정보를 보면 다음과 같다.

kd> !fltkd.frames

Frame List: 83a159f8 
   FLTP_FRAME: 8531b6c8 "Frame 0" "0 to 429998.99"
      FLT_FILTER: 859e5e30 "V3Flt2K" "326020"
         FLT_INSTANCE: 860c05d0 "V3Flt2K Instance" "326020"
         FLT_INSTANCE: 860c0308 "V3Flt2K Instance" "326020"
      FLT_FILTER: 86172008 "luafv" "135000"
         FLT_INSTANCE: 86155a50 "luafv" "135000"
      FLT_FILTER: 85336980 "FileInfo" "45000"
         FLT_INSTANCE: 858f50e8 "FileInfo" "45000"
         FLT_INSTANCE: 85900830 "FileInfo" "45000"

현재 필터 매니저를 이용중인 미니 필터들을 볼 수 있다.
Frame 0가 모든 Altitude 영역인 0 ~ 429998.99까지 모두 사용하는 것을 볼 수 있다.


2. Frame 0, Frame 1 두 개인 상황
프레임이 두개 보이는 경우가 있는데 이 때 필터 드라이버 디바이스 스택은 다음과 같다.

kd> !devstack 85900c08  
  !DevObj   !DrvObj            !DevExt   ObjectName
  86b786e0  \Driver\AhnFlt2K   86b78798  
  85a19ab8  \FileSystem\FltMgr 85a19b70   ===> Frame 1
> 85900c08  \Driver\AFPAnsi    85900cc0  
  85930648  \FileSystem\FltMgr 85930700  ===> Frame 0  
  85963020  \FileSystem\Ntfs   859630d8  

AFPAnsi의 동작 조건을 수정했더니 이렇게 변한 것이다.
현재 AFPAnsi는 Group을 삭제해서 지정된 Group이 없는 상태다.
그랬더니 Frame 0과 Frame 1 사이에 위치하게 되었다.

필터 매니저는 부팅시에 올라오는 Legacy 필터 드라이버는 비교적 아래쪽의 Altitude를 부여한다.
부팅시에 올라오는 필터 드라이버라서 다른 미니 필터보다 우선 순위를 높여주려는 목적으로 보인다.
(이 부분은 좀 더 확인이 필요한 부분이다.)

프레임 정보를 확인해 보면 Frame 0와 Frame 1이 어떻게 구성되어 있는지 알 수 있다.

kd> !fltkd.frames

Frame List: 8743a9f8 
   FLTP_FRAME: 859dacc0 "Frame 1" "49999 to 326020"
      FLT_FILTER: 859b66d8 "V3Flt2K" "326020"
         FLT_INSTANCE: 85a6ad80 "V3Flt2K Instance" "326020"
         FLT_INSTANCE: 85a658a8 "V3Flt2K Instance" "326020"
      FLT_FILTER: 86852ac0 "luafv" "135000"
         FLT_INSTANCE: 86856008 "luafv" "135000"
 
   FLTP_FRAME: 8531d6c8 "Frame 0" "0 to 49999"
      FLT_FILTER: 8533b438 "FileInfo" "45000"
         FLT_INSTANCE: 858f5118 "FileInfo" "45000"
         FLT_INSTANCE: 85976dc8 "FileInfo" "45000"

Frame 0는 Altitude 0 ~ 49999까지 Frame 0가 사용하면서 Legacy보다도 아래쪽에 위치해야 하는 FileInfo 미니 필터의 자리를 확보해 준다.

AFPAnsi는 부팅시 로드되는 Legacy로 처리되어 바로 이 위에 붙었다.

Frame 1은 Lagacy 위에 붙어서 일반적인 미니 필터들을 위해 49999 부터 현재 가장 높은 altitude인 V3Flt2k의 Altitude인 326020까지 자리를 확보해 준다.

나중에 올라온 AhnFlt2k는 이 위에 붙었다.


3. 여기서 미니 필터가 추가로 로드되는 경우

a) 326020 이하의 Altitude를 가지는 미니 필터가 로드되면 Frame 1 안에서 동작할 것이다.
b) 326021 이상의 Altitude를 가지는 미니 필터가 로드되면 Frame 2 가 생성되고 여기서 동작할 것이다.

이 부분은 아쉽게도 아직 확인해 보지 않았다. ^^

반응형
Posted by GreeMate
Windows OS2011. 9. 26. 20:58
반응형
드라이버가 언로드되지 않는 현상이 발생해 원인을 찾아보다가 발견한 OS 버그다.
Vista와 Windows 7 커널에 존재하는 버그고 확인해 보니 Windows 8 커널에서는 수정되었다.

어떤 볼륨이 NTFS로 포맷되어 있는 경우 마운트되면서 커널이 볼륨 디바이스의 레퍼런스 카운트를 하나 올리고 내리지 않아서 발생하는 문제다. 레퍼런스 카운트가 남아 있으므로 드라이버가 내려가지 않는다. -_-;;;

NTFS 파일시스템 관련 초기화를 하는 중에 TransactionManager를 생성하는데 여기에 버그가 존재한다.
(Vista부터 Transactional NTFS(Txf)가 도입되었는데 이와 관련된 초기화 작업으로 보인다.)

문제의 원인이 되는 콜스택은 아래와 같다.
마운트가 완료된 직후 볼륨을 초기화하는 과정이다. 


nt!ObfReferenceObject+0x24 (FPO: [0,0,0])
nt!IoGetAttachedDeviceReference+0x23
nt!TmpIsClusteredTransactionManager+0x29  ; IoGetAttachedDeviceReference()를 
                                                                      ; 호출하고 dereference하지 않는 버그가 있다
nt!TmpCreateLogFile+0x18d
nt!TmpCreateOrOpenLogTransactionManager+0x34
nt!TmInitializeTransactionManager+0x1e1
nt!NtCreateTransactionManager+0x155
nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 8831b9e8)
nt!ZwCreateTransactionManager+0x11 (FPO: [6,0,0])
Ntfs!TxfCreateTmInstance+0x9d (FPO: [Non-Fpo])
Ntfs!TxfStartRm+0xceb (FPO: [Non-Fpo])
Ntfs!TxfInitializeVolume+0x688 (FPO: [Non-Fpo])
Ntfs!NtfsCommonFileSystemControl+0x99 (FPO: [Non-Fpo])
Ntfs!NtfsFspDispatch+0x264 (FPO: [Non-Fpo])
nt!ExpWorkerThread+0xfd
nt!PspSystemThreadStartup+0x9d
nt!KiThreadStartup+0x16



nt!TmpIsClusteredTransactionManager 함수의 내용을 들여다 보면 IoGetAttachedDeviceReference를 호출하고 나서 ObDereferenceObject를 호출하지 않는 것을 확인할 수 있다.

kd> uf nt!TmpIsClusteredTransactionManager

nt!TmpIsClusteredTransactionManager:

82d9e5d3 8bff            mov     edi,edi

82d9e5d5 55              push    ebp

82d9e5d6 8bec            mov     ebp,esp

82d9e5d8 83ec20          sub     esp,20h


...


82d9e5f1 8b4508          mov     eax,dword ptr [ebp+8]

82d9e5f4 ff7004          push    dword ptr [eax+4]

82d9e5f7 e8bac9f1ff      call    nt!IoGetAttachedDeviceReference (82cbafb6)


...


nt!TmpIsClusteredTransactionManager+0x5d:

82d9e630 8bd0            mov     edx,eax

82d9e632 8bce            mov     ecx,esi

82d9e634 e81fdee9ff      call    nt!IofCallDriver (82c3c458)


...


nt!TmpIsClusteredTransactionManager+0xbd:

82d9e690 5e              pop     esi

82d9e691 5b              pop     ebx

82d9e692 c9              leave

82d9e693 c20800          ret     8


함수가 끝나도 ObDereferenceObject를 호출하는 부분은 없다.

이 함수가 어떻게 볼륨 디바이스에 영향을 미치는지 이해하기 위해 볼륨 디바이스 스택을 확인해 본다.
일반적인 볼륨 디바이스 스택은 아래와 같다. 


kd> !devstack 8514d400
  !DevObj   !DrvObj            !DevExt   ObjectName
  852c6020  \Driver\volsnap    852c60d8  
  852bf020  \Driver\Ecache     852bf0d8  
> 8514d400  \Driver\volmgr     8514d4b8  HarddiskVolume1


레퍼런스 카운트가 올라간 디바이스 오브젝트는 HarddiskVolume1의 854d400일까?
아니다.

HarddiskVolume1에 대한 마운트가 일어나면서 이 녀석에 대해 Ntfs!TxfInitializeVolume가 발생하지만 nt!TmpIsClusteredTransactionManager가  IoGetAttachedDeviceReference를 호출하기 때문에 최상위 스택의 852c6020의 레퍼런스 카운트가 올라간다.

따라서 HarddiskVolume1 디바이스 오브젝트는 잘 사라지고 \Driver\volmgr도 잘 언로드 될 수 있다.
하지만 디바이스 오브젝트 852c6020은 레퍼런스 카운트가 1 남기 때문에 Delete해도 DELETE_PENDING으로 남아 있고 \Driver\volsnap도 영원히 내려가지 않는다.

OS에서 volsnap은 원래 내리지 않기 때문에 별다른 문제없이 잘 사용하고 있었나 보다.

혹시 자기만의 볼륨 디바이스를 만들다가 드라이버가 내려가지 않는 현상이 발생한다면...
nt!TmpIsClusteredTransactionManager에 의한 레퍼런스 카운트 문제가 아닌지 확인해 봐야 한다.

반응형
Posted by GreeMate
Windows OS2011. 9. 16. 22:30
반응형
ImagePath도 잘 설정되어 있는데 이런 일이 발생하는 난감한 경우가 있죠.

아래의 블로그에서 명쾌하게 설명해 주고 있네요.

http://thepassion.tistory.com/30 

감동받아 링크해 봅니다. ^^ 
반응형
Posted by GreeMate