模块 74 - 反虚拟环境技术 - 多种延迟执行技术
恶意软件开发课程 - 反虚拟环境技术 - 多种延迟执行技术
模块 74 - 反虚拟环境技术 - 多种延迟执行技术#
反虚拟环境技术 - 多种延迟执行技术#
简介#
延迟执行 (Delay Execution) 是躲避沙箱环境的一种通用技术。沙箱通常具有时间限制,无法长时间监控二进制程序的行为。因此,恶意软件可以在关键代码执行前引入长时间的停顿,迫使沙箱在还没来得及执行恶意逻辑时就因超时而被迫终止。
如果沙箱的分析上限为 2 分钟,而恶意软件在解密和运行 Payload 前先等待了 3 分钟,那么该样本在沙箱眼中就是安全的。
本模块将介绍在探测到沙箱环境后可用于延迟执行的多种函数。
💡 初学者提示:为什么要延迟执行?
类比理解: 想象你是一颗定时炸弹(恶意软件)。如果你被送进一个排爆室(沙箱),排爆专家只会被授权观察你 5 分钟。
- 策略一:立即引爆。专家会立刻看到你的所有行为,你的目的就暴露了。
- 策略二:延迟执行。你设定一个 10 分钟的闹钟,在此之前你只是一块普通的石头。由于专家只有 5 分钟时间,时间一到,他就得把你放回普通的堆场。等到了第 10 分钟,你在普通堆场(真实机器)里才开始执行你的真正任务。
1. 探测“快进”欺骗 (Fast-Forwards)#
大多数现代沙箱已经意识到这种延迟战术,并实现了相应的对策:时间快进。沙箱会通过 API Hook 修改传递给延迟函数的参数(如将 Sleep(10000) 改为 Sleep(0)),从而瞬间跳过等待。
为了对抗这种欺骗,我们在延迟结束后必须核实流逝的时间。这可以通过 GetTickCount64 WinAPI 来实现。
BOOL DelayFunction(DWORD dwMilliSeconds){
DWORD T0 = GetTickCount64();
// 这里放置延迟 dwMilliSeconds 毫秒的逻辑
DWORD T1 = GetTickCount64();
// 如果实际经过的时间少于要求的时间,说明发生了快进,函数返回失败
if ((DWORD)(T1 - T0) < dwMilliSeconds)
return FALSE;
else
return TRUE;
}c2. 利用 WaitForSingleObject 延迟执行#
我们经常使用 WaitForSingleObject 等待事件变得有信号。在这里,我们可以利用它等待一个由 CreateEvent 产生的空事件。由于该事件永远不会变的有信号,函数将不得不等待超时发生,从而达到延迟的目的。
// ftMinutes: 要延迟的分钟数
BOOL DelayExecutionVia_WFSO(FLOAT ftMinutes) {
// 1. 分钟转换为毫秒
DWORD dwMilliSeconds = (DWORD)(ftMinutes * 60000);
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
DWORD _T0 = NULL, _T1 = NULL;
_T0 = GetTickCount64();
// 2. 等待事件超时
if (WaitForSingleObject(hEvent, dwMilliSeconds) == WAIT_FAILED) {
printf("[!] WaitForSingleObject 失败,错误码 : %d \n", GetLastError());
return FALSE;
}
_T1 = GetTickCount64();
// 3. 时间校验
if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
return FALSE;
CloseHandle(hEvent);
return TRUE;
}c3. 利用 MsgWaitForMultipleObjectsEx 延迟执行#
MsgWaitForMultipleObjectsEx 的功能与 WaitForSingleObject 类似,但它还能响应输入事件,常用于 GUI 程序。
BOOL DelayExecutionVia_MWFMOEx(FLOAT ftMinutes) {
DWORD dwMilliSeconds = (DWORD)(ftMinutes * 60000);
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
DWORD _T0 = NULL, _T1 = NULL;
_T0 = GetTickCount64();
// 等待 dwMilliSeconds 毫秒
if (MsgWaitForMultipleObjectsEx(1, &hEvent, dwMilliSeconds, QS_HOTKEY, NULL) == WAIT_FAILED) {
printf("[!] MsgWaitForMultipleObjectsEx 失败,错误码 : %d \n", GetLastError());
return FALSE;
}
_T1 = GetTickCount64();
if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
return FALSE;
CloseHandle(hEvent);
return TRUE;
}c4. 利用 NtWaitForSingleObject 延迟执行#
我们可以直接使用系统调用 NtWaitForSingleObject。它是 WaitForSingleObject 的底层内核版本。
[!IMPORTANT]
Nt系列函数中的时间单位是 100 纳秒间隔(Ticks)。1 个 Ticks 等于 0.0001 毫秒。传递给Timeout的值应为:-(dwMilliSeconds * 10000)。
typedef NTSTATUS (NTAPI* fnNtWaitForSingleObject)(
HANDLE Handle,
BOOLEAN Alertable,
PLARGE_INTEGER Timeout
);
BOOL DelayExecutionVia_NtWFSO(FLOAT ftMinutes) {
DWORD dwMilliSeconds = (DWORD)(ftMinutes * 60000);
HANDLE hEvent = CreateEvent(NULL, NULL, NULL, NULL);
LONGLONG Delay = NULL;
NTSTATUS STATUS = NULL;
LARGE_INTEGER DelayInterval = { 0 };
fnNtWaitForSingleObject pNtWaitForSingleObject = (fnNtWaitForSingleObject)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtWaitForSingleObject");
DWORD _T0 = NULL, _T1 = NULL;
// 毫秒转纳秒间隔负数
Delay = dwMilliSeconds * 10000;
DelayInterval.QuadPart = -Delay;
_T0 = GetTickCount64();
// 执行系统调用等待
if ((STATUS = pNtWaitForSingleObject(hEvent, FALSE, &DelayInterval)) != 0x00 && STATUS != 0x102) { // 0x102 为超时
printf("[!] NtWaitForSingleObject 失败,状态码 : 0x%0.8X \n", STATUS);
return FALSE;
}
_T1 = GetTickCount64();
if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
return FALSE;
CloseHandle(hEvent);
return TRUE;
}c5. 利用 NtDelayExecution 延迟执行#
NtDelayExecution 系统调用专门用于同步延迟。它类似于内核版的 Sleep。
typedef NTSTATUS (NTAPI *fnNtDelayExecution)(
BOOLEAN Alertable,
PLARGE_INTEGER DelayInterval
);
BOOL DelayExecutionVia_NtDE(FLOAT ftMinutes) {
DWORD dwMilliSeconds = (DWORD)(ftMinutes * 60000);
LARGE_INTEGER DelayInterval = { 0 };
LONGLONG Delay = NULL;
NTSTATUS STATUS = NULL;
fnNtDelayExecution pNtDelayExecution = (fnNtDelayExecution)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtDelayExecution");
DWORD _T0 = NULL, _T1 = NULL;
// 转换时间
Delay = dwMilliSeconds * 10000;
DelayInterval.QuadPart = -Delay;
_T0 = GetTickCount64();
// 执行内核延迟挂起
if ((STATUS = pNtDelayExecution(FALSE, &DelayInterval)) != 0x00 && STATUS != 0x102) {
printf("[!] NtDelayExecution 失败,状态码 : 0x%0.8X \n", STATUS);
return FALSE;
}
_T1 = GetTickCount64();
if ((DWORD)(_T1 - _T0) < dwMilliSeconds)
return FALSE;
return TRUE;
}c6. 演示#
下图演示了这些技术的运行效果。执行被设定延迟 6 秒(0.1 分钟)。可以看到即使使用了底层调用,依然能正确捕捉快进行为。

🎯 总结#
在本模块中,我们学习了:
- 时间差校验: 实战应用
GetTickCount64来识破沙箱引擎对Sleep类函数的修改。 - 内核原语: 理解了 Ticks 时间单位及其在
Nt系列 Native API 中的应用。 - 替代 API: 学会使用多种看似合法的 WinAPI 实现长时间隐蔽挂起程序。
📚 下一步学习
下一个模块将探讨 Module 75 - Anti-Virtual Environments - API Hammering。学习如何通过大量的 I/O 操作进行另一种形式的“密集型”延迟。