first commit
This commit is contained in:
428
modules/HCaptchaBypassModule.js
Normal file
428
modules/HCaptchaBypassModule.js
Normal file
@@ -0,0 +1,428 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* HCaptcha Bypass Module (Modularized Version)
|
||||
* ============================================================================
|
||||
* 功能:
|
||||
* 1. 攔截 hCaptcha 的網絡請求 (Fetch & XHR)
|
||||
* 2. 向 hCaptcha HTML 注入自動點擊腳本
|
||||
* 3. 模擬用戶行為繞過檢測
|
||||
* 4. 支援配置熱更新
|
||||
*
|
||||
* 使用方式:
|
||||
* const bypass = new HCaptchaBypassModule(config);
|
||||
* bypass.init();
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
class HCaptchaBypassModule {
|
||||
constructor(config = {}) {
|
||||
// 默認配置
|
||||
this.config = {
|
||||
enabled: true, // 是否啟用繞過
|
||||
autoClick: true, // 是否自動點擊
|
||||
clickDelay: 400, // 點擊檢測間隔 (ms)
|
||||
maxAttempts: 200, // 最大嘗試次數
|
||||
debug: false, // 調試模式
|
||||
settingsKey: '__HCAPTCHA_BYPASS_SETTINGS__', // 全局配置鍵名
|
||||
...config
|
||||
};
|
||||
|
||||
// 運行時狀態
|
||||
this.isInterceptorLoaded = false;
|
||||
this.isInjectorActive = false;
|
||||
this.activeIntervals = [];
|
||||
|
||||
// 綁定方法上下文
|
||||
this.checkSettings = this.checkSettings.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化模塊
|
||||
*/
|
||||
init() {
|
||||
this.log('Initializing HCaptcha Bypass Module...');
|
||||
|
||||
// 1. 將配置暴露到全局 (供 iframe 內部讀取)
|
||||
this.exposeSettings();
|
||||
|
||||
// 2. 啟動攔截器 (劫持網絡請求)
|
||||
this.setupInterceptors();
|
||||
|
||||
// 3. 啟動配置熱更新
|
||||
this.startSettingsWatcher();
|
||||
|
||||
// 4. 監聽跨域消息
|
||||
this.setupMessageListener();
|
||||
|
||||
this.log('Module initialized successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* 銷毀模塊
|
||||
*/
|
||||
destroy() {
|
||||
this.activeIntervals.forEach(id => clearInterval(id));
|
||||
this.activeIntervals = [];
|
||||
this.log('Module destroyed');
|
||||
}
|
||||
|
||||
/**
|
||||
* 暴露配置到全局
|
||||
*/
|
||||
exposeSettings() {
|
||||
window[this.config.settingsKey] = {
|
||||
autoCaptcha: this.config.autoClick,
|
||||
enabled: this.config.enabled,
|
||||
captchaService: 'hCaptcha'
|
||||
};
|
||||
|
||||
// 同時嘗試暴露到父窗口
|
||||
try {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent[this.config.settingsKey] = window[this.config.settingsKey];
|
||||
}
|
||||
} catch (e) {
|
||||
// 跨域錯誤忽略
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置熱更新監聽
|
||||
*/
|
||||
startSettingsWatcher() {
|
||||
const intervalId = setInterval(this.checkSettings, 1000);
|
||||
this.activeIntervals.push(intervalId);
|
||||
}
|
||||
|
||||
checkSettings() {
|
||||
try {
|
||||
const settings = window[this.config.settingsKey];
|
||||
if (settings) {
|
||||
this.config.enabled = settings.enabled ?? this.config.enabled;
|
||||
this.config.autoClick = settings.autoCaptcha ?? this.config.autoClick;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨域消息監聽
|
||||
*/
|
||||
setupMessageListener() {
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data?.type === 'HCAPTCHA_BYPASS_UPDATE_SETTINGS') {
|
||||
Object.assign(this.config, event.data.settings);
|
||||
this.exposeSettings();
|
||||
this.log('Settings updated via postMessage:', event.data.settings);
|
||||
}
|
||||
|
||||
if (event.data?.msg === 'hCaptcha Injector Loaded') {
|
||||
this.log('Injector script successfully loaded in iframe');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ========================================================================
|
||||
* 攔截器部分:劫持網絡請求並注入腳本
|
||||
* ========================================================================
|
||||
*/
|
||||
setupInterceptors() {
|
||||
if (this.isInterceptorLoaded) {
|
||||
this.log('Interceptor already loaded, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isInterceptorLoaded = true;
|
||||
|
||||
// 目標 URL 匹配
|
||||
this.targetUrlPattern = /newassets\.hcaptcha\.com\/captcha\/v1\/[^/]+\/static\/hcaptcha\.html/;
|
||||
|
||||
// 需要替換的代碼模式
|
||||
this.replacementPatterns = [
|
||||
/this\.\$radio\.setAttribute\("aria-checked",\s*!1\)/g,
|
||||
/this\.\$radio\.setAttribute\("aria-checked",\s*false\)/g,
|
||||
/this\.\$radio\.setAttribute\("aria-checked",\s*0\)/g,
|
||||
/setAttribute\("aria-checked",!1\)/g
|
||||
];
|
||||
|
||||
// 注入的 Payload (自動點擊腳本)
|
||||
this.injectedPayload = this.generateInjectorScript();
|
||||
|
||||
// Hook Fetch & XHR
|
||||
this.hookFetch();
|
||||
this.hookXHR();
|
||||
|
||||
this.log('Network interceptors installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成注入腳本
|
||||
*/
|
||||
generateInjectorScript() {
|
||||
const config = this.config;
|
||||
|
||||
// 這裡用模板字符串生成完整的注入代碼
|
||||
// 將之前的 injector 邏輯封裝成字符串
|
||||
return `
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// ===== 配置讀取 =====
|
||||
let isAutoClickEnabled = false;
|
||||
let settingsKey = '${config.settingsKey}';
|
||||
|
||||
function initSettings() {
|
||||
try {
|
||||
if (window.parent && window.parent[settingsKey]) {
|
||||
isAutoClickEnabled = window.parent[settingsKey].autoCaptcha || false;
|
||||
} else if (window.top && window.top[settingsKey]) {
|
||||
isAutoClickEnabled = window.top[settingsKey].autoCaptcha || false;
|
||||
}
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data?.type === 'HCAPTCHA_BYPASS_UPDATE_SETTINGS') {
|
||||
isAutoClickEnabled = event.data.settings?.autoClick || false;
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
isAutoClickEnabled = true; // 跨域限制時默認啟用
|
||||
}
|
||||
}
|
||||
|
||||
initSettings();
|
||||
|
||||
// ===== 核心點擊邏輯 =====
|
||||
const CHECKBOX_SELECTOR = '#checkbox';
|
||||
let attemptCount = 0;
|
||||
let isSolved = false;
|
||||
let clickInterval = null;
|
||||
|
||||
function attemptClick() {
|
||||
if (attemptCount >= ${config.maxAttempts} || isSolved) {
|
||||
if (clickInterval) clearInterval(clickInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
const checkbox = document.querySelector(CHECKBOX_SELECTOR);
|
||||
if (!checkbox) {
|
||||
attemptCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
const isChecked = checkbox.getAttribute('aria-checked');
|
||||
const isVisible = checkbox.offsetParent !== null;
|
||||
|
||||
if (isVisible && isChecked === 'false' && isAutoClickEnabled) {
|
||||
try {
|
||||
checkbox.click();
|
||||
console.log('[HCaptcha Bypass] Checkbox clicked');
|
||||
|
||||
setTimeout(() => {
|
||||
const newState = checkbox.getAttribute('aria-checked');
|
||||
if (newState === 'true') {
|
||||
isSolved = true;
|
||||
if (clickInterval) clearInterval(clickInterval);
|
||||
|
||||
// 通知父窗口
|
||||
window.parent.postMessage({
|
||||
type: 'HCAPTCHA_BYPASS_SOLVED',
|
||||
msg: 'hCaptcha solved successfully'
|
||||
}, '*');
|
||||
}
|
||||
}, 1000);
|
||||
} catch (e) {
|
||||
console.error('[HCaptcha Bypass] Click failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
attemptCount++;
|
||||
}
|
||||
|
||||
// 啟動點擊循環
|
||||
clickInterval = setInterval(attemptClick, ${config.clickDelay});
|
||||
|
||||
// 冗餘觸發
|
||||
setTimeout(attemptClick, 500);
|
||||
setTimeout(attemptClick, 1500);
|
||||
setTimeout(attemptClick, 3000);
|
||||
|
||||
// 通知父窗口注入成功
|
||||
setTimeout(() => {
|
||||
window.parent.postMessage({
|
||||
type: 'HCAPTCHA_BYPASS_INJECTOR_LOADED',
|
||||
msg: 'hCaptcha Injector Loaded'
|
||||
}, '*');
|
||||
}, 2000);
|
||||
|
||||
})();
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook Fetch API
|
||||
*/
|
||||
hookFetch() {
|
||||
const originalFetch = window.fetch;
|
||||
const self = this;
|
||||
|
||||
window.fetch = function(...args) {
|
||||
const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;
|
||||
|
||||
// 檢查是否為目標 URL
|
||||
const isTarget = url && self.targetUrlPattern.test(url);
|
||||
|
||||
if (!isTarget || !self.config.enabled) {
|
||||
return originalFetch.apply(this, args);
|
||||
}
|
||||
|
||||
self.log('Fetch intercepted:', url);
|
||||
|
||||
// 攔截並修改響應
|
||||
return originalFetch.apply(this, args).then(response => {
|
||||
return response.text().then(html => {
|
||||
const modifiedHtml = self.injectScript(html);
|
||||
return new Response(modifiedHtml, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook XMLHttpRequest
|
||||
*/
|
||||
hookXHR() {
|
||||
const originalOpen = XMLHttpRequest.prototype.open;
|
||||
const originalSend = XMLHttpRequest.prototype.send;
|
||||
const self = this;
|
||||
|
||||
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
|
||||
this._interceptedUrl = url;
|
||||
return originalOpen.apply(this, [method, url, ...rest]);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.send = function(...args) {
|
||||
const url = this._interceptedUrl;
|
||||
const isTarget = url && self.targetUrlPattern.test(url);
|
||||
|
||||
if (!isTarget || !self.config.enabled) {
|
||||
return originalSend.apply(this, args);
|
||||
}
|
||||
|
||||
self.log('XHR intercepted:', url);
|
||||
|
||||
const originalOnReadyStateChange = this.onreadystatechange;
|
||||
|
||||
this.onreadystatechange = function() {
|
||||
if (this.readyState === 4 && this.status === 200) {
|
||||
let html = this.responseText;
|
||||
if (html) {
|
||||
html = self.injectScript(html);
|
||||
|
||||
Object.defineProperty(this, 'responseText', {
|
||||
get: () => html,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'response', {
|
||||
get: () => html,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (originalOnReadyStateChange) {
|
||||
originalOnReadyStateChange.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return originalSend.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入腳本到 HTML
|
||||
*/
|
||||
injectScript(html) {
|
||||
let modified = html;
|
||||
|
||||
// 1. 替換檢測邏輯 (將 false 改為 true)
|
||||
this.replacementPatterns.forEach(pattern => {
|
||||
modified = modified.replace(pattern, match => {
|
||||
return match.replace(/!1|false|0/g, '!0'); // !0 === true
|
||||
});
|
||||
});
|
||||
|
||||
// 2. 注入自動點擊腳本
|
||||
const scriptTag = `<script>${this.injectedPayload}</script>`;
|
||||
|
||||
if (modified.includes('</body>')) {
|
||||
modified = modified.replace('</body>', scriptTag + '</body>');
|
||||
} else if (modified.includes('</html>')) {
|
||||
modified = modified.replace('</html>', scriptTag + '</html>');
|
||||
} else {
|
||||
modified += scriptTag;
|
||||
}
|
||||
|
||||
this.log('Script injected into hCaptcha HTML');
|
||||
return modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* 日誌輸出
|
||||
*/
|
||||
log(...args) {
|
||||
if (this.config.debug) {
|
||||
console.log('[HCaptcha Bypass Module]', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig) {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.exposeSettings();
|
||||
this.log('Config updated:', this.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手動觸發點擊 (從外部調用)
|
||||
*/
|
||||
triggerClick() {
|
||||
const iframes = document.querySelectorAll('iframe[src*="hcaptcha.com"]');
|
||||
|
||||
iframes.forEach(iframe => {
|
||||
try {
|
||||
iframe.contentWindow.postMessage({
|
||||
type: 'HCAPTCHA_BYPASS_TRIGGER_CLICK'
|
||||
}, '*');
|
||||
} catch (e) {
|
||||
this.log('Cannot access iframe:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ============================================================================
|
||||
* 全局 API 暴露
|
||||
* ============================================================================
|
||||
*/
|
||||
window.HCaptchaBypassModule = HCaptchaBypassModule;
|
||||
|
||||
// 自動初始化選項
|
||||
if (typeof HCAPTCHA_BYPASS_AUTO_INIT !== 'undefined' && HCAPTCHA_BYPASS_AUTO_INIT) {
|
||||
const instance = new HCaptchaBypassModule({
|
||||
enabled: true,
|
||||
autoClick: true,
|
||||
debug: true
|
||||
});
|
||||
instance.init();
|
||||
window.__hcaptchaBypass = instance;
|
||||
}
|
||||
Reference in New Issue
Block a user