Я хотів би пройти крізь декілька напівреалістичних прикладів та пояснити, що приховано за деякими з стратегій, та чому вони можуть бути не такими ефективними як ви могли б сподіватись.
Огляд не охоплює всі можливі варіанти, ми розгляними тільки декілька найбліьш популярних та доступних із способів збереження secret, і, що може піти не так:
Поки що все гарно і охайно, це також, імовірно, найлегший спосіб збереження та використання ресурсів. Щоб побачити, як ми можемо це зробити почнемо з завантаженя файлу APK- це можна зробити вручну з github чи за допомогою wget з командного рядка:
Тепер давайте запустимо
Вам краще бачити всі ці цікаві речі тут. Придивившись детальніше ви навіть можете побачити наші key/password, що були включені:
Отже команда
Поки утиліта
Утиліта
Ви може вставити це значення в файл .gitignore
Нажаль, це не допоможе приховати secret, як в прикладі, зображеному вище, так як ці значення виділяються у вигляді
Не зовсім. Поглянемо на proguard-rules.pro в нашому проекті:
Ми вже наказали proguard весь код в нашому пакеті (
Proguard явно не робить нічого з нашим проектом чи незашифрованими string файлами. Причиною, яка б надала всьому цьому сенс є те, що він просто не може змінити значення string від якого залежить ваш додаток, зміна може призвести до непередбачуваних результатів. Ви можете бачити, що саме proguard робить, переглянувши mapping.txt файл в нашому білді:
Тепер видно, що перейменування наших класів, методів, та імен змінних, як і очікувалось, нічим не допомагає. Погляньте на результат роботи компілятора, що б оцінити роботу proguard. Для порівняння зображена робота з та без утиліти proguard в класі MainActivity, дивіться приклад:
Normal Output:
Proguard Output:
Proguard робить свою роботу змінюючи імена та навіть значення password, перетворивши його на локальну змінну. Коли ви досліджуєте згенеровану реалізацію методів, ви можете бачити, що наш password досі в "сирому" виді:
Досі не срібна куля, але Proguard залишається важливою утилітою, якщо ви маєте намір запобігти реверсінжинірингу. Він дуже ефективний в заплутуванні коду та приховуванні логіки, що, звісно ж, підвищує стійкість коду. Ви можете переглянути версії з Proguard та без обидві включені в наш проект на Github.
Ви можете приховати ваш secret в stringsза допомогою шифрування, base64 будучи достаньо поширеною, може підійти. В нашому додатку, ми зроби це за допомогоюю операцій XOR:
Це все ще наш
Коли ви будете готові використати "приховани" ключ, ви просто запускаєте зворотній процес:
Поки не дуже розумно (чи оптимізовано), це ще один крок в правильному напрямку, ускладнюючи декомпіляцію додатку зловмисником.
source
Огляд не охоплює всі можливі варіанти, ми розгляними тільки декілька найбліьш популярних та доступних із способів збереження secret, і, що може піти не так:
- Вбудовувати в файл strings.xml
- Приховати у вихідному коді
- Приховати в BuildConfigs
- Використовувати Proguard
- Замасковані/Зашифровані файли String
- Приховати в Native Libraries
Загальні стратегії приховування секретів
Щоб допомогти зобразити деякі з цих концептів були створені приклади додатків на Android та розміщені на Github. Повний вихідний код доступний для перегляду, та все ж для впевненості перегляньте ще й декомпільваний вихідний код. Важливо щоб ви розуміли як виглядає не тільки код, який ви пишете, а й код, який бачить потенційний зловмисник при реверсінжинірингу вашого додатку.0. Включення Secret в strings.xml
Як розробник під платформу Android, першим вашим інстинктом буде, імовірно, включити секрет, так як і ключ API, у ваш XML ресурси, як ви робили раніше з іншими активами. Ми зробили це, як і зазвичай, в res/values/strings.xml file:<resources>
<string name="app_name">HidingPasswords</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="server_password">My_S3cr3t_P@$$W0rD</string>
</resources>
$ wget https://github.com/pillfill/hiding-passwords-android/releases/download/1.0/app-x86-universal-debug.apk
strings
, і подивимось, що можна знайт цікавого:$ strings app-x86-universal-debug.apk
…(Lots of output)
$ strings app-x86-universal-debug.apk | grep My
My_S3cr3t_P@$$W0rD
strings
розгромила все і знайшла API ключ з легкістю. Дана команда працює з усіма видами бінарників, а не лише з додатками Android. 1. Включення Secret у ваш вихідний код
Ще один популярний спосіб збереження розробниками ключів API. Для демонстрації, ми включимоpublic static final String
поле та навіть byte[]
масив з нашими захардкодженими ключами всередині MainActivity додатку-прикладу:public class MainActivity extends AppCompatActivity {
//A simple static field to store sensitive keys
private static final String myNaivePasswordHidingKey = "My_S3cr3t_P@$$W0rD";
//A marginally better effort to store a key in a byte array (to avoid string analysis)
private static final byte[] mySlightlyCleverHidingKey = new byte[]{
'M','y','_','S','3','c','r','3','t','_','P','@','$','$','W','0','r','D','_','2'
strings
не може знайти так само легко, як і при роботі з файлами в ресурсах XML, але все ще залишається корисною, якщо копнути глибше. Поки APK залишаєтсья стиснутим/заархівованим файли від нас приховані, ми можемо розархівувати APK і, знову-таки, знайти пароль:$ unzip app-x86-universal-debug.apk
$ strings classes.dex | grep My
My_S3cr3t_P@$$W0rD_2
My_S3cr3t_P@$$W0rD
strings
змогла знайти обидва значення (наш password в string та в навіть в масиві byte array!) з легкістю. Ми говорили перевірити classes.dex file- файл, що повністю містить повний зкомпільований java код.2. Включення Secret у ваш Build Config
Ще один варіант з минуло-тижневої дискусії на Reddit, було управління ключами в BuildConfig з плагуну Android Gradle. Безумовно, даним підходом ви зводите до мінімуму засвітити свій пароль в одній зсистем контрол версій (особливо важливо, коли ви використовуєте публічні DVCS як GitHub):buildTypes {
debug {
minifyEnabled true
buildConfigField "String", "hiddenPassword", "\"${hiddenPassword}\""
}
}
local.properties
чи відмітити gradle.properties
як показано тут:hiddenPassword=My_S3cr3t_P@$$W0rD
BuildConfig
. Це може бути досліджено і розпаковано точно таким же чином.3. Шифрування Secret за допомогою Proguard
Битва зіstrings
програна. Ми можемо запустити proguard над нашим додатком, обфускувати нащ код, і це вирішить нашу проблемку з безпекою файлів string
. Так?Не зовсім. Поглянемо на proguard-rules.pro в нашому проекті:
# Just change our classes (to make things easier)
-keep class !com.apothesource.** { *; }
com.apothesource.**
). Я можу також сказати з впевненістю що Proguard працює як представлено. Отже чому ми досі можемо бачити паролі?Proguard явно не робить нічого з нашим проектом чи незашифрованими string файлами. Причиною, яка б надала всьому цьому сенс є те, що він просто не може змінити значення string від якого залежить ваш додаток, зміна може призвести до непередбачуваних результатів. Ви можете бачити, що саме proguard робить, переглянувши mapping.txt файл в нашому білді:
com.apothesource.hidingpasswords.HidingUtil -> com.apothesource.hidingpasswords.a:
java.lang.String hide(java.lang.String) -> a
java.lang.String unhide(java.lang.String) -> b
void doHiding(byte[],byte[],boolean) -> a
com.apothesource.hidingpasswords.MainActivity -> .hidingpasswords.MainActivity:
byte[] mySlightlyCleverHidingKey -> a
java.lang.String[] myCompositeKey -> b
Normal Output:
#static fields
.field private static final TAG:Ljava/lang/String; = "HidingActivity"
.field private static final myCompositeKey:[Ljava/lang/String;
.field private static final myNaivePasswordHidingKey:Ljava/lang/String; = "My_S3cr3t_P@$$W0rD"
.field private static final mySlightlyCleverHidingKey:[B
#static fields
.field private static final n:[B
.field private static final o:[Ljava/lang/String;
.method public b(Ljava/lang/String;)V
…
move-result-object v0
const-string v1, "My_S3cr3t_P@$$W0rD"
4. Приховати секрети в стрінгах
Заголовок звучить трохи непристойно, але, звісно ж, мова йде про код "secret" та файл strings.xml. З того часу як proguard не приховує ваші файли string, чому б не зробити це самостійно?Ви можете приховати ваш secret в stringsза допомогою шифрування, base64 будучи достаньо поширеною, може підійти. В нашому додатку, ми зроби це за допомогоюю операцій XOR:
//A more complicated effort to store the XOR'ed halves of a key (instead of the key itself)
private static final String[] myCompositeKey = new String[]{
"oNQavjbaNNSgEqoCkT9Em4imeQQ=","3o8eFOX4ri/F8fgHgiy/BS47"
};
My_S3cr3t_P@$$W0rD
пароль. Ми просто приховали за допомогою XOR значення з випадково згенерованим значенням. Ви можете дослідити це за допомогою HidingUtil implementation якщо ви хочете побачити як значення генерується. Зверніть увагу, що доки цей нативнй метод генерує XOR ключ для кожного виклику, немає причин чому б ви не могли використовувати той же ключ для всіх значень у вашому додатку, які б ви хотіли захистити.Коли ви будете готові використати "приховани" ключ, ви просто запускаєте зворотній процес:
public void useXorStringHiding(String myHiddenMessage) {
byte[] xorParts0 = Base64.decode(myCompositeKey[0],0);
byte[] xorParts1 = Base64.decode(myCompositeKey[1], 0);
byte[] xorKey = new byte[xorParts0.length];
for(int i = 0; i < xorParts1.length; i++){
xorKey[i] = (byte) (xorParts0[i] ^ xorParts1[i]);
}
HidingUtil.doHiding(myHiddenMessage.getBytes(), xorKey, false);
}
Висновок
Найкращий спосіб зберегти secret це ніколи його не показувати. Розділення на секції конфіденційної інформації та операцій вашої власної серверної частини має бути вашим першим вибором. Якщо ви все-таки вирішили приховувати схеми, ви мусите зробити процес реверсінжиніринга зловмисника настільки складним, наскільки це можливо.source
Комментариев нет:
Отправить комментарий