本文主要内容主要参考自:攻击技术研判|DRIDEX木马巧用VEH混淆API调用流程 - FreeBuf网络安全行业门户
昨天初次学习VEH的基本使用,网上搜索关于VEH的东西的时候发现了这篇文章,这VEH使用的手法真是太巧妙了,且听我慢慢道来
简介
这个技术使用了int3+ret的组合,同时对抗动态分析和静态分析,基于VEH能够修改线程上下文的特点,int3在无调试器的状态下会被VEH接住,通过新增栈的数据来为两次ret跳转提供返回地址,从而使得在这里进行了一次函数调用
实验环境:VS2022
代码实现&原理
这个技术的目的是隐蔽调用函数,方式是通过异常处理进行:
- 首先我们需要一个函数地址,这里图省事就直接获取了
- 其次我们需要触发一个异常,用断点异常也好,除零异常也罢,只要能触发异常就行
- 然后紧接着在异常触发后面跟进一个ret指令,用于函数调用
到此已经为函数调用准备好了条件,接下来的处理就需要在VEH里进行了
void showFlag() {
printf("API Obfuscation Call\r\n");
}
int main(int argc) {
AddVectoredExceptionHandler(0, (PVECTORED_EXCEPTION_HANDLER)Handler);
DWORD a = (DWORD)showFlag; // 通过某种方式把要隐蔽调用的API地址放到寄存器里
DWORD b = 0;
int c;
if (argc > INT_MAX)goto LABEL; // 不存在的跳转让ret后面在静态分析时出现标签
c = a / b; // 触发VEH,修改esp内容,为之后两次跳转做准备
__asm {
ret; // 跳转到隐蔽函数处
}
LABEL:
printf("normal function code:%d\r\n", c);
return 0;
}
VEH处理函数如下:具体是在做什么事情,先看下面的调试分析会比较好理解
LONG NTAPI Handler(
struct _EXCEPTION_POINTERS* ExceptionInfo
) {
ExceptionInfo->ContextRecord->Eip +=2; // 跳过触发异常的指令
ExceptionInfo->ContextRecord->Esp -= 4;
*(DWORD*)ExceptionInfo->ContextRecord->Esp = ExceptionInfo->ContextRecord->Eip + 1; // push 跳过ret指令之后的地址
ExceptionInfo->ContextRecord->Esp -= 4;
*(DWORD*)ExceptionInfo->ContextRecord->Esp = ExceptionInfo->ContextRecord->Eax; // push 要调用的函数地址
return EXCEPTION_CONTINUE_EXECUTION;
}
具体是什么意思呢,我们打开VS在发生异常的地方下个断点:
查看此时的栈和寄存器:(这里仅把关键的部分摘出来了)
EIP = 008010B6 ESP = 0019FF18
0x0019FF0C ad 10 80 00 ?.€.
0x0019FF10 00 00 00 00 ....
0x0019FF14 40 10 80 00 @.€.
0x0019FF18 97 12 80 00 ?.€. // 当前ESP
0x0019FF1C 01 00 00 00 ....
接下来在VEH处理程序执行完的时候再下个断点,等断下来的时候再次来查看这里的寄存器和栈:
eip被修改为了0x008010b8,异常触发的指令地址是0x008010b6,这里跳过了触发异常的指令
栈的变化如下:
0x0019FF0C 00 00 00 00 ....
0x0019FF10 90 10 80 00 ?.€. // 当前ESP 00801090
0x0019FF14 b9 10 80 00 ?.€. // 008010b9
0x0019FF18 97 12 80 00 ?.€. // 之前ESP
0x0019FF1C 01 00 00 00 ....
栈里多了两个成员,0x0019FF10处的地址是我们获取的函数地址,而0x0019FF14处的地址是从异常处跳过触发异常的指令后,再跳过ret指令后的地址
通过ret进入函数调用,当函数返回的时候需要从栈里拿到一个返回地址,这个地址就作为函数的返回地址使用,从而完成了函数调用并返回
使用效果
程序直接点开,会执行被VEH处理异常:
通过动态调试器打开,调试器会接管这个异常
通过IDA打开,主函数会被截断: