BLOG main image
분류 전체보기 (17)
Life (2)
Dump Analysis (9)
Reversing (1)
Windows (1)
Book (2)
Reference (2)
Visitors up to today!
Today hit, Yesterday hit
daisy rss
tistory 티스토리 가입하기!
'WinDbg'에 해당되는 글 2건
2018. 7. 5. 22:20

이번 덤프는 문자열 처리 중 유효하지 않은 메모리 영역에 접근하여 발생한 BSOD다.

0x50은 BugCheck 코드명을 의미하며 BSOD 유형을 분류한 것이라고 생각하면 된다. 이 코드는 주로 접근할 수 없는 메모리 주소 영역을 접근했을 때 발생한다.

분석이 크게 어렵지는 않으니 가볍게 읽을 수 있을 것이다.

항상 분석의 시작은 !analyze -v 로 시작하는 것이 정석이다.

kd> !analyze -v

*******************************************************************************

*      *

*      Bugcheck Analysis   *

*      *

*******************************************************************************

PAGE_FAULT_IN_NONPAGED_AREA (50)

Invalid system memory was referenced.  This cannot be protected by try-except.

Typically the address is just plain bad or it is pointing at freed memory.

Arguments:

Arg1: a0f6c000, memory referenced.

Arg2: 00000000, value 0 = read operation, 1 = write operation.

Arg3: 92140037, If non-zero, the instruction address which referenced the bad memory

       address.

Arg4: 00000000, (reserved)

 

Debugging Details:

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

DUMP_CLASS: 1

DUMP_QUALIFIER: 401

BUILD_VERSION_STRING:  7601.17514.x86fre.win7sp1_rtm.101119-1850

SYSTEM_MANUFACTURER:  VMware, Inc.

VIRTUAL_MACHINE:  VMware

SYSTEM_PRODUCT_NAME:  VMware Virtual Platform

SYSTEM_VERSION:  None

BIOS_VENDOR:  Phoenix Technologies LTD

BIOS_VERSION:  6.00

BIOS_DATE:  07/31/2013

BASEBOARD_MANUFACTURER:  Intel Corporation

BASEBOARD_PRODUCT:  440BX Desktop Reference Platform

BASEBOARD_VERSION:  None

DUMP_TYPE:  1

BUGCHECK_P1: ffffffffa0f6c000

BUGCHECK_P2: 0

BUGCHECK_P3: ffffffff92140037

BUGCHECK_P4: 0

READ_ADDRESS:  a0f6c000 Paged pool

FAULTING_IP:

MyDrv+1037

92140037 0fb708          movzx   ecx,word ptr [eax]

MM_INTERNAL_CODE:  0

IMAGE_NAME:  MyDrv.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  5a1cdd5a

MODULE_NAME: MyDrv

FAULTING_MODULE: 9213f000 MyDrv

CPU_COUNT: 1

CPU_MHZ: e07

CPU_VENDOR:  GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 3c

CPU_STEPPING: 3

CPU_MICROCODE: 6,3c,3,0 (F,M,S,R)  SIG: 19'00000000 (cache) 19'00000000 (init)

DEFAULT_BUCKET_ID:  WIN7_DRIVER_FAULT

BUGCHECK_STR:  0x50

PROCESS_NAME:  explorer.exe

CURRENT_IRQL:  2

ANALYSIS_SESSION_HOST:  PAUL-PC

ANALYSIS_SESSION_TIME:  11-28-2017 14:40:07.0617

ANALYSIS_VERSION: 10.0.10575.567 amd64fre

 

TRAP_FRAME:  8b4a8a90 -- (.trap 0xffffffff8b4a8a90)

ErrCode = 00000000

eax=a0f6c000 ebx=a0e78680 ecx=0000006c edx=a0f6c000 esi=846c5838 edi=8b4a8b5c

eip=92140037 esp=8b4a8b04 ebp=8b4a8b0c iopl=0         nv up ei pl nz ac po nc

cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010212

MyDrv+0x1037:

92140037 0fb708          movzx   ecx,word ptr [eax]       ds:0023:a0f6c000=????

Resetting default scope

 

LAST_CONTROL_TRANSFER:  from 82a783d8 to 82ac541b

 

STACK_TEXT: 

8b4a8a78 82a783d8 00000000 a0f6c000 00000000 nt!MmAccessFault+0x106

8b4a8a78 92140037 00000000 a0f6c000 00000000 nt!KiTrap0E+0xdc

8b4a8b0c 82ca639c 84653218 00000564 8b4a8b5c MyDrv+0x1037

8b4a8b34 82c8e27f 84653218 00000564 82b79b84 nt!PsCallImageNotifyRoutines+0x62

8b4a8be8 82c7ed4a 84664d80 85eb6030 8b4a8ce4 nt!MiMapViewOfImageSection+0x670

8b4a8c58 82c7ee3a 85eb6030 8b4a8ce4 00000000 nt!MiMapViewOfSection+0x22e

8b4a8c88 82c7f599 9fcdb1e8 85eb6030 8b4a8ce4 nt!MmMapViewOfSection+0x2a

8b4a8d04 82a751ea 00001458 ffffffff 0cccee94 nt!NtMapViewOfSection+0x204

8b4a8d04 771d70b4 00001458 ffffffff 0cccee94 nt!KiFastCallEntry+0x12a

0cccefa0 00000000 00000000 00000000 00000000 0x771d70b4

 

STACK_COMMAND:  kb

THREAD_SHA1_HASH_MOD_FUNC:  53ccc111e8062d929cc0fad19df6ef4e0edf76dd

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  23675873345d32020fde61dc27c04558a8fd2339

THREAD_SHA1_HASH_MOD:  4e5c1c39aac5cd40526a2d8509134f551a034639

 

FOLLOWUP_IP:

MyDrv+1037

92140037 0fb708          movzx   ecx,word ptr [eax]

 

FAULT_INSTR_CODE:  8508b70f

SYMBOL_STACK_INDEX:  2

SYMBOL_NAME:  MyDrv+1037

FOLLOWUP_NAME:  MachineOwner

FAILURE_BUCKET_ID:  0x50_MyDrv+1037

BUCKET_ID:  0x50_MyDrv+1037

PRIMARY_PROBLEM_CLASS:  0x50_MyDrv+1037

TARGET_TIME:  2017-11-28T05:34:28.000Z

OSBUILD:  7601

OSSERVICEPACK:  1000

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

SUITE_MASK:  272

PRODUCT_TYPE:  1

OSPLATFORM_TYPE:  x86

OSNAME:  Windows 7

OSEDITION:  Windows 7 WinNt (Service Pack 1) TerminalServer SingleUserTS

OS_LOCALE: 

USER_LCID:  0

OSBUILD_TIMESTAMP:  2010-11-20 17:42:49

BUILDDATESTAMP_STR:  101119-1850

BUILDLAB_STR:  win7sp1_rtm

BUILDOSVER_STR:  6.1.7601.17514.x86fre.win7sp1_rtm.101119-1850

ANALYSIS_SESSION_ELAPSED_TIME: 766

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:0x50_mydrv+1037

FAILURE_ID_HASH:  {348be9c7-8048-f84c-6228-bb7942eb0452}

Followup:     MachineOwner

---------


.trap 명령을 사용해서 문제가 발생한 부분으로 컨텍스트 정보를 설정해보자.

kd> .trap 0xffffffff8b4a8a90

ErrCode = 00000000

eax=a0f6c000 ebx=a0e78680 ecx=0000006c edx=a0f6c000 esi=846c5838 edi=8b4a8b5c

eip=92140037 esp=8b4a8b04 ebp=8b4a8b0c iopl=0         nv up ei pl nz ac po nc

cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010212

MyDrv+0x1037:

92140037 0fb708          movzx   ecx,word ptr [eax]       ds:0023:a0f6c000=????


eax가 가리키는 a0f6c000 메모리 영역이 접근할 수 없는 영역이라 BSOD가 발생했다.

kv 명령어로 파라미터를 포함한 콜 스택을 확인해보자.

kd> kv

  *** Stack trace for last set context - .thread/.cxr resets it

 # ChildEBP RetAddr  Args to Child             

00 8b4a8b0c 82ca639c 84653218 00000564 8b4a8b5c MyDrv+0x1037

01 8b4a8b34 82c8e27f 84653218 00000564 82b79b84 nt!PsCallImageNotifyRoutines+0x62

02 8b4a8be8 82c7ed4a 84664d80 85eb6030 8b4a8ce4 nt!MiMapViewOfImageSection+0x670

03 8b4a8c58 82c7ee3a 85eb6030 8b4a8ce4 00000000 nt!MiMapViewOfSection+0x22e

04 8b4a8c88 82c7f599 9fcdb1e8 85eb6030 8b4a8ce4 nt!MmMapViewOfSection+0x2a

05 8b4a8d04 82a751ea 00001458 ffffffff 0cccee94 nt!NtMapViewOfSection+0x204

06 8b4a8d04 771d70b4 00001458 ffffffff 0cccee94 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 8b4a8d34)

07 0cccefa0 00000000 00000000 00000000 00000000 0x771d70b4


nt 커널에서 콜백 함수로 호출해주는 MyDrv 모듈의 MyDrv+0x1037 함수에서 문제가 발생했다.

이 콜백은 PsSetLoadImageNotifyRoutine을 통해 미리 등록하면 exe dll 등의 파일이 로드될 때 해당 모듈의 정보를 받을 수 있는 함수다.

MyDrv 모듈 심볼은 없지만 콜 스택에서 PsSetLoadImageNotifyRoutine 함수 다음에 호출된 함수이므로 해당 콜백임을 알 수 있다.

나는 이 콜백을 편의상 LoadImageNotifyRoutine이라고 부른다.

다행히 BSOD가 발생한 위치도 명확하고 콜 스택도 단순한 편이라 마지막 MyDrv+0x1037 부분만 분석하면 답을 찾을 수 있겠다..

다음은 BSOD가 발생한 MyDrv+0x1037 부분이 포함되도록 함수 시작부터 디스어셈블링한 내용이다.

kd> u 92140010 L19

MyDrv+0x1010:

92140010 8bff            mov     edi,edi

92140012 55              push    ebp

92140013 8bec            mov     ebp,esp

92140015 83ec08          sub     esp,8

92140018 0fb60520201492  movzx   eax,byte ptr [MyDrv+0x3020 (92142020)]

9214001f 83f801          cmp     eax,1

92140022 752e            jne     MyDrv+0x1052 (92140052)

92140024 8b4d08          mov     ecx,dword ptr [ebp+8]  // 1) 번째 파라미터 ecx 저장(ecx = ebp + 8)

92140027 8b5104          mov     edx,dword ptr [ecx+4]  // 2) 번째 파라미터의 +4 위치 edx 저장(edx = ecx + 4)

9214002a 8955fc          mov     dword ptr [ebp-4],edx

9214002d c745f800000000  mov     dword ptr [ebp-8],0

92140034 8b45fc          mov     eax,dword ptr [ebp-4]  // 3) 루프 시작

92140037 0fb708          movzx   ecx,word ptr [eax]     // 4) 2바이트 읽음, BSOD 발생 위치

9214003a 85c9            test    ecx,ecx                // 5) 값이 NULL 이면 루프 종료

9214003c 7414            je      MyDrv+0x1052 (92140052)

9214003e 8b55fc          mov     edx,dword ptr [ebp-4]

92140041 83c202          add     edx,2                  // 6) 다음 2바이트로 이동

92140044 8955fc          mov     dword ptr [ebp-4],edx

92140047 8b45f8          mov     eax,dword ptr [ebp-8]

9214004a 83c001          add     eax,1

9214004d 8945f8          mov     dword ptr [ebp-8],eax

92140050 ebe2            jmp     MyDrv+0x1034 (92140034) // 7) NULL 아니면 3)번으로 이동해서 다음 2바이트 읽기

92140052 8be5            mov     esp,ebp

92140054 5d              pop     ebp

92140055 c20c00          ret     0Ch


MyDrv 모듈에서 nt 커널에서 전달 받은 첫 번째 파라미터로 뭔가 반복적인 처리를 하다 문제가 발생했다.

전달된 파라미터가 이상했거나 잘못된 방식으로 파라미터를 처리했을 가능성이 의심된다.

첫 번째 파라미터의 정체를 알기 위해 LoadImageNotifyRoutine 콜백(MyDrv+0x1037)의 함수 원형을 MSDN에서 찾아보자.

우선 콜백을 등록하는 PsSetLoadImageNotifyRoutine 함수를 먼저 살펴보자.

NTSTATUS PsSetLoadImageNotifyRoutine(

  _In_ PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine

);


PLOAD_IMAGE_NOTIFY_ROUTINE 함수 포인터를 전달해주면 나중에 DLL 등의 파일이 커널로 로드될 때 이 함수 포인터가 콜백으로 불리는 방식이다.

이제 PLOAD_IMAGE_NOTIFY_ROUTINE을 살펴보자.

PLOAD_IMAGE_NOTIFY_ROUTINE SetLoadImageNotifyRoutine;

 

void SetLoadImageNotifyRoutine(

  _In_opt_ PUNICODE_STRING FullImageName,

  _In_     HANDLE          ProcessId,

  _In_     PIMAGE_INFO     ImageInfo

);



아하, 첫 번째 파라미터는 커널과 드라이버에서 자주 사용하는 문자열 형식인 UNICODE_STRING 구조체의 포인터다. MSDN을 읽어보면 로드되는 이미지의 전체 경로를 UNICODE_STRING 형태로 알려준다.

이제 파라미터의 정체를 알았으니 dt 명령으로 UNICODE_STRING 구조체의 내용을 살펴보자.

kd> dt _UNICODE_STRING

nt!_UNICODE_STRING

   +0x000 Length           : Uint2B

   +0x002 MaximumLength    : Uint2B

   +0x004 Buffer           : Ptr32 Uint2B


4바이트는 문자열의 크기와 문자열 버퍼의 최대 크기로 바이트 단위다. 다음 4바이트는 문자열 버퍼의 포인터다.

구조체를 보니 앞서 함수 앞 부분에서 수행했던 연산들의 의미를 알 것 같다. 2)번 이후의 연산들은 결국 UNICODE_STRING 구조체의 +4 위치에 있는 Buffer를 가지고 와서 NULL을 만날 때까지 2바이트 씩 읽는 동작이다.

2바이트 씩 읽은 이유는 UNICODE_STRING Buffer WCHAR의 포인터인 PWSTR 자료형이기 때문이다.

앞서 1)번에서 ebp+8이 첫 번째 파라미터인 것을 확인했으니 동일하게 dt 명령으로 확인해보자.

kd> dt nt!_UNICODE_STRING poi(ebp+8)

 "\Windows\assembly\GAC_MSIL\Microsoft.Web.Delegation.resources\7.1.0.0_it_31bf3856ad364e35\Microsoft.Web.Delegation.resources.dll"

   +0x000 Length           : 0x100

   +0x002 MaximumLength    : 0x100

   +0x004 Buffer           : 0xa0f6bf00  "\Windows\assembly\GAC_MSIL\Microsoft.Web.Delegation.resources\7.1.0.0_it_31bf3856ad364e35\Microsoft.Web.Delegation.resources.dll"


ebp+8 위치의 값이 UNICODE_STRING 포인터이므로 입력된 주소의 값을 가져오는 poi 명령을 사용했다.

poi(x)는 x라는 메모리 주소의 내용을 읽는 구문이다.

C 언어에서 포인터의 값을 읽을 때

value = *x;

로 표현하는데

value = poi(x)

라고 생각하면 쉽다.


Buffer 필드에 "\Windows\assembly\GAC_MSIL\Microsoft.Web.Delegation.resources\7.1.0.0_it_31bf3856ad364e35\Microsoft.Web.Delegation.resources.dll" 문자열이 보이고, Length, MaximumLength는 0x100이.

문자열을 직접 세보니 128자였다. Length 0x100이고 바이트 크기니 0x100 10진수인 256 WCHAR 크기인 2로 나누면 정확하게 128이 나온다.

MaximumLength Buffer a0f6bf00의 최대 크기로 접근 가능한 영역을 의미한다. Length와 크거나 같으면 되므로 정상이다.

 

입력된 파라미터에는 이상한 부분이 보이지 않는데 왜 문제가 발생했을까?

MyDrv 모듈에서는 Buffer a0f6bf00 값을 직접 접근해서 NULL을 만날 때까지 2바이트 씩 증가하는 루프를 수행했다. 아무래도 이 동작에 문제가 있을 것 같다.

db 명령으로 a0f6bf00 내용을 확인해보자.


kd> db a0f6bf00 L110

a0f6bf00  5c 00 57 00 69 00 6e 00-64 00 6f 00 77 00 73 00  \.W.i.n.d.o.w.s.

a0f6bf10  5c 00 61 00 73 00 73 00-65 00 6d 00 62 00 6c 00  \.a.s.s.e.m.b.l.

a0f6bf20  79 00 5c 00 47 00 41 00-43 00 5f 00 4d 00 53 00  y.\.G.A.C._.M.S.

a0f6bf30  49 00 4c 00 5c 00 4d 00-69 00 63 00 72 00 6f 00  I.L.\.M.i.c.r.o.

a0f6bf40  73 00 6f 00 66 00 74 00-2e 00 57 00 65 00 62 00  s.o.f.t...W.e.b.

a0f6bf50  2e 00 44 00 65 00 6c 00-65 00 67 00 61 00 74 00  ..D.e.l.e.g.a.t.

a0f6bf60  69 00 6f 00 6e 00 2e 00-72 00 65 00 73 00 6f 00  i.o.n...r.e.s.o.

a0f6bf70  75 00 72 00 63 00 65 00-73 00 5c 00 37 00 2e 00  u.r.c.e.s.\.7...

a0f6bf80  31 00 2e 00 30 00 2e 00-30 00 5f 00 69 00 74 00  1...0...0._.i.t.

a0f6bf90  5f 00 33 00 31 00 62 00-66 00 33 00 38 00 35 00  _.3.1.b.f.3.8.5.

a0f6bfa0  36 00 61 00 64 00 33 00-36 00 34 00 65 00 33 00  6.a.d.3.6.4.e.3.

a0f6bfb0  35 00 5c 00 4d 00 69 00-63 00 72 00 6f 00 73 00  5.\.M.i.c.r.o.s.

a0f6bfc0  6f 00 66 00 74 00 2e 00-57 00 65 00 62 00 2e 00  o.f.t...W.e.b...

a0f6bfd0  44 00 65 00 6c 00 65 00-67 00 61 00 74 00 69 00  D.e.l.e.g.a.t.i.

a0f6bfe0  6f 00 6e 00 2e 00 72 00-65 00 73 00 6f 00 75 00  o.n...r.e.s.o.u.

a0f6bff0  72 00 63 00 65 00 73 00-2e 00 64 00 6c 00 6c 00  r.c.e.s...d.l.l.

a0f6c000  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????


이런! a0f6bf00 위치에 접근할 때 BSOD가 발생했는데 메모리 내용을 보니 문자열 끝에 NULL(0x00) 값이 존재하지 않는다. a0f6c000부터는 페이지 아웃되어 접근할 수 없는 영역이다.

문자열 버퍼인 a0f6bf00 MaximumLength 크기인 a0f6bfff까지만 접근 가능한데, 이 영역을 넘어 a0f6c000까지 접근하다 BSOD가 발생한 것이다.

MyDrv에서 UNICODE_STRING의 특징을 잘 모르고 사용한 것이 문제의 원인이다.

MSDN을 찾아보면 만약 Buffer 필드의 문자열이 NULL 문자로 끝날 경우 Length 필드의 길이는 NULL 문자를 포함하지 않는다고 적혀있다. UNICODE_STRING Buffer 문자열이 NULL 로 끝나지 않을 수 있음을 암시하고 있는 것이다.


Remarks : If the string is null-terminated, Length does not include the trailing null character.



MyDrv는 아마 다음과 같은 함수를 구현해서 사용하다 BSOD를 발생시켰을 것이다.

다음에 소개하는 GetLength, TestFunc1, TestFunc2 함수는 이해를 돕기 위해 내가 작성해 본 의사 코드(Pseudo Code). GetLength 함수는 문자열의 길이를 구하는 함수고, TestFunc1, TestFunc2 함수는 GetLength 함수를 사용하는 함수를 의미한다.


ULONG

GetLength(PWSTR pImagePath)

{

       ULONG ulLength = 0;

 

       while (*pImagePath)

       {

             pImagePath ++;

             ulLength ++;

       }

 

       return ulLength;

}

 

VOID

TestFunc1(PWSTR pImagePath)

{

       ulCount = GetLength(pImagePath);

}

 

VOID

TestFunc2(PUNICODE_STRING pImagePath)

{

       ulCount = GetLength(pImagePath->Buffer);

}


GetLength 함수는 단순하게 문자열 길이를 구하는 함수인데 입력된 문자열에서 NULL 값을 만날 때까지 포인터를 하나씩 증가시키며 문자열 길이를 센다. 얼핏 보기에는 문제가 없어 보이지만 이 함수에는 심각한 문제가 숨어 있다. 바로 문자열이 항상 NULL로 끝난다고 가정하는 것이다.

문제를 단순화하기 위해 TestFunc1, TestFunc2 함수에 전달되는 pImagePath 파라미터는 항상 유효하다고 가정해보자.

TestFunc1 함수는 언제나 잘 동작할 것이다. 하지만 TestFunc2 함수는 BSOD가 발생할 가능성이 있다.

왜냐하면 UNICODE_STRING Buffer 문자열은 NULL로 끝나지 않을 수 있기 때문이다!

 

절대로 UNICODE_STRING Buffer pusImagePath->Buffer 형태로 직접 사용하면 안된다.

문자열 길이를 구하기 위함이라면 단순히 UNICODE_STRING Length를 사용하면 된다. Buffer의 값을 사용할 때는 반드시 Length 필드를 확인해서 유효한 메모리 영역만 접근해야 한다.

 

과거 코드 리뷰 중 다음과 같은 코드를 본 기억이 난다.


SomeFunc(…)

{

       PWSTR pszImagePath;

 

       // 절대로 된다!

       pszImagePath = pusImagePath->Buffer;

}


이런 코드를 본다면 머릿속에서 경고음이 마구 울려야 한다.

 

다시 한 번 말하지만 절대로 UNICODE_STRING의 문자열 버퍼가 NULL로 끝날 것이라 가정하지 마라. 그리고 UNICODE_STRING은 가급적 가공하지 말고 그대로 사용하도록 하자.

RtlUnicodeStringxxx로 시작하는 API를 사용하는 편이 좋고, 만약 Buffer 필드를 직접 사용해야 한다면 반드시 Length 필드를 함께 확인하는 습관을 들이자.

안타깝게도 개발을 하다보면 이런 주의 사항을 알고도 잊어 버리는 경우가 종종 있다.

보통 문자열을 다룰 때 많은 버그가 발생하게 되는데 UNICODE_STRING을 다룰 때는 좀 더 긴장하는 편이 좋다.

가장 좋은 방법은 문자열을 다룰 때 RtlStringCbxxx, RtlStringCchxxx 계열의 안전한 문자열 API를 사용하는 것이다.

이런 API가 사용된 코드는 문자열 관련 버그나 버퍼 오버플로우 같은 문제가 쉽게 발생하지 않는다.



'Dump Analysis' 카테고리의 다른 글

[0xC5] 풀 헤더 손상  (0) 2018.07.16
[0x1A] 페이지 손상  (0) 2018.07.12
[0x50] 해제된 핸들  (0) 2018.07.09
[0x50] 숨겨진 콜 스택  (0) 2018.07.07
필터매니저(fltmgr.sys) 버그? DRAINING ZOMBIED 와 DOE_UNLOAD_PENDING  (0) 2015.01.22
2015. 1. 22. 23:57

아주 흥미로운 덤프를 분석하게 되서 공유한다.


거의 분석 불가능한 덤프였지만 다행히 분석이 완료되었다.

MyDrv.sys 를 언로드하려고 했지만 알 수 없는 이유로 실패한 상태로 메모리에 남아 있는 이슈여서 강제로 덤프를 생성하여 분석하게 된 Case 다.


참고 : 드라이버 로드시 ERROR_ALREADY_EXISTS(183) 에러



1. 일단 필터매니저에 등록된 필터 정보를 확인해 본다.

kd> !fltkd.frames

Frame List: fffff880013302c0
    FLTP_FRAME: fffffa80039e5010 "Frame 1" "429998.99 to 429999.280700"
    FLT_FILTER: fffffa8006dcc960 "SomeDrv" "[NoName]"
    FLTP_FRAME: fffffa8001b2aac0 "Frame 0" "0 to 429998.99"   
    FLT_FILTER: fffffa800290d900 "MyDrv" "326020"
    FLT_INSTANCE: fffffa80031b4330 "MyDrv Instance" "326020"
    FLT_INSTANCE: fffffa800699d170 "MyDrv Instance" "326020"
    FLT_INSTANCE: fffffa8003658b40 "MyDrv Instance" "326020"



어라, Frame 1 의 SomeDrv.sys 모듈의 상태가 이상하다.
Filter Instance 정보는 없고 Filter Object 정보만 남아 있다.


2. 일단 언로드되지 못하고 있는 불쌍한 MyDrv.sys 를 먼저 확인해 보자.

kd> dt _FLT_FILTER fffffa800290d900
fltmgr!_FLT_FILTER
    +0x000 Base : _FLT_OBJECT
    +0x020 Frame : 0xfffffa80`01b2aac0 _FLTP_FRAME   
    +0x028 Name : _UNICODE_STRING "MyDrv"
    +0x038 DefaultAltitude : _UNICODE_STRING "326020"
    +0x048 Flags : 2 ( FLTFL_FILTERING_INITIATED )
    +0x050 DriverObject : 0xfffffa80`03673c80 _DRIVER_OBJECT
    ... ...


kd> !drvobj 0xfffffa80`03673c80
Driver object (fffffa8003673c80) is for:
    \Driver\MyDrv
Driver Extension List: (id , addr)

Device Object list:
fffffa8002e8f550


kd> !devobj fffffa8002e8f550
Device object (fffffa8002e8f550) is for:
    MyDrv \Driver\MyDrv DriverObject fffffa8003673c80
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00000040
Dacl fffff9a1000846e1 DevExt 00000000 DevObjExt fffffa8002e8f6a0
ExtensionFlags (0x00000801) DOE_UNLOAD_PENDING, DOE_DEFAULT_SD_PRESENT
Characteristics (0000000000)
Device queue is not busy.



역시 언로드되지 못하고 언로드 중인 상태로 남아 있다.
일단 내가 제일 의심스러워 MyDrv.sys 의 언로드 루틴을 검토했지만 버그로 보일만한 부분은 발견되지 않았다.


3. 이제 이상했던 SomeDrv.sys 모듈의 Filter Object 정보를 확인해보자.

kd> !fltkd.filter fffffa8006dcc960

FLT_FILTER: fffffa8006dcc960 "SomeDrv" "[NoName]"
    FLT_OBJECT: fffffa8006dcc960 [02000003] Filter DRAINING ZOMBIED

        RundownRef : 0x0000000000000001 (0) drained
        PointerCount : 0x00000001
        PrimaryLink : [fffffa800b400020-fffffa80039e50c0]
    Frame : fffffa80039e5010 "Frame 1"
    Flags : [00000002] FilteringInitiated
    DriverObject : 0xfffffa8003673c80
    FilterLink : [fffffa800b400020-fffffa80039e50c0]
    PreVolumeMount : 0000000000000000 (null)
    PostVolumeMount : 0000000000000000 (null)
    ... ...



Filte Object 가 "DRAINING ZOMBIED" 상태로 확인된다.
참고로 "DRAINING ZOMBIED" 플래그는 필터매니저가 언로드 과정 중에 내부적으로 설정하는 상태 값이다.
즉, 언로드 과정에서 이 플래그가 설정되는 것 자체는 정상적인 동작이다.


참고로, 미니필터에서 필터매니저에 Context 를 할당(FltAllocateContext )하거나 사용(FltSetStreamContext)하고 해제(FltReleaseContext)하지 않는 경우 필터매니저가 내부적으로 관리하는 ReferenceCount 가 맞지 않게 되어 언로드시 해당 미니필터의 Filter Object 가 "DRAINED" 상태로 언로드에 실패할 수 있다.
(언로드가 완료되지 못한 상태로 메모리에 남아 있게 된다)


다시 본론으로 돌아와서, 문제는 덤프 상의 SomeDrv.sys 는 이미 언로드되어 버린(메모리 상에 없는) 녀석인데도 "DRAINING ZOMBIED" 상태인 Filter Object 가 여전히 남아 있다는 것이다.
(언로드가 완료되면 Filter Object 는 제거되고, Filter Frame 에 더 이상 등록된 Filter 가 없으면 해당 Frame 도 제거되야 한다)

이미 SomeDrv.sys 는 언로드되어 메모리 상에서 없어졌기 때문에 이쯤되면 분석이 거의 불가능한 상태다.

SomeDrv.sys 가 "DRAINING ZOMBIED" 상태인 것과 MyDrv.sys 의 Unload Pending 증상 사이에 어떤 연관성이 있을까?

다행히 다른 시스템에서 MyDrv.sys 가 또 Unload Pending 에 빠진 덤프를 얻을 수 있었다.
SomeDrv.sys 도 있고 SomeDrv.sys 역시 동일하게 "DRAINING ZOMBIED" 상태에 빠져있다.


이쯤되니 다시 덤프를 꺼내 MyDrv.sys, SomeDrv.sys, 필터매니저(fltmgr.sys), 심지어 nt 커널까지 모든 걸 의심의 눈초리로 바라보며 다음과 같은 가설을 세워보기로 했다.


1) SomeDrv.sys 의 버그로 언로드시 "DRAINING ZOMBIED" 상태가 됨

2) SomeDrv.sys 가 "DRAINING ZOMBIED" 가 되는 과정에서 필터매니저나 nt 커널의 데이터가 손상됨

3) 데이터 손상으로 인한 필터매니저나 nt 커널의 오동작으로 MyDrv.sys 가 언로드되지 못함
 


음... 입증하기가 만만치 않아 보인다.
먼저 StopService() 호출 후 MyDrv.sys 의 언로드 루틴이 호출되기까지 언로드 과정을 분석해보니 다음과 같은 콜 스택으로 진행되었다.


08 MyDrv!DriverUnload
07 fltmgr!FltUnregisterFilter
06 MyDrv!MiniFilterUnload
05 fltmgr!FltpDoUnloadFilter
04 fltmgr!FltpMiniFilterDriverUnload
03 nt!IopLoadUnloadDriver                  (System)               
02 nt!IopUnloadDriver                         (Services.exe)
... ...
01 services!StopService



각 함수에서 하는 일을 정리해보면 다음과 같다.


1) 유저모드에서 MyDrv.sys 에 대해 StopService() 호출

2) nt 커널의 언로드 함수인 nt!IopUnloadDriver() 내부에서 언로드할 드라이버의 DeviceObject 에 "DOE_UNLOAD_PENDING"
플래그를 설정하고 WorkItem 으로 nt!IopLoadUnloadDriver() 호출
(이후 과정은 System Thread Context 에서 수행)


nt!IopUnloadDriver+0x37e (fffff800`01a79780):
    call to nt!IopCheckUnloadDriver (fffff800`0175c750)

kd> u nt!IopCheckUnloadDriver L25
nt!IopCheckUnloadDriver:
... ...
fffff800`0175c7bd 8bc3 mov eax,ebx
fffff800`0175c7bf eb49 jmp nt!IopCheckUnloadDriver+0xba (fffff800`0175c80a)
fffff800`0175c7c1 c60601 mov byte ptr [rsi],1
fffff800`0175c7c4 eb1c jmp nt!IopCheckUnloadDriver+0x92 (fffff800`0175c7e2)
fffff800`0175c7c6 488b8138010000 mov rax,qword ptr [rcx+138h]
fffff800`0175c7cd 83482001 or dword ptr [rax+20h],1                 DOE_UNLOAD_PENDING



3) nt!IopLoadUnloadDriver() 에서 필터매니저의 fltmgr!FltpMiniFilterDriverUnload() 호출

4) fltmgr!FltpMiniFilterDriverUnload() 에서 언로드할 드라이버를 찾아 fltmgr!FltpDoUnloadFilter() 호출

5) fltmgr!FltpDoUnloadFilter() 에서 필터매니저가 관리하는 Filter Object 에 "UnloadInProgress" 플래그를 설정하고 MyDrv.sys 의 언로드 함수인 MyDrv!MiniFilterUnload() 호출

kd> u fltmgr!FltpDoUnloadFilter L25
fltmgr!FltpDoUnloadFilter:
... ...
fffff880`01344d7f 41bc10001cc0 mov r12d,0C01C0010h
fffff880`01344d85 e9e7000000 jmp fltmgr!FltpDoUnloadFilter+0x161 (fffff880`01344e71)
fffff880`01344d8a 8bfa mov edi,edx
fffff880`01344d8c 83e701 and edi,1
fffff880`01344d8f 7404 je fltmgr!FltpDoUnloadFilter+0x85 (fffff880`01344d95)
fffff880`01344d91 83494801 or dword ptr [rcx+48h],1                 UnloadInProgress


6) MyDrv.sys 의 언로드 함수인 MyDrv!MiniFilterUnload() 에서 이후 언로드 작업 수행


이제 각 언로드 과정을 하나씩 검증해가며 문제가 없는지 분석해보자.

MyDrv.sys 의 DeviceObject 에 "DOE_UNLOAD_PENDING" 플래그는 이미 확인했으니 1)~2)번은 범인이 아니다.

6)번 과정인 MyDrv.sys 의 MyDrv!MiniFilterUnload() 함수는 MyDrv.sys 의 내부 언로드 관련 플래그를 확인해보니 아직 호출되지 않은 것으로 확인된다.

MyDrv.sys 도 범인이 아니고 가설대로면 nt 커널과 필터매니저 구간인 3)~5)번에 범인이 있을 것이다.


4. MyDrv.sys 의 Filter Object 상태를 확인해보자.

kd> !fltkd.filter fffffa800290d900

FLT_FILTER: fffffa800290d900 "MyDrv" "326020"
    FLT_OBJECT: fffffa800290d900 [02000000] Filter
        RundownRef : 0x0000000000000044 (34)
        PointerCount : 0x00000001
        PrimaryLink : [fffffa8004770020-fffffa8001cb5390]
    Frame : fffffa8001b2aac0 "Frame 0"
    Flags : [00000002] FilteringInitiated
        ... ...



응? Filter Object 에 "FilteringInitiated" 플래그만 있고 "UnloadInProgress" 플래그가 없다.
5)번 과정도 수행되지 않았다고 보는 것이 타당하다.
5)번 과정이 수행되었다면 Filter Object 의 플래그가 "FilteringInitiated UnloadInProgress" 로 변경되었을 것이다.

범인은 3)번이나 4)번일 가능성이 높다.

먼저 3)번의 nt!IopLoadUnloadDriver() 를 분석해보니 특별히 문제될만한 동작을 수행하는 함수가 아니었다.

그래서 4)번 fltmgr!FltpMiniFilterDriverUnload() 함수를 상세 분석해보기로 했다.


IDA 의 Hex-ray 없이 커널 함수를 디스어셈하여 분석하는건 굉장히 지루한 작업이지만 나에게 있어선 분석의 감을 잃지 않게 해주는 습관이기도 하다.


5. 마지막 남은 fltmgr!FltpMiniFilterDriverUnload() 함수를 분석해보자.

fltmgr!FltpMiniFilterDriverUnload:
fffff880`01344fb0 488bc4 mov rax,rsp
fffff880`01344fb3 48895808 mov qword ptr [rax+8],rbx
fffff880`01344fb7 48896810 mov qword ptr [rax+10h],rbp
fffff880`01344fbb 48897018 mov qword ptr [rax+18h],rsi
fffff880`01344fbf 48897820 mov qword ptr [rax+20h],rdi
fffff880`01344fc3 4155 push r13
fffff880`01344fc5 4883ec20 sub rsp,20h
fffff880`01344fc9 488be9 mov rbp,rcx ; rcx : DriverObject
fffff880`01344fcc ff154611feff call qword ptr [fltmgr!_imp_KeEnterCriticalRegion (fffff880`01326118)]
fffff880`01344fd2 488d0d7fb2feff lea rcx,[fltmgr!FltGlobals+0x58 (fffff880`01330258)]
                                                ; 0 FLT_FRAME.FilterUnloadLock (ERESOURCE)
fffff880`01344fd9 b201 mov dl,1
fffff880`01344fdb ff152f11feff call qword ptr [fltmgr!_imp_ExAcquireResourceSharedLite (fffff880`01326110)]
fffff880`01344fe1 488b3dd8b2feff mov rdi,qword ptr [fltmgr!FltGlobals+0xc0 (fffff880`013302c0)]
                                                ; 1 FLT_FRAME.Links
fffff880`01344fe8 4c8d2dd1b2feff lea r13,[fltmgr!FltGlobals+0xc0 (fffff880`013302c0)]
                                                ; 1 FLT_FRAME.Links->FLink (FLT_FRAME 0 .Links)
fffff880`01344fef eb43 jmp fltmgr!FltpMiniFilterDriverUnload+0x84 (fffff880`01345034)
fffff880`01344ff1 ff152111feff call qword ptr [fltmgr!_imp_KeEnterCriticalRegion (fffff880`01326118)]
fffff880`01344ff7 488d4f40 lea rcx,[rdi+40h]
                                                ; 1 FLT_FRAME.RegisteredFilters (ERESOURCE)
fffff880`01344ffb b201 mov dl,1
fffff880`01344ffd ff150d11feff call qword ptr [fltmgr!_imp_ExAcquireResourceSharedLite (fffff880`01326110)]
fffff880`01345003 4c8d9fa8000000 lea r11,[rdi+0A8h]
                                                ; 1 FLT_FRAME.RegisteredFilters.rList (FLT_FILTER.PrimaryLink)
fffff880`0134500a 498b03 mov rax,qword ptr [r11]
                                                ; FLT_FILTER.PrimaryLink.FLink (FLT_FILTER.PrimaryLink)
fffff880`0134500d eb0d jmp fltmgr!FltpMiniFilterDriverUnload+0x6c (fffff880`0134501c)
fffff880`0134500f 488d70f0 lea rsi,[rax-10h]                      ; FLT_FILTER
fffff880`01345013 48396e50 cmp qword ptr [rsi+50h],rbp    ; if( FLT_FILTER.DriverObject == DriverObject )
fffff880`01345017 744e je fltmgr!FltpMiniFilterDriverUnload+0xb7 (fffff880`01345067)        ; Success

fffff880`01345019 488b00 mov rax,qword ptr [rax]
fffff880`0134501c 493bc3 cmp rax,r11                             ; Filter List 끝인지 확인
fffff880`0134501f 75ee jne fltmgr!FltpMiniFilterDriverUnload+0x5f (fffff880`0134500f)
fffff880`01345021 488d4f40 lea rcx,[rdi+40h]                    ; 1 FLT_FRAME.RegisteredFilters (ERESOURCE)
fffff880`01345025 ff15fd10feff call qword ptr [fltmgr!_imp_ExReleaseResourceLite (fffff880`01326128)]
fffff880`0134502b ff15ef10feff call qword ptr [fltmgr!_imp_KeLeaveCriticalRegion (fffff880`01326120)]
fffff880`01345031 488b3f mov rdi,qword ptr [rdi]
fffff880`01345034 493bfd cmp rdi,r13                             ; Frame List 끝인지 확인
fffff880`01345037 75b8 jne fltmgr!FltpMiniFilterDriverUnload+0x41 (fffff880`01344ff1)
fffff880`01345039 488d0d18b2feff lea rcx,[fltmgr!FltGlobals+0x58 (fffff880`01330258)]
                                                                             ; 0 FLT_FRAME.FilterUnloadLock (ERESOURCE)
fffff880`01345040 ff15e210feff call qword ptr [fltmgr!_imp_ExReleaseResourceLite (fffff880`01326128)]
fffff880`01345046 ff15d410feff call qword ptr [fltmgr!_imp_KeLeaveCriticalRegion (fffff880`01326120)]
fffff880`0134504c 488b5c2430 mov rbx,qword ptr [rsp+30h]
fffff880`01345051 488b6c2438 mov rbp,qword ptr [rsp+38h]
fffff880`01345056 488b742440 mov rsi,qword ptr [rsp+40h]
fffff880`0134505b 488b7c2448 mov rdi,qword ptr [rsp+48h]
fffff880`01345060 4883c420 add rsp,20h
fffff880`01345064 415d pop r13
fffff880`01345066 c3 ret

fffff880`01345067 488bce mov rcx,rsi
fffff880`0134506a e8b136fdff call fltmgr!FltObjectReference (fffff880`01318720)
fffff880`0134506f 488d4f40 lea rcx,[rdi+40h]
fffff880`01345073 8bd8 mov ebx,eax
fffff880`01345075 ff15ad10feff call qword ptr [fltmgr!_imp_ExReleaseResourceLite (fffff880`01326128)]
fffff880`0134507b ff159f10feff call qword ptr [fltmgr!_imp_KeLeaveCriticalRegion (fffff880`01326120)]
fffff880`01345081 488d0dd0b1feff lea rcx,[fltmgr!FltGlobals+0x58 (fffff880`01330258)]
fffff880`01345088 ff159a10feff call qword ptr [fltmgr!_imp_ExReleaseResourceLite (fffff880`01326128)]
fffff880`0134508e ff158c10feff call qword ptr [fltmgr!_imp_KeLeaveCriticalRegion (fffff880`01326120)]
fffff880`01345094 85db test ebx,ebx
fffff880`01345096 78b4 js fltmgr!FltpMiniFilterDriverUnload+0x9c (fffff880`0134504c)
fffff880`01345098 41b001 mov r8b,1
fffff880`0134509b ba01000000 mov edx,1
fffff880`013450a0 488bce mov rcx,rsi
fffff880`013450a3 e868fcffff call fltmgr!FltpDoUnloadFilter (fffff880`01344d10)
                                                                            ; Call fltmgr!FltpDoUnloadFilter
fffff880`013450a8 eba2 jmp fltmgr!FltpMiniFilterDriverUnload+0x9c (fffff880`0134504c)
                                                                            ; Go to end of function


fltmgr!FltpMiniFilterDriverUnload() 함수 동작을 정리해보면 다음과 같다.


1) 필터매니저가 관리하는 FLT_FRAME 중 가장 마지막에 생성된 Frame 의 FLT_FILTER 를 검사(Frame 1부터)
    - fltmgr!FltGlobals 전역 변수에서 Frame 에 대한 리스트(FLT_FRAME.Links)를 얻음
    - FLT_FRAME.RegisteredFilters.rList 에서 Frame 에 등록된 Filter 에 대한 리스트(FLT_FILTER.PrimaryLink)를 얻음

2) 언로드 요청된 모듈의 DriverObject 와 FLT_FILTER 의 내부 필드인 DriverObject 의 값이 일치하는지 확인
    - FLT_FILTER.DriverObject

3) 일치할 경우 해당 모듈에 대해 fltmgr!FltpDoUnloadFilter() 를 호출해주고, 다를 경우 마지막 Frame 까지 확인



결론적으로 필터매니저에 등록된 미니필터들은 필터매니저에서 로직상 하나의 Frame 리스트로 관리된다고 볼 수 있다.

하나의 리스트를 순차적으로 검사하며 언로드를 수행하기 때문에 앞 쪽 리스트의 DriverObject 정보가 잘못되면 뒷 쪽 리스트의 미니필터가 언로드되지 못할 가능성이 있다.


이제 마지막 과정만 남았다.

함수 분석 결과 Frame 리스트에는 Frame 0 에 등록된 MyDrv.sys 보다 Frame 1 에 등록된 SomeDrv.sys 가 앞 쪽에 위치하고 있다.
필터매니저가 SomeDrv.sys 에 대해 정말로 잘못된 DriverObject 를 가지고 있었는지만 확인하면 된다.


6. 필터매니저가 보관하고 있는 SomeDrv.sys 의 정보를 확인해보자.

kd> !fltkd.frames

Frame List: fffff880013302c0
FLTP_FRAME: fffffa80039e5010 "Frame 1" "429998.99 to 429999.280700"
FLT_FILTER: fffffa8006dcc960"SomeDrv" "[NoName]"
FLTP_FRAME: fffffa8001b2aac0 "Frame 0" "0 to 429998.99"
FLT_FILTER: fffffa800290d900 "MyDrv" "326020"
FLT_INSTANCE: fffffa80031b4330 "MyDrv Instance" "326020"
FLT_INSTANCE: fffffa800699d170 "MyDrv Instance" "326020"
FLT_INSTANCE: fffffa8003658b40 "MyDrv Instance" "326020"


kd> dt _FLT_FILTER fffffa8006dcc960
fltmgr!_FLT_FILTER
    +0x000 Base : _FLT_OBJECT
    +0x020 Frame : 0xfffffa80`039e5010 _FLTP_FRAME
    +0x028 Name : _UNICODE_STRING "SomeDrv"
    +0x038 DefaultAltitude : _UNICODE_STRING ""
    +0x048 Flags : 2 ( FLTFL_FILTERING_INITIATED )
    +0x050 DriverObject : 0xfffffa80`03673c80 _DRIVER_OBJECT
    ... ...


kd> !drvobj 0xfffffa80`03673c80
Driver object (fffffa8003673c80) is for:
    \Driver\MyDrv
Driver Extension List: (id , addr)

Device Object list:
fffffa8002e8f550



역시, 필터매니저가 어떤 이유로 SomeDrv.sys 의 Filter Object 에 SomeDrv.sys 의 DriverObject 가 아닌 MyDrv.sys 의 DriverObject 를 설정해 놓았다.

결국 MyDrv.sys 를 StopService() 했을 때 필터매니저의 fltmgr!FltpDoUnloadFilter() 에서 언로드를 요청한 MyDrv.sys 가 아닌 SomeDrv.sys 를 언로드한 것이 원인이었다. OTL

언로드 과정 중 4)번 fltmgr!FltpDoUnloadFilter() 부터 잘못되버려 MyDrv.sys 의 언로드 함수인 MyDrv!MiniFilterUnload() 는 호출조차 되지 못한 것이다.


※ 이 필터매니저(fltmgr.sys) 버그는 Microsoft 에 Report 했고 최신 필터매니저 버전에서 fix 되었다.

'Dump Analysis' 카테고리의 다른 글

[0xC5] 풀 헤더 손상  (0) 2018.07.16
[0x1A] 페이지 손상  (0) 2018.07.12
[0x50] 해제된 핸들  (0) 2018.07.09
[0x50] 숨겨진 콜 스택  (0) 2018.07.07
[0x50] UNICODE_STRING  (0) 2018.07.05
prev"" #1 next