知识点:
JS逆向-项目-项目联动&自动接口&Yakit热加载
演示案例-JS逆向-项目-项目联动&自动接口&Yakit热加载
Yakit的热加载
参考:https://yaklang.com/products/Web Fuzzer/fuzz-hotpatch
核心比喻:给汽车换发动机,但不用停车
想象一下,你正在高速公路上开着车(这辆车就是 Yakit
),你的任务是不断测试路边的各种设施(这相当于执行安全测试任务)。
突然,你觉得车的发动机(这相当于一个插件或者一段检测逻辑)不够给力,或者发现它有个小毛病,你想换一个新的。
如果没有热加载:
你得先把车完全停下来(关闭Yakit
),然后熄火,打开引擎盖,把旧发动机拆下来,换上新发动机,再点火启动,重新挂挡、踩油门回到高速上。整个过程非常耗时,你的测试工作被中断了。
有了热加载:
你发现发动机需要更换。这时,你只需要在车里按一个按钮,一个新的发动机就"嗖"的一下在空中替换了旧的发动机。你的车从头到尾都没有停,一直在高速行驶,测试工作一秒都没有中断。 这个"空中换发动机"的神奇操作,就是 热加载。
总结一下,热加载是干嘛的?
热加载就是:让你在不停下Yakit
主程序的情况下,立刻、马上让修改过的或新写的代码生效。
1、热加载的模版内容
bash
格式:{{yak(函数名|参数名)}}
如密码:{{yak(upper|{{x(pass_top25)}})}}
2、热加载的代码
bash
函数名 = func(参数名) {
return 参数名
}
例子:
upper = func(s) {
// 传入的参数,类型为字符串,返回值可以是字符串或数组
return s.Upper()
}
3、热加载中的魔术方法
bash
// beforeRequest 允许发送数据包前再做一次处理,定义为 func(origin []byte) []byte
beforeRequest = func(req) {
return []byte(req)
}
// afterRequest 允许对每一个请求的响应做处理,定义为 func(origin []byte) []byte
afterRequest = func(rsp) {
return []byte(rsp)
}
案例1:前端验证签名(HMAC-SHA256)
bash
热加载:
热加载的模版内容
{{yak(signRequest|admin|{{x(pass_top25)}})}}
热加载的代码
func sign(user, pass) {
return codec.EncodeToHex(codec.HmacSha256("1234123412341234", f`username=${user}&password=${pass}`)~)
}
signRequest = result => {
pairs := result.SplitN("|", 2)
dump(pairs)
return sign(pairs[0], pairs[1])
}
bash
数据包设置变量:
password:{{x(pass_top25)}} //这样设置会出问题
{
"signature": "{{yak(signRequest|admin|{{param(password)}})}}",
"key": "31323334313233343132333431323334",
"username": "admin",
"password": "{{param(password)}}"
}

案例2:前端加密登陆(AES-CBC)
YAK Runner 解密调试:
bash
data = {
"data": "8MpsnCnixFWo67G+vrZ1Ge712JIpiDjPQvLeqNMoHdTyeMpbAdk7hnna6589oTdH",
"key": "31323334313233343132333431323334",
"iv": "bfeb9fdf041beaa31a1f136700f5e25c"
}
keyBytes = codec.DecodeHex(data.key)~
ivBytes = codec.DecodeHex(data.iv)~
a = codec.AESCBCDecryptWithPKCS7Padding(
keyBytes,
codec.DecodeBase64(data.data)~,
ivBytes
)~
println(string(a))

bash
热加载:
热加载的模板内容
{{base64({{yak(aescbc|{"username":"admin","password":"{{x(pass_top25)}}"})}})}}
热加载的代码
aescbc = result => {
result = codec.AESCBCEncryptWithPKCS7Padding(
codec.DecodeHex(`31323334313233343132333431323334`)~,
result,
codec.DecodeHex(`bfeb9fdf041beaa31a1f136700f5e25c`)~,
)~
return string(result)
}
案例3:Yakit+JsRpc+热加载(魔术方法)
1、本地上线JSRPC
浏览器控制台执行:resouces/JsEnv_Dev.js
bash
var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz");

命令终端执行:window_amd64.exe
2、浏览器控制台执行加密代码并定义加密方法
bash
function _0x2fe90c(_0x1d8ccd, _0x579d33) {
return _0x4f79d5(_0x1d8ccd - -0x6d, _0x579d33);
}
function _0x4f79d5(_0x8e93b8, _0x5e1416) {
return _0x30d2(_0x8e93b8 - 0x26, _0x5e1416);
}
function _0x30d2(_0xb85c4e, _0xd19a71) {
const _0x30d2f4 = _0xd19a();
return _0x30d2 = function(_0x37ab03, _0x251c3f) {
_0x37ab03 = _0x37ab03 - 0x156;
let _0x362566 = _0x30d2f4[_0x37ab03];
return _0x362566;
}, _0x30d2(_0xb85c4e, _0xd19a71);
}
function encrypts_aes(data) {
const _0x67b862 = CryptoJS.enc.Utf8.parse('1234567890123456');
const _0x2d9cd5 = CryptoJS.enc.Utf8.parse('1234567890123456');
const _0x1375d7 = CryptoJS.AES.encrypt(data, _0x67b862, {
'iv': _0x2d9cd5,
'mode': CryptoJS.mode.CBC,
'padding': CryptoJS.pad.Pkcs7
}).toString();
return _0x1375d7;
}

3、注册JSRPC调用方法
bash
demo.regAction("decrypt", function (resolve,param) {
//这样添加了一个param参数,http接口带上它,这里就能获得
var base666 = encrypts_aes(param)
resolve(base666);
})
4、yakit添加JSRPC接受处理
bash
handle=func getEnc(data){
parsedData = json.dumps(data);
rsp,rep,err = poc.Post("http://127.0.0.1:12080/go",poc.replaceBody("group=zzz&action=decrypt¶m="+parsedData, false),poc.appendHeader("content-type", "application/x-www-form-urlencoded"))
if(err){return(err)
}
return json.loads(rsp.GetBody())["data"]
}
5、添加热加载逻辑
bash
模板内容:{{yak(jsrpcReq|{{payload(pass_top25)}})}}
热加载代码:
jsrpcReq = func(origin /*string*/) {
// JSrpc的group
group = "zzz";
// jsrpc的action
action = "decrypt";
if (origin[0] == "{") {
rsp, rep = poc.Post(
"http://127.0.0.1:12080/go",
poc.replaceBody("group=" + group + "&action=" + action + "¶m=" + json.dumps(origin), false),
poc.appendHeader("content-type", "application/x-www-form-urlencoded")
)~
return json.loads(rsp.GetBody())["data"];
} else {
rsp, rep = poc.Post(
"http://127.0.0.1:12080/go",
poc.replaceBody("group=" + group + "&action=" + action + "¶m=" + codec.EncodeUrl(origin), false),
poc.appendHeader("content-type", "application/x-www-form-urlencoded")
)~
return json.loads(rsp.GetBody())["data"];
}
}
// beforeRequest 允许在每次发送数据包前对请求做最后的处理,定义为 func(https bool, originReq []byte, req []byte) []byte
// https 请求是否为https请求
// originReq 原始请求
// req 请求
beforeRequest = func(https, originReq, req) {
// 我们可以将请求进行一定的修改
postParams = poc.GetAllHTTPPacketPostParams(req /*type: []byte*/)
encryptedParam = jsrpcReq(postParams["encryptedData"])
req = poc.ReplaceHTTPPacketPostParam(req, "encryptedData", encryptedParam)
// 将修改后的请求返回
return []byte(req)
}
• poc.GetAllHTTPPacketPostParams 从传入的req数据包中获取所有Post参数
• jsrpcReq 将 encryptedData 的值发送到jsRpc的API中,返回值是加密后的参数值
• poc.ReplaceHTTPPacketPostParam 替换req中Post参数名为encryptedData的参数值,然后将修改后的数据包返回