前端JS加解密对抗方法简述

本文主要介绍常见的应对前端JS加解密的方法

零、简述

常见需要前端JS调试完成加解密对抗的情况有:

  1. 参数值加密:如登录过程中对用户名、密码进行了加密;
  2. 键值加密:对参数的键值都进行了加密;
  3. 数据包全加密:对数据包请求体进行了全加密;
  4. 数据包签名:对数据包内容进行了某种签名算法以防篡改;
  5. 组合利用

常见加解密方法有:

  1. 对称加密:AES、SM4(国密)、 DES、3DES、SM1、SM7、RC5、RC6、RC4、ZUC、SSF446等

    特点:加解密使用同一个密钥,可逆

  2. 非对称加密:RSA, SM2. SM9, Rabin, DH, DSA, ECC

    特点:密钥是成对的,分为公钥和私钥;

  3. 散列算法:MD5、 SM3、 MAC, HMAC, SHA-1, SHA-2 (SHA-224. SHA-256, SHA-512/224. SHA-512/256) 等

    特点:无密钥,不可逆,固定长度

问题解决思路

  1. 分析加密算法,确认加密方式,还原加解密流程

  2. 分析密钥来源:

    若为客户端生成密钥:密钥固定(硬编码密钥)/密钥随机(分析密钥生成算法)

    若为服务器返回密钥:则重新建立连接并进行抓包,获取密钥,再加解密

  3. 若无法还原加解密流程:通常是存在魔改算法或混淆场景,通过定位明文将其转发,篡改明文后再转发回去走加密流程

一、常见加解密定位

常见的渗透测试场景包括web/小程序/公众号/app,在不同场景下的分析方法有所区别。

本文主要以Web端为例,其他场景不过多讨论。

web端

定位加密算法:

1、全局搜索关键字

  • 参数名、“参数名=、参数名=、参数名:

  • URI、API接口、加密算法关键词

1
2
encrypt, decrypt, JSON.stringify, JSON.parse, secret,secretkey,publickey, privatekey,padding,
key, aes, sm4, rsa, sm2,des, iv, pkcs

搜索的关键词越短,出来的误报也就越多,所以要选好关键词

2、断点调试

找到一些关键词或关键动作打上断点,比如疑似加解密函数部分,鼠标点击特定按钮事件,网络请求等。

  • 调用堆栈:可在浏览器点击对应请求后,右侧面板查看请求调用堆栈,然后打断点跟踪定位
  • XHR断点:在source资源面板右下角,可以添加URL关键字,在发起网络请求时触发
  • 事件监听断点:可以在发生相关事件时触发断点,如登陆时触发click点击事件
  • DOM断点:可以在子元素改变时、属性改变时和元素被移除时触发断点,如滑块验证码

3、hook

在找不到关键字时,如混淆场景下,可以通过hook某些通用方法寻找突破口,如 JSON.stringify、JSON.parse

还可以hook如 cookie、header、URL、eval、Function、绕debugger等

APP端

定位加密算法:

1、静态分析

需要先对APP进行逆向,通过脱壳反编译得到源码;

然后也是jadx全局搜索关键字,类似Web思路,搜索如sign,加引号的“sign”等。

2、hook

也可以通过frida来hook一些java层的常见类/方法。

JAVA 层常见方法hook:

  • HashMap的put方法;

  • Log日志;

  • JSONObject的 Jput、JgetString方法;

其他,如:

  • ArrayList的add、addAll、set方法等;
  • TextUtils的isEmpty方法;
  • Collections的sort 方法;
  • Toast的show方法;
  • String的getBytes、 isEmpty方法、StringBuilder, StringBuffer;
  • 常见加密库相关的hook(自吐算法)等

3、动态调试

log插桩、JEB、IDA、unidbg

4、H5调试

Android:

  1. 安卓手机开启adb调试,
  2. 电脑端 Edge浏览器访问edge://inspect,Chrome浏览器访问chrome://inspect
  3. 手机访问h5页面,在浏览器点击inspect打开 devtools即可调试

IOS:

  1. mac safari设置中开启网页开发者功能
  2. iphone下safari设置中开启JavaScript、网页检查器
  3. 在手机Safari 浏览器打开需要调试的网站,然后在电脑上的Safari浏览器点击『开发』->『选择你的手机』->『选择需要调试的网址』即可打开手机网页的控制台

工具:

小程序

开启小程序调试:

  • WeChatOpenDevTool,微信小程序强制开启开发者工具,也适用于开启微信内置浏览器F12控制台

然后可以像Web一样进行调试

公众号

微信内置浏览器调试

  1. 手机用usb连接至电脑

  2. 手机微信内点击http://debugxweb.qq.com/?inspector=true(只要跳转过微信首页就是开启了调试)

  3. 微信内打开所需调试网址, 例如weixin.qq.com

  4. chrome浏览器打开chrome://inspect/#devices会看到我们打开的网页weixin.qq.com

    Edge浏览器 edge://inspect

  5. 在点击chrome里的inspect 直接调试(可以直接用鼠标进行操作)

其他H5调试方法:

1
npm install -g spy-debugger
  • spy-debugger,微信调试,各种WebView样式调试、手机浏览器的页面真机调试。便捷的远程调试手机页面、抓包工具,支持:HTTP/HTTPS,无需USB连接设备。
  • 上面提到的 WeChatOpenDevTool 也可用。

二、自动加解密思路

定位到关键加解密函数后,可以了解到程序的加解密流程和算法。一些常见的加解密算法可以通过 cyberchef 进行验证。

在测试过程中,我们肯定无法接受每次进行手动加解密,因此我们需要一些方法完成自动化加解密,常见方法有:

  • 浏览器控制台编写JS代码
  • 代理服务中转
  • JSRPC调试
  • CDP调试

三、控制台模拟操作

平时测试的时候,可能会遇到一些接口(如登陆接口)中的某些数据是加密的。

通常逻辑是通过对JS代码进行逆向分析后,了解到前端加密算法和关键函数,拿到密钥,然后编写相应脚本模拟加密过程构造想要的数据。

对于一些简单的场景也可以通过JS模拟前端的操作,可以免去对加解密的调试。

整理流程如下:

  1. 定位输入框和按钮
  2. 设置数据
  3. 点击按钮

分析前端代码,找到我们要的登录框输入点

示例代码

然后定位到数据,有jquery的话会方便很多,没有就使用原生的js也不影响

1
2
3
document.getElementsByName("username")[0]
document.getElementsByName("password")[0]
document.getElementsByClassName("btn btn-primary btn-block")[0]

如果不好找,可以直接编辑html,手动添加id属性,然后通过id进行定位

1
<input id="testusername" data-v-ad2189ae="" type="text" name="username" placeholder="请输入手机/邮箱">
1
document.getElementById("testusername")

定位后该插入数据的就插入数据,该点击的就点击,具体看下面的实现代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 设置用户名、密码字典列表
var usernames = new Array('13299999999','13333333333','18888888888','13000000000');
var passwords = new Array('qwe123','root',...,'FALSE');
// 遍历字典列表,插入相应数据,模拟点击操作
for (i in usernames) {
    document.getElementById("testusername").value=usernames[i]
    for (j in passwords) {
        document.getElementById("testpassword").value=passwords[j]
        document.getElementsByClassName("btn btn-primary btn-block")[0].click()
    }
}

效果:

image-20250726044206139

四、代理服务中转

通过对JS代码进行逆向分析后,了解到前端加密算法和关键函数,拿到密钥后,然后编写相应脚本模拟加密过程构造想要的数据。

编写通过工具或Python脚本等进行代理中转,通过脚本对数据包进行加解密处理。

介绍一些相关工具

  • mitmproxy
  • Burp插件autoDecoder
  • Yakkit 热加载

MITMProxy

mitmproxy 是一款免费的开源交互式 HTTPS 代理工具。

这里使用Burp+mitmproxy进行配合,可以配置上下游代理进行加解密

  • 配置下游代理:

    则是接收客户端加密请求进行解密,将解密后数据包发给burp。

    当接收burp到明文后进行加密,将加密后数据包发给客户端。

  • 配置上游代理:

    则是接收burp明文请求后进行加密,将加密后数据包发给服务端。

    当接收服务端加密响应,解密,发给burp。

1、下游代理:请求解密,响应加密(如decrypt.py)

1
mitmdump --mode upstream:http://127.0.0.1:8080 -s decrypt.py --listen-port 9999 --ssl-insecure
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from mitmproxy import http, ctx
import re
from aesCipher import * 

class decryptData:
# 解密请求包,发给burp
	def request(self, flow: http.HTTPFlow) -> None:
    info = ctx.log.info
    if "www.test.com" in flow.request.headers["Host"]:
      try:
        param = flow.request.get_text
        pattern = r"encryptedData=(.*)"
        match = re.search(pattern, param) 
        if match:
          plainText = decrypt_aes_cbc(match.group(1),key,iv) 
          request_body = "encryptedData=" + plainText 
          flow.request.set_text(request_body) 
        except Exception as e:
          info(e)
   # 加密响应包,发给客户端
  def response(self, flow: http.HTTPFlow) -> None:
    pass
  addons = [
    decryptData()
  ]

2、上游代理:请求加密,响应加密(如encrypt.py)

1
mitmdump -s encrypt.py --listen-port 8990 --ssl-insecure
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from mitmproxy import http, ctx
import re
from aesCipher import * 

class encryptData:
# 加密请求包,发给服务器
  def request(self, flow: http.HTTPFlow) -> None:
    info = ctx.log.info
    if "www.test.com" in flow.request.headers["Host"]:
      try:
        param = flow.request.get_text
        pattern = r"encryptedData=(.*)"
        match = re.search(pattern, param) 
        if match:
          cipherText = encrypt_aes_cbc(match.group(1),key,iv) 
          request_body = "encryptedData=" + cipherText 
          flow.request.set_text(request_body) 
        except Exception as e:
          info(e)
	# 解密响应包,发给burp
	def response(self, flow: http.HTTPFlow) -> None:
    pass
  addons = [
    encryptData()
  ]

autoDecoder

项目地址:https://github.com/f0ng/autoDecoder

加解密方式

  1. 自带算法进行加解密:直接通过插件自带的算法去加解密数据包(较为简单,仅支持部分AES、DES、DESede加密)
  2. 自定义接口进行加解密:通过python的flask框架去编写加解密数据包的api ,可自定义加解密的内容,默认传入的参数是整个请求体(request body)与整个响应体(response body),支持复杂的加解密算法,当然,这些都需要自行去写代码解密了

image-20250726060235149

python代码模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# -*- coding:utf-8 -*-
# author:f0ngf0ng
from flask import Flask,Response,request
from pyDes import *
import base64

def des_encrypt(s):
    """
    DES 加密
    :param s: 原始字符串
    :return: 加密后字符串,16进制
    """
    secret_key = "f0ngtest"
    iv = "f0ngf0ng"
    k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
    en = k.encrypt(s, padmode=PAD_PKCS5)
    return base64.encodebytes(en).decode()

def des_decrypt(s):
    """
    DES 解密
    :param s: 加密后的字符串,16进制
    :return:  解密后的字符串
    """
    secret_key = "f0ngtest"
    iv = "f0ngf0ng"
    k = des(secret_key, CBC, iv, pad=None, padmode=PAD_PKCS5)
    de = k.decrypt(base64.decodebytes(bytes(s,encoding="utf-8")), padmode=PAD_PKCS5)
    return de.decode()

app = Flask(__name__)

@app.route('/encode',methods=["POST"])
def encrypt():
    param = request.form.get('data')  # 获取  post 参数
    encry_param = des_encrypt(param.strip("\n"))
    print(param)
    print(encry_param)
    return encry_param

@app.route('/decode',methods=["POST"])
def decrypt():
    param = request.form.get('data')  # 获取  post 参数
    decrypt_param = des_decrypt(param.strip("\n"))
    print(param)
    print(decrypt_param)
    return decrypt_param

if __name__ == '__main__':
    app.debug = True # 设置调试模式,生产模式的时候要关掉debug
    app.run(host="0.0.0.0",port="8888")

Yakkit 热加载

Yakit 是一个基于yak语言编写的工具,功能类似Burpsuite,主要功能有拦截http(s)数据包,漏洞检测,网站地图,自动/手动测试web应用,编码解码,请求与响应差异数据化等功能。

热加载

在聊热加载之前,我们首先需要对其进行了解:什么是热加载?

广义上来说,热加载是一种允许在不停止或重启应用程序的情况下,动态加载或更新特定组件或模块的功能。这种技术常用于开发过程中,提高开发效率和用户体验。

在Yakit 中,热加载是一种高级技术,让 Yak 成为 Web Fuzzer 和用户自定义代码中的桥梁,它允许我们编写一段 Yak 函数,在 Web Fuzzer 过程中使用,从而实现自定义 fuzztag 或更多功能。

image-20250727063037827

在MITM界面点击下方 “热加载”标签页,在恰当的 Hook 点编写我们希望操作流量做的事儿,然后加载进引擎中,等待流量执行。

通过一个简单的图例展示热加载代码在流量劫持中的过程:

img

以下给出示例,具体代码需要根据具体场景编写。

  • 自动放行流量:热加载hijackSaveHTTPFlow方法,

  • 手动劫持流量:hijackHTTPRequest+beforeRequest处理请求,hijackHTTPResponse+afterRequest处理响应

1、自动放行流量

热加载hijackSaveHTTPFlow方法,可以对自动放行的请求和响应进行解密,并且不影响原请求响应

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
hijackSaveHTTPFlow = func(flow /* *yakit.HTTPFlow */, modify /* func(modified *yakit.HTTPFlow) */, drop/* func0 */ {
  key = "1234567890123456"
  iv = "1234567890123456"
	
  // 解密请求
	request, _ = codec.StrconvUnquote(flow.Request)
	reqBody = string(poc.GetHTTPPacketBody(request))
	regexp = `encryptedData= (*)`
  cipherUrl = re.FindSubmatch(reqBody, regexp/*string*/)[1]
  cipherB64 = codec.DecodeUrl(cipherUrl) ~
  cipherBytes = codec.DecodeBase64(cipherB64) ~
  plainText = string(codec.AE§CBCDecrypt(key /*type: []byte*/, cipherBytes, iv /*type: []byte*/)~)
	flow. Request = str.ReplaceAll (request,cipherUrl, plainText)

// // 解密响应
// response, _ = codec.StrconvUnquote(flow. Response) //......
// flow.Response = str.ReplaceAll(response,ciphel, plain Text) 
  modify(flow)
}

2、手动劫持流量

2.1、热加载hijackHTTPRequest 方法,可以对手动劫持的请求进行解密,但发给服务器之前,需要配合beforeRequest 方法再进行加密

处理响应: hijackHTTPResponse+afterRequest

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
hijackHTTPRequest = func(isHttps, url, req, forward /*func(modifiedRequest []byte)*/, drop /*func() */) {
  key = "1234567890123456"
  iv = "1234567890123456"
  reqBody = string(poc.GetHTTPPacketBody(req))
  regexp = `encryptedData=(.*)`
  cipherUrl = re.FindSubmatch(reqBody, regexp/*string*/)[1]
  cipherB64 = codec. DecodeUr(cipherUrl)~
  cipherBytes = codec.DecodeBase64(cipherB64)~
  plainText = string(codec.AESCBCDecrypt(key /*type:[]byte*/, cipherBytes, iv /*type:[]byte*/)~)
  modified = req.ReplaceAll(cipherUrl, plainText)
  forward(poc.FixHTTPRequest(modified))
}

2.2、beforeRequest方法,发给服务器之前,还原加密流程进行加密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
beforeRequest = func(ishttps, oreq/*原始请求*/req/*hijack修改后的请求*/){
  key = "1234567890123456"
  iv = "1234567890123456"
  reqBody = string(poc.GetHTTPPacketBody(req))
  regexp = `encryptedData=(.*)`
  plainText = re.FindSubmatch(reqBody, regexp/*string*/)[1] 
  cipherBytes = codec.AESCBCEndypt(key /*type: []byte*/, plainText, iv /*type: [byte*/)~
  cipherB64 = codec.EncodeBase64(cipherBytes)
  cipherUrl = codec.EscapeUrl(cipherB64)
  req = req.ReplaceAll(plainText, cipherUrl)
  return [byte(req)
}

WebFuzzer模块,也可以使用热加载来进行批量枚举爆破

编写热加载的函数,如rsa函数进行RSA加密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
rsa = func(param) {
  pemBytes = []byte(`-----BEGIN PUBLIC KEY -----
  MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRvA7giwinEkaTYIIDYCkzujvi
  NH+up0XAKXQot8RixKGpB7nr8AdidEvuo+wVCxZwDK3hlcRGrrqt0Gxqwc11btiM
  DSj92Mr3xSaJcshZU8kfj325L8DRh9jpruphHBfh955ihvbednGAvOHOrz3Qy3cb
  ocDbsNeCwNpRxwjldQIDAQAB
  -----END PUBLIC KEY -----`)
  cipherB64 = codec.EncodeBase64(codec.RSAEncryptWithPKCS1v15(pemBytes /*type:
[]byte*/, param) ~)
  cipherUrl = codec.EscapeUrl(cipherB64)
  return cipherUrl
}

在请求包中标记需要执行RSA加密的部分,调用热加载函数rsa

image-20250727082615882

因为{}这部分代码在github上渲染时会报错,所以干脆换图片了。

官方案例:热加载场景案例:爆破aes cbc加密

可以通过官方案例更深入了解 Yakit 的热加载功能及用法

官方中的AES加密函数 handle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
handle = func(p) {
    key = codec.DecodeHex("31323334313233343132333431323334")~
    iv = codec.DecodeHex("03395d68979ed8632646813f4c0bbdb3")~
    usernameDict = ["admin"]
    // passwordDict = x"{{x(pass_top25)}}" // 我们可以使用x前缀字符串来通过fuzztag语法获取pass_top25字典中的值
    passwordDict = ["admin", "123456", "admin123", "88888888", "666666"] // 也可以直接使用手写的list
    resultList = []
    for username in usernameDict {
        for password in passwordDict {
            m = {"username": username, "password": password}
            jsonInput = json.dumps(m)
            result = codec.AESCBCEncryptWithPKCS7Padding(key, jsonInput, iv)~
            base64Result = codec.EncodeBase64(result)
            resultList.Append(base64Result)
        }
    }
    return resultList
}

请求包中将data参数设置为{{yak(handle)}}

image-20250727082853914

Codec模块

Codec功能允许安全研究人员对数据进行各种编码、解码和加密操作。通过图形化界面,用户可以轻松构建复杂的数据转换流程,而无需编写大量代码。这种可视化编排方式大大提高了测试效率,特别是面对各种自定义算法时。

Yakit的v1.3.1sp1版本和Yaklang v1.3.1,我们推出了新版的 Codec 模块。替代之前比较简陋的编解码页面。

新版的Codec页面在设计上借鉴了编解码工具的优秀前辈:CyberChef。采用的”序列形式“的编解码形式,支持配置一条多种编解码组合而成的序列,达成一键处理一些复杂的编解码情况。

image-20250727070332647

实际应用场景

  1. 登录测试场景:当目标网站使用前端转换时,可以先通过Codec分析出算法,然后直接在WebFuzz中应用相同的转换流程进行测试。
  2. API参数测试:对于使用转换参数的API接口,可以先用Codec处理样本数据,修改后再转换回传,实现中间人测试。
  3. 数据变形测试:通过组合多个Codec操作,可以快速生成各种变形的测试数据,用于模糊测试。

注意codec可以直接用在热加载中,但不能在yakit runner中调用

对于MITM(中间人)场景中的热加载需求,用户可以在hijackHTTPResponse方法中调用预定义的Codec流程。虽然具体实现代码未在原文中展示,但基于Yakit的架构设计,这通常涉及加载保存的Codec配置并应用到实时流量上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 解密插件
handle = func(origin) {
  key = "1234567890123456"
  iv = "1234567890123456"
  cipherB64 = codec.DecodeUrlorigin) ~
  cipherBytes = codec.DecodeBase64(cipherB64) ~
  plainText = string(codec.AESCBCDecrypt(key, cipherBytes, iv) ~) 
  return plainText
}

//加密插件
handle = func(origin) {
  key = "1234567890123456"
  iv = 1234567890123456"
  cipherBytes = string(codec.AESCBCEncrypt(key, origin, iv) ~) 
  cipherB64 = codec.EncodeBase64(cipherBytes) 
  cipherUrl = codec. EscapeUr|(cipherB64)
  return cipherUrl 
}

推荐阅读:

五、JSRPC

JSRPC简单来说就是远程调用JavaScript函数的方法。

RPC(Remote Procedure Call,远程过程调用)是一种通过网络让程序调用另一台计算机上的服务或程序的技术。它允许程序在不同的地址空间(通常是不同的计算机)之间调用函数或方法,并且可以基于多种协议实现。

JSRPC原理

JSRPC的原理是在客户端(即浏览器)注入 JSRPC环境,使客户端与JSRPC服务器建立WebSocket连接,保持通信。然后在客户端注册需要用到的加解密函数的demo,这样当JSRPC服务器发送信息给客户端时,客户端接收到并执行相应的方法,然后将结果发送回服务器。服务器接收到结果并将其显示出来。

JSRPC 一般都是基于 WebSocket 或者 WebSocket Secure协议实现,这里简单介绍一下这俩协议:

  • WebSocket 是基于 TCP 的应用层协议,采用双向通信模式,就像一根两端开口的管道。当客户端和服务器建立连接后,双方都能随时向对方发送数据,其协议请求 url 以 ws:// 开头。这种特性使得 WebSocket 适用于需要实时交互的场景,如在线聊天、实时数据展示等。
  • WebSocket Secure(WSS)是 WebSocket 的加密版本,也是采用双向通信模式,请求协议以 wss:// 开头。为了维持稳定的长连接,WSS 通常需要每隔一段时间发送心跳包,比如简单的 ping 消息。正因如此,WSS 常用于对数据安全性要求较高的场景,像社交聊天室、股票实时报价、直播间信息流等。
  • WebSocket 与 WSS 的关系 和 http 与 https 的关系类似。

整体流程

image-20250428225321168

使用方法

JsRPC项目

JsRPC-hliang 是用 go 语言写的,是专门为 JS 逆向做的项目。 Github项目地址:https://github.com/jxhczhl/jsrpc

从Github下载 jsrpc服务端 环境,搭建RPC服务端

1、启动服务端

启动一个JSRPC服务,然后监听12080端口,

1
2
3
4
5
6
7
8
9
$ ./jsRpc
       __       _______..______      .______     ______
      |  |     /       ||   _  \     |   _  \   /      |
      |  |    |   (----`|  |_)  |    |  |_)  | |  ,----'
.--.  |  |     \   \    |      /     |   ___/  |  |
|  `--'  | .----)   |   |  |\  \----.|  |      |  `----.
 \______/  |_______/    | _| `._____|| _|       \______|

INFO[2025-04-29 18:08:08] 当前监听地址:0.0.0.0:12080 ssl启用状态:false

2、注入环境

网页开启F12控制台,在客户端控制台注入JSRPC环境。

打开 /resouces/JsEnv_De.js 复制文件中的全部代码,粘贴到浏览器控制台并执行(注意:可以在浏览器开启的时候就先注入环境,不要在调试断点时候注入)

3、建立通信

然后在控制台建立链接JSRPC服务器的通信

1
2
3
4
// 注入环境后连接通信
var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz");
// 可选  
//var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz&clientId=hliang/"+new Date().getTime())

上述语句执行成功后,服务端会有如下提示:

1
2
INFO[2025-04-29 18:08:08] 当前监听地址:0.0.0.0:12080 ssl启用状态:false
INFO[2025-04-29 18:11:25] [新上线group:zzz,clientId:->e87f9ab1-59a2-4526-971b-5e373a4af448]

4、定位关键函数

找到加解密、签名、校验等逻辑所在的关键函数,打断点,为关键函数注册可以远程调用的方法。

示例: 在浏览器控制台中进行注册

1
2
3
4
5
6
7
// 注册一个方法 第一个参数hello为方法名,
// 第二个参数为函数,resolve里面的值是想要的值(发送到服务器的)
demo.regAction("hello", function (resolve) {
    //这样每次调用就会返回“好困啊+随机整数”
    var Js_sjz = "好困啊"+parseInt(Math.random()*1000);
    resolve(Js_sjz);
})

访问接口,获得js端的返回值: http://127.0.0.1:12080/go?group=zzz&action=hello

最常用的是带参数取值,可以这么用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//假设有一个函数 需要传递两个参数
function hlg(User,Status){
    return User+"说:"+Status;
}

demo.regAction("hello3", function (resolve,param) {
    //这里还是param参数 param里面的key 是先这里写,但到时候传接口就必须对应的上
    res=hlg(param["user"],param["status"])
    resolve(res);
})

python脚本中调用

1
2
3
4
5
6
7
8
9
url = "http://127.0.0.1:12080/go"
data = {
    "group": "zzz",
    "action": "hello3",
    "param": json.dumps({"user":"黑脸怪","status":"好困啊"})
}
print(data["param"]) #dumps后就是长这样的字符串{"user": "\u9ed1\u8138\u602a", "status": "\u597d\u56f0\u554a"}
res=requests.post(url, data=data) #这里换get也是可以的
print(res.text)

后续就是需要注册需要调用哪个函数的方法,JSRPC作者也是提供了demo,过一遍就行。

5、测试调用

访问对应链接,测试是否正常进行加解密

这里我们只需要注册相应调用函数的方法就可以了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import requests
import json

js_code = """
(function(){
    console.log("test")
    return "执行成功"
})()
"""

url = "http://localhost:12080/execjs"
data = {
    "group": "zzz",
    "code": js_code
}
res = requests.post(url, data=data)
print(res.text)


url = "http://127.0.0.1:12080/go"
data = {
    "group": "zzz",
    "action": "hello3",
    "param": json.dumps({"user":"黑脸怪","status":"好困啊"})
}
print(data["param"]) #dumps后就是长这样的字符串{"user": "\u9ed1\u8138\u602a", "status": "\u597d\u56f0\u554a"}
res=requests.post(url, data=data) #这里换get也是可以的
print(res.text)

项目上使用

通过调试,找到加解密位置,将加解密函数进行注册,通过go?group={}&action={}&param={}接口调用注册的函数实现加解密。

逻辑调试通顺后,可以使用 mitmproxy 进行代理

Burp配置上游代理,设置代理条件即可进行自动化调用jsRpc进行加解密

若不使用jsrpc,传统方法是先找到前端所有涉及加解密部分JS代码并替换为空,把这部分代码补充环境到可以本地运行后,再通过mitmproxy或burp插件做到拦截请求时明文,发送请求后加密,相对的响应部分同理

而jsrpc的出现,可以让我们跳过扣代码补环境这一步,直接在发送请求时调用前端原本的加密函数进行加密处理

案例:

Sekiro框架

Sekiro 是由邓维佳大佬,俗称渣总,其功能更加强大,写的一个基于长链接和代码注入的 Android Private API 暴露框架,可以用在 APP 逆向、APP 数据抓取、Android 群控等场景,同时 Sekiro 也是目前公开方案唯一稳定的 JSRPC 框架。

项目地址:https://github.com/yint-tech/sekiro-open

官方文档:https://sekiro.iinti.cn/sekiro-doc/

frida-rpc

1、打开app,手机启用frida 2、电脑通过frida调用内存中的签名函数,并开启本地接口传递参数值

相关脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from fastapi import FastAPI, HTTPException 
from pydantic import BaseModel
import uvicorn
import frida

# hook关键函数,生成签名值
jsCode = """
  function getsign(data){
    let result = "";
    Java.perform(function () {
    	let XX = Java.use("java.XX.XX").$new();
    	result = Java.use("com.xx.SignUtils").c(XX, XX) 
    })
    return result;
  } 
	rpc.exports = (rpcfunc: getsign);
"""

process = frida.get_usb._device().attachXX#包名注入 
script = process.create_script(jsCode)
script.load()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def get_ts(): # 其他戳参数生成
  ts = XXX
  return ts

app = FastAPI() # 创建 FastAPI 应用

class ParamsRequest(BaseModel):# 定义请求模型
  params: str

class SignResponse(BaseModel): # 定义响应模型
  sign: str
  Ts: str

@app.post"/sign" response_model=SignResponse # 定义 sign接口 
async def sign(request: ParamsRequest):
  try:
    Ts = get_ts()
    sign = script.exports_sync.rpcfunc(request.params+Ts) 
    return {"sign":sign, "Ts": Ts}
  except Exception as e:
    raise HTTPException(status_code=500, detail = str(e)) 
    
if _name_ == "_main_":
  uvicorn.runapp, host="127.0.0.1" port=12345 # 启动应用

3、yakit通过热加载模块,调用本地接口,传递篡改请求包参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
beforeRequest = func(req) {
  reqBody = str.ExtractJson(req)
  Type = poc.GetHTTPPacketHeader(req,"Type")
  signOld = poc.GetHTTPPacketHeader(req,"Sign")
  tsOld = poc.GetHTTPPacketHeader(req,"Ts")
  params = Type=`+ Type + `&Data=`+ reqBody +`&Ts`=`
  data=("params":params)
  res = json.loads(http.Post(`http://127.0.0.1:12345/sign`, http.json(data))~.Data())
  signNew = res["sign"]
  tsNew = res["Ts"]
  req = req.ReplaceAll(signOld,signNew).ReplaceAll(tsOld, tsNew)
  return []byte(req)
}

六、CDP

CDP协议(Chrome DevTools Protocol)是Chrome 原生支持的调试协议,通过 WebSocket 连接控制浏览器实例。

CDP协议原理

支持动态监控 JS 执行、修改 DOM、调用函数等,适用于加解密逻辑的实时调试。 通过 Chrome 浏览器的原生调试接口(CDP),可在断点处直接执行 JS 表达式,获取作用域内的变量或调用函数,深度利用浏览器调试功能。

整体流程

工作流

  • 启动 Chrome 时开启调试端口(--remote-debugging-port=9222)。
  • 通过 CDP 客户端(如 Puppeteer)发送命令执行 JS 函数或捕获加密过程

典型工具:Chrome DevTools、基于 CDP 的自定义脚本(如 Python 调用)

练手地址:https://github.com/0ctDay/encrypt-decrypt-vuls 原创项目:https://github.com/Nstkm001/CDP_test

用于快速生成加解密接口,利用cdp协议对断点帧进行调用。

1.启用node cdp.js对cdp监听。 2.使用python去启动调试的web进行连接cdp。 3.打开f12定好断点,运行到断点。 4.调用加解密表达式。 5.规划并填充至中间代理脚本。

案例:Cdp协议深度应用Web渗透加解密

参考文献

文件属性

创建时间:2025-06-04 22:19

修订记录:

  • 2025-06-04 ,此次修订内容| 新建
  • 2025-07-27 ,完成1.0版本 | 上传

备注: 拖延症拖太久了,这文章写的跟原本预期不太一样,很多东西没填充进去,感觉有点水了。

有机会再把里面的一些方法具体拿出来说说,着急写完,不管了(狗头 🐶 )。

0%