嘿,你有没有想过你的算法有多酷和独特? ?许多程序员和公司都这样做,这就是为什么他们可能不愿意与所有人分享他们的工作。如果将部分代码移至服务器(对于客户端-服务器应用程序),这个问题会稍微好一点,但这种方法并不总是可行。有时,我们必须将敏感的代码部分直接公开。
在本文中,我们将研究 JavaScript 中的混淆,创建隐藏算法并使学习代码变得更加困难的方法。我们还将探索 AST 是什么,并提供可用于与其交互以实现混淆的工具。
这是一个愚蠢的例子。让我们想象一下这种情况:
let w = screen.width, h = screen.height; // Let's say there's a logic with some check. console.info(w, h);
不幸的是,鲍勃无法访问赠品页面,他对此感到非常沮丧。他不明白为什么。然后他在赠品规则中了解到,不允许用户拥有大而好的显示器。
幸运的是,鲍勃在高中时参加了一些计算机科学课程。他果断按下F12打开开发者控制台,研究了一下脚本,发现主办方正在检查屏幕分辨率。然后他决定通过手机参与并成功通过测试。
一个美好结局的虚构故事 - 但如果主角看到的是这个而不是之前的代码,它就不会这么好:
l=~[];l={___:++l,$$$$:(![]+"")[l],__$:++l,$_$_:(![]+"")[l],_$_:++l,$_$$:({}+"")[l],$$_$:(l[l]+"")[l],_$$:++l,$$$_:(!""+"")[l],$__:++l,$_$:++l,$$__:({}+"")[l],$$_:++l,$$$:++l,$___:++l,$__$:++l};l.$_=(l.$_=l+"")[l.$_$]+(l._$=l.$_[l.__$])+(l.$$=(l.$+"")[l.__$])+((!l)+"")[l._$$]+(l.__=l.$_[l.$$_])+(l.$=(!""+"")[l.__$])+(l._=(!""+"")[l._$_])+l.$_[l.$_$]+l.__+l._$+l.$;l.$$=l.$+(!""+"")[l._$$]+l.__+l._+l.$+l.$$;l.$=(l.___)[l.$_][l.$_];l.$(l.$(l.$$+"\""+(![]+"")[l._$_]+l.$$$_+l.__+"\"+l.$__+l.___+"\"+l.__$+l.$$_+l.$$$+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$$_+l.$$$+"\"+l.__$+l.$_$+l.__$+l.$$_$+l.__+"\"+l.__$+l.$_$+l.___+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$_$+l.___+l.$$$_+"\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$__+l.$$$+"\"+l.__$+l.$_$+l.___+l.__+";\"+l.__$+l._$_+l.$$__+l._$+"\"+l.__$+l.$_$+l.$$_+"\"+l.__$+l.$$_+l._$$+l._$+(![]+"")[l._$_]+l.$$$_+".\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$_$+l.$$_+l.$$$$+l._$+"(\"+l.__$+l.$$_+l.$$$+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+");"+"\"")())();
我向你保证,这不是乱码,而是 JavaScript!并且它执行相同的操作。您可以尝试在此处的控制台中运行代码。
我想在这种情况下,我们的英雄就会认命,不参加有奖活动,组织者也会保留他们的计划。
那么这里有什么意义呢?恭喜 - 您已经了解了 jjencode 工具以及混淆是什么以及它可以发挥什么作用。
总之,混淆是将程序代码或数据转换为人类难以理解但仍然适用于机器或程序的形式的过程。
理论已经够多了,让我们继续看更多实际例子??。现在,让我们尝试借助您更可能在互联网上找到的混淆来转换代码。让我们看一个更有趣的代码,其中包含我们的“专有技术”操作。而且非常不希望每个不懒得达到 F12 的人都可以了解它们:
let w = screen.width, h = screen.height; // Let's say there's a logic with some check. console.info(w, h);
例如,此代码收集设备和浏览器数据并将结果输出到控制台(我们将使用输出作为代码性能的指标):
l=~[];l={___:++l,$$$$:(![]+"")[l],__$:++l,$_$_:(![]+"")[l],_$_:++l,$_$$:({}+"")[l],$$_$:(l[l]+"")[l],_$$:++l,$$$_:(!""+"")[l],$__:++l,$_$:++l,$$__:({}+"")[l],$$_:++l,$$$:++l,$___:++l,$__$:++l};l.$_=(l.$_=l+"")[l.$_$]+(l._$=l.$_[l.__$])+(l.$$=(l.$+"")[l.__$])+((!l)+"")[l._$$]+(l.__=l.$_[l.$$_])+(l.$=(!""+"")[l.__$])+(l._=(!""+"")[l._$_])+l.$_[l.$_$]+l.__+l._$+l.$;l.$$=l.$+(!""+"")[l._$$]+l.__+l._+l.$+l.$$;l.$=(l.___)[l.$_][l.$_];l.$(l.$(l.$$+"\""+(![]+"")[l._$_]+l.$$$_+l.__+"\"+l.$__+l.___+"\"+l.__$+l.$$_+l.$$$+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$$_+l.$$$+"\"+l.__$+l.$_$+l.__$+l.$$_$+l.__+"\"+l.__$+l.$_$+l.___+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$_$+l.___+l.$$$_+"\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$__+l.$$$+"\"+l.__$+l.$_$+l.___+l.__+";\"+l.__$+l._$_+l.$$__+l._$+"\"+l.__$+l.$_$+l.$$_+"\"+l.__$+l.$$_+l._$$+l._$+(![]+"")[l._$_]+l.$$$_+".\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$_$+l.$$_+l.$$$$+l._$+"(\"+l.__$+l.$$_+l.$$$+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+");"+"\"")())();
现在让我们使用流行的 JS 混淆器 - obfuscator.io 来修改上面的代码。结果,我们会得到这样的代码:
function getGpuData(){ let cnv = document.createElement("canvas"); let ctx = cnv.getContext("webgl"); const rendererInfo = ctx.getParameter(ctx.RENDERER); const vendorInfo = ctx.getParameter(ctx.VENDOR); return [rendererInfo, vendorInfo] } function getLanguages(){ return window.navigator.languages; } let data = {}; data.gpu = getGpuData(); data.langs = getLanguages(); console.log(JSON.stringify(data))
瞧!现在,只有机器会乐意解析这段代码(你和我可能不在其中?)。尽管如此,它仍然有效并产生相同的结果。注意变化:
在这种情况下,最后一种技术可能是最令人讨厌的,因为它增加了静态代码分析的负担。
好吧,看来所有的秘密都被隐藏了。我们可以将代码部署到生产中吗?
等等...如果有代码混淆服务,也许有一些可以把这些东西拉回来?绝对吗?而且不止一个!让我们尝试使用其中之一 - webcrack。看看我们是否可以获得原始的、可读的代码。以下是使用该反混淆器的结果:
{"gpu":["ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar","Mozilla"],"langs":["en-US","en"]}
哎呀?。 当然,它没有返回变量的名称,但谢谢你。
所以事实证明,在这种情况下冷静研究我们的代码的唯一障碍是研究人员使用反混淆器的意志力。毫无疑问,也可以使用其他解决方案和定制,但对于任何流行的混淆,我们很可能应该期待流行的反混淆。
我们应该绝望并不战而屈人之兵吗?当然不是!让我们看看我们还能做些什么......
混淆器 - 听起来像是来自奇幻宇宙的某种法师,不是吗? ??♂️
当然,有人可以在编写代码时混淆代码,并且是天生的魔术师。你甚至可能无意中自己已经能够施展这样的咒语一段时间了。但是,如果这些技能由于“高级程序员”的批评而消失,并且您有一个可能使您的程序难以调查的想法,那么您现在该怎么办?在这种情况下,转向与代码结构本身交互并允许您修改它的工具是有意义的。让我们来看看它们。
也可以尝试通过像与文本交互一样简单地与代码交互来修改代码,用正则表达式替换某些结构等等。但我想说,按照这种方式,你有更多的机会毁掉你的代码和时间,而不是混淆它。
为了更可靠和受控的修改,将其引入抽象结构、树(AST - 抽象语法树)是有意义的,通过它我们可以更改我们感兴趣的元素和结构.
处理 JS 代码有不同的解决方案,最终的 AST 也有所不同。在本文中,我们将使用 babel 来实现此目的。你不需要安装任何东西,你可以在astexplorer这样的资源上尝试一切。
(如果你不想搞乱 babel,请查看 shift-refactor。它允许你使用 **CSS 选择器与 AST 交互。非常简约且方便的学习方法并修改代码。但它使用特定版本的 AST,与 babel 不同,您可以在 shift-query 交互式演示中测试此工具的 CSS 查询)。
现在让我们通过一个简单的示例来看看如何在不离开浏览器的情况下轻松使用这些工具。假设我们需要将同名函数中的测试变量的名称更改为changed:
let w = screen.width, h = screen.height; // Let's say there's a logic with some check. console.info(w, h);
将此代码粘贴到astexplorer中(从上面选择JavaScript和@babel/parser),它应该在那里显示为AST。您可以单击测试变量以在右侧窗口中查看此代码部分的语法:
为了解决我们的问题,我们可以编写以下 babel 插件,它将解析我们的代码并查找其中的所有名称标识符,并在满足某些条件时重命名它们。让我们将其粘贴到 astexplorer 的左下窗口中(打开 transform 滑块并选择 babelv7 使其出现):
let w = screen.width, h = screen.height; // Let's say there's a logic with some check. console.info(w, h);
控制台输出包含在这个插件中是有原因的。这允许我们通过检查浏览器控制台中的输出来调试我们的插件。在这种情况下,我们输出有关 Identifier 类型的所有节点的信息。这些信息包含有关节点本身(node)、父节点(parent)和环境(范围-包含在当前上下文中创建的变量以及对它们的引用)的数据:
因此,在右下窗口中,我们可以注意到源代码中的变量已成功更改,而没有影响其他标识符:
l=~[];l={___:++l,$$$$:(![]+"")[l],__$:++l,$_$_:(![]+"")[l],_$_:++l,$_$$:({}+"")[l],$$_$:(l[l]+"")[l],_$$:++l,$$$_:(!""+"")[l],$__:++l,$_$:++l,$$__:({}+"")[l],$$_:++l,$$$:++l,$___:++l,$__$:++l};l.$_=(l.$_=l+"")[l.$_$]+(l._$=l.$_[l.__$])+(l.$$=(l.$+"")[l.__$])+((!l)+"")[l._$$]+(l.__=l.$_[l.$$_])+(l.$=(!""+"")[l.__$])+(l._=(!""+"")[l._$_])+l.$_[l.$_$]+l.__+l._$+l.$;l.$$=l.$+(!""+"")[l._$$]+l.__+l._+l.$+l.$$;l.$=(l.___)[l.$_][l.$_];l.$(l.$(l.$$+"\""+(![]+"")[l._$_]+l.$$$_+l.__+"\"+l.$__+l.___+"\"+l.__$+l.$$_+l.$$$+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$$_+l.$$$+"\"+l.__$+l.$_$+l.__$+l.$$_$+l.__+"\"+l.__$+l.$_$+l.___+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$_$+l.___+l.$$$_+"\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$__+l.$$$+"\"+l.__$+l.$_$+l.___+l.__+";\"+l.__$+l._$_+l.$$__+l._$+"\"+l.__$+l.$_$+l.$$_+"\"+l.__$+l.$$_+l._$$+l._$+(![]+"")[l._$_]+l.$$$_+".\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$_$+l.$$_+l.$$$$+l._$+"(\"+l.__$+l.$$_+l.$$$+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+");"+"\"")())();
我希望,基于这个例子,我们可以更清楚地了解如何解析和修改代码。无论如何,让我总结一下所做的工作:
现在很清楚如何进行代码修改了。让我们尝试一些更有用的东西,我们可以将其称为混淆:)我们将采用我们在上一节中尝试混淆的更复杂的代码。现在我们将其中所有变量和函数的名称更改为随机名称。因此,潜在的逆向工程师对某些代码元素的用途了解较少。
此外,请随意使用任何 JS 代码来调试问题。 正如他们所说,没有比痛苦更好的老师?.
以下插件将帮助我们完成工作:
function getGpuData(){ let cnv = document.createElement("canvas"); let ctx = cnv.getContext("webgl"); const rendererInfo = ctx.getParameter(ctx.RENDERER); const vendorInfo = ctx.getParameter(ctx.VENDOR); return [rendererInfo, vendorInfo] } function getLanguages(){ return window.navigator.languages; } let data = {}; data.gpu = getGpuData(); data.langs = getLanguages(); console.log(JSON.stringify(data))
这段代码有什么作用?与前面的示例几乎相同:
作为插件执行的结果,我们得到以下带有随机变量名称和函数的代码:
{"gpu":["ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar","Mozilla"],"langs":["en-US","en"]}
您可以通过在控制台中执行代码来检查它 - 经过我们的操作,它仍然有效!这就是一个好的混淆器的主要品质✨。
但是我们的混淆质量怎么样?对我来说 - 邪恶还不太强大:即使通过替换名称,经验丰富的程序员也很容易理解这段代码的目的。如果任何 JS 压缩器都可以处理这个任务,那还有什么意义呢?现在是否可以为逆向者做一些更实际、更麻烦的事情呢?还有一个咒语...
当我写“一切”时,我可能有点自信,但我们现在要做的将最大程度地隐藏我们代码的操作。在本节中,我们将隐藏字符串和各种对象属性,以使静态分析复杂化,并可能阻止“客户端”深入我们的代码!
让我们使用上一阶段获得的隐藏名称的代码,并对其应用以下插件:
let w = screen.width, h = screen.height; // Let's say there's a logic with some check. console.info(w, h);
我已经在代码注释中描述了这个插件的一些工作,但让我们一步一步简单描述一下它的作用:
值得一提的是,解析操作不是顺序执行的,而是在 AST 处理过程中找到必要的节点。
执行此插件的结果,我们将得到以下代码:
l=~[];l={___:++l,$$$$:(![]+"")[l],__$:++l,$_$_:(![]+"")[l],_$_:++l,$_$$:({}+"")[l],$$_$:(l[l]+"")[l],_$$:++l,$$$_:(!""+"")[l],$__:++l,$_$:++l,$$__:({}+"")[l],$$_:++l,$$$:++l,$___:++l,$__$:++l};l.$_=(l.$_=l+"")[l.$_$]+(l._$=l.$_[l.__$])+(l.$$=(l.$+"")[l.__$])+((!l)+"")[l._$$]+(l.__=l.$_[l.$$_])+(l.$=(!""+"")[l.__$])+(l._=(!""+"")[l._$_])+l.$_[l.$_$]+l.__+l._$+l.$;l.$$=l.$+(!""+"")[l._$$]+l.__+l._+l.$+l.$$;l.$=(l.___)[l.$_][l.$_];l.$(l.$(l.$$+"\""+(![]+"")[l._$_]+l.$$$_+l.__+"\"+l.$__+l.___+"\"+l.__$+l.$$_+l.$$$+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$$_+l.$$$+"\"+l.__$+l.$_$+l.__$+l.$$_$+l.__+"\"+l.__$+l.$_$+l.___+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$_$+l.___+l.$$$_+"\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$__+l.$$$+"\"+l.__$+l.$_$+l.___+l.__+";\"+l.__$+l._$_+l.$$__+l._$+"\"+l.__$+l.$_$+l.$$_+"\"+l.__$+l.$$_+l._$$+l._$+(![]+"")[l._$_]+l.$$$_+".\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$_$+l.$$_+l.$$$$+l._$+"(\"+l.__$+l.$$_+l.$$$+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+");"+"\"")())();
从结果代码中可以看到,所有属性都已被具有给定索引的 getData 函数调用替换。我们对字符串做了同样的事情,并开始通过函数调用来获取它们。属性名称和字符串本身使用 base64 进行编码,使它们更难以被注意到......
我想您已经注意到了 - 这个插件以及一般的代码在这个阶段存在缺陷。例如,可以纠正以下问题:
尽管有这些简单性和缺点,我认为它已经可以称为混淆。但话又说回来,我们与开源混淆器有何不同,因为它们做类似的事情?
我们必须记住最初的问题——这些混淆对于公共反混淆器来说是小菜一碟。现在,让我们使用得到的代码并在 webcrack 中对其进行反混淆! (希望它仍然无法解决我们的咒语?)。我想你可能会说实际重要性已经实现 - 我们的“受保护”代码不再可以通过公共反混淆器一键拉回
现在让我们学习一个全新的咒语。尽管公共反混淆器无法处理我们的插件,但是,在研究了混淆的实际概念后,我们可以注意到一些可用于恢复源代码的模式。
让我们开始吧,并特别利用:
鉴于这些缺点,我们可以实现以下插件:
let w = screen.width, h = screen.height; // Let's say there's a logic with some check. console.info(w, h);
我们来描述一下这个反混淆插件的功能:
结果,我们得到以下代码:
l=~[];l={___:++l,$$$$:(![]+"")[l],__$:++l,$_$_:(![]+"")[l],_$_:++l,$_$$:({}+"")[l],$$_$:(l[l]+"")[l],_$$:++l,$$$_:(!""+"")[l],$__:++l,$_$:++l,$$__:({}+"")[l],$$_:++l,$$$:++l,$___:++l,$__$:++l};l.$_=(l.$_=l+"")[l.$_$]+(l._$=l.$_[l.__$])+(l.$$=(l.$+"")[l.__$])+((!l)+"")[l._$$]+(l.__=l.$_[l.$$_])+(l.$=(!""+"")[l.__$])+(l._=(!""+"")[l._$_])+l.$_[l.$_$]+l.__+l._$+l.$;l.$$=l.$+(!""+"")[l._$$]+l.__+l._+l.$+l.$$;l.$=(l.___)[l.$_][l.$_];l.$(l.$(l.$$+"\""+(![]+"")[l._$_]+l.$$$_+l.__+"\"+l.$__+l.___+"\"+l.__$+l.$$_+l.$$$+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$$_+l.$$$+"\"+l.__$+l.$_$+l.__$+l.$$_$+l.__+"\"+l.__$+l.$_$+l.___+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+"\"+l.$__+l.___+"=\"+l.$__+l.___+"\"+l.__$+l.$$_+l._$$+l.$$__+"\"+l.__$+l.$$_+l._$_+l.$$$_+l.$$$_+"\"+l.__$+l.$_$+l.$$_+".\"+l.__$+l.$_$+l.___+l.$$$_+"\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$__+l.$$$+"\"+l.__$+l.$_$+l.___+l.__+";\"+l.__$+l._$_+l.$$__+l._$+"\"+l.__$+l.$_$+l.$$_+"\"+l.__$+l.$$_+l._$$+l._$+(![]+"")[l._$_]+l.$$$_+".\"+l.__$+l.$_$+l.__$+"\"+l.__$+l.$_$+l.$$_+l.$$$$+l._$+"(\"+l.__$+l.$$_+l.$$$+",\"+l.$__+l.___+"\"+l.__$+l.$_$+l.___+");"+"\"")())();
因此,我们能够通过使用所示的缺点为 babel 编写一个简单的插件来摆脱隐藏属性和字符串的混淆。
我希望这个小例子能够解释你如何在babel的帮助下对抗这些滋扰。使用这些方法,你还可以解决更复杂的混淆问题 - 主要是找到代码中的模式并熟练地使用 AST 进行操作。
我们了解了混淆,一种使代码逆向工程复杂化的技术,以及实现它的工具。尽管有一些公共解决方案可以混淆 JavaScript 代码,但也有同样多的公共解决方案可以立即消除这种保护。
因此,您需要编写自己的解决方案来保护公共反混淆器无法删除的代码。在 JS 中实现混淆的一种可靠方法是编写自定义 babel 插件,与所需代码的 AST 交互,将其转换为可读性较差的形式。
当然,这个领域已经有已知的混淆技术和方法,但仍然对创造力和新“技巧”持开放态度,这可能会使学习代码变得更加困难。尽管此类技术数量众多,但它们根本不能保证算法的保密性,因为代码始终“掌握在客户端手中”。此外,还有调试的可能性,这可以使研究代码变得更容易。混淆可以让您拒绝动机不良的研究人员,从而增加逆向工程的成本。
有一些高级方法,例如,混淆中的一种是代码虚拟化,或者简单地说,在JS中创建一个虚拟机来执行自定义字节码。这种方法几乎完全消除了静态分析的机会,并使调试变得尽可能困难。然而,这是一个单独的主题来讨论?....
我希望这对您获取有关此主题的信息有用,并且您不会再因为最初混淆的代码而责怪自己或您的程序员。欣赏这些奇才??♀️!我很高兴在这里与您讨论魔法的最新趋势?
以上是以下是如何在 JavaScript 中进行混淆而无需耗费大量时间:AST、Babel、插件。的详细内容。更多信息请关注PHP中文网其他相关文章!