该文章是在工作碰到的需要js破解的网站,记录下破解过程。希望对js破解有一定的帮助,用以参考。
查询加密的大招:
全域搜索enc.utf8.parse,在此打断点,调试查看运行过程
第一个网站
-
分析请求
请求参数是经过加密的,因此需要分析发送请求的过程。 -
ajax请求分析
2.1 开启ajax断点
2.2 点击按钮后会进入调试模式,一直按 F10 键,跑完一次请求,并观察。
2.3 发现请求参数,然后在其前面打断点(下次直接按 F8即可到达断点位置) -
分析代码
3.1 通过url全局搜索,来寻找函数入口。
Sources栏下,右击网站网址,出现 search in all files,点击进入输入部分url
此处可以输入:ScoreLines/UCodes/QueryList
即可到达请求入口函数
该处可以得到原始请求参数和对参数进行加密处理的函数。
3.2 继续向下搜索,
此处即为全局搜索:(函数名称) youzyEpt
当然,还要根据调用路径找到所有调用函数及文件。
然后对每行代码进行分析,可以尝试使用python实现。
var text = JSON.stringify(data);# 该行对字典进行字符串化,可以用python实现
var textBytes = youzyEptService.utils.utf8.toBytes(text);
var aesCtr = new youzyEptService.ModeOfOperation.ctr(tzyCollegeFirst.aesKey(), new youzyEptService.Counter(5));
var encryptedBytes = aesCtr.encrypt(textBytes);
var encryptedHex = youzyEptService.utils.hex.fromBytes(encryptedBytes);
- 最快速的js破解方式
最快的方式就是使用 execjs包直接调用js函数实现加密破解。
4.1 首先要将js代码剥离出来
-.自建一个js文件,将 youzyEpt函数copy到文件内,在将执行过程中的js代码依次分析并剥离,copy到自己的js文件中。
-.此处相对较为容易,因为加密代码都在一个文件(即youzyEpt.sever.js)中,只是将加密的密匙进行了几个文件处理,由于是AES加密,密匙肯定固定可以进行分析得到。
-.此处我的处理就是:将youzyEpt.sever.js文件整个copy,将youzyEpt函数copy到文件内,然后根据调用顺序,找到最低层调用函数,将这些函数copy,独立为独立函数。
python调用js代码如下:
# 如果运行报错,可能需要安装node.js
import execjs
def get_js(path):
f = open(path, 'r', encoding='utf-8') # 打开JS文件
line = f.readline()
htmlstr = ''
while line:
htmlstr = htmlstr + line
line = f.readline()
return execjs.compile(htmlstr)
path1 = "./youzyEpt.sever1.js"
ctx = get_js(path1)
data = {
"provinceId": provinceId,
"collegeId": collegeId
}
res_str = ctx.call('youzyEptt', data)
4.2 然后,运行文件,根据报错来提取函数,再运行,如此循环处理,直到跑通,得到需要的结果。
在这过程中,不管是单一方法,还是类对象,都是copy,提到最外层,
最终的效果如下:
# 加密函数 此处的加密密匙已经固化处理
var youzyEptt = function (data) {
var text = JSON.stringify(data);
var textBytes = toBytes(text);
var aesCtr = new ModeOfOperationCTR(处理后的key值, new Counter(5));
var encryptedBytes = aesCtr.encrypt(textBytes);
var encryptedHex = fromBytes(encryptedBytes);
return encryptedHex;
};
# 以下都是提取出的函数,包括function,常量,new对象
function toBytes(text) {}
var Hex = '0123456789abcdef';
function fromBytes(bytes) {}
function coerceArray(arg, copy) {}
function checkInts(arrayish) {}
function checkInt(value) {}
var Counter = function(initialValue) {}
Counter.prototype.setValue = function(value) {}
Counter.prototype.setBytes = function(bytes) {};
Counter.prototype.increment = function() {}
function createArray(length) {}
var ModeOfOperationCTR = function(key, counter) {}
ModeOfOperationCTR.prototype.encrypt = function(plaintext) {}
ModeOfOperationCTR.prototype.decrypt = ModeOfOperationCTR.prototype.encrypt;
var AES = function(key) {}
AES.prototype._prepare = function() {}
AES.prototype.encrypt = function(plaintext) {}
AES.prototype.decrypt = function(ciphertext) {}
var numberOfRounds = {16: 10, 24: 12, 32: 14}
var rcon = [];
var S = [];
var Si =[];
var T1 = [];
var T2 = [];
var T3 = [];
var T4 = [];
var T5 = [];
var T6 = [];
var T7 = [];
var T8 = [];
var U1 = [];
var U2 = [];
var U3 = [];
var U4 = [];
function convertToInt32(bytes) {}
- 处理请求结果
获取响应数据后,还发现有前端加密,发现解密处理函数如下
var _cnDeCrypt = function (zlVjhiyMm1) {
var YB2 = "";
zlVjhiyMm1['\x73\x70\x6c\x69\x74']("\x7c")['\x66\x6f\x72\x45\x61\x63\x68'](function ($lvd3) {
if ($lvd3['\x73\x65\x61\x72\x63\x68'](/��(.*?)��/) != -1) {
YB2 += $lvd3['\x72\x65\x70\x6c\x61\x63\x65']("\u3010", "")['\x72\x65\x70\x6c\x61\x63\x65']("\u3011", "")
} else {
$lvd3 = $lvd3['\x72\x65\x70\x6c\x61\x63\x65'](/[g-t]/ig, "");
YB2 += "\x26\x23\x78" + $lvd3 + "\x3b"
}
});
return YB2
# 使用console.log('\x73\x70\x6c\x69\x74')===> split
# 最终使用python重写,得到
def handle(txt):
result = ''
tt = txt.split('|')
for t in tt:
if '【' in t:
result += t.replace('【', '').replace('】', '')
else:
ttt = re.sub('[g-tG-T]', '', t)
result += '&#x' + ttt + ';'
return result
- 前端字体反爬
得到前端字符后的解密过程参考我的另一篇文章
python字体反爬
第二个网站
同样,第一步分析请求
分析结果是:请求参数没问题,但是有cookie加密,每次请求都会变化。
前一个分析是时间戳
后一个是cookie加密
此处全局搜索使用cooke中的键值,比如:Hm_lpvt
但是dR9BixLC1sIxD0tw搜索不到,因此只能调试,但是该网站有前端反调试,处理方式如下:
google浏览器按 F12 ,出现如下:
此时,右击 1 处,选择 Never pause here,按F10出现的代码行,如此处理多行。
就可以点击时按F12进入正常调试模式。
多次调试发现,每次请求都会调用此函数
每次调试,可能函数名不一样,使用了js混淆,但是代码行号都是一样的。
在调试过程中,将鼠标放在变量名上,可以发现有些变量名是常量,可以进行替换
例如:_$Jg=>“concat”,_$QN=>“slice”,_$He=>1,_$Ng=>“dR9BixLC1sIxD0tw”
# 替换前
function _$7T() {
_$UY(_$oS, _$O4, _$xQ);
_$UY(_$oS, _$FK, _$D7);
_$UY(_$oS, _$UU, _$vG);
_$UY(_$oS, _$Nj, _$b7);
_$UY(_$oS, _$GO, _$FA);
_$UY(_$Wq, _$wG, _$tC);
}
# 替换后
function _$7T() {
_$UY(_$oS, "mouseup", _$xQ);
_$UY(_$oS, "mousemove", _$D7);
_$UY(_$oS, "keydown", _$vG);
_$UY(_$oS, "touchmove", _$b7);
_$UY(_$oS, "click", _$FA);
_$UY(_$Wq, 'load', _$tC);
}
如此可以猜到要干什么,进一步对这些函数进行python编写实现
例如:
function _$jl(_$te) {
var _$iB = [];
for (var _$4S = 0; _$4S < _$te; _$4S++) {
_$iB.push(_$K0(16));
}
return _$iB;
}
def create_random(num):
return math.floor(random.random() * num)
# _$jl函数
def create_list(num):
org = []
for i in range(num):
org.append(create_random(16))
return org
当然,有些方法不能python实现的可以使用execjs调用实现其功能
最后实现效果如下
def get_str(l_53,click_times,keydown_times,load_times,mouseup_times,mousemove_times):
stamp1 = java_to_stamp(l_53[41:])
org_list = []
l_59 = create_list(59) # 随机数组
org_list.extend(ssss(stamp1, 12)) # 时间戳相对固定
append_to_list(1, 2, org_list)
append_to_list(3, 2, org_list)
append_to_list(2, 2, org_list)
# 1 参数待确定 "click"
append_to_list(click_times, 4, org_list)
# 1 参数待确定 keydown
append_to_list(keydown_times, 4, org_list)
# 1 参数待确定 load
append_to_list(load_times, 4, org_list)
# 1 参数待确定 mouseup
append_to_list(mouseup_times, 4, org_list)
# 1 参数待确定 mousemove
append_to_list(mousemove_times, 4, org_list)
# touchmove
append_to_list(0, 4, org_list)
stamp2 = get_timestamp()
org_list.extend(ssss(stamp2, 12))
org_list.extend(l_53[:41])
list_2 = two_len_list(org_list)
# 填充奇数项
ot(list_2, org_list, l_59)
# 填充偶数项
xj(list_2, l_59)
txt = base_str(l_53)
result = funcb(txt, funcv(list_2))
return result
观察发现每天初始化的数组l_53都固定,鼠标事件每次请求都不一样
因此需要每天对数组l_53更新