I've written about my fight with Google Chrome updates and broken features in the past. This time let's talk about the brain-dead decision to disable Backspace.
This was their rationale for the change:
We have UseCounters showing that 0.04% of page views navigate back via the backspace button and 0.005% of page views are after a form interaction. The latter are often cases where the user loses data. Years of user complaints have been enough that we think it's the right choice to change this given the degree of pain users feel by losing their data and because every platform
has another keyboard combination that navigates back.
So, just because 50 persons out of each 1'000'000 are f*king idiots, all the others have to suffer? Makes no sense to me.
To prove my point, let's look at the simple Google search: "Google Chrome backspace". It gives 238'000+ results. First few results are: "Backspace to go Back - Chrome Web Store", "Go Back With Backspace - Chrome Web Store", "Back to Backspace - Chrome Web Store", "How to restore the backspace key as a keyboard shortcut to go back in ...", "So where's the Chrome flag to RE-ENABLE BACKSPACE going back a...".
Apparently, I'm not the only one who is hurt by this change.
Hidden BackspaceGoesBack feature
When the change was first introduced in Google Chrome, developers also created a hidden feature that you could set and make Backspace work as it used to. To use it, you just need to launch chrome.exe with a command-line like this:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --enable-features=BackspaceGoesBack
But in commit 0fe1505a this feature was removed as well.
If you enter the commit number in Chromium Find Releases tool, you'll see that in went out in public in v61.0.3116.0. Another check in Chrome Channel Releases tool will tell you that as of this moment the change is already out for both Canary and Dev channels, and will hit Beta and Stable channels in next months:
So, let's fix this issue for good! And by "fixing it" I don't mean some stupid JavaScript-based Chrome extension (which doesn't work when JavaScript is disabled and in hundreds of other cases..), I mean a proper fix in the code.
Patching Google Chrome again
If you've read my previous post, you know the drill. Set the symbol path, load chrome.dll in IDA, get yourself some coffee and wait. Wait a lot. And after 20-30 minutes you'll be able to start working.
This is the commit that's causing our headaches: commit 0fe1505a and the corresponding place in disassembly of Chrome 58:
.text:0001819AB871 mov ecx, edi .text:0001819AB873 sub ecx, 33000 .text:0001819AB879 jz short locIDC_BACK .text:0001819AB87B sub ecx, 1 .text:0001819AB87E jz short locIDC_FORWARD ... .text:0001819AB8D9 locIDC_FORWARD: ; CODE XREF: chrome::BrowserCommandController::ExecuteCommandWithDisposition(int,WindowOpenDisposition)+BEj .text:0001819AB8D9 mov rcx, [rbx+0F0h] .text:0001819AB8E0 mov rax, [rcx] .text:0001819AB8E3 call qword ptr [rax+110h] .text:0001819AB8E9 .text:0001819AB8E9 doGoForward: ; CODE XREF: chrome::BrowserCommandController::ExecuteCommandWithDisposition(int,WindowOpenDisposition)+1C3j .text:0001819AB8E9 mov rcx, [r14+18h] ; browser .text:0001819AB8ED mov edx, r15d ; disposition .text:0001819AB8F0 call ?GoForward@chrome@@YAXPEAVBrowser@@W4WindowOpenDisposition@@@Z ; chrome::GoForward(Browser *,WindowOpenDisposition) .text:0001819AB8F5 jmp loc_1819AC7C2 .text:0001819AB8FA ; --------------------------------------------------------------------------- .text:0001819AB8FA .text:0001819AB8FA locIDC_BACK: ; CODE XREF: chrome::BrowserCommandController::ExecuteCommandWithDisposition(int,WindowOpenDisposition)+B9j .text:0001819AB8FA mov rcx, [rbx+0F0h] .text:0001819AB901 mov rax, [rcx] .text:0001819AB904 call qword ptr [rax+110h] .text:0001819AB90A .text:0001819AB90A doGoBack: ; CODE XREF: chrome::BrowserCommandController::ExecuteCommandWithDisposition(int,WindowOpenDisposition)+1DBj .text:0001819AB90A mov rcx, [r14+18h] ; browser .text:0001819AB90E mov edx, r15d ; disposition .text:0001819AB911 call ?GoBack@chrome@@YAXPEAVBrowser@@W4WindowOpenDisposition@@@Z ; chrome::GoBack(Browser *,WindowOpenDisposition) .text:0001819AB916 jmp loc_1819AC7C2 ... .text:0001819AB954 mov ecx, edi .text:0001819AB956 sub ecx, 33007 .text:0001819AB95C jz loc_1819AB9FC .text:0001819AB962 sub ecx, 2 .text:0001819AB965 jz short loc_1819AB9BC .text:0001819AB967 sub ecx, 1 .text:0001819AB96A jz short locIDC_BACKSPACE_BACK .text:0001819AB96C cmp ecx, 1 .text:0001819AB96F jnz loc_1819AC729 .text:0001819AB975 .text:0001819AB975 locIDC_BACKSPACE_FORWARD: ; feature .text:0001819AB975 lea rcx, ?kBackspaceGoesBackFeature@features@@3UFeature@base@@B .text:0001819AB97C call ?IsEnabled@FeatureList@base@@SA_NAEBUFeature@2@@Z ; base::FeatureList::IsEnabled(base::Feature const &) .text:0001819AB981 test al, al .text:0001819AB983 jnz doGoForward .text:0001819AB989 mov dl, 1 .text:0001819AB98B jmp short loc_1819AB9A3 .text:0001819AB98D ; --------------------------------------------------------------------------- .text:0001819AB98D .text:0001819AB98D locIDC_BACKSPACE_BACK: ; CODE XREF: chrome::BrowserCommandController::ExecuteCommandWithDisposition(int,WindowOpenDisposition)+1AAj .text:0001819AB98D lea rcx, ?kBackspaceGoesBackFeature@features@@3UFeature@base@@B ; feature .text:0001819AB994 call ?IsEnabled@FeatureList@base@@SA_NAEBUFeature@2@@Z ; base::FeatureList::IsEnabled(base::Feature const &) .text:0001819AB999 test al, al .text:0001819AB99B jnz doGoBack .text:0001819AB9A1 xor edx, edx .text:0001819AB9A3 .text:0001819AB9A3 loc_1819AB9A3: ; CODE XREF: chrome::BrowserCommandController::ExecuteCommandWithDisposition(int,WindowOpenDisposition)+1CBj .text:0001819AB9A3 mov rax, [r14+18h] .text:0001819AB9A7 mov rcx, [rax+0F0h] .text:0001819AB9AE mov rax, [rcx] .text:0001819AB9B1 call qword ptr [rax+108h] ; window()->MaybeShowNewBackShortcutBubble() .text:0001819AB9B7 jmp loc_1819AC7C2
What a mess!
Luckily for us, compiler decided to emit nice switch table in version v61.0.3153.2:
.text:0001806CE0C3 loc_1806CE0C3: ; CODE XREF: sub_1806CDF28+CFj .text:0001806CE0C3 lea eax, [rbx-33000] ; jumptable 00000001806CE00B default case .text:0001806CE0C9 cmp eax, 0Bh ; switch 12 cases .text:0001806CE0CC ja loc_1806CEACC ; jumptable 00000001806CE03B cases 2-11,13 .text:0001806CE0D2 lea rcx, off_1806CEC30 .text:0001806CE0D9 movsxd rax, dword ptr [rcx+rax*4] .text:0001806CE0DD add rax, rcx .text:0001806CE0E0 jmp rax ; switch jump ... .text:0001806CEC30 off_1806CEC30: .text:0001806CEC30 B2 F4 FF FF dd offset locIDC_BACK - 1806CEC30h .text:0001806CEC34 5B FD FF FF dd offset locIDC_FORWARD - 1806CEC30h ; jumptable 00000001806CE0E0 case 1 .text:0001806CEC38 91 FD FF FF dd offset loc_1806CE9C1 - 1806CEC30h ; jumptable 00000001806CE0E0 case 2 .text:0001806CEC3C B3 FD FF FF dd offset loc_1806CE9E3 - 1806CEC30h ; jumptable 00000001806CE0E0 case 3 .text:0001806CEC40 D5 FD FF FF dd offset loc_1806CEA05 - 1806CEC30h ; jumptable 00000001806CE0E0 case 4 .text:0001806CEC44 9C FE FF FF dd offset loc_1806CEACC - 1806CEC30h ; jumptable 00000001806CE0E0 default case .text:0001806CEC48 F5 FD FF FF dd offset loc_1806CEA25 - 1806CEC30h ; jumptable 00000001806CE0E0 case 6 .text:0001806CEC4C 21 FE FF FF dd offset loc_1806CEA51 - 1806CEC30h ; jumptable 00000001806CE0E0 case 7 .text:0001806CEC50 9C FE FF FF dd offset loc_1806CEACC - 1806CEC30h ; jumptable 00000001806CE0E0 default case .text:0001806CEC54 15 FE FF FF dd offset loc_1806CEA45 - 1806CEC30h ; jumptable 00000001806CE0E0 case 9 .text:0001806CEC58 43 FE FF FF dd offset locIDC_BACKSPACE_BACK - 1806CEC30h ; jumptable 00000001806CE0E0 case 10 .text:0001806CEC5C 65 FE FF FF dd offset locIDC_BACKSPACE_FORWARD - 1806CEC30h ; jumptable 00000001806CE0E0 case 11
To make Backspace work as intended, we can simply overwrite 2 entries in jump table.
.text:0001806CEC30 off_1806CEC30: .text:0001806CEC30 B2 F4 FF FF dd offset locIDC_BACK - 1806CEC30h .text:0001806CEC34 5B FD FF FF dd offset locIDC_FORWARD - 1806CEC30h ; jumptable 00000001806CE0E0 case 1 .text:0001806CEC38 91 FD FF FF dd offset loc_1806CE9C1 - 1806CEC30h ; jumptable 00000001806CE0E0 case 2 .text:0001806CEC3C B3 FD FF FF dd offset loc_1806CE9E3 - 1806CEC30h ; jumptable 00000001806CE0E0 case 3 .text:0001806CEC40 D5 FD FF FF dd offset loc_1806CEA05 - 1806CEC30h ; jumptable 00000001806CE0E0 case 4 .text:0001806CEC44 9C FE FF FF dd offset loc_1806CEACC - 1806CEC30h ; jumptable 00000001806CE0E0 default case .text:0001806CEC48 F5 FD FF FF dd offset loc_1806CEA25 - 1806CEC30h ; jumptable 00000001806CE0E0 case 6 .text:0001806CEC4C 21 FE FF FF dd offset loc_1806CEA51 - 1806CEC30h ; jumptable 00000001806CE0E0 case 7 .text:0001806CEC50 9C FE FF FF dd offset loc_1806CEACC - 1806CEC30h ; jumptable 00000001806CE0E0 default case .text:0001806CEC54 15 FE FF FF dd offset loc_1806CEA45 - 1806CEC30h ; jumptable 00000001806CE0E0 case 9 .text:0001806CEC30 B2 F4 FF FF dd offset locIDC_BACK - 1806CEC30h ; jumptable 00000001806CE0E0 case 10 .text:0001806CEC34 5B FD FF FF dd offset locIDC_FORWARD - 1806CEC30h ; jumptable 00000001806CE0E0 case 11
Mission accomplished! 🙂
In the next part of this blog series, I'll show you how to make this patch more user friendly and a few ways how to automate the patching (so that you can receive automatic Google Chrome updates, if you wish).
Till next time!
kao.