2019年末逆向复习系列之Boss直聘Cookie加密字段__zp_stoken__逆向分析
- 2019 年 12 月 24 日
- 筆記
嗨!這个最有深度的『研究笔记』由程序员界最会排版の追星族运营?
2019年
12月16日
推荐几个高质量公众号!
小编:Lateautumn4lin(逆向小小小学生)
郑重声明:本项目的所有代码和相关文章, 仅用于经验技术交流分享,禁止将相关技术应用到不正当途径,因为滥用技术产生的风险与本人无关。
这篇文章是公众号《云爬虫技术研究笔记》的《2019年末逆向复习系列》的第七篇:《Boss直聘Cookie加密字段__zp_stoken__逆向分析》
本次案例的代码都已上传到Review_Reverse上面,后面会持续更新,大家可以Fork
一波。
具体加密JS可以在Review_Reverse.boss_zp.encrypt.js
中看到,替换具体的参数即可。

「
背景分析
」
Boss直聘
大概可以算是目前最火的招聘软件了!几个月前的火遍全网的“找工作,马上,和老板谈!”真的是洗脑了一遍又一遍啊!

鉴于Boss直聘
的职位更新速度快,职位发布多的两大特点,很多做行业分析和各地岗位差距报告的人都会利用Boss直聘
的数据来做分析,几个月前,Boss直聘
对他们的接口在Cookie
方面做了__zp_stoken__
字段的加密,这次案例就用它来做逆向分析。
「
逆向分析
」
__zp_stoken__字段生成流程分析
我们需要分析的是__zp_stoken__
字段,我们看看它是在哪里生成的

我们要获取Cookie
生成的过程,先把网站的数据清空,在Chrome
中的Application
中利用Clear site data
清理好该网站下的数据,然后我们重新刷新网站(我们需要在具体的职位信息页面刷新,因为首页不需要Cookie
就能直接访问),看看Network
列表中的Cookie
生成过程

我们首先会访问具体的职位信息页面,得到一个HttpCode
是302
的响应,代表这次的访问重定向到一个新的URL
了,这个URL
可以在响应中的Location
中看到,拿到的是security-check.html
这个URL
,我们看看这个URL
是什么内容


这个URL
具体有几个Param
name
ts
callbackUrl
srcReferer
seed
根据多次的访问测试,发现变化的字段主要是seed
、name
、ts
,也就是这个URL
是动态的,我们直接下断点是打不到具体的地方的,在Chrome
里我们也拿不到这个链接的具体响应内容,我们利用Fiddler
来获取链接内容

保存响应内容到html
中,具体看看里面的内容,文件中搜索__zp_stoken__
var Cookie = {
get: function(name) { var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); if ((arr = document.cookie.match(reg))) { return unescape(arr[2]); } else { return null; } }, set: function(name, value, time, domain, path) { var str = name + "=" + encodeURIComponent(value); if (time) { var date = new Date(time).toGMTString(); str += ";expires=" + date; } str = domain ? str + ";domain=" + domain : str; str = path ? str + ";path=" + path : str; document.cookie = str; } }; var url = window.location.href; var seed = decodeURIComponent(getQueryString("seed")) || ""; var ts = getQueryString("ts"); var fileName = getQueryString("name"); var callbackUrl = decodeURIComponent(getQueryString("callbackUrl")); var srcReferer = decodeURIComponent(getQueryString("srcReferer")||''); if (seed && ts && fileName) { seriesLoadScripts("security-js/" + fileName + ".js", function() { var expiredate = new Date().getTime() + 32 * 60 * 60 * 1000 * 2; var code = ""; var nativeParams = {}; var ABC = window.ABC || frame.contentWindow.ABC; try { code = new ABC().z(seed, parseInt(ts)+(480+new Date().getTimezoneOffset())*60*1000); } catch (e) {} if (code && callbackUrl) { Cookie.set("__zp_stoken__", code, expiredate, COOKIE_DOMAIN, "/"); // 据说iOS 客户端存在有时写cookie失败的情况,因此调用客户端提供的方法,交由客户端额外写一次cookie if (typeof window.wst != "undefined" && typeof wst.postMessage == "function") { nativeParams = { name: "setWKCookie", params: { url: COOKIE_DOMAIN, name: "__zp_stoken__", value: encodeURIComponent(code), expiredate: expiredate, path: "/" } }; window.wst.postMessage(JSON.stringify(nativeParams)); } if(srcReferer && isSeo(srcReferer) && srcReferer != 'https://m.baidu.com/'){ window.location.replace(srcReferer); } else { window.location.replace(callbackUrl); } } else { window.history.back(); } }); } else { if(srcReferer && isSeo(srcReferer) && srcReferer != 'https://m.baidu.com/'){ window.location.replace(srcReferer); }else if (callbackUrl) { window.location.replace(callbackUrl); } else { window.history.back(); } }
可以看出__zp_stoken__
是这么生成的
var seed = decodeURIComponent(getQueryString("seed")) || ""; var ts = getQueryString("ts"); code = new ABC().z(seed, parseInt(ts)+(480+new Date().getTimezoneOffset())*60*1000);
简化一下
480+new Date().getTimezoneOffset() = 0 code = ABC.z(seed,ts)
如上所示,通过传入的seed
和ts
参数调用实例ABC
的c
方法来生成,给当前的document
对象加上带__zp_stoken__
的Cookie
之后重新加载页面,这样整个从无Cookie
访问到生成Cookie
重载页面访问的流程就分析到这里。
__zp_stoken__生成逆向分析
1. 切中关键语句,下断点
刚才说了security-check.html
这个问题是动态的,我们不能去给这个文件下断点,重新看看security-check.html
的页面源码
var fileName = getQueryString("name"); seriesLoadScripts("security-js/" + fileName + ".js", function())
加载了包含加密方法的Js文件,fileName
是security-check.html
的params
中的name
字段,我们在Sources
的Page
中找到该Js
文件,整个文件一共是5381
行

上面我们分析了加密使用ABC
的z
方法,在文件中搜索ABC
字符,找到了z
方法

先对最后的return
语句打断点

重新刷新页面,可以获取的整个方法中各个参数的值,接下来开始分析整个加密方法的流程
2. 删繁就简,核心流程分析
分析z加密方法
ABC[_0x56ae('0x3e')]['z'] = function(_0x172aa3, _0x38e762) { //_0x56ae('0x3e') 是 prototype,ABC[_0x56ae('0x3e')]['z']也就是给ABC方法的prototype属性中的键z指定方法,_0x172aa3是seed,ts是时间戳 var _0x3a1f8c = {}; _0x3a1f8c[_0x56ae('0x3f')] = function(_0x5362ae, _0x320fdd) { return _0x5362ae == _0x320fdd; } ; _0x3a1f8c[_0x56ae('0x40')] = _0x56ae('0x41'); _0x3a1f8c[_0x56ae('0x42')] = _0x56ae('0x43'); _0x3a1f8c[_0x56ae('0x44')] = function(_0x2e473d, _0x3ad5ac, _0x1c497c) { return _0x2e473d(_0x3ad5ac, _0x1c497c); } ; _0x3a1f8c[_0x56ae('0x45')] = function(_0x1f3c2a, _0x218684) { return _0x1f3c2a + _0x218684; } ; try { if (document && _0x3a1f8c[_0x56ae('0x3f')](_0x3a1f8c[_0x56ae('0x40')], typeof document[_0x56ae('0x46')])) { _0x5606fe = _0x172aa3; _0x2f4975 = _0x38e762; } else { _0x5606fe = _0x3a1f8c[_0x56ae('0x42')]; } } catch (_0x1f6304) { _0x5606fe = _0x3a1f8c[_0x56ae('0x42')]; } //上面几项是给dict赋值,没有什么难度,可以不做修改,重点是最后的return语句中的方法 return _0x3a1f8c[_0x56ae('0x44')](_0x49423a, this, _0x3a1f8c[_0x56ae('0x45')](_0x172aa3[_0x56ae('0x47')], 0xb));
return
语句是这样,我们在console
中获取每个参数的值,看看能不能做精简
_0x3a1f8c[_0x56ae('0x44')]
function(_0x2e473d, _0x3ad5ac, _0x1c497c) { return _0x2e473d(_0x3ad5ac, _0x1c497c); }
_0x3a1f8c[_0x56ae('0x44')]是这样一个方法,参数一是方法,参数二、三作为参数一方法的参数
this
function ABC() { this['i0'] = 0x0; this['d'] = [this]; }
this就是ABC实例,可以在同一个文件中搜索ABC可以得到
_0x3a1f8c[_0x56ae('0x45')](_0x172aa3[_0x56ae('0x47')], 0xb
这个参数直接放在console里面运行,得到固定值是55,
从上面分析来看,return语句可以直接简化成
_0x49423a(ABC,55)
3. 还是那句话,缺啥补啥
我们现在拿到了关键语句,接下来就是调试_0x49423a
方法,往js
文件里面添加缺少的变量和函数方法,出于简单的考虑,直接使用node
来调用js
文件

直接拿node
运行js
文件得出来的结果和在Chrome
的console
里运行js
内容得到的结果不一样,看来还需要调试在哪个地方引用的windows
,this
之类的值两个运行环境有差别
__zp_stoken__参数生成注意要求
- Js加密函数所在文件不同,不过文件内容逻辑基本相同,只用分析一个文件即可
- __zp_stoken__参数生成之后,不能直接在Cookie中使用,需要做URLencode才能使用
「
复习要点
」
- 这个案例也是很好的动态
Cookie
生成的案例,和之前的努比亚论坛的Cookie
生成案例类似,利用window.location.href/replace/reload()
这些机制来重载页面,具体如下图所示 - 在调试的时候注意关键词的值,比如
this
,document
,windows
这些,往往有时候虽然方法走通了,但是值不对,很有可能就是上面这些值在赋值时发生了错误。

END
鉄子,“在看”来一个
