FS寄存器指向当前活动线程的TEB结构(线程结构)
偏移 说明
000 指向SEH链指针
004 线程堆栈顶部
008 线程堆栈底部
00C SubSystemTib
010 FiberData
014 ArbitraryUserPointer
018 FS段寄存器在内存中的镜像地址(TEB)
020 进程PID
024 线程ID
02C 指向线程局部存储指针
030 PEB结构地址(进程结构)
034 上个错误号
得到KERNEL32.DLL基址的方法
assume fs:nothing ;打开FS寄存器
mov eax,fs:[30h] ;得到PEB结构地址
mov eax,[eax + 0ch] ;得到PEB_LDR_DATA结构地址
mov esi,[eax + 1ch] ;InInitializationOrderModuleList
lodsd ;得到KERNEL32.DLL所在LDR_MODULE结构的InInitializationOrderModuleList地址
mov edx,[eax + 8h] ;得到BaseAddress,既Kernel32.dll基址
S.E.H结构原理:
首先确定自己的链表知识回调函数知识已经补充.
S.E.H结构包含两个DWORD指针:SEH链表指针和异常处理函数句柄,8个字节,8个要点.
1)SEH结构体存放在系统栈;
2)线程初始化将自动向栈里安装一个SEH结构,作为默认线程异常处理.
3)如果程序源代码中使用了_try{}和_excopt{}或者宏等异常处理机制,编译器将最终通过向当前函数栈安装一个SEH来实现异常处理.
4)栈中一般会同时存在多个SEH.
5)栈中的多个SEH通过链表指针在栈内由栈顶向栈底穿成单向链表,位于链表最顶端的SEH通过TEB 0字节处的指针来标识.
6)当异常发生时,操作系统会中断程序,并首先从TEB的 0字节偏移处取得最近的SEH,使用异常处理函数句柄所指向的代码来处理异常.
7)当离异常最近的异常处理函数不能处理时,将沿顺SEH链表逐步尝试.
8)如果程序所安装的异常处理函数都不能解决,系统将采用默认的异常处理函数来处理,通常弹出错误对话框,强制关闭程序.
根据以上8个要点可以总结出以下处理顺序:
①--->首先执行线程异常处理函数及最近的SEH.
②失败后将执行链表中的下一个异常处理函数,只止于NULL而不能解决.
③执行进程异常处理SEH
④执行系统异常处理SEH.
线程异常处理:2个状态值,参数无须解释.值得一提的是unwind操作.清理现场,释放资源技巧可以借鉴.
unwind操作通过kernerl.32中的函数rtlunwind()来实现.
进程异常处理:3种状态值.进程的异常处理回调函数需要使用kernerl32.dll中的函数setunhandledexceptionfliter()函数来注册.
系统默认异常处理:终极BOSS也称UEF.如果进程异常处理函数失败或者进程异常处理函数没有注册,系统异常处理函数unhandledexptionfilter()将被调用,所有的异常都将被捕获.
首先这个函数检查注册表,HKLM/SOFTWARE/MICROSOFT/windowsnt/cv/aedebug.其中值得一提的是其中的参数.指定了程序调试器.
ring3中第一个异常处理函数为kiuserexceptiondispatcher(),其首先检查调试状态,遍历SEH表,unwind操作,
然后失败调用终极BOSS.winxp增加了VEH.其中VEH与SEH不同的一个显著特点是,其为双向链表.其处理级别要高于SEH并保存在堆内.unwind操作对起不起作用.
对于Ring3的应用程序,fs:[0]的地址指向的是TEB结构,这个结构的开头是一个NT_TIB结构,NT_TIB结构的0x18偏移处是一个Self指针,指向这个结构自身,也就是指向TEB结构的开头。
TEB结构的0x30偏移是一个指向PEB的指针。PEB又是一个结构,这个结构的0x2偏移处是一个UChar,名叫BeingDebugged,当进程被调试时,此值为1,未被调试时此值为0
因此以下代码逐行执行后的结果:
mov eax,dword ptr fs:[18h];eax=TEB的指针
mov eax,dword ptr [eax+30h];eax=PEB的指针
movzx eax,byte ptr [eax+2h];eax=PEB.BeingDebugged(byte扩展为dword)
TEB和PEB结构的详细内容可以在windbg内核调试状态下使用dt _TEB、dt _PEB命令来察看。
MASM中默认是fs:error,也就是默认不能使用fs段寄存器,因此要在masm中使用它时必须先assume fs:nothing
fs是段寄存器,即保存段选择子,对应的地址通过GDT或LDT中的相应项目来决定其范围和使用权限等。
最后回到IsDebuggerPresent,它就是通过检查PEB中的BeingDebugged字段来确定进程是否处于被调试状态的,因此修改此字段可以直接影响此API的返回值。
以上内容在调试或者说软件加密与解密过程中是基本的常识。