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插件?
- Chrome的市場佔有率更高.
- 開發簡單
- 應用場景更普遍,Firefox插件只能運行在Firefox上,而Chrome除了Chrome瀏覽器以外,還能夠運行在全部webkit內核的國產瀏覽器,好比360極速瀏覽器、360安全瀏覽器、搜狗瀏覽器、QQ瀏覽器等等;
- 除此以外,Firefox瀏覽器也對Chrome插件的運行提供了必定的支持;
2. 文件結構
Chrome插件沒有嚴格的項目結構要求,只要保證本目錄有一個manifast.json
便可.也不須要專門的IDE,普通的web開發工具便可。
從右上角菜單->更多工具->擴展程序能夠進入 插件管理頁面,也能夠直接在地址欄輸入 chrome://extensions
訪問。
勾選開發者模式
便可以文件夾的形式直接加載插件,不然只能安裝.crx
格式的文件。Chrome要求插件必須從它的Chrome應用商店安裝,其它任何網站下載的都沒法直接安裝,因此,其實咱們能夠把crx
文件解壓,而後經過開發者模式直接加載。
開發中,代碼有任何改動都必須從新加載插件,只須要在插件管理頁按下Ctrl+R
便可,以防萬一最好還把頁面刷新一下。
1. manifest.json
這是一個Chrome插件最重要也是必不可少的文件,用來配置全部和插件相關的配置,必須放在根目錄。其中,manifest_version
、name
、version
3個是必不可少的,description
和icons
是推薦的。
{ // 清單文件的版本,這個必須寫,並且必須是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_at
爲document_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
能夠包含任意你想要的HTML內容,而且會自適應大小。能夠經過default_popup
字段來指定popup頁面,也能夠調用setPopup()
方法。
配置方式:
{ "browser_action": { "default_icon": "img/icon.png", // 圖標懸停時的標題,可選 "default_title": "這是一個示例Chrome插件", "default_popup": "popup.html" } }
須要特別注意的是,因爲單擊圖標打開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
中的代碼(包括直接寫onclick
和addEventListener
2種方式都不行),可是,「在頁面上添加一個按鈕並調用插件的擴展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.com
或file:///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) }