JS逆向學習筆記 - 持續更新中

2020-11-13 06:00:02

JS逆向學習筆記

尋找深圳爬蟲工作,微信:cjh-18888

文章目錄

一. JS Hook

1. JS HOOK 原理和作用

原理:替換原來的方法. (好像寫了句廢話)

function test(aa,bb){
    cc = aa + bb;
    return cc;
}

Hook程式碼:

var _test = test;  // 拿到test
test = function(x,y){
    console.log(x,y); //輸出拿到的引數
    var retval = _test(x,y); // retval 是原來的計算結果
    console.log( retval)
    return retval + 1 // 修改返回結果
}

此時重新呼叫test, 結果比正常值多了1

作用: 可以去Hook一些內建的函數, 例如Debugger, setInterval,JSON.stringify等等

//Hook setInterval 
var _setInterval = setInterval;
setInterval = function(a,b){
    console.log(a + '',b)
    return 'setInterval is Kill'
}

//Hook JSON.stringify
stringify = JSON.stringify;
JSON.stringify = function(a){
console.log('Hook JSON.stringify ->' + stringify(a))
return stringify(a)
}
 

2.JSHook 檢測與過檢測

原理: 其實就是檢測程式碼是否和原來的相等.

案例程式碼:

function test(aa,bb){var cc = aa + bb;return cc;}

function checkTest(func){
    test + '' == 'function test(aa,bb){var cc = aa + bb;return cc;}'?console.log('func未被修改'):console.log('func被修改了')
}

此時我們可以把hook程式碼置入到瀏覽器

//控制檯置入的程式碼
var _test = test; 
test = function(x,y){
    console.log(x,y); 
    var retval = _test(x,y); 
    console.log( retval)
    return retval 
}

繞過手段: 修改Function的toString方法.

Function.prototype.toString=function(){
	return "function test(x,y){z=x+y;return z;}";
}

3.JS過反偵錯

反偵錯程式碼:

var fuck=["\u0068\u0065\u006e\u0076\u0061\u0074","\u005f\u006b\u0070\u006f\u0076\u0074\u0071\u005f\u0076\u006b\u0074","\u0066\u006b\u005f\u0071\u0069\u0061\u0070\u0076","\u0068\u0071\u0070\u005f\u0076\u0065\u006b\u0070\u0022\u006f\u0076\u0074\u0056\u006b\u0051\u0065\u0070\u0076\u003a\u002a\u006f\u0076\u0074\u0025\u0077\u0068\u006b\u0074\u002a\u0078\u005d\u0074\u0022\u0065\u0039\u0032\u002e\u005d\u0074\u0074\u0039\u0057\u0059\u0037\u0065\u003e\u006f\u0076\u0074\u0030\u006e\u0061\u0070\u0063\u0076\u006a\u0037\u0065\u0027\u0027\u0025\u0077\u0078\u005d\u0074\u0022\u005d\u0039\u006f\u0076\u0074\u0030\u005f\u006a\u005d\u0074\u003f\u006b\u0066\u0061\u003d\u0076\u002a\u0065\u0025\u0037\u005d\u0074\u0074\u0030\u0072\u0071\u006f\u006a\u002a\u005d\u0021\u0034\u003b\u005d\u0029\u0036\u003c\u005d\u0027\u0034\u0025\u0037\u0079\u0074\u0061\u0076\u0071\u0074\u0070\u0022\u0070\u0061\u0073\u0022\u0051\u0065\u0070\u0076\u003a\u003d\u0074\u0074\u005d\u0075\u002a\u005d\u0074\u0074\u0025\u0037\u0079","\u0076\u006b\u004f\u0076\u0074\u0065\u0070\u0063","\u0074\u0061\u0076\u0071\u0074\u0070\u0022\u0076\u006a\u0065\u006f","\u0068\u0071\u005f\u0067\u0055\u006b\u0071","\u0070\u0067\u005b\u0059\u0078\u0061\u0067\u0072","\u006c\u0076\u005d\u006a","\u0066\u0061\u0059\u0072\u005b\u006c\u005d\u0072\u005f\u0065\u0059\u0067","\u0066\u0066\u0066","\u005b\u0067\u0072\u006b\u0067\u0070\u005d\u0032\u0070\u0067\u005f\u002c\u0026\u005d\u0076\u0076\u0026\u0021","\u006a\u0059\u0068\u0069\u005b\u005b\u0059\u0078\u002f","\u006c\u0069\u0074\u0057\u007a\u005d\u0063\u0074\u0026\u0067\u0059\u007a\u003d\u0074\u007a\u0059\u0078\u007c\u0055\u0072\u002e\u001d\u0026\u006f\u0026\u004f\u0074\u0055\u007a\u005d\u007c\u0059\u0026\u0057\u0063\u006a\u0059\u0051\u0026\u0071","\u006f\u0061\u0076\u0045\u0070\u0076\u0061\u0074\u0078\u005d\u006e\u002a\u0068\u0071\u005f\u0067\u002d\u0057\u0027\u0057\u0059\u0059\u0057\u006f\u0056\u006b\u004f\u002a\u0068\u0071\u005f\u0067\u0057\u0038\u0059\u0025\u0059\u002a\u0068\u0071\u005f\u0067\u002d\u0057\u0027\u0057\u0059\u0059\u0057\u006f\u0056\u006b\u004f\u002a\u0068\u0071\u005f\u0067\u0057\u0038\u0059\u0025\u0059\u002a\u0068\u0071\u005f\u0067\u002d\u0057\u0027\u0057\u0059\u0059\u0057\u006f\u0056\u006b\u004f\u002a\u0068\u0071\u005f\u0067\u0057\u0038\u0059\u0025\u0059\u002a\u0068\u0071\u005f\u0067\u0057\u002d\u0034\u0059\u0025\u0025\u0025\u002e\u002d\u0032\u0032\u0032\u0025","\u006a\u006d\u005b\u0063\u0029\u0053\u0023\u0053\u0055\u0055\u0053\u006b\u0058\u0067\u004b\u002c\u006b\u0058\u0067\u004b\u002c\u006a\u006d\u005b\u0063\u0053\u0031\u0055\u0021\u0021\u0055\u0035\u0035\u002b\u0037\u005b\u0067\u0072\u006b\u0067\u0070\u005d\u0032\u0070\u0067\u005f\u002c\u0026\u0067\u006e\u0066\u0063\u003e\u002f\u0038\u002f\u002f\u002f\u003a\u0026\u0023\u006a\u006d\u005b\u0063\u0029\u0053\u0023\u0053\u0055\u0055\u0053\u006b\u0058\u0067\u004b\u002c\u006b\u0058\u0067\u004b\u002c\u006a\u006d\u005b\u0063\u0053\u0031\u0055\u0021\u0021\u0055\u0023\u0026\u002f\u0038\u0026\u0021\u003e\u005b\u0067\u0072\u006b\u0067\u0070\u005d\u0032\u0070\u0067\u005f\u002c\u0026\u005d\u0076\u0076\u0026\u0021"],bianchengmao=-1,fff=-1;var fuck1=[1,2,3,4];console.log("過了檢測就會給你正確答案哦!");function Uint8ToStr_(arr){for(var i=0,str="";i<arr.length;i++){var a=arr[i];str+=String.fromCharCode(a)}return str}function strToUint8_(str){for(var i=0,arr=[];i<str.length;i++){var a=str.charCodeAt(i);arr.push(a)}return new Uint8Array(arr)}function strToUint8(str){for(var i=0,arr=[];i<str.length;i++){var a=str.charCodeAt(i);arr.push(a%2?a-4:a+2)}return new Uint8Array(arr)}function Uint8ToStr(arr){for(var i=0,str="";i<arr.length;i++){var a=arr[i];str+=String.fromCharCode(a%2?a+4:a-2)}return str}function sToS(x){return Uint8ToStr(strToUint8_(x))}fuck1[!+[]+!+[]]=[][sToS(fuck[0])][sToS(fuck[1])];fuck1[+[]]=fuck1[!+[]+!+[]](sToS(fuck[5]))(),fuck1[+[]][sToS(fuck[6])]=sToS;fuck1[!+[]+!+[]](fuck1[+[]][sToS(fuck[6])](fuck[14]))();fuck1[+[]][fuck1[+[]]["fuckYou"](fuck1[+[]]["fuckYou"](fuck[7]))][fuck1[+[]]["fuckYou"](fuck1[+[]]["fuckYou"](fuck[8]))]==fuck1[+[]]["fuckYou"](fuck1[+[]]["fuckYou"](fuck[9]))?fuck1[+[]][(![]+[])[+[]]+(![]+[])[+[]]+(![]+[])[+[]]]=1:fuck1[+[]][[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+[]]+(![]+[])[+[]]+(![]+[])[+[]])()]=0;fuck1[+[]][(![]+[])[+[]]+(![]+[])[+[]]+(![]+[])[+[]]]==+!+[]?fuck1[+[]][sToS(sToS(fuck[9]))]=1:fuck1[!+[]+!+[]](fuck1[+[]][sToS(fuck[6])](fuck[14]))();setInterval+""==fuck1[+[]][sToS(fuck[6])](fuck1[+[]][sToS(fuck[6])](fuck1[+[]][sToS(fuck[6])](fuck[13])))?fuck1[+[]][sToS(sToS(fuck[9]))]=fuck1[+[]][sToS(sToS(fuck[9]))]+2:fuck1[!+[]+!+[]](fuck1[+[]][sToS(fuck[6])](fuck[14]))();fuck1[!+[]+!+[]](fuck1[+[]][sToS(fuck[6])](fuck1[+[]][sToS(fuck[6])](fuck[15])))(+!+[])+(+!+[]);

我們直接看到這塊



fuck1[!+[] + !+[]] = [][sToS(fuck[0])][sToS(fuck[1])];
// fuck[2] = Function;
fuck1[+[]] = fuck1[!+[] + !+[]](sToS(fuck[5]))(),
// fuck[0] = Function('return this')()
fuck1[+[]][sToS(fuck[6])] = sToS;
fuck1[!+[] + !+[]](fuck1[+[]][sToS(fuck[6])](fuck[14]))();
// fuck1[2]("setInterval(debugger;,1000)")()

通過上面的程式碼解析, 我們可以看到,debugger其實就是通過setInterval方法來呼叫的. 那麼我們其實可以寫這個過偵錯程式碼.

4. JSHook 物件屬性

Hook物件屬性需要使用到

Object.defineProperty() //方法會直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性,並返回此物件。 | 這個常用一些.
Object.defineProperties() // 方法直接在一個物件上定義新的屬性或修改現有屬性,並返回該物件。

1. Object.defindPropety()

下面這個是定義了一個案例. 我們使用Object.defindProperty()來修改屬性物件的set

var obj = {
    'name': function(){
        return 'xiaopang';
    }
}

然後我們編寫Hook程式碼.

Object.defineProperty(obj,'name',{
    'set':function(x){
        console.log(x)
        return x;
    }
})

Hook注意點:

  • 物件需要建立以後方可Hook
  • 一般都是Hook全域性物件.
  • 不只是可以Hook自定義,我們還可以Hook系統的物件屬性, document.cookie

二. Chrome拓展(Chrome Extension)開發

1. 基本介紹

部分資料參照於 https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html

1. 什麼是Chrome外掛?

​ 嚴格來講,我們正在說的東西應該叫Chrome擴充套件(Chrome Extension),真正意義上的Chrome外掛是更底層的瀏覽器功能擴充套件,可能需要對瀏覽器原始碼有一定掌握才有能力去開發。鑑於Chrome外掛的叫法已經習慣,本文也全部採用這種叫法,但讀者需深知本文所描述的Chrome外掛實際上指的是Chrome擴充套件。

​ Chrome外掛是一個用Web技術開發、用來增強瀏覽器功能的軟體,它其實就是一個由HTML、CSS、JS、圖片等資源組成的一個.crx字尾的壓縮包.

​ 另外,其實不只是前端技術,Chrome外掛還可以配合C++編寫的dll動態連結庫實現一些更底層的功能(NPAPI),比如全螢幕幕截圖。由於安全原因,Chrome瀏覽器42以上版本已經陸續不再支援NPAPI外掛,取而代之的是更安全的PPAPI。

2. 學習Chrome外掛開發的意義

增強瀏覽器功能, 實現屬於自己的「客製化版」瀏覽器。然後再本文中,我們則是需要通過學習Chrome瀏覽器外掛的開發,來實現 JSHOOK 程式碼的注入.

Chrome提供了非常多實用的API,包括但不限於:

  • 書籤控制
  • 下載控制
  • 視窗控制
  • 標籤控制
  • 網路請求控制,各類事件堅挺
  • 自定義原生選單
  • 完善的通訊機制
  • 等等

3. 為什麼是Chrome外掛,而不是firefox外掛?

  1. Chrome的市場佔有率更高.
  2. 開發簡單
  3. 應用場景更廣泛,Firefox外掛只能執行在Firefox上,而Chrome除了Chrome瀏覽器之外,還可以執行在所有webkit核心的國產瀏覽器,比如360極速瀏覽器、360安全瀏覽器、搜狗瀏覽器、QQ瀏覽器等等;
  4. 除此之外,Firefox瀏覽器也對Chrome外掛的執行提供了一定的支援;

2. 檔案結構

Chrome外掛沒有嚴格的專案結構要求,只要保證本目錄有一個manifast.json即可.也不需要專門的IDE,普通的web開發工具即可。

從右上角選單->更多工具->擴充套件程式可以進入 外掛管理頁面,也可以直接在位址列輸入 chrome://extensions 存取。

勾選開發者模式即可以資料夾的形式直接載入外掛,否則只能安裝.crx格式的檔案。Chrome要求外掛必須從它的Chrome應用商店安裝,其它任何網站下載的都無法直接安裝,所以,其實我們可以把crx檔案解壓,然後通過開發者模式直接載入。

開發中,程式碼有任何改動都必須重新載入外掛,只需要在外掛管理頁按下Ctrl+R即可,以防萬一最好還把頁面重新整理一下。

1. manifest.json

這是一個Chrome外掛最重要也是必不可少的檔案,用來設定所有和外掛相關的設定,必須放在根目錄。其中,manifest_versionnameversion3個是必不可少的,descriptionicons是推薦的。

{
	// 清單檔案的版本,這個必須寫,而且必須是2
	"manifest_version": 2,
	// 外掛的名稱
	"name": "demo",
	// 外掛的版本
	"version": "1.0.0",
	// 外掛描述
	"description": "簡單的Chrome擴充套件demo",
	// 圖示,一般偷懶全部用一個尺寸的也沒問題
	"icons":
	{
		"16": "img/icon.png",
		"48": "img/icon.png",
		"128": "img/icon.png"
	},
	// 會一直常駐的後臺JS或後臺頁面
	"background":
	{
		// 2種指定方式,如果指定JS,那麼會自動生成一個背景頁
		"page": "background.html"
		//"scripts": ["js/background.js"]
	},
	// 瀏覽器右上角圖示設定,browser_action、page_action、app必須三選一
	"browser_action": 
	{
		"default_icon": "img/icon.png",
		// 圖示懸停時的標題,可選
		"default_title": "這是一個範例Chrome外掛",
		"default_popup": "popup.html"
	},
	// 當某些特定頁面開啟才顯示的圖示
	/*"page_action":
	{
		"default_icon": "img/icon.png",
		"default_title": "我是pageAction",
		"default_popup": "popup.html"
	},*/
	// 需要直接注入頁面的JS
	"content_scripts": 
	[
		{
			//"matches": ["http://*/*", "https://*/*"],
			// "<all_urls>" 表示匹配所有地址
			"matches": ["<all_urls>"],
			// 多個JS按順序注入
			"js": ["js/jquery-1.8.3.js", "js/content-script.js"],
			// JS的注入可以隨便一點,但是CSS的注意就要千萬小心了,因為一不小心就可能影響全域性樣式
			"css": ["css/custom.css"],
			// 程式碼注入的時間,可選值: "document_start", "document_end", or "document_idle",最後一個表示頁面空閒時,預設document_idle
			"run_at": "document_start"
		},
		// 這裡僅僅是為了演示content-script可以設定多個規則
		{
			"matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
			"js": ["js/show-image-content-size.js"]
		}
	],
	// 許可權申請
	"permissions":
	[
		"contextMenus", // 右鍵選單
		"tabs", // 標籤
		"notifications", // 通知
		"webRequest", // web請求
		"webRequestBlocking",
		"storage", // 外掛本地儲存
		"http://*/*", // 可以通過executeScript或者insertCSS存取的網站
		"https://*/*" // 可以通過executeScript或者insertCSS存取的網站
	],
	// 普通頁面能夠直接存取的外掛資源列表,如果不設定是無法直接存取的
	"web_accessible_resources": ["js/inject.js"],
	// 外掛主頁,這個很重要,不要浪費了這個免費廣告位
	"homepage_url": "https://www.baidu.com",
	// 覆蓋瀏覽器預設頁面
	"chrome_url_overrides":
	{
		// 覆蓋瀏覽器預設的新分頁
		"newtab": "newtab.html"
	},
	// Chrome40以前的外掛設定頁寫法
	"options_page": "options.html",
	// Chrome40以後的外掛設定頁寫法,如果2個都寫,新版Chrome只認後面這一個
	"options_ui":
	{
		"page": "options.html",
		// 新增一些預設的樣式,推薦使用
		"chrome_style": true
	},
	// 向位址列註冊一個關鍵字以提供搜尋建議,只能設定一個關鍵字
	"omnibox": { "keyword" : "go" },
	// 預設語言
	"default_locale": "zh_CN",
	// devtools頁面入口,注意只能指向一個HTML檔案,不能是JS檔案
	"devtools_page": "devtools.html"
}

2. content-scripts

所謂content-scripts,其實就是Chrome外掛中向頁面注入指令碼的一種形式(雖然名為script,其實還可以包括css的),藉助content-scripts我們可以實現通過設定的方式輕鬆向指定頁面注入JS和CSS(如果需要動態注入,可以參考下文),最常見的比如:廣告遮蔽、頁面CSS客製化,等等。

{
	// 需要直接注入頁面的JS
	"content_scripts": 
	[
		{
			//"matches": ["http://*/*", "https://*/*"],
			// "<all_urls>" 表示匹配所有地址
			"matches": ["<all_urls>"],
			// 多個JS按順序注入
			"js": ["js/jquery-1.8.3.js", "js/content-script.js"],
			// JS的注入可以隨便一點,但是CSS的注意就要千萬小心了,因為一不小心就可能影響全域性樣式
			"css": ["css/custom.css"],
			// 程式碼注入的時間,可選值: "document_start", "document_end", or "document_idle",最後一個表示頁面空閒時,預設document_idle
			"run_at": "document_start"
		}
	],
}

特別注意,如果沒有主動指定run_atdocument_start(預設為document_idle),下面這種程式碼是不會生效的:

document.addEventListener('DOMContentLoaded', function()
{
	console.log('我被執行了!');
});

content-scripts和原始頁面共用DOM,但是不共用JS,如要存取頁面JS(例如某個JS變數),只能通過injected js來實現。content-scripts不能存取絕大部分chrome.xxx.api,除了下面這4種:

  • chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
  • chrome.i18n
  • chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
  • chrome.storage

其實看到這裡不要悲觀,這些API絕大部分時候都夠用了,非要呼叫其它API的話,你還可以通過通訊來實現讓background來幫你呼叫(關於通訊,後文有詳細介紹)。

好了,Chrome外掛給我們提供了這麼強大的JS注入功能,剩下的就是發揮你的想象力去玩弄瀏覽器了。

3.background

後臺(姑且這麼翻譯吧),是一個常駐的頁面,它的生命週期是外掛中所有型別頁面中最長的,它隨著瀏覽器的開啟而開啟,隨著瀏覽器的關閉而關閉,所以通常把需要一直執行的、啟動就執行的、全域性的程式碼放在background裡面。

background的許可權非常高,幾乎可以呼叫所有的Chrome擴充套件API(除了devtools),而且它可以無限制跨域,也就是可以跨域存取任何網站而無需要求對方設定CORS

經過測試,其實不止是background,所有的直接通過chrome-extension://id/xx.html這種方式開啟的網頁都可以無限制跨域

設定中,background可以通過page指定一張網頁,也可以通過scripts直接指定一個JS,Chrome會自動為這個JS生成一個預設的網頁:

{
	// 會一直常駐的後臺JS或後臺頁面
	"background":
	{
		// 2種指定方式,如果指定JS,那麼會自動生成一個背景頁
		"page": "background.html"
		//"scripts": ["js/background.js"]
	},
}

需要特別說明的是,雖然你可以通過chrome-extension://xxx/background.html直接開啟後臺頁,但是你開啟的後臺頁和真正一直在後臺執行的那個頁面不是同一個,換句話說,你可以開啟無數個background.html,但是真正在後臺常駐的只有一個,而且這個你永遠看不到它的介面,只能偵錯它的程式碼。

4. event-pages

這裡順帶介紹一下event-pages,它是一個什麼東西呢?鑑於background生命週期太長,長時間掛載後臺可能會影響效能,所以Google又弄一個event-pages,在組態檔上,它與background的唯一區別就是多了一個persistent引數:

{
	"background":
	{
		"scripts": ["event-page.js"],
		"persistent": false
	},
}

它的生命週期是:在被需要時載入,在空閒時被關閉,什麼叫被需要時呢?比如第一次安裝、外掛更新、有content-script向它傳送訊息,等等。

除了組態檔的變化,程式碼上也有一些細微變化,個人這個簡單瞭解一下就行了,一般情況下background也不會很消耗效能的。

5. popup

popup是點選browser_action或者page_action圖示時開啟的一個小視窗網頁,焦點離開網頁就立即關閉,一般用來做一些臨時性的互動。

部落格園網摘插件popup效果

popup可以包含任意你想要的HTML內容,並且會自適應大小。可以通過default_popup欄位來指定popup頁面,也可以呼叫setPopup()方法。

設定方式:

{
	"browser_action":
	{
		"default_icon": "img/icon.png",
		// 圖示懸停時的標題,可選
		"default_title": "這是一個範例Chrome外掛",
		"default_popup": "popup.html"
	}
}

img

需要特別注意的是,由於單擊圖示開啟popup,焦點離開又立即關閉,所以popup頁面的生命週期一般很短,需要長時間執行的程式碼千萬不要寫在popup裡面。

在許可權上,它和background非常類似,它們之間最大的不同是生命週期的不同,popup中可以直接通過chrome.extension.getBackgroundPage()獲取background的window物件。

6. injected-script

這裡的injected-script是我給它取的,指的是通過DOM操作的方式向頁面注入的一種JS。為什麼要把這種JS單獨拿出來討論呢?又或者說為什麼需要通過這種方式注入JS呢?

這是因為content-script有一個很大的「缺陷」,也就是無法存取頁面中的JS,雖然它可以操作DOM,但是DOM卻不能呼叫它,也就是無法在DOM中通過繫結事件的方式呼叫content-script中的程式碼(包括直接寫onclickaddEventListener2種方式都不行),但是,「在頁面上新增一個按鈕並呼叫外掛的擴充套件API」是一個很常見的需求,那該怎麼辦呢?其實這就是本小節要講的。

content-script中通過DOM方式向頁面注入inject-script程式碼範例:

// 向頁面注入JS
function injectCustomJs(jsPath)
{
	jsPath = jsPath || 'js/inject.js';
	var temp = document.createElement('script');
	temp.setAttribute('type', 'text/javascript');
	// 獲得的地址類似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
	temp.src = chrome.extension.getURL(jsPath);
	temp.onload = function()
	{
		// 放在頁面不好看,執行完後移除掉
		this.parentNode.removeChild(this);
	};
	document.head.appendChild(temp);
}

你以為這樣就行了?執行一下你會看到如下報錯:

Denying load of chrome-extension://efbllncjkjiijkppagepehoekjojdclc/js/inject.js. Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.

意思就是你想要在web中直接存取外掛中的資源的話必須顯示宣告才行,組態檔中增加如下:

{
	// 普通頁面能夠直接存取的外掛資源列表,如果不設定是無法直接存取的
	"web_accessible_resources": ["js/inject.js"],
}

7.更多

更多關於Chrome Extension的開發請看部落格https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html

3.實戰 - Js自動注入Hook程式碼

在2.2中,介紹了許許多多的Chrome的頁面各個作用. 接下來進行實戰.

1. manifest.json

由於我們是需要在網頁開啟的時候, 立馬將我們的程式碼注入進去,這樣才能毫無遺漏的把一些操作給Hook出來。因此,我們的manifest.json檔案應該如下設定

{
	"manifest_version": 2,
	"name": "小胖JS自動注入外掛",
	"version": "1.0",
	"description": "小胖JS自動注入外掛,QQ2625112940",
	"author": "xiaopang",
	"icons":
	{
		"16":"ico.png",
		"48": "icon.png",
		"128": "icon.png"
	},
	"browser_action": 
	{
		"default_icon": "icon.png",
		"default_popup": "popup.html"
	},
	"content_scripts": 
	[
		{
			"matches": ["<all_urls>"],
			"js": ["content-script.js"],
			"run_at": "document_start",
			"all_frames": true
		}
	],
	"permissions":
	[
        "<all_urls>",
        "webRequest",
        "webRequestBlocking",
        "tabs",
        "http://*/*",
        "https://*/*",
        "contextMenus",
        "cookies",
        "unlimitedStorage",
        "notifications",
        "storage",
        "clipboardWrite"
    ]
}

2. content_script.js

那麼在瀏覽器中, 我們應該要如何載入js檔案呢?可以參照下面程式碼

(function() {
	var spt = document.createElement('script');
	
	
	spt.innerHTML = `
		
		
		// ---- Cookie 監聽
		var cookie_cache = document.cookie; // 獲取到原來的cookie

		Object.defineProperty(document,'cookie',{

			// 獲取Cookie時,觸發的動作
			get: function(){
				return cookie_cache; 
			},

			//當Cookie被設定的時候,觸發的動作
			set: function(val){
				console.log('Cookies Setting',val);
				// debugger;
				var cookie = val.split(';')[0];
				var ncookie = cookie.split("=");
				var flag = false;
				var cache = cookie_cache.split("; ");
				cache = cache.map(function(a){
					if (a.split("=")[0] === ncookie[0]){
						flag = true;
						return cookie;
					}
					return a;
				})
				cookie_cache = cache.join("; ");
				if (!flag){
					cookie_cache += cookie + "; ";
				}
				this._value = val;
				return cookie_cache;
				

			}
		})

		// ----

	`
	document.documentElement.appendChild(spt);
})();

上面的例子是對Cookie進行監控的程式碼,暫且忽略功能的實現問題. 就單純看建立Script的過程, 其實就是下面這點而已.

(function() {
	var spt = document.createElement('script');
	spt.innerHTML = `
		// 業務邏輯程式碼
	`
	document.documentElement.appendChild(spt);
})();

下面再祭出一些注入的業務邏輯程式碼

    //HOOK JSON stringify
    var rstringify = JSON.stringify;
    JSON.stringify = function(a){
        console.log("Detect Json.stringify", a);
		//debugger;
        return rstringify(a);
    }

    //HOOK json parse
    //var strparse = JSON.parse
    //JSON.parse = function(b){
        //console.log("Detect Json.Parse", b);
        //return strparse(b);
    //}

    //var plugins_cache = window.navigator
    //Object.defineProperty(navigator, 'plugins', {
    //    get: function() {
    //        console.log('Getting plugins');
    //        //debugger;
    //        return plugins_cache;
    //    },
    //    set: function(val) {
    //      console.log('獲取資訊');
    //      console.log(val);
    //      debugger;
    //    },
    //});

	var _eval = eval;
	eval = function(e){
		_eval(e.replace("debugger",""));
	}
	eval.toString = _eval.toString;

	var _Function = Function;
	Function = function(e){
		_Function(e.replace("debugger",""));
	}
	Function.toString = _Function.toString;

	var _constructor = constructor;
	Function.prototype.constructor = function(s) {
		if (s == "debugger"){
			console.log(s);
			return null;
		}
		return _constructor(s);
	}
	

三. 偵錯技巧

1. 快速定位關鍵程式碼

  • initiator函數堆疊
  • callstack函數堆疊
  • xhr斷點
  • JS HOOK

2. Conditional breakpoints

在程式碼左邊的行號 - > 右鍵 -> Edit breakpoints -> 然後輸入表示式, 結果=true的時候會自動斷下

3. Reres拓展外掛

筆者使用該外掛一般情況:

一般情況下是遇到大檔案的時候,會使用Reres外掛,或者需要修改程式碼進行偵錯的時候

Github地址:https://github.com/annnhan/ReRes

新增規則

點選「新增規則」按鈕,輸入以下資訊,然後儲存:

  • If URL match: 一個正規表示式,當請求的URL與之匹配時,規則生效。注意:不要填開頭的/和結束的/gi,如/.*/gi請寫成.*
  • Response: 對映的響應地址,這個地址會替換掉url中與上面正則匹配的部分。線上地址請以http://開頭,本地地址以file:///開頭,比如http://cssha.comfile:///D:/a.js

練習網站:

  • https://www.cls.cn/ 登入的password
  • http://api.51pin.foxconn.com/iRecruitWeb/Recruit/Activity/ActivityParticipate.html?module=2

4. monitor監聽方法

5. monitorEvents監聽方法

6. watch監聽變數

7.控制檯實時表示式

四. 實戰

1. webpack整體改寫方案

其實就是在webpack命名的函數. webpack中,會有需要的

var aaa = n(12);

var bbb = n(45);

我們對於webpack的網站,我自認為不適合用扣演演算法,不適合用缺少補啥的方法,誰能知道n(*)裡面還有沒有巢狀其他的n呢. 所以我認為用整體改寫就是一個好的辦法.

整體改寫的思路如下:
1. 找到加密位置
2. 查到當前方法實現程式碼,整體拿下.

類似這種的就拿下.

(window["webpackJsonp"]=window["webpackJsonp"]||[]).push(
"aaa":function(e,t,r){},
"bbb":functino(e,t,r){}
)()
3. 找到"n"函數宣告位置.

一般類似於這樣, 具體如何說我好像沒法表達. 也是整個檔案拿下一般.

!function(e) {
    function r(r) {
        for (var n, a, i = r[0], c = r[1], l = r[2], p = 0, s = []; p < i.length; p++)
            a = i[p],
            Object.prototype.hasOwnProperty.call(o, a) && o[a] && s.push(o[a][0]),
            o[a] = 0;
        for (n in c)
            Object.prototype.hasOwnProperty.call(c, n) && (e[n] = c[n]);
        for (f && f(r); s.length; )
            s.shift()();
        return u.push.apply(u, l || []),
        t()
    }
    function t() {
        for (var e, r = 0; r < u.length; r++) {
            for (var t = u[r], n = !0, i = 1; i < t.length; i++) {
                var c = t[i];
                0 !== o[c] && (n = !1)
            }
            n && (u.splice(r--, 1),
            e = a(a.s = t[0]))
        }
        return e
    }
    var n = {}
      , o = {
        1: 0
    }
      , u = [];
    function a(r) {
        if (n[r])
            return n[r].exports;
        var t = n[r] = {
            i: r,
            l: !1,
            exports: {}
        }
          , o = !0;
        try {
            e[r].call(t.exports, t, t.exports, a),
            o = !1
        } finally {
            o && delete n[r]
        }
        return t.l = !0,
        t.exports
    }
    a.e = function(e) {
        var r = []
          , t = o[e];
        if (0 !== t)
            if (t)
                r.push(t[2]);
            else {
                var n = new Promise((function(r, n) {
                    t = o[e] = [r, n]
                }
                ));
                r.push(t[2] = n);
                var u, i = document.createElement("script");
                i.charset = "utf-8",
                i.timeout = 120,
                a.nc && i.setAttribute("nonce", a.nc),
                i.src = function(e) {
                    return a.p + "static/chunks/" + ({}[e] || e) + "." + {
                        53: "6d99d4eacdc1f6ea047f",
                        54: "cbec7184fead9e811bbf"
                    }[e] + ".js"
                }(e);
                var c = new Error;
                u = function(r) {
                    i.onerror = i.onload = null,
                    clearTimeout(l);
                    var t = o[e];
                    if (0 !== t) {
                        if (t) {
                            var n = r && ("load" === r.type ? "missing" : r.type)
                              , u = r && r.target && r.target.src;
                            c.message = "Loading chunk " + e + " failed.\n(" + n + ": " + u + ")",
                            c.name = "ChunkLoadError",
                            c.type = n,
                            c.request = u,
                            t[1](c)
                        }
                        o[e] = void 0
                    }
                }
                ;
                var l = setTimeout((function() {
                    u({
                        type: "timeout",
                        target: i
                    })
                }
                ), 12e4);
                i.onerror = i.onload = u,
                document.head.appendChild(i)
            }
        return Promise.all(r)
    }
    ,
    a.m = e,
    a.c = n,
    a.d = function(e, r, t) {
        a.o(e, r) || Object.defineProperty(e, r, {
            enumerable: !0,
            get: t
        })
    }
    ,
    a.r = function(e) {
        "undefined" !== typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
            value: "Module"
        }),
        Object.defineProperty(e, "__esModule", {
            value: !0
        })
    }
    ,
    a.t = function(e, r) {
        if (1 & r && (e = a(e)),
        8 & r)
            return e;
        if (4 & r && "object" === typeof e && e && e.__esModule)
            return e;
        var t = Object.create(null);
        if (a.r(t),
        Object.defineProperty(t, "default", {
            enumerable: !0,
            value: e
        }),
        2 & r && "string" != typeof e)
            for (var n in e)
                a.d(t, n, function(r) {
                    return e[r]
                }
                .bind(null, n));
        return t
    }
    ,
    a.n = function(e) {
        var r = e && e.__esModule ? function() {
            return e.default
        }
        : function() {
            return e
        }
        ;
        return a.d(r, "a", r),
        r
    }
    ,
    a.o = function(e, r) {
        return Object.prototype.hasOwnProperty.call(e, r)
    }
    ,
    a.p = "",
    a.oe = function(e) {
        throw console.error(e),
        e
    }
    ;
    var i = window.webpackJsonp = window.webpackJsonp || []
      , c = i.push.bind(i);
    i.push = r,
    i = i.slice();
    for (var l = 0; l < i.length; l++)
        r(i[l]);
    var f = c;
    t()
}([]);

4. window.n = a;
!function(e) {
    function r(r) {
        for (var n, a, i = r[0], c = r[1], l = r[2], p = 0, s = []; p < i.length; p++)
            a = i[p],
            Object.prototype.hasOwnProperty.call(o, a) && o[a] && s.push(o[a][0]),
            o[a] = 0;
        for (n in c)
            Object.prototype.hasOwnProperty.call(c, n) && (e[n] = c[n]);
        for (f && f(r); s.length; )
            s.shift()();
        return u.push.apply(u, l || []),
        t()
    }
    function t() {
        for (var e, r = 0; r < u.length; r++) {
            for (var t = u[r], n = !0, i = 1; i < t.length; i++) {
                var c = t[i];
                0 !== o[c] && (n = !1)
            }
            n && (u.splice(r--, 1),
            e = a(a.s = t[0]))
        }
        return e
    }
    var n = {}
      , o = {
        1: 0
    }
      , u = [];
    function a(r) {
        if (n[r])
            return n[r].exports;
        var t = n[r] = {
            i: r,
            l: !1,
            exports: {}
        }
          , o = !0;
        try {
            e[r].call(t.exports, t, t.exports, a),
            o = !1
        } finally {
            o && delete n[r]
        }
        return t.l = !0,
        t.exports
    }
    a.e = function(e) {
        var r = []
          , t = o[e];
        if (0 !== t)
            if (t)
                r.push(t[2]);
            else {
                var n = new Promise((function(r, n) {
                    t = o[e] = [r, n]
                }
                ));
                r.push(t[2] = n);
                var u, i = document.createElement("script");
                i.charset = "utf-8",
                i.timeout = 120,
                a.nc && i.setAttribute("nonce", a.nc),
                i.src = function(e) {
                    return a.p + "static/chunks/" + ({}[e] || e) + "." + {
                        53: "6d99d4eacdc1f6ea047f",
                        54: "cbec7184fead9e811bbf"
                    }[e] + ".js"
                }(e);
                var c = new Error;
                u = function(r) {
                    i.onerror = i.onload = null,
                    clearTimeout(l);
                    var t = o[e];
                    if (0 !== t) {
                        if (t) {
                            var n = r && ("load" === r.type ? "missing" : r.type)
                              , u = r && r.target && r.target.src;
                            c.message = "Loading chunk " + e + " failed.\n(" + n + ": " + u + ")",
                            c.name = "ChunkLoadError",
                            c.type = n,
                            c.request = u,
                            t[1](c)
                        }
                        o[e] = void 0
                    }
                }
                ;
                var l = setTimeout((function() {
                    u({
                        type: "timeout",
                        target: i
                    })
                }
                ), 12e4);
                i.onerror = i.onload = u,
                document.head.appendChild(i)
            }
        return Promise.all(r)
    }
    ,
    a.m = e,
    a.c = n,
    a.d = function(e, r, t) {
        a.o(e, r) || Object.defineProperty(e, r, {
            enumerable: !0,
            get: t
        })
    }
    ,
    a.r = function(e) {
        "undefined" !== typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, {
            value: "Module"
        }),
        Object.defineProperty(e, "__esModule", {
            value: !0
        })
    }
    ,
    a.t = function(e, r) {
        if (1 & r && (e = a(e)),
        8 & r)
            return e;
        if (4 & r && "object" === typeof e && e && e.__esModule)
            return e;
        var t = Object.create(null);
        if (a.r(t),
        Object.defineProperty(t, "default", {
            enumerable: !0,
            value: e
        }),
        2 & r && "string" != typeof e)
            for (var n in e)
                a.d(t, n, function(r) {
                    return e[r]
                }
                .bind(null, n));
        return t
    }
    ,
    a.n = function(e) {
        var r = e && e.__esModule ? function() {
            return e.default
        }
        : function() {
            return e
        }
        ;
        return a.d(r, "a", r),
        r
    }
    ,
    a.o = function(e, r) {
        return Object.prototype.hasOwnProperty.call(e, r)
    }
    ,
    a.p = "",
    a.oe = function(e) {
        throw console.error(e),
        e
    }
    ;
    var i = window.webpackJsonp = window.webpackJsonp || []
      , c = i.push.bind(i);
    i.push = r,
    i = i.slice();
    for (var l = 0; l < i.length; l++)
        r(i[l]);
    var f = c;
    t()
    // 重要****  
    window.n = a;
    // 重要****
}([]);

2. sojson反偵錯

案例地址:https://www.sojson.com/beian/

1. 修改setInterval

如何定位不說了, 直接走到關鍵的地方

window[b('96', 'lInO')](function() { //b('96', 'lInO') == "setInterval"
    var cf = {
        'gSHOk': function(cg) {
            return cg(); //cg()其實就是一個檢測debug的函數.
        }
    };
    cf['gSHOk'](en);
}, 0x7d0);

為了不影響到其他setInterval函數的執行. 這裡可以加一點條件.

var _setInterval = setInterval;
setInterval = function(a,b){
    console.log(a + '',b)
    if(a.indexOf("gSHOk':function(cg){return cg();}")!= -1){
		return 'setInterval is Kill'
	}
    _setInterval(a,b)
}

2. Conditional breakpoints

這裡開啟偵錯工具會直接跳到下面這一塊,我們這節對debugger;行號欄目, 右鍵 add conditional breakpoints, 輸入false . 當條件為false的時候,就不會執行此條件.

function eC(eD) {
    var eE = {
        'oihUc': b('161', '!9L9'),
        'YSZMe': ep[b('162', 'bhfu')]
    };
    if (ep[b('163', 'QS!f')](b('164', '1$&&'), ep[b('165', 'C5IH')])) {
        en();
    } else {
        if (typeof eD === ep[b('166', '18LM')]) {
            var eG = function() {
                if (eE[b('167', 'tEyN')] !== eE[b('168', 'Gq^E')]) {
                    debugger ;
                } else {
                    so[b('169', 'eOuM')](res[b('16a', 'w2W4')]);
                }
            };
            return ep['bzKJh'](eG);
        } else {
            if (ep[b('16b', 'IIR5')](ep[b('16c', 'BDu]')]('', eD / eD)[ep[b('16d', '(igu')]], 0x1) || eD % 0x14 === 0x0) {
                debugger ;
            } else {
                debugger ;
            }
        }
        ep['RwYcr'](eC, ++eD);
    }
}

3. 函數替換,函數置空

4. Activate breakpoints

直接快捷鍵. Ctrl + F8

5. 修改debugger

(貌似失效了)

簡單點的

Function.prototype.constructor = function(){}

完善點

Function.prototype.__constructor_back = Function.prototype.constructor;
Function.prototype.constructor = function() {
    if(arguments && typeof arguments[0]==='string'){
        //alert("new function: "+ arguments[0]);
        if("debugger" === arguments[0]){
            //arguments[0]="console.log(\"anti debugger\");";
            //arguments[0]=";";
            return
        }
    }
   return Function.prototype.__constructor_back.apply(this,arguments);
}

總結:個人認為,最好用的方法應該是1,2,4了. 操作簡單.

3. 某視訊反偵錯案例

http://peng3.com/vip?a=https%3A%2F%2F2.08bk.com%2F%3Furl%3D%0D%0A&url=http%3A%2F%2F1.zhananhome.applinzi.com%2Fwx

//定位到這邊.
jdetects.create(function(e) {
    var a = 0;
    var n = setInterval(function() {
        if ("on" === e) {
            setTimeout(function() {
                if (a === 0) {
                    a = 1;
                    setTimeout(Base64.decode(code));
                }
            }, 200);
        }
    }, 100);

注入JS程式碼

var _setInterval = setInterval;
setInterval = function(a,b){
    console.log(a + '',b)
    if(a+''.indexOf("setTimeout(Base64.decode(code));")!= -1){
		return 'Debugger is Kill'
	}
    _setInterval(a,b)
}

解決這個以後又發現個驚人的操作.在控制檯發現Console was cleared

function o() {
    window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized ? t("on") : (a = "off",
                                                                                                console.log(d),
                                                                                                ("undefined" !== typeof console.clear) && console.clear(),
                                                                                                t(a));
}

修改下o方法

o = function(){}

或者在上面的setInterval程式碼加多個條件. 原因是向上跟蹤發現以下程式碼

var f = setInterval(o, i);
var _setInterval = setInterval;
setInterval = function(a,b){
    console.log(a + '',b)
    if( a + ''.indexOf("setTimeout(Base64.decode(code));")!= -1){
		return 'Debugger is Kill'
	}
    if (a + ''.indexOf('console.clear')!=-1){
        return 'console.clear is Kill'
    }
    _setInterval(a,b)
}

4. 自寫演演算法案例 -1

5. 自寫演演算法案例 -2

6. JS混淆原理(eval和Function)

7. JS混淆原理(數位混淆和字串混淆)

8. 五秒防火牆fuckjs原理分析改寫

9. 流程控制混淆原理(switch)

10. 流程控制混淆原理(逗號運運算元)

五.AST入門與實戰

1. AST抽象語法樹入門

2. Babel元件traverse

3. Babel元件types

4. 用Babel生成一個新函數

5. Babel中節點操作

6. 用Babel給函數加點料

7. 用Babel實現變數名混淆

7. 用Babel實現陣列亂序

8. 用Babel實現字串加密

9. 實現十六進位制文字加密

10. 實現unicode加密

11. JS混淆還原(字串解密)

12. JS混淆還原(去除花指令)

13. JS混淆還原(AST節點偵錯技巧)

14. switch流程平坦化還原(復原指令順序)

15. JS混淆實戰案例

六. 滾軸破解

1. 雲片

2. 2980