Windows捕获会引起程序崩溃的异常

1.给空指针赋值引起的异常(__try __except)

访问冲突(Access Violation)

  • 这通常发生在尝试访问无效或不可访问的内存地址时,例如空指针解引用或访问已释放的内存。
  • 示例:int* ptr = NULL; *ptr = 5;free(ptr); ptr = NULL; *ptr = 10;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <windows.h>
#include <stdio.h>

void test() {
__try {
// 这里执行可能会抛出异常的代码
int* ptr = NULL;
*ptr = 42; // 这会触发访问冲突异常
}
__except (EXCEPTION_EXECUTE_HANDLER) {
// 捕获异常并处理
printf("An exception occurred!\n");
}
}

int main() {
printf("Hello, World!\n");
test();
return 0;
}

2.修改常量引起的异常(__try __except)

  • 如果程序尝试修改一个常量,通常会遇到内存保护错误。
  • 示例:const int x = 10; x = 20; 这会引起访问保护错误,因为常量被存储在只读区域。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <windows.h>
#include <stdio.h>

const int c_Var = 0x400000;

int main(int argc, char* argv[])
{
printf("%d\n", c_Var);
DWORD dwOldProtect = 0;
__try
{
__asm
{
mov dword ptr[c_Var], 634
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("Exception caught while executing arry.\n");
}

printf("%d\n", c_Var);
return 0;
}

3.执行不可执行的内存区域引起的异常(__try __except)

例如数组的地址是可读可写不可执行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <windows.h>

// 数组里是非法指令,模拟非法执行
unsigned char arry[] = { 0xC3 }; // RET 指令,实际上是安全的,但可以换成非法指令看效果

void test()
{
__try
{
void (*func)() = (void(*)())arry;
func(); // 调用非法地址
printf("Function call succeeded.\n");
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("Exception caught while executing arry.\n");
}
}

int main()
{
printf("Hello, World!\n");
test();
return 0;
}

🧠 各种分配方式的默认执行权限:

分配方式 默认是否可执行 说明
std::vector ❌ 否 在堆上分配,内存标记为 RW(读写),不可执行(受到 DEP 保护)
malloc ❌ 否 vector 类似,分配到堆,默认 RW
new ❌ 否 malloc 类似,不可执行
局部变量(栈上) ❌ 否 栈内存默认不可执行
VirtualAlloc ✅ 可设置 可以手动指定 PAGE_EXECUTE_READWRITEPAGE_EXECUTE_READ 等权限
mmap(Linux) ✅ 可设置 可以指定 PROT_EXEC 使内存可执行

🧠 哪些内存区域可以设置为可执行?

内存区域 描述 可设置为可执行? 推荐使用?
.text 编译后的代码段 ✅ 是 ✅ 系统自动设置
.data 全局变量(可读写) ⛔ 否(通常 NX)
.rdata 常量数据(只读) ⛔ 否
栈(stack) 函数局部变量 ⛔ 否
堆(heap) malloc / new 分配的 ⛔ 否
VirtualAlloc 显式请求的一段内存 ✅ 是 ✅ 推荐
内存映射文件区 CreateFileMapping + MapViewOfFile ✅ 可配置 ⚠️ 灵活复杂

4.写入异常捕获更改自身内存属性,使自身程序不崩溃_try __except

windows函数默认是可读可执行不可写,修改指令需要先设置内存属性为可读可写可执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <windows.h>
#include <stdio.h>

void Demo()
{
}
int main() {
DWORD oldProtect;
// 假设我们要修改某个内存区域的保护属性
LPVOID address = (LPVOID)&Demo; // 示例地址
printf("%p\n", Demo);
unsigned char data[] = { 0xC3,0xC3};
__try
{
memcpy(address, data, sizeof(data));
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
SIZE_T size = sizeof(data); // 示例大小
if (VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("Memory protection changed successfully.\n");
memcpy(address, data, sizeof(data));
}
else
{
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
}
VirtualProtect(address, size, oldProtect, &oldProtect);
}
Demo();
getchar();
return 0;
}

5.写入异常捕获更改自身内存属性(AddVectoredExceptionHandler)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <windows.h>
#include <stdio.h>

PVOID g_handler = NULL;

LONG WINAPI VectoredHandler(PEXCEPTION_POINTERS pExceptionInfo)
{
printf("Exception code: 0x%X at address: %p\n",
pExceptionInfo->ExceptionRecord->ExceptionCode,
pExceptionInfo->ExceptionRecord->ExceptionAddress);

if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
// 修改内存保护
DWORD oldProtect;
SIZE_T size = 2; // 写入数据大小
LPVOID address = (LPVOID)pExceptionInfo->ExceptionRecord->ExceptionInformation[1];

if (VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("Memory protection changed successfully inside exception handler.\n");
return EXCEPTION_CONTINUE_EXECUTION; // 继续执行出错指令
}
else
{
printf("Failed to change memory protection.\n");
return EXCEPTION_CONTINUE_SEARCH;
}
}

return EXCEPTION_CONTINUE_SEARCH; // 如果不是访问异常,就继续默认异常处理
}

void Demo()
{
}

int main()
{
// 注册异常处理器
g_handler = AddVectoredExceptionHandler(1, VectoredHandler);

LPVOID address = (LPVOID)&Demo;
printf("Demo address: %p\n", address);

unsigned char data[] = { 0xC3, 0xC3 }; // 两个 ret 指令
size_t size = sizeof(data);

memcpy(address, data, size); // 正常写,如果保护问题,会触发异常,进 VectoredHandler

// 测试调用
Demo();

// 卸载异常处理器
if (g_handler)
RemoveVectoredExceptionHandler(g_handler);

getchar();
return 0;
}

6.修改函数内存页属性后运行函数引起的异常(AddVectoredExceptionHandler)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <windows.h>
#include <stdio.h>

void Demo()
{
char str[] = "Demo******************";
printf("%s\n", str);
}

LONG CALLBACK VehHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION)
{
printf("[VEH] Caught PAGE_GUARD at address: %p\n", ExceptionInfo->ExceptionRecord->ExceptionAddress);

return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}

int main()
{
AddVectoredExceptionHandler(1, VehHandler);

DWORD oldProtect;
LPVOID address = (LPVOID)&Demo;

SYSTEM_INFO si;
GetSystemInfo(&si);
DWORD pageSize = si.dwPageSize;
LPVOID pageAddress = (LPVOID)((uintptr_t)address & ~(pageSize - 1));

// 原保护
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(pageAddress, &mbi, sizeof(mbi));
DWORD newProtect = (mbi.Protect & 0xFF) | PAGE_GUARD;

if (!VirtualProtect(pageAddress, pageSize, newProtect, &oldProtect))
{
printf("Failed to change memory protection. Error: %lu\n", GetLastError());
return 1;
}

printf("[*] PAGE_GUARD set. Now calling Demo...\n");

Demo(); // 会被 VEH 捕获异常

printf("[*] Finished.\n");

getchar();
return 0;
}

7.只有msvc会优化_try __except,其他编译器需要使用**AddVectoredExceptionHandler** 来安装自己的异常处理回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <windows.h>
#include <stdio.h>

LONG CALLBACK MyExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
{
printf("[VEH] Exception code: 0x%X\n", ExceptionInfo->ExceptionRecord->ExceptionCode);

if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
DWORD oldProtect;
LPVOID address = (LPVOID)ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
SIZE_T size = 2;

if (VirtualProtect(address, size, PAGE_EXECUTE_READWRITE, &oldProtect))
{
printf("[VEH] Memory protection changed successfully.\n");
return EXCEPTION_CONTINUE_EXECUTION; // 继续执行
}
}
return EXCEPTION_CONTINUE_SEARCH; // 继续搜索其他处理器
}

// 禁止优化
__declspec(noinline) void Demo()
{
volatile int dummy = 0;
dummy++;
}

int main()
{
PVOID handler = AddVectoredExceptionHandler(1, MyExceptionHandler);

LPVOID address = (LPVOID)&Demo;
printf("Demo address: %p\n", address);

unsigned char data[] = { 0xC3, 0xC3 };
memcpy(address, data, sizeof(data)); // 如果崩溃,就进入 MyExceptionHandler

Demo();

RemoveVectoredExceptionHandler(handler);

getchar();
return 0;
}

8.其他行为引起的异常