乐趣区

关于python:JS-逆向百例反混淆入门某鹏教育-JS-混淆还原

关注微信公众号:K 哥爬虫,继续分享爬虫进阶、JS/ 安卓逆向等技术干货!

申明

本文章中所有内容仅供学习交换,抓包内容、敏感网址、数据接口均已做脱敏解决,严禁用于商业用途和非法用处,否则由此产生的所有结果均与作者无关,若有侵权,请分割我立刻删除!

逆向指标

  • 指标:某鹏教育登录接口加密,含有简略的 JS 混同
  • 主页:aHR0cHM6Ly9sZWFybi5vcGVuLmNvbS5jbi8=
  • 接口:aHR0cHM6Ly9sZWFybi5vcGVuLmNvbS5jbi9BY2NvdW50L1VuaXRMb2dpbg==
  • 逆向参数:Form Data:black_box: eyJ2IjoiR01KM0VWWkVxMG0ydVh4WUd...

逆向过程

本次逆向的指标同样是一个登录接口,其中的加密 JS 应用了简略的混同,可作为混同还原的入门级教程,来到登录页面,轻易输出账号密码进行登录,其中登录的 POST 申请里,Form Data 有个加密参数 black_box,也就是本次逆向的指标,抓包如下:

间接搜寻 black_box,在 login.js 里能够很容易找到加密的中央,如下图所示:

看一下 _fmOpt.getinfo() 这个办法,是调用了 fm.js 里的 OO0O0() 办法,看这个又是 0 又是 O 的,多半是混同了,如下图所示:

点进去看一下,整个 fm.js 都是混同代码,咱们选中相似 OQoOo[251] 的代码,能够看到实际上是一个字符串对象,也能够间接在 Console 里输入看到其理论值,这个 OO0O0 办法返回的 oOoo0[OQoOo[448]](JSON[OQoOo[35]](O0oOo[OQoOo[460]])),就是 black_box 的值,如下图所示:

仔细观察,能够发现 OQoOo 应该是一个相似数组的货色,通过传入元素下标来顺次取其实在值,轻易搜寻一个值,能够在代码最初面找到一个数组,这个数组其实就是 OQoOo,能够传入下标来验证一下,如下图所示:

到这里其实就晓得了其大抵混同原理,咱们能够把这个 JS 拿下来,到本地写个小脚本,将这些值替换一下:

# ==================================
# --*-- coding: utf-8 --*--
# @Time    : 2021-11-09
# @Author  : 微信公众号:K 哥爬虫
# @FileName: replace_js.py
# @Software: PyCharm
# @describe: 混同还原小脚本
# ==================================


# 待替换的值(太多了,仅列出少部分)# 以理论列表为准,要和 fm_old.js 里的列表统一
item = ['referrer', 'absolute', 'replace',...]

# 混同后的 JS
with open("fm_old.js", "r", encoding="utf-8") as f:
    js_lines = f.readlines()

js = ""
for j in js_lines:
    js += j

for i in item:
    # Qo00o 须要依据你 fm_old.js 具体的字符串进行替换
    str_old = "Qo00o[{}]".format(item.index(i))
    js = js.replace(str_old, '"'+ i +'"')

# 还原后的 JS
with open("fm_new.js", "w", encoding="utf-8") as f:
    f.write(js)

应用此脚本替换后,可能会发现 JS 会报错,起因是一些换行符、斜杠解析谬误,以及双引号重复使用的问题,能够本人手动批改一下。

这里须要留神的一点,fm.js 前面还有个后缀,相似 t=454594,t=454570 等,不同的后缀失去的 JS 内容也有差别,各种函数变量名和那个列表元素程序不同,实际上调用的办法是同一个,所以影响不大,只须要留神替换时列表内容、须要替换的那个字符串和你下载的 JS 文件里的统一即可。

将 JS 还原后,咱们能够将还原后的 JS 替换掉网站自身通过混同后的 JS,这里替换办法有很多,比方应用 Fiddler 等抓包工具替换响应、应用 ReRes 之类的插件进行替换、应用浏览器开发者工具自带的 Overrides 性能进行替换(Chrome 64 之后才有的性能)等,这里咱们应用 Fiddler 的 Autoresponder 性能来替换。

实测这个 fm.js 的后缀短时间内不会扭转,所以能够间接复制其残缺地址来替换,要谨严一点的话,咱们能够用正则表达式来匹配这个 t 值,在 Fiddler 外面抉择 AutoResponder,点击 Add Rule,增加替换规定,正则表达式的办法写法如下:regex:https:\/\/static\.tongdun\.net\/v3\/fm\.js\?t=\d+,留神 regex 前缀必不可少,上方顺次选中 Enable rules(利用规定)、Accept all CONNECTs(承受所有连贯)、Unmatched requests passthrough(不匹配规定的就依照之前的申请地址发送过来),Enable Latency 是设置提早失效工夫,不必勾选,如下图所示:

替换后再次登录,下断点,能够看到当初的 JS 曾经清晰了不少,再看看这个函数最初的 return 语句,oQOQ0["blackBox"] 蕴含了 itostv 三个参数,应用 JSON 的 stringify 办法将其转换成字符串,而后调用 QQo0 办法进行加密,如下图所示:

咱们先来看看 oQOQ0["blackBox"] 里的四个参数,其中 itosv 三个参数在这个函数开始就曾经有定义,v 就是 Q0oQQ["version"],是定值,间接搜寻能够发现这个值是在最开始的那个大列表里,os 为定值,it 是两个工夫戳相减的值,O000o 这个办法就是两个值进行相减,oQOQo 这个工夫戳能够搜寻 var oQOQo,是一开始加载就生成的工夫戳,JS 一开始加载到点击登陆进入加密函数,也就一分钟左右,所以这里咱们能够间接生成一个五位随机数(一分钟左右在毫秒上的差值在五位数左右)。

当初就剩下一个 t 参数了,往下看 t 其实就是 Q0oQQ["tokens"],两头通过了一个 if-else 语句,能够埋下断点进行调试,发现其实只执行了 else 语句,对 t 赋值也就这一句,所以剩下的代码其实在扣的时候都能够删掉。

这个 tokens 屡次测试发现是不变的,尝试间接搜寻一下 token 关键字,能够发现其赋值的中央,对 id 依照 | 符号进行宰割,取其第 1 个索引值就是 tokens,再看看 id 的值,并没有找到显著的生成逻辑,复制其值搜寻一下,发现是通过一个接口返回的,能够间接写死,也能够本人先去申请一下这个接口,取其返回的值,如下图所示:

自此所有参数都找完了,回到原来的 return 地位,还差一个加密函数,即 ooOoO["encode"](),间接跟进去,将这个办法扣下来即可,本地调试缺啥补啥,将用到的函数补全就行了。

残缺代码

GitHub 关注 K 哥爬虫,继续分享爬虫相干代码!欢送 star!https://github.com/kgepachong/

以下只演示局部要害代码,不能间接运行! 残缺代码仓库地址:https://github.com/kgepachong…

JavaScript 加密要害代码架构

function oQ0OQ(Q0o0, o0OQ) {return Q0o0 < o0OQ;}

function O000O(Q0o0, o0OQ) {return Q0o0 >> o0OQ;}

function Qo0oo(Q0o0, o0OQ) {return Q0o0 | o0OQ;}

function OOO0Q(Q0o0, o0OQ) {return Q0o0 << o0OQ;}

function OooQo(Q0o0, o0OQ) {return Q0o0 & o0OQ;}

function Oo0OO(Q0o0, o0OQ) {return Q0o0 + o0OQ;}

var oQoo0 = {};
oQoo0["_keyStr"] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
oQoo0["encode"] = function QQQ0(Q0o0) {
        var o0OQ = 62;
        while (o0OQ) {switch (o0OQ) {case 116 + 13 - 65: {}
                case 118 + 8 - 63: {}
                case 94 + 8 - 40: {}
                case 122 + 6 - 63: {}}
        }
    };
oQoo0["_utf8_encode"] = function oOQ0(Q0o0) {}

function OOoO0() {
    var tokens = "e0ia+fB5zvGuTjFDgcKahQwg2UEH8b0k7EK/Ukt4KwzyCbpm11jjy8Au64MC6s7HvLRacUxd7ka4AdDidJmYAA==";
    var version = "+X+3JWoUVBc12xtmgMpwzjAone3cp6/4QuFj7oWKNk+C4tqy4un/e29cODlhRmDy";
    var Oo0O0 = {};
    Oo0O0["blackBox"] = {};
    Oo0O0["blackBox"]["v"] = version;
    Oo0O0["blackBox"]["os"] = "web";
    Oo0O0["blackBox"]["it"] = parseInt(Math.random() * 100000);
    Oo0O0["blackBox"]["t"] = tokens;
    return oQoo0["encode"](JSON.stringify(Oo0O0["blackBox"]));
}

// 测试样例
console.log(OOoO0())

Python 登录要害代码

# ==================================
# --*-- coding: utf-8 --*--
# @Time    : 2021-11-10
# @Author  : 微信公众号:K 哥爬虫
# @FileName: open_login.py
# @Software: PyCharm
# ==================================


import time
import execjs
import requests


login_url = "脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler"


def get_black_box():
    with open('get_black_box.js', 'r', encoding='utf-8') as f:
        exec_js = f.read()
    black_box = execjs.compile(exec_js).call('OOoO0')
    return black_box


def login(black_box, username, password):
    params = {"bust": str(int(time.time() * 1000))}
    data = {
        "loginName": username,
        "passWord": password,
        "validateNum": "","black_box": black_box
    }
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
    }
    response = requests.post(url=login_url, params=params, data=data, headers=headers)
    print(response.json())


def main():
    username = input("请输出登录账号:")
    password = input("请输出登录明码:")
    black_box = get_black_box()
    login(black_box, username, password)


if __name__ == '__main__':
    main()

退出移动版