Developer library apple ios

Исследуем iOS SDK и используем недокументированные API

Из этой главы, да и из всей этой книги понятно, что самые лакомые куски программирования под iOS включены в публичные фреймворки, но не в SDK. Неофициальная политика Apple насчет этого проста: вы можете всё это использовать, но только на свой страх и риск. Ваш код может сломаться при следующем обновлении прошивки. Вам самим придётся искать компромисс между риском и прибылью.

Дисклеймеры

Краткая инструкция по поиску в SDK

$ LookSDKForSymbol.sh light level
U _UIBacklightLevelChangedNotification
Found in ./System/Library/CoreServices/SpringBoard.app/SpringBoard

001b43c4 t -[UIApplication backlightLevel]
001b4360 t -[UIApplication setBacklightLevel:]
0025ce54 t -[UIDevice _backlightLevel]
0025ce40 t -[UIDevice _setBacklightLevel:]
… и ещё несколько десятков символов

Большую часть результатов можно сразу отбросить, например -[UIApplication backlightLevel] возвращает значение подсветки, а не устанавливает его.

Оставшиеся, если их не более нескольких десятков, можно попытаться скормить гуглу. Случается, что кто-то уже занимался исследованием API, связанных с найденными символами, и в этом случае задача, считай, решена. В более сложных случаях приходится заниматься реверс-инжинерингом, то есть выяснять, как работают найденные функции, как использовать найденных оповещения и тому подобное.

Символьные строки, выдаваемые утилитой, делятся на следующие категории:

  1. Objective-C и С++ функции, классы, структуры и так далее. Всё что относится к Objective-C содержит квадратные скобки([]) либо знаки доллара ($). C++ функции как правило содержатся в каком-нибудь namespace’е, и поэтому в их названии содержиться символ разрешения пространства имён, два двоеточия (::).
  2. Objective-C блоки. Они имеют следующий общий вид:

Например:

  • Pure C функции.
  • Objective-C оповещения. Заканчиваются на Notification, например _SBMenuButtonPressedNotification .
  • Ключи/константы. Обычно начинаются на k, например: _ kCFUserNotificationAlternateButtonTitleKey .
  • Дальнейшие действия зависят от категории символа.

    1. Генерируем заголовочный файл для данного фреймворка:

      class-dump-z Foundation > $/iOS_private_headers/Foundation.h

      В большинстве случаев, сгенерированного заголовчного файла достаточно: в нем должны быть довольно хорошо описаны иерархии наследования классов, структуры, методы и т.д, чтобы потратив немного времени можно разобраться с API и использовать его в своём предложении.

      К сожалению, иногда информации содержащейся в заголовочном файле недостаточно, чтобы заставить код работать, и тогда приходится анализировать ассемблерный код, сгенерированный otool.

      Hint по дизассемблированию Objective-C кода: почти наверняка вы столкнетесь с вызовами функций типа objc_msgSend (отправка сообщения объекту). В качестве первого параметра всегда идет указатель на объект, а вторым — указатель на селектор (selector), т.е. указатель на строку, являющуюся названием метода (остальные «обычные» аргументы идут третьим, четвертым и т.д. аргументами). Определить, что за сообщение отправляется в данном случае, поможет hexdump .

    2. Про это можно сразу забыть. Блоки (обычно) локальны, их нельзя вызвать из своего кода.
    3. Самый сложный вариант. В самых простых случаях можно подобрать сигнатуру для функции, в остальных — только дизассемблирование. Больше об этом можно узнать в разделе «Как узнать сигнатуру неизвестной функции?».
    4. Начнем с того, что попытаемся отловить оповещения в одном из трёх основных центров оповещений (это Local, Darwin и CoreTelephony). Если оповещения такого типа не приходят, дело может быть в одной из двух вещей:
      — Оповещения такого типа приходитя в отдельный, специальный центр оповещений. Cледует поискать следы такого центра оповещений в том же фреймворке, к каторому принадлежит найденное оповещение.
      — Доставка оповещений отключена. Попытаться найти механизм включения доставки оповещений такого типа.
    5. В этом случае, скорее всего существует либо функция, которая принимает данную константу в виде параметра, либо словарь, в котором данная константа является ключом. В любом случае, следует искать функцию или метод, название которых начинаются с того же слова (например: константа kLockdownDeviceColorKey -> функция lockdown_copy_value(. ) ;

    Как узнать сигнатуру неизвестной функции?

    1. Найти в интернете, как это не банально. Мне довольно часто попадались китайский сайты, были корейский и японский сайты с очень полезной информацией. Обычно самого кода уже достаточно, чтобы понять что происходит и как используется данная функция, данный класс и т.д. Спасибо многословности и выразительности Objective-C!
    2. Для многих простых функций, можно попытаться угадать сигнатуру. Внимание, это может быть довольно опасно.
    Использование некоторые простые функции, таких как GSEventSetBackLightLevel, самоочевидно.
    void GSEventSetBackLightLevel(float level);

    Для многих других я использовал следующий трюк (на примере функции SBGetRingerSwitchState):

    В результате работы этого кода выяснилось, что
    1) функция возвращала значение 0x10000003 , не зависящее от реального положения переключателя.
    2) Переменная out2 изменила свое значение на self. Возвращаемое значение также не зависит от переключателя.
    3) Остальные переменные не изменили свое значение.

    Из 1) я сделал вывод функция возвращает значене типа kern_return_t , так как 0x10000003 соответствует системной ошибке MACH_SEND_INVALID_ DEST . По видимому, ошибка указывала на неправильный порт [в данном случае порт — это абстракция ядра mach (mach kernel), характеризующая права и приоритет процесса]. Как правило, если в вызове функции используется номер порта, то он идет первым аргументом. Из 2) следует, что через второй аргумент функция возвращает некое значение по ссылке.

    В результате этих нехитрых действий получается следующая сигнатура:

    Кстати, если в названии функции присутствует слово get, то согласно naming conventions Objective-C эта функция должна возвращать значение по ссылке. Это также видно из приведенного примера.

    3. Дизассемблирование. На примере все той же SBGetRingerSwitchState. Используем otool:

    $ otool -p _SBGetRingerSwitchState -tV -arch armv6 SpringBoardServices | less
    000038cc b5f0 push
    000038ce af03 add r7, sp, #12
    000038d0 b092 sub sp, #72
    000038d2 aa06 add r2, sp, #24 // значение регистра r2 затирается
    000038d4 9205 str r2, [sp, #20]
    000038d6 ac08 add r4, sp, #32 // … как и регистра r4
    000038d8 ab0f add r3, sp, #60 // … и r3
    000038da 9304 str r3, [sp, #16]
    000038dc 9103 str r1, [sp, #12] // значение r1 сохраняется в стеке
    000038de 4925 ldr r1, [pc, #148] (0x3974)
    000038e0 6011 str r1, [r2, #0]
    000038e2 6020 str r0, [r4, #0] // значение r0 также сохраняется в стеке

    Из этого кода, используя даже поверхностные знания arm-ассемблера, можно предположить, что функция принимает два аргумента типа «слово» (word)
    Выходит, что у функции два аргумента. Идем дальше, в самый конец.


    00003964 9e04 ldr r6, [sp, #16]
    00003966 6836 ldr r6, [r6, #0]
    00003968 9903 ldr r1, [sp, #12]
    0000396a 600e str r6, [r1, #0]
    // примерно соответствует (в терминах языка си): *r1 = r6; т.е. по адресу, хранящемуся в r1 записывается значение из r6;
    // Это значит, что функция возвращает значение по ссылке
    0000396c 462e mov r6, r5
    0000396e 4630 mov r0, r6
    // результат выполнения функции помещается в r0
    00003970 b012 add sp, #72
    00003972 bdf0 pop

    В сухом остатке получаем:

    Продолжая анализировать этот асcемблерный код, уточняем типы и приходим к окончательному варианту:

    Разные прошивки и разные устройства: что может сломаться и как это исправить?

    Понятно, что недокументированные API вовсе не обязательно работают на всех устройствах одинаково. По моему опыту, чаще всего ничего не меняется, и API работает одинаково на всех устройствах и всех прошивках. Так, например, все функции расширения UIDevice-IOKitExtensions (кроме определения IMEI) работают одинаково хорошо на всех устройствах и всех прошивках. Какие изменения могут произойти при обновлении iOS?
    Вот несколько практических вариантов.

      Может появиться официально документированный программный интерфейс, при этом недокументированный интерфейс, как правило, продолжает работать. Пример:
  • Программные интерфейсы переносятся в другой фреймворк. Apple может объединить несколько фреймворков в один, переименовать или удалить фреймворк. Например, функции для работы с WiFi (Apple80211Open, Apple80211Close и так далее) были перенесены из Aeropuerto.dylib в IPConfiguration.dylib.
  • API могут просто удалить, особенно если она связана с уязвимостью.
  • Для того чтобы избежать проблем совместимости, соблюдайте простые правила: проверяйте наличие функций (например, с помощью -[NSObject respondsToSelector:] ), классов ( NSClassFromString(@»SomeClass») вернет nil в случае отсутствия класса SomeClass ) и т.д., а также заранее подумайте, что должна делать программа в случае, если API отсутствует. При использовании динамической линковки библотек следует также всегда проверять возвращаемые значения dlsym(. ) и dlopen(. ) на равенство NULL.

    Примеры
    Пример 1:

    Определение положения бокового переключателя вибро (a.k.a. Ring/Silent switch, Mute switch)

    Одной из задач, которые стояли передо мной, было определение положения бокового переключателя, который в оригинале называется ring/silent switch. Этот переключатель используется для переключения между «тихим» и обычном/«громким» режимами в айфоне и айпаде. Поиск по StackOverflow дал решение:

    Которое, впрочем, не работает в iOS 5. Не сработало и использование более нового API (kAudioSessionProperty_AudioRouteDescription) которое дает расширенную информацию об аудиовходах и -выходах. (AUDIOROUTE)

    Мои дальнейшие поиски по StackOverflow вывели меня на этот пост. В нем описывается библиотечная функция AudioServicesAddSystemSoundCompletion(), чьё нестандартное поведение рассматривалось разработчиками как баг.

    Нестандартное поведение заключается в том, что вызов колбэка MyAudioServicesSystemSoundCompletionProc состоится в конце проигрывания звука в обычном режиме, но сразу после вызова AudioServicesPlaySystemSound в «тихом» режиме. Это создает лазейку для определения текущего состояния переключателя. Если, например, длина аудиофайла что мы проигрываем равна 1 с, то разница во времени вызова MyAudioServicesSystemSoundCompletionProc() в «тихом» и громком режиме составляет 1 c. На этом я построил свое второе, асинхронное решение для определения положения бокового переключателя. Вот оно:

    Хотя это новое решение и было рабочим, оно не устраивало меня по нескольким причинам. Во-первых, оно было асинхронным и работало с ощутимой задержкой (около 1/10 секунды). Снижение задержки вело к ложным срабатываниям. Во-вторых, был побочный эффект — сам проигрываемый звук, который звучал достаточно громко чтобы смутить пользователя. Позже я искусственно выкрутил громкость в ноль в аудиоредакторе. В-третьих, это был уже слишком похоже на грязный хак, хотя это, например, не помешало создателям VSSilentSwitch продавать свое решение, по всей видимости основанное на том же эффекте.

    Примерно через месяц я вернулся к этой проблеме. Я начал использовать команду nm для поиска символов в объектных файлах, на её основе я написал простейший shell-скрипт, листинг которого можно найти ниже (В разделе «Инструменты»). Скрипт запускается с одним, двумя или тремя параметрами, каждый из которых представляет ключевое слово.

    /Documents/LookSDKForSymbol.sh RingerSwitch
    # часть результатов опущена
    0000d738 S _kGSRingerSwitchCapability
    Found in ./System/Library/PrivateFrameworks/GraphicsServices.framework/GraphicsServices
    000038cc T _SBGetRingerSwitchState
    0000370c T _SBGetRingerSwitchState
    Found in ./System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices

    Функция с названием SBGetRingerSwitchState выглядела многообещающе.

    Для получения нужного порта использовалась функция:

    из того же фреймворка.

    Вот что получилось в итоге:

    Пример 2:

    IMEI (International Mobile Equipment Identity) — уникальный идентификационный
    код, присваиваемый каждому телефону, своего рода MAC-адрес телефона (хотя MAC-адрес у телефона также есть)

    Я уже и не помню, как я вышел проект Эрики Садун uidevice-extension, но по мере того, как я с ним разбирался он всё больше казался мне этакой программистской «золотой жилой».

    Одна из категорий, UIDeviсe(IOKit_Extensions) содержит функции для определения IMEI. Я протестировал эти функции на iPhone 4 c iOS 5.1 и iPad c iOS 4.3, всё работало и я перешел к другим задачам. Но в ходе бета-тестирования выяснилось, что функция для определения IMEI не работает на новых устройствах: iPad 2, the new iPad и iPhone 4S. Для выяснения причин я отправился на StackOverflow, где мои опасения подтвердились. Поиски привели меня тогда к фреймворку под названием CoreTelephony.

    $ nm -g ./CoreTelephony | grep -i imei
    U _kCFAbsoluteTimeIntervalSince1970
    00053b28 S _kCTMobileEquipmentInfoIMEI
    00053ad4 S _kCTPostponementInfoIMEI
    00053ac4 S _kCTPostponementStatusErrorDefaultIMEI
    $ nm -g ./CoreTelephony | grep MobileEquipment
    000260e4 T __CTServerConnectionCopyMobileEquipmentInfo
    00053b34 S _kCTMobileEquipmentInfo1xIMSI
    00053b20 S _kCTMobileEquipmentInfoCurrentMobileId
    00053b24 S _kCTMobileEquipmentInfoCurrentSubscriberId
    00053b40 S _kCTMobileEquipmentInfoERIVersion
    00053b2c S _kCTMobileEquipmentInfoICCID
    00053b28 S _kCTMobileEquipmentInfoIMEI
    00053b30 S _kCTMobileEquipmentInfoIMSI
    00053b38 S _kCTMobileEquipmentInfoMEID
    00053b44 S _kCTMobileEquipmentInfoMIN
    00053b3c S _kCTMobileEquipmentInfoPRLVersion

    Можно предположить что функция (_CTServerConnectionCopyMobileEquipmentInfo(. )) возвращает словарь(CFDictionaryRef) c ключами вида kCTMobileEquipmentInfo* и соответствующими им значениями. К счастью, на этот раз мне не пришлось восстанавливать сигнатуру. Поиск в гугле по запросу _CTServerConnectionCopyMobileEquipmentInfo привел меня на эту страничку, и вскоре функция для определения IMEI была готова.

    Этот метод определения IMEI работает на всех устройствах.
    Позже я нашел еще один метод определения IMEI (через lockdownd).

    Пример 3:

    Использование недокументированных оповещений: нажатия кнопок громкости.

    Изначально я наивно полагал, что любая символьная константа, заканчивающаяся на «Notification» является названием системного оповещения и её можно использовать, просто зарегистрировав наблюдателя (observer) с помощью [NSNotificationCenter defaultCenter].

    /Documents/LookSDKForSymbol.sh notification$ volume change
    001dbe60 S _MPAVControllerVolumeDidChangeNotification
    001dbe64 S _MPAVControllerVolumeMutedDidChangeNotification
    001dc4f8 S _MPMusicPlayerControllerVolumeDidChangeNotification
    001dc314 S _MPVolumeViewRouteButtonChangedNotification
    001dc310 S _MPVolumeViewVisibilityChangedNotification
    Found in ./System/Library/Frameworks/MediaPlayer.framework/MediaPlayer

    000d6d24 D _AVController_EffectiveVolumeDidChangeNotification
    000d6d60 D _AVController_VolumeDidChangeNotification
    000d6fec D _AVSystemController_CurrentRouteHasVolumeControlDidChangeNotification
    000d6ffc D _AVSystemController_EffectiveVolumeDidChangeNotification
    000d6fdc D _AVSystemController_SystemVolumeDidChangeNotification
    Found in ./System/Library/PrivateFrameworks/Celestial.framework/Celestial
    … и еще около десятка из других фреймворков

    Написав тестовую программу, я принялся проверять, какие оповещения приходили в ответ на нажатия клавиш громкости.
    Из составленного мной довольно большого списка опопвещений приходили только вот эти 2:

    Недостаток этих оповещений в том, что
    1) Нельзя напрямую определить, какая из двух кнопок была нажата
    2) Нельзя отследить, когда нажата и когда отпущена каждая из кнопок

    Ищу по другим ключевым словам:

    /Documents/LookSDKForSymbol.sh volume button
    001b221c t -[UIApplication setWantsVolumeButtonEvents:]
    003cce5c t _SBSetWantsVolumeButtonEvents$shim
    0054478c S __UIApplicationVolumeDownButtonDownNotification
    00544790 S __UIApplicationVolumeDownButtonUpNotification
    00544784 S __UIApplicationVolumeUpButtonDownNotification
    00544788 S __UIApplicationVolumeUpButtonUpNotification
    Found in ./System/Library/Frameworks/UIKit.framework/UIKit
    … и еще несколько десятков из разных фреймворкрв

    Четыре оповещения из UIKit сработали не сразу: необходимо было подать связанную с ними команду.

    После этого стали приходить оповещения о нажатиях соответствующих кнопок.
    Побочный эффект: вызов данной функции приводит к тому, что кнопки громкости больше не регулируют громкость, так что по завершении работы с кнопками следует вызвать

    Пример 4:

    Использование недокументированных оповещений: отслеживание статуса SIM-карты

    Работаем по проверенной схеме:

    /Documents/LookSDKForSymbol.sh notification$ SIM

    00052560 S _kCTSIMSupportSIMInsertionNotification
    00052564 S _kCTSIMSupportSIMStatusChangeNotification

    000525bc S _kCTSIMSupportSIMTrayStatusNotification

    Found in ./System/Library/Frameworks/CoreTelephony.framework/CoreTelephony

    Found in ./System/Library/PrivateFrameworks/FTServices.framework/FTServices
    $

    Наиболее подходящими мне показались оповещения под названиями:
    1) kCTSIMSupportSIMInsertionNotification
    2) kCTSIMSupportSIMStatusChangeNotification
    3) kCTSIMSupportSIMTrayStatusNotification

    Простейшая тестовая программа показала, что оповещения под названием (1) приходили только в момент вставки сим-карты (я мог бы догадаться и раньше по названию), (2) приходили именно тогда когда мне нужно (при вставке и вынимании), оповещения (3) не приходили вообще. Позже я узнал, что оповещения (3) относятся к специальному центру оповещений под названием CTTelephonyCenter. Об использовании CTTelephonyCenter можно прочитать здесь.

    Источник

    Читайте также:  Самый продаваемый айфон 2021 года
    Оцените статью