這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
文章關鍵詞
- 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報文。
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 Corporation00: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 One00:03:95 Californ California Amplifier00:03:96 EzCast EZ Cast Co., Ltd.00:03:97 Watchfro Watchfront Limited
代碼不貼了,直接看代碼,100行不到的代碼,還是挺簡單的:manuf.go。測試結果99%的mac地址都能映射到相應的廠商資訊。