공부해요/리버싱_핵심원리

48. SEH

yenas0 2025. 1. 8. 09:00
반응형

SEH란?

Windows 운영체제에서 제공하는 예외 처리 시스템

소스코드에서 __try, __except, __finally 키워드로 구현 가능

 

SEH 예제 실습 #1

https://github.com/reversecore/book/blob/master/%EC%8B%A4%EC%8A%B5%EC%98%88%EC%A0%9C/06_%EA%B3%A0%EA%B8%89_%EB%A6%AC%EB%B2%84%EC%8B%B1/48_SEH/bin/seh.exe

 

book/실습예제/06_고급_리버싱/48_SEH/bin/seh.exe at master · reversecore/book

리버싱 핵심원리 - 소스 코드 및 실습 예제. Contribute to reversecore/book development by creating an account on GitHub.

github.com

위에 파일로 진행

이 실행파일은 의도적으로 메모리 Access Viloation 예외를 발생 시킨 후에 SEH를 이용해서 해당 예외를 처리 후 PEB의 정보를 이용해 안티 디버깅 코드 추가 -> 디버거와 일반 실행이 다르도록 구현

일반 실행 시 위처럼 뜨지만 올리디버거로 열고 F9로 실행을 시키면

예외가 발생되면서 디버거 실행이 중지된다.

 

401019 주소의 MOV DWORD PTR DS:[EAX], 1 명령어는 예외 발생을 위해 추가된 코드로 EAX 레지스터의 값은 0인데 여기 메모리 주소 0에 값에 1을 입력하라는 의미가 된다.

근데 메모리 주소 0은 할당 되지 않았는데 쓰려고 시도해서 에러가 발생한 것

 

예외 발생시에는 아래 경고 문구가 뜬다

"Access violation when writing to [00000000] - use Shift +F7/F8/F9 to pass exception to program"

 

메모리 0번지에서 쓰기 예외가 발생했으니 shift어쩌구 써서 예외를 프로그램에 넘겨라

그래서 시키는 대로 shift+f9를 하면 위와같이 뜬다

이걸로 일반실행이랑 디버거 실행이랑 다른걸 알 수 있음 -> 예외 처리하는 방식이 다른것 ---> SEH를 이용한 안티 디버깅 기법

 

이애하려면 운영체제의 exception과 예외 처리기(SEH)구조에 대해 알아야됨

 

 

 

OS의 예외처리 방법

1. 일반 실행의 경우 예외 처리 방법

운영체제는 프로세스 실행 중에 예외 발생 시 프로세스에게 처리를 맡김

프로세스 코드에 예외 처리 구현시 -> 해당 예외를 처리 후 계속 프로세스 실행

구현 안되어 있을 시 -> 기본 예외 처리기 동작으로 프로세스 종료

 

2. 디버깅 실행의 경우 예외 처리 방법

디버깅 중에 디버기 프로세스에 예외 발생 시 운영체제는 우선적으로 디버거에게 예외를 넘겨서 처리하도록 함

디버거는 디버기에 대한 거의 모든 소유권 有 -> 디버기의 실행 및 종료 제어, 디버기 프로세스 내부의 가상 메모리 및 레지스터에 대한 읽기 및 쓰기 권한, 디버기 내부 발생된 exception 처리할 책임 有

 

2번같은 에외로 디버거 실행 중지 시 조치를 취해야 디버깅이 가능

조치 방법1) 예외 직접 수정: 코드, 레지스터, 메모리

문제가 발생한 코드, 메모리, 레지스터를 직접 수정

 

조치 방법2)예외를 디버기에게 넘겨서 처리

디버기 내부에 이미 SEH가 존재해 처리가 가능하다면 exception 통지를 디버기한테 줘서 자체 해결하도록 함 -> 1번 일반 실행 경우랑 동일하게 처리됨(아까 실습에서 한 shift어쩌구한게 이거임)

 

조치 방법3) 운영체제 기본 예외 처리기

디버기랑 디버거에서 처리 못하거나 처리안하려고 하면 그냥 운영체제의 기본 예외 처리기에서 처리한다.

디버기 프로세스 종료되면서 디버깅이 중지됨

 

 

 

EXCEPTION

대표적인 예외 다섯개

EXCEPTION_ACCESS_VIOLATION (C0000005) 존재하지 않거나 접급 권한이 없는 메모리 영역에 대해 접근 시도
EXCEPTION_BREAKPOINT (80000003) 실행 코드에 BP 설치 시 CPU가 그 주소 실행하려할 때 방생
EXCEPTION_ILLEGAL_INSTRUCTION (C000001D) CPU가 해석할 수 없는 명령어를 만날 대
EXCEPTION_INT_DIVIDE_BY_ZERO (C0000094) 정수 나눗셈에서 분모가 0인 경우
EXCEPTION_SINGLE_STEP (80000004) 명령어 하나를 실행하고 멈추기 위해 사용

 

 

SEH 상세 설명

 

SEH 체인

첫번째 예외 처리기에서 해당 예외를 처리하지 못하면 처리될 때까지 다음 예외 처리기로 예외를 넘겨준다

기술적으로 SEH는 _EXCEPTION_REGISTRATION_RECORD 구조체 연결 리스트의 형태

 

함수 정의

EXCEPTION_DISPOSITION _except_handler
(
    EXCEPTION_RECORD           *pRecord,
    EXCEPTION_REGISTRATION_RECORD *pFrame,
    CONTEXT                    *pContext,
    PVOID                      pValue
);

4개 파라미터를 받아서 EXCEPTION_DISPOSITION이라는 열거형을 리턴한다. (시스템에 의해 호출되는 콜백함수)

 

TEB.NtTib.ExceptionList

프로세스의 SEH체인에 접근하려면 TEB구조체의 NtTib 멤버를 따라가면 된다

TEB.NtTib.ExceptionList 멤버는 TEB 구조체에서 가장 첫 멤버

TEB.NtTib.ExceptionList = FS:[0]

으로 구할 수 있음(46장 참고..)

 

SEH설치 방법

C언어에서 SEH설치하려면 __try, __exception, __finally 키워드를 사용해 구현 가능함

PUSH @MyHandler ; 예외 처리기
PUSH DWORD PTR FS:[0] ; Head of SEH Linked List
MOV DWORD PTR FS:[0], ESP ; 연결 리스트 추가

SEH를 설치한다 = 기존의 SEH 체인에 자신의 에외 처리기를 추가시킨다

 

 

SEH 예제 실습 #2

아까 그 파일 다시 실행 시키자

일단 401000까지 실행시키고

401005까지 실행시키면서 FS:[0]값을 확인해보면 SEH 체인의 시작 주소를 알 수 있다.

코드 정보창이라 확인하면 FS[0] = [00383000] = 0019FF64로 나온다

난 0019FF64가 SEH 체인의 시작 주소이다

스택창에서 해당 주소를 확인하면 첫번째 EXCEPTION_REGISTRATION_RECORD 구조체가 나온다

 

두번째 EXCEPTION_REGISTRATION_RECORD 구조체 확인을 위해 0019FFE4주소로 가보았다.

Next멤버가 FFFFFFFF니까 이게 SEH체인의 마지막인걸 알 수 있다

 

 

SEH 추가

401005주소 명령어 실행하고 스택을 보면 _EXCEPTION_REGISTRATION_RECORD 구조체가 생성된다.

그록나서 40100C 명령어도 수행하면 스택 장에 새로운 SEH가 생성되었다는 주석이 생긴다

이렇게 되면 새로운 예외 처리기가 SEH 체인에 추가된거다

 

view-SEH chain에서 확인할수도 있음

욜케..

 

예외 발생

401019주소하면 예외가 발생했었다

지금은 디버깅 중이니까 예외 처리 우선순위에 따라 제어가 디버거에게 넘어가는데 예외를 다시 디버기에게 돌려주기 위해 40105A에 BP를 설치하고 shift+f9로 실행한다.

이제 예외처리기를 디버깅 할 수 있음

 

예외 처리기 디버깅

40105A 예외 처리기에는 디버거 탐지 코드가 존재

첫 줄에서 ESP+C가 예외 처리기의 pContext 파라미터 값을 의미해서 이 값을 ESI 레지스터에 입력하는 것이다.

그러고 나서 EAX 레지스터에 PEB 구조체의 시작 주소를 넣는다

이후에 PEB.BeingDebugged멤버 1바이트를 1과 비교한다.

1이랑 같으면 점프를 안하는 데 즉 일반 실행이면 401076으로 점프하고 디버깅이 실행이면 40106A 주소의 명령어를 실행한다.

현재 프로세스가 디버깅 중이면 40106A 명령어로 오는데 pContext->Eip의 값을 401023으로 변경하는 명령어다

예외 처리기가 종료 시 예외가 발생한 스레드는 401023주소의 코드를 실행한다. 이 주소에서 Debugger Detected 메시지를 출력하는 것이다.

 

(디버깅의 편의를 위해 401023에 BP를 설치해 두었다.)

이후 줄에서는 pContext->Eip값을 변경하였으므로 예외 처리기 종료코드(401080)으로 점프한다. 마지막으로 리턴값인 EAX를 0으로 세팅하고 예외 처리기는 리턴한다. 리턴 값 0의 의미는 EXCEPTION_CONTINUE_EXECUTION으로 예외를 잘 처리했읜 해당 스레드를 실행시키라는 의미이다.

RETN 명령까지 실행되면 ntdll.dll 모듈의 코드 영역으로 리턴된다.

 

 

SEH제거

40104D주소까지 오면 스택에는 앞에서 입력한EXCEPTION)REGISTRATION_RECORD 구조체가 있는데 이건 SEH에서 제일 먼저 실행되는 에외 처리기이다.

 

올리디버거 옵션 설정

- 디버거는 자신의 exception을 디버거에 먼저 보내 저리한다

:이걸로 일반 실행과 디버깅 실행이 달라지는 것 -> 자동으로 디버기에게 돌려주는 옵션 有

[options] - [Debugging options] - [Exceptions] - [Ignore memory access violations in KERNEL32 선택] 

6가지 항목이 있는데 위에서 부터 5개가 위에 적어둔 5개 예외다

각 체크 버튼에 체크하면 디버거에서 예외를 무시하고 디버기에게 돌려준다.

마지막에 All FPU exceptions은 부동 소수점 처리를 빠르게 하기 위한 장치. FPU 명령어를 처리하다가 예외 발생 시 디버기에서 처리하는 옵션임

반응형

'공부해요 > 리버싱_핵심원리' 카테고리의 다른 글

50. 안티 디버깅  (0) 2025.01.12
49. IA-32 Instruction  (0) 2025.01.12
47. PEB  (1) 2025.01.02
46. TEB  (1) 2025.01.02
45. TLS 콜백 함수  (0) 2025.01.02