|
发表于 2020-9-6 10:25:25
|
查看: 7299 |
回复: 0
动态反调试 本文介绍了几种常见的动态反调试技术,在阅读之前您可能要了解一些静态反调试手段.
前文 : 常见静态反调试技术总结
本文实例所用资源 : https://download.csdn.net/download/weixin_45551083/12555778
文章目录
- 动态反调试
- 1 SEH
- 1.0 SEH基本概念
- 1.1 异常
- (1) 几个常见异常
- (2) 异常编码对照表
- (3) OS 的异常处理方式
- 1.2 SEH详细说明
- (1) SEH链
- (2)异常处理函数
- (3) 访问SEH链 - TEB.NtTib.ExceptionList
- (4) SEH安装
- 1.3 调试seh.exe
- 1.4 OllyDbg中针对SEH的设置
- 2 Time Checking
- 3 陷阱标志TF
- 4 INT 2D
- 5 0xCC探测
1 SEH1.0 SEH基本概念基本概念 百度百科:
SEH(“Structured Exception Handling”),即结构化异常处理·是(windows)操作系统提供给程序设计者的强有力的处理程序错误或异常的武器。
SEH是Windows操作系统默认的异常处理机制,逆向分析中,SEH除了基本的异常处理功能外,还大量运用于反调试程序. 就是异常时会启动SEH,SEH里面包括了处理异常的代码.
与C语言中的__try,__except,finally等处理机制类似,不过SEH要早于C语言中的异常处理
示例程序 seh.exe 地址0x401019处尝试向DS:[0]处写入数据,引发非法访问异常(较为常见的一种异常)
常见动态反调试技术总结
正常运行:
常见动态反调试技术总结
调试器下 : F9运行程序,调试器会暂停在此处,调试器下方会提示
常见动态反调试技术总结
Shift+F7/F8/F9 运行
常见动态反调试技术总结
调试器将异常交给SEH处理,而SEH中又有反调试技术,所以程序显示Debugger detected
1.1 异常(1) 几个常见异常- EXCEPTION_ACCESS_VIOLATION ( 0xC0000005 )
试图访问不存在或不具有访问权限的内存区域时,就会发生EXCEPTION_ACCESS_VIOLATION(非法访问,较为常见)
举例:
MOV DWORD PTR DS:[0],1 ;内存地址0处是未分配的区域
ADD DWORD PTR DS:[401000],1;.text节区起始地址0x401000仅具有读权限,无写权限
XOR DWORD PTR DS:[80000000],1234;内存地址0x80000000属于内核区域,用户无法访问
- EXCEPTION_BREAKPOINT ( 0x80000003 )
运行代码中设置断点后,CPU尝试执行该地址处的指令是,将发生EXCEPTION_BREAKPOINT异常.
调试器就是利用该异常实现断点功能. 设置断点时会将该位置设置为0xCC(int 3) 但是为了方便代码可读性,并不会显示出来(仍然是最开始的指令).
- EXCEPTION_ILLEGAL_INSTRUCTION ( 0xC000001D )
CPU遇到无法解析的指令时引发该异常,比如"0FFFF"指令在x86CPU中未定义,CPU遇到该指令将引发EXCEPTION_ILLEGAL_INSTRUCTION异常.
- EXCEPTION_INT_DIVIDE_BY_ZERO ( 0xC0000094 )
整数除法运算中,若分母为0,则引发EXCEPTION_INT_DIVIDE_BY_ZERO异常
- EXCEPTION_SINGLE_STEP ( 0x80000004 )
单步的含义是执行一条指令,然后暂停.CPU进入单步模式后,每执行一条指令就会引发EXCEPTION_SINGLE_STEP异常,暂停运行.将EFLAGS寄存器的TF位设置为1后,CPU就会进入单步模式
(2) 异常编码对照表异常值描述EXCEPTION_ACCESS_VIOLATION0xC0000005程序企图读写一个不可访问的地址时引发的异常。例如企图读取0地址处的内存。EXCEPTION_ARRAY_BOUNDS_EXCEEDED0xC000008C数组访问越界时引发的异常。EXCEPTION_BREAKPOINT0x80000003触发断点时引发的异常。EXCEPTION_DATATYPE_MISALIGNMENT0x80000002程序读取一个未经对齐的数据时引发的异常。EXCEPTION_FLT_DENORMAL_OPERAND0xC000008D如果浮点数操作的操作数是非正常的,则引发该异常。所谓非正常,即它的值太小以至于不能用标准格式表示出来。EXCEPTION_FLT_DIVIDE_BY_ZERO0xC000008E浮点数除法的除数是0时引发该异常。EXCEPTION_FLT_INEXACT_RESULT0xC000008F浮点数操作的结果不能精确表示成小数时引发该异常。EXCEPTION_FLT_INVALID_OPERATION0xC0000090该异常表示不包括在这个表内的其它浮点数异常。EXCEPTION_FLT_OVERFLOW0xC0000091浮点数的指数超过所能表示的最大值时引发该异常。EXCEPTION_FLT_STACK_CHECK0xC0000092进行浮点数运算时栈发生溢出或下溢时引发该异常。EXCEPTION_FLT_UNDERFLOW0xC0000093浮点数的指数小于所能表示的最小值时引发该异常。EXCEPTION_ILLEGAL_INSTRUCTION0xC000001D程序企图执行一个无效的指令时引发该异常。EXCEPTION_IN_PAGE_ERROR0xC0000006程序要访问的内存页不在物理内存中时引发的异常。EXCEPTION_INT_DIVIDE_BY_ZERO0xC0000094整数除法的除数是0时引发该异常。EXCEPTION_INT_OVERFLOW0xC0000095整数操作的结果溢出时引发该异常。EXCEPTION_INVALID_DISPOSITION0xC0000026异常处理器返回一个无效的处理的时引发该异常。EXCEPTION_NONCONTINUABLE_EXCEPTION0xC0000025发生一个不可继续执行的异常时,如果程序继续执行,则会引发该异常。EXCEPTION_PRIV_INSTRUCTION0xC0000096程序企图执行一条当前CPU模式不允许的指令时引发该异常。EXCEPTION_SINGLE_STEP0x80000004标志寄存器的TF位为1时,每执行一条指令就会引发该异常。主要用于单步调试。EXCEPTION_STACK_OVERFLOW0xC00000FD栈溢出时引发该异常。(3) OS 的异常处理方式 同一程序在正常运行与调试运行时表现处的行为动作是不同的
进程运行过程中若发生异常,OS会委托进程处理.若进程代码中存在具体的异常处理(如SEH异常处理器)代码,则能顺利处理异常,程序继续运行.
若没有相关的SEH,则就无法处理,OS会启动默认的异常处理机制,终止进程运行.
(SEH处理代码和默认的异常处理机制中均可以加入反调试代码)
调试运行中发生异常时,OS会首先把异常抛给调试器(调试器就会暂停运行)
调试时遇到异常的几种处理方法:
(a) 直接修改异常有关的代码,寄存器,内存.(如用NOP填充异常代码)
(b) OllyDbg中的Shift+F7/F8/F9,将异常抛给被调试进程
(c ) 终止调试进程,终止调试
1.2 SEH详细说明(1) SEH链 SEH以链表的形式存在.链中存在一系列异常处理器.遇到异常先由第一个异常处理器处理,若第一个异常处理器未正常处理异常,则交由链上的第二个异常处理器,直到得到处理.(最后一个异常处理器会终止进程)
代码层面: SEH是由_EXCEPTION_REGISTER_RECODE 结构体构成的链表
_EXCEPTION_REGISTER_RECODE 声明:
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
PEXCEPTION_REGISTRATION_RECORD Next;//指针,指向下一个_EXCEPTION_REGISTER_RECODE结构体
PEXCEPTION_DISPOSITION Handler;//异常处理函数(异常处理器)地址
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
若Next成员的值为0xFFFFFFFF,则表示它是链表最后一个节点.
常见动态反调试技术总结
(2)异常处理函数 定义:
EXCEPTION_DISPOSITION _except_handler
(
EXCEPTION_RECORD*pRecord,
EXCEPTION_REGISTRATION_RECORD *pFrame ,
CONTEXT*pContext,
PVOIDpValue
);
- _except_handler 第一个参数 *pRecord 指向 EXCEPTION_RECORD 结构体的指针
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;//异常代码的值(如EXCEPTION_ACCESS_VIOLATION 就是0xC0000005)
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;//发生异常的代码地址
DWORD NumberParameters;
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
注意ExceptionCode(第一个参数)和ExceptionAddress(第四个参数即可)
- _except_handler 第三个参数*pContext指向Context结构体
CONTENT 线程结构体
typedef struct _CONTEXT
{
DWORD ContextFlags;
DWORD Dr0;//04h
DWORD Dr1;//08h
DWORD Dr2;//0Ch
DWORD Dr3;//10h
DWORD Dr6;//14h
DWORD Dr7;//18h
FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;//88h
DWORD SegFs;//90h
DWORD SegEs;//94h
DWORD SegDs;//98h
DWORD Edi;//9Ch
DWORD Esi;//A0h
DWORD Ebx;//A4h
DWORD Edx;//A8h
DWORD Ecx;//ACh
DWORD Eax;//B0h
DWORD Ebp;//B4h
DWORD Eip;//B8h
DWORD SegCs;//BCh
DWORD EFlags;//C0h
DWORD Esp;//C4h
DWORD SegSs;//C8h
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; //512bytes
} CONTEXT;
多线程环境下,每一个线程内部都有一个CONTEXT结构体用来备份CPU寄存器的值.
CPU离开当前线程去其他线程时,CPU寄存器的值就会保存到当前线程的CONTEXT结构体
CPU再次运行该线程时,会使用保存在CONTEXT结构体中的值来覆盖CPU寄存器的,从EIP处开始继续执行
异常发生时,执行异常代码的线程就会终端运行,转而运行SEH.
此时OS会把当前线程的CONTEXT结构体传递给异常处理函数.
异常处理函数可以修改CONTEXT.Eip的值(偏移B8),设置为其他的地址.这样,之前暂停的线程会执行新设置的EIP地址处的代码.(反调试中可以采用这条技术)
typedf enum _EXCEPTION_DISPOSITION
{
ExceptionContinueExecution=0,//继续执行异常代码,从发生异常处的代码继续运行
ExceptionContinueSearch=1,//使用下一个异常处理器 将异常派送给下一个SEH链的异常处理器
ExceptionNestedException=2,//在OS内部使用
ExceptionCollidedUnwind=3//在OS外部使用
}EXCEPTION_DISPOSITION;
(3) 访问SEH链 - TEB.NtTib.ExceptionList 通过TEB结构体的NtTib访问SEH链:
TEB.NtTib.ExceptionList是TEB结构体第一个成员 即位于FS:[0]处
TEB结构体:
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
... ... ...
... ... ...
typedef struct_NT_TIB{
struct_EXCEPTION_REGISTRATION_RECORD *ExceptionList; //TEB.NtTib.ExceptionList
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
union {
PVOID FiberData;
DWORD Version;
};
PVOID ArbitraryUserPointer;
struct_NT_TIB *Self;
} NT_TIB;
typedef NT_TIB *PNT_TIB;
(4) SEH安装 C语言中使用__try,__except,finally
汇编中:
push @myhandler;压入异常处理器的地址 handler
push DWORD PTR FS:[0] ;SEH链的头部 next
mov DWORD PTR FS:[0]:ESP ;向链表中添加该异常处理器
新添加的异常处理器将取代原来链表中的第一个位置成为第一个异常处理器
这里DWORD PTR FS:[0]是next (原来的第一个异常处理器地址,安装了新的SEH后变为第二个)
1.3 调试seh.exe
常见动态反调试技术总结
- 0x401000-0x40100C 为SEH的安装 handler地址为0040105A
- F7运行到00401005查看FS:[0]内存处
常见动态反调试技术总结
- 0019FF60处就是SEH链的开始(先压入Handler的地址再压入next)
- 下面为未安装我们的SEH异常处理器的时候的SEH链(编译器,操作系统产生)
常见动态反调试技术总结
常见动态反调试技术总结
常见动态反调试技术总结
常见动态反调试技术总结
- 运行到此处时在0040105A(第一个SEH处理器处下断点),然后Shift+F7 运行到SEH代码处
- 异常处理器代码
常见动态反调试技术总结
- 前面提到异常处理函数有四个参数,分别来看一下前三个参数
常见动态反调试技术总结
- 第一个参数 : ESP+4处 : 是一个指向EXCEPTION_RECORD 结构体的指针
- 这个结构体第一个参数为异常值,第三个为发生异常代码的地址
常见动态反调试技术总结
- 异常值:C0000005 异常代码地址:00401019
- 再看第二个参数ESP+8处:0019FF24
常见动态反调试技术总结
- 虽然之前没有提到第二个参数,这个参数值为0019FF60, 正是当前FS:[0]的位置
- 第三个参数:*pContext指向CONTEXT的指针0019FA44
- CONTEXT.Eip的位置0019FA44+B8=0019FAFC
常见动态反调试技术总结
- 里面正好是 00401019 (触发异常处)
- 三个参数了解之后再看异常调试器代码
常见动态反调试技术总结
- 这里SS:[ARG.3]为异常处理函数第三个参数,取异常处理函数第三个参数(CONTEXT指针)到ESI
- 这里FS:[30]为PEB的位置,取地址到EAX
- PEB偏移0x02处为BeingDebugged , 该值在调试状态下为1.
- 与1对比看是否在调试状态.
- 处于调试状态则不跳转.修改DS:[ESI+0B8]处值为00401023(CONTEXT.Eip)
- 未调试状态跳转,在00401076处修改CONTEXT.Eip值为00401039
常见动态反调试技术总结
- 这两处刚好是两种处理方式 , 这里只是弹个框,实际过程中为别的指令(干扰调试)
- 再看XOR EAX,EAX(EAX为返回值) ,返回0 与前面异常处理函数返回值匹配
- 然后是SEH函数的删除
常见动态反调试技术总结
1.4 OllyDbg中针对SEH的设置
常见动态反调试技术总结
常见动态反调试技术总结
- 可以在此让调试器选择忽略一些SEH,即交给被调试进程处理
- 但是SEH中有反调试手段的话也不可忽略,必须调试异常处理代码
2 Time Checking 通过比较一段代码的运行时长是否正常来判断是否处于反调试状态
2.1 时间测量方法 常用的有如下两种方法:
- 利用CPU的计数器
RDTSC
kernel32! QueryPerformanceCounter( ) / ntdll !NtQueryPerformanceCounter( )
kernel32! GetTickCount ( )
准确度:RDTSC > NtQueryPerformanceCounter( ) > GetTickCount ( ) - 利用系统的实际时间
timeGetTime()
_ftime()
2.2 RDTSC- x86CPU中存在一个名为TSC(Time Stamp Counter)64位寄存器, TSC保存着精确的时钟周期计数
- RDTSC是一条汇编指令,用来将TSC的值读入EDX:EAX 寄存器(高32位EDX,低32位EAX)
示例DynAD_ RDTSC.exe
常见动态反调试技术总结
- 在0x40101C处是第一次RDTSC指令,0x40102A 第二次
- 代码部分先比较了高32位,再比较了低32位
- 即若前后两次计数器差大于FFFFFFFF则属于异常(高32位比较)
- 大于FFFFFF也异常(低32位比较)
- 大于FFFFFFFF直接跳转到0040103E ,大于FFFFFF也会执行0040103E处指令
常见动态反调试技术总结
- 0040103E 处非法访问异常,程序会中止
- 破解之法:
常见动态反调试技术总结
3 陷阱标志TF原理
常见动态反调试技术总结
- TF设置为1时,CPU将进入单步模式,单步模式下,CPU执行一条指令就会触发一个EXCEPTION_SINGLE_STEP异常
- 实际上用到了前面的SEH反调试技术
- 可以在SEH异常处理器中使用反调试技术,修改Context.EIP的值
示例DynAD_SingleStep.exe
常见动态反调试技术总结
常见动态反调试技术总结
- 忽略单步异常 在SEH处理函数下断点,F9运行
- 调试器在单步和未忽略单步异常的情况下会自动设置TF为0
常见动态反调试技术总结
- 这里修改了Context.Eip,retn后直接到了卸载SEH异常处理器的代码
4 INT 2D原理 INT 2D原为内核模式中触发断点异常的指令,在用户模式下也会触发异常.
但在调试程序时,仅仅忽略下一条指令的第一个字节而不会触发异常
也就是调试模式下,线程的SEH不会触发,通过这个进行反调试
示例:DynAD_INT2D.exe
常见动态反调试技术总结
- 首先在0x40100B处安装了SEH异常处理程序
- 在0x40101E处有INT 2D指令
- 正常情况下,执行INT 2D应该触发异常,进入SEH,但调试器下并不会
- SEH代码:
常见动态反调试技术总结
- 修改了CONTEXT.Eip值为00401044,然后返回0
- 正常情况下从SEH出来将会直接从00401044继续执行,而跳过了00401021处的
MOV DWORD PTR SS:[EBP-4],1 - 而下面有判断会比较此处
常见动态反调试技术总结
破解之法:
修改SS:[EBP-4]处的值
或者int 2D 改为int 3
或修改跳转等
选择改为int 3,要让调试器忽略此处的异常
常见动态反调试技术总结
就可以进入SEH
常见动态反调试技术总结
5 0xCC探测原理 0xCC 是 int 3的机器码,调试下断点其实就是将 该位置的指令修改为0xCC
可以通过探测异常的0xCC 指令,来判断程序是否处于调试状态
有以下两种方法
调试过程中为了方便经常会用到API断点
获取某某API起始第一个字节是否为0xCC 即可判断是否处于反调试状态
破解之法:避开在API的第一条指令下断点
记录下某个地址区域指令的校验和,然后进行比较
如: DynAD_Checksum.exe
常见动态反调试技术总结
- 这里ECX=401070-401000 , 从ESI= 401000 开始循环,每循环一次ESI加1,按字节计算从401000到401070的校验和
- 校验和储存在EAX,与记录的校验和比较
常见动态反调试技术总结
常见动态反调试技术总结
- 若不相等,SS:[LOCAL.1]会被设置为1,在后面进行条件判断 跳转
- 破解:修改jmp指令等方法 如:
常见动态反调试技术总结
|
温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【 投诉建议】板块发帖举报。
|