유저모드에서 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; }
JMP 0x0015003c 부분을 따라 들어가면 사용자가 설치한 exception 코드가 실행되고 다음으로 ZwContinue를 호출하여 원래 실행 컨텍스트로 돌아간다. (ZwContinue의 첫 번째 인자가 CONTEXT구조체를 가지는데 해당 구조체의 0xb8번째가 원래의 eip 포인터이다.)
- 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의 구조는 아래와 같다.
#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
'Reversing > Reversing' 카테고리의 다른 글
code injection에서 shellcode to exe (0) | 2014.01.15 |
---|---|
64bit환경의 인자값 전달 방식 (0) | 2013.12.15 |
IDA로 C++ 클래스의 가상함수를 볼 때 (0) | 2013.12.15 |
IDA - 변수에 따른 데이터 영역 정리 (0) | 2013.12.14 |
무료 .NET Decompiler (1) | 2013.11.04 |