본문 바로가기

High Level Technique/Reversing

Structured Exception Handler (SEH)

Structured Exception Handler (SEH)


Structured Exception Handler (이하 SEH)는 Windows에서 제공하는 예외 처리입니다. __try, __except, __finally 키워드로 구현할 수 있습니다.

C++에서 제공하는 tyr, catch와는 다릅니다. SEH가 먼저 만들어졌습니다.



SEH 프로그램을 실행시키면 위 그림처럼 Hello :)를 출력해줍니다.


정상적으로 실행이 되는것 같지만 사실 내부에서 예외가 발생하여 처리가 되어서 정상실행 된 것입니다.


OllyDBG를 이용해서 seh.exe를 실행시켜보면 아래와 같은 위치에서 Access violaton이 발생합니다.



EAX의 값을 가져오지 못하고 있습니다.



밑에 상태창에서  Access violation이라고 나타나 있는 것을 알 수 있습니다.



Shift + F9를 눌러서 진행을 시킨 결과 Debugger detected :(가 출력되네요. 분석을 하는데 처음 실행과 똑같이 Hello가 나타난다면 OllyDBG의 Plugin 때문에 그럴 수 있으니 Plugin을 제거하고 진행해 주세요.




일반적으로 실행을 시켰을 때 예외 처리방법은 다들 한번씩 보셨을 법한 경고창이 나타납니다.



디버깅 상태에서는 조금 다른데, 디버깅 중에 프로세스에서 예외가 발생하면 OS는 디버거에게 예외를 넘겨서 처리하도록 합니다.

디버깅 중에 예외가 발생되서 실행이 중지가 되면 조치를 취한 후 디버깅을 계속할 수 있습니다.



코드, 레지스터, 메모리 수정


예외가 발생한 부분에서 직접 코드를 수정해서 문제가 발생하지 않도록 합니다.

위에서 본 것 처럼 EAX의 값을 가져오지 못한다면 EAX의 값을 유효한 메모리 범위로 지정해준다면 정상적으로 실행이 가능합니다.

아니면 NOP 처리를 해서 실행이 되지 않게 해도 되죠.


하지만 실제 작성된 프로그램에서 반드시 거쳐가야만 하는 코드라면 단순히 코드만 수정한다고 해서 원래 목적에 맞는 실행은 안됩니다.




예외를 프로세스에게 넘겨서 처리


프로세스에 SEH가 존재해서 예외를 처리할 수 있다면 프로세스에게 예외를 보내서 자체적으로 해결할 수 있도록 합니다.

Shift + F9와 같은 것이 바로 이런 것 입니다.



OS에서 예외 처리


프로세스에서도 처리를 하지 못하고 디버거에서도 처리를 하지 못한다면 OS에서 처리를 하도록 해야합니다. 





예외


디버깅을 하다가 보면 여러가지 예외가 나타납니다.


#define STATUS_WAIT_0                           ((DWORD   )0x00000000L) 

#define STATUS_ABANDONED_WAIT_0          ((DWORD   )0x00000080L)    

#define STATUS_USER_APC                  ((DWORD   )0x000000C0L)    

#define STATUS_TIMEOUT                   ((DWORD   )0x00000102L)    

#define STATUS_PENDING                   ((DWORD   )0x00000103L)    

#define DBG_EXCEPTION_HANDLED            ((DWORD   )0x00010001L)    

#define DBG_CONTINUE                     ((DWORD   )0x00010002L)    

#define STATUS_SEGMENT_NOTIFICATION      ((DWORD   )0x40000005L)    

#define STATUS_FATAL_APP_EXIT            ((DWORD   )0x40000015L)    

#define DBG_TERMINATE_THREAD             ((DWORD   )0x40010003L)    

#define DBG_TERMINATE_PROCESS            ((DWORD   )0x40010004L)    

#define DBG_CONTROL_C                    ((DWORD   )0x40010005L)    

#define DBG_PRINTEXCEPTION_C             ((DWORD   )0x40010006L)    

#define DBG_RIPEXCEPTION                 ((DWORD   )0x40010007L)    

#define DBG_CONTROL_BREAK                ((DWORD   )0x40010008L)    

#define DBG_COMMAND_EXCEPTION            ((DWORD   )0x40010009L)    

#define STATUS_GUARD_PAGE_VIOLATION      ((DWORD   )0x80000001L)    

#define STATUS_DATATYPE_MISALIGNMENT     ((DWORD   )0x80000002L)    

#define STATUS_BREAKPOINT                ((DWORD   )0x80000003L)    

#define STATUS_SINGLE_STEP               ((DWORD   )0x80000004L)    

#define STATUS_LONGJUMP                  ((DWORD   )0x80000026L)    

#define STATUS_UNWIND_CONSOLIDATE        ((DWORD   )0x80000029L)    

#define DBG_EXCEPTION_NOT_HANDLED        ((DWORD   )0x80010001L)    

#define STATUS_ACCESS_VIOLATION          ((DWORD   )0xC0000005L)    

#define STATUS_IN_PAGE_ERROR             ((DWORD   )0xC0000006L)    

#define STATUS_INVALID_HANDLE            ((DWORD   )0xC0000008L)    

#define STATUS_INVALID_PARAMETER         ((DWORD   )0xC000000DL)    

#define STATUS_NO_MEMORY                 ((DWORD   )0xC0000017L)    

#define STATUS_ILLEGAL_INSTRUCTION       ((DWORD   )0xC000001DL)    

#define STATUS_NONCONTINUABLE_EXCEPTION  ((DWORD   )0xC0000025L)    

#define STATUS_INVALID_DISPOSITION       ((DWORD   )0xC0000026L)    

#define STATUS_ARRAY_BOUNDS_EXCEEDED     ((DWORD   )0xC000008CL)    

#define STATUS_FLOAT_DENORMAL_OPERAND    ((DWORD   )0xC000008DL)    

#define STATUS_FLOAT_DIVIDE_BY_ZERO      ((DWORD   )0xC000008EL)    

#define STATUS_FLOAT_INEXACT_RESULT      ((DWORD   )0xC000008FL)    

#define STATUS_FLOAT_INVALID_OPERATION   ((DWORD   )0xC0000090L)    

#define STATUS_FLOAT_OVERFLOW            ((DWORD   )0xC0000091L)    

#define STATUS_FLOAT_STACK_CHECK         ((DWORD   )0xC0000092L)    

#define STATUS_FLOAT_UNDERFLOW           ((DWORD   )0xC0000093L)    

#define STATUS_INTEGER_DIVIDE_BY_ZERO    ((DWORD   )0xC0000094L)    

#define STATUS_INTEGER_OVERFLOW          ((DWORD   )0xC0000095L)    

#define STATUS_PRIVILEGED_INSTRUCTION    ((DWORD   )0xC0000096L)    

#define STATUS_STACK_OVERFLOW            ((DWORD   )0xC00000FDL)    

#define STATUS_DLL_NOT_FOUND             ((DWORD   )0xC0000135L)    

#define STATUS_ORDINAL_NOT_FOUND         ((DWORD   )0xC0000138L)    

#define STATUS_ENTRYPOINT_NOT_FOUND      ((DWORD   )0xC0000139L)    

#define STATUS_CONTROL_C_EXIT            ((DWORD   )0xC000013AL)    

#define STATUS_DLL_INIT_FAILED           ((DWORD   )0xC0000142L)    

#define STATUS_FLOAT_MULTIPLE_FAULTS     ((DWORD   )0xC00002B4L)    

#define STATUS_FLOAT_MULTIPLE_TRAPS      ((DWORD   )0xC00002B5L)    

#define STATUS_REG_NAT_CONSUMPTION       ((DWORD   )0xC00002C9L)    

#define STATUS_HEAP_CORRUPTION           ((DWORD   )0xC0000374L)    

#define STATUS_STACK_BUFFER_OVERRUN      ((DWORD   )0xC0000409L)    

#define STATUS_INVALID_CRUNTIME_PARAMETER ((DWORD   )0xC0000417L)    

#define STATUS_ASSERTION_FAILURE         ((DWORD   )0xC0000420L)    

#if defined(STATUS_SUCCESS) || (_WIN32_WINNT > 0x0500) || (_WIN32_FUSION >= 0x0100) 

#define STATUS_SXS_EARLY_DEACTIVATION    ((DWORD   )0xC015000FL)    

#define STATUS_SXS_INVALID_DEACTIVATION  ((DWORD   )0xC0150010L)    



여기서 중점적으로 볼 예외는 아래와 같습니다.


STATUS_ACCESS_VIOLATION

존재하지 않거나 접근 권한이 없는 메모리 영역에 대해서 접근을 시도할 때 발생합니다.


STATUS_BREAKPOINT

실행 코드에 BreakPoint가 설치되면 CPU가 해당 주소를 실행하려 할 때 STATUS_BREAKPOINT가 발생합니다. 이 예외를 가지고 BreakPoint 기능을 제공합니다.


INT3

BreakPoint를 걸면 원본 코드에서 0xCC로 변경됩니다. 어셈블리어로 INT3(0xCC)가  BreakPoint 값입니다.

BreakPoint를 걸고 PE Tools를 이용해서 Dump를 뜨면 해당 주소에 0xCC로 변경된 것을 볼 수 있습니다.



STATUS_ILLEGAL_INSTRUCTION

CPU가 해석할 수 없는 명령어를 만날 때 발생하는 예외 입니다. 간혹 디버거에서 ???와 같은 것을 볼 수 있는데 이것이 해석할 수 없는 명령어 입니다.



STATUS_FLOAT_DIVIDE_BY_ZERO

FLOAT(실수) 나누셈 연산에서 0으로 나누는 경우 발생하는 예외입니다.


STATUS_SINGLE_STEP

명령어 하나를 실행하고 멈추는 것인데, CPU가 Single Step 모드로 전환되면 명령어 하나를 실행 후 예외를 발생시킵니다.

Single Step Mode로 변경을 하고자 할 때 EFLAGS 레지스터의 TF를 1로 세팅하면 됩니다.










SEH Chain


SEH는 Chain 형태로 구성되어 있습니다. 첫 번째 예외 처리기에서 해당 예외를 처리하지 못하면 처리 될 때까지 다음 예외처리기로 예외를 넘겨줍니다.


SEH는 _EXCEPTION_REGISTRATION_RECORD 구조체 링크드 리스트 형태로 구성됩니다.



Next는 다음 _EXCEPTION_REGISTRATION_RECORD 구조체 포인터이며 Handler는 예외 처리 함수입니다.

Next의 값이 0xFFFFFFFF 이면 링크드 리스트의 마지막을 나타냅니다.


SEH의 함수 정의는 아래와 같습니다.


EXECEPTION_DISPOSITION _except_handler

{

EXCEPTION_RECORE *pRecord,

EXCEPTION_REGISTRATION_RECORD *pFrame,

CONTEXT *pContext,

PVOID pValue

}


4개의 파라미터를 받고 EXCEPTIOIN_DISPOSITION이라는 열거형을 리턴합니다.



EXCEPTION_RECORD의 구조체 정의는 아래와 같습니다.



ExceptionCode는 예외의 종류를 말하고, ExceptionAddress는 예외가 발생한 코드 주소를 나타냅니다.



CONTEXT 구조체 정의는 아래와 같습니다.



CONTEXT 구조체는 CPU 레지스터 값을 백업하는 용도로 사용됩니다. Multi Thread 환경이기 때문에 그러는데, Thread는 내부적으로 CONTEXT 구조체를 하나씩 가지고 있고, CPU가 다른 thread를 실행할 때 CPU 레지스터들의 값을 현재 thread CONTEXT 구조체에 저장합니다.



SEH에서 예외를 처리했다면 ExceptionContinueExecution을 리턴할 경우 예외가 발생한 코드부터 재 실행되고, 예외를 처리할 수 없으면 ExceptionContinueSearch를 리턴하여 SEH Chain에서 다음 예외 처리기에서 예외를 처리합니다.




TEB.NtTib.ExceptionList


프로세스의 SEH Chain에 접근하는 방법은 TEB 구조체의 NtTib에 접근하면 됩니다.

TEB는 FS 세그먼트 레지스터가 가리키는 세그먼트 메모리 시작 주소에 위치하기 때문에 TEB.NtTib.ExceptionList는 FS:[0]이라고 할 수 있습니다.




SEH 추가


SEH를 추가하려면 __try, __except, __finally 키워드를 사용해서 추가할 수 있습니다.


어셈블리어로는


PUSH MyHandler

PUSH DWORD PTR FS:[0]

MOV DWORD PTR FS:[0], ESP


로 할 수 있습니다.






SEH 실습



seh.exe를 실행시키면 위와 같은 주소에서 SEH를 추가할 때의 어셈을 볼 수 있습니다.



FS:[0]의 값을 확인해보니 002E7000이라는 값이 있고, 해당 주소를 가보니 0019ff70이라는 값이 들어있습니다.



해당 위치의 스택을 확인해보니 SEH Chain의 시작주소인 것을 알 수 있습니다.




해당 값을 따라가보니 19FFCC가 있었고 또 19FFE4가 있었습니다. 해당 위치를 또 이동해서 보면 FFFFFFFF이 저장되어있는 것을 볼 수 있습니다. SEH의 마지막은 FFFFFFFF이라고 했었습니다.



PUSH DWORD PTR FS:[0]을 실행 한 후 스택을 확인해 보면 아래와 같습니다.



새로운  _EXCEPTION_REGISTRATION_RECORD 구조체가 생성되었습니다.


그리고 MOV DWORD PTR FS:[0], ESP 명령어를 실행시키고 스택을 확인해보면 아래와 같습니다.



OlluDBG에서 SEH를 쉽게 확인 할 수 있습니다.



view - SEH chain 을 눌으면 아래와 같이 나타납니다.



40105A에 예외 처리기가 추가가 된 것을 볼 수 있습니다.




처음에 401019위치에서 EAX의 값을 확인할 수가 없어서 ACCESS_VIOLATION 예외가 발생한 것을 보았었습니다.


디버깅 중이기 때문에 예외처리기 40105A가 실행되지 않고 디버거에게 제어가 넘어옵니다.


Shift + F9를 눌러서 프로세스에게 제어권을 넘겨줍니다.


40105A에 BreakPoint를 걸어두고 실행해줍니다. Shift + F9 입니다.



해당 위치에서 BreakPoint가 걸리게 되면 예외 처리기를 디버깅 할 수 있습니다.




 첫번째 값인 19F984인 EXCEPTION_RECORD 구조체 포인터 pRecord의 내용을 확인해 봅니다.



C0000005의 값을 가지고 있는것을 보아 ACCESS_VIOLATION이라는 것을 알 수 있습니다.

그리고 ExceptionAddress는 401019인 것을 확인 할 수 있습니다.


두번째 값인 19F988은 EXCEPTION_REGISTRATION_RECORD 구조체 포인터 pFrame 입니다.

이 값은 19FF34이며 SEH Chain의 시작 주소 입니다.


세번째 값인 19F98C는 CONTEXT 구조체 포인터 pContext 입니다. 



구조체 시작에서 B8만큼 떨어져 있는 Eip 멤버입니다. Eip 멤버는 예외가 발생한 코드의 주소가 저장되어 있습니다.



네번째 값인 19F990인 pValue는 시스템 내부에서 사용되는 값으로 신경쓰지 않아도 됩니다.



해당 위치가 대표적인 안티 디버깅 코드입니다.


pContext의 값을 가져오고 FS:[30]의 값을 가져옵니다. 그래서 EAX+2와 1를 비교하고 있는데 EAX에는 PEB의 시작 주소가 저장되어있고, EAX+2는 PEB.BeingDebugged 멤버를 의미하기 때문에 같으면 점프를 하지 않습니다.



디버깅 중이기 때문에 값은 1로 같아지고 MOV DOWRD PTR DS:[ESI+B8], 401023 에 의해서 ESI+B8의 값이 401023으로 변경됩니다.


401023의 위치는 Debugger detected :(가 출력되는 부분입니다.



'High Level Technique > Reversing' 카테고리의 다른 글

Anti Debugging  (0) 2016.08.05
IA32 Instruction  (1) 2016.08.04
Process Environment Block (PEB)  (0) 2016.08.02
Thread Environment Block (TEB)  (0) 2016.08.02
Thread Local Storage CallBack (TLS)  (0) 2016.08.01