文章目录
1. 写在前面2. 扫码接口分析2. 短信接口分析3. 加密算法还原
【?作者主页】:吴秋霖
【?作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【?作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
1. 写在前面
本篇文章主要就是通过Python的方式实现可以远程发送验证码登录网站并提取Cookie更新到爬虫的这么一个自动化方案构建!这样的场景在爬虫业务中很常见,抓取数据需要账号、而一些网站只支持扫码或者手机验证码的方式登录!
而采集的Cookie一般都是有时效性的,一些聚合类的平台也就一两天基本都失效了。所以对于工程师开发者们来说,手动更新Cookie的动作显得尤为鸡肋和低效,说不定很忙的时候就忘记有这么回事了,然后线上的爬虫程序上去一看日志都是异常…
如果网站支持账号密码登录的话则更好,对登录接口进行逆向分析封装为一个定期登录更新的程序即可。像只支持扫码、短信登录的我们也可以对其接口进行分析,使用程序封装接口脱离网站的方式进行更新
2. 扫码接口分析
扫码登录的一般在打开扫码的时候会有一个接口创建二维码的信息,其中包含了这个二维码的标识(key或者id)跟二维码的链接地址,如下所示:
这个二维码的链接地址在浏览器是可以直接访问的,有无法直接访问的话可以通过重新渲染的方式生成二维码并指向这个链接地址即可,如下所示:
二维码在创建或刷新成功后,会有一个实时监测的接口不断的请求来检测用户扫码的状态!这个接口是有时效性的,也就是说当前二维码过期接口则失效。我们可以看到每一次请求扫码状态的时候需要携带key、k参数,如下所示:
扫码状态接口返回结果:{“code”:0,“data”:{“status”:1}}
k参数是动态变化的,因为加密有时间戳参数。所以在创建出二维码后,我们就需要模拟这个请求去监测扫码状态根据接口返回的status字段判定即可
一般0是二维码未被扫描、1是已扫码等待确认登录、2则是登录成功、3一般则是失效之类的
2. 短信接口分析
这里我们直接从公众号入口去进行抓包分析手机短信登录接口,Web的话资源加载的太多油有点卡顿!输入手机号点击发送验证码,查看发包信息,如下所示:
相比与扫码登录的话流程稍微简洁一些,就一个K参数是加密的,接下来我们手机接受到短信验证码以后查看提交验证码接口发包情况,如下所示:
可以看到提交短信验证码之后,登录成功接口则返回账号登录信息token
3. 加密算法还原
之前文章有详细说过关健加密位置的定位与分析,这里不再重复感兴趣的去阅读之前的文章
这里我们把POST加密的核心JS算法拿下来,代码并不多稍微还原一下就可以,如下所示:
function h(e, path, n, r) {var s = n.s , d = n.k , f = n.l , v = n.d , h = n.sort , k = n.num , y = function(content, t, e) { for (var a = Array.from(content), n = Array.from(t), r = a.length, o = n.length, d = String.fromCodePoint, i = 0; i < r; i++) a[i] = d(a[i].codePointAt(0) ^ n[(i + e) % o].codePointAt(0)); return a.join("")}(function(s, t, path, e) { return [s, t, e, path].join("(&&)")}(function(t, e) { var n = c()(t); if (!_()(n)) { var r = []; for (var d in n) m()(n[d]) && "get" === e && (n[d] = n[d].join("")), "post" === e && (m()(n[d]) || o()(n[d])) && (n[d] = JSON.stringify(n[d])), r.push(n[d]); return r.sort(), r.join("") }}(e, r), parseInt((new Date).getTime() / 1e3) - 655876800 - v, path, h), Object(l.b)(s, d, f), k);return t.from(y).toString("base64")}
这里直接使用Python对上面的JS进行还原,完整的Python算法代码如下所示:
import base64import timefrom Crypto.Cipher import AESfrom Crypto.Util.Padding import unpadimport jsondef from_str_to_bytes(text, encoding): return text.encode(encoding)def wf(e, n, o): cipher = AES.new(n, AES.MODE_CBC, o) decrypted = cipher.decrypt(bytes.fromhex(e)) return unpad(decrypted, AES.block_size).decode('utf-8')def encryptData(e, path, n, r): we_params = {} s = n['s'] c = n['k'] f = n['l'] l = n['d'] _ = n['sort'] y = n['num'] def k(content, t, path, e): content_str = f"{content}&({t})&{path}&{e}" a = list(content_str) n = list(t) r = len(a) o = len(n) result = "" for i in range(r): result += chr(ord(a[i]) ^ ord(n[(i + e) % o])) return result def process_params(t, e): if isinstance(t, dict): r = [] for c in t: if isinstance(t[c], list): if e == "get": t[c] = "".join(t[c]) elif e == "post": t[c] = json.dumps(t[c]) r.append(t[c]) r.sort() return "".join(r) return t processed = process_params(e, r) timestamp = int(time.time()) - 655876800 - l encrypted_str = k(processed, c, path, timestamp) return base64.b64encode(encrypted_str.encode('utf-8')).decode('utf-8')def get_encrypted_data(params, data, path): we_params = params o = encryptData(we_params, path, data, "post") return odef submit_sms_code(params, data): return get_encrypted_data(params, data, "/v1/user/login/code")def send_sms_code(params, data): return get_encrypted_data(params, data, "/v1/user/vc")
接下来我们把整个手机登录的流程封装测试验证一下,如下所示: