├── README.md └── Unity.h /README.md: -------------------------------------------------------------------------------- 1 | # UnityStuff 2 | ### @hackedbyshmoo 3 | 4 | This repository contains structs and other useful stuff to hack 64 bit Unity games on iOS. monoArray and monoString were written by caoyin. Included: 5 | 6 | Included: 7 | - A struct to represent a native C# array 8 | - A struct to represent a C# string 9 | - A struct to represent a List 10 | - A struct to represent a Dictionary 11 | - A function to make a C# string from a C string 12 | - A function to create a native C# array with a starting length 13 | - Some functions to get/set real values of Obscured types from Anti Cheat Toolkit (https://assetstore.unity.com/packages/tools/utilities/anti-cheat-toolkit-10395) 14 | 15 | I will add more as I go so check back frequently! 16 | 17 | To use, clone `Unity.h` into your project folder and `#include "Unity.h"` 18 | -------------------------------------------------------------------------------- /Unity.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define ASLR_BIAS _dyld_get_image_vmaddr_slide 4 | 5 | uint64_t getRealOffset(uint64_t offset){ 6 | return ASLR_BIAS + offset; 7 | } 8 | 9 | /* 10 | This struct can hold a native C# array. Credits to caoyin. 11 | 12 | Think of it like a wrapper for a C array. For example, if you had Player[] players in a dump, 13 | the resulting monoArray definition would be monoArray *players; 14 | 15 | To get the C array, call getPointer. 16 | To get the length, call getLength. 17 | */ 18 | template 19 | struct monoArray 20 | { 21 | void* klass; 22 | void* monitor; 23 | void* bounds; 24 | int max_length; 25 | void* vector [1]; 26 | int getLength() 27 | { 28 | return max_length; 29 | } 30 | T getPointer() 31 | { 32 | return (T)vector; 33 | } 34 | }; 35 | 36 | /* 37 | This struct represents a C# string. Credits to caoyin. 38 | 39 | It is pretty straight forward. If you have this in a dump, 40 | 41 | public class Player { 42 | public string username; // 0xC8 43 | } 44 | 45 | getting that string would look like this: monoString *username = *(monoString **)((uint64_t)player + 0xc8); 46 | 47 | C# strings are UTF-16. This means each character is two bytes instead of one. 48 | 49 | To get the length of a monoString, call getLength. 50 | To get a NSString from a monoString, call toNSString. 51 | To get a std::string from a monoString, call toCPPString. 52 | To get a C string from a monoString, call toCString. 53 | */ 54 | typedef struct _monoString 55 | { 56 | void* klass; 57 | void* monitor; 58 | int length; 59 | char chars[1]; 60 | int getLength() 61 | { 62 | return length; 63 | } 64 | char* getChars() 65 | { 66 | return chars; 67 | } 68 | NSString* toNSString() 69 | { 70 | return [[NSString alloc] initWithBytes:(const void *)(chars) 71 | length:(NSUInteger)(length * 2) 72 | encoding:(NSStringEncoding)NSUTF16LittleEndianStringEncoding]; 73 | } 74 | 75 | char* toCString() 76 | { 77 | NSString* v1 = toNSString(); 78 | return (char*)([v1 UTF8String]); 79 | } 80 | std::string toCPPString() 81 | { 82 | return std::string(toCString()); 83 | } 84 | }monoString; 85 | 86 | /* 87 | This struct represents a List. In the dump, a List is declared as List`1. 88 | 89 | Deep down, this simply wraps a C array around a C# list. For example, if you had this in a dump, 90 | 91 | public class Player { 92 | List`1 perks; // 0xDC 93 | } 94 | 95 | getting that list would look like this: monoList *perks = *(monoList **)((uint64_t)player + 0xdc); 96 | 97 | You can also get lists that hold objects, but you need to use void ** because we don't have implementation for the Weapon class. 98 | 99 | public class Player { 100 | List`1 weapons; // 0xDC 101 | } 102 | 103 | getting that list would look like this: monoList *weapons = *(monoList **)((uint64_t)player + 0xdc); 104 | 105 | If you need a list of strings, use monoString **. 106 | 107 | To get the C array, call getItems. 108 | To get the size of a monoList, call getSize. 109 | */ 110 | template 111 | struct monoList { 112 | void *unk0; 113 | void *unk1; 114 | monoArray *items; 115 | int size; 116 | int version; 117 | 118 | T getItems(){ 119 | return items->getPointer(); 120 | } 121 | 122 | int getSize(){ 123 | return size; 124 | } 125 | 126 | int getVersion(){ 127 | return version; 128 | } 129 | }; 130 | 131 | /* 132 | This struct represents a Dictionary. In the dump, a Dictionary is defined as Dictionary`1. 133 | 134 | You could think of this as a Map in Java or C++. Keys correspond with values. This wraps the C arrays for keys and values. 135 | 136 | If you had this in a dump, 137 | 138 | public class GameManager { 139 | public Dictionary`1 players; // 0xB0 140 | public Dictionary`1 playerWeapons; // 0xB8 141 | public Dictionary`1 playerNames; // 0xBC 142 | } 143 | 144 | to get players, it would look like this: monoDictionary *players = *(monoDictionary **)((uint64_t)player + 0xb0); 145 | to get playerWeapons, it would look like this: monoDictionary *playerWeapons = *(monoDictionary **)((uint64_t)player + 0xb8); 146 | to get playerNames, it would look like this: monoDictionary *playerNames = *(monoDictionary **)((uint64_t)player + 0xbc); 147 | 148 | To get the C array of keys, call getKeys. 149 | To get the C array of values, call getValues. 150 | To get the number of keys, call getNumKeys. 151 | To get the number of values, call getNumValues. 152 | */ 153 | template 154 | struct monoDictionary { 155 | void *unk0; 156 | void *unk1; 157 | monoArray *table; 158 | monoArray *linkSlots; 159 | monoArray *keys; 160 | monoArray *values; 161 | int touchedSlots; 162 | int emptySlot; 163 | int size; 164 | 165 | K getKeys(){ 166 | return keys->getPointer(); 167 | } 168 | 169 | V getValues(){ 170 | return values->getPointer(); 171 | } 172 | 173 | int getNumKeys(){ 174 | return keys->getLength(); 175 | } 176 | 177 | int getNumValues(){ 178 | return values->getLength(); 179 | } 180 | 181 | int getSize(){ 182 | return size; 183 | } 184 | }; 185 | 186 | /* 187 | Turn a C string into a C# string! 188 | 189 | This function is included in Unity's string class. There are two versions of this function in the dump, you want the one the comes FIRST. 190 | The method signature is: 191 | 192 | private string CreateString(PTR value); 193 | 194 | Again, you want the FIRST one, not the second. 195 | */ 196 | monoString *U3DStr(const char *str){ 197 | monoString *(*String_CreateString)(void *_this, const char *str) = (monoString *(*)(void *, const char *))getRealOffset(/*LOCATION HERE*/); 198 | 199 | return String_CreateString(NULL, str); 200 | } 201 | 202 | /* 203 | Create a native C# array with a starting length. 204 | 205 | This one is kind of tricky to complete. I'm currently looking for an easier way to implement this. 206 | 207 | The offsets you need are both found at String::Split(char separator[], int count). Go to that function in IDA and scroll down until you find something like this: 208 | 209 | TBNZ W20, #0x1F, loc_101153944 210 | CMP W20, #1 211 | B.EQ loc_1011538B0 212 | CBNZ W20, loc_101153924 213 | ADRP X8, #qword_1026E0F20@PAGE 214 | NOP 215 | LDR X19, [X8,#qword_1026E0F20@PAGEOFF] 216 | MOV X0, X19 217 | BL sub_101DD8AF8 218 | MOV X0, X19 219 | MOV W1, #0 220 | LDP X29, X30, [SP,#0x30+var_10] 221 | LDP X20, X19, [SP,#0x30+var_20] 222 | LDP X22, X21, [SP+0x30+var_30],#0x30 223 | B sub_101DD74E8 224 | 225 | I filled in the locations here: 226 | 227 | TBNZ W20, #0x1F, loc_101153944 228 | CMP W20, #1 229 | B.EQ loc_1011538B0 230 | CBNZ W20, loc_101153924 231 | ADRP X8, #qword_1026E0F20@PAGE 232 | NOP 233 | LDR X19, [X8,#qword_1026E0F20@PAGEOFF] <-------- Whatever 1026E0F20 is in your game is your second location 234 | MOV X0, X19 235 | BL sub_101DD8AF8 236 | MOV X0, X19 237 | MOV W1, #0 238 | LDP X29, X30, [SP,#0x30+var_10] 239 | LDP X20, X19, [SP,#0x30+var_20] 240 | LDP X22, X21, [SP+0x30+var_30],#0x30 241 | B sub_101DD74E8 <-------- Whatever 101DD74E8 is in your game is your first location 242 | 243 | For example, if you wanted an array of 10 ints, you would do this: monoArray *integers = CreateNativeCSharpArray(10); 244 | 245 | You can use any type with this! 246 | */ 247 | template 248 | monoArray *CreateNativeCSharpArray(int startingLength){ 249 | monoArray *(*IL2CPPArray_Create)(void *klass, int startingLength) = (monoArray *(*)(void *, int))getRealOffset(/*FIRST LOCATION HERE*/); 250 | 251 | void *unkptr0 = *(void **)(ASLR_BIAS + /*SECOND LOCATION HERE*/); 252 | void *klass = *(void **)((uint64_t)unkptr0); 253 | 254 | monoArray *arr = IL2CPPArray_Create(klass, startingLength); 255 | 256 | return arr; 257 | } 258 | 259 | /* 260 | Here are some functions to safely get/set values for types from Anti Cheat Toolkit (https://assetstore.unity.com/packages/tools/utilities/anti-cheat-toolkit-10395) 261 | 262 | I will add more to this as I go along. 263 | */ 264 | 265 | /* 266 | Get the real value of an ObscuredInt. 267 | 268 | Parameters: 269 | - location: the location of the ObscuredInt 270 | */ 271 | int GetObscuredIntValue(uint64_t location){ 272 | int cryptoKey = *(int *)location; 273 | int obfuscatedValue = *(int *)(location + 0x4); 274 | 275 | return obfuscatedValue ^ cryptoKey; 276 | } 277 | 278 | /* 279 | Set the real value of an ObscuredInt. 280 | 281 | Parameters: 282 | - location: the location of the ObscuredInt 283 | - value: the value we're setting the ObscuredInt to 284 | */ 285 | void SetObscuredIntValue(uint64_t location, int value){ 286 | int cryptoKey = *(int *)location; 287 | 288 | *(int *)(location + 0x4) = value ^ cryptoKey; 289 | } 290 | 291 | /* 292 | Get the real value of an ObscuredFloat. 293 | 294 | Parameters: 295 | - location: the location of the ObscuredFloat 296 | */ 297 | float GetObscuredFloatValue(uint64_t location){ 298 | int cryptoKey = *(int *)location; 299 | int obfuscatedValue = *(int *)(location + 0x4); 300 | 301 | union intfloat { 302 | int i; 303 | float f; 304 | }; 305 | 306 | /* use this intfloat to set the integer representation of our parameter value, which will also set the float value */ 307 | intfloat IF; 308 | IF.i = obfuscatedValue ^ cryptoKey; 309 | 310 | return IF.f; 311 | } 312 | 313 | /* 314 | Set the real value of an ObscuredFloat. 315 | 316 | Parameters: 317 | - location: the location of the ObscuredFloat 318 | - value: the value we're setting the ObscuredFloat to 319 | */ 320 | void SetObscuredFloatValue(uint64_t location, float value){ 321 | int cryptoKey = *(int *)location; 322 | 323 | union intfloat { 324 | int i; 325 | float f; 326 | }; 327 | 328 | /* use this intfloat to get the integer representation of our parameter value */ 329 | intfloat IF; 330 | IF.f = value; 331 | 332 | /* use this intfloat to generate our hacked ObscuredFloat */ 333 | intfloat IF2; 334 | IF2.i = IF.i ^ cryptoKey; 335 | 336 | *(float *)(location + 0x4) = IF2.f; 337 | } --------------------------------------------------------------------------------