popunderjs は元々 github 上にオープン ソース コードを持っていましたが、後に作者がこの需要の巨大な商業的価値を発見したため、単純にオープン ソースを停止し、直接課金したのだと思います。そのため、実装計画を検討したい場合は、公式 Web サイトにアクセスしてソース コードを入手するしかありません。
script.jsは、ポップアンダーのすべての機能を実装し、複数のAPIメソッドを定義するメイン関数です
license.demo.jsは、このファイルでのみ許可ファイルです。スムーズに呼び出します script.js のメソッド
このような商業的価値のあるコードは非常に公開されているため、逆転の問題を考慮する必要があります。リバースエンジニアリングに対してどのように機能するかを見てみましょう。
まず、コンソールを開いて 2 つの問題を見つけます:
コンソールの内容はすべて繰り返しクリアされ、次の文のみが出力されます: Console was Clear script.js?0.5309098417125133:1
ブレークポイント デバッグ機能が有効になると、匿名関数 (function() {debugger}) にリダイレクトされるため、ブレークポイント デバッグはできません
つまり、一般的に使用されるブレークポイントデバッグ方法は利用できなくなりました。使用後は、ソース コードを見て、そのロジックを理解できるかどうかを確認することしかできません。ただし、そのソースコードは次のようになります:
<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>
ソースコードを読み取ることが不可能であることがわかりますので、そのリバース防止対策を破る方法を見つける必要があります。
まず、ブレークポイントデバッグモードでどのような操作が実行されたかを段階的に確認すると、突然次のコードが見つかりました:
<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>
このコードは主に 2 つの部分で構成されています。1 つは、try {} ブロックで b() 関数を使用して、コンソールが開いているかどうかを判断し、開いている場合は、それ自体を呼び出し、繰り返しデバッガー ブレークポイントに入り、干渉を実現します。デバッグの目的で。コンソールが開かれていない場合、デバッガーを呼び出すと例外がスローされます。このとき、catch {} ブロックにタイマーを設定し、5 秒後に b() 関数を再度呼び出します。
実際、すべては setTimeout 関数から始まります (b() 関数はすべてクロージャ呼び出しであり、外部から中断できないため)。したがって、setTimeout が呼び出されている限り、これを実行させないでください。無限のサイクルは断ち切れるかもしれない。
したがって、単純に setTimeout をオーバーライドするだけです...例:
<span style="font-size: 16px;">window._setTimeout = window.setTimeout;<br>window.setTimeout = function () {};<br></span>
でも!この操作はコンソールでは実行できません。コンソールを開くと、必然的に b() 関数の無限ループに引き込まれてしまうからです。現時点では setTimeout をオーバーライドする意味はありません。
この時点で、私たちのツール TamperMonkey が機能します。コードを TM スクリプトに書き込むと、コンソールを開かなくても実行できます。
TM スクリプトを作成したら、ページを更新し、完全にロードされるまで待ってから、コンソールを開きます。この時点では、デバッガーは表示されなくなります。
次に、コンソールがコードを更新する番です
コンソールの右側にあるリンクをクリックして特定のコードを見つけ、{} をクリックして圧縮コードを美しくすると、実際に使用していることがわかりますsetInterval console.clear() を繰り返し呼び出すと、コンソールがクリアされ、「 Console was Clear
そのため、console.clear() 関数をオーバーライドし、ログ情報をフィルタリングすることで、画面のクリア動作を防ぐことができます。
は TamperMonkey のスクリプトにも書き込まれます。コード:
<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>
エラーを情報の出力に使用する理由は、プログラムのロジックを理解するのに役立つコールスタックを表示するためです。
基本的に、これらのタスクを完了した後、このコードは通常のプログラムと同様にデバッグできます。しかし、そのメインコードは難読化され暗号化されていることが多いため、デバッグが非常に困難です。プロセスについて簡単に説明しましょう。
license.demo.js から、最初に次のようなコード部分があることがわかります:
<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的区别详解
以上がJavaScript のリバースとアンチリバースの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。