当前位置: 首页 > news >正文

爬虫——夜幕团队JS逆向系列课

// chrome dev-tools console
console.count() console.table()
Copy(var)复制到粘贴板
自带jQuery中操作符: $、$$、$x(xpath)

js语法-函数

变量定义:var在函数内部起作用,const、let 块级作用域
变量提升:扫描整个函数体语句,将所有声明提升到函数顶部
全局作用域:如不使用 var、const、let 关键字声明,变量会绑定到全局 window 上
块级作用域:for、while 等语句内

js进阶

  1. 事件循环
    1. 宏任务(Macrotasks)与微任务(Microtasks)概述
      • 宏任务
        • 在 JavaScript 中,宏任务是指那些被安排到执行队列中按照顺序执行的任务。常见的宏任务包括setTimeoutsetIntervalI/O操作、script(全局任务)等。
        • 例如,当使用setTimeout函数时,它会将回调函数作为一个宏任务添加到事件循环的任务队列中。
      • 微任务
        • 微任务是在当前任务执行结束后立即执行的任务。微任务队列中的任务优先级高于宏任务队列中的任务。常见的微任务包括Promisethen/catch/finally方法、process.nextTick(在 Node.js 中)等。
        • 当一个Promise被解决(resolved)或者被拒绝(rejected)时,其对应的thencatch或者finally中的回调函数会被作为微任务添加到微任务队列中。
    2. Node.js 事件循环
      • 事件循环的阶段
        • Timers 阶段:这个阶段执行setTimeoutsetInterval的回调函数,这些回调函数在定时器到期时被添加到这个阶段的任务队列中。
        • I/O callbacks 阶段:处理上一轮循环中除了close事件之外的异步 I/O 操作的回调函数。
        • Idle, Prepare 阶段:内部使用,主要用于准备工作。
        • Poll 阶段:这个阶段是事件循环的核心部分。它有两个主要功能:
          • 如果没有定时器到期,并且没有即将被执行的setImmediate,它会阻塞在这里等待 I/O 事件的返回,并处理这些 I/O 事件对应的回调函数。
          • 如果有定时器到期,它会处理这些定时器对应的回调函数。
        • Check 阶段:执行setImmediate的回调函数。
        • Close callbacks 阶段:执行close事件的回调函数,比如server.close后的回调函数。
      • 宏任务与微任务在事件循环中的执行顺序
        • 在每个阶段内,首先执行该阶段对应的宏任务。
        • 在宏任务执行完之后,会立即执行微任务队列中的所有微任务。例如,在Timers阶段,如果有setTimeout的回调函数(宏任务)执行,在这个宏任务执行完后,如果有微任务(如Promisethen回调),会先执行微任务队列中的微任务,然后再进入下一个阶段(如I/O callbacks阶段)。
  2. 原型链
    ES6引入class关键字后用的就不多,但现有项目中很多都没用es6,或用bable转为es5

    访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 会自动在其原型对象上查找。如果原型对象也没有,就会继续在原型对象的原型上查找,这样一直向上查找直到找到该属性或方法或者到达原型链的顶端(Object.prototype
  3. 异步编程
    回调函数:简单、回调地狱
    异步操作对象:resolve reject then catch,可读性不高
    async await,多个并行操作要用 Promise.all 执行
  4. 浏览器存储
    //cookie
    通过document.cookie属性来设置,格式为name=value; expires=expiration_time; path=domain_path; domain=domain_name; secure。
    例如:document.cookie = "username=John Doe; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/";
    同样使用document.cookie,它会返回一个包含所有当前页面可用 cookies 的字符串,需要手动解析。
    将expires设置为过去的时间即可删除。//Local Storage
    localStorage.setItem('key', 'value')
    const theme = localStorage.getItem('theme') //如果不存在则返回null
    localStorage.removeItem('key')//Session Storage  数据在页面会话期间(浏览器打开直到关闭)有效,关闭浏览器后数据丢失,大小限制与localStorage类似
    sessionStorage.setItem('key', 'value')
    sessionStorage.getItem('key')
    sessionStorage.removeItem('key')// IndexedDB
    const request = indexedDB.open('myDatabase', 1);
    request.onupgradeneeded = function(event) {const db = event.target.result;const objectStore = db.createObjectStore('customers', { keyPath: 'id' });
    };

  5. 跨域
  6. Webpack 打包

python 调用 JS

库:PyV8 Js2Py PyExecJS(特殊编码可能导致报错,可通过删除或Base64编码处理) PyminiRacer、 Selenium Pyppeteer、使用nodejs开放一个执行js文件的微服务

无限 debugger

  1. 禁用所有断点,或禁用条件断点
  2. 使用devtools的override功能
  3. 使用代理修改页面内容,如 Fiddler Script, mitmProxy
  4. 函数调用前,在控制台重写函数 block = function () {}
  5. reres 插件

快速定位

明文搜索、sources查看、xhr请求追溯、hook追溯、调用栈跟踪、全局断点、dom断点、主动debugger、warning error 追溯、内存优化分析、事件监听等

搜索

dev-tools:CTRL-F、CTRL-SHIFT-F、Network 界面 Filter
Fiddler:Ctrl-F

断点

xhr、DOM、EVENT、自定义

事件监听器

hook

  1. json
    var my_stringify = JSON.stringify;
    JSON.stringify = function (params) {console.log("yemu", params);return my_stringify(params);
    };
    var my_parse = JSON.parse;
    JSON.parse = function (params) {console.log("yemu", params);return my_parse(params);
    };
  2. cookie
    var cookie_cache = document.cookie;
    Object.defineProperty(document, 'cookie', {get: function () { },set: function (val) {console.log('setting cookie', val);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]) {this.value = val;return cookie_cache;}return a;flag = true;return cookie;});cookie_cache = cache.join(";");if (!flag) {cookie_cache += cookie + ";";}}
    });
  3. window attr
  4. eval / Function
    window._cr_eval = window.eval;
    var myeval = function(src) {console.log(src);console.log("======================= eval end =======================");return window._cr_eval(src);
    };
    var myeval = myeval.bind(null);
    myeval.toString = window._cr_eval.toString;
    Object.defineProperty(window, 'eval', { value: myeval });
    // 尝试执行 eval('1+2')window._cr_fun = window.Function;
    var myfun = function() {var args = Array.prototype.slice.call(arguments, 0, -1).join(",");var src = arguments[arguments.length - 1];console.log(src);console.log("======================= Func end =======================");return window._cr_fun.apply(this, arguments);
    };
    myfun.toString = function() { return window._cr_fun + ""; };
    Object.defineProperty(window, 'Function', { value: myfun });
    // 尝试执行 let a = new Function("return 1 + 2");
  5. websocket
    Websocket.prototype.sendA = Websocket.prototype.send;
    Websocket.prototype.send = function(data) {console.info('Hook websocket', data);return this.sendA(data);
    };
  6. tampermonkey hook https://blog.csdn.net/Yy_Rose/article/details/124216720 
    hook
     // ==UserScript==
    // @name         HookBase64
    // @namespace    https://login1.scrape.center/
    // @version      0.1
    // @description  Hook Base64 encode function
    // @match        https://login1.scrape.center/
    // @grant        none
    // ==/UserScript==(function() {'use strict';function hook(object, attr){var func = object[attr]object[attr] = function(){console.log('hooked', object, attr)var ret = func.apply(object, arguments)debuggerreturn ret}}hook(window, 'btoa')
    })();
    hook
     // ==UserScript==
    // @name         Hook global
    // @namespace    http://tampermonkey.net/
    // @include      *
    // @grant        none
    // @run-at       document-start
    // ==/UserScript==(function() {'use strict';//全局变量 监控var t = window._t;var window_flag = '_t'; // 要监控的值var window_value = window[window_flag];Object.defineProperty(window, window_flag, { // window 对象上的值get: function() {console.log('Getting window._t',window_value);return t;},set: function(val) {console.log('Setting window._t', val);debugger;t = val;return t;}});
    })();
    检测是否存在可疑的加密函数
     // ==UserScript==
    // @name         HOOK 遍历
    // @namespace    http://tampermonkey.net/
    // @version      0.1
    // @description  day day up
    // @author       FY
    // @include      *
    // @grant        none
    // @run-at       document-end
    // ==/UserScript==(function() {'use strict';!function () {'use strict';var source = ['DeCode','EnCode','decodeData','base64decode','md5','decode','btoa','MD5','RSA','AES','CryptoJS','encrypt','strdecode',"encode",'decodeURIComponent','_t','JSON.stringify','String.fromCharCode','fromCharCode'];console.log("开始测试是否有解密函数");let realCtx, realName;function getRealCtx(ctx, funcName) {let parts = funcName.split(".");let realCtx = ctx;for(let i = 0; i < parts.length - 1; i++) {realCtx = realCtx[parts[i]];}return realCtx;}function getRealName(funcName) {let parts = funcName.split(".");return parts[parts.length - 1];}function test(ctx) {for(let i = 0; i < source.length; i++) {let f = source[i];let realCtx = getRealCtx(ctx, f);let realName = getRealName(f);let chars = realCtx[realName];if (chars != undefined){console.log("发现可疑函数:", f);console.log(chars);console.log("---------------------");}else{console.log("未发现:", f);}}}test(window);}();
    })();
    获取返回值、内部调用函数名
     // ==UserScript==
    // @name         HOOK ALL end
    // @namespace    http://tampermonkey.net/
    // @include      *
    // @grant        none
    // @run-at       document-end
    // ==/UserScript==(function() {'use strict';var source = ['DeCode','EnCode','decodeData','base64decode','md5','decode','btoa','MD5','RSA','AES','CryptoJS','encrypt','strdecode',"encode",'decodeURIComponent','_t','JSON.stringify','String.fromCharCode','fromCharCode'];console.log("开始测试是否有解密函数");let realCtx, realName;function getRealCtx(ctx, funcName) {let parts = funcName.split(".");let realCtx = ctx;for(let i = 0; i < parts.length - 1; i++) {realCtx = realCtx[parts[i]];}return realCtx;}function getRealName(funcName) {let parts = funcName.split(".");return parts[parts.length - 1];}function hook(ctx, funcName, level, originFunc) {ctx[funcName] = function(a){console.log("level:" + level + " function:" + funcName,a);console.log(originFunc.toString());console.log(originFunc.toString);debugger;return originFunc(a);};}function test(ctx, level) {for(let i = 0; i < source.length; i++) {let f = source[i];let realCtx = getRealCtx(ctx, f);let realName = getRealName(f);let chars = realCtx[realName];hook(realCtx, realName, level, chars);}}test(window, 1);
    })();
    ///////////////////////////
    // ==UserScript==
    // @name         HOOK 二层函数名 end
    // @namespace    http://tampermonkey.net/
    // @include      *
    // @grant        none
    // @run-at       document-end
    // ==/UserScript==(function() {'use strict';var source = ['decodeData','base64decode','md5','decode','btoa','MD5','RSA','AES','CryptoJS','encrypt','strdecode',"encode",'decodeURIComponent','_t','JSON.stringify','String.fromCharCode','fromCharCode'];console.log("开始测试是否有解密函数");let realCtx, realName;function getRealCtx(ctx, funcName) {let parts = funcName.split(".");let realCtx = ctx;for(let i = 0; i < parts.length - 1; i++) {realCtx = realCtx[parts[i]];}return realCtx;}function getRealName(funcName) {let parts = funcName.split(".");return parts[parts.length - 1];}function hook(ctx, funcName, level, originFunc) {ctx[funcName] = function(a){console.log("level:" + level + " function:" + funcName,a);let regexp = / [\S]*\(.*\)\;/g;let match = originFunc.toString().match(regexp)console.log(match);debugger;return originFunc(a);};}function test(ctx, level) {for(let i = 0; i < source.length; i++) {let f = source[i];let realCtx = getRealCtx(ctx, f);let realName = getRealName(f);let chars = realCtx[realName];hook(realCtx, realName, level, chars);}}test(window, 1);
    })();

可通过tampermonkey、执行js前打断点的方式进行注入

分析

Event、网络、XMLHttpRequest 调用栈

nodejs模拟chrome环境

// npm install jsdom
const jsdom = require('jsdom');
const { JSDOM } = jsdom;const dom = new JSDOM(`<!DOCTYPE html>`);
global.window = dom.window;
global.document = dom.window.document;// 定义 window.atob
global.window.atob = function (encoded) {// 根据 atob 的实际功能实现逻辑,如果只是模拟一个固定值返回,可以这样:return 'decoded_value';
};// 定义 screen 对象
global.screen = { width: 1920, height: 1080 };// 定义 navigator 对象
global.navigator = {userAgent: 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/[your_version] Safari/537.36',
};

处理js代码混淆

查找加密参数:

  1. 通过抓包找到加密参数
  2. 全局搜索参数
  3. 查看网络面板 Initiator
  4. xhr 断点调试
  5. hook
  6. 分析加密逻辑

颜文字、符号加密 JSFuck 等原理:

解决方案:

  1. 直接粘贴到console执行即可获得原js,通过VM查看
  2. 或删除最后一个表情('_');/括号(),添加 toString() 方法在控制台执行
  3. 删除最后的 (); 删除,执行得到代码
  4. 如果不是以()结尾,那么将最后一对括号内部的符号复制并执行,得到原生代码。剩余代码大概率为 eval 函数的编
  5. eval开头的代码,将 eval 改为 alert console.log

平坦化混淆

  1. 全局观察
    是否有 dom操作、纯计算循环体、try-catch异常捕获
  2. 整体分析与载入
    断点定于 while开头、try代码第一行、while整体取出构造原始函数
  3. 构造函数
    通过报错补充函数或数据

通过 AST 增强代码可读性 https://astexplorer.net/ ,npm的 recast 包将js转为AST语法树。平坦流将代码转换为 while-switch-case 格式,通过 consolelog 打印加AST语法分析。

CSS反爬

  1. 字体
    通过font-family指定特殊字体,在页面中不可见的unicode在指定字体中映射到可读字符。爬虫只能爬取到unicode而不是可读字体
    应对:下载woff字体转为tff字体,用字体编辑器确定其字符与unicode间映射关系,替代映射得到正确数据
    有些网站动态生成woff,很难自动化绕开
  2. 背景
    数据(通常是数字)通过雪碧图(Sprite)通过背景偏移展示,抓取时看不到实际值而是图片背景
    下载图片,手动检查获取 background-position 偏移量与实际值间映射关系,爬虫获取偏移值并转化为实际值
  3. 伪类
    不直接展示内容,而是通过伪类content属性展示值。难在获取指定元素的伪类属性
    利用puppeteer或Selenium获取伪类,解析css
    /*
    .valuable-content::before {content: "hiding content"
    }
    */
    const el = document.querySelector('.valuable-content')
    const styles = getComputedStyle(el, 'before')
    console.log(styles.content)
  4. 元素定位
    利用绝对定位(position: absolute)将某个数字或字符将原字符通过一定偏移量替换。替换的字符是随机的,直接抓取获得错误信息。
    计算出替换的元素的偏移量,与被替换元素对比,还原实际值

    const elPr = document.querySelector('.mb-10.b-airfly:nth-child(1).fixprice.prc_wp');
    let strArr = Array.from(elPr.querySelectorAll('b:first-child > i')).map(el => el.innerText);
    // 替换元素
    elPr.querySelectorAll('b:not(:first-child)').forEach(el => {// 偏移const left = Number(getComputedStyle(el).left.replace('px', ''));// 替换strArr[strArr.length + left / 16] = el.innerText;
    });
    console.log(strArr.join(''));
  5. 字符切割
    将字符串用标签分割,内联块级(inline-block)可一行展示,还可混有不显示标签(display:none)
    拼接 innerText 并忽略 display:none 标签

    const elIp = document.querySelector('.ip');
    let str = '';
    const elList = elIp.querySelectorAll('*:not([style="display: none;"])');
    elList.forEach((el, i) => {if (i === elList.length - 1) return;str += el.innerText;
    });
    console.log(str);

应对策略:

  1. 通过调试工具人工查看CSS样式
  2. 判断CSS反爬类型
  3. 思考应对措施,css反爬非常多变,没有固定套路

 

http://www.sczhlp.com/news/62326/

相关文章:

  • 算法面试简单小结
  • 如何使用qq邮箱做网站如何购买域名和空间
  • designer怎么做网站ui交互设计课程培训
  • 网站建设 山西网络营销失败案例及分析
  • 南宁网站制作价格恒峰网站建设问题
  • dedecms网站首页个人博客系统源码
  • 怎么做一个网站怎么样自己做网站可以随便起名字吗
  • 新素材网站网站修改建议
  • 张家口网站设计厦门十大软件公司
  • 专业做网站优化排名什么人适合学ui设计
  • 做网站软件流程服装设计公司英文
  • 做球衣外贸用什么网站竞价网站制作
  • python网站开发现代电子商务网站建设技术
  • 创建一个网站的项目体现项目完成速度因素的专业网站设计定制
  • 做的网站打不开了万网虚拟云空间怎么建设网站
  • 网络营销推广方法认定大将军21六安搜索引擎优化方法
  • 仿一个展示型网站多少钱兰州优化公司哪个好
  • 网站设计的第一步是景县住房和城乡规划建设局网站
  • 宾馆网站制作苏州手机网站搭建
  • 网站标题长度科创纵横 网站建设
  • 互联网网站wordpress 数据备份
  • 外贸营销网站推广wordpress 输出the id
  • 做百度移动网站排名软企业网站推广效果指标分析
  • 宁波网站建设一般多少钱最新网站建设进程
  • 小说网站开发需求新的龙岗网站建设
  • 适合设计制作公司的网站asp远吗健康生活网站开发系统背景
  • 建设网站中心电子产品网站建设 实训报告
  • 盐城网站设计公司wordpress页面调用子页面
  • 怎么做网站首页弹幕做外贸必须有网站吗
  • 建站 备案公司网站维护主要做什么