[UniteKorea2013] Protecting your Android content

15,406 views

Published on

유나이트 코리아 2013 발표자료: 안드로이드에서의 스크립트 암호화 및 에셋 보안 (에릭 헤밍)

Published in: Technology, Education
1 Comment
41 Likes
Statistics
Notes
No Downloads
Views
Total views
15,406
On SlideShare
0
From Embeds
0
Number of Embeds
2,323
Actions
Shares
0
Downloads
271
Comments
1
Likes
41
Embeds 0
No embeds

No notes for slide

[UniteKorea2013] Protecting your Android content

  1. 1. Protecting your Android contentErik HemmingUnity TechnologiesMobile Mechanic & Lead Android Developer
  2. 2. 4 Apr 2013 PageAbout me• Developer at Unity / Sweden• 3 years of Unity for Android• Used to make games• Battlefield series• Focus on mobiles and consoles• Android / PSM / VITA & PS42
  3. 3. 4 Apr 2013 PageAgenda• Unity on Android - what does it mean?• Authentication with Google Play Licensing• Application tampering detection• Code Obfuscation• Encryption of• PlayerPrefs• Scripts• Assets• Conclusion / Q&A3
  4. 4. 4 Apr 2013 PageUnity on Android4
  5. 5. 4 Apr 2013 PageUnity on Android (overview)5Linux KernelAndroid / Dalvik VMUnity on AndroidMono VMUser script / “Game”OSApp
  6. 6. 4 Apr 2013 PageUnity on Android (detail)6C# / ScriptsDalvik (Java)
  7. 7. 4 Apr 2013 PageUnity on Android (detail)7AndroidJavaObjectjava.lang.Object
  8. 8. 4 Apr 2013 PageAndroidJavaObject et al• Script objects wrap Java objects• AndroidJavaObject → java.lang.Object• AndroidJavaClass → java.lang.Class• AndroidJavaRunnable → java.lang.Runnable• AndroidJavaProxy → java.lang.reflect.Proxy (in Unity 4.2)• Automatically maps / instantiates Classes by name• Methods / Fields are handled through reflection lookups8
  9. 9. 4 Apr 2013 Page 9java.lang.String str = new java.lang.String("some string");int hash = str.hashCode();AndroidJavaObject jo =        new AndroidJavaObject("java.lang.String", "some string");int hash = jo.Call<int>("hashCode");JavaC#AndroidJavaObject (example)
  10. 10. 4 Apr 2013 PageAuthentication withGoogle Play Licensing10
  11. 11. 4 Apr 2013 PageAuthentication withGoogle Play Licensing• Provided by Google• Only available for applications published on Play Store• Online verification of purchase records• If the device is offline the verification will “fail”• Example code (LVL) provided by Google• Don’t use as-is - very easy to find and hack11
  12. 12. 4 Apr 2013 PageVerification Flow• Application → {random number} → Google• Google → {message, signature} → Application• message = purchase status + random number + timestamp + (..)• signature = RSA(message, private key)• Verify that RSA(signature, public key) is a match for ‘message’12
  13. 13. 4 Apr 2013 PageHow to handle the offline case• “Online check” == “Internet access”• Don’t require constant internet access• that would ruin the game experience while flying / roaming / etc.• Instead do the checks only if network is available• allow the app be used a week (or so) without being verified• trust the app/user during that time.• If your app has game elements that require internet connection,make sure you also do a license check at that point.13
  14. 14. 4 Apr 2013 PageServer Side Verification• Application → {some number / data request} → Google• Google → {message, signature} → Application• Application → {message, signature} → Server• Server → {application data} → Application• Server only fulfill Client requests that have correct ‘signature’14
  15. 15. 4 Apr 2013 PageUnity Plugin :Google Play License Verification• Written in C#• Except for the small Service Binder (Java) - loaded dynamically• Easy to embed / hide anywhere in your project• Available on the Unity Asset Store• Ready to be included into an existing project• Original project hosted on GitHub• Feel free to fork and improve15
  16. 16. 4 Apr 2013 PageApplication tamperingdetection16
  17. 17. 4 Apr 2013 PageApplication tampering detection• Why?• A hacker would have to remove and/or alter licensing checks• and thus change the code in your application• Also possible to change code to gain in-game advantages• Like changing the physics so that a car drives faster• In general a very easy way to determine if you’ve been hacked17
  18. 18. 4 Apr 2013 PageApplication tampering detection• Make sure the application is signed with your key• Make sure the Java code (classes.dex) isn’t altered• Make sure the Mono class library (mscorlib.dll) isn’t altered• if the License check is done in C# we will rely on it• Make sure your script code (Assembly-CSharp.dll) isn’t altered• Needs to be done from Assembly-UnityScript.dll, or v.v.• Make sure your native code (libunity.so / libmono.so / etc) isn’taltered18
  19. 19. 4 Apr 2013 PageCheck the APK signature (Java)19// Retrieve the PackageManager and packageName (i.e. com.Company.Product)Activity activity = com.unity3d.player.UnityPlayer.currentActivity;PackageManager manager = activity.getPackageManager();String name = activity.getPackageName();// Fetch APK signature(s)PackageInfo packageInfo = manager.getPackageInfo(name, PackageManager.GET_SIGNATURES);Signature[] signatures = packageInfo.signatures;// Process signatures (i.e. check their validity)for (Signature signature : signatures){    Log.i("signature", signature.toCharsString());    Log.i("signature hash", Integer.toHexString(signature.hashCode()));}
  20. 20. 4 Apr 2013 PageCheck the APK signature (UnityScript)20// Retrieve the PackageManager and packageName (i.e. com.Company.Product)var unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer");var activity = unity.GetStatic.<AndroidJavaObject>("currentActivity");var manager = activity.Call.<AndroidJavaObject>("getPackageManager");var name = activity.Call.<String>("getPackageName");// Fetch APK signature(s)var GET_SIGNATURES = 64;    // PackageManager.GET_SIGNATURESvar packageInfo = manager.Call.<AndroidJavaObject>("getPackageInfo", name, GET_SIGNATURES);var signatures = packageInfo.Get.<AndroidJavaObject[]>("signatures");// Process signatures (i.e. check their validity)for (var i = 0; i < signatures.length; ++i){    Debug.Log("signature = " + signatures[i].Call.<String>("toCharsString"));    Debug.Log("signature hash = " + signatures[i].Call.<int>("hashCode").ToString("X"));}
  21. 21. 4 Apr 2013 PageDetect changes to ‘classes.dex’ (C#)21// Unitys WWW class supports reading jar:{archive-url}!/{entry} on Androidstring urlScheme = "jar:file://";string apkPath = Application.dataPath;string separator = "!/";string entry = "classes.dex";string url = urlScheme + apkPath + separator + entry;// Read classes.dex inside package.apkWWW www = new WWW(url);yield return www;// Calculate the MD5 sum of classes.dex contentsMD5 md5 = new MD5CryptoServiceProvider();byte[] hash = md5.ComputeHash(www.bytes);// Print MD5 sumSystem.Text.StringBuilder sb = new System.Text.StringBuilder();for (int i = 0; i < hash.Length; i++)    sb.Append(hash[i].ToString("x2"));Debug.Log("md5sum(classes.dex) = " + sb.ToString());
  22. 22. 4 Apr 2013 PageNative libs check (UnityScript)22// Retrieve main Activityvar unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer");var activity = unity.GetStatic.<AndroidJavaObject>("currentActivity");// Retrieve ApplicationInfo and nativeLibraryDir (N.B. API-9 or newer only!)var info = activity.Call.<AndroidJavaObject>("getApplicationInfo");var nativeLibraryDir = info.Get.<String>("nativeLibraryDir");var unityPath = Path.Combine(nativeLibraryDir, "libunity.so");var file = new FileStream(unityPath, FileMode.Open, FileAccess.Read);var sha1 = new SHA1CryptoServiceProvider();var hash = sha1.ComputeHash(file);file.Close();// Print SHA1 sumvar sb = new System.Text.StringBuilder();for (var i = 0; i < hash.Length; i++)    sb.Append(hash[i].ToString("x2"));Debug.Log("sha1sum(libunity.so) = " + sb.ToString());
  23. 23. 4 Apr 2013 PageCode Obfuscation23
  24. 24. 4 Apr 2013 PageCode Obfuscation• Java Obfuscation• Proguard• C# Obfuscation• Obfuscar• Hides method/variable names• but still very much readable code• False sense of security24
  25. 25. 4 Apr 2013 PageJava Obfuscation (before)25private void playVideo(){    doCleanUp();    try    {        mMediaPlayer = new MediaPlayer();        if (mIsURL)        {            mMediaPlayer.setDataSource(mContext, Uri.parse(mFileName));        }        else if (mVideoLength != 0)        {            FileInputStream file = new FileInputStream(mFileName);            mMediaPlayer.setDataSource(file.getFD(), mVideoOffset, mVideoLength);            file.close();        }        else        {(...)
  26. 26. 4 Apr 2013 PageJava Obfuscation (after)26private void a(){    b();    try    {        this.r = new MediaPlayer();        if (this.h)        {            this.r.setDataSource(this.b, Uri.parse(this.e));        }        else        {            if (this.j != 0L)            {                Object localObject = new FileInputStream(this.e);                this.r.setDataSource(((FileInputStream)localObject).getFD(), this.i, this.j);                ((FileInputStream)localObject).close();            }            else            {(...)
  27. 27. 4 Apr 2013 PageEncryption27
  28. 28. 4 Apr 2013 PageEncryption of PlayerPrefs• Why?• Prevent simple cheating• Prevent cracking IAB purchases (if you cache anything locally)• In general good practice for sensitive data (like game progression)• How?• Encrypt key/values before inserting them in the PlayerPrefs• Use a user-specific encryption, so prefs cannot be copied, but still sharedin a cloud28
  29. 29. 4 Apr 2013 PageSetString(key, value, secret)29// Hide key stringstring key_string = MD5(key);// Convert value into a byte arraybyte[] bytes = UTF8Encoding.UTF8.GetBytes(value);// Encrypt value with 3DES(secret)TripleDES des = new TripleDESCryptoServiceProvider();des.Key = secret;des.Mode = CipherMode.ECB;ICryptoTransform xform = des.CreateEncryptor();byte[] encrypted = xform.TransformFinalBlock(bytes, 0, bytes.Length);// Convert encrypted array into a "readable" stringstring encrypted_string = Convert.ToBase64String(encrypted, 0, encrypted.Length);// Set the { key, encrypted value } pair in regular PlayerPrefsPlayerPrefs.SetString(key_string, encrypted_string);
  30. 30. 4 Apr 2013 Pagevalue GetString(key, secret)30// Hide key stringstring key_string = MD5(key);// Retrieve encrypted value and Base64 decode itstring value = PlayerPrefs.GetString(key_string);byte[] bytes = Convert.FromBase64String(value);// Decrypt value with 3DES(secret)TripleDES des = new TripleDESCryptoServiceProvider();des.Key = secret;des.Mode = CipherMode.ECB;ICryptoTransform xform = des.CreateDecryptor();byte[] decrypted = xform.TransformFinalBlock(bytes, 0, bytes.Length);// Return decrypted value as a proper stringreturn UTF8Encoding.UTF8.GetString(decrypted);
  31. 31. 4 Apr 2013 PageEncrypted SetString() / GetString()31// Generate a secret based on usernamestring username = "Turrican II";MD5 md5 = new MD5CryptoServiceProvider();byte[] secret = md5.ComputeHash(UTF8Encoding.UTF8.GetBytes(username));// Game progress { key, value } pairstring key = "unlocked levels";string value = "the desert rocks,traps,secret dungeons,the wall,the final challenge,the final fight";// Insert { key, value } pairSetString(key, value, secret);// Retrieve { key, value }string ret = GetString(key, secret);// Output to the logcatDebug.Log("secret = " + username);Debug.Log(key + " = " + ret);
  32. 32. 4 Apr 2013 PageEncryption of Scripts• Why?• Scripts are generally insecure• Gameplay could be altered• Security checks could be disabled• Code needs to be “hidden” for some reason (i.e. IAB logic)32
  33. 33. 4 Apr 2013 PageEncryption of Scripts• How?• Compile scripts outside Unity• Run a symmetric / asymmetric encryption on the Script.dll• Choose a delivery mechanism• Embed in the application, or• Download it from a trusted server• Decrypt the Script.dll in memory• Load it through Assembly.Load(byte[])33
  34. 34. 4 Apr 2013 PageCompile scripts outside Unity• Download Mono (www.mono-project.com)• Compile the script (Plugin.cs) with ‘gmcs’• Reference the UnityEngine.dll assembly to access to Unity34$ gmcs-target:library-out:Script.dll-r:AndroidPlayer/Managed/UnityEngine.dllPlugin.cs
  35. 35. 4 Apr 2013 PageEncrypt the assembly• Using OpenSSL• Converted to ‘text’ using Base64 encoding• Result can be embedded in Unity as a TextAsset35$ openssl rc2 -nosalt -p -in Script.dll -out Encrypted.binkey=...iv =...$ base64 Encrypted.bin > ~/UnityProject/Assets/Encrypted.txt
  36. 36. 4 Apr 2013 PagePlugin.cs36using UnityEngine;public class SomeImportantGameClass{    static SomeImportantGameClass()    {        const int GET_SIGNATURES = 64;        AndroidJavaObject activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity");        AndroidJavaObject manager = activity.Call<AndroidJavaObject>("getPackageManager");        string packageName = activity.Call<string>("getPackageName");        AndroidJavaObject packageInfo =                manager.Call<AndroidJavaObject>("getPackageInfo", packageName, GET_SIGNATURES);        AndroidJavaObject[] signatures = packageInfo.Get<AndroidJavaObject[]>("signatures");        foreach (AndroidJavaObject signature in signatures)        {            Debug.Log("signature hash = " + signature.Call<int>("hashCode").ToString("X"));        }    }(...)}
  37. 37. 4 Apr 2013 PageDecrypt and run assembly37public TextAsset assembly;void Start () {    // Load encrypted data and decryption keys    byte[] bytes = Convert.FromBase64String(assembly.text);    byte[] key = new byte[] { <key from encryption step> };    byte[] iv = new byte[] { <iv from encryption step> };    // Decrypt assembly    RC2 rc2 = new RC2CryptoServiceProvider();    rc2.Mode = CipherMode.CBC;    ICryptoTransform xform = rc2.CreateDecryptor(key, iv);    byte[] decrypted = xform.TransformFinalBlock(bytes, 0, bytes.Length);    // Load assembly and instantiate SomeImportantGameClass to trigger static constructor    Assembly asm = Assembly.Load(decrypted);    Type SomeClass = asm.GetType("SomeImportantGameClass");    SomeClass.GetConstructor(Type.EmptyTypes).Invoke(null);}
  38. 38. 4 Apr 2013 PageEncryption of Assets• Why?• Some assets might need to be protected from tampering.• “Assets” doesn’t necessarily mean just “textures”; could be• Game logic• Dalvik bytecode• Script code• Native code• .. “anything”38
  39. 39. 4 Apr 2013 PageEncryption of Assets• How?• Create an AssetBundle from the “secret” assets.• Run a symmetric / asymmetric encryption on the AssetBundle.unity3d• Choose a delivery mechanism• Embed in the application, or• Download it from a trusted server• Decrypt the AssetBundle.unity3d in memory• Load it through AssetBundle.CreateFromMemory(Byte[])39
  40. 40. 4 Apr 2013 PageConclusion• Be imaginative• APK integrity checks are so simple everyone should have them.• Sensitive code must be protected• Combine the different approaches, and create new ones• Finally: Don’t spend too much time on this• Instead update the logic for each new release.40
  41. 41. 4 Apr 2013 PageQuestions?41
  42. 42. 4 Apr 2013 PageThanks!42Twitter: @_eriq_

×