欢迎光临
我们一直在努力

从阿里140日志看前端反爬:手把手教你补全Window对象缺失属性

从阿里140日志看前端反爬:手把手教你补全Window对象缺失属性

最近在分析一些大型互联网平台的前端安全策略时,我经常遇到一个让人头疼的问题:明明代码逻辑看起来没问题,环境也模拟得挺像,但就是过不了检测。后来我发现,很多问题的根源都藏在那些看似不起眼的日志里。特别是像阿里140这样的环境检测日志,它就像一面镜子,清晰地照出了我们模拟环境时遗漏的每一个细节。今天,我就带大家深入剖析这类日志,看看如何通过补全Window对象的缺失属性,打造一个更逼真的浏览器环境。

如果你做过JavaScript逆向或者前端安全研究,肯定对“补环境”这个词不陌生。简单来说,就是要在Node.js这样的非浏览器环境中,模拟出一个完整的浏览器运行环境,让目标网站的检测代码误以为它正在真实的浏览器里执行。这听起来简单,做起来却处处是坑。很多朋友可能已经尝试过用jsdom或者puppeteer,但面对越来越严格的环境检测,这些通用方案往往力不从心。这时候,我们就需要更精细化的手段——手动补环境。

阿里140的环境检测日志给了我很大的启发。它详细记录了代码执行过程中对Window对象各个属性的访问情况,哪些属性被读取了,哪些属性返回了undefined,哪些函数的toString结果被检查了……这些信息就像一张藏宝图,指引着我们该往哪个方向努力。通过分析这些日志,我们不仅能知道要补什么,还能知道该怎么补,补到什么程度才算“像”。

1. 环境检测日志:你的调试指南针

刚开始接触环境检测日志时,我也有点懵。满屏的get、set、toString,还有各种undefined,看得人眼花缭乱。但静下心来分析,你会发现这里面有很清晰的规律。

1.1 日志的结构化解读

先来看一段典型的日志片段:

方法: get 对象: window 属性: UA_Opt 属性值: undefined 属性值类型: undefined
方法: set 对象: window 属性: UA_Opt 属性值: {} 属性值类型: object
方法: get 对象: window 属性: __acjs_awsc_140 属性值: undefined 属性值类型: undefined

这段日志告诉我们几个关键信息:

  • 访问模式:是get(读取)还是set(设置)
  • 访问对象:通常是window,也可能是document、navigator等
  • 属性名称:具体被访问的属性名
  • 属性值:当前环境下该属性的值
  • 值类型:属性的数据类型
  • 从上面的例子可以看出,检测代码首先尝试读取window.UA_Opt,发现是undefined,然后立即设置了一个空对象{}。这说明检测代码会先探测属性是否存在,如果不存在就自己创建。但更关键的是后面的__acjs_awsc_140,它被读取后仍然是undefined,而且没有后续的set操作——这意味着这个属性在真实浏览器中应该存在,但在我们的模拟环境里缺失了。

    1.2 属性访问的三种模式

    根据我的经验,环境检测对属性的访问主要有三种模式:

    模式一:存在性检查

    // 检测代码会这样检查
    if (window.someProperty) {
    // 属性存在时的逻辑
    } else {
    // 属性不存在时的逻辑,可能触发反爬
    }

    模式二:类型检查

    // 不仅检查存在,还检查类型
    if (typeof window.someProperty === \’function\’) {
    // 如果是函数,还会检查toString
    console.log(window.someProperty.toString());
    }

    模式三:原型链深度检查

    // 有些检测会深入检查原型链
    if (window.someProperty instanceof SomeConstructor) {
    // 检查构造函数
    }

    在实际的阿里140日志中,我看到了大量对toString()方法的调用检查:

    方法: toString 函数: getBattery 结果: function getBattery() { [native code] }
    方法: toString 函数: Element 结果: function Element() { [native code] }

    这告诉我们一个重要的细节:函数必须返回正确的[native code]标识。如果你补的函数返回的是自定义的实现,toString()结果不对,很容易被识别出来。

    1.3 实战:从日志到补环境清单

    让我们实际分析一段日志,提取出需要补的环境属性。假设我们有如下日志片段:

    方法
    对象
    属性
    属性值
    需要补全
    get window UA_Opt undefined
    get window __acjs undefined
    get window _uab_module undefined
    get navigator getBattery function 否(但需确保toString正确)
    get window chrome {runtime: {…}}
    get chrome history undefined
    get window mozPaintCount undefined 是(Firefox特有)

    从这个表格可以看出,我们需要补全的属性分为几类:

  • 自定义属性:如UA_Opt、__acjs等,这些是网站自己注入的
  • 浏览器特有属性:如mozPaintCount(Firefox)、webkitRTCPeerConnection(WebKit)
  • 插件相关属性:如chrome对象及其子属性
  • 事件相关属性:如attachEvent(IE)、DeviceMotionEvent等
  • 注意:不是所有undefined都需要补。有些属性在某些浏览器中本来就是undefined,比如IE的attachEvent在Chrome中就是undefined。关键是要根据UserAgent来匹配正确的属性集。

    2. Window对象补全:从基础到高级

    补环境的核心就是补Window对象,因为几乎所有浏览器API都挂载在Window或其子对象上。但Window对象太庞大了,直接全部补全不现实,也没必要。我们需要的是按需补全。

    2.1 基础属性补全策略

    我通常会把Window属性分为几个优先级:

    第一优先级:高频检测属性 这些属性在几乎所有的环境检测中都会被检查,必须优先补全。

    // 基础Window属性补全
    const basicWindowProperties = {
    // 自定义属性
    UA_Opt: {},
    __acjs: 1,
    __acjs_awsc_140: { getUA: function() {}, isReadyForSC: function() {} },
    _uab_module: 1,

    // 浏览器对象
    chrome: {
    runtime: {},
    csi: function() {},
    loadTimes: function() {},
    // 注意:chrome.history在普通页面中通常是undefined
    history: undefined,
    bookmarks: undefined,
    cookies: undefined,
    system: undefined
    },

    // 移动端特有
    WeixinJSBridge: undefined, // 微信环境
    __wxjs_environment: undefined,
    AlipayJSBridge: undefined, // 支付宝环境

    // 调试相关(通常为undefined)
    Debug: undefined,
    __BROWSERTOOLS_DOMEXPLORER_ADDED: undefined,
    __IE_DEVTOOLBAR_CONSOLE_EVAL_RESULT: undefined
    };

    第二优先级:浏览器引擎特有属性 根据UserAgent决定补哪些。

    // 根据UA判断浏览器类型
    function getBrowserType(ua) {
    if (ua.includes(\’Firefox\’)) return \’firefox\’;
    if (ua.includes(\’Chrome\’)) return \’chrome\’;
    if (ua.includes(\’Safari\’)) return \’safari\’;
    if (ua.includes(\’MSIE\’) || ua.includes(\’Trident\’)) return \’ie\’;
    return \’chrome\’; // 默认
    }

    // 浏览器特有属性
    const browserSpecificProperties = {
    firefox: {
    mozPaintCount: 0,
    mozInnerScreenX: 0,
    InstallTrigger: undefined
    },
    chrome: {
    chrome: { /* 已在上面的basicWindowProperties中定义 */ },
    webkitStorageInfo: undefined
    },
    safari: {
    safari: undefined,
    webkitRTCPeerConnection: function() { [native code] }
    },
    ie: {
    ActiveXObject: function() { [native code] },
    attachEvent: function() { [native code] },
    ScriptEngineBuildVersion: 0,
    ScriptEngineMajorVersion: 0,
    ScriptEngineMinorVersion: 0
    }
    };

    2.2 函数属性的特殊处理

    函数属性的补全要特别小心,因为检测代码不仅会检查函数是否存在,还会检查:

  • 函数类型:必须是function
  • toString结果:必须包含[native code]
  • 原型链:构造函数要有正确的原型
  • 这里有个技巧:我们可以用Proxy来拦截对函数的访问,确保toString()返回正确的结果。

    // 创建原生函数代理
    function createNativeFunctionProxy(originalName, customImplementation = null) {
    return new Proxy(customImplementation || function() {}, {
    get(target, prop) {
    if (prop === \’toString\’) {
    return () => `function ${originalName}() { [native code] }`;
    }
    return Reflect.get(target, prop);
    },
    apply(target, thisArg, argumentsList) {
    // 如果有自定义实现,使用自定义实现
    if (customImplementation) {
    return customImplementation.apply(thisArg, argumentsList);
    }
    // 否则返回一个合理值
    return undefined;
    }
    });
    }

    // 应用函数代理
    window.Bluetooth = createNativeFunctionProxy(\’Bluetooth\’);
    window.BluetoothDevice = createNativeFunctionProxy(\’BluetoothDevice\’);
    window.BluetoothUUID = createNativeFunctionProxy(\’BluetoothUUID\’);

    // 对于有实际用途的函数,可以提供简单实现
    window.getBattery = createNativeFunctionProxy(\’getBattery\’, async function() {
    return {
    charging: false,
    chargingTime: Infinity,
    dischargingTime: Infinity,
    level: 1.0,
    onchargingchange: null,
    onchargingtimechange: null,
    ondischargingtimechange: null,
    onlevelchange: null
    };
    });

    2.3 对象属性的深度补全

    有些属性不是简单的值或函数,而是复杂的对象。这时候需要递归补全。

    // 深度补全对象属性
    function deepPatchObject(target, source) {
    for (const key in source) {
    if (source.hasOwnProperty(key)) {
    if (typeof source[key] === \’object\’ && source[key] !== null) {
    if (!target[key] || typeof target[key] !== \’object\’) {
    target[key] = {};
    }
    deepPatchObject(target[key], source[key]);
    } else {
    // 特殊处理:如果target已有该属性且不是undefined,保留原值
    if (target[key] === undefined) {
    target[key] = source[key];
    }
    }
    }
    }
    return target;
    }

    // 补全performance对象
    const performancePatch = {
    timing: {

    赞(0)
    未经允许不得转载:171主机测评 » 从阿里140日志看前端反爬:手把手教你补全Window对象缺失属性
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址