If it weren’t for the FLOCKDN flag, the PRRs could be dealt with as follows:
python chipsec_util.py mmio write SPIBAR 0x84 0x4 0x00000000
python chipsec_util.py mmio write SPIBAR 0x88 0x4 0x00000000
I believe this is equivalent to what prr2.exe does. Unfortunately it won’t fly with FLOCKDN. I tried a couple approaches that could make it possible to remove the PRRs without having to resort to external flashing. I think it could work with an exploit like this one by Cr4sh (also check out his excellent write-up: Exploring and Exploiting Lenovo) but the laptop in question is somewhat too new to be affected, or maybe with a specially-crafted S3 Boot Script, which can also applied with Chipsec’s s3script_modify.
In the end since I already had a plan to disassemble the laptop one more time anyway, I decided to follow the path of least resistance, that is to defeat the protection by modifying the UEFI firmware where FLOCKDN is set, which at last brings me in line with the topic of this thread.
TL;DR In my BiosRegionLockDxe there are 2 places where the value 0x8000 (= 2^15, equivalent to testing for the most significant bit, which is FLOCKDN) appears. Since one of those places looks similar to what @Wootever posted, I first tried the quick fix of changing the value to 0x0 as well (so that the test is always negative). However, this changed nothing, both FLOCKDN and PRRs were still in place. I then delved deeper into the disassembled code and it occured to me that FLOCKDN seems to be set outside this module (perhaps somehow dependent on the return value from it) but the module itself is responsible for implementing the Protected Range Registers.
In the end, I replaced two conditional jumps in the module entry point with NOPs (0x66 0x90), which bypasses nearly all of the code in this module. FLOCKDN still gets set but it doesn’t matter anymore because the PRRs are zeroed:
python chipsec_main.py -m common.bios_wp
2
3
4
5
6
7
8
9
10
11
12
[*] BIOS Region: Base = 0x00200000, Limit = 0x007FFFFF
SPI Protected Ranges
------------------------------------------------------------
PRx (offset) | Value | Base | Limit | WP? | RP?
------------------------------------------------------------
PR0 (84) | 00000000 | 00000000 | 00000000 | 0 | 0
PR1 (88) | 00000000 | 00000000 | 00000000 | 0 | 0
PR2 (8C) | 00000000 | 00000000 | 00000000 | 0 | 0
PR3 (90) | 00000000 | 00000000 | 00000000 | 0 | 0
PR4 (94) | 00000000 | 00000000 | 00000000 | 0 | 0
[!] None of the SPI protected ranges write-protect BIOS region
The output is from a command-line wrapper script I wrote to automate the operations a bit, I can share it if anyone is interested but basically the BIOS flash is done with FPTW64.
Some practical considerations (perhaps only for this particular laptop):
- Flashing an image generated with Insyde H2O Easy Binary Editor (EZE) (x86) 100.00.03.04 seems to end up with a boot loop, and the only way to recover from it is to use an external programmer. Perhaps this tool is too new for this platform, version 100.00.02.13 seems to work fine.
- Reflashing ME (2MB) or the whole chip contents (8MB), which includes the ME with another (earlier) version seems to cause a boot loop as well but this can be resolved by just taking off the cover and unplugging both batteries for a while to reset the settings to their defaults.
- Boot loops caused by incorrect BIOS settings can generally be resolved the same way as in (2).