从上面的栈溢出实现的过程可知,攻击实现的关键是获得溢出指针(jmp esp指令的地址),从而利用溢出指针实现跳转到shellcode。因为在windows系统中诸如kernel32.dll、user32.dll之类的动态链接库几乎被所有的进程加载,且加载基地址始终相同。攻击可以在自己的系统上搜索这些DLL中的类似jmp esp指令的地址,然后把它作为在目标系统中的溢出指针,就可以准确地实现溢出跳转。根据上面的分析可知,如果能改变系统每次运行起来之后加载DLL的基地址的话,就能很好的抑制溢出攻击的实现。地址空间随机化技术可以很好地解决利用静态地址的溢出攻击,但是不能解决相对地址攻击、信息泄露攻击和本地攻击。
本文提出函数行为预测方法来杜绝0day攻击。
因为ActiveX控件的实现体是DLL,DLL的导出函数作为接口提供给程序使用。如果某个导出函数存在0day漏洞的话,攻击者可以通过这个存在漏洞的接口实现溢出攻击。一个函数可能存在的可以被利用的漏洞地点无非是两个地方:一是函数体内部某个地方由于调用其它函数而发生溢出,从而导致在函数未执行完就遭受攻击;二是函数体内部漏洞利用,导致函数执行ret返回后发生溢出,程序执行流程发生改变。函数溢出点见图1-8。
见下图1-9:
⑴ ActiveX导出函数inline hook设计
为ActiveX的每个导出函数配备一块跳板,导出函数的开头和结尾都采用inline hook进行重定向,使该函数被调用时能最终跳入跳板。
⑵ 跳板的设计
跳板的设计分成四部分,一是承接导出函数开头调到跳板的上下文,然后跳入分发器;二是指向session链表头指针;三是承接导出函数结尾跳板的上下文,然后跳入分发器;四是指向下块跳板指针。
⑶ Session链表
每个session结构体对应一次该函数的调用过程。当应用程序调用ActiveX控件的接口的时候,分发器就会根据进入跳板的上下文来决定是否创建session。Session结构体中包含一个定时器,一个保存导出函数返回地址的变量(记为FirstCallRet),一个也是保存导出函数返回地址的变量(记为SecondCallRet),一个布尔型变量。其中,定时器作用是session创建时候,设定定时器,当定时器到期的时候,等待定时器的线程就会被唤醒。
⑷ 分发器设计
分发器总体上完成四步工作,一是创建session;二是把导出函数的返回地址保存进session;三是跳回跳板(pop eax,jmp 跳板+y),四是使定时器到期。
⑸ 线程和队列设计
系统中创建两个线程,一个是用来等待定时器(假设为A线程),另外一个是等待信号量(假设为B线程)。A线程等待定时器,定时器一旦到期则唤醒,然后把session相关信息放入队列,并把信号量置有信号。线程B等待信号量,一旦信号量有信号则线程B唤醒,线程B读取队列头的session信息,然后根据session中的相关信息来判断是否发生溢出
1.2.2.3 函数行为预测方法保护ActiveX控件免受0day漏洞攻击的过程
I 溢出发生在导出函数的内部ret之前
见图1-9。函数被调用之后,会进入跳板,跳板使之跳入分发器。分发器为这次调用创建一个session,并保存这次调用的返回地址到session的FirstCallRet中,同时创建定时器,启动定时器,把定时器放入session中,并置session中的布尔型变量为FALSE。
之后有以下两种可能:
① 如果对该导出函数的调用是正常的情况的话,在导出函数快结束的时候,势必又要跳入跳板,跳板再次进入分发器,分发器根据这次调用的session ID搜索session链表,找到之后把函数返回地址放入该session的SecondCallRet中,置布尔型变量为TRUE,并置定时器到期。
② 如果对该导出函数的调用是恶意的话,在导出函数结束之前,势必已经发生溢出并跳入恶意预定的shellcode去执行。在这种情况下,原导出函数的结尾跳转部分未得到执行,关于这次调用的session中的定时器只有自然的到期,并且SecondCallRet为空,布尔型变量为FALSE。
对于上面的两种情况,线程B明显可以根据三个变量FiresCallRet、SecondCallRet和布尔型变量的不同情况来判断是否中途程序被劫持了。
判读的伪代码如下:
If(布尔型变量 == FALSE)
{
//
//说明函数中途被劫持,发生溢出
//
}
Else
{
。。。
}
II 溢出发生在导出函数的内部ret之后
见图1-9。函数被调用之后,会进入跳板,跳板使之跳入分发器。分发器为这次调用创建一个session,并保存这次调用的返回地址到session的FirstCallRet中,同时创建定时器,启动定时器,把定时器放入session中,并置session中的布尔型变量为FALSE。
之后有以下两种可能:
① 如果对该导出函数的调用是正常的情况的话,在导出函数快结束的时候,势必又要跳入跳板,跳板再次进入分发器,分发器根据这次调用的session ID搜索session链表,找到之后把函数返回地址放入该session的SecondCallRet中,置布尔型变量为TRUE,并置定时器到期。
② 如果对该导出函数的调用是恶意的话,在导出函数准备返回时,再次跳入跳板。此时,分发器用session ID搜索session链,找到之后, 将栈中的返回地址放入SecondCallRet中,置布尔型变量为TRUE,并置定时器过期。
对于上面的两种情况线程B,可以根据三个变量FiresCallRet、SecondCallRet和布尔型变量的不同情况来判断是否中途程序被劫持了。
判读的伪代码如下:
If(布尔型变量 == FALSE)
{
//
//说明函数中途被劫持,发生溢出
//
}
Else
{
If(FirstCallRet = = SecondCallRet )
//
//正常执行ActiveX接口的调用
//
Else
//
//发生栈溢出攻击
//
}
2、反逆向方法:
⑴ 消除符号信息,字节码混淆器可以做到这点。
DLL导出表,以函数名导出易被逆向分析,以序号导出不易被逆向分析。
⑵ 打乱程序,修改程序的规划,逻辑,数据及组织来实现,在保持原有程序的功能不变的基础上最大程度的降低程序的可读性。
⑶ 嵌入反调试代码
3、反调试方法:
⑴ 自己手工实现IsDebuggerPresent API
⑵ 检测内核调试器windbg,调用ZwQuerySystemInformation/NtQuerySystemInformation,返回SYSTEM_KERNEL_DEBUGGER_INFORMATION中,检测DebuggerEnable
⑶ 检测内核调试器softice,检测int 1中断的异常处理代码是否是STATUS_ACCESS_VIOLATION。
⑷ 设置trap标志,检测是否出现异常,如果没有异常,则表明存在调试器。缺点:pushfd和popad太显眼,检测代码易被绕过。
⑸ 通过代码校验保护敏感代码。缺点:不能检验硬件断点。