<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>代码分析 on Tao</title><link>https://743v45.github.io/di4urp/categories/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/</link><description>Recent content in 代码分析 on Tao</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><managingEditor>di4urp@gmail.com (taevas)</managingEditor><webMaster>di4urp@gmail.com (taevas)</webMaster><lastBuildDate>Thu, 12 Mar 2026 17:35:00 +0800</lastBuildDate><atom:link href="https://743v45.github.io/di4urp/categories/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/index.xml" rel="self" type="application/rss+xml"/><item><title>【留档】Puppeteer Stealth 插件 JS 代码分析</title><link>https://743v45.github.io/di4urp/posts/stealth-js-analysis/</link><pubDate>Thu, 12 Mar 2026 17:35:00 +0800</pubDate><author>di4urp@gmail.com (taevas)</author><guid>https://743v45.github.io/di4urp/posts/stealth-js-analysis/</guid><description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;在分析 &lt;a href="https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth"&gt;puppeteer-extra-plugin-stealth&lt;/a&gt; 项目时，发现其核心代码被打包成一段高度压缩的 JavaScript。本文是对该代码的详细解析。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;版本：v2.7.3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="代码结构概览"&gt;代码结构概览&lt;/h2&gt;
&lt;p&gt;这段压缩 JS 采用立即执行函数 (IIFE) 结构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(({&lt;span style="color:#a6e22e"&gt;_utilsFns&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_mainFunction&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_args&lt;/span&gt;}) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;utils&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Object.&lt;span style="color:#a6e22e"&gt;fromEntries&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Object.&lt;span style="color:#a6e22e"&gt;entries&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;_utilsFns&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;(([&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;]) =&amp;gt; [&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;, eval(&lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;)])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;utils&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; eval(&lt;span style="color:#a6e22e"&gt;_mainFunction&lt;/span&gt;)(&lt;span style="color:#a6e22e"&gt;utils&lt;/span&gt;, ...&lt;span style="color:#a6e22e"&gt;_args&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})(...)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;执行流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;动态加载 &lt;code&gt;_utilsFns&lt;/code&gt; 中的工具函数（通过 &lt;code&gt;eval&lt;/code&gt; 执行字符串形式的代码）&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;utils.init()&lt;/code&gt; 初始化&lt;/li&gt;
&lt;li&gt;执行 &lt;code&gt;_mainFunction&lt;/code&gt; 主函数，传入工具函数和参数&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="核心工具函数"&gt;核心工具函数&lt;/h2&gt;
&lt;h3 id="1-stripproxyfromerrors"&gt;1. stripProxyFromErrors&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：&lt;/strong&gt; 从错误堆栈中移除 Proxy 相关的调用栈，防止被检测。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;stripProxyFromErrors&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {}) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;newHandler&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; { ...&lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 拦截每个 trap
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;traps&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;forEach&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;trap&lt;/span&gt; =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;newHandler&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;trap&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; () {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;trap&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;apply&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;arguments&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 清理错误堆栈中的 Proxy 痕迹
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stack&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;stripWithAnchor&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stack&lt;/span&gt;) &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;stripWithBlacklist&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stack&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;newHandler&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;检测对抗原理：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当 Proxy 内部抛出错误时，堆栈会显示类似：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;TypeError: xxx
at Reflect.get (native)
at Object.newHandler.&amp;lt;computed&amp;gt; [as get] (...)
at realUserCode (...)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这段代码会自动移除前两行，只保留用户代码的堆栈。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="2-patchtostring"&gt;2. patchToString&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：&lt;/strong&gt; 修改 &lt;code&gt;Function.prototype.toString&lt;/code&gt;，让代理函数返回 &lt;code&gt;[native code]&lt;/code&gt; 形式。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;patchToString&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;obj&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;str&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;apply&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;target&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;obj&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;str&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;utils&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;makeNativeString&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;obj&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;target&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;call&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;toStringProxy&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Proxy(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Function.&lt;span style="color:#a6e22e"&gt;prototype&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toString&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;utils&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stripProxyFromErrors&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;utils&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;replaceProperty&lt;/span&gt;(Function.&lt;span style="color:#a6e22e"&gt;prototype&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;toString&amp;#39;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;toStringProxy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;效果：&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 正常情况，Proxy 函数会被暴露：
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;someProxyFunction&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toString&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// =&amp;gt; &amp;#34;function () { [native code] }&amp;#34; &amp;lt;-- 这是假的
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 但如果被检测：
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;someProxyFunction&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toString&lt;/span&gt;() &lt;span style="color:#f92672"&gt;===&lt;/span&gt; Function.&lt;span style="color:#a6e22e"&gt;prototype&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toString&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;call&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;someProxyFunction&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// =&amp;gt; true，看起来像原生函数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="3-makenativestring"&gt;3. makeNativeString&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：&lt;/strong&gt; 生成原生函数字符串。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;makeNativeString&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 基于缓存的 native toString 模板
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;utils&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;cache&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;nativeToStringStr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;replace&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;toString&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 结果示例：
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// &amp;#34;function getAttribute() { [native code] }&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="4-replaceproperty"&gt;4. replaceProperty&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：&lt;/strong&gt; 安全地重写对象属性描述符。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;replaceProperty&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;obj&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;propName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;descriptorOverrides&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {}) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Object.&lt;span style="color:#a6e22e"&gt;defineProperty&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;obj&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;propName&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 保留原有描述符
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...(Object.&lt;span style="color:#a6e22e"&gt;getOwnPropertyDescriptor&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;obj&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;propName&lt;/span&gt;) &lt;span style="color:#f92672"&gt;||&lt;/span&gt; {}),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 应用覆盖
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ...&lt;span style="color:#a6e22e"&gt;descriptorOverrides&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h3 id="5-replacewithproxy--mockwithproxy"&gt;5. replaceWithProxy / mockWithProxy&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：&lt;/strong&gt; 创建代理并替换原对象。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;replaceWithProxy&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;obj&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;propName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;original&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;obj&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;propName&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;proxy&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Proxy(&lt;span style="color:#a6e22e"&gt;original&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;utils&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stripProxyFromErrors&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;utils&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;replaceProperty&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;obj&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;propName&lt;/span&gt;, { &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;proxy&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;proxy&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="被修改的浏览器-api"&gt;被修改的浏览器 API&lt;/h2&gt;
&lt;p&gt;代码针对以下检测点进行伪装：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;检测点&lt;/th&gt;
&lt;th&gt;原始问题&lt;/th&gt;
&lt;th&gt;修复方式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;navigator.webdriver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Puppeteer 会将其设为 &lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;重写为 &lt;code&gt;undefined&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chrome.runtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无 Chrome 扩展 API&lt;/td&gt;
&lt;td&gt;注入假的 &lt;code&gt;chrome&lt;/code&gt; 对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;navigator.plugins&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;插件列表为空&lt;/td&gt;
&lt;td&gt;伪造常见插件数组&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;navigator.languages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;语言设置异常&lt;/td&gt;
&lt;td&gt;确保返回合理数组&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;navigator.hardwareConcurrency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPU 核心数可疑&lt;/td&gt;
&lt;td&gt;可配置伪装值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;navigator.permissions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;权限 API 行为异常&lt;/td&gt;
&lt;td&gt;修复 &lt;code&gt;query()&lt;/code&gt; 返回值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iframe.contentWindow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跨窗口检测不一致&lt;/td&gt;
&lt;td&gt;统一代理处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;window.outerWidth/outerHeight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无头浏览器特征&lt;/td&gt;
&lt;td&gt;修复窗口尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="工作原理总结"&gt;工作原理总结&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│ 网站检测 Bot 的方式 │
├─────────────────────────────────────────────────────────────┤
│ 1. navigator.webdriver === true │
│ 2. Function.toString 返回非原生代码 │
│ 3. 错误堆栈中出现 Proxy 相关调用 │
│ 4. 浏览器特征缺失 (plugins, chrome 等) │
│ 5. Canvas/WebGL 指纹异常 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Stealth 插件的对策 │
├─────────────────────────────────────────────────────────────┤
│ 1. 重写属性，返回期望值 │
│ 2. 代理 Function.prototype.toString │
│ 3. 清理错误堆栈中的 Proxy 痕迹 │
│ 4. 注入伪造的浏览器特征 │
│ 5. 统一各窗口的 API 行为 │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="检测对抗示例"&gt;检测对抗示例&lt;/h2&gt;
&lt;h3 id="示例-1webdriver-属性"&gt;示例 1：webdriver 属性&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 网站检测代码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;navigator&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;webdriver&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;检测到自动化脚本！&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Stealth 修复后
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Object.&lt;span style="color:#a6e22e"&gt;defineProperty&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;navigator&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;webdriver&amp;#39;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; () =&amp;gt; &lt;span style="color:#66d9ef"&gt;undefined&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 现在 navigator.webdriver === undefined
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="示例-2tostring-检测"&gt;示例 2：toString 检测&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 网站检测代码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;navigator&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getAttribute&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;fn&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;toString&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;includes&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;[native code]&amp;#39;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 正常
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;函数被篡改！&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Stealth 修复后
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 即使 getAttribute 被 Proxy 包装，toString 仍返回：
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// &amp;#34;function getAttribute() { [native code] }&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="项目结构"&gt;项目结构&lt;/h2&gt;
&lt;p&gt;这段压缩代码来自 &lt;a href="https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth"&gt;puppeteer-extra-plugin-stealth&lt;/a&gt;，其源码结构：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;puppeteer-extra-plugin-stealth/
├── src/
│ └── index.js # 入口，打包生成压缩代码
└── evasion/ # 各检测规避模块
├── chrome.app/
├── chrome.csi/
├── chrome.loadTimes/
├── chrome.runtime/
├── iframe.contentWindow/
├── media.codecs/
├── navigator.hardwareConcurrency/
├── navigator.languages/
├── navigator.permissions/
├── navigator.plugins/
├── navigator.webdriver/
├── user-agent-override/
├── utils.js # 工具函数（本文分析的核心）
└── ...
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;这段压缩 JS 是 &lt;strong&gt;puppeteer-extra-plugin-stealth&lt;/strong&gt; 的核心代码，通过以下技术手段隐藏浏览器自动化痕迹：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Proxy 包装&lt;/strong&gt;：拦截浏览器原生 API 调用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;堆栈清洗&lt;/strong&gt;：自动移除错误堆栈中的 Proxy 痕迹&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;toString 伪装&lt;/strong&gt;：让代理函数看起来像原生代码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性注入&lt;/strong&gt;：伪造缺失的浏览器特征&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些技术使得 Puppeteer/Playwright 脚本在网站眼中看起来像真实用户操作，常用于爬虫、自动化测试、数据采集等场景。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth"&gt;puppeteer-extra-plugin-stealth GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/berstend/puppeteer-friendly-chrome"&gt;Bot detection techniques&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>