一、逆向案例之Python逆向实现请求加密
//具体代码如下 function l(t, e) { return t.toString().toUpperCase() > e.toString().toUpperCase() ? 1 : t.toString().toUpperCase() == e.toString().toUpperCase() ? 0 : -1 } function u(t) { for (var e = Object.keys(t).sort(l), n = "", a = 0; a < e.length; a++) if (void 0 !== t[e[a]]) if (t[e[a]] && t[e[a]]instanceof Object || t[e[a]]instanceof Array) { var i = JSON.stringify(t[e[a]]); n += e[a] + i } else n += e[a] + t[e[a]]; return n } function d(t) { for (var e in t) "" !== t[e] && void 0 !== t[e] || delete t[e]; var n = r["a"] + u(t); return s(n).toLocaleLowerCase() }
1.1、步骤1,通过https://curlconverter.com/ 生成基础爬虫代码
1.2、Python代码的结构调整
1.3、补充知识点1:0值的概念
在整型中,只有0是false 其他的不管是正数1 2 3...还是-1,-2,-3...都是true
在字符串中,只有“”,是false ;其他的“ ”,“0”...等只要不是空字符串都是true
1.4、补充知识点2:与或非逻辑运算 短路运算
与 and(Pyhon) &&(JS) 全真为真,有假既假
或 or(Python) || (JS) 有真既真,全假为假
1.5、在对列表或字典进行便利的时候,不能直接对其进行操作(增删改),需要用t.copy
""" 一、处理“零值” 【t是一个字典】 for (var e in t) "" !== t[e] && void 0 !== t[e] || delete t[e]; 1、Python没=== 2、Python 的 or 后面不能使用del命令,所以只能使用t.pop进行删除操作 3、在对列表或字典进行便利的时候,不能直接对其进行操作(增删改),需要用t.copy t.copy()产生一个新的字典去循环便利, t.pop()在对原始字典进行操作处理 """ # 方式1: for key in t.copy(): print(key) # 获取每一个key t[key] != "" and t[key] != 0 or t.pop(key) print(t)
1.6、列表和元组排序:sort() sorted()
l = [10, 2, 3, 1, 5] l.sort() # 从小到大 print(l) # [1, 2, 3, 5, 10] l = [10, 2, 3, 1, 5] l.sort(reverse=True) # 从大到小 print(l) # [10, 5, 3, 2, 1] l = ["2", "1", "10", "100", "5"] l.sort() # 字符串安装ASCII码字母表排序从小到大,所以结果有点乱七八糟 print(l) # ['1', '10', '100', '2', '5'] # 补充知识点:sorted() l = [10, 2, 3, 1, 5] print(sorted(l)) # [1, 2, 3, 5, 10] # sorted() 比sort()强大的多,sort()不管什么是什么元素直接就给你硬排,而且需要对象进行调用 # sorted() 有返回值,可以自定义排序规则 # 案例1 # my_sort函数是排序规则 只能有一个形参item,是l2中的每一个元素 def my_sort(item): # 然后根据每一个元素中的具体数据进行排序,根据什么排序,就返回什么,例如根据年龄就返回item[1] return item[1] # l2必须是列表或者元组,不能是字典,字典是无序的没办法排序 l2 = [["yuan", 23, 175], ["rain", 18, 180], ["alvin", 45, 165]] # 对谁进行排序l2 如何排序,排序规则 key print(sorted(l2, key=my_sort)) # [['rain', 18, 180], ['yuan', 23, 175], ['alvin', 45, 165]] print(sorted(l2, key=lambda item: item[1])) # 使用lambda函数 # [['rain', 18, 180], ['yuan', 23, 175], ['alvin', 45, 165]] # 案例2 def my_sort2(item): return item["height"] l3 = [ { "name": "yuan", "age": 23, "height": 175 }, { "name": "rain", "age": 18, "height": 180 }, { "name": "alvin", "age": 45, "height": 165 }, ] print(sorted(l3, key=my_sort2)) # [{'name': 'alvin', 'age': 45, 'height': 165}, {'name': 'yuan', 'age': 23, 'height': 175}, {'name': 'rain', 'age': 18, 'height': 180}] # 由以上案例1和案例2,总结如下 t = { "ts": 1754484535355, "pageNo": 4, "pageSize": 20, "total": 2749, "KIND": "GCJS", "GGTYPE": "1", "timeType": "6", "BeginTime": "2025-02-06 00:00:00", "EndTime": "2025-08-06 23:59:59" } # 由于字典没办法进行排序,所以只能先把字典转列表 t_list = t.items() print("::::",t_list) # [('ts', 1754484535355), ('pageNo', 4), ('pageSize', 20), ('total', 2749), ('KIND', 'GCJS'), ('GGTYPE', '1'), ('timeType', '6'), ('BeginTime', '2025-02-06 00:00:00'), ('EndTime', '2025-08-06 23:59:59')] # 案例2 def my_sort3(item): return item[0] print(sorted(t_list,key=my_sort3)) # [('BeginTime', '2025-02-06 00:00:00'), ('EndTime', '2025-08-06 23:59:59'), ('GGTYPE', '1'), ('KIND', 'GCJS'), ('pageNo', 4), ('pageSize', 20), ('timeType', '6'), ('total', 2749), ('ts', 1754484535355)]
1.7、最终代码:01逆向案例之Python逆向实现请求加密.py
import requests from hashlib import md5 import time headers = { 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Connection': 'keep-alive', 'Content-Type': 'application/json;charset=UTF-8', 'Origin': 'https://ggzyfw.fujian.gov.cn', 'Referer': 'https://ggzyfw.fujian.gov.cn/business/list/', '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/128.0.0.0 Safari/537.36', # 'portal-sign': 'af2344abf132bc8634f2fc0e28466209', 因为需要替换它,所以这个不需要了 'sec-ch-ua': '"Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', } # 构建一个函数,用来生成sign,并且放到请求头中 def get_sign(t): """ 一、处理“零值” for (var e in t) "" !== t[e] && void 0 !== t[e] || delete t[e]; 1、Python没=== 2、Python 的 or 后面不能使用del命令,所以只能使用t.pop进行删除操作 3、在对列表或字典进行便利的时候,不能直接对其进行操作(增删改),需要用t.copy t.copy()产生一个新的字典去循环便利, t.pop()在对原始字典进行操作处理 """ # 方式1: # for key in t.copy(): # print(key) # 获取每一个key # t[key] != "" and t[key] != 0 or t.pop(key) # print(t) # 方式2: # 创建一个新的字典,保存更新后的数据 new_t = {} for key, val in t.items(): if val == "" or val == 0: continue new_t[key] = val # print(new_t) # {'pageNo': 1, 'pageSize': 20, 'total': 2810, 'KIND': 'GCJS', 'GGTYPE': '1', 'timeType': '6', 'BeginTime': '2025-02-13 00:00:00', 'EndTime': '2025-08-13 23:59:59', 'ts': 1755068622063} # 二、排序 new_t_list = new_t.items() # 把字典转换为列表 def my_sort3(item): return item[0] # 使用sorted() 对列表进行排序 ret = sorted(new_t_list, key=my_sort3) s1 = "" # 便利列表 并把列表中的每个元组的key和val进行拼接,最后在拼接成一个字符串 for key, val in ret: s1 += key + str(val) # key先和val拼接,val有可能是数字所以需要转字符串 print(s1) # BeginTime2025-02-13 00:00:00EndTime2025-08-13 23:59:59GGTYPE1KINDGCJSpageNo1pageSize20timeType6total2810ts1755068622063 # 三、拼接参数字符串 s2 = "B3978D054A72A7002063637CCDF6B2E5" s = s2 + s1 # 四、md5值计算 md5_obj = md5() # md5_obj.update(s.encode()) # 使用s的二进制字节串,所以.encode return md5_obj.hexdigest() # 返回32位的值 # 创建一个main函数,把所有的业务流程放到该处 def main(): json_data = { 'pageNo': 1, 'pageSize': 20, 'total': 2810, 'AREACODE': '', 'M_PROJECT_TYPE': '', 'KIND': 'GCJS', 'GGTYPE': '1', 'PROTYPE': '', 'timeType': '6', 'BeginTime': '2025-02-13 00:00:00', 'EndTime': '2025-08-13 23:59:59', 'createTime': '', 'ts': int(time.time() * 1000), # 获取当前的时间戳 } # 把Python实现的加密字段替换到请求头中 sign = get_sign(json_data) print(sign) headers["portal-sign"] = sign response = requests.post('https://ggzyfw.fujian.gov.cn/FwPortalApi/Trade/TradeInfo', headers=headers, json=json_data) print(response.text) main()
二、入口定位:请求堆栈&&拦截器关键字
入口定位 -- 关键字搜索 -- 方法关键字 -- encrypt -- decrypt -- key关键字(最高频) -- headers关键字 -- 路径关键字 -- 拦截器关键字 如果把加密的内容放到了拦截器了,就直接用这个方式搜索 interceptors.request.use(func) interceptors.response.use(func) -- 请求堆栈--主要关注请求,响应无关 请求入口定位,与响应无关 -- hook
2.1、请求堆栈
2.1.1、什么是堆栈,堆栈如何操作
2.1.2、堆栈中的参数传递
备注:由此我们可以推出,如果当我们不知道如何定位加密参数portal_sign找不到这个字段的时候,我们只要定位到最后的一个调用,根据堆栈的请求顺序,倒序的去寻找portal_sign字段是在那个函数中产生的,然后在进行解析和代码的处理
2.1.3、如何准确搞笑的定位入口
方式一:这种方式不好用,有其他的一些接口经常干扰
方式二:经常使用这种方式
补充:
2.2、拦截器关键字:n.then(t.shift(), t.shift())
类似以上的多个接口都是一样的加密方式,都有portal_sign
n = n.then(t.shift(), t.shift());拦截器
-- 拦截器关键字 如果把加密的内容放到了拦截器了,就直接用这个方式搜索 interceptors.request.use(func) interceptors.response.use(func)
当我们判断这个网站把接口请求解密的动作放到了拦截器中,就不要用别的关键字搜索了
直接搜索
这是前端人员固定语法,调用的一个库,所以直接进行搜索 拦截器关键词,就可以定位到入口