FRP 0.38.0 流量加密分析

FRP是一款开源的轻量级反向代理工具,可快速、稳定地代理NAT或者防火墙后面的服务,应用较为广泛。其使用Go语言编写,具备很好的跨平台特性。
FRP仓库地址:https://github.com/fatedier/frp


由于网络上几乎没有分析frp协议及其加密机制的文章,而且frp每个版本的加密逻辑还不一样(例如0.38.0与0.52.0),笔者跟了一遍源码,简单记下这篇文章,供相关从业者参考。

##结论

  • 加密算法:AES-CFB
  • iv:首次发送报文时向对方传递
  • salt:固定盐值
  • 会话密钥:用配置文件里的tokenpbkdf2算法派生

分析过程

首先下载源码,搜索关键字encrypt,发现一处可能与加密相关的地方,跟进去。
image-2.png
发现引用了frpIo这个包,而这个包是从github引用,去它仓库克隆下代码来。
image-3.png
很明显WithEncryption就是加密逻辑的入口:传入密钥,对读写函数进行封装。
image-4.png
跟进NewReader()函数,找到具体的加密逻辑:
image-5.png
+ 第一步,使用pbkdf2算法从主密钥派生出会话密钥。需要的参数及寻找位置如下:
+ 主密钥:从frp代码的/server/control.go中发现,主密钥就是serverCfg.Token,也就是我们配置文件frps.iniCommon中的token字段。
image-6.png
+ 盐值:从frp代码的/server/main.go入口函数中找到,默认是"frp"
image-7.png
+ 迭代次数:固定64。
+ 输出长度:与aes每个block长度一致,默认为16字节。
+ 哈希函数:固定为sha1。

+ 第二步,生成iv向量。虽然代码注释上写着“random iv”,但查看代码发现,iv只初始化了大小,没有赋值,因此固定是16字节的0.**当时狠狠坑了我好长时间**
+ 第三步,AES CFB加密。这点需要了解基础的密码学常识,在CFB模式下首部不能有多余字符,否则分组错乱,整个密文解开都是错的。而在抓包分析时,经常有使用0或者其他标志填充首部,这点也容易导致解密失败。**编写解密脚本时需要注意。**

解密脚本编写

到此为止,整个加密逻辑就很清楚了。可以参照源码,用go编写解密脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func Decrypt(token, ciphertext, iv []byte) ([]byte, error) {
// 根据你的需求处理 token,用作密钥
key := pbkdf2.Key(token, []byte(DefaultSalt), 64, aes.BlockSize, sha1.New)

// 创建 AES 加密块
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

// 检查密文长度是否有效
if len(ciphertext) == 0 {
return nil, fmt.Errorf("empty ciphertext")
}

// 使用 CFB 解密模式
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ciphertext, ciphertext)

return ciphertext, nil
}

总结

总结来说,frp是用配置文件里的tokenpbkdf2算法派生会话密钥;首次发送报文时向对方传递iv;使用固定值作为salt;采用AES-CFB模式对流量进行加密。

作者

Catop

发布于

2023-12-08

更新于

2024-11-10

许可协议

评论