18-魔改算法 - 教程
本文我们学习一下魔改算法,学习案例网站:产权交易
找加密参数
找的顺序无所谓,然后找完发现这两处为加密:

负载密文一般都是字符串,负载格式决定requests中参数用data还是json,表单用json,字符串text用data,也可以看标头确定是啥形式:

然后预览和负载都是密文,先逆向负载,获取到响应数据再逆向预览,如果**标头也有加密就先逆向标头,**下面开始寻找负载的加密位置,可以搜关键字encrypt,但是呆碰运气,hook一般hook stringify,但是这里也难hook,咱直接用xhr吧
逆向负载
找加密位置
通过xhr断点断下来之后跟栈(断下来的栈是加密好的),看最后一个栈:
然后中间找,缩小范围:

这个栈啥参数都没,咱直接看再上个栈:

有参数但没有加密,看看这里面的内容(return里啥也没的):

找到了,还记得标准对称加密有个特点吗–>五个东西不变(明文,密钥,iv(ECB工作模式没有iv),以及填充方式,工作模式不变)那加密的密文也不变,下面我们看看是不是标准对称加密:

明显在变吧,那我们进去看看核心代码
扣代码
下面是核心代码:
这个就是核心代码,扣下来(整个函数):

进去看一下这个函数:

补上,运行:

this是调用类函数的一种方式,说明带this都要扣,那我们能将类直接扣下来么,扣下来直接就完事儿了,咱么以刚才的进核心代码之前的aes.encode()为突破口:

下面来补充一下this怎么搜索:
- 第一种:xxx.aes = xxx(先创建类,再赋值或更改值)
// 搜索this.aes
var aa = {
age: 18,
aes: 'xxx'
}
aa.aes = 123
- 第二种 直接赋值 aes:
// 搜索aes:
var bb = {
aes: '123'
}
- 第三种
// 直接搜索:aes =
需要我们一个一个看,咱在这里搜索this.aes

咱们打个断点看一下,这里的断点要通过刷新来触发,因为这里不一定是翻页必走的,咱要刷新页面来让网页重新加载,对比数据看看和下面的this.aes一不一样:

看来是一样的,那这里就找对了,进入new g 然后将标黄的那整个函数全扣下来:

可以这样折叠着扣:

粘贴下来:

这回一下补全了
后面就按网页的写就行(自己再封装个方法):


写活明文
还有一个问题是咱们传入的明文是写死的,它会有变化的值么,需要验证一下:

继续翻页继续打印:

找一下变化的值的生成逻辑:

第一个外层id是随机数,但内层id不知道怎么生成的,只知道是从e中取出来的,我们跟一下e:

进入上一个栈:

这里是this,可以搜list = 找赋值的位置,但是本文就不细讲了,最后找到也是随机数,这两个都是随机数我们都先写死,下面写py代码发送请求再看到底能不能写死
目前只有page改变,把明文修改如下:
// 魔改AES
var g = function () {
function e() {
this.codeStr = "",
this.pubPass = "BX1o65CoobwcDP33iQW6ld1OyIPsNzF1",
this.pubPassNum = [],
this.publicKey = "",
this.setPass(this.pubPass)
}
return e.prototype.encode = function (e) {
var t = "";
try {
t = JSON.stringify(e)
} catch (n) {
return console.error(n + "这不是一个正确的json对象"),
""
}
return this.encryptCode(t)
}
,
e.prototype.decode = function (e) {
var t;
try {
t = JSON.parse(this.decryptCode(e))
} catch (n) {
return void console.error(n + "json对象转出失败")
}
return t
}
,
e.prototype.encryptCode = function (e) {
for (var t = encodeURI(e), n = [], i = 0, r = "", o = this.random(16, 32), a = this.randomStr(o), s = this.stringChangeASCIINumberArrs(a), l = 0, c = 0, u = 0, h = 0; h < t.length; h++)
i = t.charCodeAt(h),
l == this.pubPassNum.length && (l = 0),
i += this.pubPassNum[l],
l++,
c == s.length && (c = 0),
i += s[c],
c++,
u += i,
u > 65535 && (u -= 65535),
r = i.toString(36),
r = ("00" + r).substr(-2, 2),
1 == r.length && (r = "0" + r),
n.push(r);
var d = "";
return d = u.toString(36),
d = ("0000" + r).substr(-4, 4),
n.unshift(a),
n.unshift(o.toString(36)),
n.unshift(d),
n.join("")
}
,
e.prototype.decryptCode = function (e) {
var t = ""
, n = 0
, i = ""
, r = []
, o = []
, a = 0
, s = 0;
t = e.substr(4, 1),
n = parseInt(t, 36),
i = e.substr(5, n),
r = this.stringChangeASCIINumberArrs(i),
t = e.substr(5 + n, e.length - 5 - n);
for (var l = "", c = 0, u = 0, h = 0; h < t.length / 2; h++)
l = t.substr(u, 2),
u += 2,
c = parseInt(l, 36),
s == r.length && (s = 0),
c -= r[s],
s++,
a == this.pubPass.length && (a = 0),
c -= this.pubPassNum[a],
a++,
l = String.fromCharCode(c),
o.push(l);
return t = o.join(""),
t = decodeURI(t),
t
}
,
e.prototype.setPass = function (e) {
this.pubPassNum = this.stringChangeASCIINumberArrs(e)
}
,
e.prototype.stringChangeASCIINumberArrs = function (e) {
for (var t = [], n = 0; n < e.length; n++)
t.push(e.charCodeAt(n));
return t
}
,
e.prototype.random = function (e, t) {
return void 0 === e && (e = 0),
void 0 === t && (t = 1e4),
Math.floor(Math.random() * (t - e) + e)
}
,
e.prototype.randomStr = function (e) {
for (var t = [], n = 0; n < e; n++)
t.push(this.random(0, 35).toString(36));
return t.join("")
}
,
e
}()
var aes = new g; // 初始化类,相当于py的实例化类
function getAES(page) {
var params = `{"id":"rtyuf34s1e8aon3z","projectKey":"honsan_cloud_ccprec","clientKey":"rtyue1x44wwqdjw8","token":null,"clientDailyData":{},"acts":[{"id":"rtyuf34rl74qr0n3","fullPath":"/ccprec.com.cn.web/client/info/cqweb_nonphy_cqzr","args":[${page},20,null]}]}`
return aes.encode(params)
}
console.log(getAES(1))
py发送请求
import requests
import os
import execjs
class JSExecutor:
def __init__(self, js_file_path):
if not os.path.exists(js_file_path):
raise FileNotFoundError(f"{js_file_path} does not exist")
with open(js_file_path, 'r', encoding='utf-8') as f:
self.js_code = f.read()
self.js_code = execjs.compile(self.js_code)
def call(self, function_name, *args):
return self.js_code.call(function_name, *args)
def get_data(data_params):
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Content-Type': 'text/xml;charset=UTF-8',
'Origin': 'https://ccprec.com',
'Pragma': 'no-cache',
'Referer': 'https://ccprec.com/projectSecPage/',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0',
'sec-ch-ua': '"Microsoft Edge";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
data = f'{data_params}'
response = requests.post('https://ccprec.com/honsanCloudAct', headers=headers, data=data)
return response.text
if __name__ == '__main__':
js_executor = JSExecutor('D:\\python_project\\python_crawler\\crawler_reverse_engineering\\15-产权交易魔改AES.js')
for _ in range(1, 6): # 多写几页看看会不会检查随机数
data_params = js_executor.call('getAES', _)
print(f'开始获取第{_}页数据...')
print(get_data(data_params))
运行完发现不会:

下面开始将响应密文逻辑逆向出来
逆向预览(响应结果)
hook加密位置

回到上一个栈:

这里的方法我们可以去刚扣的类里看看:

搜到了,那说明这里也是调用的这个类,ok返回上一个栈看看:

那我们直接写就好了
写js解密代码

py调用
py里再call一下就了:

浙公网安备 33010602011771号