JS 基础

瞎几把写就得,记录一下特性即可。

  1. 万物皆对象,了解一些内置函数和内置属性,比如name, arguments

  2. var 有变量提升,let没有,闭包概念

  3. 匿名方法 const 匿名方法 = (functinn() {})

  4. JS是弱类型语言

  5. 自主垃圾回收,引用计数

  6. 原型对象和实例对象,构造函数,打印原型对象得.prototype,原型对象和实例对象下面都有一个 __proto__属性,指向了其原型对象

  7. 原型对象和实例对象的 this 指向是不同的,实例对象:谁调用的就是谁(比如 window),原型对象:指向的是原型对象的实例

  8. 一个函数被new出来就变成了一个构造函数&原型对象,构造函数的属性和原型对象的属性不同

  9. 原型链:对于实例而言a.__proto__ 指向其原型对象,对于原型对象而言指向的其父原型对象,直到 Object 原型对象

  10. 一个例子:

    1
    2
    3
    4
    5
    6
    7
    function pro() {
    this.a = 1;
    }
    pro.a = 2;
    var aa = new pro(); // new 出来一个 pro 对象,aa是实例,a是属性
    // aa.__proto__ 指向pro对象,pro.prototype指向的也是pro对象,aa.__proto__.__proto__指向的是pro对象的父亲 Object 对象
    // 如果要往 pro 对象里添加属性,可以用 pro.prototype.b = 2 或者 aa.__proto__.b = 2
  11. 作用域:全局作用域、语句作用域(比如括号里面的)、块作用域(比如大括号中间的)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var a = 1;
    function xx() {
    var a = 2;
    console.log(a); // 输出的是 2,先从块作用域找,再从语句作用域找,再从全局找
    }
    /*
    全局作用域 {
    a = 1
    语句作用域 {
    块作用域 {
    a = 2
    console.log()
    }
    }
    }


    */
  12. Object.defineProperty 在对象上定义一个新属性或修改一个对象的现有属性,并返回该对象,用于 hook。有三个参数 obj, prop, descriptor,分别是对象,定义或修改的属性的属性值symbol,要定义或修改的属性的描述符(比如,可不可以被修改,可不可以被删除等等)

  13. 可以通过 delete aa.a 删除 aa 实例的 a 属性,但是Object.freeze(aa)可以冻结,不能修改和删除属性,Object.seal(aa)不能删除属性但是可以改

  14. Object.prototype.__defineGetter__,hook getter方法,比如 aa.__defineGetter__("a", function(){console.log("1")});就是只要 aa 实例里的 a 属性被访问,就会打印 1

  15. 一个花指令的例子:(0, a.request),这是一个函数调用,括号会返回最后一个元素的值,也就是这句话等价于 a.request

工具使用

JS 逆向流程:抓包 -> 调试 -> 脱离浏览器环境(V8, node.js, pyv8等) ->

fiddler配置

版本: v5.0.20204.45441 for .NET 4.6.1 Built: 2020年11月3日

  1. Tools -> HTTPS -> 全勾选 -> 安装证书 -> 管理员权限重启 fiddler

  2. 设置自定义端口号

  3. 用浏览器插件设置本地代理和端口号

  4. 修改 protocols 为 <client>; ssl2; ssl3; tls1.0; tls1.1; tls1.2

  5. 打开 Decode 快速解压缩

  6. 断点:捕获数据,Inspectors:放行数据

  7. 自动断点,在 request 之前/之后

  8. 修改 User Agent 地址

  9. 编程猫插件配置

  10. 编程猫插件注入 hook:开启,并删除掉地址就是匹配所有 URL。只能hook全局,局部变量是没法 hook 的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //当前版本hook工具只支持Content-Type为html的自动hook
    //下面是一个示例:这个示例演示了hook全局的cookie设置点
    (function() {
    //严谨模式 检查所有错误
    'use strict';
    //document 为要hook的对象 这里是hook的cookie
    var cookieTemp = "";
    Object.defineProperty(document, 'cookie', {
    //hook set方法也就是赋值的方法
    set: function(val) {
    //这样就可以快速给下面这个代码行下断点
    //从而快速定位设置cookie的代码
    console.log('Hook捕获到cookie设置->', val);
    cookieTemp = val;
    return val;
    },
    //hook get方法也就是取值的方法
    get: function()
    {
    return cookieTemp;
    }
    });
    })();

浏览器工具

  1. 下断点

  2. 复制 JS 路径并直接修改本地的值

  3. Event Listeners:绑定的事件列表,且可以筛选特定按钮的绑定事件

  4. 手机模式不是真的手机,没有手机的环境,浏览器的环境有 window,UA,Cookie等

  5. 微信 UA :可以通过微信抓包获取

    QQ_1730388027841

  6. F1:设置面板。取消Javascript source maps 功能(可能导致断不上) ,了解一下 webpack;NetWork:禁止输出,禁止清空缓存;NetWork Throttling Profiles:控制网速

  7. XHR:XMLHttpRequest,JS 控制的一种发包方式(只能筛选JS发的包)

  8. Performance:实时跟踪翻页时发送的包,以及时序,此外还能看到别的线程,和事件调用等(信息量有点大emm)

    QQ_1730389633044

  9. Application:网站储存的数据总览,Cookies 是本地文件,一般在缓存目录下,还有session Storage, LocalStorage, indexDB等

  10. Security:是否支持 HTTPS 等

  11. Sources:有个 Snippets ,临时脚本/片段

  12. 还有一堆功能藏在这三个点里,比如 Network request blocking等

    image-20241031235241084

  13. 断点:

    • F9能进异步,F7不能
    • 忽略断点按钮没法忽略DOM断点和异常断点
    • 异常断点:try catch
    • DOM断点:页面刷新的时候在HTML上下断
    • 全局事件断点(生命周期断点):比如在加载的时候下断,滚动条下断,鼠标事件下断,XHR下断等
    • 除了 XHR,还有 jsonp 的请求,请求后会返回的response会立即执行,且浏览器自己处理(XHR:用户请求用户处理,jsonp:用户请求浏览器处理)
    • 代码行断点:跳过当前断点,编辑断点(条件断点:过滤断点,写上条件,让其在我们想要其断的时候再断,否则跳过)
    • debugger:直接打上条件断点 false,其就不会执行了
    • watch:监控变量的值,可以实时打印其值
    • 方法栈:先运行的在下面,当前的在最上面。可以看出方法名,当前的代码行数,一些特殊的方法(比如定时器,XHR等方法),可以看出调用关系
    • 作用域:Local(本地作用域),Closure(上层作用域,可能多个嵌套),Global(全局作用域),找一个变量:从当前开始找,然后一层一层网上找,直到全局
    • 智能提示:会提示到当前的方法
    • 下断点:方法开始,方法结束,想要的数据
    • send, ajax 是分装发 XHR,直接找他俩上面的栈帧
    • 本地替换:
      1. 浏览器本地替换
      2. DF本地替换,如果有个URL地址其 ? 后面是个随机数,可以用FD替换,找到FD的 Auto Responser,也可以正则匹配(有个test按钮,点一下把想要匹配的目标URL贴进去就可以看出是否匹配成功),也可以自己写插件。如果替换的是 JS 文件,本地文件的后缀也需要是 JS 文件
  14. 反调试:

    • https://www.jsjiami.com/jsjiami.v6.html

    • 禁止控制台调试

      image-20241106171015782

    • 写一个demo(比如一行代码 window.ep = 1;放到一个新页面的 snippet中

      1. 运行,发现有反调试

        image-20241106171142545

      2. 解决手动 debugger:直接本地替换进行修改,把debugger删掉即可

      3. 解决代码格式化检测:点击最高配置发现检测是有用的,格式化再运行会直接卡死

        image-20241106172255931

        过检测:一般格式化检测利用的是这个原理:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        function xx() {

        }

        xx.toString() // 或者 xx + ""
        /*
        这种就会返回一个字符串,格式化由于空格,换行符的问题会导致长度不一致,这样用正则匹配一个未格式化的String,比如 funtion xx(){},如果代码格式化了,就肯定匹配不成功
        那么过检测的方法就是先寻找特征
        RegExp: 正则匹配
        test: 正则匹配函数
        toString: 字符串转化
        然后下断看看会不会卡死
        */

        image-20241106174900860

        下面就是一个典型的格式化检测代码:

        image-20241106173311724

        removeCookie 这个函数,先 toString ,再 test 和正则进行比较,如果发现其格式化了就返回一个检测值。

        那么一个有效的过检测手段就是先格式化,再把 return 的结果直接取反即可

        image-20241106173507125

        第二种方法是 hook:对 toString 方法进行替换

        image-20241106175209522

        1
        _0x52e0da['removeCookie']['toString'] = function() {return "function(){return 'dev';}"}

        image-20241106175342847

        当然也可以 hook 正则的 test 方法

        一个友好HOOK步骤:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        // 保存原函数
        RegExp.prototype.test_ = RegExp.prototype.test;
        // 替换原函数
        RegExp.prototype.test = function() {
        // 执行原函数,用 apply 执行
        var ret = RegExp.prototype.test_.apply(this, arguments);
        return !ret;
        }
        // 保护 toString (如果检测了就保护)
        RegExp.prototype.test_.toString = RegExp.prototype.test.toString;
      4. 第二种反调试:进虚拟机 VM

        1
        2
        // 1. eval
        var a = eval("debugger;function xx(){return 2;} xx();");

        QQ_1731078633806

        1
        2
        3
        // 2. Function 包裹语句
        var a = Function("console.log(1)");
        // 3. Blob 内存对象

        怎么过 Function:hook掉即可,Function的参数就是要执行的语句,直接把里面的debugger删掉即可。

        1
        2
        3
        4
        5
        Function.prototype.constructor_ = Function.prototype.constructor;
        Function.prototype.constructor = function(a) {
        a = a.replace("debugger", "");
        return Function.prototype.constructor_(a);
        }
      5. 对于一个登录页面来说,我们需要找到用户名,密码,加密逻辑

        • 如果是 document 类型,就复制这个 API 的 URL 然后直接搜索,大概率能找到逻辑
        • 如果是 xhr ,就直接从network里点击该包后的交叉引用,如果是 ajax,就找到其 caller,大概率能找到发包时的内容,再往上进行交叉引用,或查看栈帧找到其 caller 即可
        • 如果是 jsonp,一般会有个回调函数,也是一样,找到其caller即可
      6. 异步调试:涉及到 promise

        1. Promise标准:

          • 拥有一个then方法

          • fulfill 成功时的操作,即resolve

          • reject 失败时的操作

          • padding 等待

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        // 有一个线程
        var a = []; //定义一个数组,这个数组里放的都是方法
        function Promise(p) {
        a.push(p);
        while(true) {
        for(let i = 0; i < a.length; i++) {
        if(a[i].z == -1) {
        xxx
        } elsec if(a[i].z == "pending") {
        xxx
        }
        }
        }
        }
        new Promise(); // 想要给 a 数组添加方法就要 new 这个对象,此时 执行构造方法,就是 Promise 方法,会立即执行
        // 异步始终是在一个线程中的

        一个例子

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        const p = new Promise((resolve, reject) => {
        setTimeout(function () {
        // 用于模拟异步操作成功或失败
        flag = 1
        if (flag) {
        resolve('异步操作成功啦~')
        } else {
        reject("异步操作失败啦~")
        }
        }, 2000)
        })

        p.then(value => {
        console.log(value);
        }, reason => {
        console.log(reason);
        })
        • function.prototype.apply:调用一个具有给定 this 值的函数,以及以一个数组或类数组对象的形式提供的参数,o.apply(this, arguments ) 等价于 this.o(arguments),就是给 this 这个对象 bind 了一个方法 o 传入 argume nts

        • 一般会 new Promise(function(){}),重点看里面的方法

        • 控制流平坦化:wrapu.prev = u.next,u.sent 等价于其上一个方法的返回值,方法就是找 u.next 对应的前驱,把该代码块复制出来,去掉控制执行流的代码,再把u.sent 替换成上一个方法的返回值即可

        • 异步封装:

          Callback 栈帧前三个一般都是发包的请求,然后看一些特殊的 js 代码

          1
          2
          3
          4
          5
          6
          7
          while(b.length) {
          a.then(b.shift(), b.shift());
          } // shift可以理解为出栈 pop
          // then 有两个参数,一个是成功回调一个是失败回调
          // b 是一个数组,看其第一个元素即可
          // 下断点在b.shift前面下
          // 只有在异步中加密的才需要跟,在异步前后加密的可以直接看栈帧,如果看异步的时候发现执行时已经加密了,那就不用看异步了
      7. HOOK:油猴插件/fiddler插件

        油猴插件:原理是利用浏览器自带的API,直接调用这些API进行 HOOK,没有逃离浏览器的框架,不足是即使把运行时期改称了 document start,也可能 hook 不到(比如一些动态加载到js,在document刷新之前就会执行,这就hook不到),而且其会插入一段js代码,可能会被检测到

        image-20241222172814579

        编程猫fiddler插件:基于FD代理,其脱离浏览器而存在,不被浏览器感知

        hook 主要用于定位逻辑,常见的 hook 是对 LocalStorage的 hook

        本地替换:override随意修改本地代码,浏览器自带的功能,但是这种方法会被检测,比如添加随机数校验,或者检查代码是否格式化,还有URL地址改变也会被识别成不同的文件

      8. 反调试

        禁止控制台调试:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        // 1.检测控制台调试,用 while 或者定时器
        setInterval("console.clear()", 1000);
        // 修复:禁用console.clear,有个浏览器选项勾上即可

        // 2.替换 console.log 为空
        console.log = function(){}
        // 修复:hook
        console.log_ = console.log;
        console.log = function() {};
        // console.log被删掉以后
        console.log = console.log_

        检测调试:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        // 基于时间
        function testDebuggerByTime() {
        var cur = Date.now();
        debugger;
        if((Date.now() - cur) > 200) {
        console.log("debugger detected");
        }
        }

        testDebuggerByTime();
        // 过检测,hook Date.now函数即可
        Date.now = function() {return 1;}

        //基于 DOM 检测
        // 1. 监听F12键
        document.addEventListener('keydown', function(event) {
        // 检查是否是 F12 键
        if (event.key === 'F12') {
        console.log('F12 key was pressed!');
        // 在这里可以执行其他操作,例如阻止默认行为
        event.preventDefault();
        }
        });

        // 2. 监听F8或者别的快捷键
        // 3. Object.toString()
        // 4. windows 宽纵比,控制台单独打开不要挤压原页面即可
        // 5. proxy 拦截 log 执行,其实就是hook,在log中添加一些检测
        // 6. 数据劫持,意思是如果没有打开控制台,就不会执行console.log语句,基于此我们可以打印一个document中的一个id值,或者class都可以,并重写其get方法,一旦执行console.log()函数,就会获得这个id值,进而触发检测代码,这个times就是检测被console.log几次
        let times = 1;
        var abc = document.createElement('div');
        Object.defineProperty(abc, 'id', {
        get: function() {
        times++;
        console.warn("控制台被打开了");
        }
        });
        console.log(abc);

        // 7.