codec快捷加解密

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

在yakit中定义一个热加载函数 enc_hex_aes:

(1)codec中创建对应加密模块,保存命名为enc_hex_aes

image-20250630174552751

image-20250630174630669

(3)热加载代码中定义对应函数,调用:

image.png

img

yakit热加载有一个相较于其他工具很显著的优点:

可以定义codec模块中的样例模版,用来在热加载代码中直接调用

yakit热加载常用的API

定义函数

enc_md5 = func(p) {
aa = codec.Md5(p)
res := fuzz.Strings(aa) //这块会将aa转换为一个字符串数组[]string
return res[0] //取出第一个元素,也就是md5加密后返回的字符串
}

image.png

//函数调用这块和其他的Python、java等类似
enc_data = enc_md5('123456')

请求数据包req相关用法

req:=`POST /user/traditionlogin HTTP/1.1
Host: xx.xx.xxx
Connection: keep-alive
Content-Length: 399
Pragma: no-cache
Cache-Control: no-cache
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/137.0.0.0
Accept: application/json
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

certType=03ED54xxxxxxxx119CFAFC2CD2&certNo=CE827BxxxxxxxxxF56BCD&lPlatform=756F68AD2A17DFD7840B5B5204&lType=E1B1D6DD80C1624FCE09215AE112C2B5&verifiCode=88237E379E6C325DEC3E3483FB44ECBC`

(1)获取请求方法

req_method = poc.GetHTTPRequestMethod(req)
println(req_method) //POST

(2)获得请求体内容-针对POST类型的表单请求

//获取请求体    
req_body = poc.GetHTTPPacketBody(req)
println(req_body) //certType=03ED54D53C234F9881119CFAFC2CD2&certNo=CE827B3602E47F849DC759F56BCD&lPlatform=756F68AD2A17DFD7840B5B5204&lType=E1B1D6DD80C1624FCE09215AE112C2B5&verifiCode=88237E379E6C325DEC3E3483FB44ECBC

(3)获取请求体中某个具体参数(适用于json格式)

修改req参数的请求头为JSON格式,如下:

req:=`POST /user/traditionlogin HTTP/1.1
Host: xx.xx.xxx
Connection: keep-alive
Content-Length: 399
Pragma: no-cache
Cache-Control: no-cache
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/137.0.0.0
Accept: application/json
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6

{
"certType": "03ED54xxxxxxxxxFC2CD2",
"certNo": "CE82xxxxxxxxxxBCD",
"lPlatform": "756F68AD2A17DFD7840B5B5204",
"lType": "E1B1D6DD80C1624FCE09215AE112C2B5",
"verifiCode": "88237E379E6C325DEC3E3483FB44ECBC"
}`
body_json, _ = poc.ExtractPostParams(req)
println(body_json)
println(body_json['verifiCode']) //88237E379E6C325DEC3E3483FB44ECBC

img

(4)获取get请求中的参数体

修改请求包如下:

req:=`GET /traditionlogin?appkey=J7zqYf&appsecret=2ABFny&appversion=2.12.2&certNo=CE827B3602E47F849DC7E7B259F56BCD&certType=03ED54D53C234F988561F19CFAFC2CD2&channel=ZHH5&lPlatform=756F68AD2A17DF1B918FD7840B5B5204&lType=E1B1D6DD80C1624FCE09215AE112C2B5&market=0&requestNo=9D7D2C8FD692F030EDEB24436AE5233867389B26C0BEFBBF21549CD7C3356CFC&sign=jAZR7b&verifiCode=88237E379E6C325DEC3E3483FB44ECBC&zy_requestno=D2025062813225745166 HTTP/1.1
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/137.0.0.0
Accept: application/json
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6`

可以使用如下方法获取到请求头内容

path = poc.GetHTTPRequestPath(req)
parts:=path.Split("?")
println(parts)
println(parts[1])

(5)获取请求头中某个具体参数

//获取User-Agent请求头的值
ua = poc.GetHTTPPacketHeader(req, 'User-Agent')
println(ua) //Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/137.0.0.0

(6)获取所有的请求头

headers = poc.GetHTTPPacketHeaders(req)
println(headers)
println(type(headers)) //map[string]string

img

这块输出的是一个map的字典,可以像Python一样直接获取对应字段的值

//获取User-Agent请求头的值
print(headers['User-Agent'])

(6)获取请求体中某个具体参数(适用于form表单)

方法一:直接获取

post_param = poc.GetHTTPPacketPostParam(req,'verifiCode')
println(post_param) //88237E379E6C325DEC3E3483FB44ECBC

方法二:间接获取(适用GET和POST类型的form表单)

①获取请求头对应键值对

②获取具体参数值

post_param = poc.GetAllHTTPPacketPostParams(req)
println(post_param)
println(post_param['verifiCode']) //88237E379E6C325DEC3E3483FB44ECBC

方法三:直接获取(适用GET和POST类型的form表单)

param1 = poc.GetHTTPPacketQueryParam(req, 'verifiCode')
println(param1)

(7)修改请求头参数内容

①删除某个请求头

req = poc.DeleteHTTPPacketHeader(req, 'User-Agent')
println(req) //删除UA请求头

②增加请求头

req = poc.AppendHTTPPacketHeader(req, 'aaa', '1111')
println(req)

③修改请求头

req = poc.ReplaceHTTPPacketHeader(req, 'aaa', '123456')
println(req)

(8)获取请求的URL

url = poc.GetHTTPRequestPathWithoutQuery(req)
println(url) ///zyfund_temp/user/traditionlogin

(8)修改请求头参数

①GET请求

//将certNo参数值修改为123456
req = poc.ReplaceHTTPPacketQueryParam(req, 'certNo', '123456')
println(req)
//移除参数certNo
req = poc.DeleteHTTPPacketQueryParam(req, 'certNo')
println(req)
//增加参数aaa,参数值为123456
req = poc.AppendHTTPPacketQueryParam(req, 'aaa', '123456')
println(req)

②POST请求-form表单形式

//将certNo参数值修改为123456
req = poc.ReplaceHTTPPacketPostParam(req, 'certNo', '123456')
println(req)
//移除参数certNo
req = poc.DeleteHTTPPacketPostParam(req, 'certNo')
println(req)
//增加参数aaa,参数值为123456
req = poc.AppendHTTPPacketPostParam(req, 'aaa', '123456')
println(req)

③POST请求-JSON格式

//获取json请求体
body_json, _ = poc.ExtractPostParams(req)
// 打印json
println(body_json)
// 提取json中的参数值
println(body_json["certNo"])
// 替换req中,json中的certNo的参数值
body_json["certNo"] = "123456"
req=poc.ReplaceHTTPPacketJsonBody(req, body_json)
println(req)

img

body_json.Delete('certNo')  //删除json中某个字段
body_json["aaa"] = "123456" //新增aaa参数

响应rsp相关用法

定义响应体如下:

rsp := `HTTP/1.1 200
Server: Tengine
Content-Type: application/json;charset=UTF-8
Connection: keep-alive
Date: Sat, 28 Jun 2025 05:22:59 GMT
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: *
X-Xss-Protection: 1;mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
X-Permitted-Cross-Domain-Policies: all
X-Download-Options: value
Timing-Allow-Origin: *
EagleId: 8ccfeca317510881786806024e
Content-Length: 128

F98A10AEB7D2B3D4CE69E66C8F9E3156C13BABF3A333F3674F62049D30C7F89379D8FADA79C35FE6EA3738A42190FDF0BC59D81E707CABDFDF079E32F925764F`

常规用法

body = poc.GetHTTPPacketBody(rsp) // 响应体
println(body)

rspIns = poc.ParseBytesToHTTPResponse(rsp)~
println(rspIns)
println(rspIns.StatusCode) // 响应状态码
body = io.ReadAll(rspIns.Body)~
println(string(body)) // 响应体

rspIns = poc.ParseBytesToHTTPResponse(rsp)~
println(rspIns.Header["Content-Type"][0]) //获取响应头content-type字段

实战案例

前面介绍了一些主要的API接口,下面结合实战项目来利用yakit热加载实战加解密&前面

某金融项目众测,抓包,发现数据包如下:

image.png

通过观察数据包,可以得到如下信息:

  • 响应包加密—>需要做解密处理
  • 请求包中有参数sign—>需要破解前面

JS逆向

具体逆向过程就不分析了,主要逻辑如下:

(1)响应包解密

打断点可以定位到使用了AES加密,找到对应的key和iv,成功解密:

img

(2)请求参数sign

通过跟栈,定位到sign参数生成逻辑为:

①获取所有的请求参数

②按照键值中的键进行排序

③对排序后的字符串做MD5

④再对MD5结果进行处理得到sign

img

热加载代码

响应包解密

可以现在yakit runner中写对应的代码来做测试

rsp=`HTTP/1.1 200
Server: Tengine
Content-Type: application/json;charset=UTF-8
Connection: keep-alive
Date: Mon, 30 Jun 2025 08:30:13 GMT
X-Xss-Protection: 1;mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
X-Permitted-Cross-Domain-Policies: all
X-Download-Options: value
Via: cache3.l2et2-2[43,0], kunlun16.cn5135[147,0]
Timing-Allow-Origin: *
EagleId: 758786a417512722127164355e
Content-Length: 128

F98A10AEB7D2B3D4CE69E66C8F9E3156A844A9D4077179D3B30BD581986F833EFEAA1DE64D9124135A05CA0D8D9BC8AB386116CC94E337D1C0314E5CFFB4EF06`

decode_aes_hex = func(data){
data = codec.DecodeHex(data)~
key = codec.DecodeHex('3333xxxxxxxxxxxxxxxxxxxxxxx13939')~
result = codec.AESECBDecrypt(key, data, key)~
return result
}


resp_body = poc.GetHTTPPacketBody(rsp) // 响应体
result = decode_aes_hex(resp_body)
rsp = poc.ReplaceHTTPPacketBody(rsp, result)

println(rsp)

img

对响应包进行解密为了方便观察返回包的内容,所以,这块可以将解密代码写到如下两个热加载方法中:

  • hijackSaveHTTPFlow(不会影响原始的请求和响应,只会在history中展示为明文)
  • afterRequest(在响应从服务器返回到yakit过程中做处理,可以在web Fuzzer中显示为明文)
decode_aes_hex = func(data){
data = codec.DecodeHex(data)~
key = codec.DecodeHex('33333xxxxxxxxxxxxxxxxxxxx939')~
result = codec.AESECBDecrypt(key, data, key)~
return result
}


// hijackSaveHTTPFlow 会在流量被存储到数据库前被调用,可以通过该函数对入库前的流量进行修改,例如修改请求/响应,添加tag/染色等
// flow 流量结构体,可以通过鼠标悬浮提示查看其拥有的字段并对其进行修改
// modify(modified) 提交修改后的流量结构体,如果未被调用,则使用原始的流量结构体
// drop() 丢弃流量
hijackSaveHTTPFlow = func(flow /* *yakit.HTTPFlow */, modify /* func(modified *yakit.HTTPFlow) */, drop/* func() */) {
// flow.Request 转义后的请求
// flow.Response 转义后的响应
// 对于转义后的请求和响应,需要通过以下方式拿到原始的请求/响应
// req = str.Unquote(flow.Request)~
// rsp = str.Unquote(flow.Response)~
// 对于修改后的请求和响应,需要通过以下方式再将其转义回去
// flow.Request = str.Quote(req)
// flow.Response = str.Quote(rsp)
// flow.AddTag("tag") // 添加tag
// flow.Red() // 染红色
//
// Example:
// responseBytes, _ = codec.StrconvUnquote(flow.Response)
// if str.MatchAnyOfRegexp(responseBytes, "/admin/", "accessKey") {
// flow.Red();
// modify(flow)
// }

rsp = str.Unquote(flow.Response)~
resp_body = poc.GetHTTPPacketBody(rsp) // 响应体
result = decode_aes_hex(resp_body)
rsp = poc.ReplaceHTTPPacketBody(rsp, result)
flow.Response = str.Quote(rsp)
flow.AddTag("decrypted")
modify(flow)
}
// afterRequest 允许在返回响应前对响应做最后的处理,定义为 func(https bool, originReq []byte, req []byte, originRsp []byte, rsp []byte) []byte
// https 请求是否为https请求
// originReq 原始请求
// req 请求
// originRsp 原始响应
// rsp 响应
afterRequest = func(https, originReq, req, originRsp, rsp) {
// 我们可以将响应进行一定的修改,例如解密响应
/*
一个替换响应的例子
body = poc.GetHTTPPacketBody(rsp)
data = json.loads(body)~
if "result" in data {
data["result"] = string(codec.DecodeBase64(data["result"])~)
}
*/
resp_body = poc.GetHTTPPacketBody(rsp) // 响应体
result = decode_aes_hex(resp_body)
rsp = poc.ReplaceHTTPPacketBody(rsp, result)

return []byte(rsp)
}

效果展示:

img

img

计算签名

由于yaklang代码只了解部分API的使用方式,计算签名这块的逻辑比较复杂,所以,这块借助flsask+yakit实现签名计算。

(1)flask代码

function encryptSign(e) {
for (var t = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"], n = e.substring(0, 8), a = 1073741823 & parseInt(n, 16), r = "", i = 0; i < 6; i++) {
r += t[parseInt(61 & a)],
a >>= 5
}
return r
}
from flask import Flask, request
import hashlib
import execjs


def sort(query_str):
params = [
param.split('=', 1) # 保留值中的等号
for param in query_str.split('&')
if '=' in param
]

# 按键名排序
sorted_params = sorted(params, key=lambda x: x[0])

# 重新组合参数
return '&'.join(f"{k}={v}" for k, v in sorted_params)


def gen_md5(str):
md5 = hashlib.md5()
md5.update(str.encode('utf-8'))
result = md5.hexdigest().upper()
return result


def encrypt_sign(str):
with open('getSign.js', 'r', encoding='utf-8') as f:
js_obj = execjs.compile(f.read())
result = js_obj.call("encryptSign", str)
return result


def switch_sign(data):
data = sort(data)
md5 = gen_md5(data)
sign = encrypt_sign(md5)
return sign


app = Flask(__name__)


@app.route('/sign' , methods=["POST"])
def sign():
body = request.get_data().decode('utf-8')
print(body)
signature = switch_sign(body)
return signature


if __name__ == '__main__':
app.run(host="0.0.0.0", port="8889")

使用yakit runner进行测试,成功获得sign值

req = poc.DeleteHTTPPacketQueryParam(req, 'sign')

path = poc.GetHTTPRequestPath(req)
parts = str.Split(path, "?")
req_body=parts[1]
// println(req_body)

rsp2, req2 = poc.HTTP(`POST /sign HTTP/1.1
Host:127.0.0.1:8889
Content-Type: application/x-www-form-urlencoded

paramStr`,poc.replaceBody(req_body, false))~


rspIns2 = poc.ParseBytesToHTTPResponse(rsp2)~
body2 = io.ReadAll(rspIns2.Body)~
println(body2)

image.png

img

(2)yakit热加载代码

这块的代码只需要添加到beforeRequest魔术函数中就行

// beforeRequest 会在请求到达服务器之前被调用,可以通过该函数对请求做最后一次修改
// isHttps 请求是否为https请求
// oreq 原始请求
// req hijackRequest修改后的请求
// 返回值: 修改后的请求,如果没有返回值则使用hijackRequest修改后的请求
beforeRequest = func(ishttps /*bool*/, oreq /*[]byte*/, req/*[]byte*/){
// Example:
// if str.Contains(string(req), "凝聚磅礴的中国文学力量") {
// modified = poc.FixHTTPRequest(str.ReplaceAll(req, "凝聚磅礴的中国文学力量", "AAAAAAAAAAAAAAAA"))
// return []byte(modified)
// }
req = poc.DeleteHTTPPacketQueryParam(req, 'sign')

path = poc.GetHTTPRequestPath(req)
parts = str.Split(path, "?")
req_body=parts[1]
println(req_body)

rsp2, req2 = poc.HTTP(`POST /sign HTTP/1.1
Host:127.0.0.1:8889
Content-Type: application/x-www-form-urlencoded

paramStr`,poc.replaceBody(req_body, false))~


rspIns2 = poc.ParseBytesToHTTPResponse(rsp2)~
body2 = io.ReadAll(rspIns2.Body)~
println(body2)
req = poc.ReplaceHTTPPacketQueryParam(req, "sign", body2)
return req
}

查看效果,当携带有正确的sign时,返回内容如下:

img

修改custNo参数值/直接删除sign值,这个时候签名就不对了,返回如下:

img

启用yakit热加载代码后,就算删除了sign的值,借助代码,也可以成功计算签名,正确输出

img