Crash Dump Analysis Patterns (Part 3) – False Positive Dump

Crash Dump Analysis Patterns (Part 3) – False Positive Dump

https://www.dumpanalysis.org/blog/index.php/2006/11/01/crash-dump-analysis-patterns-part-3/

제가 자주 접하는 또 다른 패턴은 False Positive Dump 라고 하는 것입니다. 잘못된 곳을 가리키고 있거나 분석에 도움이 되지 않는 덤프를 받는 경우입니다. 이러한 현상의 원인은 크래쉬 덤프를 캡쳐할 때 설정이 잘못되었거나 잘못된 툴이 사용되었기 때문에 발생합니다. 아래에서 자세한 예로 살펴보겠습니다.

 

한 고객님이 자주 spooler 크래쉬를 경험했습니다. 따라서 크래쉬를 일으키는 컴포넌트(대부분 프린터 드라이버)를 찾기 위해 덤프가 보내졌습니다. WinDbg는 아래의 예외 스레드 스택을 보여주었습니다(가독성을 위해 파라미터는 생략):

KERNEL32!RaiseException+0x56

KERNEL32!OutputDebugStringA+0x55

KERNEL32!OutputDebugStringW+0x39

HPZUI041!ConvertTicket+0x3c90

HPZUI041!DllGetClassObject+0x5d9b

HPZUI041!DllGetClassObject+0x11bb

 

바로 딱 보기에는 HPZUI041.DLL을 가리키고 있지만 KERNEL32!OutputDebugStringA에게 전해진 파라미터를 살펴보면 NULL로 끝난 올바른 string이 넘겨진 것을 확인할 수 있습니다.

0:010> da 000d0040

000d0040 ".Lower DWORD of elapsed time = 3"

000d0060 "750000."

 

만약 OutputDebugStringA를 RaiseException 호출 부분까지 디어셈블해보면 아래와 같은 코드를 볼 수 있을 것입니다:

0:010> u KERNEL32!OutputDebugStringA

KERNEL32!OutputDebugStringA+0x55

KERNEL32!OutputDebugStringA:

push ebp

mov ebp,esp

push 0FFFFFFFFh

push offset KERNEL32!'string'+0x10

push offset KERNEL32!_except_handler3

mov eax,dword ptr fs:[00000000h]

push eax

mov dword ptr fs:[0],esp

push ecx

push ecx

sub esp,228h

push ebx

push esi

push edi

mov dword ptr [ebp-18h],esp

and dword ptr [ebp-4],0

mov edx,dword ptr [ebp+8]

mov edi,edx

or ecx,0FFFFFFFFh

xor eax,eax

repne scas byte ptr es:[edi]

not ecx

mov dword ptr [ebp-20h],ecx

mov dword ptr [ebp-1Ch],edx

lea eax,[ebp-20h]

push eax

push 2

push 0

push 40010006h

call KERNEL32!RaiseException

 

KERNEL32!RaiseException 호출 이전에 어떠한 분기문도 존재하지 않는데, 이것은 원래 예외가 발생하도록 되어 있었다는 것을 의미합니다. MSDN에도 다음처럼 나와있습니다.

“If the application has no debugger, the system debugger displays the string. If the application has no debugger and the system debugger is not active, OutputDebugString does nothing.”

만약 어플리케이션 디버거 없이 동작한다면, 시스템 디버거가 문자열을 보여줄 것이다. 만약 어플리케이션 디버거도 없고, 시스템 디버거도 동작중이 아니라면, OutputDebugString은 아무것도 하지 않는다.”

따라서 spoolsv.exe는 디버거에 의해 모니터되는 중이었고, 디버거는 예외를 catch하여 무시하지 않고 spooler 프로세스를 덤프해 버린 것입니다.

‘analyze -v’ 를 입력해보면 다음과 같은 내용을 볼 수 있습니다:

Comment: 'Userdump generated complete user-mode minidump

with Exception Monitor function on WS002E0O-01-MFP'ERROR_CODE: (NTSTATUS) 0x40010006 -

Debugger printed exception on control C.

(커멘트: Userdump가 유저모드 미니덤프를 아래의 에러 코드로 생성하였습니다. WS002E0O-01-MFP'ERROR_CODE: (NTSTATUS) 0x40010006 – 디버거가 control C로 예외를 출력하였습니다-

 

우리는 이제 그 디버거가 바로 마이크로소프트 웹사이트에서 다운로드 할 수 있는 User Mode Process Dumper 임을 알 수 있습니다.

덤프 파일을 만들기 위해 Userdump.exe를 어떻게 사용하는가?

다운로드가 완료되면 설치하고 다음과 같이 크래쉬를 일으키는 TestOutputDebugString라는 이름의 Visual C++ 콘솔 프로그램을 작성해봅니다.

#include "stdafx.h"
#include
int _tmain(int argc, _TCHAR* argv[])
{
    OutputDebugString(_T("Sample string"));
    return 0;
}

릴리즈 모드로 컴파일을 하고 제어판(Control Panel)에서 TestOutputDebugString.exe 항목을 다음과 같이 설정합니다.

(역주: 룰을 추가할 때, ‘.exe’를 포함한 완벽한 파일이름을 지정해주셔야 합니다)

그리고 우리의 프로그램을 실행하면 Process Dumper가 KERNEL32!RaiseException을 잡아내서 덤프를 만드는 것을 볼 수 있습니다.

만약 kernel32.dll 안에서 발생하는 예외를 무시하기로 선택(역주:’Ignore exceptions that occur inside Kernel32.dll’ 체크옵션)한다고 해도, 이 툴은 여전히 우리 프로세스를 덤프할 것입니다. 결국 그 고객이 ‘All Exceptions’ 체크박스를 켜놨을 것이라고 결론지을 수 있습니다. 따라서 그 고객 분에게는 아래 그림처럼 디폴트 룰을 사용하도록 해야 합니다.

또는 Exception Codes를 일일이 수동으로 선택해야 합니다. 이 경우에는 만약 우리가 수동으로 Exception Codes 전부를 선택한다고 해도 덤프가 만들어지지 않습니다. 그렇게 설정해도 NULL 포인터를 참조하는 코드를 실행하면 여전히 access violation을 잡아낼 것이고 Process Dumper는 그것을 잡아 덤프를 만들어낼 것입니다.

결론 : 고객은 디폴트 포스트포텀 디버거로 NTDS를 사용해야만 합니다. 그래야만 만약 크래쉬가 발생했을 때 진짜 크래쉬를 발생한 컴포넌트가 무엇인지 알 수 있고 요청한 덤프로 다른 패턴들을 적용할 수 있을 것입니다.

- Dmitry Vostokov –

번역(translation):

김희준(drost@naver.com),

2007-05-31, https://www.driveronline.org, https://insidekernel.net