ultrafunkamsterdam / undetected-chromedriver

Custom Selenium Chromedriver | Zero-Config | Passes ALL bot mitigation systems (like Distil / Imperva/ Datadadome / CloudFlare IUAM)
https://github.com/UltrafunkAmsterdam/undetected-chromedriver
GNU General Public License v3.0
9.57k stars 1.14k forks source link

[FOUND WORKAROUND] Fetches x86-64 on ARM64 (OSError: [Errno 8] Exec format error) #917

Open trueToastedCode opened 1 year ago

trueToastedCode commented 1 year ago

Platform is Debian on ARM64 (using Apple Silicon VM)

Python 3.10.8 (main, Nov  4 2022, 09:21:25) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import undetected_chromedriver as uc
>>> self.driver = uc.Chrome(use_subprocess=True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/anonymous/_/bcontrol/venv/lib/python3.10/site-packages/undetected_chromedriver/__init__.py", line 429, in __init__
    super(Chrome, self).__init__(
  File "/home/anonymous/_/bcontrol/venv/lib/python3.10/site-packages/selenium/webdriver/chrome/webdriver.py", line 81, in __init__
    super().__init__(
  File "/home/anonymous/_/bcontrol/venv/lib/python3.10/site-packages/selenium/webdriver/chromium/webdriver.py", line 103, in __init__
    self.service.start()
  File "/home/anonymous/_/bcontrol/venv/lib/python3.10/site-packages/selenium/webdriver/common/service.py", line 91, in start
    self._start_process(self.path)
  File "/home/anonymous/_/bcontrol/venv/lib/python3.10/site-packages/selenium/webdriver/common/service.py", line 189, in _start_process
    self.process = subprocess.Popen(
  File "/usr/lib/python3.10/subprocess.py", line 971, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.10/subprocess.py", line 1847, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
OSError: [Errno 8] Exec format error: '/home/anonymous/.local/share/undetected_chromedriver/ffa9280a50b582c7_chromedriver'
trueToastedCode commented 1 year ago

It tries to execute x86-64 chromium although its on ARM64 $ file /home/anonymous/.local/share/undetected_chromedriver/2fd8036c81a1359c_chromedriver /home/anonymous/.local/share/undetected_chromedriver/2fd8036c81a1359c_chromedriver: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5334671b5a2412bfdfe18309fb12af91c933542d, stripped

trueToastedCode commented 1 year ago

OK so the problem is, that the google archive does actually only provide x86-64 Linux Chromedriver. The solution is to download a custom one and patch it so it doesn't get detected.

  1. uname -r > for me 6.0.0-kali3-arm64
  2. Download a fitting chromedriver_... from https://github.com/electron/electron/releases and extract the chromedriver
  3. nano patch.py and write the following (its the current chromedriver patching script copy-pasted from this library) and execute it
    
    #!/usr/bin/python
    import io
    import re
    import random
    import string

executable_path = './chromedriver'

def random_cdc(): cdc = random.choices(string.asciilowercase, k=26) cdc[-6:-4] = map(str.upper, cdc[-6:-4]) cdc[2] = cdc[0] cdc[3] = "" return "".join(cdc).encode()

def patch_binary(): """ Patches the ChromeDriver binary :return: False on failure, binary name on success """ linect = 0 replacement = random_cdc() with io.open(executablepath, "r+b") as fh: for line in iter(lambda: fh.readline(), b""): if b"cdc" in line: fh.seek(-len(line), 1) newline = re.sub(b"cdc_.{22}", replacement, line) fh.write(newline) linect += 1 return linect

if name == 'main': patch_binary()

5. `chromedriver` is patched now so we can proceed with the library where we just have to define a custom chromedriver... for example

import undetected_chromedriver as uc driver_executable_path = './chromedrive' driver = uc.Chrome(driver_executable_path=driver_executable_path) driver.get("https://www.google.com")



It would be nice if this library would support the chromedriver from electron for Arm64. This platform is actually used by quite a lot of people ... e.g. me who is using a M1 chip with Linux virtual machine or people using a raspberry pi. 
montgomeryb commented 1 year ago

I had a similar situation (issue 1109) running on an old pi. Is is possible to set environment variables or a config file that the default parameters when the class is invoked? In my case I am using another project and have to modify that code passing in my executable path and version.

trueToastedCode commented 10 months ago

OK so the problem is, that the google archive does actually only provide x86-64 Linux Chromedriver. The solution is to download a custom one and patch it so it doesn't get detected.

  1. uname -r > for me 6.0.0-kali3-arm64
  2. Download a fitting chromedriver_... from https://github.com/electron/electron/releases and extract the chromedriver
  3. nano patch.py and write the following (its the current chromedriver patching script copy-pasted from this library) and execute it
#!/usr/bin/python
import io
import re
import random
import string

executable_path = './chromedriver'

def random_cdc():
    cdc = random.choices(string.ascii_lowercase, k=26)
    cdc[-6:-4] = map(str.upper, cdc[-6:-4])
    cdc[2] = cdc[0]
    cdc[3] = "_"
    return "".join(cdc).encode()

def patch_binary():
    """
    Patches the ChromeDriver binary
    :return: False on failure, binary name on success
    """
    linect = 0
    replacement = random_cdc()
    with io.open(executable_path, "r+b") as fh:
        for line in iter(lambda: fh.readline(), b""):
            if b"cdc_" in line:
                fh.seek(-len(line), 1)
                newline = re.sub(b"cdc_.{22}", replacement, line)
                fh.write(newline)
                linect += 1
        return linect

if __name__ == '__main__':
    patch_binary()
  1. chromedriver is patched now so we can proceed with the library where we just have to define a custom chromedriver... for example
import undetected_chromedriver as uc
driver_executable_path = './chromedrive'
driver = uc.Chrome(driver_executable_path=driver_executable_path)
driver.get("https://www.google.com")

It would be nice if this library would support the chromedriver from electron for Arm64. This platform is actually used by quite a lot of people ... e.g. me who is using a M1 chip with Linux virtual machine or people using a raspberry pi.

Necessary till this day, here is an update for the latest version:

#!/usr/bin/python
import logging
import time
import re
import io

executable_path = './chromedriver'

def patch_exe():
        start = time.perf_counter()
        logging.info("patching driver executable %s" % executable_path)
        with io.open(executable_path, "r+b") as fh:
            content = fh.read()
            # match_injected_codeblock = re.search(rb"{window.*;}", content)
            match_injected_codeblock = re.search(rb"\{window\.cdc.*?;\}", content)
            if match_injected_codeblock:
                target_bytes = match_injected_codeblock[0]
                new_target_bytes = (
                    b'{console.log("undetected chromedriver 1337!")}'.ljust(
                        len(target_bytes), b" "
                    )
                )
                new_content = content.replace(target_bytes, new_target_bytes)
                if new_content == content:
                    logging.warning(
                        "something went wrong patching the driver binary. could not find injection code block"
                    )
                else:
                    logging.debug(
                        "found block:\n%s\nreplacing with:\n%s"
                        % (target_bytes, new_target_bytes)
                    )
                fh.seek(0)
                fh.write(new_content)
        logging.debug(
            "patching took us {:.2f} seconds".format(time.perf_counter() - start)
        )

if __name__ == '__main__':
    logging.basicConfig(
        format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
        datefmt='%Y/%d/%m %H:%M:%S',
        level=logging.DEBUG,
        handlers=[logging.StreamHandler()]
    )
    patch_exe()
gottogethelp commented 2 months ago

OK so the problem is, that the google archive does actually only provide x86-64 Linux Chromedriver. The solution is to download a custom one and patch it so it doesn't get detected.

  1. uname -r > for me 6.0.0-kali3-arm64
  2. Download a fitting chromedriver_... from https://github.com/electron/electron/releases and extract the chromedriver
  3. nano patch.py and write the following (its the current chromedriver patching script copy-pasted from this library) and execute it
#!/usr/bin/python
import io
import re
import random
import string

executable_path = './chromedriver'

def random_cdc():
    cdc = random.choices(string.ascii_lowercase, k=26)
    cdc[-6:-4] = map(str.upper, cdc[-6:-4])
    cdc[2] = cdc[0]
    cdc[3] = "_"
    return "".join(cdc).encode()

def patch_binary():
    """
    Patches the ChromeDriver binary
    :return: False on failure, binary name on success
    """
    linect = 0
    replacement = random_cdc()
    with io.open(executable_path, "r+b") as fh:
        for line in iter(lambda: fh.readline(), b""):
            if b"cdc_" in line:
                fh.seek(-len(line), 1)
                newline = re.sub(b"cdc_.{22}", replacement, line)
                fh.write(newline)
                linect += 1
        return linect

if __name__ == '__main__':
    patch_binary()
  1. chromedriver is patched now so we can proceed with the library where we just have to define a custom chromedriver... for example
import undetected_chromedriver as uc
driver_executable_path = './chromedrive'
driver = uc.Chrome(driver_executable_path=driver_executable_path)
driver.get("https://www.google.com")

It would be nice if this library would support the chromedriver from electron for Arm64. This platform is actually used by quite a lot of people ... e.g. me who is using a M1 chip with Linux virtual machine or people using a raspberry pi.

Necessary till this day, here is an update for the latest version:

#!/usr/bin/python
import logging
import time
import re
import io

executable_path = './chromedriver'

def patch_exe():
        start = time.perf_counter()
        logging.info("patching driver executable %s" % executable_path)
        with io.open(executable_path, "r+b") as fh:
            content = fh.read()
            # match_injected_codeblock = re.search(rb"{window.*;}", content)
            match_injected_codeblock = re.search(rb"\{window\.cdc.*?;\}", content)
            if match_injected_codeblock:
                target_bytes = match_injected_codeblock[0]
                new_target_bytes = (
                    b'{console.log("undetected chromedriver 1337!")}'.ljust(
                        len(target_bytes), b" "
                    )
                )
                new_content = content.replace(target_bytes, new_target_bytes)
                if new_content == content:
                    logging.warning(
                        "something went wrong patching the driver binary. could not find injection code block"
                    )
                else:
                    logging.debug(
                        "found block:\n%s\nreplacing with:\n%s"
                        % (target_bytes, new_target_bytes)
                    )
                fh.seek(0)
                fh.write(new_content)
        logging.debug(
            "patching took us {:.2f} seconds".format(time.perf_counter() - start)
        )

if __name__ == '__main__':
    logging.basicConfig(
        format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
        datefmt='%Y/%d/%m %H:%M:%S',
        level=logging.DEBUG,
        handlers=[logging.StreamHandler()]
    )
    patch_exe()

Tried replicating this solution for an M1 Mac (UC 3.5.5) with the following:

import time
import re
import io
from undetected_chromedriver import Chrome

executable_path = "/Users/philipjoss/Downloads/chromedriver-mac-arm64/chromedriver"

def patch_exe():
    start = time.perf_counter()
    logging.info("patching driver executable %s" % executable_path)
    with io.open(executable_path, "r+b") as fh:
        content = fh.read()
        # match_injected_codeblock = re.search(rb"{window.*;}", content)
        match_injected_codeblock = re.search(rb"\{window\.cdc.*?;\}", content)
        if match_injected_codeblock:
            target_bytes = match_injected_codeblock[0]
            new_target_bytes = (
                b'{console.log("undetected chromedriver 1337!")}'.ljust(
                    len(target_bytes), b" "
                )
            )
            new_content = content.replace(target_bytes, new_target_bytes)
            if new_content == content:
                logging.warning(
                    "something went wrong patching the driver binary. could not find injection code block"
                )
            else:
                logging.debug(
                    "found block:\n%s\nreplacing with:\n%s"
                    % (target_bytes, new_target_bytes)
                )
            fh.seek(0)
            fh.write(new_content)
    logging.debug(
        "patching took us {:.2f} seconds".format(time.perf_counter() - start)
    )

if __name__ == "__main__":
    patch_exe()

    driver = Chrome(driver_executable_path=executable_path)

    driver.get("https://www.google.com")

patch_exe runs fine and it updates chromedriver however when the script tries to create the driver an error box pops up:

Screenshot 2024-07-02 at 13 10 38

And then the line errors with:

Exception has occurred: WebDriverException
Message: Can not connect to the Service /Users/philipjoss/Downloads/chromedriver-mac-arm64/chromedriver
  File "/Users/philipjoss/GitHub/polgara_v2/uc_arm64.py", line 43, in <module>
    driver = Chrome(driver_executable_path=executable_path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
selenium.common.exceptions.WebDriverException: Message: Can not connect to the Service /Users/philipjoss/Downloads/chromedriver-mac-arm64/chromedriver

Any ideas on what might be going wrong?