内网渗透系列:内网隧道之iox

Posted 思源湖的鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内网渗透系列:内网隧道之iox相关的知识,希望对你有一定的参考价值。

目录

前言

本文研究端口转发 & 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 

(2)客户端

开启apache

./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

socks5  0.0.0.0 1080

监听并映射端口

./iox proxy -l 2222 -l 1080

(2)客户端

./iox proxy -r 192.168.10.128:2222

(3)隧道建立

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

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


(4)抓包看看

tcp握手


nmap期间

三、探索

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


以上是关于内网渗透系列:内网隧道之iox的主要内容,如果未能解决你的问题,请参考以下文章

内网渗透系列:内网隧道之pingtunnel

内网渗透系列:内网隧道之NATBypass

内网渗透系列:内网隧道之icmpsh

内网渗透系列:内网隧道之icmpsh

内网渗透系列:内网隧道之icmp_tran

内网渗透系列:内网隧道之pingtunnel