Created
November 14, 2019 20:58
-
-
Save unixpickle/eb3898f2b72b6c1589c1be0d64ea1003 to your computer and use it in GitHub Desktop.
Revisions
-
unixpickle created this gist
Nov 14, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,106 @@ # Overview In this document, I will describe how I managed to get unlimited stars and coins in the Android game "Lily's Garden". In short, here are the steps: * Use the `adb backup` feature to extract all of the game's data * Extract the Android backup into a tar file * Modify the file which stores the number of coins and stars * Re-sign the coins/stars field using a reverse-engineered HMAC key * Convert the tar file back to an Android backup * Use the `adb restore` feature to put the new game data on the phone # Steps To backup the game's data, run the following command with your phone plugged in and in developer mode: $ adb backup dk.tactile.lilysgarden This creates a file called backup.ab. To extract a tarbal from this, run this command ([source](https://myridia.com/dev_posts/view/1772)): $ dd if=backup.ab bs=1 skip=24 | python2 -c "import zlib,sys;sys.stdout.write(zlib.decompress(sys.stdin.read()))" >backup.tar Now you want to edit the file called `apps/dk.tactile.lilysgarden/sp/dk.tactile.lilysgarden.v2.playerprefs.xml` inside of `backup.tar`. You can do this with vim, or with some other tool. I found that untarring->editing->tarring did not work, probably because of permissions. Inside of `dk.tactile.lilysgarden.v2.playerprefs.xml`, look for a line starting with: <string name="4b79e50f33d58cde3869c24d8dd0102c"> This contains most of the game's state, as far as I can tell. The tag contains a bunch of URL-encoded JSON, followed by a 32-byte HMACSHA256 checksum. Near the end of the JSON should be something like this: "in":{"it":{"BoosterBomb":0,"BoosterMagic":0,"BoosterRocket":0,"BoosterClearSingle":1,"Life":0,"Coin":590,"star":0,"BoosterClearCross":0,"UnlimitedLives":0}} Here, you can change `Coin` and `star` as much as you like. However, you must re-generate the checksum as follows: checksum = hmac_sha256(key="9e30c2ab-e2d6-47bf-9999-3371f14bdac0", data=<json>+"UserSettingsManagerLocalPrivateUserSettings") Where `<json>` is the new JSON data (not URL-encoded) and `+` is string concatenation. Now that you have modified the backup tarbal, you can create a new backup, `backup_new.ab` file as follows: $ dd if=backup.ab bs=1 count=24 >backup_new.ab $ cat backup.tar | python2 -c "import zlib,sys;sys.stdout.write(zlib.compress(sys.stdin.read()))" >>backup_new.ab Finally, upload this backup file to your phone like so: $ adb restore backup_new.ab # How I did it First, I extracted the apk from my phone using `adb`, and then I extracted it with [apktool](https://ibotpeaches.github.io/Apktool/). I quickly noticed that the game uses Unity, and that most of the source code was in a file called `armeabi-v7a/libil2cpp.so`. Upon discovering that `libil2cpp.so` had no symbols, I tried using `adb backup` to inspect the app's data directly. Using the data extracted from `adb backup`, I grep'd for keywords like "coin" and "star", and eventually found where the game stored these quantities. However, I could not modify these fields as they were signed. Modifying any values resulted in the game being effectively reset to the starting point. Since the backup data was signed, I knew I needed to look deeper at the code. I found [Il2CppInspector](https://github.com/djkaty/Il2CppInspector), and used it to get symbols. With symbols, I found a few things of note: ``` public class TactilePlayerPrefs // TypeDefIndex: 5728 { ... public static void SetSignedString(string name, string value); // 0x00B2060C public static void SetSecuredString(string name, string value); // 0x00B209F0 public static string GetString(string name); // 0x00B20A98 public static string GetString(string name, string defaultValue); // 0x00B20B48 public static string GetSignedString(string name, string defaultValue); // 0x00B20C00 public static string GetSecuredString(string name, string defaultValue); // 0x00B20EC8 ... } public static class TactilePrefsExtensions // TypeDefIndex: 5730 { // Extension methods public static string TactilePlayerPrefsHash(this string value, string salt); // 0x00B20724 public static byte[] TactilePlayerPrefsCryptKeyHash(this string value); // 0x00B219DC } ``` I used `objdump` to dump the assembly for `libil2cpp.so`, and found the corresponding blocks of instructions for these methods. It turned out that `SetSecuredString()` simply called `SetSignedString`, which md5-hashes the key name and signs the value using a hash from `TactilePlayerPrefsHash`. Looking at the disassembly for `TactilePlayerPrefsHash()` (which I include below), I inferred the following original code: ``` key = <something from a static variable after a lot of indirection> x = HMACSHA256(Encoding.UTF8.GetBytes(key)) y = x.ComputeHash(Encoding.UTF8.GetBytes(value + salt)) z = StringBuilder() foreach (byte b in y) { z.append(b.ToString("x2")) } return z.ToString() ``` The only missing piece was `key`. It seemed that the key was stored somewhere in the `.bss` section (for static variables). This looked bad, since it could be generated at runtime in some complex way. However, I was hopeful that the key was just sitting inside of the binary somewhere, or in `global-metadata.dat` (where `libil2cpp.so` stores many of its strings and symbols). Thus, I wrote a little program (keysearch.cs below) that tried every short substring in the entire binary as the key. The program worked, and I found the key of `9e30c2ab-e2d6-47bf-9999-3371f14bdac0`! This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,161 @@ ; Start of TactilePlayerPrefsHash b20728: e28db018 add fp, sp, #24 b2072c: e24dd008 sub sp, sp, #8 b20730: e59f027c ldr r0, [pc, #636] ; b209b4 <dladdr@plt+0x90b9d8>, value: 0x00a6722c b20734: e1a08002 mov r8, r2 b20738: e59f4278 ldr r4, [pc, #632] ; b209b8 <dladdr@plt+0x90b9dc>, value: 0x000936fc b2073c: e1a05001 mov r5, r1 b20740: e08f0000 add r0, pc, r0 ; value is now 0x1587974 b20744: e0840000 add r0, r4, r0 ; value is now 0x161b070 b20748: e5d00145 ldrb r0, [r0, #325] ; 0x145 b2074c: e3500000 cmp r0, #0 b20750: 1a000008 bne b20778 <dladdr@plt+0x90b79c> b20754: e59f0260 ldr r0, [pc, #608] ; b209bc <dladdr@plt+0x90b9e0> b20758: e59f1260 ldr r1, [pc, #608] ; b209c0 <dladdr@plt+0x90b9e4> b2075c: e08f6000 add r6, pc, r0 b20760: e7910006 ldr r0, [r1, r6] b20764: e5900000 ldr r0, [r0] b20768: eb18eb33 bl 115b43c <_ZNSt6vectorISsSaISsEE19_M_emplace_back_auxIJSsEEEvDpOT_+0x7da4> b2076c: e0840006 add r0, r4, r6 b20770: e3a01001 mov r1, #1 b20774: e5c01145 strb r1, [r0, #325] ; 0x145 b20778: e59f0244 ldr r0, [pc, #580] ; b209c4 <dladdr@plt+0x90b9e8> b2077c: e59f1244 ldr r1, [pc, #580] ; b209c8 <dladdr@plt+0x90b9ec> b20780: e08f0000 add r0, pc, r0 b20784: e7910000 ldr r0, [r1, r0] b20788: e3a01000 mov r1, #0 b2078c: e5cd1007 strb r1, [sp, #7] b20790: e5900000 ldr r0, [r0] b20794: e5d010b2 ldrb r1, [r0, #178] ; 0xb2 b20798: e3110001 tst r1, #1 b2079c: 0a000003 beq b207b0 <dladdr@plt+0x90b7d4> b207a0: e5901060 ldr r1, [r0, #96] ; 0x60 b207a4: e3510000 cmp r1, #0 b207a8: 1a000000 bne b207b0 <dladdr@plt+0x90b7d4> b207ac: eb193173 bl 116cd80 <_ZNSt6vectorISsSaISsEE19_M_emplace_back_auxIJSsEEEvDpOT_+0x196e8> b207b0: e59f0214 ldr r0, [pc, #532] ; b209cc <dladdr@plt+0x90b9f0>, 0x00a671b0 b207b4: e3a01000 mov r1, #0 b207b8: e59f7210 ldr r7, [pc, #528] ; b209d0 <dladdr@plt+0x90b9f4>, 0x00002a98 b207bc: e08f0000 add r0, pc, r0 b207c0: e7974000 ldr r4, [r7, r0] ; read from 158a40c, 0x01603b2c b207c4: e3a00000 mov r0, #0 b207c8: ebf32e00 bl 7ebfd0 <dladdr@plt+0x5d6ff4> ; UTF-8 b207cc: e1a06000 mov r6, r0 b207d0: e5940000 ldr r0, [r4] b207d4: e5d010b2 ldrb r1, [r0, #178] ; 0xb2 b207d8: e3110001 tst r1, #1 b207dc: 0a000003 beq b207f0 <dladdr@plt+0x90b814> b207e0: e5901060 ldr r1, [r0, #96] ; 0x60 b207e4: e3510000 cmp r1, #0 b207e8: 1a000000 bne b207f0 <dladdr@plt+0x90b814> b207ec: eb193163 bl 116cd80 <_ZNSt6vectorISsSaISsEE19_M_emplace_back_auxIJSsEEEvDpOT_+0x196e8> b207f0: e3560000 cmp r6, #0 b207f4: 0a00006b beq b209a8 <dladdr@plt+0x90b9cc> b207f8: e59f01d4 ldr r0, [pc, #468] ; b209d4 <dladdr@plt+0x90b9f8>, value 00a6716c b207fc: e5961000 ldr r1, [r6] b20800: e08f4000 add r4, pc, r0 b20804: e7970004 ldr r0, [r7, r4] ; 2a98 + b20808 + a6716c = 158a40c -> 01603b2c b20808: e5913104 ldr r3, [r1, #260] ; 0x104 b2080c: e5912108 ldr r2, [r1, #264] ; 0x108 b20810: e5900000 ldr r0, [r0] ; -> 0a0000ea b20814: e5900050 ldr r0, [r0, #80] ; 0x50 b20818: e5901000 ldr r1, [r0] b2081c: e1a00006 mov r0, r6 b20820: e12fff33 blx r3 b20824: e1a07000 mov r7, r0 b20828: e59f01a8 ldr r0, [pc, #424] ; b209d8 <dladdr@plt+0x90b9fc> b2082c: e7900004 ldr r0, [r0, r4] b20830: e5900000 ldr r0, [r0] b20834: eb19eba2 bl 119b6c4 <_ZNSt8_Rb_treeISsSsSt9_IdentityISsESt4lessISsESaISsEE8_M_eraseEPSt13_Rb_tree_nodeISsE+0xafac> b20838: e1a06000 mov r6, r0 b2083c: e1a01007 mov r1, r7 b20840: e3a02000 mov r2, #0 b20844: ebe94c98 bl 573aac <dladdr@plt+0x35ead0> ; HMACSHA256 b20848: e59f018c ldr r0, [pc, #396] ; b209dc <dladdr@plt+0x90ba00> b2084c: e3a01000 mov r1, #0 b20850: e7904004 ldr r4, [r0, r4] b20854: e3a00000 mov r0, #0 b20858: ebf32ddc bl 7ebfd0 <dladdr@plt+0x5d6ff4> ; UTF-8 b2085c: e1a07000 mov r7, r0 b20860: e5940000 ldr r0, [r4] b20864: e5d010b2 ldrb r1, [r0, #178] ; 0xb2 b20868: e3110001 tst r1, #1 b2086c: 0a000003 beq b20880 <dladdr@plt+0x90b8a4> b20870: e5901060 ldr r1, [r0, #96] ; 0x60 b20874: e3510000 cmp r1, #0 b20878: 1a000000 bne b20880 <dladdr@plt+0x90b8a4> b2087c: eb19313f bl 116cd80 <_ZNSt6vectorISsSaISsEE19_M_emplace_back_auxIJSsEEEvDpOT_+0x196e8> b20880: e3a00000 mov r0, #0 b20884: e1a01005 mov r1, r5 b20888: e1a02008 mov r2, r8 b2088c: e3a03000 mov r3, #0 b20890: ebf2c70d bl 7d24cc <dladdr@plt+0x5bd4f0> ; concat b20894: e1a01000 mov r1, r0 b20898: e3570000 cmp r7, #0 b2089c: 0a000041 beq b209a8 <dladdr@plt+0x90b9cc> b208a0: e5970000 ldr r0, [r7] b208a4: e5903104 ldr r3, [r0, #260] ; 0x104 b208a8: e5902108 ldr r2, [r0, #264] ; 0x108 b208ac: e1a00007 mov r0, r7 b208b0: e12fff33 blx r3 b208b4: e1a01000 mov r1, r0 b208b8: e3560000 cmp r6, #0 b208bc: 0a000039 beq b209a8 <dladdr@plt+0x90b9cc> b208c0: e1a00006 mov r0, r6 b208c4: e3a02000 mov r2, #0 b208c8: ebe93a8d bl 56f304 <dladdr@plt+0x35a328> ; HashAlgorithm::ComputeHash b208cc: e1a09000 mov r9, r0 b208d0: e3590000 cmp r9, #0 b208d4: 0a000033 beq b209a8 <dladdr@plt+0x90b9cc> b208d8: e59f0100 ldr r0, [pc, #256] ; b209e0 <dladdr@plt+0x90ba04> b208dc: e59f1100 ldr r1, [pc, #256] ; b209e4 <dladdr@plt+0x90ba08> b208e0: e08f0000 add r0, pc, r0 b208e4: e7910000 ldr r0, [r1, r0] b208e8: e5900000 ldr r0, [r0] b208ec: eb19eb74 bl 119b6c4 <_ZNSt8_Rb_treeISsSsSt9_IdentityISsESt4lessISsESaISsEE8_M_eraseEPSt13_Rb_tree_nodeISsE+0xafac> b208f0: e1a05000 mov r5, r0 b208f4: e599000c ldr r0, [r9, #12] b208f8: e3a02000 mov r2, #0 b208fc: e3a07000 mov r7, #0 b20900: e1a01080 lsl r1, r0, #1 b20904: e1a00005 mov r0, r5 b20908: ebf2fbb1 bl 7df7d4 <dladdr@plt+0x5ca7f8> ; StringBuilder() b2090c: e599000c ldr r0, [r9, #12] b20910: e3500001 cmp r0, #1 b20914: ba00001a blt b20984 <dladdr@plt+0x90b9a8> b20918: e59f10c8 ldr r1, [pc, #200] ; b209e8 <dladdr@plt+0x90ba0c> b2091c: e2896010 add r6, r9, #16 b20920: e59f20c4 ldr r2, [pc, #196] ; b209ec <dladdr@plt+0x90ba10> b20924: e28d8007 add r8, sp, #7 b20928: e08f1001 add r1, pc, r1 b2092c: e7924001 ldr r4, [r2, r1] b20930: e1500007 cmp r0, r7 b20934: 8a000001 bhi b20940 <dladdr@plt+0x90b964> b20938: eb19afed bl 118c8f4 <_ZNSs12_S_constructIN9__gnu_cxx17__normal_iteratorIPcSt6vectorIcSaIcEEEEEES2_T_S7_RKS4_St20forward_iterator_tag+0x12a8> b2093c: eb19ac25 bl 118b9d8 <_ZNSs12_S_constructIN9__gnu_cxx17__normal_iteratorIPcSt6vectorIcSaIcEEEEEES2_T_S7_RKS4_St20forward_iterator_tag+0x38c> b20940: e5941000 ldr r1, [r4] b20944: e3a02000 mov r2, #0 b20948: e7d60007 ldrb r0, [r6, r7] b2094c: e5cd0007 strb r0, [sp, #7] b20950: e1a00008 mov r0, r8 b20954: ebec2be6 bl 62b8f4 <dladdr@plt+0x416918> ; Byte::ToString() ? b20958: e1a01000 mov r1, r0 b2095c: e3550000 cmp r5, #0 b20960: 0a000010 beq b209a8 <dladdr@plt+0x90b9cc> b20964: e1a00005 mov r0, r5 b20968: e3a02000 mov r2, #0 b2096c: ebf2c92c bl 7d2e24 <dladdr@plt+0x5bde48> ; Append() b20970: e599000c ldr r0, [r9, #12] b20974: e2877001 add r7, r7, #1 b20978: e1570000 cmp r7, r0 b2097c: baffffeb blt b20930 <dladdr@plt+0x90b954> b20980: ea000001 b b2098c <dladdr@plt+0x90b9b0> b20984: e3550000 cmp r5, #0 b20988: 0a000006 beq b209a8 <dladdr@plt+0x90b9cc> b2098c: e5950000 ldr r0, [r5] b20990: e59020cc ldr r2, [r0, #204] ; 0xcc b20994: e59010d0 ldr r1, [r0, #208] ; 0xd0 b20998: e1a00005 mov r0, r5 b2099c: e12fff32 blx r2 b209a0: e24bd018 sub sp, fp, #24 b209a4: e8bd8bf0 pop {r4, r5, r6, r7, r8, r9, fp, pc} This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,30 @@ // Run with a grep command like this: // mono-csc keysearch.cs // mono keysearch.exe | grep -i 8a-fc-37-ef-22-65-bb-57-3e-8b-11 using System; using System.Linq; using System.Text; using System.Security.Cryptography; using System.IO; public class Program { public static void Main() { Encoding ascii = Encoding.UTF8; Byte[] data = File.ReadAllBytes("assets/bin/Data/Managed/Metadata/global-metadata.dat"); String salt = "UserSettingsManagerLocalPublicUserSettings"; String body = "{\"_settingsVersion\":0}"; String combined = body + salt; for (int keyLen = 1; keyLen <= 0x60; keyLen++) { Console.Error.WriteLine("working on keys of length " + keyLen); Byte[] arrCopy = new Byte[keyLen]; for (int i = 0; i < data.Length-keyLen; i++) { Array.Copy(data, i, arrCopy, 0, keyLen); HMACSHA256 hmac = new HMACSHA256(arrCopy); Console.WriteLine(i + ": " + BitConverter.ToString(hmac.ComputeHash(ascii.GetBytes(combined))) + " (key: " + BitConverter.ToString(arrCopy) + ")" ); } } } }