苹果重复应用问题
苹果提审中,被4.3重复应用打回来很常见(诸如上马甲包等),而首先一步是面对机审,苹果检测二进制文件,而游戏主要要处理的也就代码和资源文件,以及icon和splash。
一、处理资源文件
我的做法是将所有资源压缩成一个文件,再对压缩资源进行简单的异或加密,游戏时,在解压前再用相同的key对资源进行异或运算还原资源,最后直接解压到persistentDataPath目录。以下是异或加密算法。
1 //异或加密 2 public static void XOREncryption(string resPath, string key) 3 { 4 //读取资源文件 5 FileStream fileStream = File.OpenRead(resPath); 6 byte[] data = new byte[fileStream.Length]; 7 fileStream.Read(data, 0, data.Length); 8 fileStream.Close(); 9 10 //异或加密 11 for (int i = 0; i < data.Length; i++) 12 { 13 int index = i % key.Length; 14 data[i] = (byte)(data[i] ^ key[index]); 15 } 16 17 //替换加密后文件 18 File.Delete(resPath); 19 File.WriteAllBytes(resPath, data); 20 }
二、处理代码
处理方法是通过工具生成一些垃圾代码来改变代码二进制文件,以下是整个工具实现:
1 using LuaFramework; 2 using UnityEditor; 3 using System; 4 using System.Text; 5 using System.IO; 6 using System.Security.Cryptography; 7 8 //文件定义:垃圾代码生成器 9 //一、设计要点: 10 //1、用当前时间+生成代码序号生成md5字符,用md5字符命名类名、函数名、变量名等 11 //2、随机一种函数模版,用1生成的函数名、参数名生成函数体 12 //3、生成GarbageCodeManager类调用所有生成的代码 13 //二、范式生成要点: 14 //1、用正常c#写代码 15 //2、用ChangeClassFile2CodeCreateTool.py工具将正常代码转成生成该代码的代码(类名、函数名等已替换为参数) 16 //三、游戏调用 17 //1、在main.cs中通过UNITY_IPHONE && SDK宏控制调用GarbageCodeManager.CallAllGarbageCode()调用生成的代码 18 //作者:青树 19 //时间:2017/10/20 20 21 //垃圾代码参数 22 public class GarbageCodeParam { 23 public string className; //类名 24 public string mainFuncName; //入口函数名 25 public string mainFuncParam1; //入口函数参数1 26 public string mainFuncParam2; //入口函数参数2 27 public string mainFuncParam3; //入口函数参数3 28 } 29 30 //垃圾代码生成器 31 public class GarbageCodeTool 32 { 33 //生成垃圾代码函数模版个数 34 private const int funcTplCount = 1; 35 //函数模版委托:入口函数名,入口函数参数1,入口函数参数2,入口函数参数3 ;返回生成该函数的字符串 36 private delegate string FuncTplHandle(string methonName, string param1, string param2, string param3); 37 //函数模版数组 38 private static FuncTplHandle[] arrFuncTplHandle; 39 //管理所有垃圾代码的文件名 40 private const string garbageCodeManagerName = "CallAllCodeManager"; 41 private static bool isGenerateXcode = false; 42 43 //c#垃圾代码变量 44 private const string namespaceName = "LuaFramework"; //生成代码的命名空间 45 private static string mstCreateCodeFilePath = //生成代码路径 46 UnityEngine.Application.dataPath + "/LuaFramework/Scripts/GarbageCode"; 47 private const int maxFileCount = 1000; //垃圾代码个数 48 49 //Xcode垃圾代码变量 50 private static string mstCreateXCodeFilePath = //生成XCode代码路径 51 UnityEngine.Application.dataPath + "/LuaFramework/Scripts/GarbageXCode"; 52 private const int maxXCodeFileCount = 50; //XCode垃圾代码个数 53 54 //生成C#垃圾代码 55 [MenuItem("Tools/GenerateGarbageCode")] 56 static void GenerateGarbageCode() { 57 isGenerateXcode = false; 58 59 //删除旧目录代码 60 if (Directory.Exists(mstCreateCodeFilePath)) 61 { 62 Directory.Delete(mstCreateCodeFilePath, true); 63 } 64 AssetDatabase.Refresh(); 65 AssetDatabase.SaveAssets(); 66 67 #if !UNITY_IPHONE 68 //只有苹果才生成垃圾代码 69 return; 70 #endif 71 72 //重新创建目录 73 Directory.CreateDirectory(mstCreateCodeFilePath); 74 Directory.CreateDirectory(mstCreateCodeFilePath + "/Code"); 75 76 //获取本地时间作为生成md5条件之一 77 string timeNow = DateTime.Now.ToString(); 78 string[] arrClassName = new string[maxFileCount]; 79 string[] arrMethonName = new string[maxFileCount]; 80 81 #region 生成所有垃圾代码 82 StringBuilder stringBuilder = null; 83 for (int i = 0; i < maxFileCount; i++) { 84 //获取垃圾代码参数 85 GarbageCodeParam funcTplParam = GetFuncTplParam(timeNow,i); 86 87 //生成文件名 88 string fileName = Path.Combine(mstCreateCodeFilePath + "/Code", funcTplParam.className + ".cs"); 89 if (string.IsNullOrEmpty(fileName)) 90 { 91 continue; 92 } 93 94 //构造生成文本对象 95 stringBuilder = new StringBuilder(); 96 97 //添加命名空间 98 if (!string.IsNullOrEmpty(namespaceName)) 99 { 100 stringBuilder.AppendFormat(string.Format("namespace {0}\n", namespaceName)); 101 stringBuilder.AppendLine("{"); 102 } 103 stringBuilder.AppendLine(""); 104 105 //生成类名 106 stringBuilder.AppendFormat("public class {0} \n", funcTplParam.className); 107 stringBuilder.AppendLine("{"); 108 109 //随机选择一种函数模版作为类函数 110 string funcContent = GetFuncContent(funcTplParam.mainFuncName, 111 funcTplParam.mainFuncParam1, funcTplParam.mainFuncParam2, funcTplParam.mainFuncParam3); 112 stringBuilder.Append(funcContent); 113 114 //添加最后的括号,保存文件 115 stringBuilder.AppendLine("}"); 116 if (!string.IsNullOrEmpty(namespaceName)) 117 { 118 stringBuilder.AppendLine("}"); 119 } 120 File.WriteAllText(fileName, stringBuilder.ToString()); 121 122 arrClassName[i] = funcTplParam.className; 123 arrMethonName[i] = funcTplParam.mainFuncName; 124 } 125 #endregion 生成所有垃圾代码 126 127 #region 生成调用所有垃圾代码的代码 128 string managerFilePath = Path.Combine(mstCreateCodeFilePath, garbageCodeManagerName + ".cs"); 129 if (string.IsNullOrEmpty(managerFilePath)) 130 { 131 return; 132 } 133 stringBuilder = new StringBuilder(); 134 if (!string.IsNullOrEmpty(namespaceName)) 135 { 136 stringBuilder.AppendFormat(string.Format("namespace {0}\n", namespaceName)); 137 stringBuilder.AppendLine("{"); 138 } 139 stringBuilder.AppendLine("using UnityEngine;"); 140 stringBuilder.AppendLine("using System.Collections;"); 141 stringBuilder.AppendLine(""); 142 stringBuilder.AppendLine("namespace LuaFramework {"); 143 stringBuilder.AppendLine(" //垃圾代码管理器"); 144 stringBuilder.AppendFormat("public class {0}\n", garbageCodeManagerName); 145 stringBuilder.AppendLine(" {"); 146 stringBuilder.AppendLine(" //调用所有垃圾代码"); 147 stringBuilder.AppendLine(" public static void CallAllGarbageCode() {"); 148 149 150 //调用所有垃圾代码 151 Random rd = new Random(); 152 for (int i = 0; i < arrClassName.Length; i++) 153 { 154 string className = arrClassName[i]; 155 string methonName = arrMethonName[i]; 156 if (className == "" || methonName == "") 157 { 158 continue; 159 } 160 int randa = rd.Next(0, 1000); 161 int randb = rd.Next(0, 1000); 162 int randc = rd.Next(0, 1000); 163 stringBuilder.AppendFormat(" {0} _{1} = new {2}();\n", className, className, className); 164 if (randc > 500) 165 { 166 stringBuilder.AppendFormat(" _{0}.{1}({2},{3});\n", className, methonName, randa, randb); 167 } 168 else 169 { 170 stringBuilder.AppendFormat(" _{0}.{1}({2},{3},{4});\n", className, methonName, randa, randb, randc); 171 } 172 stringBuilder.AppendLine(""); 173 } 174 175 176 stringBuilder.AppendLine(" }"); 177 stringBuilder.AppendLine(" }"); 178 stringBuilder.AppendLine("}"); 179 stringBuilder.AppendLine(""); 180 if (!string.IsNullOrEmpty(namespaceName)) 181 { 182 stringBuilder.AppendLine("}"); 183 } 184 File.WriteAllText(managerFilePath, stringBuilder.ToString()); 185 #endregion 生成调用所有垃圾代码的代码 186 187 AssetDatabase.Refresh(); 188 AssetDatabase.SaveAssets(); 189 190 EditorUtility.DisplayDialog("GenerateGarbageCode","生成完毕!", "确定"); 191 } 192 193 //生成Xcode垃圾代码 194 [MenuItem("Tools/GenerateXCodeGarbageCode")] 195 static void GenerateXCodeGarbageCode() 196 { 197 isGenerateXcode = true; 198 199 //删除旧目录代码 200 if (Directory.Exists(mstCreateXCodeFilePath)) 201 { 202 Directory.Delete(mstCreateXCodeFilePath, true); 203 } 204 AssetDatabase.Refresh(); 205 AssetDatabase.SaveAssets(); 206 207 #if !UNITY_IPHONE 208 //只有苹果才生成垃圾代码 209 return; 210 #endif 211 212 //重新创建目录 213 Directory.CreateDirectory(mstCreateXCodeFilePath); 214 Directory.CreateDirectory(mstCreateXCodeFilePath + "/Code"); 215 216 //获取本地时间作为生成md5条件之一 217 string timeNow = DateTime.Now.ToString(); 218 string[] arrClassName = new string[maxXCodeFileCount]; 219 string[] arrMethonName = new string[maxXCodeFileCount]; 220 221 #region 生成所有xcode垃圾代码 222 StringBuilder stringBuilder = null; 223 for (int i = 0; i < maxXCodeFileCount; i++) 224 { 225 //获取垃圾代码参数 226 GarbageCodeParam funcTplParam = GetFuncTplParam(timeNow, i); 227 228 //生成文件名 229 string fileName = Path.Combine(mstCreateXCodeFilePath + "/Code", funcTplParam.className + ".mm"); 230 if (string.IsNullOrEmpty(fileName)) 231 { 232 continue; 233 } 234 235 //构造生成文本对象 236 stringBuilder = new StringBuilder(); 237 238 stringBuilder.AppendLine(""); 239 240 //随机选择一种函数模版作为类函数 241 string funcContent = GetFuncContent(funcTplParam.mainFuncName, 242 funcTplParam.mainFuncParam1, funcTplParam.mainFuncParam2, funcTplParam.mainFuncParam3); 243 stringBuilder.Append(funcContent); 244 245 //保存文件 246 File.WriteAllText(fileName, stringBuilder.ToString()); 247 248 arrClassName[i] = funcTplParam.className; 249 arrMethonName[i] = funcTplParam.mainFuncName; 250 } 251 #endregion 生成所有xcode垃圾代码 252 253 #region 生成调用所有垃圾代码的代码 254 string managerFilePath = Path.Combine(mstCreateXCodeFilePath, garbageCodeManagerName + ".mm"); 255 if (string.IsNullOrEmpty(managerFilePath)) 256 { 257 return; 258 } 259 stringBuilder = new StringBuilder(); 260 stringBuilder.AppendLine(" //调用所有代码"); 261 stringBuilder.AppendFormat(" void {0}()\n", garbageCodeManagerName); 262 stringBuilder.AppendLine("{"); 263 264 //调用所有垃圾代码 265 Random rd = new Random(); 266 for (int i = 0; i < arrMethonName.Length; i++) 267 { 268 string methonName = arrMethonName[i]; 269 if (methonName == "") 270 { 271 continue; 272 } 273 int randa = rd.Next(0, 1000); 274 int randb = rd.Next(0, 1000); 275 int randc = rd.Next(0, 1000); 276 277 stringBuilder.AppendFormat(" extern int {0}(int a,int b,int c = 0);\n", methonName); 278 if (randc > 500) 279 { 280 stringBuilder.AppendFormat(" {0}({1},{2});\n", methonName, randa, randb); 281 } 282 else 283 { 284 stringBuilder.AppendFormat(" {0}({1},{2},{3});\n", methonName, randa, randb, randc); 285 } 286 stringBuilder.AppendLine(""); 287 } 288 stringBuilder.AppendLine("}"); 289 stringBuilder.AppendLine(""); 290 File.WriteAllText(managerFilePath, stringBuilder.ToString()); 291 #endregion 生成调用所有垃圾代码的代码 292 293 AssetDatabase.Refresh(); 294 AssetDatabase.SaveAssets(); 295 296 EditorUtility.DisplayDialog("GenerateGarbageXCodeCode", "生成完毕!", "确定"); 297 } 298 299 //根据时间字符加序号产生垃圾代码参数 300 public static GarbageCodeParam GetFuncTplParam(string timeNow,int index) { 301 //根据当前时间+序号 生成md5值 302 string md5 = CalcMd5(timeNow + index.ToString()); 303 md5 = "_" + md5; 304 305 //用md5值生成类名、入口函数名、参数名 306 GarbageCodeParam garbageCodeParam = new GarbageCodeParam(); 307 garbageCodeParam.className = md5; 308 garbageCodeParam.mainFuncName = md5 + "m"; 309 garbageCodeParam.mainFuncParam1 = md5 + "a"; 310 garbageCodeParam.mainFuncParam2 = md5 + UnityEngine.Random.Range(0, 100); 311 garbageCodeParam.mainFuncParam3 = md5 + "c"; 312 return garbageCodeParam; 313 } 314 315 //获取函数体(根据已有模版随机生成) 316 public static string GetFuncContent(string methonName, string param1, string param2, string param3) { 317 #if !UNITY_IPHONE 318 //只有苹果才生成垃圾代码 319 return ""; 320 #endif 321 //如果没有初始化则初始化函数模版 322 if (null == arrFuncTplHandle || arrFuncTplHandle.Length < funcTplCount) { 323 arrFuncTplHandle = new FuncTplHandle[funcTplCount]; 324 arrFuncTplHandle[0] = FuncTpl1; 325 } 326 327 //随机一种模版生成函数体 328 int randNum = UnityEngine.Random.Range(0, funcTplCount); 329 if (arrFuncTplHandle[randNum] == null) 330 { 331 Util.LogError("不存在范式,索引:" + randNum.ToString()); 332 return ""; 333 } 334 return arrFuncTplHandle[randNum](methonName, param1, param2, param3); 335 } 336 337 /// <summary> 338 /// 计算字符串的MD5值 339 /// </summary> 340 static string CalcMd5(string source) 341 { 342 MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider(); 343 byte[] data = System.Text.Encoding.UTF8.GetBytes(source); 344 byte[] md5Data = md5.ComputeHash(data, 0, data.Length); 345 md5.Clear(); 346 347 string destString = ""; 348 for (int i = 0; i < md5Data.Length; i++) 349 { 350 destString += System.Convert.ToString(md5Data[i], 16).PadLeft(2, '0'); 351 } 352 destString = destString.PadLeft(32, '0'); 353 return destString; 354 } 355 356 357 //函数模版1 358 static string FuncTpl1(string methonName, string param1, string param2, string param3) 359 { 360 StringBuilder stringBuilder = new StringBuilder(); 361 stringBuilder.AppendFormat(" int {0}2(int {1})\n", methonName, param1); 362 stringBuilder.AppendLine(" {"); 363 stringBuilder.AppendFormat(" return (int)(3.1415926535897932384626433832795028841 * {0} * {0});\n", param1); 364 stringBuilder.AppendLine(" }"); 365 stringBuilder.AppendLine(""); 366 if (isGenerateXcode) 367 { 368 //入口函数xcode没有public 369 stringBuilder.AppendFormat(" int {0}(int {1},int {2},int {3} = 0) \n", methonName, param1, param2, param3); 370 } 371 else 372 { 373 stringBuilder.AppendFormat(" public int {0}(int {1},int {2},int {3} = 0) \n", methonName, param1, param2, param3); 374 } 375 stringBuilder.AppendLine(" {"); 376 stringBuilder.AppendFormat(" int t{0}p = {0} * {1};\n", param1, param2); 377 stringBuilder.AppendFormat(" if ({1} != 0 && t{0}p > {1})\n", param1,param3); 378 stringBuilder.AppendLine(" {"); 379 stringBuilder.AppendFormat(" t{1}p = t{1}p / {0};\n", param3, param1); 380 stringBuilder.AppendLine(" }"); 381 stringBuilder.AppendLine(" else"); 382 stringBuilder.AppendLine(" {"); 383 stringBuilder.AppendFormat(" t{1}p -= {0};\n", param3, param1); 384 stringBuilder.AppendLine(" }"); 385 stringBuilder.AppendLine(""); 386 stringBuilder.AppendFormat(" return {0}2(t{1}p);\n", methonName, param1); 387 stringBuilder.AppendLine(" }"); 388 return stringBuilder.ToString(); 389 } 390 }
三、人工审核
暂时没有一套好的方案,我们的方案是只保留了充值和最简单的功能,资源也用简单几套替换,提包的时候用vpn隐藏真实ip。