随着微信小游戏平台的普及和完善,越来越多的游戏开发者加入到微信小游戏开发队伍中,很多公司开发者一个团队就开发了几十款游戏。
目前,微信小游戏同质化严重,大多是一套代码,换换皮肤就变成了一款新游戏。更有甚者,有些开发者直接反编译别人的游戏包进行简单修改,变成自己的产品申请上架。严重损害著作权所有人的利益和平台规则。
为了遏制和打击这种现象,微信小游戏平台有一个环节是机审,审查代码包的相似度,如果代码包相似度超过一定的比例就会被判定为“代码包侵权”,审核不予通过。
这本身是好事,但是却产生了大量“误杀”行为。很多公司开发团队用同一套自己研发的游戏引擎开发出不同的产品,申请上线的时候会判定为“侵权”。这就郁闷了,自己侵权自己。
虽然微信官方说可以申诉,但是时间不受控制,这不是一个上策。更好的方式是修改代码,混淆代码,让其看起来和别的项目不像,不要触发微信代码机审的“黑机关”。
好了,废话了半天,该上菜了!希望各位读者收获满满!
混淆代码的思路:
1、修改所有代码Class 类名称不要重复
2、修改全部全局属性、和至少 1/3的方法名称不要重复(这部分可以写程序去批量改)
3、打乱那些基类、工具类里面的方法顺序, 举例 Class A里面有 Function B、C、D,在不同项目里面改乱顺序,项目1里面的 Class A中顺序是B、D、C;项目2里面的 Class A中顺序是D、C、B;
4、如果定义了 package包名,包名也改掉;
5、每个类里面 随机插入一些废代码(注意,不是直接复制独立的废代码文件到项目中,这种方式无效)
6、如果是白鹭引擎,记把exml里面的代码也改改,各种重用的组件比如按钮、Class名称和文件名都改一下。
7、如果是 Laya 2.0项目,class 前面 不要用default 定义。
按照以上思路,修改到位,经过作者30+款产品上线的经验,通过率99.99%。
以上修改思路是有一定工作量的,而且是枯燥的,所以最好写代码脚本去处理,作者一般是手动+自动相结合的方式去混淆代码,以下代码实现了部分混淆代码的功能:
/* 为项目添加干扰代码,主要做这几件事: 1、修改每个类里面的私有方法,添加一个随机数 2、在代码里面随机一行插入一个随机命名的方法体 3、在代码里面随机一行插入一行没有意义的代码 */ const LINENUM = 20; const LINENUM_RADOM = 13; var fs = require('fs'); var path = require('path'); var filterFils = ["Base64", "ThemeAdAPter","AssetAdapter", "Platform", "wxgamesdk"];//忽略文件 var filterDirs = ["xxxx"];//忽略目录 var traceName = "xxxx";//添加的干扰代码 var funString = '_xxxx_fun(){ console.log("';//添加的方法体 var root_Url = "D:/code/project/src";//项目diam路径 fileDisplay(path.resolve(root_Url)); /** * 文件遍历方法 * @param filePath 需要遍历的文件路径 */ function fileDisplay(filePath){ //根据文件路径读取文件,返回文件列表 fs.readdir(filePath,function(err,files){ if(err){ console.warn(err) }else{ //遍历读取到的文件列表 files.forEach(function(filename){ //获取当前文件的绝对路径 var filedir = path.join(filePath,filename); //根据文件路径获取文件信息,返回一个fs.Stats对象 fs.stat(filedir,function(eror,stats){ if(eror){ console.warn('获取文件stats失败'); }else{ var isFile = stats.isFile();//是文件 var isDir = stats.isDirectory();//是文件夹 if(isFile){ console.log(filename,filedir); let onlyName = filename.split(".")[0]; if(filename.indexOf(".ts") != -1 && filterFils.indexOf(onlyName) == -1){ changeDode(filedir); } } if(isDir){ if(!filterDirs || filterDirs.indexOf(filename) == -1){ fileDisplay(filedir);//递归,如果是文件夹,就继续遍历该文件夹下面的文件 } } } }) }); } }); } function changeDode(phppath){ //const PHPpath = "D:/test/PlayGameCtrl.ts"; //phppath = "D:/test/HomeTop.ts"; console.log("执行文件:" + phppath); let phpcontent = fs.readFileSync(phppath, { encoding: "UTF8" }); //console.log(phpContent); //====修改私有方法名================================================================== //let arr = phpContent.match(/function\s*(\w+)/); let arr = phpContent.match(/private .*?\(/g); console.log(arr); if(arr){ let len = "private ".length; for(var i=0; i<arr.length; i++){ let str = arr[i]; if(str.indexOf("private static") != -1) continue; if(str.indexOf("private async") != -1) continue; if(str.indexOf("private get") != -1) continue; if(str.indexOf("private set") != -1) continue; if(str.indexOf("= new ") != -1) continue; //过滤这种:private con:eui.Rect = new eui.Rect(); let name = str.substr(len, str.length - len - 1); console.log(name); let number = Math.floor(Math.random() * 9999); let name2; let lastIndex = name.lastIndexOf("_"); if(lastIndex == -1){ name2 = name + "_" + number; }else{ name2 = name.substr(0, lastIndex) + "_" + number; } let name2s = name2 + "("; //phpContent = phpContent.replace("private "+name + "(", "private " + name2s); //不能直接替换需要用正则,因为一个文件里面可能有多个类,可能存在多个同名方法 var regExp0 = new RegExp("private "+name + "\\(", 'gi'); phpContent = phpContent.replace(regExp0, "private " + name2s); var regExp = new RegExp("this."+name + "\\(", 'gi'); phpContent = phpContent.replace(regExp, "this." + name2s); regExp = new RegExp("this."+name + "\\," , 'gi'); phpContent = phpContent.replace(regExp, "this." + name2 + ","); regExp = new RegExp("this."+name + "\\)" , 'gi'); phpContent = phpContent.replace(regExp, "this." + name2 + ")"); var regExp2 = new RegExp("self."+name + "\\(", 'gi'); phpContent = phpContent.replace(regExp2, "self." + name2s); } } //====增加干扰代码================================================================== let arr2 = phpContent.split("\n"); //console.log(arr2.length, arr2); let isInterFace; for(var i=LINENUM; i<arr2.length - 1; i++){ if(!arr2[i]) continue; let formatStr = trim(arr2[i], "g"); if(formatStr == "" || formatStr == "{") continue; arr2[i] = String(arr2[i]); if(arr2[i].indexOf("return") !=-1) continue; if(arr2[i].indexOf("class ") !=-1) continue; if(arr2[i].indexOf("super(") !=-1) continue; if(arr2[i].indexOf("public constructor") !=-1) continue; if(arr2[i].indexOf("else") !=-1) continue; if(arr2[i].indexOf("else if") !=-1) continue; if(arr2[i].indexOf("//") !=-1) continue; if(arr2[i].indexOf("catch(") !=-1) continue; if(arr2[i].indexOf(",") !=-1 && arr2[i].indexOf("(") ==-1) continue;//object里面的key value if(arr2[i].indexOf("public ") !=-1 && arr2[i].indexOf("{") ==-1) continue;//属性 if(arr2[i].indexOf("private ") !=-1 && arr2[i].indexOf("{") ==-1) continue;//属性 if(arr2[i].indexOf("protected ") !=-1 && arr2[i].indexOf("{") ==-1) continue;//属性 if(arr2[i].indexOf("public ") !=-1 && arr2[i].indexOf("{") !=-1 && arr2[i].indexOf("=") !=-1) continue;//属性 if(arr2[i].indexOf("private ") !=-1 && arr2[i].indexOf("{") !=-1 && arr2[i].indexOf("=") !=-1) continue;//属性 if(arr2[i].indexOf("function") !=-1 && arr2[i].indexOf(":") !=-1) continue; //object里面的key value if(!arr2[i-1]) continue; let formatStr2 = Trim(arr2[i-1], "g"); arr2[i-1] = String(arr2[i-1]); if(arr2[i-1].indexOf("return") !=-1) continue; if(arr2[i-1].indexOf("if") !=-1 && arr2[i-1].indexOf("{") ==-1) continue; if(arr2[i-1].indexOf("else") !=-1 && arr2[i-1].indexOf("{") ==-1) continue; if(arr2[i-1].indexOf("for") !=-1 && arr2[i-1].indexOf("{") ==-1) continue; if(arr2[i-1].indexOf(",") !=-1 && arr2[i-1].indexOf("(") ==-1) continue;//object里面的key value if(formatStr == "})" && formatStr2 == "}") continue; if(formatStr == "})" && formatStr2 == "") continue; let isFun = (arr2[i].indexOf("private ") !=-1 && arr2[i].indexOf("(") !=-1) || (arr2[i].indexOf("public ") !=-1 && arr2[i].indexOf("(") !=-1) || (arr2[i].indexOf("/**") !=-1); if(isFun && (formatStr2 == ""|| formatStr2 == "*/" || arr2[i-1].indexOf("}") != -1 || arr2[i-1].indexOf("//") != -1 || arr2[i-1].indexOf("class") != -1) ) continue; if(isFun && arr2[i-1].indexOf("private ") !=-1 ) continue; if(isFun && arr2[i-1].indexOf("public ") !=-1 ) continue; if(arr2[i].indexOf("function") !=-1 && arr2[i-1].indexOf(",") !=-1) continue; //object里面的key value if(arr2[i].indexOf("}") !=-1 && arr2[i-1].indexOf(":") !=-1) continue; let formatStr3 = Trim(arr2[i+1], "g"); arr2[i+1] = String(arr2[i+1]); if(formatStr == "}" && formatStr3 == "}") continue; if(formatStr == "}" && formatStr3 == "") continue; let str = getAddSpace(arr2[i]) + traceName + "(\"" + getRadomStr() + "\");"; arr2.splice(i,0,str); let randomNum = Math.random(); i = i + LINENUM + ( Math.round(randomNum * LINENUM_RADOM) * (randomNum<0.5 ? 1:-1) );//随机行数添加 } //====增加干扰代码 增加干扰方法 ================================================================== let pbArr = phpContent.match(/public .*?\(/g); let count = (arr ? arr.length : 0) + (pbArr ? pbArr.length : 0); let rate = 0.5; if(count > 5) rate = 0.4; if(count > 10) rate = 0.25; if(count > 20) rate = 0.15; rate *= 0.75 for(var i=LINENUM; i<arr2.length - 1; i++){ let formatStr = Trim(arr2[i], "g"); arr2[i] = String(arr2[i]); let formatStr2 = Trim(arr2[i-1], "g"); arr2[i-1] = String(arr2[i-1]); let formatStr3 = Trim(arr2[i+1], "g"); arr2[i+1] = String(arr2[i+1]); let canInsert = false; let isFun = ( (arr2[i].indexOf("private ") !=-1 || arr2[i].indexOf("public ") !=-1) && arr2[i].indexOf("(") !=-1) && arr2[i].indexOf("=") ==-1; var str = ''; if(isFun && formatStr2 == "}"){ canInsert = Math.random() < rate; str = getAddSpace(arr2[i]) + 'private ' + getRadomStr(4, 1) + funString + getRadomStr() + '"); }' + '\n'; } else{ let str3 = arr2[i+1]; isFun = ( (str3.indexOf("private ") !=-1 || str3.indexOf("public ") !=-1 || str3.indexOf("/**") !=-1) && str3.indexOf("(") !=-1) && str3.indexOf("=") ==-1; if(formatStr == "" && isFun && formatStr2 == "}") { canInsert = Math.random() < rate; str = getAddSpace(arr2[i-1]) + 'private ' + getRadomStr(4, 1) + funString + getRadomStr() + '"); }'; } } if(canInsert){ arr2.splice(i,0,str); } } phpContent = arr2.join("\n"); //console.log(phpContent); fs.writeFileSync(phppath, phpContent, { encoding: "utf8" }); console.log("执行完成!--" + phppath); } function getAddSpace(str){ if(!str) return ""; let num = str.length - lTrim(str).length; let formatStr = Trim(str, "g"); if(formatStr == "}") num += 4; let space = ""; while(num > 0){ space += " "; num--; } return space; } function getRadomStr(len, type){ len = len || Math.floor(Math.random()*32) + 1; var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/ if(type == 1) $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; //非数字 var maxPos = $chars.length; var pwd = ''; for (var i = 0; i < len; i++) { pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); } return pwd; } function Trim(str, is_global) { //console.log("==", str) str = String(str); var result; result = str.replace(/(^\s+)|(\s+$)/g,""); if(is_global && is_global.toLowerCase()=="g"){ result = result.replace(/\s/g,""); } return result; } function lTrim(str) { str = String(str); var result = str.replace(/(^\s+)/g,""); return result; }
备注:以上命令用nodejs执行。由于只是辅助工具,并没有写的特别完善,有时会在一些错误的行数插入代码,造成代码格式不对,运行报错,这个需要手动删除该废代码。欢迎有兴趣的读者继续完善!
特别说明:本文只是做技术学习,并不是教开发者怎么绕开微信审查进行侵权行为,作为程序员更要尊重自己和别人的劳
添加新评论