18using System.Collections.Generic;
22using System.Text.RegularExpressions;
23using System.Threading.Tasks;
34 internal static class ChromePatcher
41 static readonly
string[] chromedriverSourceMatches =
new[]
49 ,
"STALE_ELEMENT_REFERENCE"
51 ,
"shadow root is detached from the current frame"
52 ,
"stale element not found in the current frame"
62 public static void PatchChromeDriver(IBrowser browser)
64 if (
null == ((Browser)browser).BrowserSettings.DriverLocation)
66 ((Browser)browser).BrowserSettings.DriverLocation = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Substring(6);
68 string filePath = FileHelper.EnsureDirectoryEndsWithBackslash(((Browser)browser).BrowserSettings.DriverLocation) +
$@"{GPAL.GPALSettings.ChromeDriverFilename}";
70 long fileSize =
new FileInfo(filePath).Length;
71 int totalSteps = 6 + chromedriverSourceMatches.Length;
72 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Stealth patching [{filePath}] ({fileSize / 1024 / 1024} MB, {totalSteps} steps)", browser, Enums.GPALObjectType.Browser);
74 if (
true == CheckIfPatched(filePath))
76 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"[{filePath}] already patched.", browser, Enums.GPALObjectType.Browser);
81 string backupPath = filePath +
".org";
82 if (File.Exists(backupPath))
84 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"[{backupPath}] backup already exists. Deleting.", browser, Enums.GPALObjectType.Browser);
85 File.Delete(backupPath);
88 File.Copy(filePath, backupPath);
89 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"[{backupPath}] backup created.", browser, Enums.GPALObjectType.Browser);
91 Action<int, int, string> onProgress = (step, total, description) =>
92 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Patch step [{step}/{total}]: {description}", browser, Enums.GPALObjectType.Browser);
95 if (PatchExe(filePath, onProgress))
97 if (
true == CheckIfPatched(filePath))
98 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"[{filePath}] patched successfully.", browser, Enums.GPALObjectType.Browser);
100 GPAL.PublishSimpleEvent(Enums.GPALEventType.ERROR, $
"[{filePath}] patching failed.", browser, Enums.GPALObjectType.Browser);
103 GPAL.PublishSimpleEvent(Enums.GPALEventType.ERROR, $
"[{filePath}] patching failed.", browser, Enums.GPALObjectType.Browser);
112 static bool PatchExe(
string executablePath, Action<int, int, string> onProgress)
115 byte[] content = File.ReadAllBytes(executablePath);
118 content = ApplyRegexPatch(content, onProgress);
121 File.WriteAllBytes(executablePath, content);
136 static byte[] ApplyRegexPatch(
byte[] content, Action<int, int, string> onProgress)
138 int total = 6 + chromedriverSourceMatches.Length;
141 onProgress(++step, total,
"cdc variable assignments");
142 content = ApplyPattern(content,
143 @"window\.cdc_[a-zA-Z0-9]{22,}_(Array|Promise|Symbol|Object|Proxy|JSON) = window\.(Array|Promise|Symbol|Object|Proxy|JSON);",
144 match =>
new string(
'\n', match.Length));
146 onProgress(++step, total,
"cdc variable guards");
147 content = ApplyPattern(content,
148 @"window\.cdc_[a-zA-Z0-9]{22,}_(Array|Promise|Symbol|Object|Proxy|JSON) \|\|",
149 match =>
new string(
'\n', match.Length));
151 onProgress(++step, total,
"cdc Window references");
152 content = ApplyPattern(content,
153 @"window\.cdc_[a-zA-Z0-9]{22,}_Window",
154 match =>
"window.Window");
156 onProgress(++step, total,
"WindowProxy binding");
157 content = ApplyPattern(content,
158 @"let WindowProxy = window\.cdc_[a-zA-Z0-9]{22,}_Window(?:\s*\|\|\s*window\.Window;)?",
159 match =>
"let WindowProxy = window.Window");
161 onProgress(++step, total,
"item.cdc Window references");
162 content = ApplyPattern(content,
163 @"item\.cdc_[a-zA-Z0-9]{22,}_Window",
164 match =>
"item.Window");
166 onProgress(++step, total,
"console.log stamp");
167 byte[] logMessageBytes = Encoding.ASCII.GetBytes($
"{{console.log(\"GPAL {GPAL.Version} chromedriver patch!\")}}");
168 content = ApplyPattern(content,
169 @"console\.log\(([^)]+)\)",
172 byte[] newTargetBytes =
new byte[match.Length];
173 Array.Copy(logMessageBytes, 0, newTargetBytes, 0, logMessageBytes.Length);
174 for (
int i = logMessageBytes.Length; i < match.Length; i++)
176 newTargetBytes[i] = (byte)
' ';
178 return Encoding.ASCII.GetString(newTargetBytes);
181 foreach (var originalString
in chromedriverSourceMatches)
183 onProgress(++step, total, $
"mutate string [{originalString}]");
184 string alteredString = AlterString(originalString);
185 content = ApplyPattern(content, Regex.Escape(originalString), _ => alteredString);
198 static string AlterString(
string input)
200 if (
string.IsNullOrEmpty(input))
return input;
203 char firstChar = input[0];
204 char shiftedChar = (char)(firstChar + 1);
207 if (
char.IsLetter(firstChar))
209 if (
char.IsLower(firstChar) && shiftedChar >
'z') shiftedChar =
'a';
210 if (
char.IsUpper(firstChar) && shiftedChar >
'Z') shiftedChar =
'A';
213 else if (
char.IsDigit(firstChar))
215 if (shiftedChar >
'9') shiftedChar =
'0';
219 return shiftedChar + input.Substring(1);
232 static byte[] ApplyPattern(
byte[] content,
string pattern, Func<Match, string> replacementFunc)
235 Regex regex =
new Regex(pattern, RegexOptions.Compiled);
236 string contentStr = Encoding.UTF8.GetString(content);
237 MatchCollection matches = regex.Matches(contentStr);
239 foreach (Match match
in matches)
241 byte[] targetBytes = Encoding.UTF8.GetBytes(match.Value);
242 string replacement = replacementFunc(match);
243 byte[] replacementBytes = Encoding.UTF8.GetBytes(replacement);
246 byte[] paddedReplacementBytes = PadByteArray(replacementBytes, targetBytes.Length);
248 content = ReplaceBytes(content, targetBytes, paddedReplacementBytes);
262 static byte[] PadByteArray(
byte[] byteArray,
int desiredLength)
264 if (byteArray.Length >= desiredLength)
267 byte[] paddedArray =
new byte[desiredLength];
268 byteArray.CopyTo(paddedArray, 0);
269 for (
int i = byteArray.Length; i < desiredLength; i++)
271 paddedArray[i] = (byte)
' ';
284 static byte[] ReplaceBytes(
byte[] content,
byte[] oldBytes,
byte[] newBytes)
286 using (MemoryStream stream =
new MemoryStream())
290 while (index <= content.Length - oldBytes.Length)
294 for (
int j = 0; j < oldBytes.Length; j++)
296 if (content[index + j] != oldBytes[j])
305 stream.Write(newBytes, 0, newBytes.Length);
306 index += oldBytes.Length;
310 stream.WriteByte(content[index]);
315 stream.Write(content, index, content.Length - index);
316 return stream.ToArray();
326 static bool CheckIfPatched(
string filePath)
328 byte[] content = File.ReadAllBytes(filePath);
329 string contentStr = Encoding.UTF8.GetString(content);
332 if (Regex.IsMatch(contentStr,
@"console\.log\(\"".*?chromedriver patch!\""\)"))