exception
CPU 예외 발생시 VEH 전역체인에 등록된 순서대로 호출. SEH는 __try/__except 로 처리하며 스택프레임별로 체인이 만들어짐.
// veh_seh_cpp.cpp
// 데모 목적: VEH(전역) → SEH(스택 프레임) → C++ 예외(언어 레벨) 흐름을 비교
// 빌드: cl /EHsc /Zi veh_seh_cpp.cpp (또는 동작 비교용 /EHa)
#include <windows.h>
#include <excpt.h> // __try/__except, GetExceptionInformation
#include <iostream>
#include <stdexcept>
#include <string>
#include <cstdio>
static LONG CALLBACK VectoredHandler(PEXCEPTION_POINTERS ep)
{
DWORD code = ep->ExceptionRecord->ExceptionCode;
std::cout << "[VEH] First-chance: code=0x" << std::hex << code << std::dec << "\n";
// 로그만 남기고 계속 아래 체인(SEH, Unhandled)으로 넘김
return EXCEPTION_CONTINUE_SEARCH;
// 만약 여기서 복구가 가능하고 실제로 계속 실행하고 싶다면(매우 특수한 경우)
// return EXCEPTION_CONTINUE_EXECUTION; // <- 위험! 레지스터/상태 복구 필요
}
static int SehFilter(PEXCEPTION_POINTERS ep)
{
DWORD code = ep->ExceptionRecord->ExceptionCode;
std::cout << "[SEH] Handled: code=0x" << std::hex << code << std::dec << "\n";
// 이 프레임에서 처리하고 언와인딩 진행
return EXCEPTION_EXECUTE_HANDLER;
// 디버깅 위해 계속 탐색시키고 싶다면:
// return EXCEPTION_CONTINUE_SEARCH;
}
static void demo_access_violation_with_SEH()
{
std::cout << "== 1) SEH로 접근위반(AV) 처리 ==\n";
__try
{
std::cout << " (SEH 블록 내부) 의도적 AV 발생\n";
volatile int* p = nullptr;
*p = 1; // AV
std::cout << " 이 줄은 실행되지 않아야 함\n";
}
__except (SehFilter(GetExceptionInformation()))
{
std::cout << " [SEH] 예외 처리 후 안전하게 계속 진행\n";
}
}
static void demo_raiseexception_with_SEH()
{
std::cout << "== 2) SEH로 소프트웨어 예외 처리(RaiseException) ==\n";
__try
{
std::cout << " RaiseException(0xE0000001) 호출\n";
RaiseException(0xE0000001, 0, 0, nullptr);
std::cout << " 이 줄은 실행되지 않아야 함\n";
}
__except (SehFilter(GetExceptionInformation()))
{
std::cout << " [SEH] RaiseException 처리 완료\n";
}
}
static void demo_cpp_exception()
{
std::cout << "== 3) C++ 예외 try/catch ==\n";
try
{
std::cout << " throw std::runtime_error\n";
throw std::runtime_error("cpp error");
}
catch (const std::exception& e)
{
std::cout << " [C++] caught std::exception: " << e.what() << "\n";
}
catch (...)
{
std::cout << " [C++] caught unknown\n";
}
}
static void demo_seh_as_cpp_exception()
{
std::cout << "== 4) (옵션) SEH를 C++처럼 catch(...)로 잡기 (/EHa 필요) ==\n";
try
{
std::cout << " 의도적 AV 발생 (nullptr write)\n";
volatile int* p = nullptr;
*p = 1; // AV
std::cout << " 이 줄은 /EHa 아닐 땐 실행되지 않거나, 프로세스 크래시로 끝날 수 있음\n";
}
catch (const std::exception& e)
{
std::cout << " [C++ under /EHa] std::exception: " << e.what() << "\n";
}
catch (...)
{
std::cout << " [C++ under /EHa] caught non-C++ exception (likely SEH)\n";
}
}
int main()
{
// 0) VEH 등록: First-chance에서 모든 예외를 "가장 먼저" 관찰
PVOID veh = AddVectoredExceptionHandler(/*FirstHandler=*/1, VectoredHandler);
if (!veh) {
std::cerr << "AddVectoredExceptionHandler failed\n";
return 1;
}
std::cout << "[Init] VEH installed\n\n";
// 1) SEH로 하드웨어 예외demo_access_violation_(AV) 처리
demo_access_violation_with_SEH();
std::cout << "\n";
// 2) SEH로 RaiseException 처리
demo_raiseexception_with_SEH();
std::cout << "\n";
// 3) C++ 예외
demo_cpp_exception();
std::cout << "\n";
// 4) (/EHa 빌드 시) SEH를 C++ catch로도 포착되는지 비교
std::cout << " (다음 동작은 /EHa 일 때만 C++ catch에 걸릴 수 있음)\n";
demo_seh_as_cpp_exception();
std::cout << "\n";
// 마무리
if (veh) RemoveVectoredExceptionHandler(veh);
std::cout << "[Done]\n";
return 0;
}
실행결과
모든 예외는 VEH에서 먼저 잡히고, 이후에 SEH나 C++예외에서 잡힌다.
SEH는 필터가 받아서 처리하고 일반 예외는 catch에서 처리한다.
[Init] VEH installed
== 1) SEH로 접근위반(AV) 처리 ==
(SEH 블록 내부) 의도적 AV 발생
[VEH] First-chance: code=0xc0000005
[SEH] Handled: code=0xc0000005
[SEH] 예외 처리 후 안전하게 계속 진행
== 2) SEH로 소프트웨어 예외 처리(RaiseException) ==
RaiseException(0xE0000001) 호출
[VEH] First-chance: code=0xe0000001
[SEH] Handled: code=0xe0000001
[SEH] RaiseException 처리 완료
== 3) C++ 예외 try/catch ==
throw std::runtime_error
[VEH] First-chance: code=0xe06d7363
[C++] caught std::exception: cpp error
(다음 동작은 /EHa 일 때만 C++ catch에 걸릴 수 있음)
== 4) (옵션) SEH를 C++처럼 catch(...)로 잡기 (/EHa 필요) ==
의도적 AV 발생 (nullptr write)
[VEH] First-chance: code=0xc0000005
SEH VEH는 체인 형태라서 SEH는 스택처럼 최신등록한것부터 실행되고, VEH는 등록한순서대로 실행된다. SEH는 커스텀이 가능함.
최신 등록이라는거는 그냥 내부 exception이 실행된다는 의미임.
VEH든 SEH든 EXCEPTION_CONTINUE_SEARCH 이걸 리턴하면 다음 핸들러에서 처리하겠다는 의미임. EXCEPTION_EXECUTE_HANDLER 이걸 리턴하면 처리가 완료됐다는 의미라서 다음 예외체인에 전파되지 않는다.
// seh_chain_demo.cpp
#include <windows.h>
#include <excpt.h>
#include <iostream>
static LONG CALLBACK VehHandler1(PEXCEPTION_POINTERS ep) {
std::cout << "[VEH1] called, code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
// 계속 아래로 넘김
return EXCEPTION_CONTINUE_SEARCH;
}
static LONG CALLBACK VehHandler2(PEXCEPTION_POINTERS ep) {
std::cout << "[VEH2] called, code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
return EXCEPTION_CONTINUE_SEARCH;
}
int nestedSehFilterA(PEXCEPTION_POINTERS ep) {
std::cout << " [SEH A filter] code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
// 여기서 처리하자
return EXCEPTION_EXECUTE_HANDLER;
}
int nestedSehFilterB(PEXCEPTION_POINTERS ep) {
std::cout << " [SEH B filter] code=0x" << std::hex << ep->ExceptionRecord->ExceptionCode << std::dec << "\n";
// A는 처리하지 않고 다음(밖)의 SEH로 넘김
return EXCEPTION_CONTINUE_SEARCH;
}
int main() {
// VEH 등록 순서: 먼저 등록한게 먼저 호출됨(우리는 1->2로 등록)
PVOID v1 = AddVectoredExceptionHandler(1, VehHandler1);
PVOID v2 = AddVectoredExceptionHandler(1, VehHandler2);
if (!v1 || !v2) {
std::cerr << "VEH 등록 실패\n";
return 1;
}
std::cout << "VEH 등록 완료\n\n";
std::cout << "---- 예외 유도: 중첩 SEH 데모 ----\n";
// 바깥 SEH (A)
__try {
std::cout << "Entered SEH A block\n";
// 안쪽 SEH (B)
__try {
std::cout << " Entered SEH B block\n";
// 의도적 접근 위반
volatile int* p = nullptr;
*p = 123; // AV 발생
std::cout << " (이 지점은 실행되지 않음)\n";
}
__except (nestedSehFilterB(GetExceptionInformation())) {
std::cout << " [SEH B handler] after filter returned EXCEPTION_EXECUTE_HANDLER\n";
}
std::cout << "Exited SEH B block\n";
}
__except (nestedSehFilterA(GetExceptionInformation())) {
std::cout << "[SEH A handler] after filter\n";
}
std::cout << "Program continues after SEH blocks\n";
// VEH 제거
RemoveVectoredExceptionHandler(v2);
RemoveVectoredExceptionHandler(v1);
return 0;
}
실행해보면 아래와같다. 모든 핸들러가 다 실행됨.
VEH 등록 완료
---- 예외 유도: 중첩 SEH 데모 ----
Entered SEH A block
Entered SEH B block
[VEH2] called, code=0xc0000005
[VEH1] called, code=0xc0000005
[SEH B filter] code=0xc0000005
[SEH A filter] code=0xc0000005
[SEH A handler] after filter
Program continues after SEH blocks
리버싱하는 입장에서는 코드 흐름이 비정상적으로 변경돼서 복잡하게 느낄 수 있다.
일부러 코드에서 예외 발생시켜서 예외핸들러로 넘김
핸들러에서 보안로직을 실행하거나 코드를 복원하는 등의 작업을 함
Comments