目录

  • 前言
  • 一、概述
  • 1、简介
  • 2、原理
  • 3、用法
  • (1)端口转发
  • (2)SOCKS代理
  • 二、实践
  • 1、测试场景
  • 2、端口转发
  • (1)服务端
  • (2)客户端
  • (3)隧道建立
  • (4)抓包看看
  • 3、SOCKS代理
  • (1)服务端
  • (2)客户端
  • (3)隧道建立
  • (4)抓包看看
  • 三、探索
  • 1、源码与分析
  • (1)main.go
  • (2)options.go
  • (3)parsecli.go
  • (4)context.go
  • (5)forward.go
  • (6)socks5.go
  • (7)fwd.go
  • (8)proxy.go
  • 2、检测与绕过
  • (1)特征字符串和特征码
  • (2)端口控制
  • (3)进程和库调用
  • (4)SOCKS代理的检测
  • 结语


前言

本文研究端口转发 & SOCKS代理工具的一个工具,iox

github:https://github.com/EddieIvan01/iox

一、概述

1、简介

最后更新于2020年,用Go编写,功能类似于lcx/ew,优化了网络逻辑,简化了使用方法

  • 支持跨平台
  • 支持TCP/UDP
  • 反向代理模式中使用TCP多路复用
  • 有流量加密功能

2、原理

就是端口转发和SOCKS代理,与lcx和EW的原理相仿

3、用法

#加密
./iox fwd -r 192.168.0.100:3389 -r *1.1.1.1:8888 -k 656565 #目标
./iox fwd -l *8888 -l 33890 -k 656565 #攻击机
# UDP -u
./iox fwd -l 53 -r *127.0.0.1:8888 -k 000102 -u
./iox fwd -l *8888 -l *9999 -k 000102 -u
./iox fwd -r *127.0.0.1:9999 -r 8.8.8.8:53 -k 000102 -u

(1)端口转发

#监听 0.0.0.0:8888 和0.0.0.0:9999,将两个连接间的流量转发
./iox fwd -l 8888 -l 9999
#监听0.0.0.0:8888,把流量转发到1.1.1.1:9999
./iox fwd -l 8888 -r 1.1.1.1:9999
#连接1.1.1.1:8888和1.1.1.1:9999, 在两个连接间转发
./iox fwd -r 1.1.1.1:8888 -r 1.1.1.1:9999

(2)SOCKS代理

#在本地 0.0.0.0:1080启动Socks5服务
./iox proxy -l 1080
#在被控机开启Socks5服务,将服务转发到公网VPS
#在VPS上转发0.0.0.0:9999到0.0.0.0:1080
#你必须将两条命令成对使用,因为它内部包含了一个简单的协议来控制回连
./iox proxy -r 1.1.1.1:9999
./iox proxy -l 9999 -l 1080      #注意,这两个端口是有顺序的
#接着连接内网主机
proxychains.conf
socks5://1.1.1.1:1080

$ proxychains rdesktop 192.168.0.100:3389

二、实践

1、测试场景

攻击机(服务端):kali 192.168.10.128
目标机(客户端):ubuntu 192.168.10.129

都没有限制TCP连接

2、端口转发

(1)服务端

./iox fwd -l *2222 -l 3333 -k 123456

docker proxychain docker proxychains iox 透传_UDP

(2)客户端

开启apache

docker proxychain docker proxychains iox 透传_内网渗透_02

./iox fwd -r 127.0.0.1:80 -r *192.168.10.128:2222 -k 123456

docker proxychain docker proxychains iox 透传_docker proxychain_03

(3)隧道建立

docker proxychain docker proxychains iox 透传_UDP_04


还可以nc、ssh等,根据端口来确定服务

(4)抓包看看

docker proxychain docker proxychains iox 透传_隧道_05


tcp三次握手建立连接,客户端向外发起连接的端口是60614和60618

docker proxychain docker proxychains iox 透传_UDP_06


心跳包

3、SOCKS代理

(1)服务端

修改/etc/proxychains.conf

socks5  0.0.0.0 1080

监听并映射端口

./iox proxy -l 2222 -l 1080

docker proxychain docker proxychains iox 透传_内网渗透_07

(2)客户端

./iox proxy -r 192.168.10.128:2222

docker proxychain docker proxychains iox 透传_内网渗透_08

(3)隧道建立

之后通过proxychains可以执行命令
如nmap扫描端口信息

proxychains4 nmap -p 1-1000 -Pn -sT 192.168.10.129

docker proxychain docker proxychains iox 透传_docker proxychain_09


docker proxychain docker proxychains iox 透传_安全_10

(4)抓包看看

tcp握手

docker proxychain docker proxychains iox 透传_UDP_11


nmap期间

docker proxychain docker proxychains iox 透传_docker proxychain_12

三、探索

1、源码与分析

(1)main.go

使用方法和调用相应模式

package main

import (
	"fmt"
	"iox/operate"
	"iox/option"
	"os"
)

const VERSION = "0.4"

func Usage() {
	fmt.Printf(
		"iox v%v\n"+
			"Usage: iox fwd/proxy [-l [*][HOST:]PORT] [-r [*]HOST:PORT] [-k HEX] [-t TIMEOUT] [-u] [-h] [-v]\n\n"+
			"Options:\n"+
			"  -l [*][HOST:]PORT\n"+
			"      address to listen on. `*` means encrypted socket\n"+
			"  -r [*]HOST:PORT\n"+
			"      remote host to connect, HOST can be IP or Domain. `*` means encrypted socket\n"+
			"  -k HEX\n"+
			"      hexadecimal format key, be used to generate Key and IV\n"+
			"  -u\n"+
			"      udp forward mode\n"+
			"  -t TIMEOUT\n"+
			"      set connection timeout(millisecond), default is 5000\n"+
			"  -v\n"+
			"      enable log output\n"+
			"  -h\n"+
			"      print usage then exit\n", VERSION,
	)
}

func main() {
	mode, submode, local, remote, lenc, renc, err := option.ParseCli(os.Args[1:])
	if err != nil {
		if err == option.PrintUsage {
			Usage()
		} else {
			fmt.Println(err.Error())
		}
		return
	}
	// 端口转发和代理两种模式
	switch mode {
	case "fwd":
		switch submode {
		case option.SUBMODE_L2R:
			operate.Local2Remote(local[0], remote[0], lenc[0], renc[0])
		case option.SUBMODE_L2L:
			operate.Local2Local(local[0], local[1], lenc[0], lenc[1])
		case option.SUBMODE_R2R:
			operate.Remote2Remote(remote[0], remote[1], renc[0], renc[1])
		}
	case "proxy":
		switch submode {
		case option.SUBMODE_LP:
			operate.ProxyLocal(local[0], lenc[0])
		case option.SUBMODE_RP:
			operate.ProxyRemote(remote[0], renc[0])
		case option.SUBMODE_RPL2L:
			operate.ProxyRemoteL2L(local[0], local[1], lenc[0], lenc[1])
		}
	}
}

(2)options.go

缺省值

package option

const (
	TCP_BUFFER_SIZE = 0x8000

	// UDP protocol's max capacity
	UDP_PACKET_MAX_SIZE = 0xFFFF - 28

	UDP_PACKET_CHANNEL_SIZE = 0x800

	CONNECTING_RETRY_DURATION = 1500

	SMUX_KEEPALIVE_INTERVAL = 20
	SMUX_KEEPALIVE_TIMEOUT  = 60
	SMUX_FRAMESIZE          = 0x8000
	SMUX_RECVBUFFER         = 0x400000
	SMUX_STREAMBUFFER       = 0x10000
)

var (
	TIMEOUT = 5000

	PROTOCOL = "TCP"

	// enable log output
	VERBOSE = false

	// logic optimization, changed in v0.1.1
	FORWARD_WITHOUT_DEC = false
)

(3)parsecli.go

读取输入参数

package option

import (
	"encoding/hex"
	"errors"
	"iox/crypto"
	"strconv"
)

var (
	errUnrecognizedMode    = errors.New("Unrecognized mode. Must choose a working mode in [fwd/proxy]")
	errHexDecodeError      = errors.New("KEY must be a hexadecimal string")
	PrintUsage             = errors.New("")
	errUnrecognizedSubMode = errors.New("Malformed args. Incorrect number of `-l/-r` params")
	errNoSecretKey         = errors.New("Encryption enabled, must specify a KEY by `-k` param")
	errNotANumber          = errors.New("Timeout param must be a number")
	errUDPMode             = errors.New("UDP mode only support fwd mode")
)

const (
	SUBMODE_L2L = iota
	SUBMODE_R2R
	SUBMODE_L2R

	SUBMODE_LP
	SUBMODE_RP
	SUBMODE_RPL2L
)

// Dont need flag-lib
func ParseCli(args []string) (
	mode string,
	submode int,
	local []string,
	remote []string,
	lenc []bool,
	renc []bool,
	err error) {

	if len(args) == 0 {
		err = PrintUsage
		return
	}

	mode = args[0]

	switch mode {
	case "fwd", "proxy":
	case "-h", "--help":
		err = PrintUsage
		return
	default:
		err = errUnrecognizedMode
		return
	}

	args = args[1:]
	ptr := 0

	for {
		if ptr == len(args) {
			break
		}

		switch args[ptr] {
		case "-l", "--local":
			l := args[ptr+1]
			if l[0] == '*' {
				lenc = append(lenc, true)
				l = l[1:]
			} else {
				lenc = append(lenc, false)
			}

			if _, err := strconv.Atoi(l); err == nil {
				local = append(local, "0.0.0.0:"+l) //默认监听0.0.0.0
			} else {
				if l[0] == ':' {
					local = append(local, "0.0.0.0"+l)
				} else {
					local = append(local, l)
				}
			}
			ptr++

		case "-r", "--remote":
			r := args[ptr+1]
			if r[0] == '*' {
				renc = append(renc, true)
				r = r[1:]
			} else {
				renc = append(renc, false)
			}

			remote = append(remote, r)
			ptr++

		case "-u", "--udp":
			PROTOCOL = "UDP"

		case "-k", "--key":
			var key []byte
			key, err = hex.DecodeString(args[ptr+1])
			if err != nil {
				err = errHexDecodeError
				return
			}
			crypto.ExpandKey(key)
			ptr++

		case "-t", "--timeout":
			TIMEOUT, err = strconv.Atoi(args[ptr+1])
			if err != nil {
				err = errNotANumber
				return
			}
			ptr++
		case "-v", "--verbose":
			VERBOSE = true
		case "-h", "--help":
			err = PrintUsage
			return
		}

		ptr++
	}

	if mode == "fwd" {
		switch {
		case len(local) == 0 && len(remote) == 2:
			submode = SUBMODE_R2R
		case len(local) == 1 && len(remote) == 1:
			submode = SUBMODE_L2R
		case len(local) == 2 && len(remote) == 0:
			submode = SUBMODE_L2L
		default:
			err = errUnrecognizedSubMode
			return
		}
	} else {
		switch {
		case len(local) == 0 && len(remote) == 1:
			submode = SUBMODE_RP
		case len(local) == 1 && len(remote) == 0:
			submode = SUBMODE_LP
		case len(local) == 2 && len(remote) == 0:
			submode = SUBMODE_RPL2L
		default:
			err = errUnrecognizedSubMode
			return
		}
	}

	if len(lenc) != len(local) || len(renc) != len(remote) {
		err = errUnrecognizedSubMode
		return
	}

	if crypto.SECRET_KEY == nil {
		for i, _ := range lenc {
			if lenc[i] {
				err = errNoSecretKey
				return
			}
		}

		for i, _ := range renc {
			if renc[i] {
				err = errNoSecretKey
				return
			}
		}
	}

	if PROTOCOL == "UDP" && mode == "proxy" {
		err = errUDPMode
		return
	}

	shouldFwdWithoutDec(lenc, renc)

	return
}

func shouldFwdWithoutDec(lenc []bool, renc []bool) {
	if len(lenc)+len(renc) != 2 {
		return
	}

	var result uint8
	for i, _ := range lenc {
		if lenc[i] {
			result++
		}
	}

	for i, _ := range renc {
		if renc[i] {
			result++
		}
	}

	if result == 2 {
		FORWARD_WITHOUT_DEC = true
	}
}

(4)context.go

TCP和UDP的信息的加密写和解密读

package netio

import (
	"iox/crypto"
	"iox/option"
	"net"
)

type Ctx interface {
	DecryptRead(b []byte) (int, error)
	EncryptWrite(b []byte) (int, error)

	net.Conn
}

var _ Ctx = &TCPCtx{}
var _ Ctx = &UDPCtx{}

type TCPCtx struct {
	net.Conn
	encrypted bool

	// Ensure stream cipher synchronous
	encCipher *crypto.Cipher
	decCipher *crypto.Cipher
}

func NewTCPCtx(conn net.Conn, encrypted bool) (*TCPCtx, error) {
	// if tc, ok := conn.(*net.TCPConn); ok {
	//     tc.SetLinger(0)
	// }

	encrypted = encrypted && !option.FORWARD_WITHOUT_DEC

	ctx := &TCPCtx{
		Conn:      conn,
		encrypted: encrypted,
	}

	if encrypted {
		encCipher, decCipher, err := crypto.NewCipherPair()
		if err != nil {
			return nil, err
		}

		ctx.encCipher = encCipher
		ctx.decCipher = decCipher
	}

	return ctx, nil
}

func (c *TCPCtx) DecryptRead(b []byte) (int, error) {
	n, err := c.Read(b)
	if err != nil {
		return n, err
	}

	if c.encrypted {
		c.decCipher.StreamXOR(b[:n], b[:n])
	}

	return n, err
}

func (c *TCPCtx) EncryptWrite(b []byte) (int, error) {
	if c.encrypted {
		c.encCipher.StreamXOR(b, b)
	}
	return c.Write(b)
}

type UDPCtx struct {
	*net.UDPConn
	encrypted  bool
	connected  bool
	remoteAddr *net.UDPAddr

	// sync.Mutex
}

func NewUDPCtx(conn *net.UDPConn, encrypted bool, connected bool) (*UDPCtx, error) {
	encrypted = encrypted && !option.FORWARD_WITHOUT_DEC

	ctx := &UDPCtx{
		UDPConn:   conn,
		encrypted: encrypted,
		connected: connected,
	}

	return ctx, nil
}

// Encryption for packet is different from stream
func (c *UDPCtx) DecryptRead(b []byte) (int, error) {
	var n int
	var err error

	if !c.connected {
		var remoteAddr *net.UDPAddr
		n, remoteAddr, err = c.ReadFromUDP(b)
		if err != nil {
			return n, err
		}
		c.remoteAddr = remoteAddr

	} else {
		n, err = c.Read(b)
		if err != nil {
			return n, err
		}
	}

	if c.encrypted {
		if len(b) < 0x18 {
			// no nonce, skip
			return 0, nil
		}
		nonce := b[n-0x18 : n]
		b = b[:n-0x18]

		cipher, err := crypto.NewCipher(nonce)
		if err != nil {
			return 0, err
		}

		n -= 0x18
		cipher.StreamXOR(b[:n], b[:n])
	}

	return n, err
}

func (c *UDPCtx) EncryptWrite(b []byte) (int, error) {
	if c.encrypted {
		iv, err := crypto.RandomNonce()
		cipher, err := crypto.NewCipher(iv)
		if err != nil {
			return 0, err
		}

		cipher.StreamXOR(b, b)
		b = append(b, iv...)
	}

	if !c.connected {
		return c.WriteTo(b, c.remoteAddr)
	}
	return c.Write(b)
}

/*
func (c UDPCtx) IsRemoteAddrRegistered() bool {
	return c.remoteAddr != nil
}
*/

(5)forward.go

读写功能,每个套接字只将数据包写入最近向其发送数据包的地址,而不是广播到所有地址

package netio

import (
	"io"
	"iox/logger"
	"iox/option"
)

func CipherCopy(dst Ctx, src Ctx) (int64, error) {
	buffer := make([]byte, option.TCP_BUFFER_SIZE)
	var written int64
	var err error

	for {
		var nr int
		var er error

		nr, er = src.DecryptRead(buffer)

		if nr > 0 {
			var nw int
			var ew error

			nw, ew = dst.EncryptWrite(buffer[:nr])

			if nw > 0 {
				logger.Info("<== [%d bytes] ==> ", nw)
				written += int64(nw)
			}
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = io.ErrShortWrite
				break
			}
		}
		if er != nil {
			if er != io.EOF {
				err = er
			}
			break
		}
	}

	return written, err
}

func PipeForward(ctxA Ctx, ctxB Ctx) {
	signal := make(chan struct{}, 1)

	go func() {
		CipherCopy(ctxA, ctxB)
		signal <- struct{}{}
	}()

	go func() {
		CipherCopy(ctxB, ctxA)
		signal <- struct{}{}
	}()

	<-signal
}

// This function will run forever
// If need to do performance optimization in future, I will consider a go-routine pool here,
// but it will introduce the mutex-lock overhead
func ForwardUDP(ctxA Ctx, ctxB Ctx) {
	go func() {
		buffer := make([]byte, option.UDP_PACKET_MAX_SIZE)
		for {
			nr, _ := ctxA.DecryptRead(buffer)
			if nr > 0 {
				if nr == 4 &&
					buffer[0] == 0xCC && buffer[1] == 0xDD &&
					buffer[2] == 0xEE && buffer[3] == 0xFF {
					continue
				}

				nw, _ := ctxB.EncryptWrite(buffer[:nr])
				if nw > 0 {
					logger.Info("<== [%d bytes] ==>", nw)
				}
			}
		}
	}()

	go func() {
		buffer := make([]byte, option.UDP_PACKET_MAX_SIZE)
		for {
			nr, _ := ctxB.DecryptRead(buffer)
			if nr > 0 {
				if nr == 4 &&
					buffer[0] == 0xCC && buffer[1] == 0xDD &&
					buffer[2] == 0xEE && buffer[3] == 0xFF {
					continue
				}

				nw, _ := ctxA.EncryptWrite(buffer[:nr])
				if nw > 0 {
					logger.Info("<== [%d bytes] ==>", nw)
				}
			}
		}
	}()

	select {}
}

var UDP_INIT_PACKET = []byte{
	0xCC, 0xDD, 0xEE, 0xFF,
}

// Each socket only writes the packet to the address which last sent packet to it recently,
// instead of broadcasting to all the address
func ForwardUnconnectedUDP(ctxA Ctx, ctxB Ctx) {
	addrRegistedA := false
	addrRegistedB := false
	addrRegistedSignalA := make(chan struct{})
	addrRegistedSignalB := make(chan struct{})

	packetChannelA := make(chan []byte, option.UDP_PACKET_CHANNEL_SIZE)
	packetChannelB := make(chan []byte, option.UDP_PACKET_CHANNEL_SIZE)

	// A read
	go func() {
		for {
			buffer := make([]byte, option.UDP_PACKET_MAX_SIZE)
			nr, _ := ctxA.DecryptRead(buffer)
			if nr > 0 {
				if !addrRegistedA {
					addrRegistedA = true
					addrRegistedSignalA <- struct{}{}
				}

				if !(nr == 4 &&
					buffer[0] == 0xCC && buffer[1] == 0xDD &&
					buffer[2] == 0xEE && buffer[3] == 0xFF) {
					packetChannelB <- buffer[:nr]
				}
			}
		}
	}()

	// B read
	go func() {
		for {
			buffer := make([]byte, option.UDP_PACKET_MAX_SIZE)
			nr, _ := ctxB.DecryptRead(buffer)
			if nr > 0 {
				if !addrRegistedB {
					addrRegistedB = true
					addrRegistedSignalB <- struct{}{}
				}

				if !(nr == 4 &&
					buffer[0] == 0xCC && buffer[1] == 0xDD &&
					buffer[2] == 0xEE && buffer[3] == 0xFF) {
					packetChannelA <- buffer[:nr]
				}
			}
		}
	}()

	// A write
	go func() {
		<-addrRegistedSignalA
		var n int
		for {
			packet := <-packetChannelA
			n, _ = ctxA.EncryptWrite(packet)
			if n > 0 {
				logger.Info("<== [%d bytes] ==>", n)
			}
		}
	}()

	// B write
	go func() {
		<-addrRegistedSignalB
		var n int
		for {
			packet := <-packetChannelB
			n, _ = ctxB.EncryptWrite(packet)
			if n > 0 {
				logger.Info("<== [%d bytes] ==>", n)
			}
		}
	}()

	select {}
}

(6)socks5.go

写socks5

// code from https://github.com/ring04h/s5.go
package socks5

import (
	"errors"
	"io"
	"iox/logger"
	"iox/netio"
	"iox/option"
	"net"
	"strconv"
	"time"
)

var (
	Commands = []string{"CONNECT", "BIND", "UDP ASSOCIATE"}
	AddrType = []string{"", "IPv4", "", "Domain", "IPv6"}
	Verbose  = false

	errAddrType      = errors.New("socks addr type not supported")
	errVer           = errors.New("socks version not supported")
	errMethod        = errors.New("socks only support noauth method")
	errAuthExtraData = errors.New("socks authentication get extra data")
	errReqExtraData  = errors.New("socks request get extra data")
	errCmd           = errors.New("socks only support connect command")
)

const (
	socksVer5       = 0x05
	socksCmdConnect = 0x01
)

func readAtLeast(r netio.Ctx, buf []byte, min int) (n int, err error) {
	if len(buf) < min {
		return 0, io.ErrShortBuffer
	}

	for n < min && err == nil {
		var nn int
		nn, err = r.DecryptRead(buf[n:])
		n += nn
	}
	if n >= min {
		err = nil
	} else if n > 0 && err == io.EOF {
		err = io.ErrUnexpectedEOF
	}
	return
}

func handShake(conn netio.Ctx) (err error) {
	const (
		idVer     = 0
		idNmethod = 1
	)

	buf := make([]byte, 258)

	var n int

	// make sure we get the nmethod field
	if n, err = readAtLeast(conn, buf, idNmethod+1); err != nil {
		return
	}

	if buf[idVer] != socksVer5 {
		return errVer
	}

	nmethod := int(buf[idNmethod]) //  client support auth mode
	msgLen := nmethod + 2          //  auth msg length
	if n == msgLen {               // handshake done, common case
		// do nothing, jump directly to send confirmation
	} else if n < msgLen { // has more methods to read, rare case
		if _, err = readAtLeast(conn, buf[n:msgLen], len(buf[n:msgLen])); err != nil {
			return
		}
	} else { // error, should not get extra data
		return errAuthExtraData
	}
	/*
	   X'00' NO AUTHENTICATION REQUIRED
	   X'01' GSSAPI
	   X'02' USERNAME/PASSWORD
	   X'03' to X'7F' IANA ASSIGNED
	   X'80' to X'FE' RESERVED FOR PRIVATE METHODS
	   X'FF' NO ACCEPTABLE METHODS
	*/
	// send confirmation: version 5, no authentication required
	_, err = conn.EncryptWrite([]byte{socksVer5, 0})

	return
}

func parseTarget(conn netio.Ctx) (host string, err error) {
	const (
		idVer   = 0
		idCmd   = 1
		idType  = 3 // address type index
		idIP0   = 4 // ip addres start index
		idDmLen = 4 // domain address length index
		idDm0   = 5 // domain address start index

		typeIPv4 = 1 // type is ipv4 address
		typeDm   = 3 // type is domain address
		typeIPv6 = 4 // type is ipv6 address

		lenIPv4   = 3 + 1 + net.IPv4len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv4 + 2port
		lenIPv6   = 3 + 1 + net.IPv6len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv6 + 2port
		lenDmBase = 3 + 1 + 1 + 2           // 3 + 1addrType + 1addrLen + 2port, plus addrLen
	)
	// refer to getRequest in server.go for why set buffer size to 263
	buf := make([]byte, 263)
	var n int

	// read till we get possible domain length field
	if n, err = readAtLeast(conn, buf, idDmLen+1); err != nil {
		return
	}

	// check version and cmd
	if buf[idVer] != socksVer5 {
		err = errVer
		return
	}

	/*
	   CONNECT X'01'
	   BIND X'02'
	   UDP ASSOCIATE X'03'
	*/

	if buf[idCmd] > 0x03 || buf[idCmd] == 0x00 {
		logger.Info("Unknown Command: %d", buf[idCmd])
	}

	if buf[idCmd] != socksCmdConnect { //  only support CONNECT mode
		err = errCmd
		return
	}

	// read target address
	reqLen := -1
	switch buf[idType] {
	case typeIPv4:
		reqLen = lenIPv4
	case typeIPv6:
		reqLen = lenIPv6
	case typeDm: // domain name
		reqLen = int(buf[idDmLen]) + lenDmBase
	default:
		err = errAddrType
		return
	}

	if n == reqLen {
		// common case, do nothing
	} else if n < reqLen { // rare case
		if _, err = readAtLeast(conn, buf[n:reqLen], len(buf[n:reqLen])); err != nil {
			return
		}
	} else {
		err = errReqExtraData
		return
	}

	switch buf[idType] {
	case typeIPv4:
		host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
	case typeIPv6:
		host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
	case typeDm:
		host = string(buf[idDm0 : idDm0+buf[idDmLen]])
	}
	port := bigEndianUint16(buf[reqLen-2 : reqLen])
	host = net.JoinHostPort(host, strconv.Itoa(int(port)))

	return
}

func bigEndianUint16(b []byte) uint16 {
	_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
	return uint16(b[1]) | uint16(b[0])<<8
}

func pipeWhenClose(conn netio.Ctx, target string) {
	remoteConn, err := net.DialTimeout(
		"tcp", target,
		time.Millisecond*time.Duration(option.TIMEOUT),
	)
	if err != nil {
		logger.Info("Connect remote :" + err.Error())
		return
	}
	defer remoteConn.Close()

	tcpAddr := remoteConn.LocalAddr().(*net.TCPAddr)
	if tcpAddr.Zone == "" {
		if tcpAddr.IP.Equal(tcpAddr.IP.To4()) {
			tcpAddr.Zone = "ip4"
		} else {
			tcpAddr.Zone = "ip6"
		}
	}

	rep := make([]byte, 256)
	rep[0] = 0x05
	rep[1] = 0x00 // success
	rep[2] = 0x00 //RSV

	//IP
	if tcpAddr.Zone == "ip6" {
		rep[3] = 0x04 //IPv6
	} else {
		rep[3] = 0x01 //IPv4
	}

	var ip net.IP
	if "ip6" == tcpAddr.Zone {
		ip = tcpAddr.IP.To16()
	} else {
		ip = tcpAddr.IP.To4()
	}
	pindex := 4
	for _, b := range ip {
		rep[pindex] = b
		pindex += 1
	}
	rep[pindex] = byte((tcpAddr.Port >> 8) & 0xff)
	rep[pindex+1] = byte(tcpAddr.Port & 0xff)

	conn.EncryptWrite(rep[0 : pindex+2])
	// Transfer data

	remoteConnCtx, err := netio.NewTCPCtx(remoteConn, false)
	if err != nil {
		logger.Info("Socks5 remote connect error: %s", err.Error())
		return
	}

	netio.PipeForward(conn, remoteConnCtx)
}

func HandleConnection(conn netio.Ctx) {
	if err := handShake(conn); err != nil {
		logger.Info("Socks5 handshake error: %s", err.Error())
		return
	}
	addr, err := parseTarget(conn)
	if err != nil {
		logger.Info("socks consult transfer mode or parse target: %s", err.Error())
		return
	}
	pipeWhenClose(conn, addr)
}

(7)fwd.go

TCP和UDP的三种端口转发

package operate

import (
	"iox/crypto"
	"iox/logger"
	"iox/netio"
	"iox/option"
	"net"
	"time"
)

func local2RemoteTCP(local string, remote string, lenc bool, renc bool) {
	listener, err := net.Listen("tcp", local)
	if err != nil {
		logger.Warn("Listen on %s error: %s", local, err.Error())
		return
	}
	defer listener.Close()

	for {
		logger.Info("Wait for connection on %s", local)

		localConn, err := listener.Accept()
		if err != nil {
			logger.Warn("Handle local connect error: %s", err.Error())
			continue
		}

		go func() {
			defer localConn.Close()

			logger.Info("Connection from %s", localConn.RemoteAddr().String())
			logger.Info("Connecting " + remote)

			localConnCtx, err := netio.NewTCPCtx(localConn, lenc)
			if err != nil {
				logger.Warn("Handle local connect error: %s", err.Error())
				return
			}

			remoteConn, err := net.DialTimeout(
				"tcp", remote,
				time.Millisecond*time.Duration(option.TIMEOUT),
			)
			if err != nil {
				logger.Warn("Connect remote %s error: %s", remote, err.Error())
				return
			}
			defer remoteConn.Close()

			remoteConnCtx, err := netio.NewTCPCtx(remoteConn, renc)
			if err != nil {
				logger.Warn("Connect remote %s error: %s", remote, err.Error())
				return
			}

			logger.Info("Open pipe: %s <== FWD ==> %s",
				localConn.RemoteAddr().String(), remoteConn.RemoteAddr().String())
			netio.PipeForward(localConnCtx, remoteConnCtx)
			logger.Info("Close pipe: %s <== FWD ==> %s",
				localConn.RemoteAddr().String(), remoteConn.RemoteAddr().String())
		}()
	}

}

func local2RemoteUDP(local string, remote string, lenc bool, renc bool) {
	localAddr, err := net.ResolveUDPAddr("udp", local)
	if err != nil {
		logger.Warn("Parse udp address %s error: %s", local, err.Error())
		return
	}
	listener, err := net.ListenUDP("udp", localAddr)
	if err != nil {
		logger.Warn("Listen udp on %s error: %s", local, err.Error())
		return
	}
	defer listener.Close()

	remoteAddr, err := net.ResolveUDPAddr("udp", remote)
	if err != nil {
		logger.Warn("Parse udp address %s error: %s", local, err.Error())
		return
	}
	remoteConn, err := net.DialUDP("udp", nil, remoteAddr)
	if err != nil {
		logger.Warn("Dial remote udp %s error: %s", local, err.Error())
		return
	}
	defer remoteConn.Close()

	listenerCtx, err := netio.NewUDPCtx(listener, lenc, false)
	if err != nil {
		return
	}
	remoteCtx, err := netio.NewUDPCtx(remoteConn, renc, true)
	if err != nil {
		return
	}

	netio.ForwardUDP(listenerCtx, remoteCtx)
}

func Local2Remote(local string, remote string, lenc bool, renc bool) {
	if option.PROTOCOL == "TCP" {
		logger.Success("Forward TCP traffic between %s (encrypted: %v) and %s (encrypted: %v)",
			local, lenc, remote, renc)
		local2RemoteTCP(local, remote, lenc, renc)
	} else {
		logger.Success("Forward UDP traffic between %s (encrypted: %v) and %s (encrypted: %v)",
			local, lenc, remote, renc)
		local2RemoteUDP(local, remote, lenc, renc)
	}
}

func local2LocalTCP(localA string, localB string, laenc bool, lbenc bool) {
	var listenerA net.Listener
	var listenerB net.Listener

	for {
		signal := make(chan byte)
		var localConnA net.Conn
		var localConnB net.Conn

		go func() {
			var err error
			listenerA, err = net.Listen("tcp", localA)
			if err != nil {
				logger.Warn("Listen on %s error: %s", localA, err.Error())
				return
			}
			defer listenerA.Close()

			for {
				logger.Info("Wait for connection on %s", localA)

				var err error
				localConnA, err = listenerA.Accept()
				if err != nil {
					logger.Warn("Handle connection error: %s", err.Error())
					continue
				}
				break
			}
			signal <- 'A'
		}()

		go func() {
			var err error
			listenerB, err = net.Listen("tcp", localB)
			if err != nil {
				logger.Warn("Listen on %s error: %s", localB, err.Error())
				return
			}
			defer listenerB.Close()

			for {
				logger.Info("Wait for connection on %s", localB)

				var err error
				localConnB, err = listenerB.Accept()
				if err != nil {
					logger.Warn("Handle connection error: %s", err.Error())
					continue
				}
				break
			}
			signal <- 'B'
		}()

		switch <-signal {
		case 'A':
			logger.Info("%s connected, waiting for %s", localA, localB)
		case 'B':
			logger.Info("%s connected, waiting for %s", localB, localA)
		}

		<-signal

		go func() {
			defer func() {
				if localConnA != nil {
					localConnA.Close()
				}

				if localConnB != nil {
					localConnB.Close()
				}
			}()
			//找了中间端口转发
			localConnCtxA, err := netio.NewTCPCtx(localConnA, laenc)
			if err != nil {
				logger.Warn("handle local %s error: %s", localA, err.Error())
			}

			localConnCtxB, err := netio.NewTCPCtx(localConnB, lbenc)
			if err != nil {
				logger.Warn("handle local %s error: %s", localB, err.Error())
			}

			logger.Info("Open pipe: %s <== FWD ==> %s",
				localConnA.RemoteAddr().String(), localConnB.RemoteAddr().String())
			netio.PipeForward(localConnCtxA, localConnCtxB)
			logger.Info("Close pipe: %s <== FWD ==> %s",
				localConnA.RemoteAddr().String(), localConnB.RemoteAddr().String())
		}()
	}
}

func local2LocalUDP(localA string, localB string, laenc bool, lbenc bool) {
	localAddrA, err := net.ResolveUDPAddr("udp", localA)
	if err != nil {
		logger.Warn("Parse udp address %s error: %s", localA, err.Error())
		return
	}
	listenerA, err := net.ListenUDP("udp", localAddrA)
	if err != nil {
		logger.Warn("Listen udp on %s error: %s", localA, err.Error())
		return
	}
	defer listenerA.Close()

	localAddrB, err := net.ResolveUDPAddr("udp", localB)
	if err != nil {
		logger.Warn("Parse udp address %s error: %s", localB, err.Error())
		return
	}
	listenerB, err := net.ListenUDP("udp", localAddrB)
	if err != nil {
		logger.Warn("Listen udp on %s error: %s", localB, err.Error())
		return
	}
	defer listenerB.Close()

	listenerCtxA, err := netio.NewUDPCtx(listenerA, laenc, false)
	if err != nil {
		return
	}
	listenerCtxB, err := netio.NewUDPCtx(listenerB, lbenc, false)
	if err != nil {
		return
	}

	netio.ForwardUnconnectedUDP(listenerCtxA, listenerCtxB)
}

func Local2Local(localA string, localB string, laenc bool, lbenc bool) {
	if option.PROTOCOL == "TCP" {
		logger.Success("Forward TCP traffic between %s (encrypted: %v) and %s (encrypted: %v)",
			localA, laenc, localB, lbenc)

		local2LocalTCP(localA, localB, laenc, lbenc)
	} else {
		logger.Success("Forward UDP traffic between %s (encrypted: %v) and %s (encrypted: %v)",
			localA, laenc, localB, lbenc)
		local2LocalUDP(localA, localB, laenc, lbenc)
	}
}

func remote2remoteTCP(remoteA string, remoteB string, raenc bool, rbenc bool) {
	for {
		var remoteConnA net.Conn
		var remoteConnB net.Conn

		signal := make(chan struct{})

		go func() {
			for {
				var err error
				logger.Info("Connecting remote %s", remoteA)

				remoteConnA, err = net.DialTimeout(
					"tcp", remoteA,
					time.Millisecond*time.Duration(option.TIMEOUT),
				)
				if err != nil {
					logger.Info("Connect remote %s error, retrying", remoteA)
					time.Sleep(option.CONNECTING_RETRY_DURATION * time.Millisecond)
					continue
				}
				break
			}

			signal <- struct{}{}
		}()

		go func() {
			for {
				var err error
				logger.Info("Connecting remote %s", remoteB)

				remoteConnB, err = net.DialTimeout(
					"tcp", remoteB,
					time.Millisecond*time.Duration(option.TIMEOUT),
				)
				if err != nil {
					logger.Info("Connect remote %s error, retrying", remoteB)
					time.Sleep(option.CONNECTING_RETRY_DURATION * time.Millisecond)
					continue
				}
				break
			}

			signal <- struct{}{}
		}()

		<-signal
		<-signal

		go func() {
			defer func() {
				if remoteConnA != nil {
					remoteConnA.Close()
				}

				if remoteConnB != nil {
					remoteConnB.Close()
				}
			}()

			if remoteConnA != nil && remoteConnB != nil {
				remoteConnCtxA, err := netio.NewTCPCtx(remoteConnA, raenc)
				if err != nil {
					logger.Warn("Handle remote %s error: %s", remoteA, err.Error())
				}
				remoteConnCtxB, err := netio.NewTCPCtx(remoteConnB, rbenc)
				if err != nil {
					logger.Warn("Handle remote %s error: %s", remoteB, err.Error())
				}

				logger.Info("Start pipe: %s <== FWD ==> %s",
					remoteConnA.RemoteAddr().String(), remoteConnB.RemoteAddr().String())
				netio.PipeForward(remoteConnCtxA, remoteConnCtxB)
				logger.Info("Close pipe: %s <== FWD ==> %s",
					remoteConnA.RemoteAddr().String(), remoteConnB.RemoteAddr().String())
			}
		}()
	}
}

func remote2remoteUDP(remoteA string, remoteB string, raenc bool, rbenc bool) {
	remoteAddrA, err := net.ResolveUDPAddr("udp", remoteA)
	if err != nil {
		logger.Warn("Parse udp address %s error: %s", remoteA, err.Error())
		return
	}
	remoteConnA, err := net.DialUDP("udp", nil, remoteAddrA)
	if err != nil {
		logger.Warn("Dial remote udp %s error: %s", remoteA, err.Error())
		return
	}
	defer remoteConnA.Close()

	remoteAddrB, err := net.ResolveUDPAddr("udp", remoteB)
	if err != nil {
		logger.Warn("Parse udp address %s error: %s", remoteB, err.Error())
		return
	}
	remoteConnB, err := net.DialUDP("udp", nil, remoteAddrB)
	if err != nil {
		logger.Warn("Dial remote udp %s error: %s", remoteB, err.Error())
		return
	}
	defer remoteConnB.Close()

	remoteCtxA, err := netio.NewUDPCtx(remoteConnA, raenc, true)
	if err != nil {
		return
	}
	remoteCtxB, err := netio.NewUDPCtx(remoteConnB, rbenc, true)
	if err != nil {
		return
	}

	{
		// Need to send init packet to register the remote address, it doesn't matter even tough target is not `iox`
		//
		// There is a design fault here, and I need to consider the case where the FORWARD_WITHOUT_DEC flag is set
		// but actually needs to be encrypted, otherwise there is no IV in the ciphertext
		if raenc {
			iv, err := crypto.RandomNonce()
			cipher, err := crypto.NewCipher(iv)
			if err != nil {
				return
			}

			b := make([]byte, 4, 20)
			copy(b, netio.UDP_INIT_PACKET)

			cipher.StreamXOR(b, b)
			b = append(b, iv...)
			remoteCtxA.Write(b)

		} else {
			remoteCtxA.Write(netio.UDP_INIT_PACKET)
		}
		if rbenc {
			iv, err := crypto.RandomNonce()
			cipher, err := crypto.NewCipher(iv)
			if err != nil {
				return
			}

			b := make([]byte, 4, 20)
			copy(b, netio.UDP_INIT_PACKET)

			cipher.StreamXOR(b, b)
			b = append(b, iv...)
			remoteCtxB.Write(b)

		} else {
			remoteCtxB.Write(netio.UDP_INIT_PACKET)
		}
	}

	netio.ForwardUDP(remoteCtxA, remoteCtxB)
}

func Remote2Remote(remoteA string, remoteB string, raenc bool, rbenc bool) {
	if option.PROTOCOL == "TCP" {
		logger.Success("Forward TCP traffic between %s (encrypted: %v) and %s (encrypted: %v)",
			remoteA, raenc, remoteB, rbenc)
		remote2remoteTCP(remoteA, remoteB, raenc, rbenc)
	} else {
		logger.Success("Forward UDP traffic between %s (encrypted: %v) and %s (encrypted: %v)",
			remoteA, raenc, remoteB, rbenc)
		remote2remoteUDP(remoteA, remoteB, raenc, rbenc)
	}
}

(8)proxy.go

package operate

import (
	"iox/logger"
	"iox/netio"
	"iox/socks5"
	"net"
	"os"
	"os/signal"
)

func ProxyLocal(local string, encrypted bool) {
	listener, err := net.Listen("tcp", local)
	if err != nil {
		logger.Warn("Socks5 listen on %s error: %s", local, err.Error())
		return
	}

	logger.Success("Start socks5 server on %s (encrypted: %v)", local, encrypted)

	for {
		conn, err := listener.Accept()
		if err != nil {
			logger.Warn("Socks5 handle local connect error: %s", err.Error())
			continue
		}

		go func() {
			defer conn.Close()
			connCtx, err := netio.NewTCPCtx(conn, encrypted)
			if err != nil {
				return
			}

			socks5.HandleConnection(connCtx)
		}()
	}
}

func ProxyRemote(remote string, encrypted bool) {
	session, ctlStream, err := clientHandshake(remote)
	if err != nil {
		logger.Warn(err.Error())
		return
	}
	defer session.Close()

	logger.Success("Remote socks5 handshake ok (encrypted: %v)", encrypted)

	connectRequest := make(chan uint8, MAX_CONNECTION)
	defer close(connectRequest)
	endSignal := make(chan struct{})

	// handle ctrl+C
	{
		sigs := make(chan os.Signal)
		signal.Notify(sigs, os.Interrupt)
		go func() {
			<-sigs
			ctlStream.Write(marshal(Protocol{
				CMD: CTL_CLEANUP,
				N:   0,
			}))
			logger.Success("Recv Ctrl+C, exit now")
			os.Exit(0)
		}()
	}

	// handle ctl stream
	go func() {
		defer ctlStream.Close()

		for {
			pb, err := readUntilEnd(ctlStream)
			if err != nil {
				logger.Warn("Control connection has been closed, exit now")
				os.Exit(-1)
			}

			p := unmarshal(pb)
			switch p.CMD {
			case CTL_CONNECT_ME:
				connectRequest <- p.N
			case CTL_CLEANUP:
				endSignal <- struct{}{}
				return
			}
		}
	}()

	// handle CONNECT_ME request
	for {
		select {
		case <-endSignal:
			logger.Success("Recv exit signal from remote, exit now")
			return
		case n := <-connectRequest:
			for n > 0 {
				go func() {
					stream, err := session.OpenStream()
					if err != nil {
						logger.Info(err.Error())
						return
					}
					defer stream.Close()

					connCtx, err := netio.NewTCPCtx(stream, encrypted)
					if err != nil {
						return
					}

					socks5.HandleConnection(connCtx)
				}()
				n--
			}
		}
	}
}

func ProxyRemoteL2L(control string, local string, cenc bool, lenc bool) {
	masterListener, err := net.Listen("tcp", control)
	if err != nil {
		logger.Warn("Listen on %s error", control)
		return
	}
	defer masterListener.Close()

	logger.Info("Listen on %s for reverse socks5", control)

	localListener, err := net.Listen("tcp", local)
	if err != nil {
		logger.Warn("Listen on %s error", local)
		return
	}
	defer localListener.Close()

	session, ctlStream, err := serverHandshake(masterListener)
	if err != nil {
		logger.Warn(err.Error())
		return
	}
	defer session.Close()
	defer ctlStream.Close()

	logger.Success("Reverse socks5 server handshake ok from %s (encrypted: %v)", session.RemoteAddr().String(), cenc)
	logger.Success("Socks5 server is listening on %s (encrypted: %v)", local, lenc)

	// handle ctrl+C
	{
		sigs := make(chan os.Signal)
		signal.Notify(sigs, os.Interrupt)
		go func() {
			<-sigs
			ctlStream.Write(marshal(Protocol{
				CMD: CTL_CLEANUP,
				N:   0,
			}))
			logger.Success("Recv Ctrl+C, exit now")
			os.Exit(0)
		}()
	}

	localConnBuffer := make(chan net.Conn, MAX_CONNECTION)
	defer close(localConnBuffer)

	// handle ctl stream read
	go func() {
		for {
			pb, err := readUntilEnd(ctlStream)
			if err != nil {
				logger.Warn("Control connection has been closed, exit now")
				os.Exit(-1)
			}

			p := unmarshal(pb)
			switch p.CMD {
			case CTL_CLEANUP:
				logger.Success("Recv exit signal from remote, exit now")
				os.Exit(0)
			}
		}
	}()

	// handle local connection
	go func() {
		for {
			localConn, err := localListener.Accept()
			if err != nil {
				continue
			}

			localConnBuffer <- localConn

			_, err = ctlStream.Write(marshal(Protocol{
				CMD: CTL_CONNECT_ME,
				N:   1,
			}))
			if err != nil {
				logger.Warn("Control connection has been closed, exit now")
				os.Exit(-1)
			}
		}
	}()

	for {
		remoteStream, err := session.AcceptStream()
		if err != nil {
			continue
		}

		localConn := <-localConnBuffer

		go func() {
			defer remoteStream.Close()
			defer localConn.Close()

			remoteConnCtx, err := netio.NewTCPCtx(remoteStream, cenc)
			if err != nil {
				return
			}

			localConnCtx, err := netio.NewTCPCtx(localConn, lenc)
			if err != nil {
				return
			}

			netio.PipeForward(remoteConnCtx, localConnCtx)
		}()
	}
}

2、检测与绕过

(1)特征字符串和特征码

命令和log里的特征字符串可以作为检测特征
然后是代码里的特征码

绕过方法:修改掉相应的特征

(2)端口控制

这类端口转发的工具,如果端口限制死就失去作用了

绕过方法:无

(3)进程和库调用

通过终端的进程链控制和第三方库的调用情况在做检测

绕过方法:白进程利用,尽可能不调用库,加壳,主要是木马免杀那套

(4)SOCKS代理的检测

这是IDS这块,具体原理不清

结语

iox主要是比较新