06.05.2014

Безопасность IOS-приложений (часть 22) – Анализ и манипуляции с кодом во время работы приложения при помощи GDB

image

В этой статье мы рассмотрим динамический анализ IOS-приложений при помощи GDB. В предыдущих статьях мы рассматривали анализ и манипуляции во время работы приложения при помощи Cycript. При этом мы использовали технику method swizzling, вызывали свои собственные методы и тем самым изменяли логику работы приложения.

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

В этой статье мы рассмотрим динамический анализ IOS-приложений при помощи GDB. В предыдущих статьях мы рассматривали анализ и манипуляции во время работы приложения при помощи Cycript. При этом мы использовали технику method swizzling, вызывали свои собственные методы и тем самым изменяли логику работы приложения. Так зачем же нам нужен GDB? Все дело в том, что в Cycript мы пока не можем устанавливать точки останова и изменять значения переменных и регистров после выполнения нужной инструкции. GDB позволяет нам погрузиться в самую суть приложения, просматривать ассемблерные инструкции, манипулировать значениями переменных и регистров. По большому счету все эти возможности позволяют нам полностью изменять логику работы программы.

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

Рисунок 1: Внешний вид тестового приложения

Как только приложение установлено на устройстве, подключаемся к нему через ssh.

Рисунок 2: Подключение к устройству через ssh

Запустите приложение GDB-Demo на устройстве, а затем в GDB подключитесь к запущенному процессу при помощи команды attach GDB-Demo.PID. Здесь PID – идентификатор процесса приложения GDB-Demo (в вашем случае идентификатор может быть другим). В командной строке GDB просто напечатайте attach GDB-Demo, и нажмите на клавишу TAB, после чего отобразится корректный PID. После этого нажмите на клавишу ENTER, и GDB подсоединится к запущенному процессу.

Рисунок 3: Подсоединение к запущенному процессу

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

Рисунок 4: Установка точки останова

Теперь вводим любое имя пользователя и пароль и нажимаем на клавишу Login, после чего срабатывает точка останова.

Рисунок 5: Сработала точка останова

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

Рисунок 6: Перечень инструкций, которые следуют после точки останова

Кроме того, из предыдущей статьи мы знаем, что при вызове внешнего метода или считывании информации из свойства, вызывается функция objc_msgSend. Однако обычно в приложении довольно много вызовов objc_msgSend, а нам нужно только те из них, которые имеют отношение к нашей функции. То есть нам нужно найти адреса всех инструкций, которые вызывают функцию objc_msgSend, и установить на них точки останова. Самый простой способ сделать это – найти инструкцию blx, и установить по этому адресу точку останова.

Рисунок 7: Устанавливаем точки останова на все инструкции blx

Сейчас мы установили точки останова на некоторые вызовы objc_msgSend внутри нашей функции. Теперь мы будем просматривать каждый такой вызов, и смотреть, есть ли там что-то для нас интересное, находящееся внутри какого-либо регистра. Мы будем выводить на экран содержимое регистра r1 для каждого вызова objc_msgSend. Если ничего интересного нет, мы будем продолжать выполнение приложения при помощи команды c.

Рисунок 8: Содержимое регистра r1 при каждом вызове objc_msgSend

Тут появилось нечто интересное. Если мы посмотрим в самый низ, то увидим вызов функции isEqualToString, которая, по всей видимости, отвечает за сравнение строк. Из предыдущей статьи мы знаем, что в регистре r2 будет аргумент, передаваемый в эту функцию. Также, если у вас есть хотя бы небольшой опыт в написании программ на Objective-C, то вы знаете, что каждый объект в Objective-C является указателем. Подводя итог, отмечаем, что функция isEqualToString в качестве аргумента будет принимать строковый указатель, который будет находиться в регистре r2. В GDB есть специальная команда po, которая выводит значение указателя, находящегося в регистре.

Рисунок 9: Содержимое значения указателя регистра r2

Теперь мы знаем, что сравнение строки происходит со значением Admin (весьма вероятно, что это имя пользователя). Половина работы выполнена. При желании, вы также можете вывести на экран содержимое регистра r2.

Рисунок 10: Содержимое регистра r2 (указатель) и значение указателя (Admin)

Теперь нам нужно ввести в качестве имени пользователя значение «Admin», поскольку в противном случае до проверки пароля дело может не дойти. Вновь ставим точку останова на функции loginButtonTapped, в поле имя пользователя вводим «Admin» и в поле пароль – любое значение. После этого срабатывает другая точка останова при вызове метода isEqualToString. Распечатав значение регистра r2, мы видим, что пароль - HELLOIOSAPPLICATIONEXPERTS.

Рисунок 11: Содержимое регистра r2

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

Рисунок 12: Успешное завершение процесса аутентификации

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

Рисунок 13: Два места, где происходит проверка регистра r0

В обоих случая решение принимается после сравнения регистра r0 с нулем. Установим точки останова на обеих строках и продолжим выполнение приложения.

Рисунок 14: Установка точек останова, где происходит сравнение регистра r0 с нулем

После срабатывания первой точки останова в регистр r0 занесем значение 1 при помощи команды set $r0 = 1. Ту же самую команду выполняем при срабатывании второй точки останова. После этого вы увидите, что процесс авторизации прошел успешно, даже если вы введете некорректную комбинацию имени пользователя и пароля.

Рисунок 15: При срабатывании каждой точки останова, устанавливаем значение 1 в регистр r0

Рисунок 16: Успешное завершение процесса аутентификации

Ниже приводится код функции loginButtonTapped, над которой мы только что «поиздевались» J.

1

2

3

4

5

6

7

- (IBAction)loginButtonTapped:(id)sender {

if([_usernameTextField.text isEqualToString:@"Admin"] && [_passwordTextField.text isEqualToString:@"HELLOIOSAPPLICATIONEXPERTS"]){

[self performSegueWithIdentifier:@"adminPage" sender:self];

}else{

[[[UIAlertView alloc] initWithTitle:@"Error" message:@"Incorrect Username or password" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show];

}

}

Заключение

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

или введите имя

CAPTCHA