Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UC patches are detected by Function.toString() fingerprint #273

Closed
HMaker opened this issue Aug 13, 2021 · 6 comments
Closed

UC patches are detected by Function.toString() fingerprint #273

HMaker opened this issue Aug 13, 2021 · 6 comments

Comments

@HMaker
Copy link

HMaker commented Aug 13, 2021

It is known that Function.toString() returns "function (){ [native code] }" for browser-native JS functions, but for user-made functions it returns the source code. This is exploited by webdriver detection mechanisms, patches should be undetectable too.

This is a recursive problem, if you mock Function.toString() then Function.toString.toString() can be detected and so on...

@HMaker HMaker changed the title UC patches fall into Function.toString() fingerprint UC patches are detected by Function.toString() fingerprint Aug 13, 2021
@HMaker
Copy link
Author

HMaker commented Aug 13, 2021

The only way to reliably patch browser is by changing its source-code.

@ultrafunkamsterdam
Copy link
Owner

this is simply not true. been there done that.

@HMaker
Copy link
Author

HMaker commented Aug 13, 2021

@ultrafunkamsterdam the following code says it's true:

<!-- ./detector.html -->
<html>
    <head>
        <title>Webdriver Detector</title>
        <script>
            function isNativeFunction(targetFunc, toString, depth=3) {
                if(typeof targetFunc != "function" || typeof toString != "function") return false;
                if(depth > 0) {
                    return isNativeFunction(toString, toString.toString, depth - 1) && [
                        `function${targetFunc.name.replaceAll(" ", "")}(){[nativecode]}`,
                        `function${targetFunc.name.replace("get ", "").replaceAll(" ", "")}(){[nativecode]}`
                    ].includes(targetFunc.toString().replaceAll("\n", "").replaceAll(" ", ""))
                } else {
                    return true;
                }
            }
            function isFakeProperty(obj, prop) {
                const desc = Object.getOwnPropertyDescriptor(obj, prop);
                if(typeof desc != "object") return false;
                return (
                    ("get" in desc && !isNativeFunction(desc.get, desc.get.toString)) ||
                    ("value" in desc && "get" in desc.value && !isNativeFunction(desc.value.get, desc.value.get.toString))
                );
            }
            function isWebdriverControlled() {
                return (
                    navigator.webdriver ||
                    !isNativeFunction(Object.getOwnPropertyDescriptor, Object.getOwnPropertyDescriptor.toString) ||
                    (Object.getOwnPropertyDescriptor(navigator, 'webdriver') && isFakeProperty(navigator, 'webdriver')) ||
                    isFakeProperty(window, 'navigator')
                );
            }
            function isMultitouchPatched() {
                return isFakeProperty(navigator, "maxTouchPoints");
            }
            function isNotificationsPermissionPatched() {
                return  isFakeProperty(window, "Notification") || isFakeProperty(Notification, "permission");
            }
            function isChromeGlobalPatched() {
                return isFakeProperty(window, "chrome");
            }
            function isUndetectedChromedriver() {
                return isWebdriverControlled() || isMultitouchPatched() || isNotificationsPermissionPatched() || isChromeGlobalPatched();
            }
        </script>
        <body>
            <script>
                const msg = document.createElement("h1");
                document.body.appendChild(msg);
                if(isUndetectedChromedriver()) {
                    msg.innerText = "You are using undetected chromedriver!!";
                } else {
                    msg.innerText = "You are a human :)";
                }
            </script>
        </body>
    </head>
</html>

then on python try

import undetected_chromedriver.v2 as uc


options = ChromeOptions()
options.add_argument('--no-first-run')
options.add_argument('--no-default-browser-check')
driver = uc.Chrome(version_main=92, options=options)
try:
    driver._configure_headless() # this makes it detectable as I said
    driver.get("file:///tmp/test/detector.html")
    input("press any key to exit...")
finally:
    driver.quit()

@HMaker
Copy link
Author

HMaker commented Aug 13, 2021

This test can be bypassed only if your patch go deeper than check done by isNativeFunction(), so headless mode was always broken for basic detectors.

@ultrafunkamsterdam
Copy link
Owner

_configure_headless is only be called when you set the headless flag (because you want a headless browser) and since it is a private method, it shouldnt even be called by user code at all).
It is no secret that uc does NOT work in headless mode, except for less sophisticated detections by mocking just the most obvious of things, i say that all the time, browse the anwers. neither is it on top of my todo list as the differences in headless are countless. Mocking the stuff is not needed (and should be avoided at all cost) in regular mode. It exists just to make headless "less worse".
No need to write your full fledged scanner to prove something, when you're using it wrong in the first place. Meanwhile you could have cloned uc and made all the changes you want..... waste of time....

@HMaker
Copy link
Author

HMaker commented Aug 14, 2021

It's obvious I called _configure_headless() to keep browser headfull so detection result could be seem... as you know it's same as passing options.headless = True. Bug reports with reproducible examples are welcomed by most open source projects, for you it's waste of time? I did a test with 10 concurrent webdrivers, headfull browser consumes the double of resources (CPU and RAM) of headless one, so it's not "countless". browserless.js handles Function.toString() fingerprint in their patches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants