기본 콘텐츠로 건너뛰기

Windows-Cpp-Exception-via-SEH-1

SEH를 거치는 Windows C++ Exception

윈도우에서 사용되고 있는 Structured Exception Handling(SEH)는 Exception 관리 시스템이다. 하지만 C++은 자체적으로 Exceptoin을 관리한다. 우리는 예제를 작성하여 WinDbg에서 어떻게 Exception이 관리되는 지 볼 것이다.

Structured Exception Handling(SEH)
Exception은 프로그램이 실행되는 동안 발생되는 이벤트로 프로그램이 정상적인 제어 흐름을 벗어날 때 야기된다. Exception은 hardware exceptionsoftware exception 크게 두가지 종류로 분류된다. Hardware exception은 CPU에 의해 시작된다. 0 나누기 연산이나, 접근 불가 메모리에 접근을 시도하는 명령어 구절을 실행하려고 할 때 발생한다. Sofrware exception은 응용 프로그램이나 운영체제에 의해 발생한다. 예를 들어, 유효하지 않은 인자에 값을 지정할 때 감지될 수 있다.

Structured exception handling은 hardware와 software exception 양쪽을 처리하는 메커니즘이다. 그리므로 코드는 hardware와 software exception을 동일하게 처리할 수 있다. Structured exception handling은 디버거, 모든 프로그래밍 언어와 기계를 지원하며 제어를 가능하게 한다. (Vectored exception handlingstructured exception handling이 확장된 개념이다.)

시스템 또한 termination handling을 지원하므로 보호된 코드 본문이 실행될 때 마다 지정된 종료 코드 블록이 실행되도록 할 수 있다. 종료 코드는 어떻게 보호된 본문을 빠져나갈 것인지에 대한 제어 흐름과 관계없이 실행된다. 예를 들어, termination handler는 코드의 보호된 본문이 실행되는 동안 예외 또는 오류가 발생하더라도 정리 작업이 수행되도록 보장할 수 있다.

Source code

Zero Exception에 의해 Catch하여 분기되는 간단한 소스코드를 살펴보자.

#include <iostream>
#include <stdexcept>

int divEx(int numerator, int denominator) {
    if (denominator == 0) {
        throw std::overflow_error("Divide by zero exception");
    }

    return numerator / denominator;
}

int main() {
    int b = 0;

    try {
        divEx(0x42, 0);
    } catch(std::overflow_error e) {
        std::cout << "Error " << e.what();
    }

    return 0;
}

!exchain

Windbg를 실행하고 예제 프로그램을 불러온 후, 메인 함수에 브레이크 포인트를 설정한다. !exchain 명령어는 SEH는 chain list을 출력해준다.

0:000:x86> bp seh_analysis!main
breakpoint 0 redefined
0:000:x86> bl
     0 e Disable Clear  x86 00bc1060     0001 (0001)  0:**** seh_analysis!main
0:000:x86> g
Breakpoint 0 hit
seh_analysis!main:
00bc1060 55              push    ebp
0:000:x86> !exchain
005ef7d8: seh_analysis!_except_handler4+0 (00bc1ebb)
  CRT scope  0, filter: seh_analysis!__scrt_common_main_seh+12b (00bc1740)
                func:   seh_analysis!__scrt_common_main_seh+13f (00bc1754)
005ef834: ntdll_77380000!_except_handler4+0 (773f6a20)
  CRT scope  0, filter: ntdll_77380000!__RtlUserThreadStart+3ed78 (77423790)
                func:   ntdll_77380000!__RtlUserThreadStart+3edbf (774237d7)
005ef84c: ntdll_77380000!FinalExceptionHandlerPad21+0 (77409ec5)

CPU가 인터럽트를 일으킬 때, 커널은 chain list를 탐색하고 함수를 호출하여 오류를 catch할 것이다. 만약 오류를 catch할 함수가 설계되지 않았다면, 커널은 자체적으로 처리한다. (werfalut를 호출하거나 프로세스 종료 처리)

!exchain 명령이 호출하는 전체 chain list를 출력하는 방법을 알아두면 유용하게 활용할 수 있다. (fs:[0]은 chain list 포인터를 포함한다.)
스레드는 자신의 스레드 정보 블록의 시작에서 _EXCEPTION_REGISTRATION_RECORD 리스트와 연결된다. __try는 컴파일러에 정의된 EH_prolog 함수를 호출한다. 이 함수는 스택에 _EXCEPTION_REGISTRATION_RECORD 를 할당하고, 이것은 msvcrt.dll__except_handler3 함수를 가리켜, 리스트 헤드에 레코드를 추가한다.
__try 블록의 끝에서는 역 연산을 수행하는 EH_epilog 함수가 호출된다. (컴파일러에서 정의된 이 루틴들은 인라인화될 수 있다.) __except__finally블록들은 __except_handler3 내부에서 호출된다. 이러한 블록들이 존재한다면 생성된 _EXCEPTION_REGISTRATION_RECORD 는 필드를 추가 확장하여 __except_handler3에 의해 사용된다.

사용자 모드 코드에서의 예외일 경우, 운영체제는 핸들러 신호로 예외를 처리하거나 리스트가 끝날 때까지 스레드의 _EXCEPTION_REGISTRATION_RECORD 리스트를 분석하고 순서에 따라 각 예외 처리기를 호출한다. 리스트의 마지막은 항상 일반 보호 장애를 표시하는 kernel32!UnhandledExceptionFilter이다. 그 후 리스트는 처리기에게 사용된 자원들을 정리하기 위해 한번 더 순회된다. 마지막으로 실행은 커널 모드로 반환되어 프로세스가 재개되거나 종료된다.

dt 구조체타입이름 멤버필드이름 [구조체주소]
- 구조체에서 특정한 멤버필드만 확인

0:000:x86> dd fs:[0]
0053:00000000  005ef7d8 005f0000 005ed000 00000000
0053:00000010  00001e00 00000000 006ea000 00000000
0053:00000020  00000164 000011b4 00000000 006ea02c
0053:00000030  006e7000 00000000 00000000 00000000
0053:00000040  00000000 00000000 00000000 00000000
0053:00000050  00000000 00000000 00000000 00000000
0053:00000060  00000000 00000000 00000000 00000000
0053:00000070  00000000 00000000 00000000 00000000
0:000:x86> dt seh_analysis!_EXCEPTION_REGISTRATION_RECORD 005ef7d8
   +0x000 Next             : 0x005ef834 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x00bc1ebb     _EXCEPTION_DISPOSITION  seh_analysis!_except_handler4+0
0:000:x86> dt seh_analysis!_EXCEPTION_REGISTRATION_RECORD 005ef834
   +0x000 Next             : 0x005ef84c _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x773f6a20     _EXCEPTION_DISPOSITION  ntdll_77380000!_except_handler4+0
0:000:x86> dt seh_analysis!_EXCEPTION_REGISTRATION_RECORD 005ef84c
   +0x000 Next             : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : 0x77409ec5     _EXCEPTION_DISPOSITION  ntdll_77380000!FinalExceptionHandlerPad21+0

마지막 함수 (FinalExceptionHandlePad21)을 따라가면 일반적인 메시지를 찾을 수 있다.

ntdll_77380000!_FinalExceptionHandler+0x4f:
7745a678 6882473877      push    offset ntdll_77380000!`string' (77384782)
7745a67d 50              push    eax
7745a67e e88d240000      call    ntdll_77380000!RtlUnhandledExceptionFilter2 (7745cb10)

ntdll_77380000!RtlUnhandledExceptionFilter2+0x7a:
7745cb8a ff30            push    dword ptr [eax]
7745cb8c 68ac813877      push    offset ntdll_77380000!`string' (773881ac)

0:000:x86> dc 773881ac
773881ac  2a200a0a 55202a2a 6e61686e 64656c64  .. *** Unhandled
773881bc  63786520 69747065 30206e6f 38302578   exception 0x%08
773881cc  202c786c 20746968 25206e69 253a7377  lx, hit in %ws:%
773881dc  000a0a73 2a2a2a20 746e6520 2e207265  s... *** enter .
773881ec  20727865 66207025 7420726f 65206568  exr %p for the e
773881fc  70656378 6e6f6974 63657220 0a64726f  xception record.
7738820c  00000000 2a2a2a20 6e652020 20726574  .... ***  enter 
7738821c  7278632e 20702520 20726f66 20656874  .cxr %p for the 

Traditional exception

이제 본격적으로 예제를 살펴보자. 먼저 SEH 상단에 함수를 추가하는 것을 확인 할 수 있다.

seh_analysis!main:
00bc1060 55              push    ebp
00bc1061 8bec            mov     ebp,esp
00bc1063 6aff            push    0FFFFFFFFh
00bc1065 685821bc00      push    offset seh_analysis!CxxThrowException+0x17 (00bc2158)
00bc106a 64a100000000    mov     eax,dword ptr fs:[00000000h]seh_analysis!main:
00bc1060 55              push    ebp
00bc1061 8bec            mov     ebp,esp
00bc1063 6aff            push    0FFFFFFFFh
00bc1065 685821bc00      push    offset seh_analysis!CxxThrowException+0x17 (00bc2158)
00bc106a 64a100000000    mov     eax,dword ptr fs:[00000000h]
00bc1070 50              push    eax
00bc1071 83ec14          sub     esp,14h
00bc1074 a10050bc00      mov     eax,dword ptr [seh_analysis!__security_cookie (00bc5000)]
00bc1079 33c5            xor     eax,ebp
00bc107b 8945ec          mov     dword ptr [ebp-14h],eax
00bc107e 53              push    ebx
00bc107f 56              push    esi
00bc1080 57              push    edi
00bc1081 50              push    eax
00bc1082 8d45f4          lea     eax,[ebp-0Ch]
00bc1085 64a300000000    mov     dword ptr fs:[00000000h],eax

SEH 초기화한 후, divEX(0x42, 0) 함수를 호출한다.

00bc108b 8965f0          mov     dword ptr [ebp-10h],esp
00bc108e c745fc00000000  mov     dword ptr [ebp-4],0
00bc1095 e866ffffff      call    seh_analysis!divEx (00bc1000)

denominator확인 후 CxxThrowException을 호출한다.

00bc100f 68b838bc00      push    offset seh_analysis!_TI3?AVoverflow_errorstd (00bc38b8)
00bc1014 8d45f4          lea     eax,[ebp-0Ch]
00bc1017 50              push    eax
00bc1018 e824110000      call    seh_analysis!CxxThrowException (00bc2141)

*CxxThrowException은 빌드 Exception을 기록하고 런타임 환경에서 Exception 프로세스를 시작한다. (MSDN _CxxThrowException)

extern "C" void __stdcall _CxxThrowException(  
   void* pExceptionObject  
   _ThrowInfo* pThrowInfo  
);  

0:000:x86> dt -r4 seh_analysis!_s__ThrowInfo 00bc38b8
   +0x000 attributes       : 0
   +0x004 pmfnUnwind       : 0x00bc13e0     void  seh_analysis!std::overflow_error::~overflow_error+0
   +0x008 pForwardCompat   : (null) 
   +0x00c pCatchableTypeArray : 0x00bc3854 _s__CatchableTypeArray
      +0x000 nCatchableTypes  : 0n3
      +0x004 arrayOfCatchableTypes : [0] 0x00bc389c _s__CatchableType
         +0x000 properties       : 0
         +0x004 pType            : 0x00bc5030 _TypeDescriptor
            +0x000 pVFTable         : 0x00bc3124 Void
            +0x004 spare            : (null) 
            +0x008 name             : [0]  ".?AVoverflow_error@std@@"
         +0x008 thisDisplacement : _PMD
            +0x000 mdisp            : 0n0
            +0x004 pdisp            : 0n-1
            +0x008 vdisp            : 0n0
         +0x014 sizeOrOffset     : 0n12
         +0x018 copyFunction     : 0x00bc1020           void  seh_analysis!std::overflow_error::overflow_error+0

CxxThrowException이후 NtRaiseException을 호출하고 NtRaiseExceptionKiRaiseException을 호출한다.

0:000:x86> u NtRaiseException
ntdll_77380000!NtRaiseException:
773efd20 b85c010000      mov     eax,15Ch
773efd25 baf09c4077      mov     edx,offset ntdll_77380000!Wow64SystemServiceCall (77409cf0)
773efd2a ffd2            call    edx
773efd2c c20c00          ret     0Ch
773efd2f 90              nop

0:000:x86> u 77409cf0
ntdll_77380000!Wow64SystemServiceCall:
77409cf0 ff2518924977    jmp     dword ptr [ntdll_77380000!Wow64Transition (77499218)]


0:000:x86> u poi(77499218)
wow64cpu!KiFastSystemCall:
579d7000 ea09709d573300  jmp     0033:579D7009

이제 커널은 SEH에서 첫 번째 함수를 호출하고 catch 서브 프로그램에 도달하는 것을 볼 수 있다.

이 블로그의 인기 게시물

Remove-Server-Header

응답 메시지 내 서버 버전 정보 제거 1. Apache 1) 조치 방법 “/etc/htpd/conf/httpd.conf” 파일 안에서 1. ServerTokens OS → ServerTokens Prod 2. ServerSignature On → ServerSignature Off 로 변경한 후 아파치를 재시작하면 헤더 값의 아파치 버전 정보 및 OS 정보를 제거할 수 있다. 2) 참고 URL http://zetawiki.com/wiki/CentOS_ 아파치_보안권장설정_ServerTokens_Prod,_ServerSignature_Off 2. IIS 1) 조치 방법 IIS 6.0 urlscan_setup 실행. 설치. \windows\system32\inetsrv\urlscan\urlscan.ini 파일을 열어 다음 수정(RemoveServerHeader=0 을 RemoveServerHeader=1 로 변경) 서비스에서 IIS Admin Service 재시작. IIS 7.0 IIS 관리자를 열고 관리하려는 수준으로 이동합니다. 기능 보기에서 HTTP 응답 헤더를 두 번 클릭합니다. HTTP 응답 헤더 페이지에서 제거할 헤더를 선택합니다. 작업 창에서 제거를 클릭하고 예를 클릭합니다. 2) 참고 URL IIS 6.0 : http://gonnie.tistory.com/entry/iis6- 응답헤더-감추기 IIS 7.0 : https://technet.microsoft.com/ko-kr/library/cc733102(v=ws.10).aspx 3. jetty 1) 조치 방법 “jetty.xml” 파일에서 jetty.send.server.version=false 설정 2) 참고 URL http://attenuated-perspicacity.blogspot.kr/2009/09/jetty-61x-hardening.html 4. Nginx

X-Frame-Options-Test

X-Frame-Options 테스트하기 X-Frame-Options 페이지 구성 시 삽입된 프레임의 출처를 검증하여 허용하지 않는 페이지 URL일 경우 해당 프레임을 포함하지 않는 확장 응답 헤더이다. 보안 목적으로 사용되는 확장 헤더로 아직 적용되지 않은 사이트들이 많지만 앞으로 점차 적용될 것으로 보인다. X-Frame OptionsDENY, SAMEORIGIN, ALLOW-FROM 옵션을 이용하여 세부 정책을 설정한다. 옵션 설명 DENY Frame 비허용 SAMEORIGIN 동일한 ORIGIN에 해당하는 Frame만 허용 ALLOW-FROM 지정된 ORIGIN에 해당하는 Frame만 허용 크롬 4.1 , IE 8 , 오페라 10.5 , 사파리 4.0 , 파이어폭스 3.6.9 이상에서는 DENY , SAMEORIGIN 이 적용되며, ALLOW-FROM 은 각 브라우저 마다 지원 현황이 다르다. https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/X-Frame-Options 해당 확장헤더는 브라우저에서 처리하는 응답 헤더이므로 미지원 브라우저 사용 시 설정과 무관하게 페이지 내 포함된 모든 Frame을 출력한다. (검증 테스트: Opera 5.0.0) 테스트 코드 DENY <!DOCTYPE html> < html lang = "en" > < head > < meta http-equiv = "X-Frame-Options" content = "deny" /> < title > Deny option Test </ title > </ head > < bod

데일 카네기 인간관계론 정리