术语
1. 动态js
- 在js运行期间,通过字符串拼接等形式生成一段js代码,然后通过eval或Function的方式动态执行
- 某数的动态js是和页面配套的,ajax请求不会改变动态js
- F5刷新页面或者浏览器输入网址得方式都会生成新的动态js
- 动态js和生成他的页面有配套关系,不配套时生成的cookie或MmEwMD参数无效
- 某数动态js每次改变的仅仅是变量名称,其他逻辑丝毫不变(敲黑板,划重点)
- 动态js是某数反爬的核心代码,有上万行代码
2. $_ts
- 某数中仅有的固定名称的变量
- 是一个全局变量,页面js、动态js通过该全局变量互通有无
- 全局变量再页面js、动态js均有替身,替身的名字随机生成
3. 变量名称池
- 某数在生成动态js时维护了一个变量名称的数组,称为变量名称池
- 页面代码根据这个变量名称池生成动态js的变量名
- 对于不同的动态js可惜根据这个变量名称池的下标进行对应
4. MmEwMD 参数后缀
- ajax请求的url地址信你自动添加的后缀参数
- 在职位列表页面内点击翻页或者筛选时会发现ajax请求都再url中添加了后缀参数,其中 MmEwMD后缀参数有用,其他几个参数作用不大
5. FSSBBIl1UgzbN7N443T、FSSBBIl1UgzbN7N443S
- cookie中存放的key值,其中FSSBBIl1UgzbN7N443S是服务器端返回的(httponly),FSSBBIl1UgzbN7N443T是通过js在浏览器生成的,目前来看443S服务器端未校验
- 这个443其实是服务器的端口号,有的网站是FSSBBIl1UgzbN7N80T、FSSBBIl1UgzbN7N80S
6. 版本号
- 443T的第一位、MmEwMD后缀参数的第一位都是数字,表示某数的版本,某联用的是5版本
7. localStorage
- 动态js中部分信息会存储到localStorage中,localStorage不可用时回通过全局变量存储
8. 六个盒子
- 在动态js中有6个长度为255的整数数组,在字符串转整数数组加密时会用到这些数组进行置换,我称之为六个盒子。
页面组成
1. meta content
- 存放的是一些在动态js中要用到的字符串常量
2. js引用 iso编码的文件
- 这个文件名和内容时目前是固定的(大大降低了难度)
- 这个是动态js的主要内容来源
3. 页面js代码
- 生成全局变量$ts,名称固定
- 结合引用的外部js生成动态js
- 动态js的变量名是随机生成的,形式为_$xx
- 开头的几个函数都是在动态js中要调用的
- 页面js还有
requestid
目前来看没用处
正常访问流程
1. 访问页面
- 生成并执行动态js代码
- 动态js对xhr进行hook
- 增加定时任务
- 挂载各种鼠标、键盘等事件监听
2. 定时更新cookie
- 动态js定时(50s)更新cookie中的FSSBBIl1UgzbN7N443T
- 该有另外一个定时任务,用来投毒检验
3. 收集鼠标键盘事件
- 如果有鼠标点击(按下、松开)、移动、滚轮滚动或者键盘按键封事件发生,会将相关数据写入一个数组,同时记录事件类型以及该事件类型的次数
- 目前发现服务端没有进行检验
4. ajax请求
- 对ajax请求进行拦截,基于cookie中的FSSBBIl1UgzbN7N443T生成一个新的FSSBBIl1UgzbN7N443T,再这个新的FSSBBIl1UgzbN7N443T后添加一个64位字符串行程MmEwMD后缀参数追加到url中
- 除追加上述参数在,在此之前还会追加paeid、requestid等参数,实际发现不加这两个也没有影响
动态js主要逻辑分析
动态js的业务逻辑可以划分为
- 初始化阶段
- 定时任务阶段
- ajax hook触发阶段
初始化阶段
也就是通过eval执行动态js的时候,这个过程主要工作有
- 是对js代码的变量进行初始化赋值
- 对页面meta中的content进行解析处理成字符串常量数组
- 根据一个固定密钥(84位)生成6个整数数组(六个盒子),字符串转整数时都基于6个盒子进行转换
- 生成一个初始的FSSBBIl1UgzbN7N443T,存入cookie中
定时任务阶段
- 50秒重新生成一次FSSBBIl1UgzbN7N443T,新FSSBBIl1UgzbN7N443T的生成需要基于cookie中已有的FSSBBIl1UgzbN7N443T
- 此过程也需要读取meta中的一个字符串常量
- 也需要全局变量$_ts中数据参与
- 还需要调用页面js代码中的函数
- 新生成的FSSBBIl1UgzbN7N443T存入cookie中
ajax hook触发阶段
- 检验请求的域名是都在白名单中
- 检验请求的协议是否时http或者https
- 基于cookie中的FSSBBIl1UgzbN7N443T生成一个新的FSSBBIl1UgzbN7N443T(新生成的不存入cookie中)
- 加密生成一个64位字符串拼接到FSSBBIl1UgzbN7N443T之后,作为MmEwMD追加但请求的url之后
- 发起ajax请求,得到json格式的职位列表信息
核心代码提取
以上主要逻辑梳理清晰后,就可以把不相干的代码干掉了,精简后不到1000行代码。
- 进行简单的封装,提供一个对外函数,输入ajax请求的url,返回一个追加了MmEwMD后缀参数的url
- 通过其他语言执行js,调用对外函数
- 其他需要就可以爬去职位列表了
- 当然某联限制第3页及以后的数据需要登录才能拿到,登录的cookie就小菜一碟了!
后记
- 六个盒子的置换算法有点像置换密码本加密,又不是,不确定属于哪种加密算法,不过挺有趣!(随然折腾了我挺长时间!)
- 关于鼠标键盘事件,暂时直接屏蔽了,不确定大规模采集是否回检验,不过追加这些数据难度不大,按规则伪造即可
- requesid、pageid直接忽略了,但后续版本可梦会增加这些参数的检验
- 只需要分析一套页面js、meta、动态js,然后将其中的一些变量数据都可以固化下来,然后就可以以不变应万变了生成MmEwMD了
页面reload机制
在2020年5月1日前 存在这种机制,目前没有了
- 第一次访问页面会返回一个只包含百余行js代码的简单页面
- 这段代码作用流式生成一个acw_sc__v2字样的cookie
- 携带这个cookie自动重新夹在页面,就看到那个带meta的主页面了
第一次访问
响应的页面是一段含有JS代码的页面,并且
Set-Cookie: acw_tc=2760825115885621782278886e6ff96bc0a1ae185b1a44bad37a5e81b0450e;path=/;HttpOnly;Max-Age=2678401
JS代码负责生成cookie acw_sc__v2,并再次重新加载当前页面
第二次访问
携带两个cookie值
Cookie: acw_tc=2760825115885621782278886e6ff96bc0a1ae185b1a44bad37a5e81b0450e; acw_sc__v2=5eaf89021c00cc59ef77c712ae257ab673cb0a83
响应设置两个cookie值:
Set-Cookie: FSSBBIl1UgzbN7N443S=Ln0Z3igJCz1OlYV7LMd.SROLwsWoQ21ZB5uwPc7iL9A9AsK.dwTKxVy6cCRZuIVs; Path=/; expires=Thu, 02 May 2030 03:16:18 GMT; Secure; HttpOnly```
此次访问的响应页面,包含meta content 以及加密js引用等,负责每隔50秒生成一个cookie FSSBBIl1UgzbN7N443T