STATEMENT
声明
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测及文章作者不为此承担任何责任。
雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
安恒西安运营能力中心
NO.1 socks5代理
关于使用 Golang 做一个常规的 socks5 代理的文章, 网上已经有很多, 所以本次来尝试一种特殊情况, 即在目标机器不出网的情况下, 使用 Golang 实现一个反向的 socks5 隧道。
NO.2 工具的基本原理
反向 socks5 与正向的 socks5 所不同的地方在于, socks5 服务端并不直接处于目标内网中, 而是通过 agent 维持的长连接来将代理流量传递到目标内网中。
如图, 因为有防火墙的存在,user 和 server 无法直接正向连接到 agent 所在的机器,因此需要让 agent 主动回连,在 server 上再做一次类似端口转发的工作。
agent 运行后, 主动向 server 端发起长连接, 每一个长连接将会承载一个socks5代理请求。
server 端监听两个端口, 其中一个是面向用户使用的socks5服务端口, 另一个是与 agent 通信的端口。
在运行之后, agent 会向 server 发起一个长连接, 而 server 端在接收 user 的 socks5 代理连接之后, 将这两个连接怼到一起就好了。
NO.3 代码实现
agent
agent 的实现其实非常简单, 这里使用了这个 socks5 的库(http://github.com/armon/go-socks5), 因为它有一个比较方便的ServeConn方法。在TCP连接建立之后,就将连接交给处理 socks5 协议的库去处理,如果对协议部分感兴趣的话,可以跟到这个 socks5 的库源码里面看一看,大概的步骤就是读取协议头,进行认证(如果有的话),然后从协议头中获取客户端的目标地址,由 server 端向目标发起连接,然后将客户端到 server 的连接和 server 到目标的连接对接起来。
package main
import (
"fmt"
"net"
"time"
"github.com/armon/go-socks5"
)
var server *socks5.Server
func main() {
// 起一个简单的socks5服务
var err error
server, err = socks5.New(&socks5.Config{})
if err != nil {
panic(err)
}
// 不断向server发起连接请求
for {
conn, err := net.Dial("tcp", "127.0.0.1:8989")
if err != nil {
continue
}
// 连接成功之后,使用socks5库处理该连接
go handleSocks5(conn)
}
}
func handleSocks5(conn net.Conn) {
defer conn.Close()
_ = conn.SetDeadline(time.Time{})
// 使用该socks5库提供的ServeConn方法
err := server.ServeConn(conn)
if err != nil {
fmt.Println(err)
}
}
server
server 这边的实现也很简单首先监听两个端口,一个供 user 连接使用,另一个供 agent 回连使用。
在 agent 成功回连之后,再取一条 user 的连接,调用 golang 的 io.Copy 方法,将两个连接的输入输出互相复制,即可将流量转发到 agent 进行处理。
package main
import (
"fmt"
"io"
"net"
"sync"
"time"
)
func main() {
// 使用两个channel来暂存agent和user的连接请求
userConnChan := make(chan net.Conn, 10)
agentConnChan := make(chan net.Conn, 10)
// 监听agent服务端口
go ListenService(agentConnChan, "127.0.0.1:8989")
// 监听user服务端口
go ListenService(userConnChan, "127.0.0.1:1080")
for agentConn := range agentConnChan {
userConn := <-userConnChan
go copyConn(userConn, agentConn)
}
}
func ListenService(c chan net.Conn, ListenAddress string) {
listener, err := net.Listen("tcp", ListenAddress)
if err != nil {
panic(err)
}
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println(err)
continue
}
c <- conn
}
}
func copyConn(srcConn, dstConn net.Conn) {
_ = srcConn.SetDeadline(time.Time{})
_ = dstConn.SetDeadline(time.Time{})
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
defer srcConn.Close()
defer dstConn.Close()
_, err := io.Copy(srcConn, dstConn)
if err != nil {
return
}
}()
go func() {
defer wg.Done()
defer dstConn.Close()
defer srcConn.Close()
_, err := io.Copy(dstConn, srcConn)
if err != nil {
return
}
}()
wg.Wait()
}
NO.4 效果图
NO.5 一些可以改进的地方
可以改进的地方其实有很多, 本文给出的 demo 只能算作一个可行性验证, 如果在真实环境中使用会遇到一些问题, 例如重度使用时不可避免的会遇到连接数过高的问题, socks5 虽然是一个轻量化的协议, 但是特征还是太过于明显, 并且因为 agent 引用了一个完整的 socks5 实现库, 体积必然不会小到哪里去。因此, 如果想要改进的话, 可以尝试由 server 来处理 socks5 的认证步骤, agent 和 server 之间采用自己实现的更轻量化的协议, 这样做可以:
· 带来更小的 agent 体积
· 复用 agent 和 server 的 TCP 连接来更精确的控制连接数
· agent 和 server 之间可以使用 UDP 来通信, 更加隐蔽
RECRUITMENT
招聘启事
END
长按识别二维码关注我们
文章评论