Protecting your Android content
Erik Hemming
Unity Technologies
Mobile 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 with
Google Play Licensing
10
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
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 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
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_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"));
}
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());
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());
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(), mVideoOffset, 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' 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);
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);
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);
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.bin
key=...
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 different 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
4 Apr 2013 Page
Thanks!
42
Twitter: @_eriq_

[UniteKorea2013] Protecting your Android content

  • 1.
    Protecting your Androidcontent Erik Hemming Unity Technologies Mobile Mechanic & Lead Android Developer
  • 2.
    4 Apr 2013Page 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 2013Page 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.
    4 Apr 2013Page Unity on Android 4
  • 5.
    4 Apr 2013Page Unity on Android (overview) 5 Linux Kernel Android / Dalvik VM Unity on Android Mono VM User script / “Game” OS App
  • 6.
    4 Apr 2013Page Unity on Android (detail) 6 C# / Scripts Dalvik (Java)
  • 7.
    4 Apr 2013Page Unity on Android (detail) 7 AndroidJavaObject java.lang.Object
  • 8.
    4 Apr 2013Page 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 2013Page 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 2013Page Authentication with Google Play Licensing 10
  • 11.
    4 Apr 2013Page 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 2013Page 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 2013Page 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 2013Page 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 2013Page 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
  • 16.
    4 Apr 2013Page Application tampering detection 16
  • 17.
    4 Apr 2013Page 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 2013Page 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 2013Page 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 2013Page 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 2013Page 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 2013Page 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());
  • 23.
    4 Apr 2013Page Code Obfuscation 23
  • 24.
    4 Apr 2013Page Code Obfuscation • Java Obfuscation • Proguard • C# Obfuscation • Obfuscar • Hides method/variable names • but still very much readable code • False sense of security 24
  • 25.
    4 Apr 2013Page 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(), mVideoOffset, mVideoLength);             file.close();         }         else         { (...)
  • 26.
    4 Apr 2013Page 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             { (...)
  • 27.
    4 Apr 2013Page Encryption 27
  • 28.
    4 Apr 2013Page 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 2013Page 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 2013Page 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 2013Page 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 2013Page 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 2013Page 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 2013Page 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 2013Page 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
  • 36.
    4 Apr 2013Page 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"));         }     } (...) }
  • 37.
    4 Apr 2013Page 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 2013Page 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 2013Page 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 2013Page 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
  • 41.
    4 Apr 2013Page Questions? 41
  • 42.
    4 Apr 2013Page Thanks! 42 Twitter: @_eriq_