본문 바로가기
Reversing/Reversing

Ollydbg - Exception시 SEH 처리 확인하기

by bbolmin 2014. 2. 8.



유저모드에서 FS레지스터는 TEB를 가리킨다.


아래는 FS에서 자주 사용되는 데이터들이다.


FS:[0x0] : ExceptionList

FS:[0x18] : TEB (self)

FS:[0x30] : PEB



아래는 XP 환경에서 TEB 구조체를 확인한 결과인데 TEB의 처음 0x01b까지가 TIB 구조체이며 TIB의 첫 번째 데이터가 

ExceptionList를 가리키는 것을 알 수 있다.






FS:[0x0]이 가리키는 ExceptionList는 EXCEPTION_REGISTRATION_RECORD 구조체로 구성되어 있다.



[EXCEPTION_REGISTRATION_RECORD]

- Pointer to next SEH Record

- Exception Handler


Pointer to next SEH Record값이 0xFFFFFFFF가 되면 kernel32!_except_handler가 호출




- SEH Chain -




Exception Handler의 프로토 타입


EXCEPTION_DISPOSITION __cdecl _except_handler ( 

struct _EXCEPTION_RECORD *ExceptionRecord, 

void *EstablisherFrame, 

struct _CONTEXT *ContextRecord, 

void *DispatcherContext );



리턴값 EXCEPTION_DISPOSITION의 종류


Typedef enum _EXCEPTION_DISPOSITION{ 

 ExceptionContinueExecution, //0 

 ExceptionContinueSearch, //1 

 ExceptionNestedException, //2 

 ExceptionCollidedUnwind //3 

} EXCEPTION_DISPOSITION; 





#include "windows.h"
#include "stdio.h"
ULONG G_nValid; 

EXCEPTION_DISPOSITION __cdecl except_handler ( 
struct _EXCEPTION_RECORD *ExceptionRecord, void *EstablisherFrame, 
struct _CONTEXT *ContextRecord, void *DispatcherContext) { 
	void *ptr = EstablisherFrame; 
	printf("Exception!! \n");
	ContextRecord->Eax = (ULONG)&G_nValid; 
	return ExceptionContinueExecution; 
} 

int main(int argc, char *argv[]) { 
	ULONG nHandler = (ULONG)except_handler; 
	PCHAR pTest = (PCHAR)0; 
	__asm { 
		push nHandler //SEH 설치 
			push FS:[0] 
		mov FS:[0], ESP 
	} 
	__asm { 
		mov eax, 0 //SEH 발생 
			mov [eax], 'a' 
	}
	__asm {
		mov eax, [ESP] //SEH 제거 
		mov FS:[0], EAX 
			add esp, 8 
	} 
	return 0; 
} 





위와 같이 간단한 SEH 예제를 만들어서 Ollydbg로 확인해보면 아래와 같다.



shift + f8을 눌러 예외처리 루틴으로 들어가면 먼저 ntdll 내부로 들어오게 된다.


JMP 0x0015003c 부분을 따라 들어가면 사용자가 설치한 exception 코드가 실행되고 다음으로 ZwContinue를 호출하여 원래 실행 컨텍스트로 돌아간다. (ZwContinue의 첫 번째 인자가 CONTEXT구조체를 가지는데 해당 구조체의 0xb8번째가 원래의 eip 포인터이다.)








이번에는 SEH를 직접 설치하지 않고 VC++의 __try, __except 키워드를 사용해서 예외처리를 해보겠다.
VC의 키워드를 사용하여 예외를 만들면 SEH체인의 구조가 달라지게 된다. 
위에서는 EXCEPTION_REGISTRATION_RECORD 구조체에 두개의 멤버가 존재하고 Exception Handler에 사용자가 만든 예외 핸들러가 들어갔었다.

- Pointer to next SEH Record

- Exception Handler



하지만 VC에서 SEH를 구조는 아래와 같이 ScopeTable과 Trylevel이 추가 되어있다. 또 Exception Handler에 사용자가 만든 예외 핸들러가 아니라 VC++에서 이미 만들어 놓은 __exception_handler3라는 함수가 등록된다.


- Pointer to next SEH Record

- Exception Handler

- ScopeTable

- Trylevel(default : 0xffffffff

)

- 이전의 ebp



그럼 사용자가 만들어 놓은 예외 함수는 ScopeTable에 들어가게 되는데 ScopeTable의 구조는 아래와 같다.


 [ ScopeTable ]


아래 소스코드로 볼때 Filter 함수 부분에는 EXCEPTION_EXECUTE_HANDLER에 대한 내용이 들어가고 Handler함수에 __except로 처리한 함수가 들어 간게 된다. (Filter에서 EXCEPTION_EXECUTE_HANDLER가 1을 뜻하므로 1을 리턴하는 코드가 들어감.)



#include "stdio.h"
#include "Windows.h"

ULONG G_nValid;
int main()
{
	char *pAccessMem;

	__try
	{
		pAccessMem = (char *)0;
		*pAccessMem = 'a';
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
		pAccessMem = (char *)&G_nValid;
		printf("Exception 실행 ... \n");
	}

	return 0;
}



exception 처리 순서는 다음과 같다.


[*] exception 발생 -> VC++의 __exception_handler3 호출 -> scopetable의 Filter 호출 -> 반환값에 따라 현재 테이블의 핸들러를 호출하거나 다음 테이블로 넘어감.





- 0x4112b0에서 1을 리턴하는 코드가 Filter함수 부분이고 0x4112b6에서 사용자가 만든 핸들러 코드가 수행됨.





[*] 추가적으로 ScopeTable의 Filter나 Handler부분의 함수를 다른 주소로 변조해보니 아래와 같이 exception_handler3 내부에서 아래와 같이 SUB, AND를 사용해서 주소 영역을 체크하는 루틴이 존재하여 하위 12byte밖에 수정할 수 없었다.









[ref]


- Windows 구조와 원리 - OS를 관통하는 프로그래밍의 원리

- http://passket.tistory.com/32