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