"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
[UniteKorea2013] Protecting your Android content
1. Protecting your Android content
Erik Hemming
Unity Technologies
Mobile Mechanic & Lead Android Developer
2. 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
3. 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
8. 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
9. 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)
10. 4 Apr 2013 Page
Authentication with
Google Play Licensing
10
11. 4 Apr 2013 Page
Authentication with
Google 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 hack
11
12. 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
13. 4 Apr 2013 Page
How 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. 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
15. 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
17. 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
18. 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
19. 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()));
}
20. 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_SIGNATURES
var 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. 4 Apr 2013 Page
Detect changes to ‘classes.dex’ (C#)
21
// Unity's WWW class supports reading 'jar:{archive-url}!/{entry}' on Android
string urlScheme = "jar:file://";
string apkPath = Application.dataPath;
string separator = "!/";
string entry = "classes.dex";
string url = urlScheme + apkPath + separator + entry;
// Read classes.dex inside package.apk
WWW www = new WWW(url);
yield return www;
// Calculate the MD5 sum of classes.dex contents
MD5 md5 = new MD5CryptoServiceProvider();
byte[] hash = md5.ComputeHash(www.bytes);
// Print MD5 sum
System.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. 4 Apr 2013 Page
Native libs check (UnityScript)
22
// Retrieve main Activity
var 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 sum
var 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());
28. 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
29. 4 Apr 2013 Page
SetString(key, value, secret)
29
// Hide 'key' string
string key_string = MD5(key);
// Convert 'value' into a byte array
byte[] 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" string
string encrypted_string = Convert.ToBase64String(encrypted, 0, encrypted.Length);
// Set the { key, encrypted value } pair in regular PlayerPrefs
PlayerPrefs.SetString(key_string, encrypted_string);
30. 4 Apr 2013 Page
value GetString(key, secret)
30
// Hide 'key' string
string key_string = MD5(key);
// Retrieve encrypted 'value' and Base64 decode it
string 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 string
return UTF8Encoding.UTF8.GetString(decrypted);
31. 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 } pair
string key = "unlocked levels";
string value = "the desert rocks,traps,secret dungeons,the wall,the final challenge,the final fight";
// Insert { key, value } pair
SetString(key, value, secret);
// Retrieve { key, value }
string ret = GetString(key, secret);
// Output to the logcat
Debug.Log("secret = " + username);
Debug.Log(key + " = " + ret);
32. 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
33. 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
34. 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
35. 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.bin
key=...
iv =...
$ base64 Encrypted.bin > ~/UnityProject/Assets/Encrypted.txt
37. 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);
}
38. 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
39. 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
40. 4 Apr 2013 Page
Conclusion
• 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