[unitekorea2013] protecting your android content
DESCRIPTION
유나이트 코리아 2013 발표자료: 안드로이드에서의 스크립트 암호화 및 에셋 보안 (에릭 헤밍)TRANSCRIPT
Protecting your Android contentErik HemmingUnity TechnologiesMobile Mechanic & Lead Android Developer
4 Apr 2013 Page
About 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 & PS4
2
4 Apr 2013 Page
Agenda• Unity on Android - what does it mean?
• Authentication with Google Play Licensing
• Application tampering detection
• Code Obfuscation
• Encryption of
• PlayerPrefs
• Scripts
• Assets
• Conclusion / Q&A
3
4 Apr 2013 Page
Unity on Android
4
4 Apr 2013 Page
Unity on Android (overview)
5
Linux Kernel
Android / Dalvik VM
Unity on Android
Mono VM
User script / “Game”
OS
App
4 Apr 2013 Page
Unity on Android (detail)
6
C# / Scripts
Dalvik (Java)
4 Apr 2013 Page
Unity on Android (detail)
7
AndroidJavaObject
java.lang.Object
4 Apr 2013 Page
AndroidJavaObject 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 lookups
8
4 Apr 2013 Page 9
java.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");
Java
C#
AndroidJavaObject (example)
4 Apr 2013 Page
Authentication withGoogle Play Licensing
10
4 Apr 2013 Page
Authentication withGoogle Play Licensing
• Provided by Google
• Only available for applications published on Play Store
• Online verification of purchase records
• If the device is o"ine the verification will “fail”
• Example code (LVL) provided by Google
• Don’t use as-is - very easy to find and hack
11
4 Apr 2013 Page
Verification 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
4 Apr 2013 Page
How to handle the o!ine 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
4 Apr 2013 Page
Server 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
4 Apr 2013 Page
Unity 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 improve
15
4 Apr 2013 Page
Application tampering detection
16
4 Apr 2013 Page
Application 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 hacked
17
4 Apr 2013 Page
Application 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’t altered
18
4 Apr 2013 Page
Check 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()));}
4 Apr 2013 Page
Check 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"));}
4 Apr 2013 Page
Detect changes to ‘classes.dex’ (C#)
21
// Unity's 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());
4 Apr 2013 Page
Native 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());
4 Apr 2013 Page
Code Obfuscation
23
4 Apr 2013 Page
Code Obfuscation
• Java Obfuscation
• Proguard
• C# Obfuscation
• Obfuscar
• Hides method/variable names
• but still very much readable code
• False sense of security
24
4 Apr 2013 Page
Java Obfuscation (before)
25
private 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(), mVideoO"set, mVideoLength); file.close(); } else {(...)
4 Apr 2013 Page
Java Obfuscation (after)
26
private 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 {(...)
4 Apr 2013 Page
Encryption
27
4 Apr 2013 Page
Encryption 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 shared in a cloud
28
4 Apr 2013 Page
SetString(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);
4 Apr 2013 Page
value 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);
4 Apr 2013 Page
Encrypted SetString() / GetString()
31
// Generate a secret based on 'username'string 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);
4 Apr 2013 Page
Encryption 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
4 Apr 2013 Page
Encryption 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
4 Apr 2013 Page
Compile scripts outside Unity
• Download Mono (www.mono-project.com)
• Compile the script (Plugin.cs) with ‘gmcs’
• Reference the UnityEngine.dll assembly to access to Unity
34
$ gmcs -target:library -out:Script.dll -r:AndroidPlayer/Managed/UnityEngine.dll Plugin.cs
4 Apr 2013 Page
Encrypt the assembly
• Using OpenSSL
• Converted to ‘text’ using Base64 encoding
• Result can be embedded in Unity as a TextAsset
35
$ openssl rc2 -nosalt -p -in Script.dll -out Encrypted.binkey=...iv =...
$ base64 Encrypted.bin > ~/UnityProject/Assets/Encrypted.txt
4 Apr 2013 Page
Plugin.cs
36
using 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")); } }(...)}
4 Apr 2013 Page
Decrypt and run assembly
37
public 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);}
4 Apr 2013 Page
Encryption 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
4 Apr 2013 Page
Encryption 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
4 Apr 2013 Page
Conclusion
• Be imaginative
• APK integrity checks are so simple everyone should have them.
• Sensitive code must be protected
• Combine the di#erent approaches, and create new ones
• Finally: Don’t spend too much time on this
• Instead update the logic for each new release.
40
4 Apr 2013 Page
Questions?
41