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
'security > 리버싱핵심원리' 카테고리의 다른 글
TEB & PEB 정리 (0) | 2023.04.05 |
---|---|
TLS 콜백 함수 (0) | 2023.04.05 |
CreateRemoteThread()를 이용한 DLL Injetcion의 한계 (0) | 2023.04.04 |
ASLR 제거 (0) | 2023.04.04 |
API 코드 패치를 이용한 API hooking - notepad.exe 프로세스 은폐하기 (2) (0) | 2023.04.03 |