JS逆向学习笔记

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

练习网站:

>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

添加微信交流群, 联系微信:cjh-18888