Home >Web Front-end >JS Tutorial >Javascript reverse and anti-reverse
popunderjs Originally there was open source code on github, but later it was estimated that the author discovered the huge commercial value of this demand, so he simply stopped open source and charged directly. So now if we want to study its implementation plan, we can only go to the official website to pick up its source code.
script.js is the main function, which implements all functions of popunder and defines multiple API methods
license.demo.js is the authorization file. Only with this file can you successfully call the methods in script.js
For such a commercially valuable code to be openly used by you, you must consider the issue of being reversed. Let's see how it works against reverse engineering.
First, open the console and find 2 problems:
All the contents of the console have been cleared repeatedly, Only this sentence is output: Console was cleared script.js?0.5309098417125133:1
##Cannot breakpoint debugging, because once the breakpoint debugging function is enabled, it will Being directed to an anonymous function (function() {debugger})
In other words, the commonly used breakpoint debugging method can no longer be used, we can only Take a look at the source code and see if you can understand its logic. However, its source code is like this:
<span style="font-size: 16px;">var a = typeof window === S[0] && typeof window[S[1]] !== S[2] ? window : global;<br> try {<br> a[S[3]](S[4]);<br> return function() {}<br> ;<br> } catch (a) {<br> try {<br> (function() {}<br> [S[11]](S[12])());<br> return function() {}<br> ;<br> } catch (a) {<br> if (/TypeError/[S[15]](a + S[16])) {<br> return function() {}<br> ;<br> }<br> }<br> }<br></span>
It can be seen that the source code is impossible to read, so we still have to find a way to break its anti-reverse measures.
First, step by step in the breakpoint debugging mode, check what operations it has performed, and suddenly found out Such a piece of code:
<span style="font-size: 16px;">(function() {<br> (function a() {<br> try {<br> (function b(i) {<br> if (('' + (i / i)).length !== 1 || i % 20 === 0) {<br> (function() {}<br> ).constructor('debugger')();<br> } else {<br> debugger ;<br> }<br> b(++i);<br> }<br> )(0);<br> } catch (e) {<br> setTimeout(a, 5000);<br> }<br> }<br> )()<br>}<br>)();<br></span>
This code mainly has two parts. One is to judge whether the console is opened through the b() function in the try {} block. If so, it will call itself, repeatedly. Enter the debugger breakpoint to interfere with our debugging. If the console is not opened, calling the debugger will throw an exception. At this time, set the timer in the catch {} block and call the b() function after 5 seconds.
In this way, everything actually starts from the setTimeout function (because the b() function is all a closure call and cannot be broken from the outside world), so as long as it is called in setTimeout , this infinite loop can be broken by not letting it execute.
So we just need to simply override setTimeout... For example:
<span style="font-size: 16px;">window._setTimeout = window.setTimeout;<br>window.setTimeout = function () {};<br></span>
but! This operation cannot be done in the console! Because when you open the console, you will inevitably be sucked into the infinite loop of the b() function. There is no point in overriding setTimeout at this time.
At this time, our tool TamperMonkey comes into play. By writing the code into the TM script, it can be executed even without opening the console.
TM After the script is written, refresh the page, wait until it is completely loaded, and then open the console. At this time, the debugger will no longer appear!
Then it’s the console’s turn to refresh the code
Click on the link on the right side of Console was cleared to locate the specific code. Click {} to beautify the compressed code and find that it actually uses setInterval to repeatedly call console.clear() to clear the console and output the message
Console was cleared
, but note that setInterval cannot be directly overridden. Because this function also has important uses elsewhere.So we can prevent its screen clearing behavior by overriding the console.clear() function and filtering the log information.
is also written into the TamperMonkey script, code:
<span style="font-size: 16px;">window.console.clear = function() {};<br>window.console._log = window.console.log;<br>window.console.log = function (e) {<br> if (e['nodeName'] && e['nodeName'] == 'p') {<br> return ;<br> }<br> return window.console.error.apply(window.console._log, arguments);<br>};<br></span>
The reason why error is used to output information is to view its call stack, which is helpful for understanding the program logic.
Basically, after completing these tasks, this code can be debugged normally like an ordinary program. But there is another problem. Its main code is often obfuscated and encrypted, so it is very difficult to debug. Let’s briefly talk about the process.
You can see a piece of code at the beginning from license.demo.js Is such that:
<span style="font-size: 16px;">var zBCa = function T(f) {<br> for (var U = 0, V = 0, W, X, Y = (X = decodeURI("+TR4W%17%7F@%17.....省略若干"),<br> W = '',<br> 'D68Q4cYfvoqAveD2D8Kb0jTsQCf2uvgs'); U < X.length; U++,<br/> V++) {<br/> if (V === Y.length) {<br/> V = 0;<br/> }<br/> W += String["fromCharCode"](X["charCodeAt"](U) ^ Y["charCodeAt"](V));<br/> }<br/> var S = W.split("&&");<br/></span>
通过跟踪执行,可以发现 S 变量的内容其实是本程序所有要用到的类名、函数名的集合,类似于 var S = ['console', 'clear', 'console', 'log'] 。如果要调用 console.clear() 和 console.log() 函数的话,就这样
<span style="font-size: 16px;">var a = window;<br/>a[S[0]][S[1]]();<br/>a[S[2]][S[3]]();<br/></span>
license.demo.js 中有多处这样的代码:
<span style="font-size: 16px;">a['RegExp']('/R[\S]{4}p.c\wn[\D]{5}t\wr/','g')['test'](T + '')<br/></span>
这里的 a 代表 window,T 代表某个函数, T + '' 的作用是把 T 函数的定义转成字符串,所以这段代码的意思其实是,验证 T 函数的定义中是否包含某些字符。
每次成功的验证,都会返回一个特定的值,这些个特定的值就是解密核心证书的参数。
可能是因为我重新整理了代码格式,所以在重新运行的时候,这个证书一直运行不成功,所以后来就放弃了通过证书来突破的方案。
通过断点调试,我们可以发现,想一步一步深入地搞清楚这整个程序的逻辑,是十分困难,因为它大部分函数之间都是相互调用的关系,只是参数的不同,结果就不同。
所以我后来想了个办法,就是只查看它的系统函数的调用,通过对调用顺序的研究,也可以大致知道它执行了哪些操作。
要想输出所有系统函数的调用,需要解决以下问题:
覆盖所有内置变量及类的函数,我们既要覆盖 window.console.clear() 这样的依附在实例上的函数,也要覆盖依附在类定义上的函数,如 window.HTMLAnchorElement.__proto__.click()
需要正确区分内置函数和自定义函数
经过搜索后,找到了区分内置函数的代码:
<span style="font-size: 16px;">// Used to resolve the internal `[[Class]]` of values<br/> var toString = Object.prototype.toString;<br/><br/> // Used to resolve the decompiled source of functions<br/> var fnToString = Function.prototype.toString;<br/><br/> // Used to detect host constructors (Safari > 4; really typed array specific)<br> var reHostCtor = /^\[object .+?Constructor\]$/;<br><br> // Compile a regexp using a common native method as a template.<br> // We chose `Object#toString` because there's a good chance it is not being mucked with.<br> var reNative = RegExp('^' +<br> // Coerce `Object#toString` to a string<br> String(toString)<br> // Escape any special regexp characters<br> .replace(/[.*+?^${}()|[\]\/\\]/g, '\\$&')<br> // Replace mentions of `toString` with `.*?` to keep the template generic.<br> // Replace thing like `for ...` to support environments like Rhino which add extra info<br> // such as method arity.<br> .replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'<br> );<br><br> function isNative(value) {<br> var type = typeof value;<br> return type == 'function'<br> // Use `Function#toString` to bypass the value's own `toString` method<br> // and avoid being faked out.<br> ? reNative.test(fnToString.call(value))<br> // Fallback to a host object check because some environments will represent<br> // things like typed arrays as DOM methods which may not conform to the<br> // normal native pattern.<br> : (value && type == 'object' && reHostCtor.test(toString.call(value))) || false;<br> }<br></span>
然后结合网上的资料,写出了递归覆盖内置函数的代码:
<span style="font-size: 16px;">function wrapit(e) {<br> if (e.__proto__) {<br> wrapit(e.__proto__);<br> }<br> for (var a in e) {<br> try {<br> e[a];<br> } catch (e) {<br> // pass<br> continue;<br> }<br> var prop = e[a];<br> if (!prop || prop._w) continue;<br><br> prop = e[a];<br> if (typeof prop == 'function' && isNative(prop)) {<br> e[a] = (function (name, func) {<br> return function () {<br> var args = [].splice.call(arguments,0); // convert arguments to array<br> if (false && name == 'getElementsByTagName' && args[0] == 'iframe') {<br> } else {<br> console.error((new Date).toISOString(), [this], name, args);<br> }<br> if (name == 'querySelectorAll') {<br> //alert('querySelectorAll');<br> }<br> return func.apply(this, args);<br> };<br> })(a, prop);<br> e[a]._w = true;<br> };<br> }<br>}<br></span>
使用的时候只需要:
<span style="font-size: 16px;">wrapit(window);<br>wrapit(document);<br></span>
然后模拟一下正常的操作,触发 PopUnder 就可以看到它的调用过程了。
参考资料:
A Beginners’ Guide to Obfuscation Detect if function is native to browser Detect if a Function is Native Code with JavaScript
以上内容就是Javascript逆向与反逆向的教程,希望能帮助到大家。
相关推荐:
JavaScript中undefined与null的区别详解
The above is the detailed content of Javascript reverse and anti-reverse. For more information, please follow other related articles on the PHP Chinese website!