Skip to content

WIP: Find IP based on packets without MAC #302

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 131 additions & 50 deletions ios/pcap/ipfinder.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
}
27 changes: 22 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Usage:
ios httpproxy remove [options]
ios pair [--p12file=<orgid>] [--password=<p12password>] [options]
ios ps [--apps] [options]
ios ip [options]
ios ip [options] [--lazy] [--timeout=<sec>]
ios forward [options] <hostPort> <targetPort>
ios dproxy [--binary]
ios readpair [options]
Expand Down Expand Up @@ -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=<sec>] 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] <hostPort> <targetPort> 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.
Expand Down Expand Up @@ -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
Expand Down