Opened 4 months ago

Closed 3 months ago

Last modified 7 weeks ago

#5207 closed defect (fixed)

Uponit circumvention of WebRTC blocking

Reported by: kzar Assignee: kzar
Priority: P2 Milestone: Adblock-Plus-1.13.3-for-Chrome-Opera
Module: Platform Keywords:
Cc: dzr156, fanboy, mapx, sebastian, Lain_13, SMed79 Blocked By:
Blocking: Platform: Chrome
Ready: yes Confidential: no
Tester: Ross Verified working: yes
Review URL(s):

https://codereview.adblockplus.org/29437555/
https://codereview.adblockplus.org/29437560/

Description (last modified by kzar)

Environment

Chrome 58, Adblock Plus dev build built from 4d758880ad0b.
EasyList Hebrew+EasyList, AA disabled.

How to reproduce

  1. Open chrome://webrtc-internals/ in a new tab.
  2. Open a second tab, open the developer tools for that and switch to the Adblock Plus pane.
  3. Browse to http://www.ynet.co.il in the second tab.

Observed behaviour

  • No WebRTC connection is listed in the Adblock Plus pane.
  • A WebRTC connection is listed on chrome://webrtc-internals/.

Expected behaviour

The WebRTC connection should be listed in the Adblock Plus pane and blocked.

Notes

This is a simplified version of what they're doing:

function unwrapAPIs(...args)
{
  let iframe = document.createElement("iframe");
  iframe.style.display = "none";
  iframe.style.width = "1px";
  iframe.style.height = "1px";
  iframe.srcdoc = "a";
  document.body.appendChild(iframe);

  let iframeWindow = iframe.contentDocument.defaultView;
  iframeWindow.document.write("a");
  try
  {
    iframeWindow.stop();
  } catch (e) {}

  for (let apiName of args)
    window[apiName] = iframeWindow.eval(apiName);
}

unwrapAPIs("RTCPeerConnection", "RTCSessionDescription",
           "RTCIceCandidate", "WebSocket");

Their code is heavily obfuscated, they even have some crazy encoding for sensitive strings. Here's the code to decode those:

let decoder = {};
decoder.u = function(a) {
  this.C = [];
  this.D = 256;
  for (var b = 0; b < this.D; b++)
    this.C[b] = a.charCodeAt(b % a.length);
  this.L = function(a) {
    for (var b = "", d = 0; d < a.length; d++)
      b += String.fromCharCode(a.charCodeAt(d) ^ this.C[d % this.D]);
    return b
  }
}
;
decoder.F = new decoder.u("!np!PoayN9DfrmHLy5moQDP4vVe1fZ41");
decoder.G = function(a) {
  return decoder.F.L(a)
}
;
decoder.decode = function(a, e) {
  !1 !== e && (a = atob(a));
  return decoder.G(a)
};

decoder.decode("VgceRT8YTysaehcDAR4hIxdxCBwyNjlEAj8KX0YmSBFWBx5FPxhPDitbLw8GPxwPKlAeHDgrPnATJQZDDypAWE4AUF0sTxYQIF0rEVwAJzYrYS48NDcjXRk4IVQVOUZYURoZTj5PHQVuTi0IFgI/YhRGPzsSFzVHBT8KXyI/R1JTBwBVOQAP");

For reference here's the original unwrapping code, with only the strings decoded:

function I() {
  function a(a, e, f) {
    try {
      if (!e)
        return {};
      var g = d(a), k = g.Object, v = g.hasOwnProperty, h = k(), m;
      for (m in e)
        if (v.call(e, m)) {
          var l = e[m]
          , n = g["eval"](m);
          void 0 !== l.bind && (n = n.bind(l.bind));
          h[l.name] = n
        }
      c(g) && f && a.parentElement && a.parentElement.removeChild(a);
      return h
    } catch (N) {
      return {}
    }
  }
  function e() {
    var a = document.createElement("iframe");
    a.style.display = "none";
    a.style.width = "1px";
    a.style.height = "1px";
    a["srcdoc"] = "a";
    (document.body || document.head || document.documentElement).appendChild(a);
    var c = d(a);
    "undefined" === typeof c.document.documentElement && c.document.write("a");
    try {
      c["stop"]()
    } catch (t) {}
    return a
  }
  function d(a) {
    var c = "contentDocument"
    , e = "defaultView"
    , d = "contentWindow";
    return a[c] ? a[c][e] || a[d] : a[d]
  }
  function c(a) {
    return "undefined" !== typeof a["InstallTrigger"]
  }
  function g(a) {
    return !!a["chrome"] && !!a["chrome"]["webstore"] && !!a["webkitResolveLocalFileSystemURL"] && !("safari" in a)
  }
  var f = "window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection"
  , l = "window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.msRTCSessionDescription"
  , k = "window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate || window.msRTCIceCandidate"
  , h = "window.WebSocket"
  , n = e()
  , m = d(n);
  if (900 >= (m["innerWidth"] || document["documentElement"]["clientWidth"] || document.body["clientWidth"]) || !(g(m) || m["opr"] && m["opr"]["addons"] && m["chrome"] || c(m) && "undefined" !== typeof m["mozInnerScreenX"] && "undefined" !== typeof m["mozRTCIceCandidate"]) && (g(m) || m["opr"] && m["opr"]["addons"] || m["opera"] || void 0 === m["webkitAudioContext"])) {
    var p = {};
    p["RTCPeerConnection"] = window["eval"](f);
    p["RTCSessionDescription"] = window["eval"](l);
    p["RTCIceCandidate"] = window["eval"](k);
    p["WebSocket"] = window["eval"](h);
    return p;
  }
  var q = null
  , u = {};
  u[f] = {
    bind: void 0,
    name: "RTCPeerConnection"
  };
  u[l] = {
    bind: void 0,
    name: "RTCSessionDescription"
  };
  u[k] = {
    bind: void 0,
    name: "RTCIceCandidate"
  };
  f = {
    bind: void 0,
    name: "WebSocket"
  };
  q = {};
  c(m) ? (q = {},
          q[h] = f,
          h = e(),
          q = a(h, q, !0)) : u[h] = f;
  h = a(n, u, !1);
  for (p in q)
    q.hasOwnProperty(p) && (h[p] = q[p]);
  return h
}

Also see #4586 which is very similar.

Hints for testers

I recommend this is tested at the same time as #4586.

  • Please test that the blocking of WebRTC connections, WebSockets and element hiding still work for both new and old Chrome versions.
  • Check that complex webpages that use lots of frames still work, things like Google Drive.
  • Test that websites that use WebRTC like Google Hangouts.
  • Check that WebRTC connections are listed (and blocked) by Adblock Plus for merriam-webster.com.
  • Test that you can't find websites that open WebRTC connections that are listed in the chrome://webrtc-internals page but not in the Adblock Plus connections panel.

Change History (20)

comment:1 Changed 4 months ago by kzar

  • Sensitive set

comment:2 Changed 4 months ago by kzar

  • Platform changed from Firefox to Chrome

comment:3 Changed 4 months ago by kzar

  • Description modified (diff)

comment:4 Changed 4 months ago by kzar

  • Description modified (diff)

comment:5 Changed 4 months ago by kzar

  • Description modified (diff)

comment:6 Changed 4 months ago by kzar

  • Cc Lain_13 added

comment:7 Changed 3 months ago by kzar

  • Description modified (diff)

comment:8 Changed 3 months ago by mapx

  • Cc SMed79 added

comment:9 Changed 3 months ago by kzar

  • Review URL(s) modified (diff)
  • Status changed from new to reviewing

comment:10 Changed 3 months ago by fanboy

How were they working around the original block?

comment:11 Changed 3 months ago by kzar

Something like creating an iframe where our content script doesn't run (thanks Chrome...), using that to access the unwrapped APIs, then using those to open connections without us knowing.

I have a fix up for review now that's actually working pretty well, I'll keep you posted.

comment:13 Changed 3 months ago by kzar

Yes and no.

Their discussion did start from looking at some similar circumvention code. But they're actually discussing adding a $csp filter option which would allow filter list authors to specify content security policies themselves. That's not what I'm doing here, but it's actually a pretty interesting idea. I'll open an issue for that.

Edit: There you go #5241.

Last edited 3 months ago by kzar (previous) (diff)

comment:14 Changed 3 months ago by kzar

  • Description modified (diff)
  • Milestone set to Adblock-Plus-for-Chrome-Opera-next
  • Resolution set to fixed
  • Status changed from reviewing to closed

comment:15 follow-up: Changed 3 months ago by dzr156

Hey, I just tested the filter with the latest code on yad2.co.il (Uponit site) and it doesn't work for there, seems like a different WebRTC circumvention version (the connections weren't blocked).

http://www.yad2.co.il/

Last edited 3 months ago by dzr156 (previous) (diff)

comment:16 in reply to: ↑ 15 Changed 3 months ago by dzr156

Replying to dzr156:

Hey, I just tested the filter with the latest code on yad2.co.il (Uponit site) and it doesn't work for there, seems like a different WebRTC circumvention version (the connections weren't blocked).
http://www.yad2.co.il/

Hey guys, my mistake, the new wrapper works great. Thanks, Kzar!

comment:17 Changed 8 weeks ago by Ross

Appears fine:

  • Blocking of WebSockets, WebRTC and element hiding still work as expected.
  • Sites with lots of frames like Drive seem fine.
  • Video/audio in Hangouts works as expected with ABP installed (on both ends, or either).
  • Various sites from the WebRTC tracking census. Their WebRTC calls display in the ABP devtools panels and can be blocked/unblocked.

Not sure about:

  • Merriam Webster no longer appears to use a WebRTC connection?
  • The ynet.co.il site. It creates a WebRTC connection which appears in the ABP devtools list as expected, but isn't blocked using either EasyList or EasyList Hebrew. I could block it manually. Is that expected?

ABP 1.13.2.1785
Chrome 49 / Windows 7
Chrome 59 / Windows 10

comment:18 Changed 8 weeks ago by kzar

Merriam Webster no longer appears to use a WebRTC connection?

Yea, that seems to be the case. I can't see any WebRTC connections being opened for that site now either.

The ynet.co.il site. It creates a WebRTC connection which appears in the ABP devtools list as expected, but isn't blocked using either EasyList or EasyList Hebrew. I could block it manually. Is that expected?

Sounds OK to me. (I see the same behaviour.)

comment:19 Changed 7 weeks ago by kzar

  • Sensitive unset

comment:20 Changed 7 weeks ago by Ross

  • Tester changed from Unknown to Ross
  • Verified working set

Done. WebRTC blocking is working as in #4455.

ABP 1.13.2.1785
Chrome 49 / 58 / Windows 7
Opera 39 / 45 / Windows 7

Note: See TracTickets for help on using tickets.