开端
我……掉到了 V2,没办法再免费停车了。
26 年 1 月时的我发现了这个事,但由于本人太过懒散,又或许是受到了 xxyz30 老师的森空岛自动签到脚本的启发,于是我有了写一个自动签到程序的念头。
发展
第一次尝试
我一开始的想法很简单,直接使用抓包工具查看一下发送的请求不就行了。说干就干,我原本以为安卓手机在这个方面会更加容易,但没想到把抓包软件的证书放进系统证书目录竟然需要 root 权限。没办法的我只能转而尝试 iPhone,诶,很快哦,轻而易举地就把请求抓了出来。
手机点击签到,说到底也是向服务器发送一个请求。我把这条请求里的登录 token、风控 token 和活动编号拿出来,用 Python 再发送一遍,成功了。接下来就是让程序每天向服务器发送这个请求,再把结果推送到我的手机上。
于是,从 1 月 14 日起,我以为我跑通了龙湖签到的一切。
第一次转折
第一次转折发生在 1 月 20 日,我的手机上收到这么一条消息:
这个网络故障是什么呢?服务器的网络怎么会有问题?不信邪的我登录自己的账号,这才明白原来是被风控了:屏幕中是一个滑块验证码,需要人类将其从最左侧精确地滑到对应的位置。我的程序只是一个发送请求的机器人,当然是被这个验证滑块挡住了。
那就继续抓包,看看它是怎么验证的。但当我分析收到的图片时,又发现了不对劲的地方。为什么这个图片竟然是这样的?

抓包得到的乱序背景图
一张图片被切成了很多个小块,随后又被打乱组合在一起。既然小程序中显示的是完整图片,那客户端肯定有把它拼回去的办法。我开始上网搜索相关内容,最后找到了这几篇:验证码底图还原、顶象滑块逆向分析——背景图还原分析、顶象图片还原算法记录。
阅读后我知道了,我需要一个记录切片顺序的数组,只要拿到它,就能将背景图还原出来。这个数组是哪来的呢?它不可能凭空冒出来吧。但是在抓到的结果中,也只能找到一个以 dingxiang 开头的字符串 o,这个会是我们要的数组吗?
还真是,只不过要先把这个字符串转换一下。
对应的代码会取 o 的前 32 个字符,把每个字符转成数字,再除以 32 取余数。如果得到的数字已经出现过,就加一继续算,直到得到一个没有用过的数字。这样最后就能得到一组没有重复的、从 0 到 31 的数字,也就是这 32 个切片的位置顺序。
接下来就是照着顺序裁切、重新拼图。这里我还得弄清楚,数组记录的是“从哪个位置取”,还是“放到哪个位置去”,方向弄反了,拼出来还是乱的。
终于,背景图还原出来了:

还原后的背景图
那么下一步,就是识别与滑块相同形状的位置了。
这部分用的是 OpenCV。先处理滑块图的透明区域,提取滑块和背景的边缘,再拿滑块的形状去背景里匹配,找出最像的位置。为了方便检查,我还让程序把识别到的位置画回图片里,这样一眼就能看出来它到底找对了没有。
图片有了,位置也有了,应该能提交了吧?结果请求里面还有一个很长的 ac 参数,又得继续找。
继续看 JS
顺着 basic-captcha-js.js 往下看,可以找到 ac 是从 getUA() 拿到的。别人的分析里又提到了 greenseer.js,但我抓包的时候并没有顺利找到这个文件。等把文件找来,里面又是各种看不懂的变量和拼接。
于是我一边查资料,一边把代码丢给 AI 一起看,后来找到了 greenseerAST.js,试着用它来反混淆。
这个脚本会把代码里的数组引用、字符串拼接和一些能直接算出来的表达式还原,让原本绕来绕去的代码稍微好读一点。处理之后,再沿着 reload、start 这些方法继续往下找,才逐渐看明白它在做什么。
原来验证时除了最后的坐标,还要带上时间、浏览器和页面信息,以及滑动过程中的轨迹。ac 就是这些数据经过编码后得到的结果,光把终点坐标填进去还不行。
所以又得生成滑动轨迹,对照 JS 把这些数据的编码过程补上,再和前面算出来的位置一起提交。
那天我从 6 点半一直研究到 11 点 32,终于第一次跑通了验证码的处理。验证成功后,把返回的值填进 X-LF-DXRisk-Captcha-Token,重新发送签到请求,终于签好了。
但是,还没完
后面我又添加了重试功能,一次不行就再试一次,但效果不是很理想。中间断断续续签到失败了很多次,有时候能过,有时候又不行。
我把每次处理验证码的图片和识别记录都保存了下来,再下载到本地的 longfor-sign-CAPTCHA 文件夹里,自己一张一张看。
尤其是那些返回 POSITION_MISMATCH,也就是位置不匹配的记录:到底是 CV 没找到缺口,还是已经找到了,但提交的距离不对?
后来才慢慢对上,这里面的坐标不能直接拿来用。缺口在图片中的位置,和滑块从起点需要移动的距离,中间还差着偏移;识别时取的是左边缘还是中线,也得对应起来。
于是我又对着那些图,一点一点算到底还要减掉多少。现在翻以前的聊天记录,还能看到我当时写的“x 要减掉 20”。类似这样的修修改改,持续了一个多月,最终在 2 月 25 日解决了验证码的问题。
到了 3 月,我把程序迁移到了华为云函数工作流,每天让定时触发器叫它起来签到,签完就结束,再把结果推送给我。
接下来,它就这么一直跑着,直到 10 月 29 日。
第二次转折
这天,我收到了一条新的提示:
我尝试了手动签到,但是也是这个提示,感觉又被风控了。于是我先暂停了程序,没有让它继续重试,并在第二天下午成功签到。
想了想,每天都在固定时间,按差不多的节奏把这些账号签一遍,好像确实有点规律了。所以我又添加了随机时间签到的功能。
每天先生成一份随机计划,把账号安排到不同的时间,顺序也打乱。云函数每半小时起来检查一下,有账号到时间了就签到,没有就结束,等下一次再检查。当天的计划和执行结果会保存下来,下一次运行时就知道哪些已经签过了。
这样就不用每天都在同一个时间签到。原来固定触发的版本我也保留了,想用哪一个都可以。
又做了一个 Docker 版
就在前几天,我在刷 Linux DO 的时候,发现 ken861222 也做了一个相似的签到工具。他的项目可以部署在自己的服务器上,还带有一个网页面板,这又给了我灵感。
既然有常开的服务器,那就可以把程序放在上面,每天随机出一个具体时间,到点再签到,也方便自己选择时间范围、调整账号间隔。
所以我又给自己的程序加了一个网页面板,把账号管理、签到计划、执行记录和推送设置都放了进去,再整理成 Docker 版本。这样部署的时候也不用自己一个个安装 Python 依赖,启动容器后,打开网页就能配置。
如果大家有常开的服务器,或者支持 Docker 的 NAS,也可以用这个版本。
没有的话,华为云的两个版本也都还在,固定触发和随机时间可以自己选。原来的基础脚本、GitHub Actions 工作流同样保留,不过现在不推荐使用 GitHub Actions 作为长期签到的部署方式。
代码和部署说明放在这里:Longfor Sign。