一个由实战派推动的安全内容建设平台,传递一线经验,助力实战应用能力提升! 本文来源于长风安全实战能力知识库,针对JS逆向加解密自动化进行探讨,实现了V_jstools、jsrpc、autodecoder三者强强联动自动加解密💡 V_jstools、jsrpc、autodecoder 测试环境:http://39.98.108.20:8085/#/login autoDecoder:https://github.com/f0ng/autoDecoder/ jsRpc:https://github.com/jxhczhl/JsRpc v_jstools:https://github.com/cilame/v_jstools
首先安装v_jstools,配置如下 随便输个账号密码登录下查看数据包,发现请求包和返回包都加密了,并且还有sign、timestamp、requestId,说明可能有签名、时间戳、请求ID等校验,我们下面需要通过js查看对应的关系 输入账号密码,查看控制台并定位加密函数 定位到加密函数后,下个断点;timestamp是通过r = Date.parse(new Date)来获取,也就是时间戳 requestId是通过i = p()这个函数来获取 sign是通过s = a.a.MD5(n + i + r) 来获取的,其中n= JSON.stringify(v(t.data)) 也就是我们输入的账号密码的json字段,然后加上requestId和时间戳,最后再取md5值就是签名 加密函数也就是t.data =l(n)来实现的 下面来找解密函数 也是通过v_jstools控制台输出的内容快速定位解密的js文件 我们下个断点调试下 在这一步的时候,我们步入函数 发现这一段代码便是实现解密的函数,t.data 可以输出调试下 于是弄清楚了加解密后我们启动jsrpc服务端,配置默认即可 然后我们取消所有断点(一定要取消!),然后再客户端注入js环境 在控制台传入JsEnv_Dev的代码并回车 https://github.com/jxhczhl/JsRpc/blob/main/resouces/JsEnv_Dev.js然后连接我们的服务端 var demo = new Hlclient('ws://127.0.0.1:12080/ws?group=zzz'); 然后我们需要去控制台断点并注册js方法 先是加密的: // 时间戳 window.time = Date.parse;
// requestId window.id = function() { return p(); };
// v函数 window.v1 = function(param) { return v(param); };
// 签名 window.m = function(data) { return a.a.MD5(data).toString(); };
// 加密 window.enc = function(data) { return l(data); };
然后传递函数名进行调用
// 注册函数 demo.regAction('req', function(resolve, param) { // 请求头 let timestamp = window.time(new Date()); let requestid = window.id(); let v_data = JSON.stringify(window.v1(param)); let sign = window.m(v_data + requestid + timestamp); // 加密请求体 let encstr = window.enc(v_data); let res = { 'timestamp': timestamp, 'requestid': requestid, 'encstr': encstr, 'sign': sign }; resolve(res); });
然后我们控制台打印测试下,没问题 然后我们调用下接口(调用时需要取消断点),也没问题,并且我们需要的参数也都成功返回在data参数中了
接着我们给注册解密的js方法 我们断点到解密函数的位置,并直接把整个方法进行注册 然后传递函数名调用 我们控制台输出测试下,没问题 然后取消断点,试试接口是否能成功解密并返回 也是没问题的 然后我们实现autoDecoder,代码如下
代码实现可以参考flasktestheader.py ,格式的话建议按照它原来的模板来,否则会有很多坑,建议多加print输出并结合burp的logging进行调试 https://github.com/f0ng/autoDecoder/blob/main/flasktestheader.py import requests import json from flask import Flask, Response, request import re
app = Flask(__name__) url = 'http://localhost:12080/go'
@app.route('/encode', methods=['POST']) def encrypt(): body = request.form.get('dataBody') # 获取 post 参数 headers = request.form.get('dataHeaders') # 获取 post 参数 reqresp = request.form.get('requestorresponse') # 获取 post 参数 data = { 'group': 'zzz', 'action': 'req', 'param': body }
if headers is not None: # 开启了请求头加密 # 使用正则表达式提取 timestamp、requestid 和 sign timestamp_match = re.search(r'timestamp: (\d+)', headers) requestid_match = re.search(r'requestId: ([\w-]+)', headers) sign_match = re.search(r'sign: ([\w-]+)', headers)
# 提取匹配的值 timestamp_before = timestamp_match.group(1) if timestamp_match else None requestid_before = requestid_match.group(1) if requestid_match else None sign_before = sign_match.group(1) if sign_match else None
# 发送请求到目标 URL res = requests.post(url, data=data) encry_param = json.loads(res.text)['data'] print(encry_param)
# 将 encry_param 转换为字典 encry_param_dict = json.loads(encry_param) encstr = encry_param_dict['encstr']
# 提取新的 timestamp、requestid 和 sign timestamp_new = encry_param_dict['timestamp'] # 提取 timestamp requestid_new = encry_param_dict['requestid'] # 提取 requestid sign_new = encry_param_dict['sign']
# 确保 timestamp_new 是字符串 timestamp_new = str(timestamp_new)
# 替换 headers 中的旧值为新值 headers = headers.replace(sign_before, sign_new, 1) headers = headers.replace(requestid_before, requestid_new, 1) headers = headers.replace(timestamp_before, timestamp_new, 1)
return f'{headers}\r\n\r\n\r\n\r\n{encstr}' # 返回值为固定格式,不可更改 必需必需必需,共四个\r\n
# 否则,只返回 encstr return encstr
@app.route('/decode', methods=['POST']) def decrypt(): body = request.form.get('dataBody') # 获取 post 参数 headers = request.form.get('dataHeaders') # 获取 post 参数 reqresp = request.form.get('requestorresponse') # 获取 post 参数 data = { 'group': 'zzz', 'action': 'decrypt', 'param': body }
res = requests.post(url, data=data) body = json.loads(res.text)['data'] print(body)
if headers is not None: # 如果需要处理响应头 # 返回值为固定格式,不可更改 必需必需必需,共四个\r\n return f'{headers}\r\n\r\n\r\n\r\n{body}'
# 否则,只返回 body return body if __name__ == '__main__': app.debug = True # 设置调试模式,生产模式的时候要关掉debug app.run(host='0.0.0.0', port='8888')
启动调试 在burp中测试也是能正常加解密的 然后配置下这几个地方便可以实现自动加解密了 但这里有点坑,proxy里面是可以正常解密,但在repeater中就返回空白,很奇怪 当我们产生问题的时候可以从burp的logging日志里面分析 这里我们header头以及加密内容都是正确传输的,但为什么返回空白 这里有个坑点,我们知道错误代码400是因为客户端请求产生的报错,说明我们脚本里面的请求包构造有问题 不卖关子了,是因为header头下多了个换行,我们这只保留三个\r\n 但如果只保留三个\r\n,autoDecoder就无法调试了,但在proxy、repeater、intruder就不影响,如果想在autoDecoder中调试还是需要改回四个\r\n ok,成功,完美实现在有签名、时间戳、请求id等校验下实现自动加解密 proxy: repeater: logging: intruder:
|