Обычный способ исследования трафика при помощи цепочек из прокси серверов не работал, поскольку в macOS есть функция SIP.
Во время работы библиотекой Whereami меня заинтересовало, как на самом деле работают сервисы от Apple, связанные с обнаружением местонахождения. Я знаю, что этот протокол связан locationd, поскольку в Little Snitch эта служба остается заблокированной. Обычный способ исследования трафика при помощи цепочек из прокси серверов не работал, поскольку в macOS есть функция SIP (System Integrity Protection; Защита целостности системы).
Альтернативный путь: установка Charles в качестве промежуточного прокси-сервера на iOS-устройстве. После просеивания трафика, в основном связанного с оповещениями большого брата (device phoning home), я получил информацию о запросе служб, связанных с определением местонахождением.
Запрос к службам, связанным с местонахождением
Сам по себе запрос представляет собой application/x-www-form-urlencode с некоторыми бинарными данными.
POST /clls/wloc HTTP/1.1
Host: gs-loc.apple.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 97
Proxy-Connection: keep-alive
Accept: */*
User-Agent: locationd/1756.1.15 CFNetwork/711.5.6 Darwin/14.0.0
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive
00000000: 00 01 00 05 65 6e 5f 55 53 00 13 63 6f 6d 2e 61 ....en_US..com.a
00000010: 70 70 6c 65 2e 6c 6f 63 61 74 69 6f 6e 64 00 0c pple.locationd..
00000020: 38 2e 34 2e 31 2e 31 32 48 33 32 31 00 00 00 01 8.4.1.12H321....
00000030: 00 00 00 2d 12 13 0a 11 62 34 3a 35 64 3a 35 30 ...-....b4:5d:50
00000040: 3a 39 34 3a 33 39 3a 62 33 12 12 0a 10 39 38 3a :94:39:b3....98:
00000050: 31 3a 61 37 3a 65 36 3a 38 35 3a 37 30 18 00 20 1:a7:e6:85:70..
00000060: 64
Поскольку вышеуказанные данные не содержат gzip-заголовок 0x1f8b, мое второе предположение было связано с использованием Protocol buffers (механизм сериализации структурированной информации). В конце концов, эту платформу сейчас многие используют. Попробуем выполнить декодирование.
$ xxd -r request.hex | protoc --decode_raw
Failed to parse input.
Не сработало. Возможно, в запросе присутствуют дополнительные данные. Логических анализ подсказывает, что Mac-адреса должны входить в этот информационный массив (выделено жирным цветом ниже).
00000000: 00 01 00 05 65 6e 5f 55 53 00 13 63 6f 6d 2e 61 ....en_US..com.a
00000010: 70 70 6c 65 2e 6c 6f 63 61 74 69 6f 6e 64 00 0c pple.locationd..
00000020: 38 2e 34 2e 31 2e 31 32 48 33 32 31 00 00 00 01 8.4.1.12H321....
00000030: 00 00 00 2d 12 13 0a 11 62 34 3a 35 64 3a 35 30 ...-....b4:5d:50
00000040: 3a 39 34 3a 33 39 3a 62 33 12 12 0a 10 39 38 3a :94:39:b3....98:
00000050: 31 3a 61 37 3a 65 36 3a 38 35 3a 37 30 18 00 20 1:a7:e6:85:70..
00000060: 64
$ xxd -r request2.hex | protoc --decode_raw
Failed to parse input.
Опять не сработало. Верхняя часть выглядит как заголовок. Попробуем удалить этот кусок.
00000000: 00 01 00 05 65 6e 5f 55 53 00 13 63 6f 6d 2e 61 ....en_US..com.a
00000010: 70 70 6c 65 2e 6c 6f 63 61 74 69 6f 6e 64 00 0c pple.locationd..
00000020: 38 2e 34 2e 31 2e 31 32 48 33 32 31 00 00 00 01 8.4.1.12H321....
00000030: 00 00 00 2d 12 13 0a 11 62 34 3a 35 64 3a 35 30 ...-....b4:5d:50
00000040: 3a 39 34 3a 33 39 3a 62 33 12 12 0a 10 39 38 3a :94:39:b3....98:
00000050: 31 3a 61 37 3a 65 36 3a 38 35 3a 37 30 18 00 20 1:a7:e6:85:70..
00000060: 64
$ xxd -r request2.hex | protoc --decode_raw
Failed to parse input.
Не сработало опять.
После нескольких проб и ошибок я решил удалять по одному байту, начиная с самого начала, и затем выполнять декодирование. Ниже показана немного причесанная версия моего скрипта, автоматизирующего весь процесс:
#!/bin/bash
#
# Try to decode hidden protocol buffers message from binary
size=$(wc -c < $1)
for ((i=1; i<=$size; i++))
do
dd if=$1 bs=1 skip=$i | protoc --decode_raw
if [[ $? == 0 ]]
then
printf "\n"
read -p "Removed $i bytes, continue? [Yy] " -n 1 -r
printf "\n\n"
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
exit 0
fi
fi
done
protomower.sh
$ ./protomower.sh request.bin
Во время удаления байтов из первых трех строк ничего полезного на выходе не получалось. После перехода к удалению байтов из четвертой строки я получил осмысленный результат.
45+0 records in
45+0 records out
45 bytes transferred in 0.000063 secs (714938 bytes/sec)
2 {
1: "b4:5d:50:94:39:b3"
}
2 {
1: "98:1:a7:e6:85:70"
}
3: 0
4: 100
Removed 52 bytes, continue? [Yy]
Кажется, моя гипотеза оказалась близка к истине. Выделенные байты были удалены. Остальная часть, связанная с технологией protocol buffers, успешно декодирована.
00000000: 00 01 00 05 65 6e 5f 55 53 00 13 63 6f 6d 2e 61 ....en_US..com.a
00000010: 70 70 6c 65 2e 6c 6f 63 61 74 69 6f 6e 64 00 0c pple.locationd..
00000020: 38 2e 34 2e 31 2e 31 32 48 33 32 31 00 00 00 01 8.4.1.12H321....
00000030: 00 00 00 2d 12 13 0a 11 62 34 3a 35 64 3a 35 30 ...-....b4:5d:50
00000040: 3a 39 34 3a 33 39 3a 62 33 12 12 0a 10 39 38 3a :94:39:b3....98:
00000050: 31 3a 61 37 3a 65 36 3a 38 35 3a 37 30 18 00 20 1:a7:e6:85:70..
00000060: 64 d
В итоге выяснилось, что в сообщении, имеющим отношение к запросу, четыре типа данных. В терминах технологии protocol buffers эти типы называются тегами. Каждое сообщение имеет четыре тега.
Чтобы доказать нашу гипотезу, попробуем сделать запрос с разными mac-адресами. Я использовал hex-редактор для редактирования бинарного файла запроса, а затем сделал POST-запрос при помощи curl.
00000000: 00 01 00 05 65 6E 5F 55 53 00 13 63 6F 6D 2E 61 ....en_US..com.a
00000010: 70 70 6c 65 2e 6c 6f 63 61 74 69 6f 6e 64 00 0c pple.locationd..
00000020: 38 2e 34 2e 31 2e 31 32 48 33 32 31 00 00 00 01 8.4.1.12H321....
00000030: 00 00 00 2d 12 13 0a 11 36 34 3a 64 38 3a 31 34 ...-....64:d8:14
00000040: 3a 37 32 3a 36 30 3a 30 63 12 13 0a 11 31 30 3a :72:60:0c....10:
00000050: 62 64 3a 31 38 3a 35 66 3a 65 39 3a 38 33 18 00 bd:18:5f:e9:83..
00000060: 20 64
$ curl https://gs-loc.apple.com/clls/wloc --include --request POST --data-binary @request2.bin
HTTP/1.1 400 Bad Request
Date: Sun, 07 May 2017 06:26:06 GMT
Cneonction: Close
Content-Type: text/plain
X-RID: 62904d6c-fe93-47d5-b579-548f9c83297c
Content-Length: 11
Bad Request
Не работает. Попробуем разобраться, что не так.
Глядя на дамп, видно, что сообщение стало на 1 байт больше. То есть где-то должна быть еще контрольная сумма, что вполне очевидно. Шестнадцатеричное число 0x2d равно 45 в десятичной системе. Первоначальное сообщение было длиной 45 байт. Новое сообщение размером 46 байт, что эквивалентно 0x2e в шестнадцатеричной системе. Предположу, что контрольная сумма является целым 16-битным числом (то есть 0x002e).
00000000: 00 01 00 05 65 6E 5F 55 53 00 13 63 6F 6D 2E 61 ....en_US..com.a
00000010: 70 70 6c 65 2e 6c 6f 63 61 74 69 6f 6e 64 00 0c pple.locationd..
00000020: 38 2e 34 2e 31 2e 31 32 48 33 32 31 00 00 00 01 8.4.1.12H321....
00000030: 00 00 00 2e 12 13 0a 11 36 34 3a 64 38 3a 31 34 ...-....64:d8:14
00000040: 3a 37 32 3a 36 30 3a 30 63 12 13 0a 11 31 30 3a :72:60:0c....10:
00000050: 62 64 3a 31 38 3a 35 66 3a 65 39 3a 38 33 18 00 bd:18:5f:e9:83..
00000060: 20 64
$ curl https://gs-loc.apple.com/clls/wloc --include --request POST --data-binary @request3.bin
HTTP/1.1 200 OK
X-RID: bb3cc16a-6680-4019-b5d0-fb52e8c8bd5a
Content-Type: text/plain
Content-Length: 4948
Операция успешно завершена, и теперь мы знаем формат запросов.
[header][size][message] ([заголовок][размер][сообщение])
Сам заголовок можно разделить на части попозже. Изначально я думал, что эта информация представляла собой просто магический управляющий ASCII-код. Однако читатель из Reddit’a сориентировал меня в верном направлении. Скорее всего, в заголовке использовались байтовые строки с префиксом длины (length-prefix framed byte strings). Я также полагаю, что 0x0001 означает начало заголовка, а 0x0000 – окончание.
NUL SOH /* 0x0001 start of header */
[length] /* length of the locale string in bytes */
[locale] /* en_US */
[length] /* length of the identifier string in bytes */
[identifier] /* com.apple.locationd */
[length] /* length of the version string in bytes
[version] /* 8.4.1.12H321 ie. ios version and build */
NUL NUL /* 0x0000 end of header */
NUL SOH /* 0x0001 start of header */
NUL NUL /* 0x0000 end of header */
Я не уверен, что означают последние 4 байта. Возможно, указатель на второй заголовок, который на данный момент пуст.
Ответ служб, связанных с местонахождением
Размер ответа довольно внушителен.
00000000: 00 01 00 00 00 01 00 00 13 4a 12 40 0a 10 36 34 .........J.@..64
00000010: 3a 64 38 3a 31 34 3a 37 32 3a 36 30 3a 63 12 2c :d8:14:72:60:c.,
00000020: 08 80 98 f7 f8 bc ff ff ff ff 01 10 80 98 f7 f8 ................
00000030: bc ff ff ff ff 01 18 ff ff ff ff ff ff ff ff ff ................
00000040: 01 28 ff ff ff ff ff ff ff ff ff 01 12 30 0a 11 .(...........0..
00000050: 31 30 3a 62 64 3a 31 38 3a 35 66 3a 65 39 3a 38 10:bd:18:5f:e9:8
00000060: 33 12 18 08 a1 a9 d3 40 10 a0 8c db de 26 18 39 3......@.....&.9
00000070: 20 00 28 11 30 08 58 3c 60 ec 01 a8 01 06 12 2e .(.0.X<`.......
00000080: 0a 0f 30 3a 31 65 3a 31 33 3a 37 3a 39 30 3a 64 ..0:1e:13:7:90:d
...
000012F0: 01 01 12 2f 0a 10 30 3a 32 61 3a 31 30 3a 65 65 .../..0:2a:10:ee
00001300: 3a 35 30 3a 61 34 12 18 08 c0 a3 d5 40 10 b7 c1 :50:a4......@...
00001310: c8 de 26 18 2b 20 00 28 14 30 0e 58 3e 60 e8 01 ..&.+ .(.0.X>`..
00001320: a8 01 0b 12 2f 0a 10 30 3a 31 31 3a 32 31 3a 63 ..../..0:11:21:c
00001330: 63 3a 35 36 3a 33 32 12 18 08 8d ec c9 40 10 91 c:56:32......@..
00001340: 95 cf de 26 18 61 20 00 28 14 30 0f 58 3f 60 c3 ...&.a .(.0.X?`.
00001350: 1a a8 01 01
Здесь нас вновь выручаем метод, который мы использовали во время декодирования запроса. Расшифрованный ответ получился размером примерно 1400 строк.
$ ./protomower.sh response.bin
2 {
1: "64:d8:14:72:60:c"
2 {
1: 18446744055709551616
2: 18446744055709551616
3: 18446744073709551615
5: 18446744073709551615
}
}
2 {
1: "10:bd:18:5f:e9:83"
2 {
1: 135582881
2: 10399172128
3: 57
4: 0
5: 17
6: 8
11: 60
12: 236
}
21: 6
}
...
Removed 10 bytes, continue? [Yy]
Первое значение сразу же вводит в заблуждение. Десятичное число 18446744073709551615 в шестнадцатеричной системе равно 0xfffffffffffffff (то есть максимальное беззнаковое 64-битное значение). Вероятно, сей факт означает, что mac-адрес не найден. По поводу значения 18446744055709551616 (0xfffffffbcf1dcc00) у меня также нет никаких идей.
Смысл остальных результатов более очевиден:
Вначале меня озадачил факт получения 101 результата. Затем до меня дошло, что я получил 100 успешных результатов. Первые два результата – отправленные mac-адреса. Остальные результаты – mac-адреса, находящиеся в ближайшей доступности от тех, которые я отправил.
Но почему 100 результатов?
Я думаю, что Apple выгружает трехсторонние измерения клиенту. Вместо ресурсоемких расчетов возвращается набор координат точек доступа.
Если как минимум три точки доступа видны клиенту, фреймворк core location может использовать уровень сигнала в качестве расстояния. Когда у нас есть координаты трех точек доступа и расстояние до цели, мы можем рассчитать местонахождение цели с приемлемой точностью.
Ниже показаны координаты точек доступа, полученных во время запроса местонахождения аэропорта Changi.
Рисунок 1: Точки доступа в районе аэропорта Changi
Если у нас есть информация о сотне точек доступа вокруг, снижается необходимость в повторном соединение с сервером служб, связанных с местонахождением. Поскольку в core location есть координаты трех видимых точек доступа, можно вычислить местонахождение нашей точки. Подобные расчеты могут быть сделаны даже оффлайне, поскольку WiFi включен.
Как использовать эту возможность?
Вы можете написать поддержку Core location на уровне пользователя для тех языков программирования, где-то подобный функционал отсутствует. Хотя есть более простые способы решить ту же самую задачу.
Более интересная задача – развернуть свой собственный сервер служб, связанных с местоположением с целью облегчения отладки приложений.
Другие статьи по теме
Application à l’analyse des données de géolocalisation envoyées par un smartphone Авторы: François-Xavier Aguessy и Côme Demoustier. Я не умею читать на французском языке, но в этой статье есть примеры файлов .proto и код на языке Python, который помог мне начать погружение в эту тему. Хотя протокол скорее всего изменился с момента публикации этой статьи.
Vulnerability Analysis and Countermeasures for WiFi-based Location Services and Application Авторы: Jun Liang (Roy) Feng и Guang Gong. Статья полезна для общего понимания того, как работает определение местоположения в WiFi-сетях.
Gaussian Processes for Signal Strength-Based Location Estimation Авторы: Brian Ferris, Dirk Hahnel и Dieter Fox. Я не понимаю и половины математических выкладок. Однако в этой статье объясняются различные проблемы внутри механизма, связанного с определением местоположения в WiFi-сетях.