Presentation describing the best practices concerning Android Offline Storage.
Examples included on manual encryption of files, SQLCipher, and tamper detection
5. Android offline storage possibilities
▪ Several ways to store data in Android
- SharedPreferences
- Files (Internal and external storage)
- SqlLite
- These are not secure!
▪ Back-up
▪ Rooted devices
8. Offline storage Best Practices
▪ Avoid it (if possible)
▪ Avoid external storage (outside of sandbox, globally readable)
▪ set android:allowBackup=”false”
▪ set android:saveEnabled=”false”
▪ MODE_PRIVATE with files
9. ADB shell
▪ When app is debuggable (default in DEV) or device is rooted
- adb shell
- run-as be.ordina.offlinestorage (Not necessary on rooted device)
- cd /data/data/be.ordina.offlinestorage/
▪ shared_prefs
▪ db
▪ files
10. Backup extractor -> https://github.com/nelenkov/android-backup-extractor
▪ Command line: adb backup be.ordina.offlinestorage
▪ Unlock the device and confirm backup operation
▪ Command line: java -jar abe-all.jar unpack backup.ab backup.tar
▪ Unzip the tar and check it’s contents (including the prefs file)
Backing up application
12. Files on internal storage
▪ Internal storage mode MODE_PRIVATE (MODE_WORLD_READABLE and
MODE_WORLD_WRITEABLE deprecated)
▪ Files saved on internal storage in MODE_PRIVATE are private to the application.
▪ FILE CONTENT IS NOT SECURE! -> BY BACKING-UP these files are also perfectly
readable
13. Safe file storage
▪ Encryption of files!
▪ See fragment.EncryptedInternalStorageFragment class for implementation details
15. SQLite
▪ Relational database
▪ Saved on internal storage automatically
▪ can be pulled or backed up with adb
▪ sqlitebrowser: (http://sqlitebrowser.org)
▪ SQLite3 command line interface: https://www.sqlite.org/download.html
▪ NOT SAFE
21. Hiding the key
▪ Ask each time
▪ In the code
▪ In de NDK
▪ Android KeyStore (apple KeyChain equivalent)
▪ Server-side
22. Ask each time
▪ At Startup, always ask the users password.
▪ This password can be used to decrypt the database.
23. In the code
▪ Generate a device specific key (See fragment.DeviceSpecificKeyFragment.java)
▪ As we saw earlier, this can be reverse engineered and used to recreate the device
specific key (Not very safe…)
24. In the NDK
▪ Install the NDK: https://developer.android.com/tools/sdk/ndk/index.html
▪ Documentation: <ndk>/docs/Programmers_Guide/html/index.html
- Samples/hello-jni: Example Java Native Interface
- Building/ndk-build: How to build your native c files
- Building/Android.mk: Android .mk file describing c-library
25. In the NDK
▪ Android studio
- Create folder app/jni
▪ Create Android.mk, Application.mk, <your-module>.c
- Create folder src/main/jniLibs
- Compile c module:
▪ cd in <project-path>/app directory
▪ <ndk-path>/ndk-build
26. Decompile jar with .so modules
▪ http://reverseengineering.stackexchange.com/questions/4624/how-do-i-reverse-
engineer-so-files-found-in-android-apks
▪ online disassembler: http://onlinedisassembler.com/odaweb/
28. Android KeyStore (as of 4.3)
▪ Android hardware backed KeyStore
▪ Standard Java JCA (Java Cryptography Architecture) api but ‘AndroidKeyStore’ as
provider
▪ http://developer.android.com/training/articles/keystore.html
▪ http://nelenkov.blogspot.be/2013/08/credential-storage-enhancements-android-43.html
29. Server side decryption
▪ Communication over HTTPS (of course…)
▪ Send bytes or Strings that need to be decrypted to server
▪ Server decrypts and sends unencrypted data back.
Advantages:
▪ Key information doesn’t leave the server (more secure)
Disadvantages:
▪ Application needs to be connected to internet to function correctly.
▪ More server round-trips to perform the encryption and decryption of data.
30. Tamper Detection
▪ Check if app is installed through play store
▪ Check if app is debuggable
▪ Check if app is running on emulator
▪ Check if device is rooted
34. Check if device is rooted
▪ Check for typical rooted binaries
- /sbin/, /system/bin/, /system/xbin/, /data/local/xbin/, /data/local/bin/, /system/sd/xbin/,
/system/bin/failsafe/, /data/local/, /system/app/
▪ Check for rooted run command: su
▪ @See RootDetectionUtils.java in Sample project
Show the small demo app which contains sample code that serves our presentation.
Applications run in a sandbox. This means:
Apps are given a userId and the apps run with that userId.
Files stored in the sandbox are accessible only to those userIds.
Apps with a different userId can not access those files.
REMARKS.
Apps signed with the same key or apps with the same sharedUserId (AndroidManifest) can access the same sandbox
Rooted phones overcome this limitation
On a device that’s rooted, everything basically runs as root.
As root you gain access to practically everything and all Android limitations that exist no longer apply (isDebuggable, allowBackup, etc…) (IF IM NOT MISTAKEN!)
Avoid offline storage. In the US for example, Health Insurance regulations state that apps running in airplane mode that still “work” are non-compliant.
Avoid external storage. Use ContentProviders to share information between apps instead of saving data in the globally accesible storage…
Set allowBackup=false. This will prevent adb backup command from working.
set android:saveEnabled=”false” prevents the application from saving instancestate of your activity during screen rotation…This behaviour is not desired.
MODE_PRIVATE with files. Store all files in MODE_PRIVATE. This should keep your files private to your app.
In case of a rooted device, the above best practices don’t really increase the security of your offline files.
Rooted devices are not limited by the android sandbox...
More explanation on the isDebuggable flag:
Can be set in AndroidManifest.xml.
Android studio (not sure about eclipse, but probably also) by default sets this flag on true when deploying on a device during development.
The android backup extractor can be used to convert .ab (android backup) files to .tar files.
The resulting .tar file can then simply be unzipped and the contents can be inspected!
DEMO the adb shell and adb backup tools.
adb shell demo:
Connect device
adb shell
run-as be.ordina.offlinestorage (package in AndroidManifest.xml)
cd /data/data/be.ordina.offlinestorage (normally, the run-as command already cd’s into this directory)
REMARKS:
when app isDebuggable is false, this does not work!
When running from android studio, the app is debuggable
When creating release build, the app is not debuggable
isDebuggable property is being set on the application tag in AndroidManifest.xml
adb backup demo:
Connect device
cd ~/AndroidSecurityWorkshop/Androidackups
adb backup be.ordina.offlinestorage
confirm backup on device
abe-all.jar unpack backup.ab backup.tar
extract the tar
REMARKS:
when app allowBackup=false, then the backup pulled from the device will not be readable after running the backup-extractor
Always use MODE_PRIVATE. This will ensure that files are being saved in the application’s sandbox.
Only apps signed with the siging key or apps with the same sharedUserId (AndroidManifest.xml) can access files in the sandbox!
DEMO the offline stored files.
Demo the code:
Show the code where file is created. Notice the MODE_PRIVATE. explain that this means the file will not be accessible for other applications (unless device is rooted of course)
Backup the application (or re-use an existing backup).
Show the file content!
Explain that storing files in this way is not secure!
DEMO the Sqlite database.
Open the app
Navigate to the SqlLite fragment
Insert a user
adb backup the application
open de database in sqlitebrowser (or command-line with sqlite)
SqlCipher encrypts the database with AES-256 symmetric encryption
It’s a drop-in replacement for sqlite. So all code samples you find on the internet which apply to sqlite can be used for sqlCipher. Only difference is the packagename of the sqlcipher objects (net.sqlcipher.database instead of android.database.sqlite)
SQLCipher works by hooking into the database system at a certain point where they can intercept blocks of data before they are being written or read.
At this stage, they are being encrypted using AES-256.
More details about this can be found at the url listed on this slide!
Demo SQLCipher.
First show the code. Comparison with Sqlite code (notice the drop-in replacement. No different api calls, only different package names)
Show the usage of a password
Perform backup of the application (Or re-use an existing backup)
open the encrypted database with sqlite cli (Notice this won’t work!)
open the encrypted database with sqlcipher cli (Notice that without PRAGMA key=”<KEY>”;, this won’t work either)
open the encrypted database with sqlcipher cli (USE PRAGMA key=”<KEY>”; statement and query the db!)
Demo decompiled jar and show password in code:
CD into ~/AndroidSecurityWorkshop/AndroidApks
Run: adb shell pm path your-package-name.
Run: adb pull <.apk-path>
Rename base.apk to base.zip
Extract the zip
Run: java -jar ../abe-all.jar base/classes.dex
Open the resulting jar with JD-GUI
Open the correct classname, and display the password.
Now that we have encrypted our local files and local databases, our data has become unreadable.
Our problem now shifts to hiding the key. Because when users find the key, our encryption mechanisms are useless…
DEMO the SqlCipherFragment!
DBLoginFragment is being used as login fragment… Login attemts are being sent to the OfflineStorageActivity through a callback.
In the callback, the password is retrieved and sent to the SqlCipherFragment, which tries to connect to the database with the password.
In case of a wrong password, login fails.
The compiled c-module becomes a .so module.
This module can be disassembled…
Show the contents of the .so module in the onlinedisassembler!
Show the c-code:
Explain how the method name should correspond to a method declared in java
Show the native Java method
Show how to load the library!
Show how the method is called and the password is being retrieved from the native library
As of Android 4.3, The Android KeyStore Api allows a user to store keys in the secure hardware of your CPU!
This was previously also possible (as of android 4.0) but required the use of reflection and was limited to RSA keys.
App can be tampered with when not installed through play store.
For example, the app could be decompiled, altered and packaged again and then manually be installed. This should be prevented.
Unless explicitily configured, apps in production aren’t debuggable.
App could be tampered with when you detect it’s running in debuggable mode. This might be an indication that people are trying to reverse engineer your app.
Check is being done by bitwise AND’ing the flags on our application with the FLAG_DEBUGGABLE. If the result is 0, then the app is debuggable.
This may also indicate people are trying to reverse engineer your app. Or repackage it.
Rooted device allow for applications to bypass the sandboxing model.
This implies:
Resources are accessible by anyone.
Private storage can be inspected regardless of whether the app is debuggable or backupable. (database, preferences, files, etc…)
Check is being done by looking for the typical rooted binaries and the su command.