<aside> 😀 最近遇到个任务要请求akamai的网站,然后正确一些开源指纹请求很快就无效了。然后要急。所以想到之前编译了浏览器可以改变指纹的。想着用插件配合中转先顶一下。

</aside>

📝 主旨内容

插件

配合插件转发和浏览器魔改。

编写插件配置rpc+魔改的浏览器实现小规模的akamai请求。首先我们需要让插件可以代理我们的请求。实现不同ip实现。

document.addEventListener('DOMContentLoaded', function() {
  const proxyHost =document.getElementById('proxyHost');
  const proxyPort =document.getElementById('proxyPort');
  const proxyUsername =document.getElementById('proxyUsername');
  const proxyPassword =document.getElementById('proxyPassword');
  const setProxyBtn =document.getElementById('setProxy');
  const disableProxyBtn =document.getElementById('disableProxy');
  const useDefaultBtn =document.getElementById('useDefault');
  const status =document.getElementById('status');

  // 默认配置
  const defaultConfig = {
    host: 'xxx.xxx.cn',
    port: '10086',
    username: 'xxxxxx',
    password: 'xxxxxx'
  };

  // 加载当前设置
  loadCurrentSettings();

  // 设置代理
  setProxyBtn.addEventListener('click', function() {
    const settings = {
      enabled: true,
      host: proxyHost.value.trim(),
      port: proxyPort.value.trim(),
      username: proxyUsername.value.trim(),
      password: proxyPassword.value.trim()
    };

    if (!settings.host || !settings.port) {
      alert('请输入代理主机和端口');
      return;
    }

    // 验证端口号
    const portNum = parseInt(settings.port);
    if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
      alert('请输入有效的端口号 (1-65535)');
      return;
    }

    setProxyBtn.disabled = true;
    setProxyBtn.textContent = '设置中...';

chrome.runtime.sendMessage({
      action: 'setProxy',
      settings: settings
    }, function(response) {
      setProxyBtn.disabled = false;
      setProxyBtn.textContent = '设置代理';

      if (chrome.runtime.lastError) {
        alert('设置代理失败: ' +chrome.runtime.lastError.message);
        return;
      }

      if (response && response.success) {
        updateStatus(true);
        alert('代理设置成功!');
      } else {
        alert('代理设置失败,请检查设置');
      }
    });
  });

  // 禁用代理
  disableProxyBtn.addEventListener('click', function() {
    disableProxyBtn.disabled = true;
    disableProxyBtn.textContent = '禁用中...';

chrome.runtime.sendMessage({ action: 'disableProxy' }, function(response) {
      disableProxyBtn.disabled = false;
      disableProxyBtn.textContent = '禁用代理';

      if (chrome.runtime.lastError) {
        alert('禁用代理失败: ' +chrome.runtime.lastError.message);
        return;
      }

      if (response && response.success) {
        updateStatus(false);
        alert('代理已禁用!');
      } else {
        alert('禁用代理失败');
      }
    });
  });

  // 使用默认配置
  useDefaultBtn.addEventListener('click', function() {
    proxyHost.value = defaultConfig.host;
    proxyPort.value = defaultConfig.port;
    proxyUsername.value = defaultConfig.username;
    proxyPassword.value = defaultConfig.password;
  });

  function loadCurrentSettings() {
chrome.runtime.sendMessage({ action: 'getProxy' }, function(response) {
      if (chrome.runtime.lastError) {
console.error('Error loading settings:',chrome.runtime.lastError);
        return;
      }

      if (response && response.settings) {
        const settings = response.settings;
        proxyHost.value = settings.host || '';
        proxyPort.value = settings.port || '';
        proxyUsername.value = settings.username || '';
        proxyPassword.value = settings.password || '';
        updateStatus(settings.enabled);
      } else {
        updateStatus(false);
      }
    });
  }

  function updateStatus(enabled) {
    if (enabled) {
      status.textContent = '代理已启用';
      status.className = 'status enabled';
    } else {
      status.textContent = '代理已禁用';
      status.className = 'status disabled';
    }
  }
});

image.png

然后就是搞一个ws协议搭建一个服务。让我们请求这个接口的时候可以把请求使用fetch请求实现。这里你需要注意的是fetch是没办法设置代理的。所有我们才有前面哪一步把插件设置一个局部代理让他可以实现请求。

中转的rpc的代码:

// 等待页面完全加载
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWebSocket);
} else {
  initWebSocket();
}

function initWebSocket() {
  // 检查是否已经初始化过
  if (window.proxyWebSocketInitialized) {
    return;
  }
window.proxyWebSocketInitialized = true;

console.log('Initializing WebSocket connection...');

  const socket = new WebSocket('ws://localhost:8765');

  socket.onopen = function() {
console.log('Connected to server');
  };

  socket.onmessage = async function(event) {
console.log('Message from server: ', event.data);
    try {
      // 解析服务器发送的消息
      const message =JSON.parse(event.data);

      if (message.type === 'fetch_request') {
        // 处理fetch请求
        await handleFetchRequest(message, socket);
      } else if (message.type === 'eval_code') {
        // 执行代码
        const result = eval(message.code);
        socket.send(JSON.stringify({
          success: true,
          result: result,
          requestId: message.requestId
        }));
      }
    } catch (error) {
console.error('Error processing message:', error);
      socket.send(JSON.stringify({
        success: false,
        error: error.toString(),
        requestId:message.requestId || null
      }));
    }
  };

  socket.onclose = function() {
console.log('Disconnected from server');
    // 重连机制
    setTimeout(() => {
window.proxyWebSocketInitialized = false;
      initWebSocket();
    }, 3000);
  };

  socket.onerror = function(error) {
console.error('WebSocket error: ', error);
  };

  // 处理fetch请求的函数
  async function handleFetchRequest(message, socket) {
    try {
      const { url, options = {}, requestId } = message;

      // 设置默认选项以避免携带cookies和实现跨域
      const fetchOptions = {
        ...options,
        // credentials: 'omit', // 不携带cookies
        credentials: 'include', // 不携带cookies
        mode: 'no-cors', // 改为no-cors模式,避免CORS检查
        cache: 'no-cache', // 不使用缓存
        headers: {
          ...options.headers,
          'User-Agent':navigator.userAgent
        }
      };

console.log('Fetching URL:', url);
      const response = await fetch(url, fetchOptions);

      // 获取响应基本信息
      const responseData = {
        status: response.status,
        statusText: response.statusText,
        headers:Object.fromEntries(response.headers.entries()),
        url: response.url,
        ok: response.ok
      };

      // 根据Content-Type决定如何处理响应体 - 只读取一次
      const contentType = response.headers.get('content-type') || '';
      try {
        if (contentType.includes('application/json')) {
          responseData.data = await response.json();
          responseData.dataType = 'json';
        } else if (contentType.includes('text/')) {
          responseData.data = await response.text();
          responseData.dataType = 'text';
        } else {
          // 对于二进制数据,转换为base64
          const arrayBuffer = await response.arrayBuffer();
          const uint8Array = newUint8Array(arrayBuffer);
          responseData.data = btoa(String.fromCharCode.apply(null, uint8Array));
          responseData.dataType = 'binary';
        }
      } catch (e) {
        // no-cors模式下可能无法读取响应体
        responseData.data = '';
        responseData.dataType = 'opaque';
        responseData.note = 'Response body not accessible in no-cors mode';
      }

      // 发送响应回服务器
      socket.send(JSON.stringify({
        success: true,
        response: responseData,
        requestId: requestId
      }));

    } catch (error) {
console.error('Fetch error:', error);
      socket.send(JSON.stringify({
        success: false,
        error: error.toString(),
        requestId: message.requestId
      }));
    }
  }

  // 将socket暴露到全局,方便调试
window.proxyWebSocket = socket;
}

这个随便网上抄一下就行。然后就可以使用python进行请求然后通过浏览器进行转发。

image.png

当然他还是不支持太高的规模小规模还是可以实现。主要适用于akmai等一些指纹限制。还可以用于一些可能短时间解决不了的问题。先顶一下的情况。

效果

都弄完后。配置完成后我们看一下实际的效果。

多次请求中转。

image.png

再次请求中转

image.png

在请求中转