18using System.Collections.Generic;
22using System.Text.RegularExpressions;
23using System.Threading.Tasks;
35 internal static class EdgePatcher
42 static readonly
string[] edgedriverSourceMatches =
new[]
50 ,
"STALE_ELEMENT_REFERENCE"
52 ,
"shadow root is detached from the current frame"
53 ,
"stale element not found in the current frame"
63 public static void PatchEdgeDriver(IBrowser browser)
65 if (
null == ((Browser)browser).BrowserSettings.DriverLocation)
67 ((Browser)browser).BrowserSettings.DriverLocation = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Substring(6);
69 string filePath = FileHelper.EnsureDirectoryEndsWithBackslash(((Browser)browser).BrowserSettings.DriverLocation) +
$@"{GPAL.GPALSettings.EdgeDriverFilename}";
71 long fileSize =
new FileInfo(filePath).Length;
72 int totalSteps = 6 + edgedriverSourceMatches.Length;
73 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Stealth patching [{filePath}] ({fileSize / 1024 / 1024} MB, {totalSteps} steps)", browser, Enums.GPALObjectType.Browser);
75 if (
true == CheckIfPatched(filePath))
77 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"[{filePath}] already patched.", browser, Enums.GPALObjectType.Browser);
82 string backupPath = filePath +
".org";
83 if (File.Exists(backupPath))
85 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"[{backupPath}] backup already exists. Deleting.", browser, Enums.GPALObjectType.Browser);
86 File.Delete(backupPath);
89 File.Copy(filePath, backupPath);
90 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"[{backupPath}] backup created.", browser, Enums.GPALObjectType.Browser);
92 Action<int, int, string> onProgress = (step, total, description) =>
93 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"Patch step [{step}/{total}]: {description}", browser, Enums.GPALObjectType.Browser);
96 if (PatchExe(filePath, onProgress))
98 if (
true == CheckIfPatched(filePath))
99 GPAL.PublishSimpleEvent(Enums.GPALEventType.INFO, $
"[{filePath}] patched successfully.", browser, Enums.GPALObjectType.Browser);
101 GPAL.PublishSimpleEvent(Enums.GPALEventType.ERROR, $
"[{filePath}] patching failed.", browser, Enums.GPALObjectType.Browser);
104 GPAL.PublishSimpleEvent(Enums.GPALEventType.ERROR, $
"[{filePath}] patching failed.", browser, Enums.GPALObjectType.Browser);
113 static bool PatchExe(
string executablePath, Action<int, int, string> onProgress)
116 byte[] content = File.ReadAllBytes(executablePath);
119 content = ApplyRegexPatch(content, onProgress);
122 File.WriteAllBytes(executablePath, content);
137 static byte[] ApplyRegexPatch(
byte[] content, Action<int, int, string> onProgress)
139 int total = 6 + edgedriverSourceMatches.Length;
142 onProgress(++step, total,
"cdc variable assignments");
143 content = ApplyPattern(content,
144 @"window\.cdc_[a-zA-Z0-9]{22,}_(Array|Promise|Symbol|Object|Proxy|JSON) = window\.(Array|Promise|Symbol|Object|Proxy|JSON);",
145 match =>
new string(
'\n', match.Length));
147 onProgress(++step, total,
"cdc variable guards");
148 content = ApplyPattern(content,
149 @"window\.cdc_[a-zA-Z0-9]{22,}_(Array|Promise|Symbol|Object|Proxy|JSON) \|\|",
150 match =>
new string(
'\n', match.Length));
152 onProgress(++step, total,
"cdc Window references");
153 content = ApplyPattern(content,
154 @"window\.cdc_[a-zA-Z0-9]{22,}_Window",
155 match =>
"window.Window");
157 onProgress(++step, total,
"WindowProxy binding");
158 content = ApplyPattern(content,
159 @"let WindowProxy = window\.cdc_[a-zA-Z0-9]{22,}_Window(?:\s*\|\|\s*window\.Window;)?",
160 match =>
"let WindowProxy = window.Window");
162 onProgress(++step, total,
"item.cdc Window references");
163 content = ApplyPattern(content,
164 @"item\.cdc_[a-zA-Z0-9]{22,}_Window",
165 match =>
"item.Window");
167 onProgress(++step, total,
"console.log stamp");
168 byte[] logMessageBytes = Encoding.ASCII.GetBytes($
"{{console.log(\"GPAL {GPAL.Version} msedgedriver patch!\")}}");
169 content = ApplyPattern(content,
170 @"console\.log\(([^)]+)\)",
173 byte[] newTargetBytes =
new byte[match.Length];
174 Array.Copy(logMessageBytes, 0, newTargetBytes, 0, logMessageBytes.Length);
175 for (
int i = logMessageBytes.Length; i < match.Length; i++)
177 newTargetBytes[i] = (byte)
' ';
179 return Encoding.ASCII.GetString(newTargetBytes);
182 foreach (var originalString
in edgedriverSourceMatches)
184 onProgress(++step, total, $
"string [{originalString}]");
185 string alteredString = AlterString(originalString);
186 content = ApplyPattern(content, Regex.Escape(originalString), _ => alteredString);
199 static string AlterString(
string input)
201 if (
string.IsNullOrEmpty(input))
return input;
204 char firstChar = input[0];
205 char shiftedChar = (char)(firstChar + 1);
208 if (
char.IsLetter(firstChar))
210 if (
char.IsLower(firstChar) && shiftedChar >
'z') shiftedChar =
'a';
211 if (
char.IsUpper(firstChar) && shiftedChar >
'Z') shiftedChar =
'A';
214 else if (
char.IsDigit(firstChar))
216 if (shiftedChar >
'9') shiftedChar =
'0';
220 return shiftedChar + input.Substring(1);
233 static byte[] ApplyPattern(
byte[] content,
string pattern, Func<Match, string> replacementFunc)
236 Regex regex =
new Regex(pattern, RegexOptions.Compiled);
237 string contentStr = Encoding.UTF8.GetString(content);
238 MatchCollection matches = regex.Matches(contentStr);
240 foreach (Match match
in matches)
242 byte[] targetBytes = Encoding.UTF8.GetBytes(match.Value);
243 string replacement = replacementFunc(match);
244 byte[] replacementBytes = Encoding.UTF8.GetBytes(replacement);
247 byte[] paddedReplacementBytes = PadByteArray(replacementBytes, targetBytes.Length);
249 content = ReplaceBytes(content, targetBytes, paddedReplacementBytes);
263 static byte[] PadByteArray(
byte[] byteArray,
int desiredLength)
265 if (byteArray.Length >= desiredLength)
268 byte[] paddedArray =
new byte[desiredLength];
269 byteArray.CopyTo(paddedArray, 0);
270 for (
int i = byteArray.Length; i < desiredLength; i++)
272 paddedArray[i] = (byte)
' ';
285 static byte[] ReplaceBytes(
byte[] content,
byte[] oldBytes,
byte[] newBytes)
287 using (MemoryStream stream =
new MemoryStream())
291 while (index <= content.Length - oldBytes.Length)
295 for (
int j = 0; j < oldBytes.Length; j++)
297 if (content[index + j] != oldBytes[j])
306 stream.Write(newBytes, 0, newBytes.Length);
307 index += oldBytes.Length;
311 stream.WriteByte(content[index]);
316 stream.Write(content, index, content.Length - index);
317 return stream.ToArray();
327 static bool CheckIfPatched(
string filePath)
329 byte[] content = File.ReadAllBytes(filePath);
330 string contentStr = Encoding.UTF8.GetString(content);
333 if (Regex.IsMatch(contentStr,
@"console\.log\(\"".*?msedgedriver patch!\""\)"))