【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

admin 2025年6月25日16:30:35评论4 views字数 6527阅读21分45秒阅读模式

我们在日常生活中度过的每一天,实际上也许是连续发生的奇迹也说不定呢。——《日常》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

KillWxapkg2.4.1

02

操作步骤

1、响应加密,无法分析

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

2、请求签名,无法重放

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

3、开启DevTools

https://github.com/Ackites/KillWxapkg
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

4、进入DevTools

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

03

签名分析

5、网络跟调用堆栈最顶层

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

6、依次点击调用堆栈,在Re这一步发现signStr有结果显示

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

7、发现请求签名参数signStr = b.header.signStr = p

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

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<>?:{}")
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

9、此时w=0,将M拼接字符串然后SHA1加密就得到了签名p也就是signStr

p = (0,r.default)(M + "12345678900987654321!@#$%^&*()qazwsxedcrfvtgbyhnujmikolp<>?:{}")(0,r.default)等价SHA1
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

04

解密分析

10、搜索decrypt关键字定位到解密函数,t.body是响应的body参数

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

11、l = p(c, t)

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

12、进入p函数内部

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

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",返回空字符串""
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

14、抓包分析流量,当请求中的reqClient=evcc_v2,解密函数密钥l=nonce+reqFlowNo

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

15、响应参数body赋值给t.body,nonce+reqFlowNo赋值给l,调用解密函数

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

05

JsRpc配置

16、导出r函数

globalThis.r = r;
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

17、注入小程序的JsRpc环境

https://github.com/jxhczhl/JsRpc/blob/main/resouces/WeChat_Dev.js
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

18、连接通信

var demo = new Hlclient("ws://192.168.14.159:12080/ws?group=zzz");
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

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);});
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

20、构造param,这里需要注意密文过长浏览器无法处理,使用python测试

import requestsimport 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)
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

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.1Host: 192.168.14.159:12080User-Agent: Mozilla/5.0Accept: */*Content-Type: application/x-www-form-urlencodedConnection: closeparam=+ 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)}
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

07

beforeRequest

22、第八步得知signStr与请求body有关,reqFlowNo每次重放需要生成不一样的值

【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

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实时修改签名
24、signStr和reqFlowNo填入占位符,成功实现动态修改签名
【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

原文始发于微信公众号(挖个洞先):【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月25日16:30:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【银行逆向百例】10小程序逆向之Yakit 魔术方法beforeRequest实时修改签名https://cn-sec.com/archives/4199490.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息