Closed Bug 1578220 Opened 5 years ago Closed 4 months ago

Add a toggle to enable/disable all debugger statements (separately from the breakpoint toggle)

Categories

(DevTools :: Debugger, enhancement, P3)

enhancement

Tracking

(relnote-firefox 121+, firefox121 fixed)

RESOLVED FIXED
121 Branch
Tracking Status
relnote-firefox --- 121+
firefox121 --- fixed

People

(Reporter: nbp, Assigned: ochameau)

References

(Blocks 2 open bugs)

Details

(Keywords: dev-doc-needed)

Attachments

(3 files)

Today some JavaScript Obfuscator are using the debugger; keyword to prevent reverse engineering the code, or editing value with a debugger. Unfortunately these mitigations cause performance issues which appear only in Firefox.

By offering a toggle to enable/disable debugger statements, and communicating about it, we will not fix this performance issue, but suggest to developers of these tools that this kind of mitigation is now useless. Thus, hopefully reducing the number of these from appearing on the Web.

The performance problem comes from the way the the debugger statement is used.
It is used in a newly created function, called by a function which is calling it-self recursively, and executed in a try-catch. The performance problem then appears from the fact that Firefox seems to have a bigger JavaScript stack than Chrome[1] in this specific case, thus crashing later rather than sooner. (see Bug 1475013)

[1] In the debugger console:

var i = 0;
function f() { i++; f() }
f();

On my system:
Firefox: i == 45706
Chromium: i == 15728

Priority: P5 → --
See Also: → 1562700, 1578350, 1537609
No longer blocks: firebug-gaps, dbg-control, dbg-70
No longer depends on: 925269

I believe that the "disable all debugger statements" button already exists. There is a button in the top right of the debugger that disables breakpoints, debugger statements and other types of pausing.

That doesn't address the goal of this bug though, which would be to allow people to debug normally, while basically skipping the part where debugger; statements cause a pause, thus removing the encouragement for projects to use them as an obfuscation mechanism.

hmm, i see. so breakpoints should still work, but debugger statements should be disabled...

could we address this by making it easy to disable specific debugger statements?

The hard part is evalled code, since that will create a brand new snippet with a new debugger that isn't disabled. Disabled-by-default seems to be the only way.

Could JS engine provide a config pref to solve this edge use case – disabling debugger statements? We probably need more time to evaluate our approach on how to expose this in DevTools.

Flags: needinfo?(nicolas.b.pierron)

I think this is doable, but Jim is likely to know better than me.

Flags: needinfo?(nicolas.b.pierron) → needinfo?(jimb)

Can't we just do this in the server, today, by not setting an onDebuggerStatement handler in the first place?

Flags: needinfo?(jimb)
Priority: -- → P3
See Also: → 1728096

The patch offered here https://github.com/firefox-devtools/debugger/issues/8228#issuecomment-508776592 does not work anymore

cp -n libxul.so{,.prePatch}; dd of=libxul.so if=<(printf "dbbbgggr") obs=1 conv=notrunc seek=$(strings -t d libxul.so | egrep "^[0-9]* (debugger|padStart)$"| grep padStart -A 0 -B 1 | head -n 1|cut -d" " -f1); diff <(xxd libxul.so) <(xxd libxul.so.prePatch)

... but it does work on v67, as-advertised. You may need to "stay with that version" though.

Are there any hopes about this feature?
Debugger call is nowadays abused everywhere to break debugging attempts and the only way to stop this requires a custom fix in the source code as explained here https://nullpt.rs/evading-anti-debugging-techniques/ .

This can be solved by adding a new option to disable/enable debugger call and set a customizable call name to trigger the breakpoint and bypass this anti debugger method.

There is a new feature which is present in Firefox 98 and would help as selectively ignore debugger statements:

This does not answer specifically about this specific bugs, as this does not disable all debugger statements, but this can be used as a work-around in the mean time.

(In reply to Nicolas B. Pierron [:nbp] from comment #11)

This does not answer specifically about this specific bugs, as this does not disable all debugger statements, but this can be used as a work-around in the mean time.

I am not sure if your claim is actually true.

as explained here https://nullpt.rs/evading-anti-debugging-techniques/

there is eval-usage. I am not sure how can you blackbox a line which is techincally not a line, but a string to be eval'ed.

And then there are "other techniques" too ...

Attached image image.png

There is also a button available on Debugger toolbar allowing the user to disable all breakpoints including debugger; keywords.

See the attached screenshot for the location.

Does that work for you?

(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #13)

There is also a button available on Debugger toolbar allowing the user to disable all breakpoints including debugger; keywords.

See the attached screenshot for the location.

Does that work for you?

To answer for stdedos+mozilla, it does disable all breakpoints including debugger; statements, yes. Though doesn't address the use case described in comment 2.

A simple script for that is setInterval(function() { debugger; }, 0);, though there are more sophisticated ones out there with the goal to block people from debugging and reverse-engineering foreign code.

Sebastian

I see, thank you Sebastian.

@stdedos+mozilla: what's your use case?

  1. Disable all (breakpoints + debugger; keywords)
  2. Disable just the debugger; keywords (all occurrences)
  3. Something else?

Honza

Flags: needinfo?(stdedos+mozilla)

(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #15)

I see, thank you Sebastian.

@stdedos+mozilla: what's your use case?

  1. Disable all (breakpoints + debugger; keywords)
  2. Disable just the debugger; keywords (all occurrences)
  3. Something else?

Honza

Apologies, I didn't get a chance to re-test the suggestion yesterday (I remember I didn't plain come here to cry about it, but I am not sure what was the case 2 months ago).

My use case is that I don't want "whoever decided" to while(sleep(0.1)) ; do debugger ; done the life out of a page will get it their way :-p
Even if I don't want to mess with the js itself, DevTools being open means "automatic" debugger hit & activation.

pls clear "my" needinfo flag yourself, if my answer satisfies your request

(In reply to stdedos+mozilla from comment #16)

My use case is that I don't want "whoever decided" to while(sleep(0.1)) ; do debugger ; done the life out of a page will get it their way :-p
Even if I don't want to mess with the js itself, DevTools being open means "automatic" debugger hit & activation.

I see, make sense, thank you for the clarification.

pls clear "my" needinfo flag yourself, if my answer satisfies your request

Done

Flags: needinfo?(stdedos+mozilla)

(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #13)

Created attachment 9265126 [details]
image.png

There is also a button available on Debugger toolbar allowing the user to disable all breakpoints including debugger; keywords.

See the attached screenshot for the location.

Does that work for you?

To answer this question (after so much time, sorry about that!), it seems that this "cannot" apply to my case.

The "testbed" in question, is able to detect the DevTools being open in the tab, halt the operations of the page, and render the DevTools half-inoperable - be it at the start or any other time in the tab's timeline.

I don't even get to the point where I could possibly test the feature in question (and it doesn't matter if I pre-set it).

Chrome has no such problem - after it's armed with an anti-anti-debugger, that is (didn't test Firefox).

Tested on Firefox 97.0.2 (64-bit) @ Ubuntu 20.04.

(In reply to Jan Honza Odvarko [:Honza] (always need-info? me) from comment #15)

@stdedos+mozilla: what's your use case?

  1. Disable all (breakpoints + debugger; keywords)
  2. Disable just the debugger; keywords (all occurrences)
  3. Something else?

The debugger; keyword is used in obfuscation protection loops to prevent users from opening the DevTools console, as explained in this page.
Here's an example of such code being used in the wild, see function check() at line #2:

    var tryCount = 0;var minimalUserResponseInMiliseconds = 200;
    function check(){console.clear();before = new Date().getTime();debugger;after = new Date().getTime();
    if(after - before > minimalUserResponseInMiliseconds){document.write(" Dont open Developer Tools. ");self.location.replace(window.location.protocol + window.location.href.substring(window.location.protocol.length));
    }else{before = null;after = null;delete before;delete after;}setTimeout(check, 100);}check();
    window.onload = function(){document.addEventListener("contextmenu", function(e){e.preventDefault();}, false);document.addEventListener("keydown", function(e) {
      if(e.ctrlKey && e.shiftKey && e.keyCode == 73){disabledEvent(e);}
      if(e.ctrlKey && e.shiftKey && e.keyCode == 74){disabledEvent(e);}
      if(e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) {disabledEvent(e);}
      if(e.ctrlKey && e.keyCode == 85) {disabledEvent(e);}
      if(event.keyCode == 123) {disabledEvent(e);}
    }, false);function disabledEvent(e){if(e.stopPropagation){e.stopPropagation();} else if(window.event){window.event.cancelBubble = true;}e.preventDefault();return false;}};

Disabling the debugger; keyword is not enough to resume execution in these cases.
So perhaps a new feature, being able to "silence" any function in the call stack (i.e. temporarily replacing its code with a no-op), so that when the first debugger; call is made by the obfuscation code and pauses everything, the user can go to the call stack and mark the deepest (or second or third etc deepest) function in the call stack to be silenced, so that when you resume execution that function will not queue up another recursive call.

I think this might be an alternative to what's suggested in comment 11 about blackboxing, because to blackbox you need to be able to see and select the code lines, and in lots of cases this anti-debugging trick is hidden behind sophisticated obfuscation + inside an anonymous function, so the call stack is the only way to figure out which function this defensive debugger; call is coming from.

Severity: normal → S3

I think having the ability to make "debugger" statements a no-op is a good first start.

To clarify, I mean all debugger statements.

See Also: → 1300934
Summary: Add a toggle to enable/disable all debugger statements → Add a toggle to enable/disable all debugger statements (separately from the breakpoint toggle)
Duplicate of this bug: 1846239

Here's a blog post describing how they patched SpiderMonkey/Firefox to rename the "debugger" keyword, effectively disabling it:

https://www.nullpt.rs/evading-anti-debugging-techniques

As a hacky solution, I was thinking we could add an environment variable to avoid generating the code associated with debugger statement when such environment variable is set.

Same thing is abused by sites.
Payload:

function _0x39426c(e) {
    function t(e) {
        if ("string" == typeof e) return function(e) {}.constructor("while (true) {}").apply("counter");
        1 !== ("" + e / e).length || e % 20 == 0 ? function() {
            return !0
        }.constructor("debugger").call("action") : function() {
            return !1
        }.constructor("debugger").apply("stateObject"), t(++e)
    }
    try {
        if (e) return t;
        t(0)
    } catch (e) {}
}
setInterval(function() {
    _0x39426c()
}, 4e3), console.log("%cSTOP! %s", css, "Great power comes great responsibility!");

Possible explanation(It's in French but Firefox translate works): https://www.stashofcode.fr/contourner-la-protection-debugger-des-sites-de-streaming/
Real world usage(check last few lines of this script): https://sflix.se/js/group_11/app.min.js?v=6.3

In my opinion the debugger statement as well as any other calls which act (introspectively) on the execution environment (like clearing the console, or even writing to the console!) should urgently be put into question as a whole. Urgently, because this and related "features" are successfully exploited by many webpages, with the expressed goal of curtailing the user of their browser's intended functionality!

The very, principal idea of debugging is analyzing the code without affecting it. Code should not be given the opportunity to deliberately become "Heisenberg", and much less so against the wish of the user! And much, much less so if the code isn't trusted (i.e. the user trusts a promise about what the code will do) in the first place!

I don't know what thought process, which disrespected these principles, led to the introduction of the debugger statement, but being able to deactivate this should be a no-brainer.

Assignee: nobody → hmanilla

This toggle is checked by default so that debugger statements are still enabled by default.
As for other settings, this is preserved on restart so that it is handy for users willing to
always keep them disabled. But it may be an annoyance for others forgetting that they once disabled them.

Assignee: hmanilla → nobody
Assignee: nobody → poirot.alex
Status: NEW → ASSIGNED

Current patch suggests introducing a Pause on debugger statement checkbox in the breakpoints panel.
This will be checked by default, so that we don't change current behavior.
It will be persisted exactly like any other debugger settings.
So for people willing to turns this off for ever, you have to click this once.
For the others, you will have to remember to re-enable this if you want to debugger statement to work again!

Duplicate of this bug: 1300934

I'm excited to see progress on this!

I posted this in the time-reversed-duplicate bug 1300934, but I wanted to resuggest it here: I would ideally like to see debugger statements handled as a per-host permission, with the same UX that Firefox uses for existing per-host permissions like web notifications and camera/microphone. I think it makes sense for debugger statements to be disabled by default; they have no benefit to the end user unless that user is a developer, in which case they are only useful on those sites that the developer is actively debugging, which will probably be a relatively small list of hosts like localhost and intranet-dev-server-4 and maybe qa.company.com. For those sites, Firefox should make it easy to enable the preference (‘Will you allow intranet-dev-server-4 to pause the debugger?’) and persist it; ideally, it's not something I'd have to toggle on and off manually whenever I start debugging for real versus inspecting a random website, and it also needn't be something that I have to dig for in layers of advanced devtools preferences.

Grouping this with privacy permissions also makes some sense because websites can exploit debugger statements to detect whether devtools is open, which could be seen as a privacy leak.

Pushed by apoirot@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/a4368fc0bf73
[devtools] Allow disabling debugger statement by a toggle in the breakpoints panel. r=devtools-reviewers,nchevobbe
Status: ASSIGNED → RESOLVED
Closed: 4 months ago
Resolution: --- → FIXED
Target Milestone: --- → 121 Branch

Is this something we should add to the Fx121 relnotes? Please nominate if so.

Flags: needinfo?(poirot.alex)

Release Note Request (optional, but appreciated)
[Why is this notable]: New feature for web developers in Firefox DevTools.
[Affects Firefox for Android]: n/a
[Suggested wording]: The Firefox Debugger now includes a new feature: an option to disable the debugger; keyword on the current page. This feature is accessible via a new checkbox in the Breakpoints side panel, labeled Pause on debugger statement, located next to the existing Pause on exceptions checkbox. By default, this option is enabled, meaning that the debugger statements are active unless manually disabled.
[Links (documentation, blog post, etc)]: Screenshot available in comment #28

relnote-firefox: --- → ?
Flags: needinfo?(poirot.alex)

Added to the Fx121 relnotes.

You need to log in before you can comment on or make changes to this bug.