05--JS05--逆向01:抓包工具、PyExecjs模块、扣JS代码
JS逆向01:浏览器工具、抓包工具、PyExeJs模块、扣JS代码
# 逆向的关键点:
加密 或解密 的入口
一、浏览器工具
1.1 显示请求协议
# protocal n.协议
# http协议的版本:
http/1.1 、h2 # 大多数网站是两者协议兼容,极少数的网站 会检测http协议的版本,则请求需要一致
# request模块 使用的是 http/1.1
# 发送h2协议的 则需要安装模块 httpx
pip install httpx
# httpx简单使用(与 aiohttp模块使用相似) 不得万不得已的情况,不做这个h2的尝试
client = httpx.Client(http2=True,)
client.get(参数与request一致)
client.post(参数与request一致)
1.2 事件启动器
# 查找加密的入口
### 方案一:顺序查找
1.在发送请求的 Initiator(启动器)中,点击第一个JS方法 # 这是发送请求的最后一步,参数一定是加密形式
会自动跳转到 第一个JS中的 具体发送请求方法的行
2.依次向下 点击右侧 的Call Stack(调用栈),观察参数的变化 # 这是最后 具体发送请求方法的调用栈
3.参数什么时候,由加密形式 变化为成正常状态
说明这一层的函数调用之前,一定进行过参数加密的处理 # 加密的入口,就找到了
4.剩下就是 分析、扣加密的代码
1.3 搜索窗口
# 右上角 三点 ---> Search (Ctrl + Shift + F 是全站的资源搜索)
# 搜索的小技巧:搜索url时,别搜索整个url,只搜索url中的 关键词
eg: https://fanyi.baidu.com/v2transapi?from=en&to=zh ----> 搜索 v2transapi 即可
# 查找加密的入口
### 方案二:搜索查找 按照经验来搜索某个关键词,有一定的随机性
根据 请求参数 "encSecKey",一看就是跟加密有关的
1.在 全局的 Search中,搜索,会出现 各个JS文件与关键词有关 方法
2.依次点击每个JS文件, 观看,第一个只是 一个函数定义 不是
第二个发现 有赋值的操作,打上 断点,调试一下 # 发现加密入口
3.在每个JS文件中,再Ctrl+F 搜索一下关键词 # 一个JS文件,可能出现多个位置的关键词
不确定是否是入口,就都打上断点,调试 看看值变化
1.4 XHR/fetch 断点
# 查找加密的入口
### 方案三:自动打断点 在发送每个Ajax请求 或 请求url包含某个关键词
1.在 Sources中,右侧 XHR/fetch Breakpoints,
勾选 Any XHR or fetch(所有ajax请求,打断点) 或者 点击加号 Break when URL contains:(url包含什么时,打断点)
2.再依次向下 点击右侧 的Call Stack(调用栈),观察参数的变化
3.参数什么时候,由加密形式 变化为成正常状态
说明这一层的函数调用之前,一定进行过参数加密的处理 # 加密的入口,就找到了
4.剩下就是 分析、扣加密的代码
二、Fiddler和Charles
在处理一些网站的时候,会遇到一些屏蔽F12,以及只要按出浏览器的开发者工具,就会关闭甚至死机的现象
在遇到这类网站的时候,可以使用抓包工具,把页面上屏蔽开发者工具的代码给干掉
这两款工具是非常优秀的抓包工具。它们可以监听到计算机上所有的http网络请求。利用这种特性,可以获取到页面加载过程中的所有内容
2.1 fiddler简单配置(HTTPS)
接下来,打开www.vmgirls.com,尝试抓包看看,发现https的请求默认是不可用的,需要配置一下证书。
2.2 fiddler简单使用
清理一下信息,然后重新刷新浏览器, 就看到被响应的html内容了
vmgirl之所以不可以打开开发者工具,是因为在其html中,导入了一个disable-devtools.js。
需要想办法把这个东西给干掉,只要干掉了它,就又可以打开F12的开发者工具了
那么如何干掉它呢? 这就需要了解一下fiddler和charles的工作原理了.
fiddler和charles本质,其实就是一个安装在这台计算机上的一个代理,就像这样:
本质上和代理没啥区别,但是, 由于fiddler是自己本地的软件,那么可以在fiddler里对请求过来的内容,进行截取和替换
做这样一个事情,把vmgirls的页面源代码捕获,单独写入一个html文件,然后把其中的disable-devtools.js部分注释掉.
现在这个页面源代码在本地了,就可以进行替换了.... 或者 替换 disable-devtool.min.js文件,为本地的一个空文件 也可以!
接下来. 重新刷新页面(ctrl+shift+r) , 发现右键有效果了.
2.3 charles安装使用
charles官网: https://www.charlesproxy.com/download/
charles激活: https://www.zzzmode.com/mytools/charles/
- 1.安装证书
注意:
在windows环境下,如果出现证书不可用的情况,需要手工去windows的证书管理器中,
把Charles的证书拖拽到可信任证书那一栏,然后重启浏览器和charles就可以用了
如果证书默认就可用. 就不要胡乱拖拽
window证书管理器: win+r: 输入certlm.msc
- 2.配置443端口
Mac
- 3.替换为本地文件
如果想要实现请求拦截,怎么办?
三、PyExecJS模块
pyexecjs是一个可以运行js代码的一个第三方模块,其使用是非常容易上手的
但是它的运行是要依赖能运行js的第三方环境的,这里选择用node作为运行js的位置.
3.1 安装Nodejs
一路下一步,修改安转位置,最后控制台输入:node -v
npm -v
即可
在pycharm中,也安装 nodejs 插件,方便调试JS文件
3.2 安装pyexecjs
pip install pyexecjs
测试一下:
import execjs
print(execjs.get().name) # Node.js (V8) 是V8引擎就正确
3.3 简单使用
import execjs
print(execjs.get().name)
# execjs.eval 可以直接运行js代码并得到结果
js = """
"鲁班_王昭君_猴子_亚瑟_蔡文姬".split("_")
"""
res = execjs.eval(js)
print(res)
# execjs.compile() 与 call()
# execjs.compile():事先加载好一段js代码 先编译
jj = execjs.compile("""
function an(a, b){
return a + b
}
""")
# call():运行代码中的xxx函数. 后续的参数是xxx的参数 再执行js字符串编译后 js代码
ret = jj.call("an", 10, 20)
print(ret)
windows中如果出现编码错误. 在引入execjs之前. 插入以下代码即可.
import subprocess
from functools import partial # functools 函数库 提供一堆为高阶函数服务的偏函数 (工具函数、辅助函数的意思)
# execjs原理是用 window 终端 执行 node xxx.js
# 而window终端 默认字符编码是"GBK",故修改为 utf-8
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')
# partial(fn函数名,参数=值) partial n.部分
作用:为fn函数,固定 某个参数值
import execjs
完事儿,execjs就这几个功能就够咱使用
3.4 实战案例(调试工具)
接下来,来破解真正的百度翻译
通过抓包可以发现:之前抓取的百度翻译,其实是个伪翻译
真正的一句话的翻译,其实它的url应该是:https://fanyi.baidu.com/v2transapi?from=en&to=zh
接下来就是分析这一坨
y = {
from: d.fromLang, // 从xxxx
to: d.toLang, // 翻译成xxxx
query: e, // 被翻译的内容
transtype: r, // 固定的翻译类型
simple_means_flag: 3, // 常量
sign: L(e), // ????
token: window.common.token, // token. 这个在页面源代码里能找到
domain: R.getCurDomain() // 固定值.
};
现在有3个值,无法直接确定。一个是L(e)。 一个是token。另一个是R.getCurDomain()
先看 token
由于在进行翻译的时候,页面是没有刷新的,所以这个token一般在进行翻译的时候,是不会改变的
再看 domain
在页面中设置一个断点,并重新发送请求
点进去返回的是个M. 找M
OK, M是常量,"common"
最后看 L(e)
首先e是被翻译的内容,所以这里是把"要翻译的内容"传递给了L.。但是这个L在哪里,不好找。很简单,设置一个断点,就可以
程序停在这里了,接下来就是这三个按钮,从左到右:
第一个: 释放掉当前debug,程序继续向后运行,直到结束或下一个断点
第二个: 运行下一步,相当于一行代码。
咱们这里,是在一个{}中间进行的断点。而整个{}; 被认为是一行代码,所以下面的 token等会被过掉
第三个: 运行到当前行内部。
咱们这里,就是运行到L(e)里面,看看L(e)里面是干什么的
这里就是那个L(e)了,但这东西属实看不明白,也不容易看明白。里面是一个签名算法,十分难搞
仔细观察下,发现该函数内部只调用了n() a()函数。其他函数都是js内置的东西。OK 向上简单寻找就发现了,这里是一个大闭包
也就是说整个签名的计算,就这些功能。没有调用外界的其他功能。那就好办了。可以直接把这个闭包内的三个函数搞出来,变成我们的js文件
尝试着签个名试试看:
import requests
import execjs
f = open("bbd.js", mode="r", encoding='utf-8')
js = execjs.compile(f.read())
# execjs._exceptions.ProgramError: ReferenceError: window is not defined.
ret = js.call('e', 'love')
print(ret)
报错了,说这里面没有window
确实,由于我们的js是在node里面运行的,node是没有window对象的. 怎么办?
整合起来这里要用到window['gtk'],这玩意哪里找? 老实说,真不好找。在前面找token的时候,偶然间,发现了这样一段代码:
OK了 . 搞定... 只要把js里面的window['gtk'],修改成这个就好了
至此, 所有参数到位. 写代码就好了.
代码:
import requests
import execjs
f = open("baidu加密.js", mode="r", encoding='utf-8')
js = execjs.compile(f.read())
url = "https://fanyi.baidu.com/v2transapi?from=en&to=zh"
content = input("请输入一段英文:")
data = {
"from": "en",
"to": "zh",
"query": content,
"transtype": "realtime",
"simple_means_flag": "3",
"sign": js.call("e", content),
"token": "c63d27ea65fdf259bf4d56b792e47b17",
"domain": "common",
}
headers = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Referer": "https://fanyi.baidu.com/",
"Cookie": "REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; BIDUPSID=7214BF57BA799CC46A76979BF673523E; PSTM=1623064780; __yjs_duid=1_7946e25e70873bbe5515a663714cef991623067825398; BAIDUID=C434B9AC411BEFD0688E2DD206C8429A:FG=1; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; MCITY=-131%3A; BDUSS=kl4RWZySlNaNlRrdWNkSVRXYjYtNGkyUzFCZlhmeklhMDFtcjRLM01GdG4zMVZoRVFBQUFBJCQAAAAAAAAAAAEAAACh3e-019TDvczlwfnB-bjnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGdSLmFnUi5hM; BDUSS_BFESS=kl4RWZySlNaNlRrdWNkSVRXYjYtNGkyUzFCZlhmeklhMDFtcjRLM01GdG4zMVZoRVFBQUFBJCQAAAAAAAAAAAEAAACh3e-019TDvczlwfnB-bjnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGdSLmFnUi5hM; H_PS_PSSID=34438_34440_34380_34496_33848_34450_34092_34107_26350_34425_22160; delPer=0; PSINO=2; BA_HECTOR=a08k202181a52h208d1gj6e0m0q; BAIDUID_BFESS=DDAD1DC127ED72714BFB37825559ACDB:FG=1; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1628330239,1628390196,1629451250,1630747372; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1630747372; ab_sr=1.0.1_ODBhYjIwYzRhMjk4NDNhZDYwYjRmZWZjM2QwY2E3MGNlMDRjYTI1ZjViODM2MDgxODk5NzJjNDFkZTAxYzc0OTVkYmNhYjZiMWMzZDFmNWFkN2QxMzAwMzZlZjhjYmE3ODkxZGRkNDJkNTY2N2M0NTQxODU5YmU4YTA1NGZjYTJkMDUzN2ZkMGVkMzlmOTU1M2ZlZTRhYWY4ZDQ5YTZmNTJjYmExYzk1ZTdiY2I5MTA4YjcyZDE3MWE1MTE5YjBj",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"
}
resp = requests.post(url, data=data, headers=headers)
print(resp.json())
四、JS逆向方案一:扣JS代码
# 扣JS代码 缺啥补啥 缺点:麻烦 繁琐 优点:万能
根据加密入口,一步步 将 函数 及 参数 补全,
直到只剩 JS原生代码 及 函数方法调试成功
# 若遇到第三方库时,就不需要在网站的js中 复制扣了,
采用node中的 npm来安装
# eg: CryptoJS库,是JS常用来 加密的库
# 注意:需要在你 项目文件 右键 ---> open in terminal
npm install crypto-jS # 好处是 安装的第三库,直接在你项目根目录下
# 引入/导入 安装的第三库
var CryptoJS = require("crypto-js");