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

JS逆向學習筆記

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

文章目錄

一. JS Hook

1. JS HOOK 原理和做用

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

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

Hook代碼:css

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, 結果比正常值多了1html

做用: 能夠去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代碼置入到瀏覽器java

//控制檯置入的代碼
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過反調試

反調試代碼:jquery

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])))(+!+[])+(+!+[]);

咱們直接看到這塊webpack

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方法來調用的. 那麼咱們其實能夠寫這個過調試代碼.git

4. JSHook 對象屬性

Hook對象屬性須要使用到github

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

1. Object.defindPropety()

下面這個是定義了一個案例. 咱們使用Object.defindProperty()來修改屬性對象的setweb

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

相關文章
相關標籤/搜索