基础知识
案例APP
我们使用网上找到一个登录加密的app,作为我们的案例。该app功能非常简单。
输入用户名密码后点击登录,后端数据报文如下:
参数和响应报文均使用AES进行加密。
Yakit热加载脚本输出log
python rpc接口准备
关于如何分析app,写rpc脚本在此不再详细讲解。写好的代码如下:
python脚本
import frida
import json
from flask importFlask, jsonify, request
def message(message, data):
if message['type']=='send':
print(f"[*] {message['payload']}")
else:
print(message)
session = frida.get_usb_device().attach("eseBrida")
with open("1.js")as f:
jsCode = f.read()
script = session.create_script(jsCode)
script.on("message",message)
script.load()
app =Flask(__name__)
@app.route('/encrypt', methods=['GET'])
def decrypt_class():
plaintext = request.args.get('plaintext')
print("1111",plaintext)
plaintext = plaintext.replace(" ","+")
print("222",plaintext)
res = script.exports.invokemethoden(plaintext)
print(res)
return res
@app.route('/decrypt', methods=['GET'])
def encrypt_class():
encrypttext = request.args.get('encrypttxt')
print("1111",encrypttext)
encrypttext = encrypttext.replace(" ","+")
print("222",encrypttext)
res = script.exports.invokemethodde(encrypttext)
return res
if __name__ =="__main__":
app.run(port=5001)
js代码
function fridamethod01(cleartext){
var result =null;
var key ="9876543210123456"
Java.perform(function(){
letAesEncryptionBase64=Java.use("com.ese.http.encrypt.AesEncryptionBase64");
result =AesEncryptionBase64.encrypt(key,cleartext)
});
return result;
}
function fridamethod02(encrypted){
var result =null;
var key ="9876543210123456"
// public native String method02(String str);
Java.perform(function(){
letAesEncryptionBase64=Java.use("com.ese.http.encrypt.AesEncryptionBase64");
result =AesEncryptionBase64.decrypt(key,encrypted)
});
return result;
}
rpc.exports ={
invokemethoden: fridamethod01,
invokemethodde: fridamethod02,
}
Yakit热加载脚本编写
Yakit的热加载脚本要在两个功能点处编写
-
• MITM界面热加载脚本编写
-
• Fuzzer界面热加载脚本编写
MlTM界面热加载脚本编写
热加载脚本的原理
MITM界面提供了热加载脚本的模板,我们可以直接在Yakit的MIMT界面查看
热加载脚本的原理
类似于Frida的Hook,既在整个请求的生命周期内(从发起请求到响应请求到将整个请求存入数据库)中的不同位置提供接口允许我们对请求进行修改。
在MITM界面我们只需要对流量进行解密展示,无需加密。
因此我们此时选择Hook的时机是在服务器响应后,存入数据库前对请求body和响应body进行解密展示。这样既不影响通信,也能做到将报文解密后的结果展示到MITM中。
但是这样操作带来了一个小问题,就是数据库文件中不会对密文进行存储,但是对测试是没有影响的。
根据上文的描述,在yakit将报文存入数据库前进行Hook,提供的接口和定义如下:
# hijackSaveHTTPFlow 是 Yakit 开放的 MITM 存储过程的 Hook 函数
# 这个函数允许用户在 HTTP 数据包存入数据库前进行过滤或者修改,增加字段,染色等
# 类似 hijackHTTPRequest
# 1. hijackSaveHTTPFlow 也采用了 JS Promise 的回调处理方案,用户可以在这个方法体内进行修改,修改完通过 modify(flow) 来进行保存
# 2. 如果用户不想保存数据包,使用 drop() 即可
#
hijackSaveHTTPFlow = func(flow /* *yakit.HTTPFlow */, modify /* func(modified *yakit.HTTPFlow) */, drop/* func() */){
// responseBytes, _ = codec.StrconvUnquote(flow.Response)
// if str.MatchAnyOfRegexp(responseBytes, "/admin/", "accessKey") {
// flow.Red();
// modify(flow)
// }
}
*yakit.HTTPFlow定义:
type palm/common/yakgrpc/yakit.(HTTPFlow)struct{
Fields(可用字段):
Model: gorm.Model
Hash:string
IsHTTPS:bool
Url:string
Path:string
Method:string
BodyLength: int64
ContentType:string
StatusCode: int64
SourceType:string
Request:string# 需要通过 codec.StrconvUnquote 解码
Response:string# 需要通过 codec.StrconvUnquote 解码
GetParamsTotal:int
PostParamsTotal:int
CookieParamsTotal:int
IPAddress:string
RemoteAddr:string
IPInteger:int
Tags:string
StructMethods(结构方法/函数):
PtrStructMethods(指针结构方法/函数):
func AddTag(v1:string)
func BeforeSave()return(error)
func Blue()# 蓝色
func CalcHash()return(string)
func ColorSharp(v1:string)
func Cyan()# 天蓝色
func Green()# 绿色
func Grey()# 灰色
func Orange()# 橙色
func Purple()# 紫色
func Red()# 红色
func RemoteColor()
func ToGRPCModel()return(*ypb.HTTPFlow, error)
func ToGRPCModelFull()return(*ypb.HTTPFlow, error)
func Yellow()# 黄色
}
*/
这里的接口代码较为清晰,无需过多解释。根据案例编写的代码如下:
hijackSaveHTTPFlow = func(flow /* *yakit.HTTPFlow */, modify /* func(modified *yakit.HTTPFlow) */, drop/* func() */){
// 解密请求报文
resquestByts,_ = codec.StrconvUnquote(flow.Request)
if str.MatchAnyOfRegexp(flow.Url,"fuckcfcc"){
body = resquestByts.Split("rnrn")[1]
body = json.loads(body)
username=body['username']
password= body['password']
url ="http://127.0.0.1:5001/decrypt?encrypttxt="+string(username)
rsp1, req1 = poc.Get(url)~
//自己请求的连接获取body采用如下方式
encrypt_username = rsp1.GetBody()
url2 ="http://127.0.0.1:5001/decrypt?encrypttxt="+string(password)
rsp2, req2 = poc.Get(url2)~
encrypt_password = rsp2.GetBody()
flow.Request= str.Replace(string(flow.Request),username,encrypt_username,1)
flow.Request= str.Replace(string(flow.Request),password,encrypt_password,1)
modify(flow)
}
//----------------------------------------------------------------------------------------------------------------------
//解密响应报文
responseBytes, _ = codec.StrconvUnquote(flow.Response)
if str.MatchAnyOfRegexp(flow.Url,"fuckcfcc",){
body = responseBytes.Split("rnrn")[1]
log.warn(body)
url ="http://127.0.0.1:5001/decrypt?encrypttxt="+string(body)
rsp1, req1 = poc.Get(url)~
//自己请求的连接获取body采用如下方式
body1 = rsp1.GetBody()
flow.Response= str.Replace(string(flow.Response),body,body1,1)
// flow.Response = string(responseBytes)
modify(flow)
}
}
使用上述热加载脚本后的效果如下:
Fuzzer界面热加载脚本编写
当我们需要在Fuzzer界面进行测试的时候,仍然需要Fuzzer界面进行热加载脚本进行编写。
Fuzzer界面的热加载脚本模板如下:
// 使用标签 {{yak(handle|param)}} 可触发热加载调用
handle = func(param){
// 在这里可以直接返回一个字符串
return codec.EncodeBase64("base64-prefix"+ param)+ sprintf("_origin(%v)", param)
}
// 使用标签 {{yak(handle1|...)}} 可触发热加载调用
handle1 = func(param){
// 这个特殊的 Hook 也支持返回数组
return["12312312","abc","def"]
}
// beforeRequest 允许发送数据包前再做一次处理,定义为 func(origin []byte) []byte
beforeRequest = func(req){
/*
// 我们可以提供一些基础用法,比如说单纯就是替换一个时间戳~
req = str.ReplaceAll(req, "TIMESTAMP_INT64", sprint(time.Now().Unix()))
*/
return[]byte(req)
}
// afterRequest 允许对每一个请求的响应做处理,定义为 func(origin []byte) []byte
afterRequest = func(rsp){
return[]byte(rsp)
}
// mirrorHTTPFlow 允许对每一个请求的响应做处理,定义为 func(req []byte, rsp []byte, params map[string]any) map[string]any
// 返回值回作为下一个请求的参数,或者提取的数据,如果你需要解密响应内容,在这里操作是最合适的
mirrorHTTPFlow = func(req, rsp,params){
returnparams
}
这里也是在不同的时机提供了一些接口,供我们进行Hook。
我们选择如下两个时机来进行Hook
// beforeRequest 允许发送数据包前再做一次处理,定义为 func(origin []byte) []byte
beforeRequest = func(req){
/*
// 我们可以提供一些基础用法,比如说单纯就是替换一个时间戳~
req = str.ReplaceAll(req, "TIMESTAMP_INT64", sprint(time.Now().Unix()))
*/
return[]byte(req)
}
// afterRequest 允许对每一个请求的响应做处理,定义为 func(origin []byte) []byte
afterRequest = func(rsp){
return[]byte(rsp)
}
beforeRequest修改请求报文,将明文请求加密发送到服务器。
afterRequest修改响应报文,将密文响应解密后返回到yakit。
写好的案例代码如下:
beforeRequest = func(req){
header,body = poc.Split(req)
body = json.loads(body)
username=body['username']
password= body['password']
url ="http://127.0.0.1:5001/encrypt?plaintext="+string(username)
rsp1, req1 = poc.Get(url)~
encrypt_username = rsp1.GetBody()
url2 ="http://127.0.0.1:5001/encrypt?plaintext="+string(password)
rsp2, req2 = poc.Get(url2)~
encrypt_password = rsp2.GetBody()
log.warn(encrypt_password)
req= str.Replace(string(req),username,encrypt_username,1)
req= str.Replace(string(req),password,encrypt_password,1)
return[]byte(req)
}
// afterRequest 允许对每一个请求的响应做处理,定义为 func(origin []byte) []byte
afterRequest = func(rsp){
header,body = poc.Split(rsp)
url ="http://127.0.0.1:5001/decrypt?encrypttxt="+string(body)
rsp1, req1 = poc.Get(url)~
body1 = rsp1.GetBody()
rsp = str.ReplaceAll(rsp, body, body1)
return[]byte(rsp)
}
Fuzzer热加载标签
当然我们也可以使用热加载的标签功能单独的对参数进行加密和设置不同的payload,关于热加载标签相关的知识,文档中写的比较详细,不在赘述。
https://yaklang.com/products/Web%20Fuzzer/fuzz-hotpatch
插件编写
Yak-MITM插件
加解密场景下的插件,再新建插件的时候,选择Yak-MITM插件
代码直接复用MITM场景下热加载脚本直接,粘贴即可。
然后生成本地插件。
然后在"MITM交互劫持"功能点上选择启用此插件。便可以获得MITM热加载脚本相同的结果。
一次编写,随便分享。直接复用。
Yak-Codec插件
使用该类型开发的插件就类似于Burpy,直接右键解密.这里写法相对简单
直接在handle中根据自己预设定的输入输出编写代码即可。
右键选择对应插件解密
最终实现效果
同burpy相比不支持函数界别的菜单展示,仅支持将插件名称显示在菜单上。
同时输入参数好像只能有一个。
参考文档
总结下学习过程中比较好的文档链接
官网提供的案例代码
API接口完全手册
https://yaklang.com/docs/api/global_buildin_ops
原文始发于微信公众号(移动安全星球):Yakit针对流量加密APP的Frida rpc解决方案
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论