前言

frp无疑是众多代理工具中,用得最舒服的了。但是他还是存在几个缺点的。

  • .ini配置文件泄露服务器信息。
  • 非TLS特征明显

  • golang编译打包后的体积过大

一直想改来着,但是一直拖着。前两天看到了吐司大佬发的frp讨论帖,于是下定决心改一下。

这里主要是对上面提到的前两个缺点进行改造,体积过大问题需要去整体的修改,减少他用到的一些依赖库和无用功能。

流量特征(非TLS)

在没有配置tls_enable = true

frpc在连接认证frps的时候回把frp的版本信息等等发给frps进行认证。

目前一些流量设备就通过这个特征来识别frp代理。

1592656026044

如上图,可以看到有如下几个字段值:version,hostname,arch,user,privilege_key,runid,metas

去除特征

去除的方法也很简单,只要把这些特征值修改一下就行了。

https://github.com/fatedier/frp

首先把frp的源码从github上下下来。

定位到认证信息的代码位置为:models/msg/msg.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Login struct {
Version string `json:"version"`
Hostname string `json:"hostname"`
Os string `json:"os"`
Arch string `json:"arch"`
User string `json:"user"`
PrivilegeKey string `json:"privilege_key"`
Timestamp int64 `json:"timestamp"`
RunId string `json:"run_id"`
Metas map[string]string `json:"metas"`

// Some global configures.
PoolCount int `json:"pool_count"`
}

type LoginResp struct {
Version string `json:"version"`
RunId string `json:"run_id"`
ServerUdpPort int `json:"server_udp_port"`
Error string `json:"error"`
}

如上图,这里可以看到定义的结构里有认证时候用到的变量,和认证回显的变量。我们可以修改此处来去除特征。

`json:"version"修改为json:"a"来逃避流量识别。

同时在这个go文件里还有其他的结构体。可以用同样的方法进行修改。

也可以修改字段的值,比如

1
Version      string            `json:"version"`

version这个变量的值。在Goland里跟进到这个变量被利用到的文件client/service.go

1
2
3
4
5
6
7
8
9
10
loginMsg := &msg.Login{
Arch: runtime.GOARCH,
Os: runtime.GOOS,
PoolCount: svr.cfg.PoolCount,
User: svr.cfg.User,
Version: version.Full(),
Timestamp: time.Now().Unix(),
RunId: svr.runId,
Metas: svr.cfg.Metas,
}

再跟进version.Full()方法到utils\version\version.go

我们可以修改version变量的值即可,这个变量是frp的版本号。

1
2
3
4
5
var version string = "0.33.0"

func Full() string {
return version
}

另外变量值的修改可以通过同样的方法修改。

frp优化

最近在吐司看到有师傅发了frp的讨论帖子,其中说到几个点挺好的。这里简单的记录一下。

通过tls和算法加密frp流量

加密与压缩
1
2
3
4
5
6
7
# frpc.ini
[ssh]
type = tcp
local_port = 22
remote_port = 6000
use_encryption = true
use_compression = true

如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 ssh 协议等,通过设置 use_encryption = true,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截。

如果传输的报文长度较长,通过设置 use_compression = true 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。

TLS

从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输。通过在 frpc.ini 的 common 中配置 tls_enable = true 来启用此功能,安全性更高。
为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17。
通过将 frps.ini 的 [common] 中 tls_only 设置为 true,可以强制 frps 只接受 TLS 连接。

注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。

frpc.ini写入源码

修改文件cmd\frpc\sub\root.go里的代码

把frpc.ini配置内容写到上面定义里。

1
2
3
4
5
6
7
8
9
[common]
server_addr=1.1.1.1
server_port=2333
privilege_token = pentest
tls_enable = true
[http_proxy]
type = tcp
remote_port = 23333
plugin = socks5

结果如下:

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
var (
cfgFile string
showVersion bool
fileContent string = `[common]
server_addr = 1.1.1.1
server_port = 2333
privilege_token = pentest
tls_enable = true
[http_proxy]
type = tcp
remote_port = 23333
plugin = socks5
`
serverAddr string
user string
protocol string
token string
logLevel string
logFile string
logMaxDays int
disableLogColor bool

proxyName string
localIp string
localPort int
remotePort int
useEncryption bool
useCompression bool
customDomains string
subDomain string
httpUser string
httpPwd string
locations string
hostHeaderRewrite string
role string
sk string
multiplexer string
serverName string
bindAddr string
bindPort int

kcpDoneCh chan struct{}
)

修改代码:cmd/frpc/sub/status.go

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
var statusCmd = &cobra.Command{
Use: "status",
Short: "Overview of all proxies status",
RunE: func(cmd *cobra.Command, args []string) error {
//iniContent, err := config.GetRenderedConfFromFile(cfgFile)
iniContent:= fileContent
/*if err != nil {
fmt.Println(err)
os.Exit(1)
}*/

clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

err = status(clientCfg)
if err != nil {
fmt.Printf("frpc get status error: %v\n", err)
os.Exit(1)
}
return nil
},
}

修改代码:cmd/frpc/sub/reload.go

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
var reloadCmd = &cobra.Command{
Use: "reload",
Short: "Hot-Reload frpc configuration",
RunE: func(cmd *cobra.Command, args []string) error {
//iniContent, err := config.GetRenderedConfFromFile(cfgFile)
iniContent:= fileContent

/*if err != 0 {
fmt.Println(err)
os.Exit(1)
}*/

clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

err = reload(clientCfg)
if err != nil {
fmt.Printf("frpc reload error: %v\n", err)
os.Exit(1)
}
fmt.Printf("reload success\n")
return nil
},
}

修改代码:cmd/frpc/sub/root.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func runClient(cfgFilePath string) (err error) {
var content string
//content, err = config.GetRenderedConfFromFile(cfgFilePath)
content, err = fileContent, nil
if err != nil {
return
}

cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
if err != nil {
return
}

pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
if err != nil {
return err
}

err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
return
}

运行效果如下:

1592706307411

frpc.ini的ip通过参数传入

Golang语法规则:

  • golang的命名推荐使用驼峰命名法,必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。

  • golang中根据首字母的大小写来确定可以访问的权限。无论是方法名、常量、变量名还是结构体的名称,如果首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包中使用

不用上面的直接增加定义的方法,在cmd/frpc/sub/root.go写一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
func getFileContent(ip string,port string) {
var content string = `[common]
server_addr = ` + ip + `
server_port = ` + port + `
tls_enable = true
privilege_token = pentest999
[http_proxy]
type = tcp
remote_port = 23333
plugin = socks5
`
fileContent = content
}

然后在cmd\frpc\root.go中定义传参

1
2
3
4
5
6
7
func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
rootCmd.PersistentFlags().StringVarP(&ip, "server_addr", "t", "", "server_addr")
rootCmd.PersistentFlags().StringVarP(&port, "server_port", "p", "", "server_port")
kcpDoneCh = make(chan struct{})
}

修改runClient函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func runClient(cfgFilePath string,ip string,port string) (err error) {
var content string
getFileContent(ip,port)
//scontent, err = config.GetRenderedConfFromFile(cfgFilePath)
content, err = fileContent, nil
if err != nil {
return
}

cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
if err != nil {
return
}

pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
if err != nil {
return err
}

err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
return
}

在如下代码中调用runClient()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var rootCmd = &cobra.Command{
Use: "frpc",
Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
RunE: func(cmd *cobra.Command, args []string) error {
if showVersion {
fmt.Println(version.Full())
return nil
}
// Do not show command usage here.
err := runClient(cfgFile,ip,port)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}

修改完上面的代码,就可以用

1
frpc.exe -t 1.1.1.1 -p 2333

来启动frpc.exe了。

这种在Windows下不会暴露远程ip,但是在linux下还是会暴露的。

所以可以直接加一些加密啥的,-t参数传入ip加密后的地址,然后在源码里加一个解密的步骤即可。

同样也可以将ip地址以十进制或十六进制传入,然后在程序里转换会1.1.1.1格式。

运行效果如下:

1592706410793

编译打包

frp整个项目编译打包其实也挺简单的,首先保证go和gcc环境都安装好。

然后修改一下目录下带的package.sh

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
# compile for version
make
if [ $? -ne 0 ]; then
echo "make error"
exit 1
fi

frp_version=`./bin/frps --version`
echo "build version: $frp_version"

# cross_compiles
make -f ./Makefile.cross-compiles

rm -rf ./release/packages
mkdir -p ./release/packages

os_all='linux windows darwin freebsd'
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle'

cd ./release

for os in $os_all; do
for arch in $arch_all; do
frp_dir_name="frp_${frp_version}_${os}_${arch}"
frp_path="./packages/frp_${frp_version}_${os}_${arch}"
cd ..
rm -rf ${frp_path}
done
done

cd -

然后运行这个bash脚本,就可以交叉编译得到各种操作系统环境下的应用程序了。

Github

没有环境或无法正常编译可直接到github下载

https://github.com/uknowsec/frpModify

结语

通读了一下frpc读取配置文件和认证过程两部分的代码,然后再了解了cobra库的用法,实现简单的去特征,frpc无参和传参版的改造。后面的目标会继续研究frpc减小体积部分的改造。