前言
该 IDEA 源于一次外网打入内网后做代理时的灵感,原版 FRP 只能本地加载配置文件,故联想到可不可以“魔改” FRP 使其远程加载配置文件以及实现其它操作,遂事后去搜索借鉴其它大佬的思路以及教程,对 FRP 进行改造,使其更加契合红队做代理时的场景。
本文是基于最新版 FRP 0.48.0 版本的改造。话不多说,下面教程开始!
改造教程
基于项目源码修改,最好拥有简单的代码能力。
配置文件自动删除
原版 FRPC 只能基于本地文件加载运行,若后续不手动删除配置文件,则配置文件会遗留在目标机器上,后续可能被别人拿来分析并获取你的公网 IP 等,更甚者直接溯源到你本人,故运行 FRPC 时顺手删除配置文件是必要的。
功能点在 cmd/frpc/sub/root.go
文件中修改。原理是在 init
中注册参数,然后判断参数是否开启,开启就删除。代码如下:
package sub
import (
……
)
const (
……
)
var (
cfgFile string
……
bindPort int
tlsEnable bool
// 添加该行
delEnable bool
// END
)
func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
// 添加该行
rootCmd.PersistentFlags().BoolVarP(&delEnable, "delini", "d", false, "enable auto delete frpc.ini (only local files)")
// END
}
func RegisterCommonFlags(cmd *cobra.Command) {
……
}
var rootCmd = &cobra.Command{
……
}
func Execute() {
……
}
func handleSignal(svr *client.Service, doneCh chan struct{}) {
……
}
func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
……
}
func runClient(cfgFilePath string) error {
……
}
func startService(
……
) (err error) {
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
……
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
if errRet != nil {
err = errRet
return
}
// 添加该行
if delEnable == true {
err := os.Remove(cfgFile)
if err != nil {
return err
}
}
// END
closedDoneCh := make(chan struct{})
……
return
}
使用方法:指定本地配置文件时多带一个 -d
或 --delini
参数就行,如下图:
远程加载配置文件
使得配置文件可以从网络加载,此处可以将配置文件直接写入到 FRPC 源码中,但这样不利于后期修改,故舍弃该方式,加入从网络加载方式。
功能点在 pkg/config/value.go
文件中修改。指定配置文件的参数若是 http 请求,则对其进行请求加载,否则,从对应指定路径加载。此处只需将 GetRenderedConfFromFile
函数功能修改即可,修改为如下:
func GetRenderedConfFromFile(path string) (out []byte, err error) {
var b []byte
rawUrl := path
if strings.Contains(rawUrl, "http") {
log.Info("Remote load ini file")
response, _err1 := http.Get(path)
if _err1 != nil {
return
}
defer response.Body.Close()
body, _err := io.ReadAll(response.Body)
if _err != nil {
return
}
httpContent := string(body)
var content = []byte(httpContent)
out, err = RenderContent(content)
return
} else {
log.Info("Local load ini file")
b, err = os.ReadFile(path)
if err != nil {
return
}
localContent := string(b)
var content = []byte(localContent)
out, err = RenderContent(content)
return
}
}
功能演示如下图:
修改客户端使用Proxy的流量特征
正常情况下建议 FRP 服务端开启 tls_only = true
选项,同时 FRP 客户端配置文件中指定 tls_enable = true
选项以加密传输的流量,这样可以避免暴露你的服务器 IP
或本地 IP
,如果不想开启 TLS
选项的话,又不想暴露相关 IP
,那么可以对其进行如下修改以隐藏流量特征。
功能点在 server/proxy/proxy.go
文件中修改,将 GetWorkConnFromPool
函数内代码手动指定 IP 地址(IP 可随意修改),代码如下:
……
if src != nil {
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
// 追加如下
srcAddr = "127.0.0.1"
srcPort, _ = strconv.Atoi(srcPortStr)
}
if dst != nil {
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
// 追加如下
dstAddr = "127.0.0.1"
dstPort, _ = strconv.Atoi(dstPortStr)
}
……
上述问题建议在配置文件中启用 tls_enable
参数以规避。
TLS默认字节修改
从 v0.25.0 版本开始 FRPC 和 FRPS 之间支持通过 TLS 协议加密传输,为了端口复用,FRP 建立 TLS 连接的第一个字节为 0x17
,有些流量识别程序识别到特定流量,可能会阻拦,故我们可以对其进行修改。
功能点在 pkg/util/net/tls.go
文件中修改,如下:
……
// var FRPTLSHeadByte = 0x17
// 修改为下方,可自定义其它
var FRPTLSHeadByte = 0x88
func CheckAndEnableTLSServerConnWithTimeout(
c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration,
) (out net.Conn, isTLS bool, custom bool, err error) {
sc, r := gnet.NewSharedConnSize(c, 2)
buf := make([]byte, 1)
var n int
_ = c.SetReadDeadline(time.Now().Add(timeout))
n, err = r.Read(buf)
_ = c.SetReadDeadline(time.Time{})
if err != nil {
return
}
……
该方式在新版本中可在客户端配置中加入 disable_custom_tls_first_byte = true
参数使得 TLS 不发送 0x17
。
命令方式开启socks5代理
下述方法使用命令方式开启 socks5
代理,以避免配置文件加载请求。
修改源码文件 cmd/frpc/sub/tcp.go
并添加相关参数,如下:
package sub
import (
……
)
func init() {
……
//添加这几行
tcpCmd.PersistentFlags().StringVarP(&pluginName, "plugin", "", "", "plugins")
tcpCmd.PersistentFlags().StringVarP(&pluginUser, "plugin_user", "", "", "plugins user name")
tcpCmd.PersistentFlags().StringVarP(&pluginPass, "plugin_pass", "", "", "plguins user password")
//END
rootCmd.AddCommand(tcpCmd)
}
var tcpCmd = &cobra.Command{
……
cfg.UseEncryption = useEncryption
cfg.UseCompression = useCompression
//添加这几行
cfg.Plugin = pluginName
if cfg.Plugin != "" {
cfg.PluginParams = make(map[string]string)
cfg.PluginParams["plugin_user"] = pluginUser
cfg.PluginParams["plugin_passwd"] = pluginPass
}
//END
……
}
再修改 cmd/frpc/sub/root.go
文件,增加如下参数:
var (
cfgFile string
……
bindPort int
tlsEnable bool
//添加这几行
pluginName string
pluginUser string
pluginPass string
//END
)
最后使用方式如下:
./frpc tcp -s [IP]:[PORT] -r [远端端口] -t [Token] --plugin socks5 --plugin_user [鉴权用户] --plugin_pass [鉴权密码] -n [代理名称]
如图所示:
此方法建议在 Windows 下使用,Linux 使用可以看到完整命令参数。