移动时代的前端加密
标签: 加密 前端 HTML5 移动
背景
相比其它被编译成二进制的应用。前端这样的纯文本应用,太easy被解读和窜改。
前端为什么要加密?
加密重要的目的是出于对商业利益的保护。
- 因为作品太easy被复制窜改。easy会失去渠道先机
窜改不限于下面:
- 署名被移除或替换;
- 链接地址被替换;
- 文案被改动;
- 广告被移除、替换或植入;
…
一些轻度游戏,用户仅仅会玩一两次,生命周期也就两三天。假设你开发的游戏被人山寨且他的渠道比你更广,那么对于流量就是致命打击。
- HTML5 被山寨后太便宜
在淘宝上搜索「HTML5 微信小游戏」400套/10元
- 避免泄露一些用于运营的脚本
前端加密的目标
总之就是减少加密的成本添加破解的成本:假设每次花 1 分钟加密的应用,都须要花 2 小时以上去破解那就算成功了。
- 加密后的文件不易过大;
100K 文件假设加密后到 1M 无疑添加了用户使用的成本和体验。
- 没有人工介入不能破解;
即:破解的过程须要人工介入,人工成本无疑是最大的开销。
- 限制在其它域名部署;
守护代码和业务放在一起。部署到其它域名则不能正常使用。
- 不easy被调试跟踪;
对主流的调试工具有防范能力,如:Firebug、Chrome 开发人员工具。
哪些代码不须要加密?
- 开源项目
- 用于学习的项目
减少可读性的方法
压缩(compression)
压缩的目的一般是减少传输量,但也取到减少可读性的作用。
去掉凝视、多余的分隔符、空白字符、标识符简写。
这类工具有非常多:YUI Compressor、UglifyJS、Google Closure Compiler
「标识符简写」是一种压缩也是一种混淆。
混淆(obfuscation)
混淆常见的方法是分离静态资源、打乱控制流、添加无义的代码。
UglifyJS 和 Google Closure Compiler 这类工具实际上也会做简单改变语句。
混淆是减少可读性的利器。有一款商业产品 jscrambler,最高配每一个月 95 美刀。
- 标识符混淆
混淆前
function render(obj) {
/* ... */
console.log(obj.title);
}
render({title: ‘buy‘});
混淆后
function a(e){/* ... */console.log(e.title)}a({title:‘buy‘})
- 逻辑混淆
混淆前
function render(obj) {
/* ... */
console.log(obj.title);
}
render({title: ‘buy‘});
混淆后
var self=this,o={};o.__defineSetter__(‘t‘,function(e){self[t(‘elosnoc‘)][t(‘gol‘)](e[t(‘eltit‘)])});function t(e){return e.split(‘‘).reverse().join(‘‘)};o[t(‘eltit‘)]=t(‘yub‘);o.t=o
混淆前
alert("Hello, JavaScript")
混淆后
?ω??= /`m′)? ~┻━┻ //*′?`*/ [‘_‘]; o=(???) =_=3; c=(?Θ?) =(???)-(???); (?Д?) =(?Θ?)= (o^_^o)/ (o^_^o);(?Д?)={?Θ?: ‘_‘ ,?ω?? : ((?ω??==3) +‘_‘) [?Θ?] ,???? :(?ω??+ ‘_‘)[o^_^o -(?Θ?)] ,?Д??:((???==3) +‘_‘)[???] }; (?Д?) [?Θ?] =((?ω??==3) +‘_‘) [c^_^o];(?Д?) [‘c‘] = ((?Д?)+‘_‘) [ (???)+(???)-(?Θ?) ];(?Д?) [‘o‘] = ((?Д?)+‘_‘) [?Θ?];(?o?)=(?Д?) [‘c‘]+(?Д?) [‘o‘]+(?ω?? +‘_‘)[?Θ?]+ ((?ω??==3) +‘_‘) [???] + ((?Д?) +‘_‘) [(???)+(???)]+ ((???==3) +‘_‘) [?Θ?]+((???==3) +‘_‘) [(???) - (?Θ?)]+(?Д?) [‘c‘]+((?Д?)+‘_‘) [(???)+(???)]+ (?Д?) [‘o‘]+((???==3) +‘_‘) [?Θ?];(?Д?) [‘_‘] =(o^_^o) [?o?] [?o?];(?ε?)=((???==3) +‘_‘) [?Θ?]+ (?Д?) .?Д??+((?Д?)+‘_‘) [(???) + (???)]+((???==3) +‘_‘) [o^_^o -?Θ?]+((???==3) +‘_‘) [?Θ?]+ (?ω?? +‘_‘) [?Θ?]; (???)+=(?Θ?); (?Д?)[?ε?]=‘\\‘; (?Д?).?Θ??=(?Д?+ ???)[o^_^o -(?Θ?)];(o???o)=(?ω?? +‘_‘)[c^_^o];(?Д?) [?o?]=‘\"‘;(?Д?) [‘_‘] ( (?Д?) [‘_‘] (?ε?+(?Д?)[?o?]+ (?Д?)[?ε?]+(?Θ?)+ (???)+ (?Θ?)+ (?Д?)[?ε?]+(?Θ?)+ ((???) + (?Θ?))+ (???)+ (?Д?)[?ε?]+(?Θ?)+ (???)+ ((???) + (?Θ?))+ (?Д?)[?ε?]+(?Θ?)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (?Θ?))+ (?Д?)[?ε?]+(?Θ?)+ ((o^_^o) +(o^_^o))+ (???)+ (?Д?)[?ε?]+((???) + (?Θ?))+ (c^_^o)+ (?Д?)[?ε?]+(???)+ ((o^_^o) - (?Θ?))+ (?Д?)[?ε?]+(?Θ?)+ (?Θ?)+ (c^_^o)+ (?Д?)[?ε?]+(?Θ?)+ (???)+ ((???) + (?Θ?))+ (?Д?)[?ε?]+(?Θ?)+ ((???) + (?Θ?))+ (???)+ (?Д?)[?ε?]+(?Θ?)+ ((???) + (?Θ?))+ (???)+ (?Д?)[?ε?]+(?Θ?)+ ((???) + (?Θ?))+ ((???) + (o^_^o))+ (?Д?)[?ε?]+((???) + (?Θ?))+ (???)+ (?Д?)[?ε?]+(???)+ (c^_^o)+ (?Д?)[?ε?]+(?Θ?)+ (?Θ?)+ ((o^_^o) - (?Θ?))+ (?Д?)[?ε?]+(?Θ?)+ (???)+ (?Θ?)+ (?Д?)[?ε?]+(?Θ?)+ ((o^_^o) +(o^_^o))+ ((o^_^o) +(o^_^o))+ (?Д?)[?ε?]+(?Θ?)+ (???)+ (?Θ?)+ (?Д?)[?ε?]+(?Θ?)+ ((o^_^o) - (?Θ?))+ (o^_^o)+ (?Д?)[?ε?]+(?Θ?)+ (???)+ (o^_^o)+ (?Д?)[?ε?]+(?Θ?)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (?Θ?))+ (?Д?)[?ε?]+(?Θ?)+ ((???) + (?Θ?))+ (?Θ?)+ (?Д?)[?ε?]+(?Θ?)+ ((o^_^o) +(o^_^o))+ (c^_^o)+ (?Д?)[?ε?]+(?Θ?)+ ((o^_^o) +(o^_^o))+ (???)+ (?Д?)[?ε?]+(???)+ ((o^_^o) - (?Θ?))+ (?Д?)[?ε?]+((???) + (?Θ?))+ (?Θ?)+ (?Д?)[?o?]) (?Θ?)) (‘_‘);
混淆前
alert("Hello, JavaScript")
混淆后
$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"\\"+$.__$+$.$$_+$._$_+$.__+"(\\\"\\"+$.__$+$.__$+$.___+$.$$$_+(![]+"")[$._$_]+(![]+"")[$._$_]+$._$+",\\"+$.$__+$.___+"\\"+$.__$+$.__$+$._$_+$.$_$_+"\\"+$.__$+$.$$_+$.$$_+$.$_$_+"\\"+$.__$+$._$_+$._$$+$.$$__+"\\"+$.__$+$.$$_+$._$_+"\\"+$.__$+$.$_$+$.__$+"\\"+$.__$+$.$$_+$.___+$.__+"\\\"\\"+$.$__+$.___+")"+"\"")())();
加密(encryption)
这里「加密」指代码内容可逆编码。而文中「前端加密」指页面和相关资源文件处理后能正常运行。
- 简单 base64
加密前
function a(e){/* ... */console.log(e.title)}a({title:‘buy‘})
加密后
eval(atob("ZnVuY3Rpb24gYShlKXsvKiAuLi4gKi9jb25zb2xlLmxvZyhlLnRpdGxlKX1hKHt0aXRsZTonYnV5J30p"));
- Packer
加密前
function a(e){/* ... */console.log(e.title)}a({title:‘buy‘})
加密后
eval(function(p,a,c,k,e,r){e=String;if(!‘‘.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return‘\\w+‘};c=1};while(c--)if(k[c])p=p.replace(new RegExp(‘\\b‘+e(c)+‘\\b‘,‘g‘),k[c]);return p}(‘3 0(1){4.5(1.2)}0({2:\‘6\‘})‘,7,7,‘a|e|title|function|console|log|buy‘.split(‘|‘),0,{}))
新技术带来的新思路
到移动时代我们能够放心的用上 HTML5、CSS3,使用一些新特性结合已有的方案,能够更大程度添加破解的成本。
代码能够放置其它位置
将代码放到非 JS 文件里,添加代码定位的难度。
- 放到 png 中
利用 HTML Canvas 2D Context 获取二进制数据的特性。能够用图片来存储脚本资源。
- 放到 css 文件里
利用 content
样式能存放字符串的特性,相同能够用来存储脚本资源。
运行代码字符串
不管代码放到哪。都须要运行。运行代码字符串的方式有例如以下几种:
- 创建
<script>
运行
var script = document.createElement(‘script‘);
script.src = ‘data:application/javascript,console.log("Hello%20world!"))‘;
document.querySelector(‘script‘).parentNode.appendChild(script);
- 调用
setTimeout()
/setInterval()
运行
setTimeout(‘console.log("Hello world!")‘, 0);
- 创建
new Function()
运行
new Function(‘console.log("Hello world!")‘)();
- 使用
Worker
运行
var URL = window.URL || window.webkitURL;
var Blob = window.Blob || window.webkitBlob;
var blobURL = URL.createObjectURL(
new Blob([‘console.log("Hello World!")‘], {type: ‘application/javascript‘})
);
new Worker(blobURL);
URL.revokeObjectURL(blobURL);
- 使用 DOM 事件运行
var div = document.createElement(‘div‘);
div.innerHTML = "<img src=! onerror=\"console.log(‘Hello world!‘)\">";
- location 赋值 javascript 协议的链接
location = ‘javascript:console.log("Hello world!");‘;
怎样防止代码运行被截获
比想象的难太多
- 截获
eval() / new Function()
的演示样例代码
eval = function() {
console.log(‘eval‘, JSON.stringify(arguments));
};
eval(‘console.log("Hello world!")‘);
Function = function() {
console.log(‘Function‘, JSON.stringify(arguments));
return function() {};
};
new Function(‘console.log("Hello world!")‘)();
- 还以为用字面量就妥妥了。o(╯□╰)o 后来发现是杯具的
(function(){}).constructor(‘console.log("Hello world!")‘)()
- 截获
constructor
的演示样例代码
Function.prototype.__defineGetter__(‘constructor‘, function () {
return function () {
console.log(‘constructor‘, JSON.stringify(arguments));
};
});
(function() {}).constructor(‘console.log("Hello world!")‘);
- 眼下能想到的是推断
eval
是否被重定向
演示样例,假设 eval
被重定向 z 变量不会被泄露
(function(x){
var z = ‘console.log("Hello world!")‘;
eval(‘function x(){eval(z)}‘);
x();
})(function() { /* ... */ });
防止开发人员工具
再复杂的前端加密也难对付调试工具的跟踪分析。
- 那么怎样推断浏览器是否开启控制台?
这个问题由 @EtherDream 提供了眼下最完美的解决方式。
- 推断假设控制台开启则阻塞 Javascript 运行
while(1){} // 卡死
混合加密
单个方法总是easy被破解。但组合起来千变万化就不那么easy了!破解成本显然指数增长。
- 嵌套加密
「C 方法加密
「A 方法加密
...
」
...
「B 方法加密
...
」
」
- 随机加密
「随机方法加密
「随机方法加密
...
」
...
「随机方法加密
...
」
」
实际案例
这里要安利两下:
更有力的防范
非常难做到 100% 防止逆向project,仅仅是添加一些破解的成本。
使用专属素材
改动素材也就是要做同一套素材,这个事实上不比改动代码easy到哪去。
不是单一的前端应用,依赖服务端的存储
前端easy破解。后端却不easy。物理上就隔离了。
更好的产品和服务,更快的迭代
再怎么山寨也山寨不了精髓。