- PVSM.RU - https://www.pvsm.ru -

Модификация стоковых прошивок для Android. Часть 2

Здравствуй Хабр!

В течение всей недели я размышлял о том, стоит ли писать о том что обещал или же по просьбе некоторых читателей дать разъяснения и объяснения по поводу некоторых, на мой взгляд базовых, вещей.
Не хотелось бы, но придется некоторых из вас расстроить. Писать самому — займет слишком много времени и сил, «копипастить» я не люблю больше всего, а делать из статьи кучу ссылок на разные ресурсы — будет моветон.

Например, описывать как происходит загрузка Android в данной статье будет не уместно. Если вы знаете принцип инициализации аппаратного обеспечения вашего компьютера в BIOSе, а затем его загрузка через ядро системы, то Android в этом плане ничем не отличается. Разница лишь в процессорной архитектуре. Структура файловой системы? Ну господа, это же чистой воды UNIX система, и писать где, что и как хранится — абсурдно! Править build.prop — это тюнинг системы. Да, это модификация, но большую часть этих параметров можно сделать сторонними приложениями, при чем удобными для пользования, например System Tuner [1].

Понять самому принципы системы Android заняло у меня пару месяцев, столько же займет времени и писать статьи, чтобы осветить все базовые вещи. Так что давайте лучше будем писать о конкретных примерах как разбирать Dalvik код и создавать на телефоне удобства «пользования».

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

Преамбула

Законодательство некоторых стран запрещает производить запись телефонных разговоров техническим средствами. Например в США запрещается записывать личные телефонные разговоры без предварительного согласия сторон. А на нашем пост-советском пространстве разрешено записывать беседу, в которой вы являетесь одной из сторон, без предупреждения других участников беседы. В том же самом Китае запись просто напросто приветствуется и «стучать» на соседа — идеологический принцип. И это касается не только записей разговоров. Пользование интернетом, отправка СМС сообщений, пользование соц сетями, алгоритмы шифрования, телефоны с двумя SIM картами и многое другое также индивидуально регламентируются законодательством разных стран. А теперь представьте, что вы владелец бизнеса и экспортируете свою IT продукцию по всему миру. Разумеется, свое программное обеспечение вы будете «писать» одной веткой, а не несколькими, но вот конфигурации будут отличаться от региона к региону. Для стран СНГ одно, для Европы — другое. Также и поступают производители телефонов. Операционная система Android дорабатывается только одной группой программистов и в основную ветку, а для каждого региона при компиляции финальных релизов используются специфичные конфигурационные файлы. Говорю это с уверенностью, так как работал в свое время у одного из вендоров.

В доказательство этому есть хорошее руководство [2] как делать портирование прошивки на чужое устройство.

Поехали!

Читая дизассемблированый JAVA код Phone.apk я случайно наткнулся на занимательный флаг. Интересного, но скрытого от нас функционала на самом деле очень много. Буквально сегодня обнаружил, что на моем телефоне есть параметры для японского оператора KDDI [3]. Специально сформированное СМС сообщение от провайдера может заставить мой телефон истошно издавать звуки и вибрировать несколько минут в случае землетрясения или цунами. Но вернемся к нашему флагу.

public static final boolean IS_INCALL_RECORDING_ENABLE = false;

Занимательно, подумал я. Если есть такой флаг, значит он где-то используется. Но вот где — не понятно. Тем не менее, я предположил, что кнопка записи звонка должна появляться во время звонка. Дело за малым! Я изменил FALSE на TRUE, перезаписал патченный Phone.apk, позвонил на домашний телефон, поднял трубку и увидел кнопку «Начать запись». А ведь раньше ее не было!!!
Модификация стоковых прошивок для Android. Часть 2 [4]

Еще раз расскажу как делать подобные вещи для усвоения материала:

  1. Создаем отельную папку и кладем туда Phone.apk файл и к нему smali [5] и backsmali [6]
  2. Потрошим файлик, чтобы разобрать до Dalvik кода командой java -Xmx512m -jar baksmali.jar -a <API LEVEL> -d <FRAMEWORK DIR> -o Phone -x Phone.jar

    <API LEVEL> — это API вашей версии Android. Для JB — это 16
    <FRAMEWORK DIR> — папка, где находятся все фреймворки прошивки.

  3. Открываем в текстовом редакторе файл, в котором нашли флаг PhonecomandroidphoneutilVoiceRecorderHelper.smali
  4. Заменяем
    .field public static final IS_INCALL_RECORDING_ENABLE:Z = false
    

    на

    .field public static final IS_INCALL_RECORDING_ENABLE:Z = true
    
  5. Собираем наш файл обратно: java -Xmx512m -jar smali.jar -a 16 Phone -o classes.dex
  6. Заменяем полученный classes.dex в оригинальном файле любым архиватором
  7. Перезаписываем Phone.apk в телефоне

Протестировав запись нескольких звонков, я обнаружил, что телефон записывает разговоры стандартным встроенным диктофоном и при чем в очень и очень хорошем качестве. Название файлов формируется автоматически и содержит номер или имя собеседника, а также дату и длительность звонка. Формат сохранения файлов также можно настроить в родном приложении. А зачем вы спросите мне вообще нужны эти записи звонков?

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

Вы также можете спросить а почему бы не пользоваться сторонними приложениями, коих полно в открытом и бесплатном доступе?

  • Я не доверяю сторонним и не проверенным приложениям. Зачастую, из-за них садится батарейка, так как каждое подобное приложение висит постоянно в памяти телефона и отжирает процессорное время.
  • Качество записи не всегда соответствует обещанному.
  • Я привередлив к интерфейсу. Приложением может быть богато функционалом, но если мне не удобен GUI, я его не буду использовать. Этим хромают многие отечественные разработки, к сожалению.

Как это вообще работает?

Всем разработчикам под Android известно, что в системе полно различных стандартных широковещательных сообщений. Что бы ни произошло в системе, любое приложение его может получить, если реализовать «своего» получателя такого сообщения. Можно сделать свои широковещательные сообщения, только получатель этого сообщения будет само приложение или все приложении, которые сделали вы сами и они это сообщение как-то обрабатывают. Я такое в практике видел в GoDialer и GoSMSPro.

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

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

Обработчик, а точнее два, оказались в файле comandroidphoneCallNotifier.java

Декомпилированный код (здесь показана только часть кода) из Dalvik в Java оказался следующим:

  private void onCallConnected(AsyncResult paramAsyncResult)
  {
    Connection localConnection = (Connection)paramAsyncResult.result;
    String str = ((IfConnection)localConnection).getDialString();
    VLog.d("onCallConnected() dialed number:" + str);
    removeMessages(120000);
    removeMessages(120001);
    this.mIsEccNeedRetry = false;
    this.mEccIsSwitchingForRetrying = false;
    // много много кода
  }

и

  private void onDisconnect(AsyncResult paramAsyncResult)
  {
    Phone.State localState = this.mCM.getState();
    if (CallNotifier.VDBG)
      super.log("onDisconnect()...  CallManager state: " + this.mCM.getState());
    VLog.d(this, "onDisconnect()");
    removeMessages(120000);
    removeMessages(120001);
    // много много кода
  }

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

Что такое Dalvik?

Об этом кратко написано здесь [7] и здесь [8]. Если говорить на еще более простым языком — байт код виртуальной машины основан на регистрах (выделенная область памяти для оперирования переменными) и множества инструкций и операторов. Смысл и принцип работы довольно прост: вы записываете в регистр какое-то значение и затем совершаете над ним операции, результат операции возвращаете туда, от куда за действиями обращались. Более подробно обо всех операторах и инструкциях можно подсмотреть здесь: Bytecode for the Dalvik VM [9].

Вживляем наш код

Для того чтобы что-то вписать, нам надо знать что туда вписывать. Слизать код можно с обработчика кнопки «Начать запись».
Найти где хранится обработчик даже начинающим программистам для Android это составит труда. Мы потом еще вернемся к тому что и как искать в будущих статьях. Суть первых статей объяснить принципы.

При нажатии кнопки, срабатывает следующий код:

     VoiceRecorderHelper localVoiceRecorderHelper = VoiceRecorderHelper.getInstance();
      if (!localVoiceRecorderHelper.isRecording())
      {
        localVoiceRecorderHelper.start();
      }

То есть чтобы автоматизировать запись всех звонков, надо этот код добавить в обработчик onCallConnected.

Dalvik код этой записи выглядит как

    invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;

    move-result-object v1

    invoke-virtual/range {v1 .. v1}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z

    move-result v2

    const/4 v3, 0x0

    if-ne v3, v2, :cond_a9

    invoke-virtual/range {v1 .. v1}, Lcom/android/phone/util/VoiceRecorderHelper;->start()Z

    :cond_a9

Разберем код построчно:

  1. invoke-static вызывает экземпляр класса VoiceRecorderHelper
  2. сохраняем экземпляр в регистр v1
  3. вызываем метод этого класса под названием isRecording, который возвращает true или false
  4. Результат записываем в регистр v2
  5. Записываем в регистр v3 значение 0
  6. Делаем сравнение между двумя регистрами v2 и v3. Логика: v2 != v3. Если isRecording вернет TRUE, значит v2 будет иметь значение 1 и если FALSE то наоборот. Если условие НЕ срабатывает, то прыгаем на маркер cond_a9. Если нет, то
  7. Вызывается метод start экземпляра класса, который хранится в регистре v1
  8. Наш разговор начал записываться.

Возвращаемся к нашему onCallConnected. Его dalvik код выглядит следующим образом:

.method private onCallConnected(Landroid/os/AsyncResult;)V
    .registers 8
    .parameter "r"

    .prologue

    .line 2302
    iget-object v0, p1, Landroid/os/AsyncResult;->result:Ljava/lang/Object;

    check-cast v0, Lcom/android/internal/telephony/Connection;

    .local v0, c:Lcom/android/internal/telephony/Connection;

    move-object v2, v0

Давайте и этот код разберем, чтобы было понятно что к чему относится

  • .registers 8 — количество регистров памяти, необходимое и используемое для данной функции
  • parameter "r" — название параметра, которое было использовано в исходном коде. Нас оно редко интересует.
  • prologue — начало алгоритма функции
  • .line 2302 — номер строки в исходном коде. Это только для отладки.
  • iget-object v0, p1, Landroid/os/AsyncResult;->result:Ljava/lang/Object; следующая строка соответствует (Connection)paramAsyncResult.result;
  • check-cast v0, Lcom/android/internal/telephony/Connection; соответствует Connection
  • .local v0, c:Lcom/android/internal/telephony/Connection; соответствует localConnection
  • move-object v2, v0 — клонирование локальной переменной v0 в регистр v2
  • и т.д.

Разбирать код не так уж и сложно, если обращаться к описанию и сравнивать с Java кодом.

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

Модификация регистров

Наши первые две строки вживляемого кода имеют следующее:

    invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;

    move-result-object v1

Самый простой способ искать подходящие номера регистра — это поискать в самом методе какие виды данных записываются в необходимые номера регистров. Если поискать в коде, то обнаружим, что move-result-object у нас записывается и в v2 и v 3.
Соответственно все наши v1 во вживляемом кода заменим на v2 или v3

Проделав все операции по замене номеров регистров во вживляемом кода получаем следующую картину:

    invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;

    move-result-object v3

    invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z

    move-result v4

    const/4 v5, 0x0

    if-ne v5, v4, :cond_27

    invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->start()Z

    :cond_27

Стоит отметить, что маркер cond_a9 мы изменили на cond_27. Дело в том, что маркер cond_a9 уже имелся в том файле, куда мы вживляли код и второй раз такой маркер не может быть использован. Номер маркера — шестнадцатиричный код и может быть любым, главное уникальным.

Теперь в исходном файле заменяем строку .line 2302 на наш вживляемый код и получаем

.method private onCallConnected(Landroid/os/AsyncResult;)V
    .registers 8
    .parameter "r"

    .prologue

    invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;

    move-result-object v3

    invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z

    move-result v4

    const/4 v5, 0x0

    if-ne v5, v4, :cond_27

    invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->start()Z

    :cond_27
    .line 2302

    iget-object v0, p1, Landroid/os/AsyncResult;->result:Ljava/lang/Object;

    check-cast v0, Lcom/android/internal/telephony/Connection;

    .local v0, c:Lcom/android/internal/telephony/Connection;

    move-object v2, v0

Осталось теперь собрать наш код с помощью команды java -Xmx512m -jar smali.jar -a 16 Phone -o classes.dex, заменить в Phone.apk и протестировать.

В Java варианте наша работа стала выглядеть следующим образом:

  private void onCallConnected(AsyncResult paramAsyncResult)
  {
     VoiceRecorderHelper localVoiceRecorderHelper = VoiceRecorderHelper.getInstance();
     if (!localVoiceRecorderHelper.isRecording())
     {
       localVoiceRecorderHelper.start();
     }
    Connection localConnection = (Connection)paramAsyncResult.result;
    String str = ((IfConnection)localConnection).getDialString();
    VLog.d("onCallConnected() dialed number:" + str);
    removeMessages(120000);
    removeMessages(120001);

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

     VoiceRecorderHelper localVoiceRecorderHelper = VoiceRecorderHelper.getInstance();
     if (localVoiceRecorderHelper.isRecording())
     {
       localVoiceRecorderHelper.stop();
     }

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

.method private onDisconnect(Landroid/os/AsyncResult;)V
    .registers 41
    .parameter "r"

    .prologue

    invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;

    move-result-object v34

    invoke-virtual/range {v34 .. v34}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z

    move-result v4

    if-eqz v4, :cond_33

    invoke-virtual/range {v34 .. v34}, Lcom/android/phone/util/VoiceRecorderHelper;->stop()Z

    .line 2487
    :cond_33

собираем наши изменения, заменяем в телефоне и вуаля — все работает так как должно.

Эпилог

Уверен, данный материал по сравнению с предыдущей статьей оказался в несколько раз сложнее и запутанней. Какие-то регистры, операторы, модификаторы… Похоже на бред. Я сам в первый раз когда увидел Dalvik — ужаснулся, закрыл страничку и не открывал ее в течение полу года. Когда прижало к стенке, в течение двух недель быстро разобрался что к чему и как это реализовать на практике. Что радует, за всю практику ни разу не получал кирпич.

Не для рекламы ради, хочу посоветовать два ресурса, на которых можно почерпнуть много информации:

Русскоязычный [10] и Англоязычный [11]
На обоих ресурсах я присутствую с тем же ником.

А пока до следующей статьи, надеюсь через неделю.

Автор: Falseclock

Источник [12]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/android/36130

Ссылки в тексте:

[1] System Tuner: https://play.google.com/store/apps/details?id=ccc71.pmw&hl=ru

[2] руководство: http://forum.xda-developers.com/showthread.php?t=1222746

[3] KDDI: http://ru.wikipedia.org/wiki/KDDI

[4] Image: http://radikal.ru/F/i051.radikal.ru/1306/2c/d0f87003c5d2.jpg.html

[5] smali: https://code.google.com/p/smali/downloads/detail?name=smali

[6] backsmali: https://code.google.com/p/smali/downloads/detail?name=baksmali

[7] здесь: http://ru.wikipedia.org/wiki/Dalvik_virtual_machine

[8] здесь: http://android-shark.ru/virtualnaya-mashina-dalvik/

[9] Bytecode for the Dalvik VM: http://s.android.com/tech/dalvik/dalvik-bytecode.html

[10] Русскоязычный: http://4pda.ru/forum/index.php?showforum=281

[11] Англоязычный: http://forum.xda-developers.com/index.php

[12] Источник: http://habrahabr.ru/post/182640/