前言 frp无疑是众多代理工具中,用得最舒服的了。但是他还是存在几个缺点的。
.ini配置文件泄露服务器信息。
非TLS特征明显
golang编译打包后的体积过大
一直想改来着,但是一直拖着。前两天看到了吐司大佬发的frp讨论帖,于是下定决心改一下。
这里主要是对上面提到的前两个缺点进行改造,体积过大问题需要去整体的修改,减少他用到的一些依赖库和无用功能。
流量特征(非TLS) 在没有配置tls_enable = true
frpc在连接认证frps的时候回把frp的版本信息等等发给frps进行认证。
目前一些流量设备就通过这个特征来识别frp代理。
如上图,可以看到有如下几个字段值: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"` 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:= fileContent 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:= fileContent 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 = 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 }
运行效果如下:
frpc.ini的ip通过参数传入 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) 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 } 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
格式。
运行效果如下:
编译打包 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 make if [ $? -ne 0 ]; then echo "make error" exit 1 fi frp_version=`./bin/frps --version` echo "build version: $frp_version " 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 ./releasefor 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减小体积部分的改造。