内网隧道之iox
前言
本文研究端口转发 & SOCKS代理工具的一个工具,iox
github:https://github.com/EddieIvan01/iox
一、概述
1、简介
最后更新于2020年,用Go编写,功能类似于lcx/ew,优化了网络逻辑,简化了使用方法
- 支持跨平台
- 支持TCP/UDP
- 反向代理模式中使用TCP多路复用
- 有流量加密功能
2、原理
就是端口转发和SOCKS代理,与lcx和EW的原理相仿
3、用法
代码语言:javascript复制#加密
./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)端口转发
代码语言:javascript复制#监听 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代理
代码语言:javascript复制#在本地 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)服务端
代码语言:javascript复制./iox fwd -l *2222 -l 3333 -k 123456
(2)客户端
开启apache
代码语言:javascript复制./iox fwd -r 127.0.0.1:80 -r *192.168.10.128:2222 -k 123456
(3)隧道建立
还可以nc、ssh等,根据端口来确定服务
(4)抓包看看
tcp三次握手建立连接,客户端向外发起连接的端口是60614和60618
心跳包
3、SOCKS代理
(1)服务端
修改/etc/proxychains.conf
代码语言:javascript复制socks5 0.0.0.0 1080
监听并映射端口
代码语言:javascript复制./iox proxy -l 2222 -l 1080
代码语言:javascript复制
(2)客户端
代码语言:javascript复制./iox proxy -r 192.168.10.128:2222
(3)隧道建立
之后通过proxychains可以执行命令
如nmap扫描端口信息
代码语言:javascript复制proxychains4 nmap -p 1-1000 -Pn -sT 192.168.10.129
代码语言:javascript复制
(4)抓包看看
tcp握手
nmap期间
三、探索
1、源码与分析
(1)main.go
使用方法和调用相应模式
代码语言:javascript复制package main
import (
"fmt"
"iox/operate"
"iox/option"
"os"
)
const VERSION = "0.4"
func Usage() {
fmt.Printf(
"iox v%vn"
"Usage: iox fwd/proxy [-l [*][HOST:]PORT] [-r [*]HOST:PORT] [-k HEX] [-t TIMEOUT] [-u] [-h] [-v]nn"
"Options:n"
" -l [*][HOST:]PORTn"
" address to listen on. `*` means encrypted socketn"
" -r [*]HOST:PORTn"
" remote host to connect, HOST can be IP or Domain. `*` means encrypted socketn"
" -k HEXn"
" hexadecimal format key, be used to generate Key and IVn"
" -un"
" udp forward moden"
" -t TIMEOUTn"
" set connection timeout(millisecond), default is 5000n"
" -vn"
" enable log outputn"
" -hn"
" print usage then exitn", 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
缺省值
代码语言:javascript复制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
读取输入参数
代码语言:javascript复制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的信息的加密写和解密读
代码语言:javascript复制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
读写功能,每个套接字只将数据包写入最近向其发送数据包的地址,而不是广播到所有地址
代码语言:javascript复制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
代码语言:javascript复制// 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的三种端口转发
代码语言:javascript复制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
代码语言:javascript复制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主要是比较新
红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。