popunderjs는 원래 github에 오픈소스 코드가 있었는데 나중에 작성자가 이 수요의 엄청난 상업적 가치를 발견한 것 같아서 그냥 오픈소스를 중단하고 직접 비용을 청구했습니다. 따라서 이제 구현 계획을 연구하려면 공식 웹사이트에 가서 소스 코드를 받아야 합니다.
script.js는 popunder의 모든 기능을 구현하고 여러 API 메소드를 정의하는 주요 기능입니다.
license.demo.js는 이 파일로만 가능합니다. 원활하게 호출 script.js의 메소드
이런 상업적 가치가 있는 코드는 공개적으로 공개되므로 역전되는 문제를 고려해야 합니다. 리버스 엔지니어링에 대해 어떻게 작동하는지 살펴보겠습니다.
우선, 콘솔을 열고 2가지 문제를 찾으세요:
콘솔의 모든 내용이 반복적으로 지워졌고 다음 문장만 출력되었습니다: Console wascleared 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>
이 코드는 주로 두 부분으로 구성됩니다. 하나는 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 wascleared
따라서 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的区别详解
위 내용은 자바스크립트 역방향 및 역방향 방지의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!