闲聊几句
项目发布也半年了,反沙箱模块已经有段时间没有更新了,在更新的时候发现了一些有趣的东西
time.Sleep()
首先是Golang time.Sleep() 函数,会被微步沙箱的时间流逝跳过,先来看下函数原理
time.Sleep()
是 Go 语言中用于暂停当前 goroutine 执行的函数,其原理涉及到操作系统级别的调度和时间管理。
在调用 time.Sleep(d)
时,程序会暂停当前 goroutine 的执行,让出 CPU 给其他可以运行的 goroutine。参数 d
是一个 Duration
类型,表示暂停的时间长度。
具体原理如下:
系统调用: 当
time.Sleep()
被调用时,Go 运行时会发起一个系统调用,以请求操作系统在一定时间后恢复当前 goroutine 的执行。时钟管理: 操作系统会将当前 goroutine 标记为休眠状态,并设置一个定时器(timer),用来计时暂停的时间。定时器可以基于硬件时钟或操作系统内部的时间管理机制。
进入休眠: 一旦定时器到达设定的时间(
d
),操作系统会唤醒当前 goroutine,使其重新加入到可运行状态的队列中,等待调度器(scheduler)再次分配 CPU 时间片给它。精度和误差:
time.Sleep()
的精度取决于操作系统的实现和硬件。一般来说,精度可以达到毫秒级别,但具体取决于操作系统的调度策略和实现细节。在某些操作系统上,可能存在睡眠时间的小偏差。Goroutine 调度: Go 语言的运行时会负责管理所有的 goroutine,包括它们的创建、销毁和调度。当一个 goroutine 调用了
time.Sleep()
后,它会让出当前的执行权,让其他的 goroutine 有机会执行,这种调度是由 Go 运行时管理的。
影响
神奇的是,这个函数在一些情况下会出现特殊情况
先看第一组代码
package main
import (
"fmt"
"time"
)
func main() {
startTime := time.Now()
for i := 0; i < 1000; i++ {
time.Sleep(1 * time.Millisecond)
}
fmt.Printf("%d\n", time.Since(startTime).Milliseconds())
}
输出得到
15467
第二组代码
package main
import (
"fmt"
"time"
)
func main() {
startTime := time.Now()
for i := 0; i < 1000; i++ {
time.Sleep(10 * time.Millisecond)
}
fmt.Printf("%d\n", time.Since(startTime).Milliseconds())
}
输出得到
15611
差距较大的睡眠时间,居然得出差不多的结果,有兴趣可以看看Github
https://github.com/golang/go/issues/44343
和反沙箱有什么关系
通常,网上的文章会说睡眠一段时间有概率跳过沙箱的检测,但是现在沙箱基本都会有不同于现实世界的时间流速,所有也有反其道行之的检测时间流速来判断是否是沙箱。沙箱的开发肯定也会考虑到这个问题,可能会对睡眠函数或者进程的某些地方进行检测。
NtDelayExecution
NtDelayExecution 是一个未公开的 Win32 API
不过我把它转为了Go原生可以调用的API
func NtDelayExecution(DelayInterval int64) (err error) {
delay := -(DelayInterval * 1000 * 10000)
r1, _, e1 := syscall.SyscallN(
procNtDelayExecution.Addr(),
uintptr(0),
uintptr(unsafe.Pointer(&delay)),
)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
它有两个参数
- Alertable:无需关注,设置为 0 即可
- DelayInterval:延迟间隔,但需要接收负数作为参数来表示延迟时间,负数也可以理解为未来时间
使用这个API进行延迟执行,沙箱会直接标记
Beep
Beep 函数 (utilapiset.h)
在扬声器上生成简单的音调。 函数是同步的;它执行可警报等待,在声音完成之前不会将控制权返回到其调用方。
BOOL Beep(
[in] DWORD dwFreq,
[in] DWORD dwDuration
);
参数
[in] dwFreq
声音的频率,以Hz为单位。 此参数的范围必须介于 37 到 32,767 (0x25 到 0x7FFF) 。
[in] dwDuration
声音的持续时间(以毫秒为单位)。
返回值
如果该函数成功,则返回值为非零值。
如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError。
Doc:https://learn.microsoft.com/zh-cn/windows/win32/api/utilapiset/nf-utilapiset-beep
Go Beep()
freq: 人类的听觉范围大约在 20Hz – 20000Hz 之间
duration : 设置时间,毫秒为单位
func BeepSleep(duration uint32) {
freq := uint32(30000)
startTime := time.Now()
r1, _, _ := wdll.Beep().Call(uintptr(freq), uintptr(duration*1000))
if r1 == 0 {
os.Exit(0)
}
durationElapsed := time.Since(startTime).Seconds()
if uint32(durationElapsed) != duration {
os.Exit(0)
}
}
有趣的事
Link:https://securityliterate.com/beeeeeeeeep-how-malware-uses-the-beep-winapi-function-for-anti-analysis/
当程序调用 Beep 函数时,它最终会调用一个名为NtDelayExecution的函数,该函数的作用正如其名称所示:它延迟调用程序正在运行的线程的执行。
所以,沙箱检测了NtDelayExecution函数,但是没有检测Beep???
Beep() 的Plus版本 MessageBeep()
进一步探索,还发现了 MessageBeep 函数 (winuser.h)
Doc:https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-messagebeep
注意
在 Windows Server 2022 上,已禁用任务计划程序中的 Microsoft\Windows\Multimedia\SystemSoundsService 任务。 需要启用此任务才能使 MessageBeep 正常运行。
总结
延迟执行的函数可能还不止这些,有趣的事情是:无论是直接使用函数还是使用函数最终调用的API结果居然是不同。沙箱还真的是有点意思。
请登录后查看评论内容