离线软件怎么防用户改系统时间绕过到期
前面写过一版防时间回拨,思路是本地存一个「上次见过的时间」,下次启动发现现在比它还早,就判定时间被调过。那招够用但比较朴素,只能挡住随手调表的人。后来又琢磨了一圈,离线环境里想认真防「改系统时间续命」,其实有几条不同路数的思路,单独哪一条都有破法,得叠着用。
先说个大前提:完全离线、又不能联网对时的环境下,这是软件防破解里的经典难题,没有银弹。下面这些都是抬门槛,不是绝对防住。
别看「现在几点」,看「过去留的痕」
第一类思路是文件时间戳审计。软件运行的时候,往系统各处(日志、临时目录、配置文件深处、注册表角落)偷偷写一些文件。每次启动去读这些文件的最后修改时间,只要发现当前系统时间比某个历史文件的修改时间还早——说明时间被往回调了,直接锁死。逻辑是:正常情况下,现在不可能比过去还早。
这招成本最低,但文件能被删,删干净了就重置,所以只能当一道初筛。
时间能倒退,运行时长不能
第二类是单向计数器,我觉得是更靠谱的方向。墙上的时钟能调,但「软件实际跑了多久」这件事,用户没法自然地把它调回去。
具体两种做法。一种是限制使用次数:许可证带一个最大次数,每次启动在本地加密文件里扣一次,用完就锁。另一种是累计运行时间:后台开个线程,软件每跑一段时间,就在本地累计一下。然后拿这个累计时长去跟系统时间流逝的量对账——系统时间说只过了一小时,可我记着的累计运行时长涨了十小时,那中间一定被调过表。
对账用的「实际跑了多久」不能用 System.currentTimeMillis(),那个跟着系统时钟走,一调表就废。得用单调时钟,Java 里就是 System.nanoTime()。它不受墙上调时间影响,只反映真实流逝的纳秒:
// 后台线程周期性累计真实运行时长(nanoTime 单调,不受改系统时间影响) |
启动时把「系统时间流逝」和「nanoTime 累计」一对,差得离谱(比如系统时间几乎没走、累计时长却涨了一大截,或者反过来系统时间大跳而累计没动)就认定被动了手脚,锁应用。这比单纯记墙上时间硬,因为它不依赖「时间只往前走」这个假设,而是直接量「真实运行量」。
借别人频繁更新的时钟对账
第三类是跨应用对账,专门对付那种「运行时间很短、关了再开」的工具。你的软件可能就跑几秒,累计时长根本攒不起来,用户大可以「跑五秒、关掉、调表、再跑五秒」地白嫖。
这种就别指望自己的运行时长了,借系统里那些天天在跑的东西:Windows 读系统事件日志里最近一条事件的时间,Linux/macOS 看 ~/.bash_history、/var/log 下最新文件的时间。这些都是别的程序留下的时间脚印,你启动的时候如果发现系统时间比这些脚印还早,那系统时间肯定被倒拨了。
硬件单调时钟和加密狗
再往上还有硬件层的招。就算改了墙上时间,有些底层时钟是调不回去的:Windows 的 GetTickCount64、Linux 的 clock_gettime(CLOCK_MONOTONIC),表示本次开机以来流逝的毫秒,跟系统时钟脱钩。每次退出前把这次的 monotonic 时间累加存下来,能挡「开机时长」被作弊。再往上就是带独立时钟芯片的 USB 加密狗了,软件只向狗要时间,改系统时间完全没用,那是高价值软件才上得起的配置。
计数器存哪儿才不被删
上面这些都绕不开一个问题:计数器、累计时长存在哪,才不会被用户直接删掉重置。删了文件、软件以为是第一次运行,前面全白干。实务里是散着存:一部分放应用自己的数据目录(放正经的签名许可证文件),一部分加密后伪装成系统文件丢到临时目录的角落,Windows 还可以塞注册表深处。不指望哪一处绝对安全,而是让用户没法一眼找全、删干净。
兜底还有个体验问题:主板电池没电、跨时区出差、系统自动对时,都可能让时间无意中跳一下,一刀切锁死会招来客诉。所以一般都留个宽限期,差得小的当时区抖动记下来警告但不锁,差得大的才真锁。
这几条叠下来,核心其实就是把信任从「系统能不能给个准时间」换成了「用户改不了的那些量」——真实运行时长、别人的时间脚印、硬件单调时钟。单拎一个都有破法,叠在一起,普通用户基本没那个耐心去一个个找全删干净。
