简单说一下异常处理在ctf中是如何体现的
SEH VEH
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
| #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #include <stdlib.h>
typedef PVOID(NTAPI* FnAddVectoredExceptionHandler)(ULONG, _EXCEPTION_POINTERS*); FnAddVectoredExceptionHandler MyAddVectoredExceptionHandler;
LONG NTAPI VectExcepHandler(PEXCEPTION_POINTERS pExcepInfo) { if (pExcepInfo->ExceptionRecord->ExceptionCode == 0xC0000094) { pExcepInfo->ContextRecord->Ecx = 2; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; }
int main() { HMODULE hModule = GetModuleHandle(L"Kernel32.dll"); MyAddVectoredExceptionHandler = (FnAddVectoredExceptionHandler)::GetProcAddress(hModule, "AddVectoredExceptionHandler"); MyAddVectoredExceptionHandler(0, (_EXCEPTION_POINTERS*)&VectExcepHandler); int val = 0; _asm { xor edx, edx xor ecx, ecx mov eax, 100 idiv ecx mov val, eax } printf("val = %d\n", val); getchar(); }
|
上面是VEH

主要是除0后调试器不会自动跟进异常处理函数中会直接跑起来
出题人往往在异常处理函数中写一些反调试
例题如SCTF2019某道逆向,一个AES,异常处理函数中判断是否调试然后改变密文
但那题是SEH不是VEH,总体形式很像
写起来不难,在异常处理函数中下断点就行,下面来介绍如何找到异常处理函数,要学会逆向首先得知道正向如何写
手动挂入SEH
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 62 63 64 65 66 67
| #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #include <stdlib.h>
struct _EXCEPTION { struct _EXCEPTION* Next; DWORD Handler; };
EXCEPTION_DISPOSITION _cdecl MyEexception_handler ( _In_ struct _EXCEPTION_RECORD* _ExceptionRecord, _In_ void* _EstablisherFrame, _Inout_ struct _CONTEXT* _ContextRecord, _Inout_ void* _DispatcherContext ) { if (_ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { _ContextRecord->Ecx = 2; return ExceptionContinueExecution; }
return ExceptionContinueSearch; }
int main() { DWORD temp; _EXCEPTION Exception;
_asm { mov eax, fs: [0] mov temp, eax lea ecx, Exception mov fs : [0] , ecx } Exception.Next = (_EXCEPTION*)temp; Exception.Handler = (DWORD)&MyEexception_handler;
int val = 0; _asm { xor edx, edx xor ecx, ecx mov eax, 4 idiv ecx mov val, eax } printf("val = %d\n", val); _asm { mov eax, temp mov fs : [0] , eax } getchar(); }
|
上面就是手动挂seh代码,因为有safeseh保护vs2005以上版本都不能手动挂了,有绕过方式直接hook 系统seh处理函数即可。但这和本文讲的无关,关闭safeseh即可



总的来说是要关注对fs:0的操作,seh挂入就是通过挂入fs:0,然后将Exception.Next改为原始fs:0指向
一般出题是的异常处理都不是像上面一样手动挂的,都是利用编译器支持的SEH
也就是try except
1 2 3 4 5 6 7 8
| _try 1) 挂入链表 { } _except(过滤表达式) 2) 异常过滤 { 异常处理程序 3) 异常处理程序 }
|
编译器扩展SEH
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
| #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #include <stdlib.h> int main() { int val = 0; __try { _asm { xor edx, edx xor ecx, ecx mov eax, 100 idiv ecx mov val, eax } } _except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { printf("Exception Handling in CTF\n"); } printf("val = %d\n", val); getchar(); }
|

这种在ctf中就比较常见了
当然还能换个形式
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
| #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #include <stdlib.h>
int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo) { if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { pExceptionInfo->ContextRecord->Ecx = 2; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; }
int main() { int val; _try { _asm { xor edx,edx xor ecx,ecx mov eax,100 idiv ecx mov val,eax } } _except(ExceptFilter(GetExceptionInformation())) { printf("Exception-Handling-in-CTF\n"); } printf("val = %d\n", val); getchar(); }
|

注意上面这段代码,下面来介绍下这段代码具体干了些啥
1 2 3 4
| typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next; PEXCEPTION_ROUTINE Handler; } EXCEPTION_REGISTRATION_RECORD;
|
每写一个try except,编译器会自动帮我们挂入一个_EXCEPTION_REGISTRATION_RECORD结构体,写一个还行,如果写多个嵌套try except,那就得挂多个结构体,很占空间,所以就有了拓展 _EXCEPTION_REGISTRATION_RECORD结构体
1 2 3 4 5 6 7
| struct _EXCEPTION_REGISTRATION{ struct _EXCEPTION_REGISTRATION *prev; void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD); struct scopetable_entry *scopetable; int trylevel; int _ebp; };
|


1 2 3 4 5 6
| struct scopetable_entry { DWORD previousTryLevel PDWRD lpfnFilter PDWRD lpfnHandler }
|
在ida中查看

loc_9D108A

即是过滤函数

loc_9D10C1就是except中的代码了

写一个try可能看不出有啥用,嵌套几个即可
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #include <stdlib.h>
int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo) { if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { pExceptionInfo->ContextRecord->Ecx = 2; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; }
int main() { int val; _try { _asm { xor edx,edx xor ecx,ecx mov eax,100 idiv ecx mov val,eax } _try { _asm { xor edx,edx xor ecx,ecx mov eax,50 idiv ecx mov val,eax } _try { _asm { xor edx,edx xor ecx,ecx mov eax,10 idiv ecx mov val,eax } } _except(ExceptFilter(GetExceptionInformation())) { printf("Exception-Handling-in-CTF\n"); } } _except(ExceptFilter(GetExceptionInformation())) { printf("Exception-Handling-in-CTF\n"); } } _except(ExceptFilter(GetExceptionInformation())) { printf("Exception-Handling-in-CTF\n"); } _try { _asm { xor edx, edx xor ecx, ecx mov eax, 10 idiv ecx mov val, eax } } _except(ExceptFilter(GetExceptionInformation())) { printf("Exception-Handling-in-CTF\n"); }
printf("val = %d\n", val); getchar(); }
|

在进入不同异常处理时都会赋值不同try level

操作系统根据根据trylevel 选择scopetable数组然后调用scopetable数组中对应的lpfnFilter函数,如果lpfnFilter函数返回0 向上遍历 直到previousTryLevel=-1。
未处理异常
这种在ctf出现的话就比上面稍微难一些
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
| #define _CRT_SECURE_NO_DEPRECATE #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #include <stdlib.h>
long _stdcall ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo) { if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { pExceptionInfo->ContextRecord->Ecx = 2; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; }
int main() { SetUnhandledExceptionFilter(ExceptFilter); int val; _try { _asm { xor edx,edx xor ecx,ecx mov eax,100 idiv ecx mov val,eax } } _except(UnhandledExceptionFilter(GetExceptionInformation())) { } printf("val = %d\n", val); getchar(); }
|
直接运行没啥问题

但是调试的话就会闪退
在这种情况下只有程序不在调试时才会去处理异常
1 2 3 4 5 6 7
| UnhandledExceptionFilter的执行流程:
1.通过NtQueryInformationProcess查询当前进程是否正在被调试,如果是,返回EXCEPTION_CONTINUE_SEARCH(0),此时会进入第二轮分发 2.如果没有被调试: 查询是否通过SetUnhandledExceptionFilter注册处理函数 如果有就调用 如果没有通过SetUnhandledExceptionFilter注册处理函数 弹出窗口 让用户选择终止程序还是启动即时调试器 如果用户没有启用即时调试器,那么该函数返回EXCEPTION_EXECUTE_HANDLER
|
这个函数是Ntdll.dll中一个原生态API,它用来提取一个给定进程的信息。它的第一个参数是进程句柄,第二个参数告诉我们它需要提取进程信息的类型。为第二个参数指定特定值并调用该函数,相关信息就会设置到第三个参数。第二个参数是一个枚举类型,其中与反调试有关的成员有ProcessDebugPort(0x7)、ProcessDebugObjectHandle(0x1E)和ProcessDebugFlags(0x1F)。例如将该参数置为ProcessDebugPort,如果进程正在被调试,则返回调试端口,否则返回0
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
| BOOL CheckDebug() { int debugPort = 0; HMODULE hModule = LoadLibrary("Ntdll.dll"); NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess"); NtQueryInformationProcess(GetCurrentProcess(), 0x7, &debugPort, sizeof(debugPort), NULL); return debugPort != 0; } BOOL CheckDebug() { HANDLE hdebugObject = NULL; HMODULE hModule = LoadLibrary("Ntdll.dll"); NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess"); NtQueryInformationProcess(GetCurrentProcess(), 0x1E, &hdebugObject, sizeof(hdebugObject), NULL); return hdebugObject != NULL; } BOOL CheckDebug() { BOOL bdebugFlag = TRUE; HMODULE hModule = LoadLibrary("Ntdll.dll"); NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess"); NtQueryInformationProcess(GetCurrentProcess(), 0x1E, &bdebugFlag, sizeof(bdebugFlag), NULL); return bdebugFlag != TRUE; }
|
总结
