文章目录
- 👉1、目标网址
- 👉2、接口分析
- 👉3、代码实现
【JS 逆向百例】 1/100
学习记录:哈喽~ 前面我们接触了一些JS逆向的数据获取,如果前面的百度,有道翻译和正方教务系统的登录加密你已掌握,说明我们已经入门啦。征程万里风正劲,重任千钧再出发。后面还有很长的路要走,让我们一起努力吧!今天的学习记录是关于烯牛数据网站的数据采集,这是一个很综合的例子, 它的返回数据和请求体都进行了加密。 这篇将详细记录调试步骤。
👉1、目标网址
主页:https://www.xiniudata.com/industry/newest?from=data
接口:https://www.xiniudata.com/api/search3/company/search_company_for_lib
接口:https://www.xiniudata.com/api2/service/x_service/person_company4_list/list_companies4_list_by_codes
👉2、接口分析
数据接口:
分析:
查看其各个参数,直接上搜索大法查找。先查看密文数据的解密。找合适的字符进行搜索定位,在这个接口里,我们尝试用返回的数据或请求体中的参数的关键字进行搜索大法时,发现都不好进行JS定位。对于此,我们可以搜索常用的关键字:decrypt
–encrypt
–JSON.parse
或搜索该接口的路径参数
,还可以在Network 选项卡下,该请求的 启动器(Initiator) 列里看到它的调用栈,其调用顺序为由上而下:
鼠标悬停在上面可以看到这个接口调用栈的情况,并且可以找到触发的位置。一般情况下,我们直接双击就会自动的跳转到【源码 / Sources】面板并显示对应JS代码 (点击最上面的JS效果一样)。
进入后ctr + f 进行搜索,多试了几次,在用JSON.parse
进行搜素时,发现了可疑代码。如下:
图1:
图2:
两处都很可疑,我们打上断点进行调试:
如上图:从左向右
- Pause/Resume script execution:暂停/恢复脚本执行(程序执行到**
下一断点
**停止)。 - Step over nextfunction call:执行到下一步的函数调用(跳到下一行)。
- Step into next functioncall:进入当前函数。
- Step out of current function:跳出当前执行函数。
- Step: 一步步执行代码,遇到有函数调用,则进入函数。
- Deactive/Activeall breakpoints:关闭/开启所有断点(不会取消)。
- Pause on exceptions:异常情况自动断点设置。
调试发现,图1 为我们的目标JS:
再对上面变量进行打印测试:
这里好像就是拿到密文后再对其解密的操作,所以我们继续跟下去:
分别跟进去:有d1,d2两个函数。到这里先分别打上断点进行调试观察:这里扣下JS,补齐参数进行数据测试:
我们将在右侧复制的d的值放进去测试时:数据是成功解密了,但他只有一条,emm~,难道我们找错啦?
观察前面几个接口返回的数据格式发现都如下图;
相当于这里的几个接口都是走的我们扣出的JS进行密文解密,那就直接测试:退出断点,观察我们要的公司数据接口解密是不是走的这里,如下图:
OK~复制进去测试:
发现没问题~到这里数据的解密已OK,那就去看看接口的参数有没有什么要处理的:
继续搜索大法:
发现还是在上面那个JS中找到可疑代码:2处
图1:
图2:
其实看到图2 的条件就知道它是不会执行的,但为了保险还是分别打上断点去调试:
调试后锁定图1:跟进发现这里的参数加密与密文解密一样也是走的一处,参数格式上也相似,所以我们像上面密文解密一样,退出掉其他的接口,让断点加载到我们的目标接口让他去生成加密参数:我们退出时不要点快了,看着右边的t,它是接口路径参数,等它到目标接口时就不要退出断点了,我们继续调试即可:
在此时,我们直接执行到下一步的函数调用进行观察:如果进入的循环太多,直接退出就OK。
发现它参数居然没变 !去多几次请求测试几次发现:网站数据是下拉分页加载数据,对于每一页的请求参数它是不变的,虽然它不变,但它不是我们所要的理想定值,因为不同的数据有不同的定值,那他就不可行,所以,我们还是要去进行加密分析的。
老办法,分别跟进JS进行调试:
边看边扣出JS进行调试:扣出来稍微改写一下(保证能调用能运行):先run ,梭一把,看看有什么反应:
不怕他报错,就怕它什么反应都不给你。看报错说: l is not defined ,这个就是差什么补什么,确认不是代码格式等问题后,就到对应的 JS 代码部分下进行调试:
OK~复制出来,补到我们扣下来的JS中去就好。
继续跟下去,发现有一个MD5加密:
JS实现MD5 :
安装:npm install crypto-js
导入:const crypto = require(“crypto”);
JS代码:
const crypto = require("crypto");
function md5(res) {
return crypto.createHash('md5').update(res).digest('hex')
}
到这里我们的请求体参数和解密已经分析的差不多啦,是可以进行数据的采集了,但是,这个网站的数据是下拉分页加载的,在调试它的分页请求数据时,下拉不了无法在请求数据。结束断点。正常下拉观察接口:开始我是去看它的page分页是怎么控制的,但没什么
每次下拉一页,就会更新两个接口。再回到断点:
发现一个参数:limit,看到它就要注意一下,一般是请求数据的限制,而他的值还为10
,那更应该去看一下了。最后发现规律:https://www.xiniudata.com/api/search3/company/search_company_for_lib
这个接口请求加载出公司的编号,后面的详情接口https://www.xiniudata.com/api2/service/x_service/person_company4_list/list_companies4_list_by_codes依据前面的limt的值而返回多少条数据。多调试就OK,在调试的时候自己改变加载公司编号的接口limit的值,观察 n 的变化,再继续调试走到公司详情接口,会发现limt的值不同,返回的数据也不同。
👉3、代码实现
JS:
decrypt.js
const crypto = require("crypto");
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
var _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O"
// 参数加密
function ret(n) {
var s = JSON.stringify(n)
, l = JSON.parse(s);
var r = 1
var f = e1(e2(JSON.stringify(l.payload)))
, p = sig(f);
l.payload = f
l.sig = p
l.v = Number(r)
return l
}
function e1(e) {
if (null == e)
return null;
for (var t, n, r, o, i, a, c, u = "", s = 0; s < e.length;)
o = (t = e.charCodeAt(s++)) >> 2,
i = (3 & t) << 4 | (n = e.charCodeAt(s++)) >> 4,
a = (15 & n) << 2 | (r = e.charCodeAt(s++)) >> 6,
c = 63 & r,
isNaN(n) ? a = c = 64 : isNaN(r) && (c = 64),
u = u + _keyStr.charAt(o) + _keyStr.charAt(i) + _keyStr.charAt(a) + _keyStr.charAt(c);
return u
}
function e2(e) {
if (null == (e = _u_e(e)))
return null;
for (var t = "", n = 0; n < e.length; n++) {
var r = _p.charCodeAt(n % _p.length);
t += String.fromCharCode(e.charCodeAt(n) ^ r)
}
return t
}
function sig(e) {
return md5(e + _p).toUpperCase()
}
function md5(res) {
return crypto.createHash('md5').update(res).digest('hex')
}
function _u_e(e) {
if (null == e)
return null;
e = e.replace(/\r\n/g, "\n");
for (var t = "", n = 0; n < e.length; n++) {
var r = e.charCodeAt(n);
r < 128 ? t += String.fromCharCode(r) : r > 127 && r < 2048 ? (t += String.fromCharCode(r >> 6 | 192),
t += String.fromCharCode(63 & r | 128)) : (t += String.fromCharCode(r >> 12 | 224),
t += String.fromCharCode(r >> 6 & 63 | 128),
t += String.fromCharCode(63 & r | 128))
}
return t
}
// console.log(ret(n))
encrypt.js
//密文解密
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
, _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O";
function d1(e) {
var t, n, r, o, i, a, c = "", u = 0;
for (e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); u < e.length;)
t = _keyStr.indexOf(e.charAt(u++)) << 2 | (o = _keyStr.indexOf(e.charAt(u++))) >> 4,
n = (15 & o) << 4 | (i = _keyStr.indexOf(e.charAt(u++))) >> 2,
r = (3 & i) << 6 | (a = _keyStr.indexOf(e.charAt(u++))),
c += String.fromCharCode(t),
64 != i && (c += String.fromCharCode(n)),
64 != a && (c += String.fromCharCode(r));
return c
}
function d2(e) {
for (var t = "", n = 0; n < e.length; n++) {
var r = _p.charCodeAt(n % _p.length);
t += String.fromCharCode(e.charCodeAt(n) ^ r)
}
return t = _u_d(t)
}
function _u_d(e) {
for (var t = "", n = 0, r = 0, o = 0, i = 0; n < e.length;)
(r = e.charCodeAt(n)) < 128 ? (t += String.fromCharCode(r),
n++) : r > 191 && r < 224 ? (o = e.charCodeAt(n + 1),
t += String.fromCharCode((31 & r) << 6 | 63 & o),
n += 2) : (o = e.charCodeAt(n + 1),
i = e.charCodeAt(n + 2),
t += String.fromCharCode((15 & r) << 12 | (63 & o) << 6 | 63 & i),
n += 3);
return t
}
function decrypt(l) {
var d = d1(l)
, v = d2(d)
, y = JSON.parse(v);
return y
}
// console.log(decrypt(l))
Python
"""
CSDN: 抄代码抄错的小牛马
"""
import execjs
import requests
# 加密 公司的代码编号 的参数 payload sig
def use_encrypt_JS1():
# 读取js文件
with open('./encrypt.js', encoding='utf-8') as f:
js = f.read()
# 通过compile命令转成一个js对象
docjs = execjs.compile(js)
n1 = {
"payload": {
"industry_ids": 921,
"domestic": True,
"corporate_locationIds": [],
"tag_names": [],
"corporate_rounds": [],
"sort": 76006,
"order": -1,
"start": 0,
"limit": 30
}
}
print(type(n1))
company_params = docjs.call('ret', n1)
print('JS返回的num:', type(company_params))
print('JS返回的num:', company_params)
return company_params
# 加密 公司详情 的参数 payload sig
def use_encrypt_JS2(company_code):
# 读取js文件
with open('./encrypt.js', encoding='utf-8') as f:
js = f.read()
# 通过compile命令转成一个js对象
docjs = execjs.compile(js)
# n1 = {
# "payload": {
# "codes": [
# "ZStack",
# "tingyukeji49",
# "fnmzhbjkjxgs",
# "xuehukeji37",
# "ExceedData",
# "xinhongpu",
# "jidatongxin",
# "xinmiershijue",
# "BQSJEZR0",
# "zhongkehaiwei"
# ]
# }
# }
n2 = {
"payload": {
"codes": company_code
}
}
print(type(n2))
details_params = docjs.call('ret', n2)
print('JS返回的公司详情参数:', type(details_params))
print('JS返回的公司详情参数:', details_params)
return details_params
# 解密 获取的公司的代码编号
def use_decrypt_JS3(encrypt):
# 读取js文件
with open('./decrypt.js', encoding='utf-8') as f:
js = f.read()
# 通过compile命令转成一个js对象
docjs = execjs.compile(js)
# 调用function ==> 调用的方法名, 参数1 参数2
decrypted = docjs.call('decrypt', encrypt)
print('----------------------烯牛数据----------------------')
print('解密后的公司数量数据::')
# print(decrypted)
print('要获取公司的数量为:', len(decrypted['data']))
company_code = [i['company_code'] for i in decrypted['data']]
print(company_code)
# for i in decrypted['data']:
# print(i['company_code'])
return company_code
pass
# 解密 公司详情
def use_decrypt_JS4(details_data):
# 读取js文件
with open('./decrypt.js', encoding='utf-8') as f:
js = f.read()
# 通过compile命令转成一个js对象
docjs = execjs.compile(js)
# 调用function ==> 调用的方法名, 参数1 参数2
end_data = docjs.call('decrypt', details_data)
print('----------------------烯牛数据 详情----------------------')
print('解密后的公司详情数据::')
print(end_data)
pass
# 获取请求 公司的代码编号 返回的密文
def get_data1(params):
list_api1 = 'https://www.xiniudata.com/api/search3/company/search_company_for_lib'
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
'cookie': 'btoken=LZKRG0M4XQJ6TXVDCZF4GLMBEG3UD3CA; hy_data_2020_id=18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f; hy_data_2020_js_sdk={"distinct_id":"18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f","site_id":211,"user_company":105,"props":{},"device_id":"18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f"}; utoken=QYT58WVGWTVX6IT6RPU3XPLLEKLNF999; username=YOU; export_notice=true; Hm_lvt_42317524c1662a500d12d3784dbea0f8=1681281817,1681350920,1681357499,1681430886; Hm_lpvt_42317524c1662a500d12d3784dbea0f8=1681438188'
}
lists = requests.post(url=list_api1, json=params, headers=headers).json()
print(lists)
# print(lists['d'])
return lists['d']
pass
# 获取请求 公司详情 返回的密文数据
def get_data2(details_params):
list_api2 = 'https://www.xiniudata.com/api2/service/x_service/person_company4_list/list_companies4_list_by_codes'
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
'cookie': 'btoken=LZKRG0M4XQJ6TXVDCZF4GLMBEG3UD3CA; hy_data_2020_id=18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f; hy_data_2020_js_sdk={"distinct_id":"18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f","site_id":211,"user_company":105,"props":{},"device_id":"18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f"}; utoken=QYT58WVGWTVX6IT6RPU3XPLLEKLNF999; username=YOU; export_notice=true; Hm_lvt_42317524c1662a500d12d3784dbea0f8=1681281817,1681350920,1681357499,1681430886; Hm_lpvt_42317524c1662a500d12d3784dbea0f8=1681438188'
}
lists = requests.post(url=list_api2, json=details_params, headers=headers).json()
print('----------------------烯牛数据----------------------')
print('公司详情密文::')
print(lists)
# print(lists['d'])
return lists['d']
pass
if __name__ == '__main__':
params = use_encrypt_JS1()
company_params = get_data1(params)
company_code = use_decrypt_JS3(company_params)
details_params = use_encrypt_JS2(company_code)
details_data = get_data2(details_params)
use_decrypt_JS4(details_data)
运行截图:
over~