diff --git a/ios/pcap/ipfinder.go b/ios/pcap/ipfinder.go index 09be95c8..645d26fa 100644 --- a/ios/pcap/ipfinder.go +++ b/ios/pcap/ipfinder.go @@ -1,6 +1,10 @@ package pcap import ( + "fmt" + "io" + "time" + "github.com/danielpaulus/go-ios/ios" "github.com/google/gopacket" "github.com/google/gopacket/layers" @@ -13,81 +17,158 @@ type NetworkInfo struct { IPv6 string } -func (n NetworkInfo) complete() bool { - return n.IPv6 != "" && n.Mac != "" && n.IPv4 != "" +type PacketInfo struct { + Mac string + IPv4 string + IPv6 string } -// FindIp reads pcap packets until one is found that matches the given MAC +func (n PacketInfo) complete() bool { + return (n.IPv6 != "" && n.Mac != "") || (n.IPv4 != "" && n.Mac != "") +} + +// FindIPByMac reads pcap packets until one is found that matches the given MAC // and contains an IP address. This won't work if the iOS device "automatic Wifi address" privacy // feature is enabled. The MAC needs to be static. -func FindIp(device ios.DeviceEntry) (NetworkInfo, error) { +func FindIPByMac(device ios.DeviceEntry, capture_timeout time.Duration) (NetworkInfo, error) { mac, err := ios.GetWifiMac(device) if err != nil { - return NetworkInfo{}, err + return NetworkInfo{}, fmt.Errorf("FindIPMyMac: unable to get WiFi MAC Address: %w", err) } - return findIp(device, mac) -} -func findIp(device ios.DeviceEntry, mac string) (NetworkInfo, error) { - intf, err := ios.ConnectToService(device, "com.apple.pcapd") + log.Infof("FindIPByMac: connected to pcapd. timeout %v", capture_timeout) + + pcapService, err := ios.ConnectToService(device, "com.apple.pcapd") if err != nil { - return NetworkInfo{}, err + return NetworkInfo{}, fmt.Errorf("FindIPByMac: failed connecting to com.apple.pcapd with err: %w", err) } - plistCodec := ios.NewPlistCodec() - info := NetworkInfo{} - info.Mac = mac + + endSignal := time.After(capture_timeout) + +L: for { - b, err := plistCodec.Decode(intf.Reader()) - if err != nil { - return NetworkInfo{}, err - } - decodedBytes, err := fromBytes(b) - if err != nil { - return NetworkInfo{}, err - } - _, packet, err := getPacket(decodedBytes) - if err != nil { - return NetworkInfo{}, err + select { + case <-endSignal: + break L + default: + packet, err := readPacket(pcapService.Reader()) + if err != nil { + return NetworkInfo{}, fmt.Errorf("FindIPByMac: error reading pcap packet: %w", err) + } + + if packet.Mac == mac { + return NetworkInfo{Mac: packet.Mac, IPv4: packet.IPv4, IPv6: packet.IPv6}, nil + } } - if len(packet) > 0 { - err := findIP(packet, &info) + } + + return NetworkInfo{}, fmt.Errorf("failed to get any IP matching the MAC: %s", mac) +} + +// FindIPByLazy reads pcap packets for a specified duration, whereafter it will +// try to find the IP address that occurs the most times, and assume that is the IP of the device. +// This is of course based on, that the device contacts mulitple IPs, and that there is some traffic. +// If the device only contains a single IP, then it would be 50/50 which IP will be returned. +// This is best effort! It's important to generate some traffic, when this function runs to get better results. +func FindIPByLazy(device ios.DeviceEntry, capture_duration time.Duration) (NetworkInfo, error) { + pcapService, err := ios.ConnectToService(device, "com.apple.pcapd") + if err != nil { + return NetworkInfo{}, fmt.Errorf("FindIPByLazy: failed connecting to com.apple.pcapd with err: %w", err) + } + + log.Infof("FindIPByLazy: connected to pcapd. waiting %v", capture_duration) + + ipv6Hits := make(map[string]int) + ipv4Hits := make(map[string]int) + endSignal := time.After(capture_duration) + +L: + for { + select { + case <-endSignal: + break L + default: + packet, err := readPacket(pcapService.Reader()) if err != nil { - return NetworkInfo{}, err + return NetworkInfo{}, fmt.Errorf("FindIPByLazy: error reading pcap packet: %w", err) } - if info.complete() { - return info, nil + if packet.IPv4 != "" { + ipv4Hits[packet.IPv4] += 1 } + if packet.IPv6 != "" { + ipv6Hits[packet.IPv6] += 1 + } + } } + + highestIPv4Hits, highestIPv4Addr := 0, "" + for ipv4, hits := range ipv4Hits { + if hits > highestIPv4Hits { + highestIPv4Hits = hits + highestIPv4Addr = ipv4 + } + } + + highestIPv6Hits, highestIPv6Addr := 0, "" + for ipv6, hits := range ipv6Hits { + if hits > highestIPv6Hits { + highestIPv6Hits = hits + highestIPv6Addr = ipv6 + } + } + + return NetworkInfo{IPv4: highestIPv4Addr, IPv6: highestIPv6Addr}, nil } -func findIP(p []byte, info *NetworkInfo) error { +var plistCodec = ios.NewPlistCodec() + +func readPacket(r io.Reader) (PacketInfo, error) { + b, err := plistCodec.Decode(r) + if err != nil { + return PacketInfo{}, fmt.Errorf("readPacket: failed decoding plistCodec err: %w", err) + } + decodedBytes, err := fromBytes(b) + if err != nil { + return PacketInfo{}, fmt.Errorf("readPacket: failed decoding fromBytes err: %w", err) + } + _, packet, err := getPacket(decodedBytes) + if err != nil { + return PacketInfo{}, fmt.Errorf("readPacket: failed getPacket err: %w", err) + } + if len(packet) > 0 { + pInfo := parsePacket(packet) + if pInfo.complete() { + return pInfo, nil + } + } + return PacketInfo{}, nil +} + +func parsePacket(p []byte) PacketInfo { packet := gopacket.NewPacket(p, layers.LayerTypeEthernet, gopacket.Default) + res := PacketInfo{} + // Get the TCP layer from this packet if tcpLayer := packet.Layer(layers.LayerTypeEthernet); tcpLayer != nil { tcp, _ := tcpLayer.(*layers.Ethernet) - if tcp.SrcMAC.String() == info.Mac { - if log.IsLevelEnabled(log.DebugLevel) { - log.Debugf("found packet for %s", info.Mac) - for _, layer := range packet.Layers() { - log.Debugf("layer:%s", layer.LayerType().String()) - } - } - if ipv4Layer := packet.Layer(layers.LayerTypeIPv4); ipv4Layer != nil { - ipv4, ok := ipv4Layer.(*layers.IPv4) - if ok { - info.IPv4 = ipv4.SrcIP.String() - log.Debugf("ip4 found:%s", info.IPv4) - } + res.Mac = tcp.SrcMAC.String() + + if ipv4Layer := packet.Layer(layers.LayerTypeIPv4); ipv4Layer != nil { + ipv4, ok := ipv4Layer.(*layers.IPv4) + if ok { + res.IPv4 = ipv4.SrcIP.String() + log.Debugf("ip4 found:%s", res.IPv4) } - if ipv6Layer := packet.Layer(layers.LayerTypeIPv6); ipv6Layer != nil { - ipv6, ok := ipv6Layer.(*layers.IPv6) - if ok { - info.IPv6 = ipv6.SrcIP.String() - log.Debugf("ip6 found:%s", info.IPv6) - } + } + if ipv6Layer := packet.Layer(layers.LayerTypeIPv6); ipv6Layer != nil { + ipv6, ok := ipv6Layer.(*layers.IPv6) + if ok { + res.IPv6 = ipv6.SrcIP.String() + log.Debugf("ip6 found:%s", res.IPv6) } } } - return nil + + return res } diff --git a/main.go b/main.go index ceb3121f..96fac44b 100644 --- a/main.go +++ b/main.go @@ -98,7 +98,7 @@ Usage: ios httpproxy remove [options] ios pair [--p12file=] [--password=] [options] ios ps [--apps] [options] - ios ip [options] + ios ip [options] [--lazy] [--timeout=] ios forward [options] ios dproxy [--binary] ios readpair [options] @@ -197,11 +197,13 @@ The commands work as following: > Use --nojson for a human-readable listing including BundleID when available. (not included with JSON output) > --apps limits output to processes flagged by iOS as "isApplication". This greatly-filtered list > should at least include user-installed software. Additional packages will also be displayed depending on the version of iOS. - ios ip [options] Uses the live pcap iOS packet capture to wait until it finds one that contains the IP address of the device. + ios ip [options] [--lazy] [--timeout=] Uses the live pcap iOS packet capture to wait until it finds one that contains the IP address of the device. > It relies on the MAC address of the WiFi adapter to know which is the right IP. - > You have to disable the "automatic wifi address"-privacy feature of the device for this to work. + > You have to disable the "automatic wifi address"-privacy feature of the device for this to work (if not possible, look at lazy option). > If you wanna speed it up, open apple maps or similar to force network traffic. - > f.ex. "ios launch com.apple.Maps" + > f.ex. "ios launch com.apple.Maps". + > If using lazy, it will listen for a predefined time, and will return the IP with the most requests, which does not require turning off randomized MAC. + > It is a good idea to launch e.g. apple maps before starting lazy IP finding, as it creates a lot of unique traffic. ios forward [options] Similar to iproxy, forward a TCP connection to the device. ios dproxy [--binary] Starts the reverse engineering proxy server. > It dumps every communication in plain text so it can be implemented easily. @@ -420,7 +422,22 @@ The commands work as following: b, _ = arguments.Bool("ip") if b { - ip, err := pcap.FindIp(device) + var ip pcap.NetworkInfo + var err error + + // determine timeout for commands + timeout, _ := arguments.Int("--timeout") + if timeout == 0 { + timeout = 10 + } + + lazy, _ := arguments.Bool("--lazy") + if !lazy { + ip, err = pcap.FindIPByMac(device, time.Second*time.Duration(timeout)) + } else { + ip, err = pcap.FindIPByLazy(device, time.Second*time.Duration(timeout)) + } + exitIfError("failed", err) println(convertToJSONString(ip)) return