下载地址:https://github.com/fatedier/frp
frp 是一个快速反向代理,可让您将位于 NAT 或防火墙后面的本地服务器暴露到互联网。它目前支持TCP和UDP,以及HTTP和HTTPS协议,允许通过域名将请求转发到内部服务。
随着功能逐渐增多,FRP也愈发臃肿,越来越不适用红队项目了,现在客户端已经达到了14M
,这是奔着产品去了。
红队项目需要短小精悍,体积小,只保留最核心的功能,其他能减则减。
所以学习一下FRP的优点,有机会开发出适合自己使用的工具。
先来熟悉一下源码
先来看一下服务端,先从cmd
目录开始,处理命令行,初始化配置文件
把配置文件传递给服务,并启动运行。
**server.NewService
**主要是把配置初始化给服务端的各个组件,并返回一个新的服务对象。那么新建服务的时候,都做了什么?
TLS
的配置Server
对象TCPMuxHTTPConnectPort
端口,新建tcpmux.NewHTTPConnectTCPMuxer
服务HTTPPlugins
,使用svr.pluginManager.Register()
初始化TCP
组控制器HTTP
组控制器TCPMux
组控制器TCP
监听KCP
监听QUICBindPort
设置,启动quic
监听SSHTunnelGateway
设置,启动SSHTunnelGateway
监听websocket
监听VhostHTTPPort
设置,启动http
反向代理VhostHTTPSPort
设置,启动https
反向代理tls
监听nat hole
服务之后调用自身的Run
方法启动运行。
根据参数多线程启动web GUI
之后启动一系列监听
同样的先处理配置,遵循命令行配置文件优先,没有的就使用默认。
之后开始启动客户端服务。
先调用client.NewService
初始化客户端服务
调用svr.Run
启动服务
WebServer
确定是否开启web端keepControllerWorking()
,保持代理一直处于工作状态keepControllerWorking
wait.BackoffUntil
:函数用于使用指数退避策略重复执行一个函数,直到满足某个条件或超时。它接受四个参数:
第一个参数:是一个匿名函数,也是重复执行的函数。
第二个参数:创建一个新的退避管理器实例,并指定相关选项。
第三个参数:一个布尔值,指示是否应立即执行退避,或在第一次失败后执行。
第四个参数:一个通道,当与服务器(
svr
)关联的上下文被取消时关闭,表示应该中止操作。
先根据配置初始化连接器,接着打开与服务器的底层连接,之后再生成数据流进行数据传输。
底层连接要么是TCP连接,要么是QUIC连接。
底层连接建立后,可以调用Connect()获取流。
如果未启用 TCPMux,则底层连接为零,每次调用 Connect() 时都会获得一个新的真实 TCP 连接。如果使用
Mux(多路复用)
,返回一个session
进行后续的数据交互
**QUIC协议:**QUIC 是一种建立在 UDP 之上的新型多路复用传输。
建立连接的时候,主要需要
TLS
websocket
、WSS
或者默认协议WebSocket
**设定协议:**WebSocket 是基于 TCP 的,所以协议被设置为 "tcp"。
**添加 WebSocket 钩子:**处理连接时将其升级为 WebSocket 连接。
**添加自定义 TLS 钩子:**根据配置进行 TLS 头部字节的处理(通常用于协议验证或调试)。
**配置 TLS:**将 TLS 配置应用到连接中。
WebSocket Secure (WSS
) 。wss
是 WebSocket 协议的加密版本,使用 TLS(类似于 HTTPS)。
设定协议: 使用 TCP 作为基础协议。
添加 TLS 配置钩子: 优先处理 TLS 配置,确保连接加密。
添加 WebSocket 钩子: 在 TLS 连接成功建立后,再处理 WebSocket 连接的升级。
建立连接这里也称为拨号(dial)
,如果设置了指定的拨号器(这里的自定义拨号器都是应用层协议),就使用对应的协议类型
如果没指定拨号器,默认使用TCP
或kcp
KCP是一种快速而可靠的协议,可以达到平均延迟降低30%~40%,最大延迟降低3倍的传输效果,但代价是比TCP多浪费10%~20%的带宽。
KCP 模式使用 UDP 作为底层传输。
如果前边的流程都没有问题,最终open()
把新生成的如下这样一个Session对象
赋值给连接上下文对象的muxSession
Connect 从底层连接返回一个流,如果未启用 TCPMux,则返回一个新的 TCP 连接。
为了安全性,客户端和服务端正常工作是需要认证的,先初始化登录数据结构
用前边生成的网络流进行客户端认证,成功后跟新客户端代理ID
登录成功之后,初始化客户端控制器
根据客户端上下文和Session
上下文初始化控制器
如果启用了加密,返回新的加密网络流调度器,未启用加密正常返回流调度器
加密算法如下,采用AES加密,key来自配置中的Token
接着注册消息处理程序,不同类型的消息由不同的处理器处理
传入发送调度器,生成消息发送器
生成新的代理管理器,指向控制器的pm
生成新的访客管理器,指向控制器的vm
开始工作
运行调度器,主要就是多线程启动发送池、接收池
更新配置
更新函数主要有两大块,一块是根据名字删除代理。另一块是添加新的代理,并运行检查。
del
Add
启动主要是启动工作检查和代理连通性检查
到这里就全部运行了。后续就是监控出问题后,结束控制器。
在通道控制器建立后,如果有数据,会通过调度器分配给对应的处理函数。直接定位handleReqWorkConn()
方法。
先获取一个网络流,接着初始化一个工作连接结构体,这个结构体主要是商量每个连接的认证信息。通过网络流把该结构体对象发送到服务端,并且接收成功认证连接后,服务端返回的开始信息。然后把代理名、网络流、开始信息当作参数,初始化工作连接句柄
在代理管理器中,根据名字获取对应的代理包装器
在代理包装器中,分配给对应类型的代理
默认使用TCP协议处理
还有其他几种如下:
接着来看TCP工作连接
的通用处理程序。
设置限制器
设置加密器
设置压缩器
构造插件信息
接着把插件信息传递给代理插件处理器
下边开始把处理两个网络流。生成本地网络流,远程网络流
先创建一个匿名函数,接收四个参数:
把数据从from
复制到to
。
然后创建两个多线程,调用该函数,to、from互换位置。这样随时在任意一端发送数据,同时不用等待即可发送。
到这里,数据交互的过程分析完了。但是好像又没分析完,感觉少点什么?通道也有了,数据交换也有了,少点什么那?插件好像没分析,插件是在那个步骤中起到作用了?想起来了吧,少了把数据写入通道的步骤。刚好这个步骤在插件中进行。
插件是从handle
调用开始的,里边有这么多。挑一个最常用的socks5
进行后续分析。
先把io读写器
和网络流包装到一块,调用ServeConn()
函数进一步处理。
在ServeConn()
函数中,先获取版本,对比是不是Socks5
接着创建一个新的请求对象,值从bufConn
读取器中获取。数据结构如下:
request := &Request{
Version: socks5Version,
Command: header[1],
DestAddr: dest,
bufConn: bufConn,
}
把新创建的requests
对象和conn
交给请求处理器处理
请求处理器handleRequest()
,先获取需要的目的地址格式
大家应该都知道,socks5
代理建立后,可以任意访问内网的主机。访问不同的主机就需要更换不同的目的地址。
有三个命令,实际作者公开源代码的版本只实现了建立连接命令,另外两个功能函数为空。这里建立连接命令就很好理解了,把代理使用者的请求数据发向目的地址。
在handleConnect()
函数中,会先用tcp协议
(socks5基于TCP)和目的地址建立连接。
启动两个线程,先把发过来的请求发送给目标机,再把目标机的响应发送给vps,这样就达到了高效数据交换的目的。
后边就是线程报错的相关处理了。至此SOCKS5代理分析完毕。
瘦身也就是减小编译后的程序的体积,先来分析一下影响体积的因素有哪些?
主要从一下几方面减少代码量:1、 去掉无用代码 2、去掉非必要功能
比如注释符、和一些说明,在我们红队使用过程中,都属于无用代码。
这些是客户端使用到的代码
手动一个一个删很显然浪费时间,写了个脚本批量删除
再次编译后,和源文件对比,确实小了,但小的不多。
前边源码分析的时候,就发现了很多用不到的功能和占位的代码。现在把他们一一去除,看一下效果。
从头开始,这个功能很明显用不到,这行代码,连同函数所在的文件,一块删除。
去掉目录方式读取配置文件
去掉web UI
去掉用不到的插件
再次编译,这次少了1M
本地未作任何更改的情况下,和GitHub下载的frpc.exe
做对比,大小也不一样。
前边几种方法下来,效果并不明显。这也跟语言特性有关系,go编译的本来就大,要想最大化缩小体积,用C++
应该是最好的。
不过这样较小代码量也不是没用,应该可以起到免杀的效果。
9 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!