Безопасность IOS-приложений (часть 29) – Поиск и использование уязвимостей в алгоритмах шифрования

Безопасность IOS-приложений (часть 29) – Поиск и использование уязвимостей в алгоритмах шифрования

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

Автор: Пратик Джианчандани (Prateek Gianchandani)

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

Рекомендую вам ознакомиться с документацией Apple, где рассматриваются вопросы шифрования и хеширования данных.

В этой статье мы рассмотрим способы обнаружения и взлома небезопасных техник шифрования на примере приложения InsecureCryptography-Demo, которое вы можете скачать с моего аккаунта на Github. Загрузите приложение и запустите его в симуляторе или на устройстве. При первом запуске программа попросит вас ввести новый пароль.

Рисунок 1: Первый запуск тестового приложения

Как только пароль установлен, приложение попросит вас ввести его заново, чтобы пройти процесс авторизации.

Рисунок 2: Повторный запрос пароля для прохождения аутентификации

Наша задача: обойти эту проверку. Очевидно, что пароль хранится локально на устройстве, поскольку у приложения отсутствует сетевая активность. Вы можете воспользоваться утилитами наподобие Burpsuite, чтобы удостовериться в отсутствии сетевого трафика.

Далее открываем Hopper (более подробно об этой утилите я рассказывал в предыдущей статье) и переходим в File->Read Executable to Disassemble.

Рисунок 3: Выбираем файл, который будем дизассемблировать

Если вы уже запустили приложение в Xcode, то оно успешно установилось в симуляторе IOS. Наша задача – найти местонахождение исполняемого файла приложения, который мы будем дизассемблировать в Hopper. Папка с приложением находится в /Users/$username/Library/Application Support/iPhone Simulator/$ios version of simulator/Applications/. В моем случае это директория /Users/Prateek/Library/Application Support/iPhone Simulator 7.0.3/Applications/. Зайдите в эту папку и введите команду ls –al, которая выведет директории с датой их последней модификации. Папка с самой крайней датой – это папка нашего приложения.

Рисунок 4: Находим директорию с приложением

Откройте эту директорию в приложении Finder при помощи команды open $directoryName, а затем кликните правой кнопкой мыши на файле с расширением .app (это пакет приложения) и выберите Show Package Contents.

Рисунок 5: Смотрим содержимое пакета приложения

Найденный исполняемый файл отдаем в Hopper на дизассемблирование.

Рисунок 6: Исполняемый файл приложения

Как только Hopper завершит процесс, вы увидите примерно следующее:

Рисунок 7: Результат дизассемблирования исполняемого файла

В левой части экрана виден перечень классов (один из них RNEncryptor). Скорее всего, RNEncryptor используется для шифрования информации.

Рисунок 8: Перечень классов, методов и констант приложения

По результатам поиска в Гугле выяснилось, что класс RNEncryptor с открытым исходным кодом и используется для шифрования информации. Нам нужно найти метод, отвечающий за процесс аутентификации. Проверка введенного пароля осуществляется при нажатии на кнопку return в текстовом поле.

Рисунок 9: Процесс ввода пароля

Если вы хотя бы немного знакомы с разработкой IOS-приложений, то знаете, что если текущий контроллер – делегат этого текстового поля, то имя вызываемого метода будет (BOOL)textFieldShouldReturn:(UITextField *)textField. Находим этот метод в секции labels (в Hopper) и смотрим дизассемблированную его версию.

Рисунок 10: Дизассемблированная версия метода textFieldShouldReturn

Как вы помните, в Hopper есть прекрасная возможность просматривать псевдокод методов. Кликаем на иконку Pseudo Code в правом верхнем углу.

Рисунок 11: Псевдокод метода textFieldShouldReturn

Далее приводится полная версия псевдокода метода textFieldShouldReturn:

function methImpl_ViewController_textFieldShouldReturn_ {
var_372 = arg_0;
var_368 = arg_4;
var_364 = 0x0;
_PIC_register_ = eax;
objc_storeStrong(&var_364, arg_8);
var_312 = 0x9;
var_308 = 0x1;
eax = NSSearchPathForDirectoriesInDomains(0x9, 0x1, 0x1);
eax = [eax retain];
var_304 = 0x0;
var_300 = eax;
eax = [eax objectAtIndex:0x0];
eax = [eax retain];
var_296 = eax;
eax = [eax stringByAppendingPathComponent:@"/secret-data"];
eax = [eax retain];
var_360 = eax;
[var_296 release];
[var_300 release];
var_292 = var_364;
eax = [var_372 passwordTextField];
eax = [eax retain];
var_288 = eax;
[eax release];
if (var_292 != var_288) goto loc_4fab;
goto loc_4b74;

loc_4fab:
var_172 = var_364;
eax = [var_372 returningUserTextField];
eax = [eax retain];
var_168 = eax;
[eax release];
if (var_172 != var_168) goto loc_53a5;
goto loc_4ff8;

loc_53a5:
var_379 = 0x0;
var_356 = 0x1;

loc_53b0:
var_104 = 0x0;
objc_storeStrong(&var_360, 0x0);
var_100 = 0x0;
objc_storeStrong(&var_364, 0x0);
eax = SIGN_EXTEND(var_379);
return eax;

loc_4ff8:
eax = [var_372 returningUserTextField];
eax = [eax retain];
var_164 = eax;
eax = [eax text];
eax = [eax retain];
var_160 = 0x4;
var_156 = eax;
eax = [eax dataUsingEncoding:0x4];
eax = [eax retain];
var_336 = eax;
[var_156 release];
[var_164 release];
var_332 = 0x0;
eax = [NSData dataWithContentsOfFile:var_360];
eax = [eax retain];
var_328 = eax;
var_320 = var_332;
eax = [RNDecryptor decryptData:var_328 withPassword:@"Secret-Key" error:&var_320];
eax = [eax retain];
var_152 = eax;
objc_storeStrong(&var_332, var_320);
var_324 = var_152;
eax = [var_336 isEqualToData:var_324];
if (eax != 0x0) {
eax = [var_372 loggedInLabel];
eax = [eax retain];
var_148 = 0x0;
var_144 = eax;
[eax setHidden:0x0];
[var_144 release];
eax = [var_372 returningUserTextField];
eax = [eax retain];
var_140 = 0x1;
var_136 = eax;
[eax setHidden:0x1];
[var_136 release];
eax = [var_372 returningUserLabel];
eax = [eax retain];
var_132 = 0x1;
var_128 = eax;
[eax setHidden:0x1];
[var_128 release];
var_356 = 0x0;
}
else {
var_124 = @"OK";
var_120 = @"Oops";
var_116 = @"Password is incorrect";
var_112 = 0x0;
eax = [UIAlertView alloc];
eax = [eax initWithTitle:var_120 message:var_116 delegate:0x0 cancelButtonTitle:var_124 otherButtonTitles:0x0];
var_108 = eax;
[eax show];
[var_108 release];
var_379 = 0x0;
var_356 = 0x1;
}
ecx = esp;
*ecx = &var_324;
*(ecx + 0x4) = 0x0;
objc_storeStrong();
ecx = esp;
*ecx = &var_328;
*(ecx + 0x4) = 0x0;
objc_storeStrong();
ecx = esp;
*ecx = &var_332;
*(ecx + 0x4) = 0x0;
objc_storeStrong();
ecx = esp;
*ecx = &var_336;
*(ecx + 0x4) = 0x0;
objc_storeStrong();
if (var_356 != 0x0) goto loc_53b0;
goto loc_53a5;

loc_4b74:
eax = [var_364 resignFirstResponder];
var_287 = eax;
eax = [var_364 text];
eax = [eax retain];
var_280 = eax;
eax = [eax length];
var_276 = eax;
[var_280 release];
if (var_276 != 0x0) goto loc_4c9b;
goto loc_4be5;

loc_4c9b:
eax = [var_372 passwordTextField];
eax = [eax retain];
var_252 = eax;
eax = [eax text];
eax = [eax retain];
var_248 = 0x4;
var_244 = eax;
eax = [eax dataUsingEncoding:0x4];
eax = [eax retain];
var_352 = eax;
[var_244 release];
[var_252 release];
var_348 = 0x0;
var_340 = var_348;
edi = esp;
var_240 = 0x12;
var_236 = _kRNCryptorAES256Settings;
var_232 = *0x158b0;
var_228 = edi;
var_224 = *0x157bc;
*(edi + 0xc) = *var_236;
esi = var_228;
*(esi + 0x58) = &var_340;
*(esi + 0x54) = @"Secret-Key";
*(esi + 0x8) = var_352;
*(esi + 0x4) = var_224;
*esi = var_232;
eax = objc_msgSend();
eax = [eax retain];
var_220 = eax;
objc_storeStrong(&var_348, var_340);
var_344 = var_220;
var_216 = 0x1;
eax = [var_344 writeToFile:var_360 atomically:0x1];
var_215 = eax;
eax = [NSUserDefaults standardUserDefaults];
eax = [eax retain];
var_208 = eax;
var_204 = 0x1;
[eax setBool:0x1 forKey:@"loggedIn"];
[var_208 release];
eax = [NSUserDefaults standardUserDefaults];
eax = [eax retain];
var_200 = eax;
eax = [eax synchronize];
var_199 = eax;
[var_200 release];
eax = [var_372 firstUserView];
eax = [eax retain];
var_192 = 0x1;
var_188 = eax;
[eax setHidden:0x1];
[var_188 release];
var_184 = 0x0;
objc_storeStrong(&var_344, 0x0);
var_180 = 0x0;
objc_storeStrong(&var_348, 0x0);
var_176 = 0x0;
objc_storeStrong(&var_352, 0x0);
goto loc_53a5;

loc_4be5:
var_272 = @"OK";
var_268 = @"Oops";
var_264 = @"Please enter a password";
var_260 = 0x0;
eax = [UIAlertView alloc];
eax = [eax initWithTitle:var_268 message:var_264 delegate:0x0 
cancelButtonTitle:var_272 otherButtonTitles:0x0]; 

var_256 = eax;
[eax show];
[var_256 release];
var_379 = 0x0;
var_356 = 0x1;
goto loc_53b0;
}

Вот некоторые участки псевдокода:

eax = [eax text];
eax = [eax retain];
var_160 = 0x4;
var_156 = eax;
eax = [eax dataUsingEncoding:0x4];

Введенный текст конвертируется в NSData при помощи метода dataUsingEncoding.

eax = NSSearchPathForDirectoriesInDomains(0x9, 0x1, 0x1);
eax = [eax retain];
var_300 = eax;
eax = [eax objectAtIndex:0x0];
eax = [eax retain];
var_296 = eax;
eax = [eax stringByAppendingPathComponent:@"/secret-data"];

В алгоритме используется файл с именем secret-data. Далее видим следующее выражение:

eax = [var_344 writeToFile:var_360 atomically:0x1];

Кажется, происходит запись в файл secret-data.

var_332 = 0x0;
eax = [NSData dataWithContentsOfFile:var_360];
eax = [eax retain];
var_328 = eax;
eax = [RNDecryptor decryptData:var_328 withPassword:@"Secret-Key" error:&var_320];

Считывается и расшифровывается содержимое файла при помощи пароля.

eax = [RNDecryptor decryptData:var_328 withPassword:@"Secret-Key" error:&var_320];

Ключ для шифровки и дешифровки жестко запрограммирован в коде (строка Secret-Key).

eax = [var_336 isEqualToData:var_324];

Происходит сравнение данных. Результирующее булево значение сохраняется в регистре eax.

if (eax != 0x0) {
eax = [var_372 loggedInLabel];
eax = [eax retain];
var_148 = 0x0;
var_144 = eax;
[eax setHidden:0x0];
[var_144 release];
eax = [var_372 returningUserTextField];
eax = [eax retain];
var_140 = 0x1;
var_136 = eax;
[eax setHidden:0x1];
[var_136 release];
eax = [var_372 returningUserLabel];
eax = [eax retain];
var_132 = 0x1;
var_128 = eax;
[eax setHidden:0x1];
[var_128 release];
var_356 = 0x0;
}
else {
var_124 = @"OK";
var_120 = @"Oops";
var_116 = @"Password is incorrect";
var_112 = 0x0;
eax = [UIAlertView alloc];
eax = [eax initWithTitle:var_120 message:var_116 delegate:0x0 cancelButtonTitle:var_124 otherButtonTitles:0x0];
var_108 = eax;
[eax show];
[var_108 release];
var_379 = 0x0;
var_356 = 0x1;
}

В зависимости от значения, находящегося в регистре eax, выполнение алгоритма идет по соответствующей ветке. Если значение – 0, пользователю показывается сообщение о том, что пароль неверный. Иначе – процесс авторизации успешно завершается.

Давайте поищем файл secret-data в песочнице, где выполняется приложение. Довольно быстро выясняется, что файл находится в папке Documents. Открыв файл, мы видим, что содержимое зашифровано.

Рисунок 12: Содержимое файла secret-data

Как уже было сказано выше, классы RNEncryptor и RNDecryptor – часть библиотеки с открытым исходным кодом, который можно найти здесь.

Обобщаем всю найденную информацию:

  • Когда пользователь вводит пароль, текст конвертируется в NSData при помощи метода dataUsingEncoding с параметром 0×4. Параметр 0×4 соответствует NSUTF8StringEncoding.
  • Информация считывается из секретного файла и дешифруется при помощи жестко закодированного ключа.
  • Два значения сравниваются между собой. Если значения совпадают, процесс авторизации успешно завершается.

Совершенно очевидно, что мы можем узнать пароль, если расшифруем информацию из файла secret-data и сконвертируем его в строку формата NSUTF8StringEncoding. Давайте напишем простейшее приложение для дешифровки информации. Для этого вам нужно скопировать файл secret-data из песочницы в папку Documents песочницы нового приложения. Полную версию кода можно загрузить отсюда.

Добавляем этот метод в новый проект.

NSString *dataPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) objectAtIndex:0]
stringByAppendingPathComponent:@"/secret-data"];
NSError *error;
NSData *encryptedData = [NSData dataWithContentsOfFile:dataPath];
NSData *decryptedData = [RNDecryptor decryptData:encryptedData
withPassword:@"Secret-Key"
error:&error];
NSString *password = [[NSString alloc] initWithData:decryptedData
encoding:NSUTF8StringEncoding];

UILabel *newLabel = [[UILabel alloc] initWithFrame:CGRectMake(140.0, 160.0, 100.0, 100.0)];
[self.view addSubview:newLabel];
[newLabel setText:password];

Как вы уже догадались, метод расшифровывает данные при помощи жестко прописанного пароля и записывается значение в метку, которая отображается на экране.

После запуска приложения, появляется расшифрованный пароль.

Рисунок 13: Расшифрованный пароль

Заключение

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

В следующей статье мы рассмотрим методы инъекций на стороне клиента (Client Side Injection) в IOS-приложениях. 

Если вам нравится играть в опасную игру, присоединитесь к нам - мы научим вас правилам!

Подписаться