Страницы

воскресенье, 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.

1 комментарий:

  1. Thanks for sharing, nice post! Post really provice useful information!

    Hương Lâm chuyên cung cấp bán máy photocopy và dịch vụ cho thuê máy photocopy giá rẻ, uy tín TP.HCM với dòng máy photocopy toshiba và dòng máy photocopy ricoh uy tín, giá rẻ.

    ОтветитьУдалить