вторник, 29 октября 2013 г.

Встреча с создателем ProGuard

В рамках GDG DevFest 2013 организаторы привезли к нам крутого чувака, которого зовут Eric Lafortune. Насколько я понял, нашёл его Рома Мазур где-то в Турции (на очередной андроид конфе), он же его и привез. Чувак спокоен, но позитивен.
Встреча проходила в Global Logic'е, конференс зальчик был забит.
Писать обфускатор он начал в 2002 году, поскольку тогда в тренд входили Java-аплеты, а следовательно код уходил на клиентскую сторону и хорошо, чтоб его было мало и он не очень легко декомпилировался. Собственно это и есть, две из четырех основных целей применения обфускатора:



  • уменьшить размер приложения
  • увеличить производительность
  • убрать код, который делает логи или используется только при дебаге
  • защитить код
В общем и целом, у Прогарда ужимать код выходит очень круто (по статистике Эрика от 20-90% ужатие по коду и 5-70% отъедение результирующего веса приложения).
С производительностью ситация сложнее: на десктопных Java-машинах ускорения практически нет (до 1%), потому что эти машины и так крайне умные и умеют оптимизировать всё и вся сами по себе. С Андроидом ситуация коренным образом отличается: Dalvic машина сравнительно примитивная, а потому прогардовские оптимизации могут выгадать до 20% увеличения производительности.
Работает ProGuard в три приема:
  • shrinking ("урезание") - на этом этапе выкусывается весь неиспользуемый код. Особенно это полезно для случаев, когда используется множество библиотек. Обычно, из них используется только небольшая часть функций (читай кода), который можно просто выбросить, тем самым значительно облегчив результат. Тут крутая метафора дерева: на дереве есть "живые" листочки и "мёртвые", мы дерево трясем и "мертвые" опадают.
  • optimisation ("оптимизация") - на этом этапе Прогард притворяется оптимизирующим модулем компилятора (#многоумныхслов: dead code elimination, constant propagation, method inlining, class merging, убирательство logging code, peephole optimizations, devirtualization)
  • obfuscation ("обфускация") - на этом этапе ProGuard делает то, что все про него знают - заменяет всё что можно заменить на a, b, c...
Забавно, что оптимизация и обфускация взаимно дополняют друг друга и могут считаться частями друг друга :) Автоматически оптимизированный код фиг потом разберешь, ну а короткие имена делают приложение меньше по объёму.
Ещё Эрик приводил пример крайне успешной оптимизации функции, в результате которой вызов функции был заменен на константу.
После описания работы самого обфускатора, пошло самое вкусное: как и где его применять.
Защищать стоит (или можно? я не очень понял...): приложение в целом, алгоритмы, механизм коммуникации, ресурсы, ключи.
Для начала чувак рассказал, что всё на белом свете ломабельно и сколько не обфуцируй - всё равно сломают. Если захотят. Но можно сделать так, чтоб хотелки у большинства не хватило :)
Нужно понимать, что ninja hackers сломают что угодно, но таких не очень много. Гораздо больше script kiddies, которые слабо шарят и ломают "ломающими" тулзами, вот против них обфускация - эффективный инструмент. К тому же, многие вообще не заморачиваются с защитой: если ваши конкуренты с защитой не заморочились, а вы это сделали, то зачем ломать вас? Лучше пойти поживиться даром.
В общем сделать абсолютно надёжную защиту низзя, но можно сильно усложнить процесс взлома (чаще всего, сильно усложнив процесс написания - нужно искать компромисс).
Рассказал немного, что взлом может быть статическим (это деобфускация, растарабанивание пакета приложения и т.д. и т.п.) и динамическим (это подмена системных модулей, переменных, банально дебаггером подключиться, в общем, всё, что можно делать в рантайме). Причем с динамическим в Android-мире чуточку лучше (для автора, не для взломщика), чем в остальном Java-мире ибо приложение все же в песочнице, если девайс не рутнут, то вообще много чего сделать низзя.
Ну а дальше пошло перечисление полезных приёмов (уииии, самая вкусняшка, имхо):
  • если есть какие-то секреты, которые ооооочень не хочется открывать - храните их на сервере
  • если у вас есть секретный ключ в коде, то самая примитивная, но действенная защита это хоть не светить его в открытом виде, а записать в Base64 (new String(Base64.decode(“U2VjcmV0IGtleQo=”))), но это то место, где написание своих велосипедов приветствуется (чтоб было максимально непохоже на других)
  • можно использовать рефлексию для создания классов и вызова методов, тогда в обфуцированном коде хрен кто что поймет. Правда, если такого кода много, то и в необфуцированном тоже хрен. Ну и в Андроиде рефлексию вообще не потчуют - медленная.
  • динамическая подгрузка классов. Это вообще хардкор и жесть.
  • можно переписать "секретные" части на C/C++ с NDK, ибо, как известно, С и без обфускации слабопонятно, а уж в скопилированном и обфусцированном виде так и подавно ;) шутка, не обижайтесь, если что) Но тут есть  и обратная сторона медали: можно потерять бдительность, собрать все секреты в одном месте и написать отличный интерефейс. Настолько отличный, что уже и не надо будет лезть ни в какое С и так всё будет ясно.
  • ресурсы можно хранить в шифрованной базе данных, но ведь ключ всё равно нужно будет хранить локально...
  • но тут можно, как минимум, воспользоваться Base64 и пойти дальше: к примеру, не хранить ключ как константу, а вычислять его. Причём, можно сильно заморочится и вычислять его, к примеру, DES'ом, причем его код класть рядышком. То есть, чтоб достать ключ человек должен достать сырцы, продраться сквозь обфускацию, вспомнить (или открыть ;)) как работает DES и сказать "от эта ребяты молодцы, аж красть стыдно"
  • стоит проверять при запуске, а подписанно ли приложение _вашим_ ключём или нет и не стоит ли дебаг-флаг
  • можно ещё проверять не на эмуляторе ли мы щас или не рутнут ли девайс, но тут только эвристики работают ибо надёжных способов нима
Тут Эрик ещё разок напомнил, что ломабельно всё, но можно сделать так, чтоб ломать было дорого.
Потом была весьма интересная сессия вопросов-ответов, в результате которой выяснилось что:

  • он пишет прогард 12 в одиночестве. Это осознанный выбор - не любит чужой код :)
  • сидит под Убунтой с винрарным легковесным WM'мом
  • Гугла ему денег не платит и даже не спрашивала разрешения юзать ProGuard в Android SDK
  • щас он пишет свою закрытую тулзю, на базе прогарда. Она заточена конкретно под Андроид и защиту (Прогард больше юзается для оптимизации, чем для защиты, а эта штука может даже немного ухудшать характеристики, но зато фиг отревертишь). Продает. Вроде недорого, но я не в курсе.
Итого: мне очень понравилось! Всё по делу и интересно. Всем организаторам GDG и Роме, в особенности, спасибо за привезенного чувака - продолжайте в том же духе ;)

ЗЫ нашёл его презентацию с другой конференции - немного отличается, но костяк тот же:

OWF12/PAUG Conf Days Pro guard optimizer and obfuscator for android, eric lafortune, ceo at saikoa from Open World Forum

UPD: @mrCheetahPaw скинул интересную ссылочку по поводу DexGuard, а в частности его крутой возможности шифрования строк

2 комментария:

Yuriy Ashaev комментирует...

Добавлю, что стектрейсы после обфускации в логе превращаются в сплошные a, b, c. Это особенно "приятно", когда клиент шлёт фидбек. В SDK есть тулзня, которая позволяет вернуться опять к оригинальным названиям используя специальный маппинг, однако если вы много наменяете кода, то маппинг изменится и вернуться к нормальным именам не получится.

Пустовит Михаил комментирует...

Это частично решается. К примеру, можно пользоваться вот этой штукой http://try.crashlytics.com/ Она умеет встраиваться в сборку и отправлять к себе маппинги для каждой версии. То есть, она знает соответствие "версия аппа - маппинг" и сама расшифровывает креш логи. Пока ещё и бесплатная :)