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

源码相关: https://github.com/timest/goscan

文章关键词

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

程序截图

说明

本文对于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报文。

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地址都能映射到相应的厂商信息。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2017-11-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java Web

Java 面试知识点解析(五)——网络协议篇

30590
来自专栏windealli

socket常用函数知识点整理

调用close() 之后,进程不能再使用该描述符。 但是已经发送队列中的数据还是会继续发送,等到发送回再发起四次挥手。

31620
来自专栏landv

烽火2640路由器命令行手册-02-接口配置命令

本文描述用于不同类型接口的基本命令,这些命令对应于手册包括的接口配置任务,有关配置要点,参考下面列出的各项内容。

14420
来自专栏架构说

tcp如何维护长连接

上次提到tcp数据流无边界特点 还有一个特点那就是 TCP有长连接和短连接之分 目录结构: ? tcp连接的终止 — 01 — socke正常关闭 流程:...

51190
来自专栏安富莱嵌入式技术分享

【RL-TCPnet网络教程】第23章 RL-TCPnet之地址解析协议ARP

本章节为大家讲解ARP(Address Resolution Protocol,地址解析协议),通过前面章节对TCP和UDP的学习,需要大家对ARP也有个基础的...

12950
来自专栏Java进阶架构师

一篇文章带你详解 HTTP 协议(上)

利用 TCP/IP 协议族进行网络通信时,会通过分层顺序与对方进行通信。发送端从应用层往下走,接收端则从链路层往上走。如下:

13140
来自专栏轮子工厂

这真的是你了解的网络吗?

无论是 C/S 开发还是 B/S 开发,无论是前端开发还是后台开发,网络总是无法避免的,数据如何传输,如何保证正确性和可靠性,如何提高传输效率,如何解决会话管理...

12220
来自专栏Java架构师历程

TCP连接的状态详解以及故障排查

我们通过了解TCP各个状态,可以排除和定位网络或系统故障时大有帮助。(总结网络上的内容)

1.1K20
来自专栏吴伟祥

计算机网络基础知识总结 转

计算机网络学习的核心内容就是网络协议的学习。网络协议是为计算机网络中进行数据交换而建立的规则、标准或者说是约定的集合。因为不同用户的数据终端可能采取的字符集是不...

10010
来自专栏网络 后台

TCP TIME_WAIT

从图中可以看出,若服务器主动关闭连接,在四次挥手的最后一个ACK后连接端口会变为TIME_WAIT状态, 状态停留时长为两个MSL(最大分段寿命),这个状态只有...

70500

扫码关注云+社区

领取腾讯云代金券