По следам своего выступления на Positive Hack Days я хотел бы поделиться с вами результатами исследования демона configd на MACH-уровне в iOS 6. Как вы знаете, в iOS доступно не так много информации о состоянии подключения Wi-Fi. В общем-то, Public API не дает возможности узнать ничего, кроме SSID, BSSID и сетевых настроек адаптера. А режим шифрования? мощность сигнала? Под катом я расскажу, как узнать все это без применения Private API и Jailbreak.
Заранее прошу прощения, но в этой статье я буду выкладывать много исходников. Для начала давайте вспомним, как это делалось раньше, на прошивке iOS 5.*. Использовался Apple System Log facility: можно было получить системные сообщения, которые ОС выводит в момент подключения к сети. В них фигурировали режим шифрования и мощность сигнала. А получали мы их вот так:
aslmsg asl, message;
aslresponse searchResult;
int i;
const char *key, *val;
NSMutableArray *result_dicts = [NSMutableArray array];
asl = asl_new(ASL_TYPE_QUERY);
if (!asl)
{
DDLogCError(@"Failed creating ASL query");
}
asl_set_query(asl, "Sender", "kernel", ASL_QUERY_OP_EQUAL);
asl_set_query(asl, "Message", "AppleBCMWLAN Joined BSS:", ASL_QUERY_OP_PREFIX|ASL_QUERY_OP_EQUAL);
searchResult = asl_search(NULL, asl);
while (NULL != (message = aslresponse_next(searchResult)))
{
NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
for (i = 0; (NULL != (key = asl_key(message, i))); i++)
{
NSString *keyString = [NSString stringWithUTF8String:(char *)key];
val = asl_get(message, key);
NSString *string = [NSString stringWithUTF8String:val];
[tmpDict setObject:string forKey:keyString];
}
[result_dicts addObject:tmpDict];
}
aslresponse_free(searchResult);
asl_free(asl);
Но, как это водится у Apple, узнав об этом, они закрыли доступ к системным сообщениям в ASL. Пришлось искать новый путь для получения этих данных. Тогда вопрос был поставлен по-другому: как вообще можно получить эти данные на Mac OS и iOS?
Прежде всего, при помощи утилиты scutil, которая позволяет получить данные о конфигурации системы, в том числе и необходимые нам. Тестовый iPhone с iOS 6 и Jailbreak показал, что утилита исправно отрабатывает и на нем. Для мня это стало намеком, путеводный нитью, и я начал искать, как еще можно «дотянуться» до SystemConfiguration на iOS.
Путь оказался прост и тривиален до безумия — библиотека SystemConfiguration.framework. С ее помощью на Mac OS можно программно подключаться к хранилищу значений и получать property list с данными о беспроводных сетях.
Но если посмотреть заголовочный файлы этой библиотеки на iOS, то становится грустно: использование требуемого метода запрещено.
CFPropertyListRef
SCDynamicStoreCopyValue (
SCDynamicStoreRef store,
CFStringRef key
) __OSX_AVAILABLE_STARTING(__MAC_10_1,__IPHONE_NA);
Для начала убедимся, что метод вообще работоспособен.
void *handle = dlopen("/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration", RTLD_LAZY);
CFArrayRef (*_SCDynamicStoreCopyKeyList)(int store, CFStringRef pattern) = dlsym(handle, "SCDynamicStoreCopyKeyList");
NSLog(@"Lib handle: %u", handle);
NSString *key = @"State:/Network/Global/DNS";
CFArrayRef testarrray = _SCDynamicStoreCopyKeyList(0, CFSTR("State:/Network/Interface/en0/AirPort"));
NSLog(@"Tested array res: %@", testarrray);
Все отлично, результат возвращается. Значит, у нас нет никаких программных блокировок, кроме формального запрета Apple, который не даст возможности пройти валидацию в AppStore. Впрочем, почему бы нам не написать кусочек этой библиотеки самостоятельно?
Исходники найти оказалось очень просто: это часть демона configd. Самое интересное начинается, когда читаешь описание функции SCDynamicStoreCopyValue.
#include "config.h" /* MiG generated file */
...
/* send the key & fetch the associated data from the server */
status = configget(storePrivate->server,
myKeyRef,
myKeyLen,
&xmlDataRef,
(int *)&xmlDataLen,
&newInstance,
(int *)&sc_status);
Окей. Идет обращение к сгенерированному при помощи MACH Interface Generator файлу. Соответственно, имеем описание на языке MIG, лежащее в файле неподалеку.
routine configget ( server : mach_port_t;
key : xmlData;
out data : xmlDataOut, dealloc;
out newInstance : int;
out status : int);
После этого у вас есть два пути — путь нормального человека и путь джедая. Вы могли бы запустить утилиту mig на файл config.defs и получить коды для вставки в проект. Но, к сожалению, на момент исследования мы этот файл не обнаружили и пришлось заняться реверс-инжинирингом :) Джедайствовал в итоге Дима Скляров, который смог восстановить процесс обращения к MACH-порту configd. С его помощью получилось восстановить метод целиком.
#define kMachPortConfigd "com.apple.SystemConfiguration.configd"
-(NSDictionary *)getSCdata:(NSString *)key
{
if(SYSTEM_VERSION_LESS_THAN(@"6.0"))
{
// It does not work on iOS 5.*
return nil;
}
struct send_body {mach_msg_header_t header; int count; UInt8 *addr; CFIndex size0; int flags; NDR_record_t ndr; CFIndex size; int retB; int rcB; int f24; int f28;};
mach_port_t bootstrapport = MACH_PORT_NULL;
mach_port_t configport = MACH_PORT_NULL;
mach_msg_header_t *msg;
mach_msg_return_t msg_return;
struct send_body send_msg;
// Make request
CFDataRef extRepr;
extRepr = CFStringCreateExternalRepresentation(NULL, (__bridge CFStringRef)(key), kCFStringEncodingUTF8, 0);
// Connect to Mach MIG port of configd
task_get_bootstrap_port(mach_task_self(), &bootstrapport);
bootstrap_look_up2(bootstrapport, kMachPortConfigd, &configport, 0, 8LL);
// Make request
send_msg.count = 1;
send_msg.addr = (UInt8*)CFDataGetBytePtr(extRepr);
send_msg.size0 = CFDataGetLength(extRepr);
send_msg.size = CFDataGetLength(extRepr);
send_msg.flags = 0x1000100u;
send_msg.ndr = NDR_record;
// Make message header
msg = &(send_msg.header);
msg->msgh_bits = 0x80001513u;
msg->msgh_remote_port = configport;
msg->msgh_local_port = mig_get_reply_port();
msg->msgh_id = 20010;
// Request server
msg_return = mach_msg(msg, 3, 0x34u, 0x44u, msg->msgh_local_port, 0, 0);
if(msg_return)
{
if (msg_return - 0x10000002u >= 2 && msg_return != 0x10000010 )
{
mig_dealloc_reply_port(msg->msgh_local_port);
}
else
{
mig_put_reply_port(msg->msgh_local_port);
}
}
else if ( msg->msgh_id != 71 && msg->msgh_id == 20110 && msg->msgh_bits <= -1 )
{
if ((send_msg.flags & 0xFF000000) == 0x1000000)
{
CFDataRef deserializedData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, send_msg.addr,send_msg.size0, kCFAllocatorNull);
CFPropertyListRef proplist = CFPropertyListCreateWithData(kCFAllocatorDefault, deserializedData, kCFPropertyListImmutable, NULL, NULL);
mig_dealloc_reply_port(msg->msgh_local_port);
mach_port_deallocate(mach_task_self(), bootstrapport);
mach_port_deallocate(mach_task_self(), configport);
mach_msg_destroy(msg);
NSDictionary *property_list = (__bridge NSDictionary*)proplist;
if(proplist)
CFRelease(proplist);
CFRelease(deserializedData);
CFRelease(extRepr);
return property_list;
}
}
mig_dealloc_reply_port(msg->msgh_local_port);
mach_port_deallocate(mach_task_self(), bootstrapport);
mach_port_deallocate(mach_task_self(), configport);
mach_msg_destroy(msg);
CFRelease(extRepr);
return nil;
}
Интересующие нас значения располагаются по ключу @«Setup:/Network/Interface/en0/AirPort».
Итак, мы реализовали часть SystemConfiguration.framework самостоятельно и получили необходимые данные не прибегая к Jailbreak или незаконному использованию библиотек. Что любопытно, в iOS 6 имеются больше 100 открытых для подключения MACH-портов с самыми разнообразными именами. Мне кажется, это дает довольно богатую почву для различных исследований. К сожалению, пока я не могу сказать, проходит ли подобный код в AppStore, но попытаться определенно стоит.
Спасибо за внимание.
Ссылки:
— MACH Kernel programming guide
Автор: isox