Анализ безопасности iOS-приложений для хранения паролей

Анализ безопасности iOS-приложений для хранения паролей

За последнее время securitylab опубликовал множество увлекательных статей по пентестингу iOS-приложений. Желая испытать свои силы в этой области, я решил провести анализ безопасности трех так называемых парольных менеджеров для iOS.

Автор: I.S.P.

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

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

1. Pass Manager

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

Анализ: При первом входе приложение просит ввести мастер-пароль. В качестве мастер пароля здесь и далее будем использовать P@ssw0rd. Введем также случайные реквизиты банковской карты и данные учетной записи:


Рис. 1, 2, 3 Интерфейс приложения Pass Manager

Зайдем по SSH на устройство и перейдем в папку с приложением. По умолчанию все приложения устанавливаются в папку /var/mobile/Applications/:

root# cd /var/mobile/Applications/*/PassManager.app

root# cd ..

После недолгого исследования домашней папки приложения, мы можем найти подозрительный файл passman.pm в папке /Documents. С помощью команды cat мы узнаём, что этот файл не что иное, как бинарный plist-файл (сигнатура bplist):

Риc. 4 Содержимое файла passman.pm

Но уже и в таком формате, можно заметить, что мастер-пароль приложения хранится в этом файле в открытом виде.

Скопируем файл passman.pm на Mac и преобразуем его в удобочитаемый xml-формат c помощью утилиты plutil:

$ plutil –convert xml1 passman.pm

В результате файл конвертируется в xml-формат, и мы может отыскать в нем следующие строчки:

Рис. 5, 6 Конфиденциальные данные пользователя

Это и есть те реквизиты банковской карты и учетные данные, которые мы вводили ранее! И они тоже хранятся незашифрованными.

После еще небольшого исследования приложения в папке /Library/Preferences/ мы можем найти файл com.gonzie.apple.passman.plist, который хранит мастер-пароль в открытом виде:

Рис. 7 com.apple.passman.plist

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

Примечание: приложение тестировалось на iPhone 4S с установленным джейлбрейком, но и на устройстве без джейлбрейка доступ к указанным файлам (passman.pm и com.gonzie.apple.passman.plist) можно легко получить, например, с помощью программы iExplorer.

2. Pass Vault

Описание приложения: приложение Pass Vault предназначено для хранения учетных данных сайтов. Для защиты учетных данных используется 256-битное AES шифрование; среди других функций приложения можно отметить синхронизацию в iCloud, автоблокировку и стирание данных после определенного числа неудачных попыток ввода пароля.

Анализ: Внешне похожий на предыдущее приложение, Pass Vault также предлагает при входе ввести мастер-пароль (P@ssw0rd). Далее введем информацию одной из учетных записей:

Рис. 8, 9 Интерфейс приложения Pass Vault

Теперь в папке приложения /Library/Preferences/ можно найти файл com.kmp.passvault.plist со следующим содержимым:

Рис. 10 Содержимое файла com.kmp.passvault.plist

Очевидно, что здесь не всё так очевидно, как в предыдущем приложении J, и для хранения ключа и учетных данных используется хеширование и/или шифрование. Для того чтобы понять, как именно приложение хранит данные, дизассемблируем исполняемый файл, и посмотрим, что в нем происходит. А пока просто обратим внимание на две строчки, выделенные красным, чуть позже они нам пригодятся.

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

Итак, после дизассемблирования и анализа дизассемблированного файла, мы узнаем, что при установке пароля вызывается метод [PassVaultDataSource setPassword:], который в качестве аргумента получает новый пароль, далее происходит следующее:

void __cdecl -[PassVaultDataSource setPassword:](struct PassVaultDataSource *self, SEL a2, id new_pass)
<...>
v3 = self;
v4 = objc_msgSend(&OBJC_CLASS___SHA256, "hashedValue:andData:");
v5 = objc_retainAutoreleasedReturnValue(v4);
v6 = v3->_password;
v3->_password = (struct NSString *)v5;
objc_release(v6);
v7 = objc_msgSend(v3->_password, "AES256EncryptWithKey:", CFSTR("Dui;f833f#4*&6?>%$#flsk"));
v24 = objc_retainAutoreleasedReturnValue(v7);
<...>

Следовательно, программа сначала вычисляет хеш от пароля, а затем шифрует получившийся хеш на ключе Dui;f833f#4*&6?>%$#flsk. Программа также реализует методы [SHA256 hashedValue:andData:], [NSString(AESCrypt) AES256EncryptWithKey:] и [NSData(AESCrypt) AES256EncryptWithKey:]. Рассмотрим их по порядку:

id __cdecl +[SHA256 hashedValue:andData:](struct SHA256 *self, SEL a2, id hValue, id hData)
<…>
v4 = hValue;
v5 = hData;
v19 = __stack_chk_guard;
v6 = objc_retain(hValue);
v7 = objc_retain(v5);
v8 = (void *)objc_retainAutorelease(v4);
v9 = (const char *)objc_msgSend(v8, "cStringUsingEncoding:", 4);
objc_release(v6);
v10 = (void *)objc_retainAutorelease(v5);
v11 = (const char *)objc_msgSend(v10, "cStringUsingEncoding:", 4);
objc_release(v7);
strlen(v9);
v16 = strlen(v11);
v17 = v18;
CCHmac();
v12 = objc_msgSend(&OBJC_CLASS___NSMutableString, "stringWithCapacity:", 64);
v13 = (void *)objc_retainAutoreleasedReturnValue(v12);
v14 = 0;
do
objc_msgSend(v13, "appendFormat:", '\x02:4', (unsigned __int8)v18[v14++], v16, v17);
while ( v14 != 32 );
v15 = objc_retainAutoreleaseReturnValue(v13);
objc_release(v15);
if ( __stack_chk_guard == v19 )
JUMPOUT(__CS__, *(_DWORD *)&v18[8]);
__stack_chk_fail();
}

Метод содержит в себе вызов функции CCCHmac() из библиотеки CommonCrypto, которая собственно и осуществляет хеширование, и если переписать всё нормальным языком, то выглядеть хеширование будет примерно следующим образом:

NSString *value = hValue;
NSString *data = hData;
const char *cKey = [value cStringUsingEncoding:NSUTF8StringEncoding];
const char *cData = [data cStringUsingEncoding:NSUTF8StringEncoding];
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

NSString *hash;

NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];

for(int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x", cHMAC[i]];
hash = output;

Но остается неясной одна вещь: в качестве аргументов функции хеширования передается два значения: Value (или соль) и Data, но пароль то только один! На самом деле пароль в этом приложении одновременно является и солью и самим хешируемым значением; может быть это не особо заметно в методе [PassVaultDataSource setPassword:], но зато очень хорошо видно в методе [PassVaultViewController authenticateUser]:

char __cdecl -[PassVaultViewController authenticateUser](struct PassVaultViewController *self, SEL a2)
<…>
v2 = self;
v3 = objc_msgSend(self, "passwordTextField");
v4 = (void *)objc_retainAutoreleasedReturnValue(v3);
v5 = v4;
v6 = objc_msgSend(v4, "text");
v7 = objc_retainAutoreleasedReturnValue(v6);
v8 = objc_msgSend(v2, "passwordTextField");
v9 = (void *)objc_retainAutoreleasedReturnValue(v8);
v10 = v9;
v11 = objc_msgSend(v9, "text");
v12 = objc_retainAutoreleasedReturnValue(v11);
v13 = v12;
v14 = objc_msgSend(&OBJC_CLASS___SHA256, "hashedValue:andData:", v7, v12);
<…>

Здесь в обе переменные v7 и v12 записываются значения, введенные в поле passwordTextField, которые потом передаются на хеширование в качестве соли и данных.

Далее метод [NSString(AESCrypt) AES256EncryptWithKey:] по сути является “посредником” для метода [NSData(AESCrypt) AES256EncryptWithKey:] и работает только со строками: получает строку на вход, затем вызывает для нее [NSData(AESCrypt) AES256EncryptWithKey:], и полученный результат представляет в виде base64-строки:

id __cdecl -[NSString(AESCrypt) AES256EncryptWithKey:](struct NSString *self, SEL a2, id a3)
<…>
v3 = self;
v4 = objc_retain(a3);
v5 = objc_msgSend(v3, "dataUsingEncoding:", 4);
v6 = (void *)objc_retainAutoreleasedReturnValue(v5);
v7 = objc_msgSend(v6, "AES256EncryptWithKey:", v4);
objc_release(v4);
v8 = (void *)objc_retainAutoreleasedReturnValue(v7);
v9 = v8;
v10 = objc_msgSend(v8, "base64Encoding");
v11 = objc_retainAutoreleasedReturnValue(v10);
<…>

Непосредственно за AES-шифрование отвечает метод [NSData(AESCrypt) AES256EncryptWithKey:], внутри себя он также содержит вызов стандартной функции СССrypt() из библиотеки CommonCrypto:

id __cdecl -[NSData(AESCrypt) AES256EncryptWithKey:](struct NSData *self, SEL a2, id a3)
<…>
if ( CCCrypt(0, 0, 1, _R8) )
{
free(v14);
v16 = 0;
}
else
{
v16 = objc_msgSend(&OBJC_CLASS___NSData, "dataWithBytesNoCopy:length:", v14, v17, 32);
objc_retainAutoreleasedReturnValue(v16);
}
<…>

На “нормальном” языке вызов функции CCCrypt() выглядит так:

CCCryptorStatus result = CCCrypt(kCCEncrypt, // or kCCDecrypt for decryption
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
keyPtr,
kCCKeySizeAES256,
NULL,
dataIn,
strlen(dataIn),
dataOut,
sizeof(dataOut),
&numBytesEncrypted);

Что же получается? Пользовательский пароль хешируется, причем в качестве соли выступает сам пароль, затем полученный хеш шифруется алгоритмом AES256 на ключе Dui;f833f#4*&6?>%$#flsk, и зашифрованный результат представляется в виде base64-строки.

Если проделать все вышеописанные процедуры с паролем P@ssw0rd, то в результате получится строка

J7FBTkolL7BepGlJN3BDBrYPtj3OF3bL6c98cbVVR3UsAzzUj5VCHRj+DQrGxGfsu6KjCKEcwuR74XnjMbeh0Gnk0Ij/sB6dYNwrkxPhoW8=

Именно эта строка и содержится в первом выделенном поле на Рисунке 10! Между прочим, значение ключа этого поля (VikR3R74wfFXmIjkdsf78dsfdjkd893VthANTZhRg) не несет какого-либо скрытого смысла. Это всего лишь константная строка, служащая для обозначения “здесь ключ”:

__cstring:0001A063 aVikr3r74wffxmi DCB "VikR3R74wfFXmIjkdsf78dsfdjkd893VthANTZhRg",0

Хорошо, но если пароль мы не знаем, то максимум, что можем получить – это хеш пароля. Оказывается, знания хеша пароля вполне достаточно, чтобы узнать все секретные данные. Действительно, если мы возьмем значение второго выделенного поля на Рисунке 10, декодируем его из формата base64, а затем расшифруем алгоритмом AES256 с ключом *хеш пароля* и нулевым IV, то получим следующее:

Рис.11 Расшифрованные данные

Или в более удобочитаемом формате:

<credentials>
     <fdsd3435sfoio435ip898a45DjvdffsfsdfdsivNaa8>
         Sec. Description
     </fdsd3435sfoio435ip898a45DjvdffsfsdfdsivNaa8>

     <cEQARqPcLgefI4cV9dfad3432snZmm3Mqodfiofsa>
     </cEQARqPcLgefI4cV9dfad3432snZmm3Mqodfiofsa>

     <iPJQ7wer8fdsf9Fgnfsdkli9fdf9xRqtZm0KBK34PJ0>
         1234567
     </iPJQ7wer8fdsf9Fgnfsdkli9fdf9xRqtZm0KBK34PJ0>

     <dGfdsOf2gf5fGK1GXHqO8mANWvR5uL7uIYs>
         2013-12-02 17:11:26 +0000
     </dGfdsOf2gf5fGK1GXHqO8mANWvR5uL7uIYs>

     <TE30ffdshjl438787IheQz7mvsIR4Ifgdfgg3gfdg4gfdg3ddff0jkQDcVw>
         2013-12-03 03:59:00 +0000
     </TE30ffdshjl438787IheQz7mvsIR4Ifgdfgg3gfdg4gfdg3ddff0jkQDcVw>

     <n22q1swA24gdfsfxJUT7wd423m9rUrCqO2M3S0>
         www.security.ru
     </n22q1swA24gdfsfxJUT7wd423m9rUrCqO2M3S0>

     <wuWPBHml6zd9Od342jk3QJ6RcI2Ufhs234hfkkBAhx4>
         +9 (876) 543-2123
     </wuWPBHml6zd9Od342jk3QJ6RcI2Ufhs234hfkkBAhx4>

     <ydHsoJPlKMZqCsfiu787sdfjkslkziBuzxQWzkXoY>
         65E9F165-9A19-4011-80B4-DE6C041E.....
     </ydHsoJPlKMZqCsfiu787sdfjkslkziBuzxQWzkXoY>

     <Ek1wmi76bPe823fdsffg4kGBYEIfdfUroR6GFQ>
          myuser
     </Ek1wmi76bPe823fdsffg4kGBYEIfdfUroR6GFQ>
</credentials>

Итак, учетные данные хранятся в виде xml-подобной записи, причем кроме самих учетных данных в ней можно найти информацию об UDID устройства, дате создания и последнего изменения записи. Устрашающие имена тегов также не имеют какого-либо скрытого смысла, они, как и VikR3R74wfFXmIjkdsf78dsfdjkd893VthANTZhRg, всего лишь являются константными строками.

Из Рисунка 10 можно заметить, что зашифрованы не только выделенные поля. Действительно, остальные поля зашифрованы ключом “fkjop89324-_(*(&@32ld;fk;lf*&*(&94” (который также вшит в программу), и несут они следующую служебную информацию:

Имя ключа

Значение

4am4vbFNVCoGJGdjjk78dsjdfs8sdWAXLLUddL9io

Зашифрованное кол-во неудачных попыток ввода пароля

ydHsoJPlKMZqCsfiu787sdfjkslkziBuzxQWzkXoY

Зашифрованный UDID устройства

iofd6560Ail6tyVdfsjklrd234sfsqsPJClQbPAq6s

Зашифрованное время последнего “asked for rating”

6Yzouo9XG0r1I45d4fsdf78sdf78fds68jJxI754

Зашифрованное последнее время, когда приложение было “suspended”

Вывод: Итак, приложение Pass Vault действительно использует 256-битное шифрование для хранения конфиденциальных данных, тем не менее, секретную информацию из него все же можно извлечь. Основная уязвимость приложения, на наш взгляд, заключается в том, что хеш пароля зашифрован на ключе, который в открытом виде вшит в программу (Dui;f833f#4*&6?>%$#flsk). Поэтому устранением этой уязвимости может служить, например, шифрование хеша не фиксированным ключом, а ключом, полученным с помощью функции PBKDF2 (password-based key derivation function). Тогда, не зная пароля, мы не сможем правильно вычислить ключ шифрования, а, следовательно, и расшифровать хеш.

3. iSafeBox

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

Анализ: Аналогично предыдущим случаям, введем мастер-пароль (P@ssw0rd) и некоторые пользовательские данные:


Рис. 12, 13, 14, 15 Интерфейс приложения iSafeBox

После этого в папке /Documents/iSafeBox/ появятся новые файлы:

Рис. 16 Содержимое папки /Documents/iSafeBox

Содержимое файлов detailOfPasswordpass1.plist и detailOfPersonalInfovery personal info.plist зашифровано:


Рис. 17, 18 Зашифрованные данные

Но взглянем сразу же на копию файла detailOfPasswordpass1.plist в папке /Documents/iSafeBox/DecryptFiles/:

Рис. 19 Расшифрованная копия detailOfPasswordpass1.plist

В этой папке данные о пароле расшифрованы! По-видимому, в папке /Documents/iSafeBox/DecryptFiles/ приложение сохраняет последние расшифрованные записи, но почему-то их не удаляет. Тем не менее, будем считать, что большинство данных все-таки зашифровано, и поэтому дизассемблируем исполняемый файл.

В дизассемблированном файле можно отыскать метод [LaunchAppBiz saveMasterPassword:]:

void __cdecl +[LaunchAppBiz saveMasterPassword:](struct LaunchAppBiz *self, SEL a2, id a3)
{
struct LaunchAppBiz *v3; // r4@1

v3 = self;
objc_msgSend(&OBJC_CLASS___KeyChainDataBiz, "storeSecureValue:forKey:", a3, CFSTR("iSafeBoxMasterPassword"));
j__objc_msgSend(v3, "configureSystemEnvironment");
}

Метод недвусмысленно намекает, что мастер-пароль следует искать в связке ключей (Keychain). Связка ключей – это SQLite-база данных, в которой система, а также сторонние приложения могут хранить конфиденциальную информацию (сертификаты, закрытые ключи, пароли, токены безопасности и.т.п). Элементы связки ключей, естественно, хранятся зашифрованными, но существуют утилиты, позволяющие с разной степенью успеха расшифровать элементы. Если мы расшифруем связку ключей, то действительно увидим, что мастер-пароль хранится в ней:

Decrypted item attributes:
Modification Date : 2013-12-05 04:28:51 +0000
Creation Date : 2013-12-05 04:28:13 +0000
Account : iSafeBoxMasterPassword
Access Group : 6N2QU436TZ.com.comcsoft.iSafeBox
kSecAttrAccessible : kSecAttrAccessibleWhenUnlocked
Service : (empty)
Generic : P@ssw0rd

Кроме того, в связке ключей можно найти и закрытый ключ, которым, по-видимому, шифруются данные:

Decrypted item attributes:
Modification Date : 2013-12-05 04:28:51 +0000
Creation Date : 2013-12-05 04:28:13 +0000
Account : iSafeBoxCryptKey
Access Group : 6N2QU436TZ.com.comcsoft.iSafeBox
kSecAttrAccessible : kSecAttrAccessibleWhenUnlocked
Service : (empty)
Generic :
504073737730726400000000322310104513627445136274451362744d89827e89ae0a4d89827e89ae0af168e54ef9
f2544b47b873abce167301ede12bf81fe46f4c646355f085465861faca77e048c832642b9dc2e12ff85eaed8220aa54a126e8ed7d08fa12f6
d38f8f24d325db85f5cd36f8fd372408b78f181c64aac3999167f5616c5d1636afb6c6f0e51aff69f365a97f3668bf5eabe14f5fa4eb9cfcc142e
3caa9fd

Также стоит обратить внимание на атрибут безопасности kSecAttrAccessibleWhenUnlocked: согласно архитектуре безопасности iOS, каждый элемент связки ключей принадлежит одному из шести классов защиты, причем элементы различных классов защиты шифруются различными ключами. Класс защиты kSecAttrAccessibleWhenUnlocked означает, что данный элемент становится доступен только после разблокировки устройства (ввода пароля, если он есть). Следовательно, если на устройстве установлен пароль, то нам необходимо сначала узнать его. Простой пароль из четырех цифр можно подобрать за 20-40 минут. Поэтому безопасность данных этого приложения, в конечном счете, зависит от самого пользователя, от сложности установленного им пароля.

На момент написания статьи связку ключей можно расшифровать на всех A4-устройствах (модели iPhone 4 и ниже, iPod Touch 4 и ниже, iPad 1) под управлением любой версии iOS, вплоть до iOS 7 даже без джейлбрейка, а также на новых A5+ моделях с установленным джейлбрейком.

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

Примечание ко всем приложениям: Связку ключей, а также все нужные файлы приложений возможно получить, не имея SSH-доступа к устройству или даже самого устройства! Информацию можно достать из локального iTunes-бэкапа или из iCloud-бэкапа. Для получения информации из локального iTunes-бэкапа необходимо знать либо пароль резервной копии, если бэкап зашифрован, либо ключ 0х835 (который вычисляется исключительно на устройстве), если бэкап простой; а из iCloud-бэкапа – Apple ID и пароль.

Итак, в статье было показано три различных подхода к хранению конфиденциальной информации пользователя в парольных менеджерах. В первом случае данные хранятся в открытом виде и фактически не защищены, второе приложение использует собственные механизмы шифрования и собственное хранилище, а третье приложение для хранения критической информации (мастер-ключа) пользуется системным хранилищем (keychain).

Я не намереваюсь выносить объективные оценки вышеописанным приложениям, но лично мне сложнее всего было проанализировать и понять механизмы шифрования второго приложения; кроме того, известный писатель и исследователь Джонатан Здзиарски (Jonathan Zdziarski) в своих книгах также советует избегать использования связки ключей, так как во многих случаях она может быть скомпрометирована. Вместо этого предлагается применить собственные механизмы шифрования, основываясь на библиотеке CommonCrypto. Но и здесь стоит быть внимательным, поскольку неправильная реализация своего механизма шифрования может поставить под угрозу данные пользователя.  

Тени в интернете всегда следят за вами

Станьте невидимкой – подключайтесь к нашему каналу.