본문 바로가기
WebBook/윈도우 구조

윈도우 예외 처리 이해 하기 - KeBugCheckEx

by 올엠 2022. 9. 13.
반응형

이번에는 윈도우 예외 처리 이해 하기로 KeBugCheckEx에 대해서 알아보도록 하겠다.

윈도우에서는 IDT(Inturrupt Descriptor Table)를 이용해 인터럽트 및 예외(Exception)를 처리하는데, BSOD와 같은 예외 상황 역시 IDT 테이블 통해 처리되며, 최종 KeBugCheckEx를 통해 오류상황에 대처한다. 그럼 KeBugCheckEx의 구조체를 알아보자.

VOID  KeBugCheckEx(
  __in  ULONG BugCheckCode,
  __in  ULONG_PTR BugCheckParameter1,
  __in  ULONG_PTR BugCheckParameter2,
  __in  ULONG_PTR BugCheckParameter3,
  __in  ULONG_PTR BugCheckParameter4
);

 

첫 번째 값은 버그 체크 코드(BugCheckCode)이며, 그 외 4가지 값은 해당 버그체크코드의 파라미터가 표시되는데 이 파라미터는, 버그 체크 코드 별로 해석이 달리 된다.

그럼 커널 모드 에러가 발생하면 어떻게 진행되는 것일까?

 

1.     커널 디버거로 처리를 넘긴다(커널 디버거를 연결한 경우 Chance 3).

2.     블루스크린을 표시하며, 해당 버그 체크 코드를 화면에 표시함

3.     메모리 덤프 파일을 생성한다(가능한 경우).

4.     시스템을 재부팅한다(재부팅 설정이 되었을 경우).

 

2번의 상황이 제일 기본적인 상황으로 많이 경험해 봤을 것이며, 1번의 경우 앞서 진행한 실시간 커널 디버깅 상태에서 이루어 진다. 그 외 3, 4번의 경우 상황이 맞을 경우에 한하여 구현되게 된다. 그럼 이 장애 처리 흐름을 조금 더 자세히 애기해보자.

 

 

이제 위 진행 흐름을 실제 데이터 분석을 통해 확인하기 위해 생성된 메모리 덤프를 Windbg로 열어서 분석을 진행하자.

아래 상황은 CSRSS 프로세스를 강제로 종료하여 메모리 덤프를 생성하여 분석한 내용이다.

// 0번 프로세서의 컨트롤 영역 정보를 확인한다.
0: kd> !pcr 0
KPCR for Processor 0 at ffdff000:
    Major 1 Minor 1
           NtTib.ExceptionList: ffffffff
               NtTib.StackBase: b1a16df0
              NtTib.StackLimit: b1a13000
            NtTib.SubSystemTib: 00000000
                 NtTib.Version: 00000000
             NtTib.UserPointer: 00000000
                 NtTib.SelfTib: 7ffdd000
 
                       SelfPcr: ffdff000
                          Prcb: ffdff120
                          Irql: 00000000
                           IRR: 00000000
                           IDR: ffffffff
                 InterruptMode: 00000000
                           IDT: 8003f400
                           GDT: 8003f000
                           TSS: 80042000
 
                 CurrentThread: 81c10da8
                    NextThread: 00000000
                    IdleThread: 80563cc0
 
                     DpcQueue:
// IDT  Int21Descriptor KIDTENTRY 구조체를 통해 확인해 볼수 있다.
0: kd> dt nt!_KIDTENTRY 8003f400
   +0x000 Offset           : 0x51e
   +0x002 Selector         : 8
   +0x004 Access           : 0x8e00
   +0x006 ExtendedOffset   : 0x804e
// ExtendedOffset Offset을 조합하여 해당 주소의 심볼 값을 확인해 보자.
0: kd> ln 0x804e051e
(804e051e)   nt!KiTrap00   |  (804e05f1)   nt!Dr_kit1_a
Exact matches:
    nt!KiTrap00 = <no type information>
// 해당 주소는 nt!KiTrap00이 사용하는 주소로 확인되었다.
0: kd> !idt -a
 
Dumping IDT:
 
00:       804e051e nt!KiTrap00
01:       804e069d nt!KiTrap01
02:       Task Selector = 0x0058
03:       804e0ab1 nt!KiTrap03
04:       804e0c34 nt!KiTrap04
05:       804e0d99 nt!KiTrap05
06:       804e0f1a nt!KiTrap06
07:       804e1593 nt!KiTrap07
08:       Task Selector = 0x0050
09:       804e1998 nt!KiTrap09
0a:       804e1ab6 nt!KiTrap0A
0b:       804e1bf3 nt!KiTrap0B
0c:       804e1e50 nt!KiTrap0C
0d:       804e214c nt!KiTrap0D
0e:       804e2889 nt!KiTrap0E
0f:        804e2bbe nt!KiTrap0F
10:       804e2cdc nt!KiTrap10
11:       804e2e16 nt!KiTrap11
12:       Task Selector = 0x00A0
13:       804e2f7b nt!KiTrap13
14:       804e2bbe nt!KiTrap0F
중략
// 현재 레지스터 정보를 확인하자.
0: kd> r
eax=ffdff13c ebx=82103020 ecx=00000000 edx=00000034 esi=82103020 edi=81c10da8
eip=805396ba esp=b1a16ce8 ebp=b1a16d00 iopl=0         nv up ei ng nz na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000286
nt!KeBugCheckEx+0x1b:
805396ba 5d              pop     ebp
// ESP 레지스터를 통해 ESP 저장된 스택 값을 확인하자.
0: kd> dds esp-50 esp+50
b1a16c98  81c10020
b1a16c9c  b1a16d40
b1a16ca0  8125d070
b1a16ca4  c01e7bd4
b1a16ca8  80703a4c hal!KeAcquireQueuedSpinLock
b1a16cac  00000001
b1a16cb0  00000000
b1a16cb4  00000000
b1a16cb8  00000000
b1a16cbc  00000538
b1a16cc0  00000000
b1a16cc4  b1a16c78
b1a16cc8  00000000
b1a16ccc  00000000
b1a16cd0  0000bb40
b1a16cd4  b1a16aac
b1a16cd8  00000000
b1a16cdc  0000bb40
b1a16ce0  b1a16d00 ß 복귀할 EBP 포인터가 저장된다.
b1a16ce4  805396ba nt!KeBugCheckEx+0x1b ß스택에 현재 EIP
b1a16ce8  000000f4 ß 오류코드
b1a16cec  00000003 ß 파라미터1
b1a16cf0  82103020 ß 파라미터2
b1a16cf4  82103194 ß 파라미터3
b1a16cf8  8060777e nt!PspTerminateThreadByPointer+0xfa ß 파라미터4
b1a16cfc  00000000
b1a16d00  b1a16d24
b1a16d04  80637e9d nt!PspCatchCriticalBreak+0x75
b1a16d08  000000f4
b1a16d0c  00000003
b1a16d10  82103020
b1a16d14  82103194
b1a16d18  8060777e nt!PspTerminateThreadByPointer+0xfa
b1a16d1c  82103020
b1a16d20  82103020
b1a16d24  b1a16d54
// EIP 레지스터를 통해 현재 처리중인 명령을 확인할 수 있는데 nt!KeBugCheckEx 실행후 마지막임을 알 수 있다.
0: kd> u eip-10 l10
nt!KeBugCheckEx+0xb:
805396aa 7514            jne     nt!KeBugCheckEx+0x21 (805396c0)
805396ac ff7510          push    dword ptr [ebp+10h]
805396af ff750c          push    dword ptr [ebp+0Ch]
805396b2 ff7508          push    dword ptr [ebp+8]
805396b5 e878f4ffff      call    nt!KeBugCheck2 (80538b32)
 
8053969f 8bff            mov     edi,edi
805396a1 55              push    ebp
805396a2 8bec            mov     ebp,esp
805396a4 6a00            push    0
805396a6 ff7518          push    dword ptr [ebp+18h] ß 파라미터2
805396a9 ff7514          push    dword ptr [ebp+14h] ß 파라미터2
805396ac ff7510          push    dword ptr [ebp+10h] ß 파라미터2
805396af ff750c          push    dword ptr [ebp+0Ch] ß 파라미터1
805396b2 ff7508          push    dword ptr [ebp+8] ß 오류코드 의 주소 포인터
805396b5 e878f4ffff      call    nt!KeBugCheck2 (80538b32)
805396ba 5d              pop     ebp ß 현재 EIP 위치
// 콜스택 정보를 확인하여 ESP 레지스터에서 저장된 EBP 포인터와 비교
0: kd> k
ChildEBP RetAddr 
b1a16d00 80637e9d nt!KeBugCheckEx+0x1b ß 조금전 확인한 EBP 포인터들
b1a16d24 8060773c nt!PspCatchCriticalBreak+0x75
b1a16d54 804df99f nt!NtTerminateProcess+0x7d
b1a16d54 7c93e514 nt!KiFastCallEntry+0xfc
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012f3e4 00000000 0x7c93e514
// 현재 버그체크 코드를 확인해 보자.
0: kd> .bugcheck
Bugcheck code 000000F4
Arguments 00000003 82103020 82103194 8060777e
// 그럼 언제 트랩프레임을 저장할 것일까? kv를 통해 FPO정보를 추가하여 확인하면, 트랩 프레임이 저장된 영역정보도 확인할 수 있다. FPO는 스택 영역 분석에 유용하므로, 다시 상기하기 바란다.
0: kd> kv
ChildEBP RetAddr  Args to Child             
b1a16d00 80637e9d 000000f4 00000003 82103020 nt!KeBugCheckEx+0x1b (FPO: [5,0,0]) ß FPO 관련해서 바로 다음에 나온다.
b1a16d24 8060773c 8060777e 82103020 82103194 nt!PspCatchCriticalBreak+0x75 (FPO: [3,0,0])
b1a16d54 804df99f 82103268 ffffffff 0012f3e4 nt!NtTerminateProcess+0x7d (FPO: [2,4,4])
b1a16d54 7c93e514 82103268 ffffffff 0012f3e4 nt!KiFastCallEntry+0xfc (FPO: [0,0] TrapFrame @ b1a16d64)
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012f3e4 00000000 00000000 00000000 00000000 0x7c93e514
// 트랩 프레임은 .trap 명령을 통해 전환하여, 예외 발생당시의 레지스터와 콜스택과 정보등을 확인할 수 있다.
0: kd> r
eax=ffdff13c ebx=82103020 ecx=00000000 edx=00000034 esi=82103020 edi=81c10da8
eip=805396ba esp=b1a16ce8 ebp=b1a16d00 iopl=0         nv up ei ng nz na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000286
nt!KeBugCheckEx+0x1b:
805396ba 5d              pop     ebp
0: kd> .trap b1a16d64
ErrCode = 00000000
eax=0012f3b0 ebx=00160050 ecx=00000001 edx=001771b0 esi=0012f428 edi=0012f688
eip=7c93e514 esp=0012f3d4 ebp=0012f3e4 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
001b:7c93e514 ??              ???
// .trap 명령을 재실행하면, 초기화 하여 기본 범위로 되돌아 온다.
0: kd> .trap
Resetting default scope
0: kd> r
eax=ffdff13c ebx=82103020 ecx=00000000 edx=00000034 esi=82103020 edi=81c10da8
eip=805396ba esp=b1a16ce8 ebp=b1a16d00 iopl=0         nv up ei ng nz na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000286
nt!KeBugCheckEx+0x1b:
805396ba 5d              pop     ebp
// 트랩 프레임은 _KTRAP_FRAME의 구조체를 이용하여 각 데이터를 확인할 수 있다.
0: kd> dt nt!_KTRAP_FRAME b1a16d64
   +0x000 DbgEbp           : 0x12f3e4
   +0x004 DbgEip           : 0x7c93e514
   +0x008 DbgArgMark       : 0xbadb0d00
   +0x00c DbgArgPointer    : 0x12f3dc
   +0x010 TempSegCs        : 0
   +0x014 TempEsp          : 0
   +0x018 Dr0              : 0
   +0x01c Dr1              : 0
   +0x020 Dr2              : 0
   +0x024 Dr3              : 0
   +0x028 Dr6              : 0
   +0x02c Dr7              : 0
   +0x030 SegGs            : 0
   +0x034 SegEs            : 0x23
   +0x038 SegDs            : 0x23
   +0x03c Edx              : 0x1771b0
   +0x040 Ecx              : 1
   +0x044 Eax              : 0x12f3b0
   +0x048 PreviousPreviousMode : 1
   +0x04c ExceptionList    : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
   +0x050 SegFs            : 0x3b
   +0x054 Edi              : 0x12f688
   +0x058 Esi              : 0x12f428
   +0x05c Ebx              : 0x160050
   +0x060 Ebp              : 0x12f3e4
   +0x064 ErrCode          : 0
   +0x068 Eip              : 0x7c93e514
   +0x06c SegCs            : 0x1b
   +0x070 EFlags           : 0x206
   +0x074 HardwareEsp      : 0x12f3d4
   +0x078 HardwareSegSs    : 0x23
   +0x07c V86Es            : 0
   +0x080 V86Ds            : 0
   +0x084 V86Fs            : 0
   +0x088 V86Gs            : 0
반응형