Сегодня я расскажу вам о том, как можно легко, быстро и просто написать консольную программку для запуска на iOS-девайсе. Разумеется, нам потребуется для этого jailbreak-нутый девайс, без него, увы, никак: iOS AppStore (он же iTunesStore) не позволяет распространять консольные утилиты.
Писать HelloWorld — дело не особо интересное. Поэтому, мы будем писать полезную утилиту, позволяющую просмотреть некоторую информацию о системе, полученную через приватные API.
К примеру, информацию об установленных программах и их версиях.
В принципе, можно ещё поворовать пароли и прочие персональные данные, но это оставлю как факультативное задание.
Итак, под катом — описание процесса создания консольной программки прямо в Xcode.
Создаём проект в Xcode: выбираем темплейт «Empty Application», придумываем имя программе (в моём случае — hackup). Что ж, у нас получилось пустое GUI-приложение, но ведь мы не этого хотели, не так ли? Смело удаляем из проекта лишние файлы!
Что ещё? Нам не нужен GUI, так что лишние фреймворки тоже убираем.
Теперь идём в свойства проекта, точнее — в свойства таргета. Там убираем подписывание кода.
Далее, нам не нужен Info.plist, смело сносим и его упоминание.
Бандл нам тоже не нужен: поэтому пусть итоговая программа будет лежать в фейковом бандле с расширением ".console"
Что ж, теперь немного подправим код в main.m: уберём импорт UIKit
и AppDelegate
. В .pch
тоже уберём лишнее, оставив лишь Foundation.h
. Из функции main()
так же убираем UIApplication
, поставим просто return 0.
Ну вот, можем теперь попробовать собрать проект для симулятора! Да, всё именно так просто. Но как же запустить теперь нашу замечательную ничегонеделающую программу? Ведь в симуляторе терминала нет? Да всё просто: это же симулятор, а не эмулятор, так что наша программа суть обычная программа для макоси. Но запустить её даже из терминала — штука непростая, ведь она требует фреймворки, собранные для симулятора!
Начинаем вспоминать теорию. Все либы и фреймворки ищутся и загружаются программой dyld, которая опирается на некоторые флаги, полное описание которых содержится в мане. Нас интересует параметр DYLD_ROOT_PATH
, то есть, путь, к которому dyld «приделывает» все пути к фреймворкам и либам в файле.
Ну что ж, заходим в терминале в папку с собранной для симулятора программой. Для этого выбираем «Show In Finder» для нашего таргета.
Затем можно просто перетащить hackup.console
в терминал с набранным заранее cd
.
$ pwd /Users/silvansky/Library/Developer/Xcode/DerivedData/hackup-cbpgmjwwgsfnqlgjnrlcnbjcxnmu/Build/Products/Debug-iphonesimulator/hackup.console $ ./hackupdyld: Symbol not found: _OBJC_CLASS_$_NSString Referenced from: /Users/silvansky/Library/Developer/Xcode/DerivedData/hackup-cbpgmjwwgsfnqlgjnrlcnbjcxnmu/Build/Products/Debug-iphonesimulator/hackup.console/./hackup Expected in: /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation in /Users/silvansky/Library/Developer/Xcode/DerivedData/hackup-cbpgmjwwgsfnqlgjnrlcnbjcxnmu/Build/Products/Debug-iphonesimulator/hackup.console/./hackup [1] 61835 trace trap ./hackup $ otool -L hackup hackup: /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 992.0.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 227.0.0) /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 65.0.0) /usr/lib/libSystem.dylib (compatibility version 1.0.0, current version 125.0.0)
Мы видим, что сейчас наша программа ищет фреймворк Foundation
в стандартной системной папке. Разумеется, он отличается он нужного нам фреймворка, собранного для симулятора.
Лечим: находим наш iOS SDK в бандле Xcode.app, прописываем:
$ DYLD_ROOT_PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.0.sdk" ./hackup
Ошибок нет! Программа запустилась, но по прежнему ничего не делает. Исправим это!
Для начала напишем простенькую функцию для вывода чего-либо в stdout
, замену NSLog
. Я назвал свою NSPrintf
, вот её код:
void NSPrintf(NSString *format, ...) { va_list args; va_start(args, format); NSString *message = [[[NSString alloc] initWithFormat:format arguments:args] autorelease]; va_end(args); std::cout << [message cStringUsingEncoding:NSUTF8StringEncoding]; }
Внимательный читатель заметит, что реальный вывод осуществляется через std::cout
, собственно, поэтому не забываем переименовать main.m
в main.mm
и подключить iostream
.
Что ж, теперь мы умеем печатать в консоль, но что мы будем печатать?
Здесь начинается самое интересное. Допустим, мы хотим получить список установленных программ. Публичные API не позволяют нам этого сделать. А как насчёт приватных? А где их взять? А как же отсутствие документации?
Ох, сложные это вопросы. Документации к приватным API, разумеется, нет. Хоть какого-то разумного описания — тоже. Но зато в нашем распоряжении есть замечательная штука — реверс-инженеринг! С его помощью были получены недостающие хедеры для публичных и приватных фреймворков. Собственно, есть такой познавательный репозиторий: iOS-Runtime-Headers (и есть инструмент, с помощью которого эти хедеры и были получены: RuntimeBrowser, спасибо доброму человеку Nicolas Seriot). Немного почитаем хедеры и поищем в них что-либо, связанное с программами. Рано или поздно, но наткнёмся мы на метод - (id)applications
у класса ISSoftwareMap
, включённого в приватный фреймворк iTunesStore
. Что ж, зададимся целью вызвать этот метод и распечатать что бы он нам ни вернул!
Заметим, что мы нельзя просто так взять и добавить приватный фреймворк к проекту. Поэтому мы будем его подгружать на лету, что очень удобно делать с помощью класса NSBundle
. Напишем вспомогательную функцию, которая будет пытаться загрузить приватный фреймворк:
BOOL loadPrivateFramework(NSString *framework) { NSString *path = [NSString stringWithFormat:@"/System/Library/PrivateFrameworks/%@.framework", framework]; NSBundle *b = [NSBundle bundleWithPath:path]; BOOL success = [[[b retain] autorelease] load]; if (!success) { NSPrintf(@"Failed to load private framework %@!\n", framework); } return success; }
Если функция вернула YES
, то мы можем работать с загруженным фреймворком, а точнее, нам становятся доступны классы, имеющиеся в нём. Теперь нам надо получить класс ISSoftwareMap
, что можно сделать таким способом:
Class ISSoftwareMap = NSClassFromString(@"ISSoftwareMap");
Как мы уже поняли из хедеров, нам нужно вызвать + (id)currentMap
или + (id)loadedMap
для получения экземпляра класса.
id isSoftwareMap = [ISSoftwareMap performSelector:@selector(currentMap)]; if (!isSoftwareMap) { isSoftwareMap = [ISSoftwareMap performSelector:@selector(loadedMap)]; }
Ну вот мы и научились работать с приватными фреймворками! Мои поздравления! =)
Теперь наконец получим список установленных программ:
id *applications = [isSoftwareMap performSelector:@selector(applications)]; NSPrintf(@"applications:\n%@\n", applications);
Итак, теперь протестируем нашу программу в симуляторе! Что-то? Не работает? Ну да, не работает, у нас ведь пути для загрузки фреймворков абсолютные прописаны. Так что давайте лучше запустим на устройстве. В моём случае это iPad первой версии.
Что-что? Не собирается? Ругается на неподписанность? Ну да, я ведь забыл сказать: iOS SDK не позволяет собирать бинарники для девайса без подписи. Но ведь мы твёрдо решили добиться своего! Будем править настройки SDK. Находим файлик SDKSettings.plist
в /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/
и правим его. Что, не вышло? Вы не владелец файла? Ну да, политики безопасности и всё такое… Но у нас есть sudo:
$ sudo plutil -convert xml1 SDKSettings.plist $ sudo nano SDKSettings.plist $ sudo plutil -convert binary1 SDKSettings.plist
Вместо nano, разумеется, можно использовать vim/mcedit/emacs и даже Sublime Text 2. В редакторе надо найти в XML-ке тег CODE_SIGNING_REQUIRED
и установить его значение в NO.
Теперь перезапускаем Xcode и разуемся — наша проблема решена! Теперь проект собирается. Не терпится уже закинуть его на девайс! Для этого будем пользоваться OpenSSH (ставим через Cydia):
$ pwd /Users/silvansky/Library/Developer/Xcode/DerivedData/hackup-cbpgmjwwgsfnqlgjnrlcnbjcxnmu/Build/Products/Debug-iphoneos/hackup.console $ scp hackup root@192.168.2.2:/private/var/mobile/Documents/ root@192.168.2.2's password: hackup 100% 26KB 26.3KB/s 00:00 $ ssh mobile@192.168.2.2 mobile@192.168.2.2's password: iSilvansky:~ mobile$ ~/Documents/hackup ( "<ISSoftwareApplication: 0x18b4e0>: (ru.mail.agent, 335315530:11499676)", "<ISSoftwareApplication: 0x18d010>: (com.getdropbox.Dropbox, 327630330:11201748)", # ... some more ... "<ISSoftwareApplication: 0x1936f0>: (8HLDK844H7.net.litchie.idos, 377135644:2716751)" )
Собственно, мы видим, что метод - (id)applications
возвращает нам NSArray
, содержащий объекты типа ISSoftwareApplication
. Описание этого класса так же находим в приватных хедерах того же фреймворка. Что ж, список программ получили, давайте же посмотрим на них попристальнее:
NSArray *applications = [isSoftwareMap performSelector:@selector(applications)]; if (applications) { for (id app in applications) { NSPrintf(@" *** Info for application %@\n", app); LOG_SELECTOR(app, bundleIdentifier) LOG_SELECTOR(app, bundleShortVersionString) LOG_SELECTOR(app, bundleVersion) LOG_SELECTOR(app, accountDSID) LOG_SELECTOR(app, accountIdentifier) LOG_SELECTOR(app, softwareType) LOG_SELECTOR(app, versionIdentifier) LOG_SELECTOR(app, itemIdentifier) LOG_SELECTOR(app, containerPath) LOG_SELECTOR(app, storeFrontIdentifier) LOG_SELECTOR(app, description) } }
Макрос LOG_SELECTOR
определён так:
#define LOG_SELECTOR(obj, sel)\ if ([obj respondsToSelector:@selector(sel)])\ {\ NSPrintf(@" "#sel": %@\n", [obj performSelector:@selector(sel)]);\ }
Он немного упрощает получение и вывод информации. Тестируем!
iSilvansky:~ mobile$ ~/Documents/hackup *** Info for application <ISSoftwareApplication: 0x16d5d0>: (ru.mail.agent, 335315530:11499676) bundleIdentifier: ru.mail.agent bundleShortVersionString: 4.0 bundleVersion: 3815 accountDSID: 407343733 accountIdentifier: habrahabr.ru/users/silvansky/ softwareType: (null) versionIdentifier: 11499676 itemIdentifier: 335315530 containerPath: /private/var/mobile/Applications/374BF6DB-8773-4063-9D84-F5858DE7AEBE storeFrontIdentifier: 143441 description: <ISSoftwareApplication: 0x16d5d0>: (ru.mail.agent, 335315530:11499676) *** Info for application <ISSoftwareApplication: 0x16f100>: (com.getdropbox.Dropbox, 327630330:11201748) bundleIdentifier: com.getdropbox.Dropbox # ... many more ...
Подробная информация нам может быть и пригодится, но лучше выводить только bundle id, так что уберём весь лишний вывод (или отключим до лучших времён).
Теперь вывод нашей программы несколько упростился:
iSilvansky:~ mobile$ ~/Documents/hackup ru.mail.agent com.getdropbox.Dropbox # ... more and more ... 8HLDK844H7.net.litchie.idos iSilvansky:~ mobile$ exit logout Connection to 192.168.2.2 closed.
Можем теперь этот вывод грепать, можем слать себе на мыло, можем… Да всё что захотим мы можем! Но главное: мы теперь умеем пользоваться приватными API и писать косольные программы для iOS. Теперь можно написать что-нибудь полезное и выложить в Cydia.
Собственно, полный проект для Xcode можно, как обычно, найти на гитхабе.
Что планируется сделать дальше (разумеется, с подробным описанием в статьях):
- создание твика на основе Theos
- выкладывание программы в Cydia
- создание своего репозитория для Cydia
- теория и практика создания и распространения зловредов для iOS (проникновение в систему через скачанный с торрентов/installous .ipa файл)
- реверс-инженеринг программ и библиотек для iOS и OS X
ссылка на оригинал статьи http://habrahabr.ru/post/163281/
Добавить комментарий