文章总结: 文档分享睡眠混淆技术,通过定时内存加解密与权限切换规避检测。重点解析Ekko和Cronos利用TimerQueue、ROP链及NtContinue实现休眠加密的机制。同时探讨防御方案,涵盖Hunt-Sleeping-Beacons对可疑计时器、异常调用栈等特征的扫描,以及EtwTi-FluctuationMonitor利用ETW监控内存权限波动的原理,为攻防对抗提供技术参考。 综合评分: 85 文章分类: 免杀,红队,二进制安全,安全工具,实战经验
【转载】睡眠混淆技术分享
Arcueid Arcueid
隐雾安全
2026年3月8日 10:06 四川
好文推荐
文章作者:先知社区(Arcueid)
文章来源:https://xz.aliyun.com/news/17512
睡眠混淆是用于对抗内存检测的一项技术 在特定的触发器触发或定时执行内存加解密 改变权限 来规避AV检测
下面通过几个开源项目学习这种技术
sleep obfuscation
EKKO
https://github.com/Cracked5pider/Ekko
VOID EkkoObf( DWORD SleepTime ) { CONTEXT CtxThread = { 0 };
CONTEXT RopProtRW = { 0 }; CONTEXT RopMemEnc = { 0 }; CONTEXT RopDelay = { 0 }; CONTEXT RopMemDec = { 0 }; CONTEXT RopProtRX = { 0 }; CONTEXT RopSetEvt = { 0 };
HANDLE hTimerQueue = NULL; HANDLE hNewTimer = NULL; HANDLE hEvent = NULL; PVOID ImageBase = NULL; DWORD ImageSize = 0; DWORD OldProtect = 0;
// Can be randomly generated CHAR KeyBuf[ 16 ]= { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }; USTRING Key = { 0 }; USTRING Img = { 0 };
PVOID NtContinue = NULL; PVOID SysFunc032 = NULL;
hEvent = CreateEventW( 0, 0, 0, 0 ); hTimerQueue = CreateTimerQueue();
NtContinue = GetProcAddress( GetModuleHandleA( "Ntdll" ), "NtContinue" ); SysFunc032 = GetProcAddress( LoadLibraryA( "Advapi32" ), "SystemFunction032" );
ImageBase = GetModuleHandleA( NULL ); ImageSize = ( ( PIMAGE_NT_HEADERS ) ( ImageBase + ( ( PIMAGE_DOS_HEADER ) ImageBase )->e_lfanew ) )->OptionalHeader.SizeOfImage;
Key.Buffer = KeyBuf; Key.Length = Key.MaximumLength = 16;
Img.Buffer = ImageBase; Img.Length = Img.MaximumLength = ImageSize;
if ( CreateTimerQueueTimer( &hNewTimer, hTimerQueue, RtlCaptureContext, &CtxThread, 0, 0, WT_EXECUTEINTIMERTHREAD ) ) { WaitForSingleObject( hEvent, 0x32 );
memcpy( &RopProtRW, &CtxThread, sizeof( CONTEXT ) ); memcpy( &RopMemEnc, &CtxThread, sizeof( CONTEXT ) ); memcpy( &RopDelay, &CtxThread, sizeof( CONTEXT ) ); memcpy( &RopMemDec, &CtxThread, sizeof( CONTEXT ) ); memcpy( &RopProtRX, &CtxThread, sizeof( CONTEXT ) ); memcpy( &RopSetEvt, &CtxThread, sizeof( CONTEXT ) );
// VirtualProtect( ImageBase, ImageSize, PAGE_READWRITE, &OldProtect ); RopProtRW.Rsp -= 8; RopProtRW.Rip = VirtualProtect; RopProtRW.Rcx = ImageBase; RopProtRW.Rdx = ImageSize; RopProtRW.R8 = PAGE_READWRITE; RopProtRW.R9 = &OldProtect;
// SystemFunction032( &Key, &Img ); RopMemEnc.Rsp -= 8; RopMemEnc.Rip = SysFunc032; RopMemEnc.Rcx = &Img; RopMemEnc.Rdx = &Key;
// WaitForSingleObject( hTargetHdl, SleepTime ); RopDelay.Rsp -= 8; RopDelay.Rip = WaitForSingleObject; RopDelay.Rcx = NtCurrentProcess(); RopDelay.Rdx = SleepTime;
// SystemFunction032( &Key, &Img ); RopMemDec.Rsp -= 8; RopMemDec.Rip = SysFunc032; RopMemDec.Rcx = &Img; RopMemDec.Rdx = &Key;
// VirtualProtect( ImageBase, ImageSize, PAGE_EXECUTE_READWRITE, &OldProtect ); RopProtRX.Rsp -= 8; RopProtRX.Rip = VirtualProtect; RopProtRX.Rcx = ImageBase; RopProtRX.Rdx = ImageSize; RopProtRX.R8 = PAGE_EXECUTE_READWRITE; RopProtRX.R9 = &OldProtect;
// SetEvent( hEvent ); RopSetEvt.Rsp -= 8; RopSetEvt.Rip = SetEvent; RopSetEvt.Rcx = hEvent;
puts( "[INFO] Queue timers" );
CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopProtRW, 100, 0, WT_EXECUTEINTIMERTHREAD ); CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopMemEnc, 200, 0, WT_EXECUTEINTIMERTHREAD ); CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopDelay, 300, 0, WT_EXECUTEINTIMERTHREAD ); CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopMemDec, 400, 0, WT_EXECUTEINTIMERTHREAD ); CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopProtRX, 500, 0, WT_EXECUTEINTIMERTHREAD ); CreateTimerQueueTimer( &hNewTimer, hTimerQueue, NtContinue, &RopSetEvt, 600, 0, WT_EXECUTEINTIMERTHREAD );
puts( "[INFO] Wait for hEvent" );
WaitForSingleObject( hEvent, INFINITE );
puts( "[INFO] Finished waiting for event" ); }
DeleteTimerQueue( hTimerQueue ); }
首先创建了一个事件 一个TimerQueue
然后获取NtContinue用于切换线程上下文 SystemFunction032 用于加密
CreateTimerQueueTimer的第三个参数是个回调RtlCaptureContext 用于获取线程上下文 填充到CtxThread
在if内进行ROP的初始化 利用NtContinue切换rip 依次执行修改内存属性 加密 sleep 解密 修改内存属性
对应的函数参数 作者给出了注释
以上这些事是工作线程做的 最终SetEvent 使控制流返回主线程
CreateTimerQueueTimer 底层调用ntdll!RtlCreateTimer 调用了ntdll!TpSetTimerEx 调用了ntdll!TppSetTimer
在TppSetTimer中调用TppETWTimerSet 最终会调用NtTraceEvent记录
但是实际上在TppEtwTimerSet上下断并断不下来
SharedData是空的
Cronos
https://github.com/Idov31/Cronos
创建定时器 同样通过RtlCaptureContext 获取线程上下文
从加载的所有模块中搜特征码
同样通过Ntcontinue切换上下文
QuadSleep纯汇编实现
rcx: pop rcx; ret
rdx: pop rdx; ret
r8: add rsp,20; pop rdi; ret
r9: SleepEx
调用SleepEx从而APC
修改内存属性为RW -> 加密 -> 解密 -> 修改内存属性为RWX
检测
Hunt-Sleeping-Beacons
具体WorkFactory相关知识可以参考
https://urien.gitbook.io/diago-lima/a-deep-dive-into-exploiting-windows-thread-pools/attacking-worker-factories
直接跟进process_scanner::scan_processes
对传进来的process数组进行扫描 具体的扫描行为定义在
suspicious_timer
通过NtQuerySystemInformation 查询进程句柄信息 获取所有WorkerFactory
每个WorkerFactory都有对应的StartRoutine 由其创建的工作线程执行
通过NtQueryInformationWorkerFactory 可以查询StartRoutine
这里通过该API查询 获得StartParameter 一个指向 TP_POOL的指针
TimerQueue就处于这个结构中
typedef struct _FULL_TP_POOL { struct _TPP_REFCOUNT Refcount; long Padding_239; union _TPP_POOL_QUEUE_STATE QueueState; struct _TPP_QUEUE* TaskQueue[3]; struct _TPP_NUMA_NODE* NumaNode; struct _GROUP_AFFINITY* ProximityInfo; void* WorkerFactory; void* CompletionPort; struct _RTL_SRWLOCK Lock; struct _LIST_ENTRY PoolObjectList; struct _LIST_ENTRY WorkerList; struct _TPP_TIMER_QUEUE TimerQueue; struct _RTL_SRWLOCK ShutdownLock; UINT8 ShutdownInitiated; UINT8 Released; UINT16 PoolFlags; long Padding_240; struct _LIST_ENTRY PoolLinks; struct _TPP_CALLER AllocCaller; struct _TPP_CALLER ReleaseCaller; volatile INT32 AvailableWorkerCount; volatile INT32 LongRunningWorkerCount; UINT32 LastProcCount; volatile INT32 NodeStatus; volatile INT32 BindingCount; UINT32 CallbackChecksDisabled : 1; UINT32 TrimTarget : 11; UINT32 TrimmedThrdCount : 11; UINT32 SelectedCpuSetCount; long Padding_241; struct _RTL_CONDITION_VARIABLE TrimComplete; struct _LIST_ENTRY TrimmedWorkerList; } FULL_TP_POOL, * PFULL_TP_POOL;
通过判断函数地址 以确认是否恶意
blocking_apc
遍历堆栈 检查是否调用了KiUserApcDispatcher
blocking_timer
遍历堆栈,检查是否调用了 RtlpTpTimerCallback
abnormal_intermodular_call
检测异常调用堆栈 通过判断kernelbase或kernel32 是否是由ntdll调用过来的 以决定是否恶意
因为正常情况下是不存在这种调用的
return_address_spoofing
检测返回地址是否为 jmp rbx, jmp rbp等
stomped_module
遍历调用堆栈 判断其返回地址的模块是否是原始共享模块(即未被修改) 来判断是否恶意 (一些关键dll不在此列)
hardware_breakpoints
检测DR0-3 DR7 从而判断是否启用了硬件断点 以判断是否恶意
non_executable_memory
判断地址是否没有任一 执行权限 PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY
调用堆栈中的地址没有执行权限 这是不正常的
EtwTi-FluctuationMonitor
https://github.com/jdu2600/EtwTi-FluctuationMonitor
Black hat ASIA 2023 You Can Run, but You Can’t Hide – Finding the Footprints of Hidden Shellcode 提到
一块内存反复的修改权限 是不正常的 可以设置一个阈值 当该内存修改次数达到阈值时告警
通过订阅Microsoft-Windows-Threat-Intelligence的KERNEL_THREATINT_KEYWORD_PROTECTVM_LOCAL
设置回调 判断是否是第一次发生权限变化
变化后插入g_ImmutableCodePages 即标记为不可变
如果后续发生变化 且是不可变的 则输出…is fluctuating 也就是说这里的阈值是1
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:隐雾安全 Arcueid Arcueid《【转载】睡眠混淆技术分享》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论