解放双手系列,和上篇也有点关系。

这外挂是个Chrome插件,用付费取得的access-token登录。

MITMProxy

比较幸运,登录接口是HTTP的,抓包不需要折腾证书……

凑个json response骗插件绕过登陆,浏览器开代理指向mitmweb --script mitm.py --listen-port 8081:

import mitmproxy
import json
import time
import mitmproxy.http as http
# not real response
account = {
    "status": true,
    "key": value,
    # ... 
}
def make_header():
    return {
        "Etag": str(time.time()),
        "Content-Type": "application/json; charset=utf-8",
        "Access-Control-Allow-Origin": "*",
        }

def request(flow: mitmproxy.http.HTTPFlow):
    # not real url
    if "plugin-check-code-url" in flow.request.pretty_url and flow.request.method == "GET":
        flow.response = http.HTTPResponse.make(
            200, 
            json.dumps(account).encode('utf-8'),
            make_header())

JavaScript Deobfuscate

验证码模块经历了三个阶段:老URL无验证,新URL有验证,老URL失效。某次更新后发现验证码接口报错,就硬着头皮看js了,比预想中要简单一些。

老版本反混淆还没什么难度,就三个步骤:

  • eval string: 文件开头定义了一个array,里面是类似\x69\x6E\x69\x74的字符串,eval结果是init
  • array substitution: 选中array点击substitution,window[_$_b650[3]][_$_b650[2]](a)[_$_b650[10]](_$_b650[8])会变成window["localStorage"]["getItem"](a)["split"](",")
  • simplify access:上一句会变成window.localStorage.getItem(a).split(","),此时已经非常可读了

但是新版本加入了一个解密步骤:

// 签名大概是这样
function decode(index, key);
// window[_$_b650[3]]就变成了
window[decode("0x1", "b)1c")][decode("0x1", "7eMj")];
// key里右括号平添不少工作量

对应解决方案是:

  • 先在console里eval出decode
  • Ctrl-f调出搜索,decode\(.*?\)搜所有调用——key带右括号的要单独处理
  • 写个AHK重复点击:next match, eval…… 批处理需求已上路
  • 继续simplify access

反混淆完把验证码URL改回老的,坚持了几天,有一天它终于失效了。

验证码识别

老验证码识别URL失效,首先想到的是这届machine learning。

在Github逛了一圈,没找到pre-trained model,验证码风格最接近的是这个项目, 试着train了一会儿看loss比较高、吹的成功率至少要4次才有95%概率识别正确(假设它生成的验证码真能过拟合游戏验证码风格),就先搁置了。

又对比了一圈付费服务的价格和功能,选了7刀5000次、支持报告识别错误的deathbycaptcha:比如2分钟内提交了第二次识别请求, 说明上一个没识别对,向服务器报错上一个就不计费。真香 另外点名批评Google Cloud Vision和Azure,根本识别不出结果。

用Bottle简单写了个代理,模仿原版数据格式返回,把验证码函数endpoint改成它,又是一条好汉。顺便记录了验证码图片,可以用来做测试集。