uBlockOrigin / uBlock-issues

This is the community-maintained issue tracker for uBlock Origin
https://github.com/gorhill/uBlock
925 stars 77 forks source link

Scriptlet to spoof CSS property value #2618

Closed MasterKia closed 1 year ago

MasterKia commented 1 year ago

Prerequisites

I tried to reproduce the issue when...

Description

What about a scriptlet that will return a different CSS property value to counter getComputedStyle() (maybe by proxying CSSStyleDeclaration.prototype.getPropertyValue) to foil sophisticated anti-blockers that check all CSS properties of an element (like megaup, aternos, ylilauta.org)?

example.com##+js(spoof-css, a[href*="ads/redirect/"] > img, clip-path, none)
example.com##a[href*="ads/redirect/"] > img:style(clip-path: circle(0) !important;)

A specific URL where the issue occurs.

.

Steps to Reproduce

.

Expected behavior

.

Actual behavior

.

uBO version

.

Browser name and version

.

Operating System and version

.

krystian3w commented 1 year ago

Why not:

example.com##a[href*="ads/redirect/"] > img:spoof(clip-path: none !important;)
example.com##a[href*="ads/redirect/"] > img:style(clip-path: circle(0) !important;)

Based on migration ra, rc to pseudo-classes syntax.

MasterKia commented 1 year ago

Nice idea. My only concern is that we might experience the same problem with ##body:remove-attr(oncontextmenu) sometimes not working on Chromium.

krystian3w commented 1 year ago

I suppose this trouble can only apply to javascript written with html attributes:

https://www.w3schools.com/tags/ref_eventattributes.asp

which is not recommended - actions should be created in the file or "<script> </script>" section (https://kornel-ski.translate.goog/pl/onclick?_x_tr_sl=pl&_x_tr_tl=en&_x_tr_hl=pl&_x_tr_pto=wapp).

uBlock-user commented 1 year ago

body:remove-attr(oncontextmenu)

Use ##body:remove-attr(oncontextmenu):watch-attr(oncontextmenu) then.

krystian3w commented 1 year ago

https://github.com/uBlockOrigin/uBlock-issues/issues/2618#issuecomment-1527416236 - This solve trouble with blocked right click on 99% of pages?

https://github.com/uBlockOrigin/uBlock-issues/discussions/2403#discussioncomment-4638987

gorhill commented 1 year ago

Based on migration ra, rc to pseudo-classes syntax.

Spoofing requires to patch JavaScript calls, which can only be done in the page world. Code for procedural cosmetic filters execute in content script world.

krystian3w commented 1 year ago

And are there any plans to e.g. on musixmatch.com replace +js(ra, oncontextmenu, body, complete) to +js(nowoif, something)?

https://github.com/uBlockOrigin/uAssets/blob/ee4ce452e50fccf8253a6014db44122f4faf5e38/filters/annoyances.txt#L2007

gorhill commented 1 year ago

Can I get real world examples (preferably not requiring account) for which I can test such scriptlet?

peace2000 commented 1 year ago

Can I get real world examples (preferably not requiring account) for which I can test such scriptlet?

https://ylilauta.org/jorma/131700539

To spoof that ##iframe.a has value display: block even if it's truly hidden?

gorhill commented 1 year ago

When I use ##iframe.a, the frame is not hidden, is there an exception filter? If so I am not seeing it in the logger.


Ok never mind, code stepping I see an exception filter, not reported in logger. It works if I use ##iframe.a.a.

MasterKia commented 1 year ago

On soft98.ir when I only add: soft98.ir##+js(spoof-css, a[href*="s0ft98.ir"], clip-path, none) or soft98.ir##+js(spoof-css, a[href*="s0ft98.ir"], nonsense, none)

I get detection after a few seconds.

But if I use this userscript:

// @run-at      document-start

(function () {
'use strict';
const handler = {
  apply(target, thisArg, args) {
    let response = Reflect.apply(target, thisArg, args);
    if (args == "clip-path" || args == "clipPath") {
      response = "none";
    }
    return response;
  }
}

const myProxy = new Proxy(CSSStyleDeclaration.prototype.getPropertyValue, handler);
CSSStyleDeclaration.prototype.getPropertyValue = myProxy;

}) ();

and then use this filter: soft98.ir##a[href*="s0ft98.ir"]:style(clip-path: circle(0) !important;)

Then I get no detection.

Edit: added @run-at.

gorhill commented 1 year ago

But if I use this userscript:

I tried with ViolentMonkey and I still get detection with your script.


Ok, seems to work when using // @run-at document-start.

gorhill commented 1 year ago

Note that your code has an issue but it accidentally works because of truthiness. Should be:

if (args[0] === "clip-path" || args[0] === "clipPath") {
gorhill commented 1 year ago

Ok the page is doing something else than just checking the properties. For spoof-css to work on that site, I had to wholly remove the proxying of getComputedStyle(), so it does something with that call that triggers the detection.

MasterKia commented 1 year ago

I had to wholly remove the proxying of getComputedStyle()

Bu this also works for me:

// ==UserScript==
// @name         spoof-css
// @match        http://*.soft98.ir/*
// @match        https://*.soft98.ir/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
  'use strict';

  const selector = 'a[href*="s0ft98.ir"]';
  var prop = 'clip-path';
  const targetValue = 'circle(0) !important';
  var currentValues = {};

  function spoofCSS() {
    const nodes = document.querySelectorAll(selector);
    if (nodes.length == 0) {
      console.log('No elements were found for your selector.')
      return false;
    }

    nodes.forEach(node => {
      currentValues[node] = getComputedStyle(node).getPropertyValue(prop);
    });

    const handler = {
      get(target, thisArg, receiver) {
        if (thisArg == "toString") {
          return target.toString.bind(target);
        } else {
          return Reflect.get(target, thisArg, receiver);
        }
      },
      apply(target, thisArg, args) {
        let result = Reflect.apply(target, thisArg, args);
        nodes.forEach(node => {
          if (args[0] == node) {
            if (prop == 'clip-path' || prop == 'clipPath') {
              prop = 'clipPath';
            }
            Object.defineProperty(result, prop, {
              get: function() {
                return currentValues[node];
              }
            });
            const handler2 = {
              apply(target, thisArg, args) {
                let result = Reflect.apply(target, thisArg, args);
                if (prop == 'clip-path' || prop == 'clipPath') {
                  prop = 'clip-path';
                }
                if (args[0] == prop) {
                  result = currentValues[node];
                }
                return result;
              }
            }
            result.getPropertyValue = new Proxy(result.getPropertyValue, handler2);
          }
        });
        return result;
      }
    }
    window.getComputedStyle = new Proxy(window.getComputedStyle, handler);

    const styleSheet = new CSSStyleSheet();
    styleSheet.replaceSync(`${selector} {${prop}: ${targetValue};}`);
    document.adoptedStyleSheets = [styleSheet];
  }

  document.addEventListener('DOMContentLoaded', spoofCSS);

}) ();

Sorry for the logic errors (== vs ===) as I had written this a week ago.

Yuki2718 commented 1 year ago

Is it possible to specify not a static value but value obtainable from elements or scripts? On https://github.com/uBlockOrigin/uAssets/discussions/17361#discussioncomment-6960192 an excerpt of detection code https://www.btcbitco.in/myassets/aOW9dugd31nhaaOW9dugd31nhd.js?ver= (direct access forbidden even with parameter)

Code ``` function aOW9dugd31nhests() { var done = 20; if (done === 0) { setTimeout(function() { if ($('.l-button').length > 0) { $('.l-button').hide(); } var aOW9dugd31nhe2 = document.getElementById('aOW9dugd31nhedz'); var aOW9dugd31nhe4 = aOW9dugd31nhe2.clientHeight; if (!aOW9dugd31nhe2 || aOW9dugd31nhe4 != 97) { if (typeof gtag === "function") { gtag('event', 'session_ends'); } alert("Session ends, thanks for using GainLink."); window.location.href = 'https://www.btcbitco.in/page/endsessions'; }else{ aOW9dugd31nhest(); } }, 4000); } else { if ($('.l-button').length > 0) { $('.l-button').hide(); } setTimeout(function() { var aOW9dugd31nhek = document.getElementById('aOW9dugd31nhedz'); var aOW9dugd31nheh = aOW9dugd31nhek.clientHeight; if (!aOW9dugd31nhek || aOW9dugd31nheh != 97) { if (typeof gtag === "function") { gtag('event', 'ads_blocker_detected'); } var aOW9dugd31nhec = confirm("Go ahead and turn off your adblocker? Click 'OK' if you agree to turn off your adblocker or 'Cancel' to stop here."); if (aOW9dugd31nhec === false) { alert("Session ends, thanks for using GainLink."); if (typeof gtag === "function") { gtag('event', 'session_ends'); } window.location.href = 'https://www.btcbitco.in/page/endsessions'; } else { alert("Please disable your adblocker and reload"); } }else{ $.ajax({ url: "https://www.btcbitco.in/page/validate?ver=1d75b01ce9033fe7764904b77212315e25779b17a25e77d79aafc9500edbd20e", type: "GET", success: function(response) { if (response === "valid") { aOW9dugd31nhest(); } else { alert("Invalid response received!"); } }, error: function() { alert("An error occurred, please reload!"); location.reload(); } }); } }, 1000); } } window.onload = function() { var aOW9dugd31nhevd = document.createElement("iframe"); aOW9dugd31nhevd.src = "https://cryptocoinsad.com/ads/show.php?a=256522&b=397269"; aOW9dugd31nhevd.style = "width: 100%; height: 97px; overflow: hidden; border: 0px; padding: 0px; background-color: transparent;"; aOW9dugd31nhevd.id = "aOW9dugd31nhedz"; var aOW9dugd31nhezd = document.getElementsByClassName("entry-content")[0]; var aOW9dugd31nheparent = aOW9dugd31nhezd.parentNode; aOW9dugd31nheparent.replaceChild(aOW9dugd31nhevd, aOW9dugd31nhezd); function aOW9dugd31nhemm() { var aOW9dugd31nheca = document.getElementById("aOW9dugd31nhedz"); if (aOW9dugd31nheca) { clearInterval(aOW9dugd31nheid); aOW9dugd31nhests(); } } var aOW9dugd31nheid = setInterval(aOW9dugd31nhemm, 1000); } ```

They create a bait element with random height (here 97px, but varies on each load) and checks it. It's not inline so rpnt can't be used. As this is iframe, I just removed cosmetic filter and redirect=noop.html is enough, but the suggested capability may be useful.

JobcenterTycoon commented 10 months ago

kiktu.com doing the same now. A bait element getting added but with visiblity: hidden while the real element has visiblity: visible. Now the site checks if the bait has visiblity: visible or the real element visiblity: hidden and if yes -> Anti adblock.

BlazeFTL commented 10 months ago

Another issue terabox.fun/sl/3lsTuo98O3

script Location is 3p Screenshot_20231124-115033_Firefox Nightly

Is there any way to modify script values which arent inline ?... ideally what i do is replace the element they check with a element that wouldnt be blocked with rpnt

Would something like Replace work for such cases ?

stephenhawk8054 commented 10 months ago

$replace could work, but only Firefox

krystian3w commented 10 months ago

Own replace need bumping uBo to 1.54.1+ or works like trusted- scriptlets and we should share own idea to AdGuard (So that their add-on would not require bumping in the Moz://a store)?

gorhill commented 10 months ago

Own replace need bumping uBo to 1.54.1+ or works like trusted- scriptlets

I don't understand what is being said here.

krystian3w commented 10 months ago

Would something like Replace work for such cases ?

$replace could work, but only Firefox

Own replace need bumping uBo to 1.54.1+ or works like trusted- scriptlets

So I ask, if stephenhawk8054 adds a filter, will it start in uBo 1.54.0 with a differential update or a full filter list alone, because if AdGuard would add it I would have to wait for the next version of the add-on in December 2023 or January 2024.


This could be added to the wiki that our replace does not require an update of the entire add-on or that we have identically solved this as in AdGuard and such a filter will start after a month or in beta releases.

gorhill commented 10 months ago

I still don't understand. replace= is available in 1.54.0 as a trusted-source filter option.

Differential update has nothing to do with this, I don't know why you mention this, and I also don't understand why you mention AdGuard.

krystian3w commented 10 months ago

I ask here because that's what BlazeFTL did.

My question is whether the $replace= filter added today to filters-2023.txt requires an update of stable ubo 1.54.0 to 1.54.1 (beta) or 1.54.2 (stable) to not break the Moz://a store limitation/policy/restriction.

For example, AdGuard $replace= has to wait for a new version of the add-on, and it is unlikely that the developers check whether the old restriction is already dead in order to keep the filter active without a forced update of the entire extension.

gorhill commented 10 months ago

AdGuard $replace= has to wait for a new version of the add-on

You have a source for this? I used the debugger in Firefox and I can see AdGuard parse replace= filters when updating AdGuard Base.

BlazeFTL commented 5 months ago

Spoof-css Makes Stuck At Checking Browser In FF Android Random Anti Adb On Chrome Based Browser🥲 https://devuploads.com/fcdlxmzxep5o Filters Used


jytechs.in,dev.miuiflash.com,djxmaza.in,thecubexguide.com##[id*="div-gpt-ad-"], [id*="google_ads_"], #featuredimage, [href*="random-affiliate.atimaze.com"]:style(visibility: collapse !important;)
jytechs.in,dev.miuiflash.com,djxmaza.in,thecubexguide.com##+js(spoof-css, [id*="div-gpt-ad-"]\, [id*="google_ads_"]\, #featuredimage\, [href*="random-affiliate.atimaze.com"], visibility, visible)