在二开之前我们先认识一下FRP的使用
一、基础配置
(1)旧版0.48
该配置拿0.48版本的frp来做讲解,新版本的改为了toml配置,toml相比于原来的配置格式来说改动比较大的,本次二开以0.48旧版本ini配置为主
frpc.ini配置如下
[common]
tls_enable = true#tls加密更加安全
server_addr = 10.225.91.15#这里填服务端地址
server_port = 9999#这里填frp程序端口
protocol = kcp#使用kcp协议与C2互连
[sock]
type = tcp
plugin = socks5
remote_port = 27777
plugin_user = ldh
plugin_passwd = ldh
use_encryption = true
use_compression = true
如果是单个端口映射则使用下面配置,不要使用kcp协议
[common]
tls_enable = true
server_addr = 1xx.xx.2xx.23x
server_port = 38412
token = xxx
[10571]
type = tcp
local_ip = 192.168.2.211
local_port = 10571
remote_port = 10571
use_encryption = true
use_compression = true
frps.ini配置参数如下:
[common]
bind_port = 9999#这里是绑定开辟的端口,与客户端一致
kcp_bind_port = 9999#这里是将开辟的端口使用kcp协议,因为客户端也使用kcp协议
token = 8412666
dashboard_port = 10005#这里是配置网站端口,服务端启动后访问
dashboard_user = admin
dashboard_pwd = admin
log_file = ./frps.log#保存记录文件
log_level = info
allow_ports = 1-65535
max_ports_per_client = 0
启动命令:
frpc -c frpc.ini
(2)新版0.58
个人不太喜欢使用0.52以后的版本,遂抛弃它
frp0.52后仅支持toml、yaml和json格式配置,抛弃了曾经的ini配置格式,这里主要讲解toml格式
frpc.ini配置参数如下:
serverAddr = "11.11.11.3"
serverPort = 7001
[[proxies]]
name = "socks穿透"
type = "tcp"#隧道协议类型
remotePort = 6028
[proxies.plugin]
transport.tls.enable = true#启用tls加密,从 v0.50.0版本开始,transport.tls.enable的默认值为 true
type = "socks5"#类型为socks5
transport.useEncryption = true #传输加密,加密算法采用 aes-128-cfb
transport.useCompression = true #传输压缩,压缩算法采用 snappy
frps.ini配置参数如下:
bindPort = 7000#服务端与客户端通信端口
auth.method ="token"#不写默认为token验证方式
auth.token = "public"#相当于旧版的token
webServer.addr = "0.0.0.0"#后台管理地址
webServer.port = 7500#后台管理端口
webServer.user = "admin"#后台登录用户名
webServer.password = "admin"#后台登录密码
启动命令无太大变化
frpc -c frpc.toml
但是要注意一点,toml格式为什么我用不惯呢?除了布尔和数字类型的值,都得打上双引号,否则会报错error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type v1.ClientConfig
由于我喜欢的几个参数都被剔除了(socks的账号密码,当然也可能是我没找到),官方和搜索引擎都找不到对应的参数,遂放弃研究0.52之后版本,此次大更血对开发者比较友好,对搞渗透的人来说其实不太友好
二、隐藏特征
参考文章:
https://www.freebuf.com/sectool/332824.html
(1)敏感字符隐藏
当客户端执行Frpc,当Frpc在向Frps建立TCP握手时,第一次握手的数据会带上非常明显的特征,FRP版本、系统架构、密钥、IP、甚至代理socks的用户名和密码都一览无余
因此要隐藏版本号(/pkg/util/version/version.go),随便修改,因为它不会影响任何功能,当然如果开启tls这个修改可以略过
还有/pkg/msg/msg.go中的敏感字符也改掉
(2)TLS加密与特征消除
如果frp没有启用tls_enable=true则交互都是明文,同时也推荐开启下面两个加密和压缩的功能
# frpc.ini
[ssh]
tls_enable=true
type = tcp
local_port = 22
remote_port = 6000
use_encryption = true#开启加密
use_compression = true#对传输内容进行压缩,降低网络压力,但会增加CPU负担
(pkg/util/net/tls.go)开启TLS把头部字节0x17改成其他的十六进制数,frps也需要重新编译,否则frpc和frps的tls头字节对不上会导致报错
tls_enable = true
开启TLS后的结果
(3)深度剖析与二开
你是否想把frpc提取shellcode到内网做任何想做的事情?但是因为它本身需要搭配ini使用,非常麻烦,这里我二开的目的是为了:客户端隐藏配置文件+shellcode提取以便做免杀化处理
1)强制传参(抛弃)
一开始我想的是,就把所有server_addr、server_port、remote_port参数直接写死,但这里不推荐直接改,操作空间完全就不留余地,而且各个参数位置非常杂乱,找起来很麻烦,即使单独改了这些东西,也是需要有配置文件来触发后续的变量修改,还不如不改...
2)内部传递ini配置(抛弃)
跟进ParseClientConfig函数
继续跟进GetRenderedConfFromFile函数
打断点一路跟过来,可以看到这里的b就是读的frpc.ini内容,我们使用fmt打印试试(问我为什么不调试?因为环境有点问题)
成功打印出读取的内容,现在我们需要篡改b的内容
直接重新定义b变量的内容为ini内容,并删除文件读取函数,防止找不到文件触发异常
func GetRenderedConfFromFile(path string) (out []byte, err error) {
var b []byte
b = []byte(`[common]
tls_enable = true
server_addr = 1xx.xx.xxx.xx8
server_port = 48412
protocol = tcp
token = xxxxxx
[sock]
type = tcp
plugin = socks5
remote_port = 27777
plugin_user = ldh
plugin_passwd = ldh
use_encryption = true
use_compression = true`)
out, err = RenderContent(b)
return
}
此时文件已经写死了,我们将frpc.ini文件内容删除并执行frpc.exe,此时达到无配置执行
在这里我们可以把端口设置成10000-65535的随机端口,尽量不要搞10000以下,怕端口冲突,再把名字更改为主机IP
import (
"bytes"
"os"
"strings"
"text/template"
"math/rand"
"time"
"fmt"
"io/ioutil"
"net/http"
)
funcGetPublicIP() (string, error) {
resp, err := http.Get("https://api.ipify.org")
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
funcgetRandomPort(min, max int) int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(max-min+1) + min
}
funcGetRenderedConfFromFile(path string) (out []byte, err error) {
ip, err := GetPublicIP()
var b []byte
b = []byte(`[common]
tls_enable = true
server_addr = 1xx.3x.xxx.xxx
server_port = 48412
protocol = tcp
token = xxxxxx
[sock]
type = tcp
plugin = socks5
remote_port = 27777
plugin_user = xxxxxx
plugin_passwd = xxxxxx
use_encryption = true
use_compression = true`)
randomPort := getRandomPort(10000, 65535)
randomPortStr := fmt.Sprintf("%d", randomPort)
b = bytes.ReplaceAll(b, []byte("remote_port = 27777"), []byte("remote_port = "+randomPortStr))
b = bytes.ReplaceAll(b, []byte("[sock]"), []byte("[" + ip + "]"))
out, err = RenderContent(b)
return
}
有些时候IP可能会出错,就不检测IP了
import (
"bytes"
"os"
"strings"
"text/template"
"math/rand"
"time"
"fmt"
)
func getRandomPort(min, max int) int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(max-min+1) + min
}
func GetRenderedConfFromFile(path string) (out []byte, err error) {
var b []byte
b = []byte(`[common]
tls_enable = true
server_addr = 10x.xx.2xx.23x
server_port = 48412
protocol = tcp
token = xxxx
[sock]
type = tcp
plugin = socks5
remote_port = 27777
plugin_user = xxxxx
plugin_passwd = xxxxx
use_encryption = true
use_compression = true`)
randomPort := getRandomPort(10000, 65535)
randomPortStr := fmt.Sprintf("%d", randomPort)
b = bytes.ReplaceAll(b, []byte("remote_port = 27777"), []byte("remote_port = "+randomPortStr))
b = bytes.ReplaceAll(b, []byte("[sock]"), []byte("[" + randomPortStr + "]"))
out, err = RenderContent(b)
return
}
3)外部传参
温馨提示:frps我没有做外部传参修改,攻防期间往往都是拿客户端搞事的,服务端还是老老实实配ini吧,因为我懒!
frp执行的逻辑为:箭头中所指向的模块,如果我传参输入什么名字,它就会走到哪个模块中
在进入tcp.go、udp.go之类的模块条件是要传递模块名称,否则进入root.go,其中root.go内部也定义了一些参数
打断点一路跟过来发现这里强制要求传local_ip和local_port,众所周知socks端口的映射无需填写上面两个内容,从这里来看作者貌似把路给写死了,同时在root.go和tcp.go中并无plugin = socks5这个参数项
找了半天也不知道socks5的入口在哪里,只知道读ini配置文件可以加plugin=socks5这个参数,只能继续按断点走,先配置好对应的文件,因为目前传参是没法开启socks5的,只有传配置文件路径才行
在main.go中下断点
走到Execute,内部执行rootCmd
到rootCmd下断点,可以看到这里判断了是否传递配置文件ini的路径
最终走到runClient
在runClient下断
可以看到cfg和pxyCfgs包含了frpc.ini中的配置
接下来我们就需要对这里的内容进行写死操作
funcrunClient(cfgFilePath string) error {
cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath)
if err != nil {
return err
}
err = nil
cfg.ClientConfig.BaseConfig.AuthenticationMethod = "token"
cfg.ClientConfig.BaseConfig.AuthenticateHeartBeats = false
cfg.ClientConfig.BaseConfig.AuthenticateNewWorkConns = false
cfg.OidcClientConfig.OidcClientID = ""
cfg.OidcClientConfig.OidcClientSecret = ""
cfg.OidcClientConfig.OidcAudience = ""
cfg.OidcClientConfig.OidcScope = ""
cfg.OidcClientConfig.OidcTokenEndpointURL = ""
cfg.OidcClientConfig.OidcAdditionalEndpointParams = nil
cfg.ClientConfig.TokenConfig.Token = "xxx"//这里写token
cfg.ServerAddr = "10x.3x.xxx.xxx"//这里写IP
cfg.ServerPort = 38412//这里写端口
cfg.DialServerTimeout = 10
cfg.DialServerKeepAlive = 7200
cfg.ConnectServerLocalIP = ""
cfg.HTTPProxy = ""
cfg.LogFile = "console"
cfg.LogWay = "console"
cfg.LogLevel = "info"
cfg.LogMaxDays = 3
cfg.DisableLogColor = false
cfg.AdminAddr = "127.0.0.1"
cfg.AdminPort = 0
cfg.AdminUser = ""
cfg.AdminPwd = ""
cfg.AssetsDir = ""
cfg.PoolCount = 1
cfg.TCPMux = true
cfg.TCPMuxKeepaliveInterval = 60
cfg.User = ""
cfg.DNSServer = ""
cfg.LoginFailExit = true
cfg.Start = []string{}
cfg.Protocol = "tcp"
cfg.QUICKeepalivePeriod = 10
cfg.QUICMaxIdleTimeout = 30
cfg.QUICMaxIncomingStreams = 100000
cfg.TLSEnable = true
cfg.TLSCertFile = ""
cfg.TLSKeyFile = ""
cfg.TLSTrustedCaFile = ""
cfg.TLSServerName = ""
cfg.DisableCustomTLSFirstByte = false
cfg.HeartbeatInterval = 30
cfg.HeartbeatTimeout = 90
cfg.Metas = nil
cfg.UDPPacketSize = 1500
cfg.IncludeConfigFiles = []string{}
cfg.PprofEnable = false
//pxyCfgs := make(map[string]config.ProxyConf)
pxyCfgs["sock2"] = &config.TCPProxyConf{
BaseProxyConf: config.BaseProxyConf{
ProxyName: "sock2",
ProxyType: "tcp",
UseEncryption: true,
UseCompression: true,
Group: "",
GroupKey: "",
ProxyProtocolVersion: "",
//BandwidthLimit: config.BandwidthQuantity{s: "", i: 0},
BandwidthLimitMode: "client",
Metas: nil,
},
LocalSvrConf: config.LocalSvrConf{
LocalIP: "127.0.0.1",
LocalPort: 0,
Plugin: "socks5",
PluginParams: make(map[string]string), // 空的 map
},
HealthCheckConf: config.HealthCheckConf{
HealthCheckType: "",
HealthCheckTimeoutS: 0,
HealthCheckMaxFailed: 0,
HealthCheckIntervalS: 0,
HealthCheckURL: "",
HealthCheckAddr: "",
},
RemotePort: 27776, // 设置 RemotePort
}
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
}
但这里存在一个问题,cfg、pxyCfgs都是ParseClientConfig函数返回的结构体,在外部必须先被函数内部定义才能进行赋值,所以我们只能把cfg、pxyCfgs的值写死在函数内部,在内部中返回写死的值,或者把结构体搬到root.go中,但是我疏忽了一点!cfg和pxyCfgs结构体最终是被放进startService函数启动的!我们完全可以在startService函数中定义所有配置参数
funcstartService(
cfg config.ClientCommonConf,
pxyCfgs map[string]config.ProxyConf,
visitorCfgs map[string]config.VisitorConf,
cfgFile string,
) (err error) {
cfg.ClientConfig.BaseConfig.AuthenticationMethod = "token"
cfg.ClientConfig.BaseConfig.AuthenticateHeartBeats = false
cfg.ClientConfig.BaseConfig.AuthenticateNewWorkConns = false
cfg.OidcClientConfig.OidcClientID = ""
cfg.OidcClientConfig.OidcClientSecret = ""
cfg.OidcClientConfig.OidcAudience = ""
cfg.OidcClientConfig.OidcScope = ""
cfg.OidcClientConfig.OidcTokenEndpointURL = ""
cfg.OidcClientConfig.OidcAdditionalEndpointParams = nil
cfg.ClientConfig.TokenConfig.Token = "xxx"//这里放token
cfg.ServerAddr = "10x.xx.xxx.xxx"//这里放ip
cfg.ServerPort = 38412//这里放端口
cfg.DialServerTimeout = 10
cfg.DialServerKeepAlive = 7200
cfg.ConnectServerLocalIP = ""
cfg.HTTPProxy = ""
cfg.LogFile = "console"
cfg.LogWay = "console"
cfg.LogLevel = "info"
cfg.LogMaxDays = 3
cfg.DisableLogColor = false
cfg.AdminAddr = "127.0.0.1"
cfg.AdminPort = 0
cfg.AdminUser = ""
cfg.AdminPwd = ""
cfg.AssetsDir = ""
cfg.PoolCount = 1
cfg.TCPMux = true
cfg.TCPMuxKeepaliveInterval = 60
cfg.User = ""
cfg.DNSServer = ""
cfg.LoginFailExit = true
cfg.Start = []string{}
cfg.Protocol = "tcp"
cfg.QUICKeepalivePeriod = 10
cfg.QUICMaxIdleTimeout = 30
cfg.QUICMaxIncomingStreams = 100000
cfg.TLSEnable = true
cfg.TLSCertFile = ""
cfg.TLSKeyFile = ""
cfg.TLSTrustedCaFile = ""
cfg.TLSServerName = ""
cfg.DisableCustomTLSFirstByte = false
cfg.HeartbeatInterval = 30
cfg.HeartbeatTimeout = 90
cfg.Metas = nil
cfg.UDPPacketSize = 1500
cfg.IncludeConfigFiles = []string{}
cfg.PprofEnable = false
//pxyCfgs := make(map[string]config.ProxyConf)
pxyCfgs["sock2"] = &config.TCPProxyConf{
BaseProxyConf: config.BaseProxyConf{
ProxyName: "sock2",
ProxyType: "tcp",
UseEncryption: true,
UseCompression: true,
Group: "",
GroupKey: "",
ProxyProtocolVersion: "",
//BandwidthLimit: config.BandwidthQuantity{s: "", i: 0},
BandwidthLimitMode: "client",
Metas: nil,
},
LocalSvrConf: config.LocalSvrConf{
LocalIP: "127.0.0.1",
LocalPort: 0,
Plugin: "socks5",
PluginParams: make(map[string]string), // 空的 map
},
HealthCheckConf: config.HealthCheckConf{
HealthCheckType: "",
HealthCheckTimeoutS: 0,
HealthCheckMaxFailed: 0,
HealthCheckIntervalS: 0,
HealthCheckURL: "",
HealthCheckAddr: "",
},
RemotePort: 27776, // 设置 RemotePort
}
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
cfg.LogMaxDays, cfg.DisableLogColor)
if cfgFile != "" {
log.Trace("start frpc service for config file [%s]", cfgFile)
defer log.Trace("frpc service for config file [%s] stopped", cfgFile)
}
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
if errRet != nil {
err = errRet
return
}
closedDoneCh := make(chan struct{})
shouldGracefulClose := cfg.Protocol == "kcp" || cfg.Protocol == "quic"
// Capture the exit signal if we use kcp or quic.
if shouldGracefulClose {
go handleSignal(svr, closedDoneCh)
}
err = svr.Run()
if err == nil && shouldGracefulClose {
<-closedDoneCh
}
return
}
接下来就是让frpc明白一个道理!即使没有frpc.ini,你也要自己生成一个模板
在前面内部传参中提到过,GetRenderedConfFromFile函数内部读取了ini文件的内容,返回给了content,我们可以把err!=nil注释,把content内容设置为非空,这样就弥补了作者没有预留socks5外部传参的遗憾了!
接下来就可以直接运行了,跟内部传递ini配置比较像,只不过换到外部来定义content了
接下来我们的所有socks配置都可以在root.go中随意设置了,再也不用像前面一样还要用占位符和replace去更改变量,那样很low又很奇怪
接下来就是解决传参问题了,上半部分init才是真正的传参,下半部分的传参必须要带上协议名称,例如frpc.exe tcp -s这种才行,直接输入frpc.exe -s是不行的,默认是在init中接收参数
后续你会发现不仅加载了ini中设置的默认模板内容,还加载了我们传参的配置,不管那么多原因了,我们打断点看cfg的格式
此时我们需要将我们写的默认值改掉,不要原封不动的去再次映射定义pxyCfgs,因为它的结构属性非常复杂,一个个手动赋值比较麻烦。
注意真正的socks密码藏在了BaseProxyConf.LocalSvrConf.PluginParams,而不是LocalSvrConf.PluginParams,这里耗费了我半天时间!!!
这里是假的密码,不知道作用是什么,没空研究
pxyCfgs[Name3] = &config.TCPProxyConf{
BaseProxyConf: config.BaseProxyConf{
ProxyName: Name3,
ProxyType: "tcp",
UseEncryption: true,
UseCompression: true,
Group: "",
GroupKey: "",
ProxyProtocolVersion: "",
//BandwidthLimit: config.BandwidthQuantity{s: "", i: 0},
BandwidthLimitMode: "client",
Metas: nil,
},
LocalSvrConf: config.LocalSvrConf{
LocalIP: "127.0.0.1",
LocalPort: 0,
Plugin: "socks5",
PluginParams: make(map[string]string), // 空的 map
},
HealthCheckConf: config.HealthCheckConf{
HealthCheckType: "",
HealthCheckTimeoutS: 0,
HealthCheckMaxFailed: 0,
HealthCheckIntervalS: 0,
HealthCheckURL: "",
HealthCheckAddr: "",
},
RemotePort: 27776, // 设置 RemotePort
}
正确代码如下,修改map,而不是创建一个新的pxyCfgs,同时要注意一点,pxyCfgs的键名与proxyName必须一致,如果光改动proxyName不改键值会提示找不到该配置
if tcpConf, ok := pxyCfgs["socks5--ban"].(*config.TCPProxyConf); ok {
if tcpConf.LocalSvrConf.PluginParams == nil {
tcpConf.LocalSvrConf.PluginParams = make(map[string]string)
}
tcpConf.BaseProxyConf.LocalSvrConf.PluginParams["plugin_user"] = userAccount3
tcpConf.BaseProxyConf.LocalSvrConf.PluginParams["plugin_passwd"] = userPassword3
tcpConf.BaseProxyConf.ProxyName = Name3
tcpConf.RemotePort = remotePort3
} else {
fmt.Println("Failed to assert type to *config.TCPProxyConf")
}
newKey := Name3
if val, ok := pxyCfgs["socks5--ban"]; ok {
pxyCfgs[newKey] = val // 添加新键值对
delete(pxyCfgs, "socks5--ban") // 删除旧键
} else {
fmt.Println("Key 'socks5--ban' not found")
}
总结:
root.go
package sub
import (
"fmt"
"io/fs"
"net"
"os"
"os/signal"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
"github.com/spf13/cobra"
"github.com/fatedier/frp/client"
"github.com/fatedier/frp/pkg/auth"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/pkg/util/version"
)
const (
CfgFileTypeIni = iota
CfgFileTypeCmd
)
var (
cfgFile string
cfgDir string
showVersion bool
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
bandwidthLimit string
bandwidthLimitMode string
customDomains string
subDomain string
httpUser string
httpPwd string
locations string
hostHeaderRewrite string
role string
sk string
multiplexer string
serverName string
bindAddr string
bindPort int
tlsEnable bool
serverAddr3 string
serverPort3 int
Token3 string
Name3 string
userAccount3 string
userPassword3 string
remotePort3 int
)
funcinit() {
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().StringVarP(&serverAddr3, "server_Addr3", "i", "", "")
rootCmd.PersistentFlags().IntVarP(&serverPort3, "server_Port3", "p", 80, "")
rootCmd.PersistentFlags().StringVarP(&Token3, "Token3", "t", "null", "")
rootCmd.PersistentFlags().StringVarP(&Name3, "Name3", "n", "null", "")
rootCmd.PersistentFlags().StringVarP(&userAccount3, "userAccount3", "u", "admin", "")
rootCmd.PersistentFlags().StringVarP(&userPassword3, "userPassword3", "P", "admin!@#666", "")
rootCmd.PersistentFlags().IntVarP(&remotePort3, "remotePassword3", "r", 65532, "")
}
funcRegisterCommonFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
cmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
cmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
cmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
cmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
cmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls")
}
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
}
// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
// Note that it's only designed for testing. It's not guaranteed to be stable.
if cfgDir != "" {
var wg sync.WaitGroup
_ = filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() {
return nil
}
wg.Add(1)
time.Sleep(time.Millisecond)
go func() {
defer wg.Done()
err := runClient(path)
if err != nil {
fmt.Printf("frpc service error for config file [%s]n", path)
}
}()
return nil
})
wg.Wait()
return nil
}
// Do not show command usage here.
err := runClient(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
}
funcExecute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
funchandleSignal(svr *client.Service, doneCh chanstruct{}) {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
svr.GracefulClose(500 * time.Millisecond)
close(doneCh)
}
funcparseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
cfg = config.GetDefaultClientConf()
ipStr, portStr, err := net.SplitHostPort(serverAddr)
if err != nil {
err = fmt.Errorf("invalid server_addr: %v", err)
return
}
cfg.ServerAddr = ipStr
cfg.ServerPort, err = strconv.Atoi(portStr)
if err != nil {
err = fmt.Errorf("invalid server_addr: %v", err)
return
}
cfg.User = user
cfg.Protocol = protocol
cfg.LogLevel = logLevel
cfg.LogFile = logFile
cfg.LogMaxDays = int64(logMaxDays)
cfg.DisableLogColor = disableLogColor
// Only token authentication is supported in cmd mode
cfg.ClientConfig = auth.GetDefaultClientConf()
cfg.Token = token
cfg.TLSEnable = tlsEnable
cfg.Complete()
if err = cfg.Validate(); err != nil {
err = fmt.Errorf("parse config error: %v", err)
return
}
return
}
funcrunClient(cfgFilePath string) error {
cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath)
if err != nil {
return err
}
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
}
funcstartService(
cfg config.ClientCommonConf,
pxyCfgs map[string]config.ProxyConf,
visitorCfgs map[string]config.VisitorConf,
cfgFile string,
) (err error) {
cfg.ClientConfig.TokenConfig.Token = Token3
cfg.ServerAddr = serverAddr3
cfg.ServerPort = serverPort3
if tcpConf, ok := pxyCfgs["socks5--ban"].(*config.TCPProxyConf); ok {
if tcpConf.LocalSvrConf.PluginParams == nil {
tcpConf.LocalSvrConf.PluginParams = make(map[string]string)
}
tcpConf.BaseProxyConf.LocalSvrConf.PluginParams["plugin_user"] = userAccount3
tcpConf.BaseProxyConf.LocalSvrConf.PluginParams["plugin_passwd"] = userPassword3
tcpConf.BaseProxyConf.ProxyName = Name3
tcpConf.RemotePort = remotePort3
} else {
fmt.Println("Failed to assert type to *config.TCPProxyConf")
}
newKey := Name3
if val, ok := pxyCfgs["socks5--ban"]; ok {
pxyCfgs[newKey] = val // 添加新键值对
delete(pxyCfgs, "socks5--ban") // 删除旧键
} else {
fmt.Println("Key 'socks5--ban' not found")
}
log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
cfg.LogMaxDays, cfg.DisableLogColor)
if cfgFile != "" {
log.Trace("start frpc service for config file [%s]", cfgFile)
defer log.Trace("frpc service for config file [%s] stopped", cfgFile)
}
svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
if errRet != nil {
err = errRet
return
}
closedDoneCh := make(chan struct{})
shouldGracefulClose := cfg.Protocol == "kcp" || cfg.Protocol == "quic"
// Capture the exit signal if we use kcp or quic.
if shouldGracefulClose {
go handleSignal(svr, closedDoneCh)
}
err = svr.Run()
if err == nil && shouldGracefulClose {
<-closedDoneCh
}
return
}
parse.go
package config
import (
"bytes"
"fmt"
"os"
"path/filepath"
)
funcParseClientConfig(filePath string) (
cfg ClientCommonConf,
pxyCfgs map[string]ProxyConf,
visitorCfgs map[string]VisitorConf,
err error,
) {
var content []byte
//content, err = GetRenderedConfFromFile(filePath)
//if err != nil {
//return
//}
content = []byte(`[common]
tls_enable = true
server_addr = 1.1.1.1
server_port = 11111
protocol = tcp
token = 12345
[socks5--ban]
type = tcp
plugin = socks5
remote_port = 12345
plugin_user = 12345
plugin_passwd = 12345
use_encryption = true
use_compression = true`)
configBuffer := bytes.NewBuffer(nil)
configBuffer.Write(content)
// Parse common section.
cfg, err = UnmarshalClientConfFromIni(content)
if err != nil {
return
}
cfg.Complete()
if err = cfg.Validate(); err != nil {
err = fmt.Errorf("parse config error: %v", err)
return
}
// Aggregate proxy configs from include files.
var buf []byte
buf, err = getIncludeContents(cfg.IncludeConfigFiles)
if err != nil {
err = fmt.Errorf("getIncludeContents error: %v", err)
return
}
configBuffer.WriteString("n")
configBuffer.Write(buf)
// Parse all proxy and visitor configs.
pxyCfgs, visitorCfgs, err = LoadAllProxyConfsFromIni(cfg.User, configBuffer.Bytes(), cfg.Start)
if err != nil {
return
}
return
}
// getIncludeContents renders all configs from paths.
// files format can be a single file path or directory or regex path.
funcgetIncludeContents(paths []string) ([]byte, error) {
out := bytes.NewBuffer(nil)
for _, path := range paths {
absDir, err := filepath.Abs(filepath.Dir(path))
if err != nil {
return nil, err
}
if _, err := os.Stat(absDir); os.IsNotExist(err) {
return nil, err
}
files, err := os.ReadDir(absDir)
if err != nil {
return nil, err
}
for _, fi := range files {
if fi.IsDir() {
continue
}
absFile := filepath.Join(absDir, fi.Name())
if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched {
tmpContent, err := GetRenderedConfFromFile(absFile)
if err != nil {
return nil, fmt.Errorf("render extra config %s error: %v", absFile, err)
}
out.Write(tmpContent)
out.WriteString("n")
}
}
}
return out.Bytes(), nil
}
(4)编译
修改完上面的几点以后就可以开始进行编译了
go build main.go
编译时github下载慢可以更换go代理
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn
frpc直接输入命令go build main.go一切正常
frps编译时报错如下,发现Execute函数存在于root.go中,该写法是对的,为什么go解释器认为该函数未定义提示undefined:Execute?
查询相关资料
https://blog.csdn.net/xiao_xiao_w/article/details/133840235
编译语句把main.go改为.代表所有模块
go build .
即可解决
如果想在windows上编译elf,则可以交叉编译
SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build main.go
上面的设置是暂时的,不用担心下次编译exe会一直编译elf的情况,所以不用担心还原的问题
(5)使用
假设我的服务端IP为8.8.8.8
./frps -c 你的配置.ini
frpc.exe -i 8.8.8.8 -p 38412 -t mssys666 -r 61111 -u mssys -P 123456
原文始发于微信公众号(奉天安全团队):记一次二开frp的思路
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论