一個js破解教程

很好的一篇文章,先存着之後用到。php

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

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

 

 

 

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

你甚至能夠將本文章做爲學習 Chrome DevTools 的教學文章。es6

樣本

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

本次樣本:CKFinder 3.4.2(PHP版)算法

破解目的

下載頁面chrome

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.npm

全部下載都是完整版,只有少數功能被鎖定。使用您的許可證密鑰解鎖完整的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 斷點)

  • 斷下以後下內存斷點

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

  • 單步運行

  • 沿着調用棧向上找

  • 類比推理

相關文章
相關標籤/搜索