112

用Go开发可以内网活跃主机嗅探器 · Issue #1 · timest/goscan · GitHub

 6 years ago
source link: https://github.com/timest/goscan/issues/1?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

文章关键词

  • go/golang
  • gopacket
  • pcap/libpcap
  • 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地址都能映射到相应的厂商信息。

TIGERB, wujunze, seerjk, chigosec, and zhangqiangpei reacted with thumbs up emoji All reactions

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK