• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья FUD Manual - чистка Андроид приложений от сигнатур

lukas

(L3) cache
Пользователь
Регистрация
11.10.2018
Сообщения
282
Реакции
691
FUD Manual - чистка Андроид приложений от сигнатур


ОГЛАВЛЕНИЕ
I ИНТРО
II ОБФУСКАТОРЫ
III СЕРВИСЫ ПРОВЕРКИ
IV МЕСТА РАЗМЕЩЕНИЯ СИГНАТУР
V ПОИСК
VI ЧИСТКА
VII ЗАКЛЮЧЕНИЕ



I ИНТРО

До сих пор не встречал дельных мануалов по чистке apk в сети (хотя может плохо искал). Так что пришлось учиться всему на собственном опыте. Основываясь на нём, написал небольшую статью, возможно окажется кому-то полезной.

Эта статья призвана помочь тем, чьё приложение необоснованно блокируется антивирусами.

Удалить сигнатуры антивирусов из apk-файла не так сложно, как может показаться. И я в кратце расскажу как это сделать.
Дополнения и поправки приветствуются.

Используемый софт:
- свежая Android Studio
- скрипт по очистке java-файлов от кода
- самописный обфускатор
- множество небольших скриптов для автоматизации поиска сигнатур и чека apk через API чек-сервисов.


II ОБФУСКАТОРЫ

Прежде чем начинать что-либо чистить, убедитесь что apk достаточно обфусцирован. Это избавит вас от лишней работы в дальнейшем и может даже сразу убрать детекты некоторых мелких АВ.

Известных обфускаторов не так уж и много:

  • ProGuard - идёт в составе Android Studio. Считаю что без него не стоит и начинать.
  • DexGuard - платная версия ProGuard.
  • Allatori - качественный коммерческий обфускатор, но сам факт его наличия может провоцировать детект.
  • Помимо этого, для качественной обфускации стоит написать собственный несложный обфускатор, заточенный именно под ваш софт.
О том, что он должен делать станет ясно при прочтении последующих глав.


III СЕРВИСЫ ПРОВЕРКИ

Тут, к сожалению, вибор тоже невелик. Ясно лишь одно - пользоваться подобным сервисом по подписке намного проще чем самолично устанавливать 40-50 АВ и чекать вручную.

Конечно, если файл не детектится крупными АВ (а их всего 3-4), остальные можно и не проверять. Но для удобного быстрого чека всё же требуется API, а также гарантия что apk не уйдет в АВ-компанию.

Всё это можно доверить чек-сервисам:
  • scan4you - старый, хорошо зарекомендовавший себя сервис.
  • dyncheck - иногда показывает 0 детекта, в то время как virustotal продолжает палить apk.
  • virustotal - НЕ используйте никогда, если не хотите чтобы ваш apk ушёл на исследование в АВ компанию и оброс сигнатурами


IV МЕСТА РАЗМЕЩЕНИЯ СИГНАТУР

Здесь я опишу все известные мне места, где АВ размещают свои сигнатуры.

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

Особенно пристально анализируется AndroidManifest.xml - в нём учитывается кол-во запрашиваемых разрешений, сам факт наличия некоторых из них. Может учитываться количество activities/services/receivers и т.п.

Крупный АВ может иметь несколько сигнатур (обычно не больше 5) в пределах одного apk.
Мелкие АВ часто ограничиваются каким-то одним незначительным признаком.


Сигнатуры размещаются:

ЧАСТО:

  • на текстовых строках. Их желательно шифровать рандомным ключом, так чтобы при каждой сборке строка изменялась
  • на строковых константах самого Android API. Например, в коде используется Context.ALARM_SERVICE, который по факту является строкой "alarm" - на неё тоже вешаются сигнатуры.
  • на вызовах встроенных функций Android, например:
    • Создание нового объекта: new WebView(context)
    • Вызов метода объекта: getWindow().setLayout(...)
    • Вызов методов встроенных классов: "string".contains(), "string".split()
  • на имени пакета, именах директорий и классов, их методов - это решается с помощью обфускации.

РЕДКО:

  • на наличии определенных классов в манифесте - таких как BootReceiver, классах для подмены стандартного SMS мессенджера
  • на факте наличия определенных разрешений (<uses-permission>) в манифесте. Некоторые из них очень важны для детекта, большинство не имеет значения. Иногда даже кол-во запрашиваемых разрешений является частью сигнатуры.
  • на структуре вашего кода. Например, 1 функция вызывает 2 других, в которых вызываются системные функции.
  • на versionCode/versionName в манифесте.
  • на конструкциях вроде switch и их аргументах.
  • на именах и хеш-суммах ресурсов - графика, xml и т.п. (содержимое main/res/)
    • Исключение - строки в xml-файлах, особенно res/values/strings.xml - они могут стать частью сигнатуры как и обычные строки в коде
  • на количестве activities/services/receivers в манифесте
  • на сертификате, которым подписан apk



V ПОИСК

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

1) Прежде всего надо иметь скрипт, который принимает на входе java-файл и на выходе выдает его же, но с методами, очищенными от кода и свойствами с обнулёнными значениями. Из строковых переменных удаляются значения, int-ы обнуляются, объекты и результаты return заменяются на null и т.п.

Такой скрипт можно написать на основе https://github.com/javaparser/javaparser

2) Далее, пишем скрипт, который рекурсивно пробегается по нашему проекту и очищает в нем все файлы от кода.

Желательно при этом создавать очищенную копию проекта в отдельной директории, чтобы ненароком не уничтожить свои исходники.

3) Готовим скрипт для чека apk через API чек-сервиса.
Это важный момент, во время чистки файл предстоит проверять десятки раз. Делать это вручную через форму практически нереально. Соответственно, тариф чек-сервиса должен предусматривать многочисленные проверки, а не уменьшать баланс после каждого чека, иначе разоритесь.

Удобный чек-скрипт работает примерно так:
./check.sh test.apk (обфусцируем, компилим gradlew, отправляем на чек-сервис, парсим ответ)
Ответ скрипта: "2 detects (nod32, eset)"

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

4) Ну и, собственно, переходим к самому процессу анализа кода и поиска сигнатур.
  • очищаем проект от кода
  • заменяем 1 очищенный файл обратно на его полную версию
  • компилим и чекаем скриптом
  • смотрим ответ

Если восстановленный файл не добавил детектов, восстанавливаем следующий файл и чекаем снова ...

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

3rd-party библиотеки обрастают сигнатурами очень редко.

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

Сигнатуры, в основном, висят на строках и вызовах системных функций.

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

Чтобы файл участвовал в проверке АВ, очищенное от кода приложение всё же должно вызывать его методы.

Например, если вы чистите WebHelpers.java, то убедитесь что в коде приложения всё ещё существуют вызовы его методов.

Например, манифест вызывает активити, активити запускает сервис, а сервис использует методы WebHelpers.
Если активити или сервис очищены от кода, WebHelpers никогда не будет запущен, а значит он и не будет проверяться АВ.

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

Иногда несколько файлов вызывают 1 метод из пресловутого WebHelpers, который в свою очередь дёргает системный метод. И на этом методе висит сигнатура. В таком случае, восстановление любого из этих файлов будет приводить к детекту АВ.

Чтобы не запутаться, сосредоточтесь на одном файле с детектом, раскопайте сигнатуру до самой последней строчки, закомментируйте её или замените на null и идите дальше.

Итак, приступаем к поиску сигнатур.

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

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

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

Вот так, вкратце, происходит поиск сигнатур.

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

Ещё раз о том, как АВ размещают свои сигнатуры.

Крупные АВ (nod32, eset, kasper) обычно вешают сразу несколько уникальных сигнатур на один apk-файл.
Пока вы не найдете и не уберете их все - файл будет детектиться.

Мелкие АВ (практически все остальные из списка в 50 штук) цепляются на какую-нибудь мелочь, вроде 1 вызова функции или вообще на имя пакета/директории. Причем по 6-8 разных АВ детектят одно и то же.
Уберёте этот признак и избавитесь от всех детектов сразу.

Если никак не удаётся удалить сигнатуру (например она висит на пустом классе BootReceiver или важном разрешении), переходите к поиску и удалению следующих. Такие сигнатуры являются частью цепочки, другие члены которой вполне возможно найти и удалить - тогда цепочка разорвётся.


VI ЧИСТКА

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

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

Функционал обфускатора:
  • Добавление рандомного количества пустых классов, с минимальным кодом - для случаев, если сигнатура висит на количестве activities/services/receivers. Но иногда эти пустые классы сами по себе могут стать сигнатурой.
  • Переименование классов, упомянутых в манифесте - т.к. ProGuard их не переименовывает
  • Добавление дополнительных разрешений в манифест.
  • Важно учесть, что у каждого разрешения есть "Protection level", который может быть "dangerous". Добавление такого разрешения лишь ухудшит ситуацию с АВ. Выбирайте разрешения с "Protection level: normal".
  • Рандомизация versionCode и versionName в манифесте
  • Удаление всех Log.*
  • Создание нового сертификата (с помощью keytool)
Итак, обфускатор написан, поиск сигнатур закончен и на данном этапе у вас есть файлик, где выписаны все строки с найденными сигнатурами.
Некоторые из них (например, сигнатуры на именах файлов) вы очистили от детекта еще во время поиска.
Нашли и закриптовали статичные строки.
Пережали/переименовали png-файлы и другие ресурсы, чтобы снять сигнатуры с них.

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

Остается поработать только с java-кодом непосредственно вашего проекта.

Основные способы чистки вашего java-кода:
  • реорганизация, рефакторинг кода - требуется редко
Плюсы: иногда простое слияние двух методов может удалить сигнатуру
Минусы: можно сломать рабочий код
Примечание: иногда помогает изменение параметров вызова системного метода на альтернативные.
Например .read(is) -> .read(is, 0) -- в случае если значение 0 и так является дефолтным
  • спрятать код в NDK-библиотеку, написанную на С++
На грани фантастики, всегда обходился без этого
К тому же неизвестно, как сигнатуры липнут к бинарным .so-библиотекам и как их чистить в случае появления.
  • спрятать вызов системной функции во внешний dex-файл
Плюсы: удобно, быстро, не ломает код
Минусы: на код самой загрузки dex-файла сигнатуры липнут как пчёлы на мёд
  • спрятать вызов системной функции с помощью Java Reflection
Плюсы: несложно (если есть опыт), легко вернуть код в исходное состояние
Минусы:
  • порой это само становится сигнатурой и приходится восстанавливать исходный код
  • код на Reflection выглядит монструозно и нечитабельно, модифицировать его намного сложнее
Нет смысла писать здесь учебник по использованию Reflection, но так как это самый применимый способ убрать сигнатуру, я приведу простой пример.
Например, есть такой код, провоцирующий детект АВ:
Код:
Class<?> cls = ClassLoader.loadClass("my.test.class");
Меняем на null и получаем 0 детектов:
Код:
Class<?> cls = null;
Значит сигнатура висит на вызове системного метода loadClass.

Переписываем вызов на Reflection:
Код:
try{
    Class<?> cls = ClassLoader.class.getDeclaredMethod("loadClass", String.class).invoke(null, "my.test.class");
}catch(Exception e){ e.printStackTrace(); }
Это может помочь, но иногода АВ палит и само использование класса ClassLoader.

Получим класс по его имени:
Код:
Class.forName("java.lang.ClassLoader")
В результате имеем:
Код:
try{
    Class<?> cls = (Class<?>) Class.forName("java.lang.ClassLoader").getDeclaredMethod("loadClass", String.class).invoke(classLoader, "my.test.class");
}catch(Exception e){ e.printStackTrace(); }
Если идти дальше, то не стоит вообще создавать объект типа Class<?>.
Создаем Object и в дальнейшем приводим его к нужному типу:
Код:
try{
    Object cls_obj = Class.forName("java.lang.ClassLoader").getDeclaredMethod("loadClass", String.class).invoke(classLoader, "my.test.class");
}catch(Exception e){ e.printStackTrace(); }
Использование объекта с приведением к нужному типу:
Код:
... (Class<?>) cls_obj ...

Надо ли говорить, что в данном коде статичные строки "java.lang.ClassLoader" и "loadClass" это лакомый кусок для расстановщика сигнатур, так что это строки стоит зашифровать и менять ключ шифровки при каждой пересборке. Но об этом может позаботиться хороший обфускатор.


VII ЗАКЛЮЧЕНИЕ

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

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

Reflection делает код ужасным, но иногда чистка приводит к упрощению кода проекта, что наоборот делает код более компактным и сигнатуроустойчивым.

При активном использовании apk (если вы конечно не рассылаете его имейл-спамом) чистка требуется не чаще раза в пару недель.


2019 (с) автор статьи Naladka, при перепечатке просьба ссылаться на оригинал.

взято с exploit.in
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх