51

Samy Kamkar's website, http://samy.pl, knows when the console is being opened and wipes the source/console when it does open.

enter image description here

How does this work?

Gala
  • 2,592
  • 3
  • 25
  • 33

1 Answers1

68

This took some digging. samy.pl has several levels of indirection and obfuscation on top of this code. It uses a different version of the detection code than the GitHub repository found by JohanP. The code in samy.pl, unlike the GitHub repository, can detect the devtools when they are not docked.

It does so by using a short script that executes differently depending on whether devtools is open or closed.

Example script

Here's a self-contained example; open it in a browser and notice how the output changes as the devtools are opened and closed (whether it is docked or not):

<!DOCTYPE html>
<html>
    <body>
        <pre id="output"></pre>
        <script type="text/javascript">
            var element = new Image;
            var devtoolsOpen = false;
            element.__defineGetter__("id", function() {
                devtoolsOpen = true; // This only executes when devtools is open.
            });
            setInterval(function() {
                devtoolsOpen = false;
                console.log(element);
                document.getElementById('output').innerHTML += (devtoolsOpen ? "dev tools is open\n" : "dev tools is closed\n");
            }, 1000);
        </script>
    </body>
</html>

How it works

The setInterval is executed every second. console.log always executes, whether the devtools are open or closed: The console object is always defined. However, the log method only writes output to the console when the devtools are open. If the devtools are closed, console.log is a no-op. That's the key that lets you detect if the devtools are open: detecting if the log operation is a no-op.

In the process of writing element to the console, it gets the id of the element. That calls the function attached with __defineGetter__. Therefore, console.log(element) only calls that function when the devtools are open and console.log is not a no-op. The flag is set in that function, giving us an updated view of the devtools state every second.

samy.pl uses some additional tricks to hide this: the console is also cleared every second, and this code is obfuscated with a whitespace (!) encoding.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
kevingessner
  • 18,559
  • 5
  • 43
  • 63
  • 7
    Should we write a Chrome/Chromium bug report to prevent this behavior? I kind of don't like if people know I have that open. – Gala Feb 12 '17 at 22:49
  • 1
    Great answer. If you have time could you expand on the last sentence? `and this code is obfuscated with a whitespace (!) encoding` Thanks! – Jake Feb 12 '17 at 23:14
  • 9
    @Jake The obfuscation could be an answer in itself! The source code that includes this detection is encoded by converting each character to the binary representation of its ASCII character code (e.g. 'A' == '1000001'); then converting each 0 to a space and 1 to a tab. That string of whitespace is converted back to binary, then HTML, and executed with this JS (where `s` is the string of whitespace): `s.replace(/.{7}/g,function(w){document.write(String.fromCharCode(parseInt(w.replace(/ /g,'0').replace(/ /g,'1'),2)))})`. – kevingessner Feb 12 '17 at 23:25
  • @kevingessner Ok thanks. That is a lot of effort to go to! Did you find this on the samy site? or somewhere else? The reason i ask is I cannot find any whitespace in the `load.js` – Jake Feb 12 '17 at 23:44
  • 3
    @Jake I didn't look at load.js but figured this out from what's served at samy.pl. Setting a pre-execution breakpoint (in Chrome devtools, via Sources tab > Event Listener Breakpoints > Script > Script First Statement) let me step through the code as it executed, which was most valuable. That included the decoding and the obfuscation code, and gave me chances to interfere with or prevent some of it. – kevingessner Feb 13 '17 at 00:47
  • 5
    this is really cool, just had to figure out how to defeat it :) sorry samy. To defeat it, type `console.log = function() {}`, the browser will reload, then quickly press up and enter to run that again, and you can see the source. http://imgur.com/a/1dBPn – adgelbfish Feb 14 '17 at 16:02
  • @adgelbfish Just typing `javascript: delete element.id` in URL box is enough :) Well, you can also execute `solecon.clear = solecon.go = function(){};` to prevent it from clearing the console. – stek29 Jun 19 '17 at 01:22
  • It was recently fixed in [Chrome](https://bugs.chromium.org/p/chromium/issues/detail?id=795547) and (AFAIK) never worked in Firefox. That being said, there are multiple other ways of detecting if DevTools are open, see [this GH issue](https://github.com/sindresorhus/devtools-detect/issues/15) for some ideas. – Konrad Dzwinel Jan 07 '18 at 21:46
  • 1
    does not detect on firefox browser – Fthr Jan 26 '21 at 20:49