Страницы

воскресенье, 16 июня 2013 г.

Нестандартный способ получить недоступную информацию на iOS

По следам своего выступления на 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

iOS Hackers handbook

Mac OS X internals

Автор: Кирилл Ермаков, исследовательский центр Positive Technologies.

Комментариев нет:

Отправить комментарий