security/리버싱핵심원리

SEH [Structured Exception Handling]

민사민서 2023. 4. 7. 21:51

OS의 예외 처리 방식

1. 일반 실행의 경우

- 프로세스에게 처리를 맡김.

- SEH가 구현되어있다면 예외 처리 후 진행, 없다면 OS 기본 예외 처리기 동작시켜 프로세스 종료

2. 디버깅 실행의 경우

- 우선적으로 디버거에게 예외를 넘김

- 디버거는 디버기 내부에서 발생하는 모든 예외/에러 상황을 처리해야함

- 예외 직접 수정하거나, 예외를 디버기에게 넘겨서 디버기의 SEH가 처리하도록 (Shift + F7/F8/F9)

디버기에게 자동으로 예외 돌려주는 옵션

 

Window OS 예외 종류

- EXCEPTION_ACCESS_VIOLATION (0xC0000005)

- EXCEPTION_ARRAY_BOUNDS_EXCEEDED (0xC000008C)

- EXCEPTION_BREAKPOINT (0x80000003) // ex) software bp (0xCC)

- EXCEPTION_DATATYPE_MISALIGNMENT (0x80000002)  // ex) when read/write 4 byte integer from odd address

- EXCEPTION_INT_DIVIDE_BY_ZERO (0xC0000094)

- EXCEPTION_STACK_OVERFLOW (0xC00000FD)

- EXCEPTION_ILLEGAL_INSTRUCTION (0xC000001D)

- EXCEPTION_SINGLE_STEP (0x80000004)

 

SEH 체인

typedef struct _EXCEPTION_REGISTRATION_RECORD {
    PEXCEPTION_REGISTRATION_RECORD Next;
    PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

- SEH는 EXCEPTION_REGISTRATION_RECORD 구조체 연결 리스트 형태로 구성됨

- 예외가 처리될 때까지 다음 예외 처리기(Handler)로 예외를 넘겨주는 형식

- Next = 0xFFFFFFFF이면 연결 리스트의 마지막

 

// Exception Handler function
EXCEPTION_DISPOSITION _except_handler
(
    EXCEPTION_RECORD *pRecord,
    EXCEPTION_REGISTRATION_RECORD *pFrmae,
    CONTEXT *pContext,
    PVOID pValue
);

 

- 첫번째 parameter pRecord는 EXCEPTION_RECORD 구조체 포인터로, 예외와 관련된 정보가 저장되어 있음

  => +0x0: DWORD ExceptionCode, +0xC: PVOID ExceptionAddress

- 세번째 parameter pContext는 CONTEXT 구조체의 포인터로, 예외 발생한 스레드의 context 정보를 가짐

  => +0xB8: DWORD Eip

typedef enum _EXCEPTION_DISPOSITION {
    ExceptionContinueException = 0, // 예외 발생한 코드부터 재실행
    ExceptionContinueSearch = 1,    // 다음 예외 처리기 실행
    ExceptionNestedException = 2,
    ExceptionCollidedUnwind = 3
} EXCEPTION_DISPOSITION;

- 4개의 parameter를 받아 EXCEPTION_DISPOSITION이라는 열거형 리턴

 

* FS:[0] = TEB.NtTib.ExceptionList 를 통해 SEH 체인에 접근 가능!!

 

SEH.exe 분석

- EP에서 진행하다보면 "CALL 00401000" (SEH 설치/제거하는 코드 구현부) 만날 수 있다

1) SEH 체인에 SEH (EXCEPTION_REGISTRATION_RECORD 구조체) 추가하고

2) 의도적으로 MEMORY ACCESS EXCEPTION 발생시키고 (주소 0에 write)

3) 에러 처리 후 진행할 코드들 존재

- 첫 두 줄을 통해 구조체의 Next (현재 SEH 체인의 첫번째 구조체), Handler (함수 주소) 멤버를 초기화하고

- 세번째 줄을 통해 새로운 구조체를 SEH 체인에 등록한다

- 0040104D~00401054에서 SEH 체인을 원복한다

- exception handler function 구현부이다

- 해당 SEH 호출될 당시 스택 상황은 RET / ARG.1 / ARG.2 / ARG.3 / ARG.4 순이다

- ESI에 세번째 인자 pContext, EAX에 addr of PEB를 넣고

- PEB.BeingDebugged 값에 따라 pContext->Eip를 변경한다 // 스레드의 context(EIP) 변경!

- RET 값을 0 (EXCEPTION_CONTINUE_EXCEPTION)로 세팅함으로써 예외 발생한 스레드를 재개시킨다

- 0x401019에서 Exception 발생 당시 (SEH 호출 당시) 스택 상황

- 첫번째 인자 pRecord 따라갔더니

- 0x0 offset에서 ExceptionCode (0xC0000005, EXCEPTION_ACCESS_VIOLATION)

- 0xC offset에서 ExceptionAddress (0x401019) 파악 가능

- 세번째 인자 pContext 따라갔더니

- 0xB8 offset에서 EIP (ExceptionAddress) 파악 가능

 

cf) 사용자 정의 SEH를 추가하기 전 SEH 체인 상황

- DWORD PTR FS:[0]=0012FF78로, dump로 해당 구조체를 확인해보았다

- exception handler는 다음과 같이 구현되어있었음 (VC++ 가 자동으로 추가해주는 코드)

- 두번째 SEH 구조체. Next=FFFFFFFF이므로 SEH 체인의 마지막임을 알 수 있다

- ntdll.dll 에 구현되어있는, 윈도우 OS가 제공하는 SEH