chromate-rb 0.0.2.pre → 0.0.3.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +20 -2
- data/Rakefile +2 -2
- data/docker_root/app.rb +6 -11
- data/docs/BOT_BROWSER.md +74 -0
- data/docs/browser.md +20 -55
- data/docs/client.md +126 -0
- data/docs/element.md +77 -1
- data/docs/elements/checkbox.md +69 -0
- data/docs/elements/radio.md +57 -0
- data/lib/bot_browser/downloader.rb +38 -26
- data/lib/bot_browser/installer.rb +27 -9
- data/lib/bot_browser.rb +5 -1
- data/lib/chromate/actions/dom.rb +24 -35
- data/lib/chromate/actions/navigate.rb +3 -0
- data/lib/chromate/actions/screenshot.rb +52 -14
- data/lib/chromate/actions/stealth.rb +38 -23
- data/lib/chromate/binary.rb +83 -0
- data/lib/chromate/browser.rb +70 -26
- data/lib/chromate/c_logger.rb +1 -0
- data/lib/chromate/client.rb +25 -8
- data/lib/chromate/configuration.rb +2 -2
- data/lib/chromate/element.rb +62 -9
- data/lib/chromate/elements/checkbox.rb +40 -0
- data/lib/chromate/elements/option.rb +43 -0
- data/lib/chromate/elements/radio.rb +37 -0
- data/lib/chromate/elements/select.rb +10 -18
- data/lib/chromate/elements/tags.rb +29 -0
- data/lib/chromate/exceptions.rb +2 -0
- data/lib/chromate/files/agents.json +11 -0
- data/lib/chromate/files/stealth.js +199 -0
- data/lib/chromate/hardwares/keyboard_controller.rb +11 -0
- data/lib/chromate/hardwares/mouse_controller.rb +8 -0
- data/lib/chromate/hardwares.rb +4 -4
- data/lib/chromate/user_agent.rb +14 -12
- data/lib/chromate/version.rb +1 -1
- data/results/bot.png +0 -0
- data/results/brotector.png +0 -0
- data/results/cloudflare.png +0 -0
- data/results/pixelscan.png +0 -0
- metadata +27 -2
@@ -0,0 +1,199 @@
|
|
1
|
+
/**
|
2
|
+
* Script de Stealth pour masquer l'automatisation
|
3
|
+
*
|
4
|
+
* Ce script est destiné à être injecté via la commande
|
5
|
+
* Page.addScriptToEvaluateOnNewDocument afin qu'il s'exécute
|
6
|
+
* avant le chargement du contenu de la page.
|
7
|
+
*
|
8
|
+
* Il redéfinit plusieurs propriétés du navigateur pour
|
9
|
+
* réduire les indices susceptibles d'indiquer qu'une automatisation est en cours.
|
10
|
+
*/
|
11
|
+
(() => {
|
12
|
+
'use strict';
|
13
|
+
|
14
|
+
// 1) Masquer navigator.webdriver
|
15
|
+
(function removeWebDriverProperty() {
|
16
|
+
// 1) Récupérer le prototype de Navigator
|
17
|
+
const proto = Object.getPrototypeOf(navigator);
|
18
|
+
|
19
|
+
// Vérifier si la propriété webdriver existe sur le prototype
|
20
|
+
if ('webdriver' in proto) {
|
21
|
+
try {
|
22
|
+
// 2) Tenter de supprimer la propriété si elle est configurable
|
23
|
+
const webdriverDescriptor = Object.getOwnPropertyDescriptor(proto, 'webdriver');
|
24
|
+
if (webdriverDescriptor && webdriverDescriptor.configurable) {
|
25
|
+
delete proto.webdriver;
|
26
|
+
} else {
|
27
|
+
// 3) Sinon, on essaye de la redéfinir pour qu'elle retourne undefined et ne soit pas énumérable
|
28
|
+
Object.defineProperty(proto, 'webdriver', {
|
29
|
+
get: () => undefined,
|
30
|
+
configurable: false, // on la rend non-configurable pour éviter d'autres re-déclarations
|
31
|
+
enumerable: false
|
32
|
+
});
|
33
|
+
}
|
34
|
+
} catch (err) {
|
35
|
+
// 4) En cas d'échec (non-configurable), on peut tenter un hack sur l'opérateur 'in'
|
36
|
+
// => ATTENTION : ceci peut avoir des effets de bord dans d'autres scripts
|
37
|
+
patchInOperatorForNavigator('webdriver');
|
38
|
+
}
|
39
|
+
} else {
|
40
|
+
// Si la propriété n'existe pas sur le prototype, vérifier si elle existe directement sur navigator
|
41
|
+
if ('webdriver' in navigator) {
|
42
|
+
try {
|
43
|
+
delete navigator.webdriver;
|
44
|
+
} catch (err) {
|
45
|
+
patchInOperatorForNavigator('webdriver');
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Hack optionnel pour empêcher `'webdriver' in navigator` de renvoyer true.
|
52
|
+
* On redéfinit la méthode hasOwnProperty / l'opérateur in pour l'objet navigator.
|
53
|
+
* Cette approche peut avoir des effets de bord, donc à utiliser en dernier recours.
|
54
|
+
*/
|
55
|
+
function patchInOperatorForNavigator(propName) {
|
56
|
+
const originalHasOwn = Object.prototype.hasOwnProperty;
|
57
|
+
Object.prototype.hasOwnProperty = function (property) {
|
58
|
+
// Si c'est navigator et qu'on teste la propriété 'webdriver', on la cache
|
59
|
+
if (this === navigator && property === propName) {
|
60
|
+
return false;
|
61
|
+
}
|
62
|
+
return originalHasOwn.call(this, property);
|
63
|
+
};
|
64
|
+
|
65
|
+
// Variante plus radicale : proxyfier l'objet navigator pour intercepter l'opérateur 'in'.
|
66
|
+
// (Non présenté ici, car encore plus invasif.)
|
67
|
+
}
|
68
|
+
})();
|
69
|
+
|
70
|
+
// 2) Surcharger navigator.permissions.query (pour ne pas renvoyer "default")
|
71
|
+
if (navigator.permissions && navigator.permissions.query) {
|
72
|
+
const originalQuery = navigator.permissions.query;
|
73
|
+
navigator.permissions.query = (params) => {
|
74
|
+
// Ex. : si on interroge les notifications, on renvoie "granted"
|
75
|
+
if (params.name === 'notifications') {
|
76
|
+
return Promise.resolve({ state: 'granted' });
|
77
|
+
}
|
78
|
+
// Pour les autres, on tente la requête d'origine, sinon "granted"
|
79
|
+
return originalQuery(params).catch(() => ({ state: 'granted' }));
|
80
|
+
};
|
81
|
+
}
|
82
|
+
|
83
|
+
// 3) Créer un PluginArray réaliste
|
84
|
+
// - On réutilise le prototype existant pour ne pas éveiller de soupçons
|
85
|
+
const pluginArrayProto = Object.getPrototypeOf(navigator.plugins);
|
86
|
+
function FakePlugin(name, description, filename) {
|
87
|
+
this.name = name;
|
88
|
+
this.description = description;
|
89
|
+
this.filename = filename;
|
90
|
+
}
|
91
|
+
// On pointe vers Plugin.prototype pour se comporter comme un plugin "réel"
|
92
|
+
FakePlugin.prototype = Plugin.prototype;
|
93
|
+
|
94
|
+
const fakePlugins = [
|
95
|
+
new FakePlugin('Chrome PDF Plugin', 'Portable Document Format', 'internal-pdf-viewer'),
|
96
|
+
new FakePlugin('Chrome PDF Viewer', '', 'mhjfbmdgcfjbbpaeojofohoefgiehjai')
|
97
|
+
];
|
98
|
+
|
99
|
+
Object.defineProperty(navigator, 'plugins', {
|
100
|
+
get() {
|
101
|
+
const pluginArray = Object.create(pluginArrayProto);
|
102
|
+
// On copie les faux plugins dans l’objet
|
103
|
+
for (let i = 0; i < fakePlugins.length; i++) {
|
104
|
+
pluginArray[i] = fakePlugins[i];
|
105
|
+
}
|
106
|
+
pluginArray.length = fakePlugins.length;
|
107
|
+
return pluginArray;
|
108
|
+
}
|
109
|
+
});
|
110
|
+
|
111
|
+
// 4) Aligner navigator.languages avec vos entêtes Accept-Language
|
112
|
+
Object.defineProperty(navigator, 'languages', {
|
113
|
+
get: () => ['en-US', 'en'],
|
114
|
+
configurable: true
|
115
|
+
});
|
116
|
+
|
117
|
+
// 5) Override de getContext pour forcer un contexte WebGL factice
|
118
|
+
const originalGetContext = HTMLCanvasElement.prototype.getContext;
|
119
|
+
HTMLCanvasElement.prototype.getContext = function (type, ...args) {
|
120
|
+
if (['webgl', 'experimental-webgl', 'webgl2'].includes(type)) {
|
121
|
+
// Tenter d'obtenir le contexte natif (au cas où)
|
122
|
+
let ctx = originalGetContext.apply(this, [type, ...args]);
|
123
|
+
if (ctx) {
|
124
|
+
// Si un contexte natif est obtenu, retourner ce contexte
|
125
|
+
return ctx;
|
126
|
+
}
|
127
|
+
console.log("No native WebGL context found, returning fake context");
|
128
|
+
// Forcer la création d'un contexte factice
|
129
|
+
const proto = (window.WebGLRenderingContext && window.WebGLRenderingContext.prototype) || {};
|
130
|
+
const fakeContext = Object.create(proto);
|
131
|
+
|
132
|
+
fakeContext.getParameter = function (param) {
|
133
|
+
if (param === 37445) return 'Intel Inc.';
|
134
|
+
if (param === 37446) return 'Intel Iris OpenGL Engine';
|
135
|
+
return null;
|
136
|
+
};
|
137
|
+
fakeContext.getSupportedExtensions = function () {
|
138
|
+
return ['WEBGL_debug_renderer_info'];
|
139
|
+
};
|
140
|
+
fakeContext.getExtension = function (name) {
|
141
|
+
if (name === 'WEBGL_debug_renderer_info') {
|
142
|
+
return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 };
|
143
|
+
}
|
144
|
+
return null;
|
145
|
+
};
|
146
|
+
|
147
|
+
// Ajout de stubs pour d'autres méthodes WebGL essentielles
|
148
|
+
fakeContext.clear = function () { };
|
149
|
+
fakeContext.clearColor = function () { };
|
150
|
+
fakeContext.viewport = function () { };
|
151
|
+
fakeContext.createShader = function () { return {}; };
|
152
|
+
fakeContext.shaderSource = function () { };
|
153
|
+
fakeContext.compileShader = function () { };
|
154
|
+
fakeContext.createProgram = function () { return {}; };
|
155
|
+
fakeContext.attachShader = function () { };
|
156
|
+
fakeContext.linkProgram = function () { };
|
157
|
+
fakeContext.useProgram = function () { };
|
158
|
+
|
159
|
+
return fakeContext;
|
160
|
+
}
|
161
|
+
return originalGetContext.apply(this, [type, ...args]);
|
162
|
+
};
|
163
|
+
|
164
|
+
// 6) Optionnel : Override de toDataURL pour renvoyer une image fixe
|
165
|
+
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
166
|
+
HTMLCanvasElement.prototype.toDataURL = function (...args) {
|
167
|
+
const gl = this.getContext('webgl') || this.getContext('experimental-webgl') || this.getContext('webgl2');
|
168
|
+
if (gl) {
|
169
|
+
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=";
|
170
|
+
}
|
171
|
+
return originalToDataURL.apply(this, args);
|
172
|
+
};
|
173
|
+
|
174
|
+
// 7) Correction des dimensions d’images
|
175
|
+
Object.defineProperty(HTMLImageElement.prototype, 'naturalWidth', {
|
176
|
+
get() { return 128; }
|
177
|
+
});
|
178
|
+
Object.defineProperty(HTMLImageElement.prototype, 'naturalHeight', {
|
179
|
+
get() { return 128; }
|
180
|
+
});
|
181
|
+
|
182
|
+
// 8) Randomisation pour varier l'injection
|
183
|
+
(function () {
|
184
|
+
const randomSuffix = Math.random().toString(36).substring(2);
|
185
|
+
document.documentElement.setAttribute('data-stealth', randomSuffix);
|
186
|
+
})();
|
187
|
+
|
188
|
+
// 9) Masquer la modification des fonctions natives
|
189
|
+
(function () {
|
190
|
+
const originalToString = Function.prototype.toString;
|
191
|
+
Function.prototype.toString = function () {
|
192
|
+
if (this === navigator.permissions.query) {
|
193
|
+
return "function query() { [native code] }";
|
194
|
+
}
|
195
|
+
return originalToString.apply(this, arguments);
|
196
|
+
};
|
197
|
+
})();
|
198
|
+
|
199
|
+
})();
|
@@ -13,6 +13,15 @@ module Chromate
|
|
13
13
|
@type_interval = rand(0.05..0.1)
|
14
14
|
end
|
15
15
|
|
16
|
+
# @param [Chromate::Element] element
|
17
|
+
# @return [self]
|
18
|
+
def set_element(element) # rubocop:disable Naming/AccessorMethodName
|
19
|
+
@element = element
|
20
|
+
@type_interval = rand(0.05..0.1)
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
16
25
|
# @param [String] key
|
17
26
|
# @return [self]
|
18
27
|
def press_key(_key)
|
@@ -23,6 +32,8 @@ module Chromate
|
|
23
32
|
# @return [self]
|
24
33
|
def type(text)
|
25
34
|
text.each_char do |char|
|
35
|
+
sleep(rand(0.01..0.05)) if rand(10).zero?
|
36
|
+
|
26
37
|
press_key(char)
|
27
38
|
sleep(@type_interval)
|
28
39
|
end
|
@@ -19,6 +19,14 @@ module Chromate
|
|
19
19
|
@client = client
|
20
20
|
end
|
21
21
|
|
22
|
+
# @param [Chromate::Element] element
|
23
|
+
# @return [self]
|
24
|
+
def set_element(element) # rubocop:disable Naming/AccessorMethodName
|
25
|
+
@element = element
|
26
|
+
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
22
30
|
# @return [Hash]
|
23
31
|
def mouse_position
|
24
32
|
@@mouse_position ||= { x: 0, y: 0 } # rubocop:disable Style/ClassVars
|
data/lib/chromate/hardwares.rb
CHANGED
@@ -19,18 +19,18 @@ module Chromate
|
|
19
19
|
browser = args[:client].browser
|
20
20
|
if browser.options[:native_control]
|
21
21
|
if mac?
|
22
|
-
Chromate::CLogger.log('
|
22
|
+
Chromate::CLogger.log('👨🏼💻🐁 Loading MacOs mouse controller')
|
23
23
|
require 'chromate/hardwares/mouses/mac_os_controller'
|
24
24
|
return Mouses::MacOsController.new(**args)
|
25
25
|
end
|
26
26
|
if linux?
|
27
|
-
Chromate::CLogger.log('
|
27
|
+
Chromate::CLogger.log('👨🏼💻🐁 Loading Linux mouse controller')
|
28
28
|
require 'chromate/hardwares/mouses/linux_controller'
|
29
29
|
return Mouses::LinuxController.new(**args)
|
30
30
|
end
|
31
31
|
raise 'Native mouse controller is not supported on Windows' if windows?
|
32
32
|
else
|
33
|
-
Chromate::CLogger.log('
|
33
|
+
Chromate::CLogger.log('🤖🐁 Loading Virtual mouse controller')
|
34
34
|
Mouses::VirtualController.new(**args)
|
35
35
|
end
|
36
36
|
end
|
@@ -41,7 +41,7 @@ module Chromate
|
|
41
41
|
# @option args [Chromate::Element] :element
|
42
42
|
# @return [Chromate::Hardwares::KeyboardController]
|
43
43
|
def keyboard(**args)
|
44
|
-
Chromate::CLogger.log('
|
44
|
+
Chromate::CLogger.log('🤖⌨️ Loading Virtual keyboard controller')
|
45
45
|
Keyboards::VirtualController.new(**args)
|
46
46
|
end
|
47
47
|
module_function :keyboard
|
data/lib/chromate/user_agent.rb
CHANGED
@@ -30,29 +30,31 @@ module Chromate
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
def self.
|
34
|
-
case
|
35
|
-
when
|
36
|
-
'
|
37
|
-
when
|
38
|
-
'
|
39
|
-
when 'Windows'
|
40
|
-
'10.0'
|
33
|
+
def self.arch
|
34
|
+
case RUBY_PLATFORM
|
35
|
+
when /x64-mingw32/
|
36
|
+
'x64'
|
37
|
+
when /x86_64-mingw32/
|
38
|
+
'x86_64'
|
41
39
|
else
|
42
|
-
|
40
|
+
'x86'
|
43
41
|
end
|
44
42
|
end
|
45
43
|
|
44
|
+
def self.agents
|
45
|
+
@agents ||= JSON.parse(File.read(File.join(__dir__, 'files/agents.json')))
|
46
|
+
end
|
47
|
+
|
46
48
|
def self.linux_agent
|
47
|
-
'
|
49
|
+
agents['linux'].sample
|
48
50
|
end
|
49
51
|
|
50
52
|
def self.mac_agent
|
51
|
-
'
|
53
|
+
agents['mac'].sample
|
52
54
|
end
|
53
55
|
|
54
56
|
def self.windows_agent
|
55
|
-
'
|
57
|
+
agents['windows'].sample
|
56
58
|
end
|
57
59
|
end
|
58
60
|
end
|
data/lib/chromate/version.rb
CHANGED
data/results/bot.png
CHANGED
Binary file
|
data/results/brotector.png
CHANGED
Binary file
|
data/results/cloudflare.png
CHANGED
Binary file
|
data/results/pixelscan.png
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chromate-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eth3rnit3
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-02-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.17.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: user_agent_parser
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.18.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.18.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: websocket-client-simple
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -61,9 +75,13 @@ files:
|
|
61
75
|
- dockerfiles/Dockerfile
|
62
76
|
- dockerfiles/README.md
|
63
77
|
- dockerfiles/docker-entrypoint.sh
|
78
|
+
- docs/BOT_BROWSER.md
|
64
79
|
- docs/README.md
|
65
80
|
- docs/browser.md
|
81
|
+
- docs/client.md
|
66
82
|
- docs/element.md
|
83
|
+
- docs/elements/checkbox.md
|
84
|
+
- docs/elements/radio.md
|
67
85
|
- lib/bot_browser.rb
|
68
86
|
- lib/bot_browser/downloader.rb
|
69
87
|
- lib/bot_browser/installer.rb
|
@@ -72,13 +90,20 @@ files:
|
|
72
90
|
- lib/chromate/actions/navigate.rb
|
73
91
|
- lib/chromate/actions/screenshot.rb
|
74
92
|
- lib/chromate/actions/stealth.rb
|
93
|
+
- lib/chromate/binary.rb
|
75
94
|
- lib/chromate/browser.rb
|
76
95
|
- lib/chromate/c_logger.rb
|
77
96
|
- lib/chromate/client.rb
|
78
97
|
- lib/chromate/configuration.rb
|
79
98
|
- lib/chromate/element.rb
|
99
|
+
- lib/chromate/elements/checkbox.rb
|
100
|
+
- lib/chromate/elements/option.rb
|
101
|
+
- lib/chromate/elements/radio.rb
|
80
102
|
- lib/chromate/elements/select.rb
|
103
|
+
- lib/chromate/elements/tags.rb
|
81
104
|
- lib/chromate/exceptions.rb
|
105
|
+
- lib/chromate/files/agents.json
|
106
|
+
- lib/chromate/files/stealth.js
|
82
107
|
- lib/chromate/hardwares.rb
|
83
108
|
- lib/chromate/hardwares/keyboard_controller.rb
|
84
109
|
- lib/chromate/hardwares/keyboards/virtual_controller.rb
|