Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

用Go开发可以内网活跃主机嗅探器 #1

Closed
timest opened this issue Oct 28, 2017 · 21 comments
Closed

用Go开发可以内网活跃主机嗅探器 #1

timest opened this issue Oct 28, 2017 · 21 comments

Comments

@timest
Copy link
Owner

timest commented Oct 28, 2017

用Go开发可以内网活跃主机嗅探器

文章关键词

  • go/golang
  • gopacket
  • 抓包
  • pcap/libpcap
  • arp
  • nbns
  • mdns
  • manuf

程序截图

image

说明

本文对于Go语言本身的讲解不会太多,想把更多的时间花在几个网络协议的讲解上,希望本文对打算或正在用Go进行TCP/IP编程和抓包的朋友带来帮助。
github地址:https://github.com/timest/goscan

程序思路

  • 通过内网IP和子网掩码计算出内网IP范围
  • 向内网广播ARP Request
  • 监听并抓取ARP Response包,记录IP和Mac地址
  • 发活跃IP发送MDNS和NBNS包,并监听和解析Hostname
  • 根据Mac地址计算出厂家信息

通过内网IP和子网掩码计算出内网IP范围

如果仅仅只是知道一个IP地址,是无法得知内网IP的网段,不能只是简单的把本机IP的最后一个字节改成1-255。需要通过子网掩码来计算得出内网的网段,这块比较简单,这里不赘述了,有疑问的网上搜索子网掩码获取更多资料。值得一提的是IP地址的最后一个字段是不能为0和255,前者是RFC规定,后者一般是广播地址。

// 单网卡模式
addrs, err := net.InterfaceAddrs()
if err != nil {
   log.Fatal("无法获取本地网络信息:", err)
}
for i, a := range addrs {
   if ip, ok := a.(*net.IPNet); ok && !ip.IP.IsLoopback() {
       if ip.IP.To4() != nil {
           fmt.Println("IP:", ip.IP)
           fmt.Println("子网掩码:", ip.Mask)
           it, _ := net.InterfaceByIndex(i)
           fmt.Println("Mac地址:", it.HardwareAddr)
           break
       }
   }
}

根据上面得到的IPNet,可以算出内网IP范围:

type IP uint32
// 根据IP和mask换算内网IP范围
func Table(ipNet *net.IPNet) []IP {
    ip := ipNet.IP.To4()
    log.Info("本机ip:", ip)
    var min, max IP
    var data []IP
    for i := 0; i < 4; i++ {
        b := IP(ip[i] & ipNet.Mask[i])
        min += b << ((3 - uint(i)) * 8)
    }
    one, _ := ipNet.Mask.Size()
    max = min | IP(math.Pow(2, float64(32 - one)) - 1)
    log.Infof("内网IP范围:%s --- %s", min, max)
    // max 是广播地址,忽略
    // i & 0x000000ff  == 0 是尾段为0的IP,根据RFC的规定,忽略
    for i := min; i < max; i++ {
        if i & 0x000000ff == 0 {
            continue
        }
        data = append(data, i)
    }
    return data
}

向内网广播ARP Request

ARP(Address Resolution Protocol),地址解析协议,是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址 ------百度百科

当我们要向以太网中另一台主机发送IP数据时,我们本地会根据目的主机的IP地址在ARP高速缓存中查询相应的以太网地址,ARP高速缓存是主机维护的一个IP地址到相应以太网地址的映射表。如果查询失败,ARP会广播一个询问(op字段为1)目的主机硬件地址的报文,等待目标主机的响应。
因为ARP高速缓存有时效性,读取到目标主机的硬件地址后,最好发送一个ICMP包验证目标是否在线。当然也可以选择不从高速缓存里读取数据,而是直接并发发送arp包,等待在线主机回应ARP报文。

15090821642044
gopacket有封装好的ARP报文:

type ARP struct {
	BaseLayer
	AddrType          LinkType     // 硬件类型
	Protocol          EthernetType // 协议类型
	HwAddressSize     uint8        // 硬件地址长度
	ProtAddressSize   uint8        // 协议地址长度
	Operation         uint16       // 操作符(1代表request 2代表reply)
	SourceHwAddress   []byte       // 发送者硬件地址
	SourceProtAddress []byte       // 发送者IP地址
	DstHwAddress      []byte       // 目标硬件地址(可以填写00:00:00:00:00:00)
	DstProtAddress    []byte       // 目标IP地址
}

给出项目中具体的代码:

// 发送arp包
// ip 目标IP地址
func sendArpPackage(ip IP) {
    srcIp := net.ParseIP(ipNet.IP.String()).To4()
    dstIp := net.ParseIP(ip.String()).To4()
    if srcIp == nil || dstIp == nil {
        log.Fatal("ip 解析出问题")
    }
    // 以太网首部
    // EthernetType 0x0806  ARP
    ether := &layers.Ethernet{
        SrcMAC: localHaddr,
        DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
        EthernetType: layers.EthernetTypeARP,
    }
    
    a := &layers.ARP{
        AddrType: layers.LinkTypeEthernet,
        Protocol: layers.EthernetTypeIPv4,
        HwAddressSize: uint8(6),
        ProtAddressSize: uint8(4),
        Operation: uint16(1), // 0x0001 arp request 0x0002 arp response
        SourceHwAddress: localHaddr,
        SourceProtAddress: srcIp,
        DstHwAddress: net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
        DstProtAddress: dstIp,
    }
    
    buffer := gopacket.NewSerializeBuffer()
    var opt gopacket.SerializeOptions
    gopacket.SerializeLayers(buffer, opt, ether, a)
    outgoingPacket := buffer.Bytes()
    
    handle, err := pcap.OpenLive(iface, 2048, false, 30 * time.Second)
    if err != nil {
        log.Fatal("pcap打开失败:", err)
    }
    defer handle.Close()
    
    err = handle.WritePacketData(outgoingPacket)
    if err != nil {
        log.Fatal("发送arp数据包失败..")
    }
}

我们只需要将第一步得到的内网IP表,开启一个goruntime遍历发送arp报文就可以。

监听并抓取ARP Response包,记录IP和Mac地址

在上一步已经发送了arp请求,只需要开启一个arp的监听goruntime,所有有返回arp response包的,就是内网在线的host。

func listenARP(ctx context.Context) {
    handle, err := pcap.OpenLive(iface, 1024, false, 10 * time.Second)
    if err != nil {
        log.Fatal("pcap打开失败:", err)
    }
    defer handle.Close()
    handle.SetBPFFilter("arp")
    ps := gopacket.NewPacketSource(handle, handle.LinkType())
    for {
        select {
        case <-ctx.Done():
            return
        case p := <-ps.Packets():
            arp := p.Layer(layers.LayerTypeARP).(*layers.ARP)
            if arp.Operation == 2 {
                mac := net.HardwareAddr(arp.SourceHwAddress)
                pushData(ParseIP(arp.SourceProtAddress).String(), mac, "", manuf.Search(mac.String()))
                go sendMdns(ParseIP(arp.SourceProtAddress), mac)
                go sendNbns(ParseIP(arp.SourceProtAddress), mac)
            }
        }
    }
}

发活跃IP发送MDNS和NBNS包,并监听和解析hostname

在上一步的过程中,我们在接受到一个arp的response后,就可以发起mdns和nbns包等待hostname的返回。

go sendMdns(ParseIP(arp.SourceProtAddress), mac)
go sendNbns(ParseIP(arp.SourceProtAddress), mac)

mDNS:往对方的5353端口和01:00:5E:00:00:FB的mac地址发送UDP的mdns(Multicast DNS)包,如果目标系统支持,回返回host name。详细协议介绍和报文格式可以查看维基百科的介绍。
NBNS:也是一个种常见的查看目标机器hostname的一种协议,和mDNS一样,传输层也是UDP,端口是在137。
篇幅太长了,具体的代码请看github上的nbns.go 和 mdns.go。

根据Mac地址计算出厂家信息

我们可以通过目标主机的硬件地址,获取到设备的生产厂家信息。这样的话,即使遇到防御比较好的系统,我们无法获取到hostname,也能从厂家信息里获取一定的信息量,比如厂家信息是oneplus或则Smartisan,就可以判断是手机了
manuf文件,文件片段:

00:03:8F	Weinsche	Weinschel Corporation
00:03:90	DigitalV	Digital Video Communications, Inc.
00:03:91	Advanced	Advanced Digital Broadcast, Ltd.
00:03:92	HyundaiT	Hyundai Teletek Co., Ltd.
00:03:93	Apple	Apple, Inc.
00:03:94	ConnectO	Connect One
00:03:95	Californ	California Amplifier
00:03:96	EzCast	EZ Cast Co., Ltd.
00:03:97	Watchfro	Watchfront Limited

代码不贴了,直接看代码,100行不到的代码,还是挺简单的:manuf.go。测试结果99%的mac地址都能映射到相应的厂商信息。

@timest
Copy link
Owner Author

timest commented Oct 30, 2017

image

@terryzwt
Copy link

terryzwt commented Nov 1, 2017

谢谢分享.
但是怎么使用?没看到相关的说明.

@timest
Copy link
Owner Author

timest commented Nov 1, 2017

@terryzwt 直接 go build main && sudo ./main 就可以了

@a4j4123
Copy link

a4j4123 commented Nov 2, 2017

kali2.0
default
1233
没玩过go,指导下

@a4j4123
Copy link

a4j4123 commented Nov 2, 2017

还得翻墙下载依赖包

@a4j4123
Copy link

a4j4123 commented Nov 2, 2017

依赖包搞定了,现在这样了
default

@a4j4123
Copy link

a4j4123 commented Nov 2, 2017

搞定了把包放vendor下就行。。。。捣鼓半天,
1

@a4j4123
Copy link

a4j4123 commented Nov 3, 2017

虚拟机里只能扫到自己,局域网其他主机扫不了

@singchia
Copy link

singchia commented Nov 3, 2017

虚机用的桥接吧,桥接的虚二层网络隔绝了vlan,所以arp出不去,可以改成host试试看

@exp-db
Copy link

exp-db commented Nov 9, 2017

补充,如果Linux上安装报错 : fatal error: pcap.h: No such file or directory 说明需要安装libpcap程序
sudo apt-get install libpcap-dev

@timest
Copy link
Owner Author

timest commented Nov 9, 2017

@exp-db 👍 不过补充在 #4 会更合适

@terryzwt
Copy link

MAC上,go build无报错.
执行./main,会有如下报错:

'./main' terminated by signal SIGKILL (Forced quit)

@dagongrenzzZ
Copy link

你好,我想请问下win环境下怎么运行?github.com\google\gopacket\pcap_obj\pcap.cgo2.o: In function _cgo_e5e2831ef2ad_Cfunc_pcap_free_datalinks': \github.com\google\gopacket\pcap/cgo-gcc-prolog:246: undefined reference to pcap_free_datalinks'这是链接的问题么?

@tcchend
Copy link

tcchend commented Dec 30, 2017

你好,win环境下,如下问题怎么解决,谢谢
time="2017-12-30T10:58:47+08:00" level=info msg="本机ip:172.28.169.17"
time="2017-12-30T10:58:47+08:00" level=info msg="内网IP范围:172.28.169.0 --- 172.28.169.255"
time="2017-12-30T10:58:47+08:00" level=fatal msg="pcap打开失败:Error opening adapter: ϵͳ\xd5Ҳ\xbb\xb5\xbdָ\xb6\xa8\xb5\xc4\xc9豸\xa1\xa3 (20)"
错误: 进程退出代码 1.

@timest
Copy link
Owner Author

timest commented Jan 3, 2018

@chenDONG9 目前没有windows测试环境,另外你这个乱码也看不懂,你已经排除是权限的问题了吗?

@qianxiansheng90
Copy link

qianxiansheng90 commented Feb 9, 2018

楼主请教个问题:
我用mac本地编译,可以通过,但是编译为Linux的时候,报错:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v fetchPortCommand.go
command-line-arguments
# command-line-arguments
./fetchPortCommand.go:51:16: undefined: pcap.FindAllDevs
./fetchPortCommand.go:60:17: undefined: pcap.OpenLive

找到解决办法:
在Linux平台编译就可以通过

@timest timest closed this as completed Aug 1, 2019
@grassto
Copy link

grassto commented Sep 29, 2019

你好,win环境下,如下问题怎么解决,谢谢
time="2017-12-30T10:58:47+08:00" level=info msg="本机ip:172.28.169.17"
time="2017-12-30T10:58:47+08:00" level=info msg="内网IP范围:172.28.169.0 --- 172.28.169.255"
time="2017-12-30T10:58:47+08:00" level=fatal msg="pcap打开失败:Error opening adapter: ϵͳ\xd5Ҳ\xbb\xb5\xbdָ\xb6\xa8\xb5\xc4\xc9豸\xa1\xa3 (20)"
错误: 进程退出代码 1.

解决了吗老哥

@AmarsDing
Copy link

time="2019-10-12T15:30:44+08:00" level=info msg="本机ip:192.168.1.30"
time="2019-10-12T15:30:44+08:00" level=info msg="内网IP范围:192.168.1.0 --- 192.168.1.255"
time="2019-10-12T15:30:44+08:00" level=fatal msg="pcap打开失败:Error opening adapter: ϵͳ\xd5Ҳ\xbb\xb5\xbdָ\xb6\xa8\xb5\xc4\xc9豸\xa1\xa3 (20)"

同样错误 win10 管理员权限

@timest
Copy link
Owner Author

timest commented Oct 12, 2019

@AmarsDing 不好意思朋友,没有windows 的测试机。暂没有对支持windows的计划。

@Xuzan9396
Copy link

为什么只能扫到自己
image

Repository owner locked as off-topic and limited conversation to collaborators Mar 4, 2021
@timest
Copy link
Owner Author

timest commented Mar 4, 2021

为什么只能扫到自己
image

建议用wireshark分析下

Repository owner deleted a comment from zboya Mar 4, 2021
Repository owner deleted a comment from a4j4123 Mar 4, 2021
Repository owner deleted a comment from a4j4123 Mar 4, 2021
Repository owner deleted a comment from a4j4123 Mar 4, 2021
Repository owner deleted a comment from wxj2016 Mar 4, 2021
Repository owner deleted a comment from wxj2016 Mar 4, 2021
Repository owner deleted a comment from wxj2016 Mar 4, 2021
Repository owner deleted a comment from wxj2016 Mar 4, 2021
Repository owner deleted a comment from wxj2016 Mar 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

12 participants