一個js破解教程


很好的一篇文章,先存着以后用到。

為了防止官網更新修復,存一下版本

https://pan.lanzou.com/b220073/ 
密碼:這是秘密

 

 

 

這篇文章以 JavaScript 為例講解了破解的一些通用方法。

你甚至可以將本文章作為學習 Chrome DevTools 的教學文章。

樣本

官方網站:https://ckeditor.com/ckeditor-4/ckfinder/

本次樣本:CKFinder 3.4.2(PHP版)

破解目的

下載頁面

All downloads are full versions with just a few features locked. Use your license key to unlock complete CKFinder functionality, with no need to download a separate package. Try it for a limited period of time and buy it when you are ready. We prepared easy licensing options for your development purposes.

所有下載都是完整版,只有少數功能被鎖定。使用您的許可證密鑰解鎖完整的CKFinder功能,無需下載單獨的軟件包。嘗試一段有限的時間,並在准備就緒時購買。 我們為您的開發目的准備了簡單的許可選項。

上面說了,下載到的這個文件是完整版本,只是有一些功能被鎖定了。當然自己使用的話完全沒什么影響,給別人的話有些麻煩。

其實沒什么鎖定,就是上面會有一個 Demo 的字樣,每 5 分鍾會彈出 Demo 版提示框,其他功能都是全的。(破解完我才發現 Demo 版不能刪除文件)

CKFinder 許可協議(CKFinder License Agreement)

Unlicensed Copies

If You did not pay the License Fee, You may use unlicensed copies of the Software for the exclusive purpose of demonstration. In this case You will be using the Software in “demo mode”. Without derogating from the forgoing, You may not use the Software in “demo mode” for any of your business purposes. The Software in “demo mode” shall only be used for evaluation purposes and may not be used or disclosed for any other purposes, including, without limitation, for external distribution. You may not remove the demo notices, if any, from the user interface of the Software nor disable the ability to display such notices nor otherwise modify the Software. Product support, if any, is not offered for the Software in “demo mode”.

未經許可的副本

如果您沒有支付許可證費用,您可以僅以演示目的使用本軟件的未經許可副本。在這種情況下,您將以“演示模式”使用本軟件。 在不違背上述規定的前提下,您不得以任何商業目的在“演示模式”下使用本軟件。“演示模式”中的軟件僅用於評估目的,不得用於任何其他目的,包括但不限於外部分發。 您不能從軟件的用戶界面中刪除演示提示(如有),也不能禁用顯示此類通知或修改軟件的功能。產品支持(如果有的話)不以提供給“演示模式”軟件。

本教程僅供學習交流使用,請勿用於其他用途。

過程

運行軟件

在代碼的根目錄下執行

php -S 127.0.0.1:8000

開啟 php 內置服務器,然后訪問 http://127.0.0.1:8000/ckfinder.html 即可。

<ignore_js_op>

 

如何填寫許可證信息?

在 config.php 中有以下內容

$config['licenseName'] = '';$config['licenseKey']  = '';

然后我們就使用 PHPStorm 強大的全局搜索功能吧。

<ignore_js_op>

 


<ignore_js_op>

 

我們會發現,./src/CKSource/CKFinder/Command/Init.php 中有一寫相關代碼,然后就下斷點分析一下原理吧。

分析 php 部分

這里調試 php 需要 xdebug 的支持,安裝 xdebug 步驟請參照 官方文檔,這里不作具體介紹。

$ln = '';$lc = str_replace('-', '', ($config->get('licenseKey') ?: $config->get('LicenseKey')) . '                                  ');$pos = strpos(CKFinder::CHARS, $lc[2]) % 5;if ($pos == 1 || $pos == 2) {     $ln = $config->get('licenseName') ?: $config->get('LicenseName'); }$datacommandObject = $ln;$data->c = trim($lc[1] . $lc[8] . $lc[17] . $lc[22] . $lc[3] . $lc[13] . $lc[11] . $lc[20] . $lc[5] . $lc[24] . $lc[27]);// 此處省略其他代碼return $data;

似乎並沒有什么有用的信息,就是把 licenseKey 和 licenseName 轉換了一下,然后就返回給瀏覽器了。

分析 js 與 php 交互

我們發現,php 斷點停住以后,Chrome 開發者工具 Network 選項卡中有一個請求的狀態一直處於 pending 狀態。

<ignore_js_op>

 

我們直接跟蹤到 XmlHttpRequest 的調用點。

<ignore_js_op>

 

下個斷點?先格式化代碼再說吧。

代碼格式化

這一步沒什么多說的,什么工具都可以,在 Chrome 開發者工具中打開 Source 標簽,點擊左下角的 {} 按鈕,然后再復制粘貼到 ckfinder.js 中就行了。一般來說這樣 uglify 的代碼應該不會有文件校驗吧。

解碼

我們看到代碼中有很多的 S('...') 的東西,我猜是字符串解碼函數,應該是作者為了避免字符串搜索。

直接在斷點停住時在 Chrome 控制台中把那個表達式粘貼上,執行一次試試。解碼成功了,看樣子不算太麻煩。

<ignore_js_op>

 

Chrome 斷點停住時,控制台的上下文是斷點語句處的上下文,可以訪問局部變量,所以斷點處調用了 S('...') 的語句,你在控制台執行的話,S 函數也一定存在。

自動解碼

因為 JavaScript 的字符串太特殊了,使用字符串匹配的話很麻煩,我這里選擇分析 AST(抽象語法樹),針對 AST 進行替換。

首先安裝 acorn 語法分析器和 escodegen 代碼構造器,一個用來從代碼生成 AST,一個用來把 AST 轉換回代碼。

npm install acorn escodegen

下面是我寫的替換代碼,判斷了一下字符串和三元運算符

const acorn = require('acorn');const walk = require('acorn/dist/walk');const escodegen = require('escodegen');const fs = require('fs');const path = require('path');function S(e) {     for (var t = '', n = e.charCodeAt(0), i = 1; i < e.length; ++i)         t += String.fromCharCode(e.charCodeAt(i) ^ i + n & 127);     return t; }function recursiveDecode(node) {     if (node.type === 'Literal') {         node.value = S(node.value);         // console.log(node.value);    } else if (node.type === 'ConditionalExpression') {         recursiveDecode(node.consequent);         recursiveDecode(node.alternate);     } else {         console.log('Node type is neither Literal nor ConditionalExpression. ' + node.start);     } }// 這里改成你的代碼位置var inputFile = path.join(__dirname, 'ckfinder/ckfinder.min.js');var outputFile = path.join(__dirname, 'ckfinder/ckfinder.js'); fs.readFile(inputFile, {encoding: 'utf-8'}, function (err, data) {     if (err) {         console.log(err);         return;     }     var ast = acorn.parse(data);     walk.simple(ast, {         CallExpression: function (node) {             if (node.callee.type === 'Identifier' && node.callee.name === 'S' && node.arguments.length === 1) {                 var arg0 = node.arguments[0];                 recursiveDecode(arg0);                 if (arg0.type === 'Literal') {                     node.type = arg0.type;                     node.value = arg0.value;                 } else if (arg0.type === 'ConditionalExpression') {                     node.type = arg0.type;                     node.test = arg0.test;                     node.consequent = arg0.consequent;                     node.alternate = arg0.alternate;                 }             }         }     });     var code = escodegen.generate(ast);     fs.writeFile(outputFile, code, function (err) {         if (err) {             return console.log(err);         }         console.log('The file was saved!');     }); });

使用方法

node decode_ckfinder.js

分析過程

我們用 PHPStorm 打開 ckfinder.js,使用 PHPStorm 的代碼定位直接找到 S 函數。

<ignore_js_op>

 

然后我們就找到了解碼方法,這段代碼已經嵌入我的解碼代碼中了。

function S(e) {     for (var t = "", n = e.charCodeAt(0), i = 1; i < e.length; ++i)         t += String.fromCharCode(e.charCodeAt(i) ^ i + n & 127);     return t }

運行結果

var CKFinder = function () {     function __internalInit(e) {         return e = e || {}, e['demoMessage'] = 'This is a demo version of CKFinder 3', e['hello'] = 'Hello fellow cracker! We are really sad that you are trying to crack our application - we put lots of effort to create it. ' + 'Would you like to get a free CKFinder license? Feel free to submit your translation! http://docs.cksource.com/ckfinder3/#!/guide/dev_translations', e['isDemo'] = !0, e;     }// 后面省略了

哈哈,作者發現我們破解了他的軟件了。

你好,你們這些破解者!我們真的很傷心,您正試圖破解我們的應用程序——我們付出了很多努力來創建它。你想獲得免費的 CKFinder 許可證嗎?放心地提交您的翻譯!http://docs.cksource.com/ckfinder3/#!/guide/dev_translations

其實很多軟件作者挺有意思的。這可能是最簡單的暗樁吧,就是一個提醒字符串。

繼續分析 js

This is a demo version of CKFinder 3 這句話就是我們要找的。

后面還有一句 e['isDemo'] = !0,就是 e['isDemo'] = true,莫非我改成 false 就 OK 了?

在 Chrome 中下個斷點,看看什么情況。

根本就沒斷下來,看來作者跟我們開了個玩笑。不過想想也對,怎么能這么容易就讓你破解了呢。

嘗試 DOM 斷點

現在我們的線索斷了,不過我們有個笨方法。在 XHR 的調用點斷下之后,下 DOM 斷點(當 DOM 節點修改的時候會斷下),然后運行,直到插入的 node 就是那個 This is a demo version of CKFinder 3 的標題的時候,我們再繼續分析。

<ignore_js_op>

 

這個過程可能比較枯燥,就是不斷的繼續運行,繼續運行,直到那個被添加的 node 是 h2 的時候。

<ignore_js_op>

 

非常抱歉,我沒找到......

新的想法?

為什么我們不能直接搜索到 This is a demo version of CKFinder 3 呢?因為肯定是被加密了啊,那么我們直接找出所有亂碼字符串就行了。

我在 decode_ckfinder.js 中加了一行 console.log(node.value);(就是上面注釋掉的那一行) 這一行會打印所有的一次解碼之后的字符串,然后我們就排查一下吧,反正才 6246 行,不到五分鍾差不多就能看完。

還真讓我找到了。

<ignore_js_op>

 

 

<ignore_js_op>

 

<ignore_js_op>

 

直接在代碼中搜索其中一個字符串,定位到附近,下斷點,執行一次。

<ignore_js_op>

 

這個就是我們要找的了,斷點之后單步運行,把這句話運行完,然后修改一下 t['message'] 的值,看看效果。

<ignore_js_op>

 

看來可行,然后我們就逆着調用棧找,找到判斷語句。

<ignore_js_op>

 

類比推理

<ignore_js_op>

 

似乎,所有的加密都有 String.fromCharCode,我們直接搜索一下這個語句,應該就能找到所有的字符串加密,他們周圍有其他驗證的判斷語句,直接 if (false)掉。

if (false) 這種方法在匯編語言里怎么表示?

一種方法是 jnz 變成 jmp 或 nop,另一種是 jnz xxx 變成 jnz 00

內存斷點

上面這種方式好麻煩啊,我們還要猜原來作者是怎么想的。有沒有方法直接在讀取 $data->c(就是返回給 js 的那個許可證) 的時候斷下來。

這個東西不就是內存斷點嘛,只不過 Chrome 不支持(據說 Firefox 是支持的),不過 StackOverflow 上的朋友們已經給出了解決方案。

我是用 Bing 搜索 chrome var changed breakpoint 搜到的。

https://stackoverflow.com/questions/11618278/how-to-break-on-property-change-in-chrome/38646109#38646109

https://github.com/paulirish/break-on-access

<ignore_js_op>

 

在 Source Snippets New snippet 中粘貼下列代碼,然后右鍵運行。(如果沒有 Snippets 注意一下 >> 這個按鈕)

function breakOn(obj, propertyName, mode, func) {     // this is directly from https://github.com/paulmillr/es6-shim    function getPropertyDescriptor(obj, name) {         var property = Object.getOwnPropertyDescriptor(obj, name);         var proto = Object.getPrototypeOf(obj);         while (property === undefined && proto !== null) {             property = Object.getOwnPropertyDescriptor(proto, name);             proto = Object.getPrototypeOf(proto);         }         return property;     }     function verifyNotWritable() {         if (mode !== 'read')             throw "This property is not writable, so only possible mode is 'read'.";     }     var enabled = true;     var originalProperty = getPropertyDescriptor(obj, propertyName);     var newProperty = { enumerable: originalProperty.enumerable };     // write    if (originalProperty.set) {// accessor property        newProperty.set = function(val) {             if(enabled && (!func || func && func(val)))                 debugger;             originalProperty.set.call(this, val);         }     } else if (originalProperty.writable) {// value property        newProperty.set = function(val) {             if(enabled && (!func || func && func(val)))                 debugger;             originalProperty.value = val;         }     } else  {         verifyNotWritable();     }     // read    newProperty.get = function(val) {           if(enabled && mode === 'read' && (!func || func && func(val)))             debugger;         return originalProperty.get ? originalProperty.get.call(this, val) : originalProperty.value;     }     Object.defineProperty(obj, propertyName, newProperty);     return {       disable: function() {         enabled = false;       },       enable: function() {         enabled = true;       }     }; };

debugger; 這條語句可以讓調試器直接斷在這個位置處,配合數據綁定(給一個對象的屬性設置 getter 和 setter),就可以做到內存斷點了。然后在調用棧向上找一層,就是斷點觸發的位置了。

注意:斷點斷在數據修改之前。

<ignore_js_op>

 

在下方控制台執行

breakOn(o, 'c', read);

這樣,任何代碼在訪問許可證秘鑰信息的時候就會斷下,然后在調用棧往上找一層就可以了。

注冊 機

單步跟蹤一下程序的流程就會找到驗證函數,可以嘗試分析一下算法,然后寫一個注冊機,這里暫時告一段落,畢竟能爆破何必注冊。有興趣的同學可以自己嘗試做一個注冊機。

<ignore_js_op>

 

<ignore_js_op>

 

總結

我們來分析一下破解軟件的通用流程,不只是 JavaScript 破解。

這次的 JavaScript 破解

  • 我們首先找 licenseKey 這個字符串,然后追查到了 XmlHttpRequest。

  • 然后因為破解的需要,找到了 S 函數,然后解碼了字符串加密。

  • 然后找到了 This is a demo version of CKFinder 3 這個字符串,發現被作者騙了。

  • 再之后,我們使用 XHR 斷點和 DOM 斷點,找到了真正寫入這個字符串的位置,通過調用棧找到了另一個解碼函數。

  • 然后,我們分析了程序邏輯,直接將 if 判斷改成 if(false)

  • 接着,我們使用類比法找到了所有含 String.charCodeAt 的位置,把這些位置的判斷都去掉了。

  • 我們換了另一種套路,內存斷點,輕松地找到了驗證函數的位置。

通用思路

  • 字符串搜索

  • 找到通用的API入口下斷點(在 Windows 下就是跨模塊調用,Javascript 中就是 XHR 斷點或 DOM 斷點)

  • 斷下之后下內存斷點

  • 其他語言或編譯器的特性(易語言尤其明顯)

  • 單步運行

  • 沿着調用棧向上找

  • 類比推理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM