- Русские Блоги
- Глава 5 Статический анализ программ Android (1) (читать код smali)
- Каталог статей
- Статический анализ программ Android
- Введение в статический анализ
- Методы статического анализа программ Android
- Читать код smali
- файловая структура smali
- Объявление класса
- Комментарий
- аннотация
- метод
- оператор цикла
- оператор переключения ветви
- инструкция try / catch
Русские Блоги
Глава 5 Статический анализ программ Android (1) (читать код smali)
Каталог статей
Статический анализ программ Android
- Статический анализ — один из наиболее распространенных методов изучения внутренней истории программ Android. Вместе с динамической отладкой он может помочь аналитикам решить различные проблемы, возникающие во время анализа.
- Требования: обладать сильной способностью понимать код.
Введение в статический анализ
- Статический анализ: без запуска кода используйте лексический анализ, грамматический анализ и другие технические средства для сканирования файла программы, генерации кода дизассемблирования и овладения функцией программы путем чтения кода дизассемблирования.
- Однако при реальном анализе невозможно вообще не запустить программу, часто бывает необходимо сначала запустить целевую программу, чтобы найти прорыв в программе.
- Так называемый «статический» означает, что основная работа в процессе анализа — это чтение кода дизассемблирования.
- Инструмент, который генерирует код дизассемблирования, называется инструментом дизассемблирования или инструментом декомпиляции. Чем мощнее инструмент, тем лучше эффект дизассемблирования, который может дать вдвое больший результат с половиной усилий.
Методы статического анализа программ Android
- Прочтите байт-код Dalvik, сгенерированный дизассемблированием. Вы можете использовать IDA Pro для анализа файлов DEX, и вы можете использовать текстовый редактор для чтения файлов smali, созданных декомпиляцией baksmali.
- Прочтите исходный код Java, сгенерированный дизассемблированием, используйте dex2jar для создания файла jar, а затем используйте jd-gui для чтения кода файла jar
Читать код smali
- ApkTool и baksmali декомпилируют APK или DEX, и в результате получается файл smali.
- Если вы хотите использовать smali для изменения некоторого поведения DEX, вы должны понимать формат файла и команд smali.
файловая структура smali
- Хотя DEX использует набор инструкций Dalvik, он изначально был разработан на Java. Концепции классов, методов, полей и аннотаций в Java такие же, как и в DEX. При компиляции собственной программы Java в класс каждый класс Java будет храниться в отдельном классе. Если класс содержит несколько внутренних классов, каждый внутренний класс также будет храниться отдельно. Файл smali в DEX аналогичен, внутренние классы в каждом DEX будут храниться в отдельном файле smali во время декомпиляции.
- Возьмем Crackme0201.apk в качестве примера, после декомпиляции с помощью ApkTool проверьте его структуру каталогов, вы обнаружите, что MainActivity существует 1. s m a l i 、 R 1.smali、R 1 . s m a l i 、 R Anim.smali — это файлы smali со знаком доллара в имени. Это внутренние файлы smali.
apktool d app-release.apk - Возьмем в качестве примера MainActivity $ 1.smali, выполните следующую команду, чтобы просмотреть его содержимое
- Это видно из выходной информации: MainActivity 1. s m a l i в Сохранить ставить из да оригинал Текст Шт название ; ‘ . r e s o u r c e ‘ Означает сделать в Означает задавать из да M a i n A c t i v i t y . j a v a ; ‘ . i m p l e m e n t s ‘ Означает сделать Сообщить Жаловаться я Oни , Этот А класс настоящий настоящее время Вверх V i e w класс из O n C l i c k L i s t e n e r Подбирать рот , “ L a n d r o i d / v i e w / V i e w 1. Имя исходного файла хранится в smali; MainActivity.java указывается в инструкции `.resource`; инструкция` .implements` сообщает нам, что этот класс реализует интерфейс OnClickListener класса View, Landroid / view / View 1 . s m a l i в Сохранить ставить из да оригинал Текст Шт название ; ‘ . r e s o u r c e ‘ Означает сделать в Означает задавать из да M a i n A c t i v i t y . j a v a ; ‘ . i m p l e m e n t s ‘ Означает сделать Сообщить Жаловаться я Oни , Этот А класс настоящий настоящее время Вверх V i e w класс из O n C l i c k L i s t e n e r Подбирать рот , “ L a n d r o i d / v i e w / V i e w OnClickListener; «Содержание этой длинной серии инструкций является полной сигнатурой класса или метода Java.
- Если взять в качестве примера MainActivity.smali, структура файла smali выглядит следующим образом:
Объявление класса
- В начале файла smali, .class против .super В команде будет указана полная подпись класса, сохраненная в файле smali, и полная подпись родительского класса класса
- Подпись каждого класса в файле DEX отличается
Комментарий
- Некоторые комментарии генерируются автоматически в файле smali, например: «# поля экземпляра» и «# виртуальные методы» указывают, какой тип информации хранится следующим в файле smali; «# поля экземпляра» обозначают поля экземпляра, после которых, Поля в каждом классе оканчиваются на .field Оператор инструкции, за которой следуют права доступа к полю и полная подпись; «# виртуальные методы» означает, что виртуальные методы класса сохраняются следующим
- Файл smali поддерживает только однострочные комментарии. Комментарии можно размещать в начале строки или в любом месте строки. Используйте # Отметьте начало комментария, часть после знака фунта — это комментарий
- Если в классе есть поля, он будет использован в «# instance fields» .field Инструкция по эксплуатации
аннотация
- Если класс содержит аннотации Java, он будет описан в файле smali с аннотацией «# annotations», а затем будет .annotation Инструкция начинается с .end annotation Конец инструкции (объявить комментарий). .annotation За инструкцией следует тип аннотации и полная подпись. Если в классе несколько аннотаций, будет несколько пар инструкций.
- В MainActivity.smali выше аннотаций нет
метод
- Класс в DEX содержит метод экземпляра «# прямые методы», реализованный самим классом, и виртуальный метод «# виртуальные методы», унаследованный от родительского класса. Каждый метод начинается с .method Инструкция начинается с .end method Инструкция заканчивается, .method За инструкцией следуют права доступа метода и полная подпись.
- В методе, помимо фактически выполняемых машинных инструкций, есть другие инструкции, перечисленные ниже:
- .locals : Объявить количество регистров, используемых в текущем методе. Для каждого метода в DEX вы можете узнать, сколько регистров он использует, с помощью статического анализа. Преимущество этого заключается в том, что виртуальная машина может заранее подготовить стековое пространство для метода при выполнении метода класса в DEX.
- .param : Укажите имя параметра в методе отладки программы. DEX, обработанный Proguard (инструмент запутывания), может не содержать этой информации
- .prologue : Указывает, что следующая часть является инструкцией DEX
- .line : Сохраните информацию о номере строки метода в DEX в исходном файле Java для отладки. DEX, обрабатываемый Proguard, может не содержать этой информации
оператор цикла
- Оператор цикла — это наиболее часто используемая грамматическая структура при разработке программ. Общие структуры цикла в разработке Android: цикл итератора, цикл for, цикл while, цикл do while.
- Цикл итератора выглядит следующим образом:
-
Первая форма итерации — разделить имя объекта и список объектов двоеточием в ключевом слове for, а затем получить прямой доступ к одному объекту в теле цикла. Эта форма кода лаконична и удобочитаема и часто используется в программировании.
Вторая форма состоит в том, чтобы вручную получить итератор, а затем вызвать hasNext () в итераторе в цикле, чтобы проверить, пуст ли он, и, наконец, вызвать его next () в теле цикла для обхода итератора.
Теперь декомпилируйте пример программы Circulate (пример кода:GitHub). Откройте файл MainActivity.smali в каталоге smali / com / droider / circate проекта декомпиляции и найдите метод iterator (). Код выглядит следующим образом:
Функция приведенного выше кода — получить список запущенных процессов, а затем использовать Toast для отображения всех имен процессов. GetRunningAppProcesses () класса ActivityManager используется для получения списка запущенных процессов. Этот метод возвращает List Объект.
В приведенном выше коде итератор () List вызывается для получения итератора списка процессов, а затем он входит в цикл итерации из goto_0. В цикле сначала вызывается hasNext () итератора, чтобы проверить, пуст ли итератор. Если итератор пуст, перейдите к cond_0 и вызовите Toas, чтобы вывести всю информацию о процессе; если итератор не пуст, это означает, что содержимое итератора еще не было выполнено, и для получения одного итератора необходимо вызвать next () RunningAppProcessInfo, а затем создайте новый временный StringBuilder, объедините имя процесса и символ новой строки и добавьте его в StringBuilder, созданный перед началом цикла, и, наконец, используйте оператор goto для перехода к началу цикла.
Можно видеть, что этот код очень похож на оператор цикла while, перечисленный ранее. Фактически, первый тип цикла итератора является реализацией цикла второго типа. Хотя их код Java отличается, сгенерированный код дизассемблирования очень похож. аналогичный
Особенности циклов итератора:
Цикл итератора вызывает hasNext () итератора, чтобы проверить, выполняются ли условия цикла.
Цикл итератора вызывает next () итератора для получения единственного объекта.
Цикл итератора использует инструкцию goto для управления потоком кода.
После того, как цикл итератора for-form развернут, это цикл итератора while-form
Далее идет традиционный цикл for. Посмотрите на forCirculate () в файле MainActivity.smali, код:
Функция приведенного выше кода — получить установленные программы и использовать Toast для вывода всех имен пакетов. GetInstalledApplications () класса PackageManager используется для получения установленных программ. Сначала создайте объект StringBuilder для хранения всей строковой информации, а затем инициализируйте регистр v4 равным 0 (как индекс для получения элемента списка). Начало цикла for — это метка goto_0. Код условия цикла: if-ge v4, v1, :cond_0 , Где v4 — значение индекса, v1 — номер ApplicationInfo в списке, а метка cond_0 — это код после окончания цикла. Если индекс не завершен (последний элемент не найден), код будет выполняться последовательно (код в теле цикла); если После индексирования (нахождения последнего элемента) он перейдет к cond_0 и выполнит Toast для отображения всей строковой информации. if-ge Первая строка кода ниже вызывает get () списка для получения одного объекта ApplicationInfo в списке, а затем добавляет имя пакета и символ новой строки к ранее объявленному StringBuilder и, наконец, добавляет 1 к значению индекса v4 и вызывает goto :goto_0 Оператор переходит в начало цикла
Особенности кода цикла:
Перед входом в цикл инициализируйте переменную счетчика цикла и измените ее значение в теле цикла.
Оценка условия цикла может быть законным утверждением, состоящим из инструкций условного перехода.
Используйте инструкцию goto для управления потоком кода в цикле
Структура циклов while и do while не сильно отличается. Код этих двух очень похож на код предыдущего цикла итератора, но расположение оценки условия цикла отличается.
оператор переключения ветви
- Это также относительно распространенная структура операторов, которая часто появляется в коде с большим количеством ветвей суждения.
- Пример: SwitchCase
- Пример кода:GitHub
- Используйте ApkTool для декомпиляции примера SwitchCase, а затем откройте файл smali / com / droider / switchcase / MainActivity.smali в каталоге декомпилированного проекта, чтобы найти код PackagedSwitch ():
- Я не знаю, использовалась ли версия AS 3.5.3 для написания экземпляра SwitchCase или по какой причине сгенерированный здесь код smali полностью отличается от книги. Книга включает инструкцию упакованного переключателя и область case, что гораздо сложнее; Сгенерированный код smali прост для понимания, это простой прыжок в знания, который в основном был проанализирован в комментариях.
- Второй метод в примере, sparseSwitch (). Хотя диапазон наблюдений становится больше и не является непрерывным, сгенерированный smali такой же, как и первый метод, поэтому нет необходимости в анализе.
- Лично я считаю, что приведенный выше код переключателя более читабелен, понятен и более эффективен, чем тот, который приведен в книге.
инструкция try / catch
- При написании кода на самом деле могут появиться различные непредсказуемые результаты. Чтобы получить как можно больше информации об исключениях, необходимо использовать в коде операторы try / catch, чтобы обернуть код, который может вызвать проблемы.
- Пример: TryCatch
- Пример кода:GitHub
- Используйте ApkTool для декомпиляции примера TryCatch, затем откройте файл smali / com / droider / trycatch / MainActivity.smali в каталоге декомпилированного проекта и найдите код tryCatch ():
- Функция приведенного выше кода очень проста: введите количество куриных ножек и количество людей, а затем используйте Toast, чтобы открыть план распределения куриных ножек. При передаче количества людей для демонстрации эффекта от оператора try / catch используется тип String. Исключения будут возникать в двух ситуациях: 1. NumberFormatException может быть сгенерировано при преобразовании типа String в тип int; 2. ArithmeticException может быть сгенерировано, когда делитель равен 0 при вычислении плана распределения.
- Блок операторов try в коде отмечен меткой, начинающейся с try_start и заканчивающейся меткой, начинающейся с try_end. Начальная метка первого оператора try — try_start_0, а конечная метка — try_end_0. При использовании нескольких блоков операторов try значение после имени метки увеличивается (в приведенном выше коде используется try_end_2)
- Используйте под ярлыком try_end_0 .catch В инструкции указывается тип обрабатываемого исключения и метка перехвата, формат:
.catch - Посмотрев на код метки catch_1, вы обнаружите, что если возникает исключение при преобразовании типа String в тип int, появится запрос «недопустимая числовая строка». Для китайских символов в коде ApkTool (или baksmali) будет использовать кодировку Unicode при декомпиляции. При чтении кода используйте инструмент преобразования кодировки (или онлайн-сайт), чтобы преобразовать его для лучшего анализа.
- Внимательно прочтите приведенный выше код еще раз, вы обнаружите, что он используется под меткой try_end_1 .catch Инструкция определяет два улова, catch_0 и catch_1. Под меткой catch_0 находится блок try с меткой try_start_2. Фактически, этот блок является фиктивным. Предположим следующий код:
- Предположим, что в приведенном выше коде возникает исключение при выполнении внутреннего оператора try. Если тип исключения — XXX, внутренний перехватчик перехватит исключение и выполнит соответствующий код обработки; если тип исключения не равен XXX, он будет искать код обработки исключения во внешнем перехвате, который также является меткой экземпляра try_end_1. Их два Причина улова. Что мне делать, если при выполнении кода обработки исключений XXX возникает исключение? В это время исключение будет распространяться на внешний улов. Поскольку внешний уровень исключения XXX имеет только одну обработку исключения YYY, будет оцениваться, относится ли сгенерированное исключение к типу YYY, если это так, оно будет обработано; если нет, исключение будет брошено в приложение. Возвращаясь к этому примеру, если во время выполнения внутренней обработки исключений ArithmeticException генерируются другие исключения, для перехвата исключения будет вызываться внешний улов. Таким образом, легко понять, почему есть ловушка_1 под меткой try_end_2.
- В наборе инструкций Dalvik нет инструкций, связанных с try / catch. При обработке операторов try / catch информация об исключении сохраняется через связанные структуры данных. В главе 4 была изучена структура данных DexCode формата файла DEX, и ее объявление выглядит следующим образом:
- Информация оператора try хранится в try_item под структурой, а его структура DexTry объявляется следующим образом:
- Каждый DexTry сохраняет начальный адрес оператора try и количество инструкций, которые могут вычислить диапазон адресов, включенный в блок оператора try. Под полем try_item указано количество обработчиков
- Теперь давайте посмотрим на информацию о попытках / отлове, хранящуюся в DEX. Из-за большого количества экземпляров TryCatch ручной поиск выполняется слишком медленно. Здесь мы используем инструмент dexdump в Android SDK (используется в главе 3). Используйте программу распаковки (например, 7-Zip), чтобы извлечь classes.dex из экземпляра, а затем выполните следующую команду, чтобы просмотреть содержимое tryCatch ():
- Как видно из вышеприведенной выходной информации, tryCatch () — это частный метод, использующий 8 регистров, всего 75 инструкций, включая три блока try, и всего два обработчика исключений. 0x0001
0x0005 — это диапазон кода первого блока попытки, а код tryCatch () расположен по адресу 0x503ac. Вычислить диапазон кода блока первой попытки:
(0x503ac + 1 * 2)(0x503ac + 5 * 2) = 0x503ae
0x503b6
Точно так же диапазон кода второго и третьего блока попыток может быть вычислен путем вычислений, которые составляют 0x503b60x50418 и 0x5041a
0x5042c соответственно.
- Наконец, организуйте этот код smali в код Java:
Источник