erfur's bits&pieces

[EN] Down the Rabbit Hole - Part III: Patching the Whitelist

Blogging is hard.

Following the trail of references

Picking up where I’ve left off, I will take a look at where the string is referenced.

fcn.000109fc (printError)

This function starts with calling a protocol which is referenced in SystemErrorLogDxe.efi, if the return value is not less than 0 (which I’m assuming means an error hasn’t occured) the rest of the blocks are executed.

The offset that points to the error string (which is at 0x10258) is used in the last block to craft the error message.

I have no idea what rbx holds, but depending on the value in its offset, the hex format thats added at the end of our error string changes. When the whole string is crafted in the buffer, it’s used as an argument in the next function call, which again I wasn’t able to resolve. But this whole function looks like it’s an error logging function.

This function has xrefs in fcn.00010b10.

fcn.00010b10 (checkID)

Up until this point, I didn’t think of asking the most important question: What does the application check to enforce the whitelist? The answer is simple: Subsystem Device IDs.

Device ID table

The program has a hardcoded table of IDs sitting at offset 0x10270:

Each entry consists of the following:

struct TableEntry {
    uint32_t category;      // Type of device
    uint16_t vendorID;      // Vendor ID
    uint16_t deviceID;      // Device ID
    uint32_t subsystemID;   // Subsystem Device ID
    uint32_t unknown;       // ???

When I checked the ID of my card with lspci -nn:

I had to go deeper with lspci -x to find the subsystem ID:

Meaning that:

Vendor ID:      8086        (Intel)
Device ID:      0085        (Centrino Advanced-N 6205 [Taylor Peak])
Subsystem ID:   8086:1311   (Centrino Advanced-N 6205 (802.11a/b/g/n))

My device’s entry is at the offset 0x102b0.

I tried to identify the rest of the IDs using this website and with some googling:

Vendor ID   Subs. ID    Vendor      Subsystem
8086:0089   8086:1311   Intel       Centrino Advanced-N + WiMAX 6250 2x2 AGN
8086:0187               Intel       Centrino Advanced-N + WiMAX 6250 AGN (USB)
8086:4238   8086:1111   Intel       Centrino Ultimate-N 6300 3x3 AGN
8086:4238   8086:1118   Intel       Centrino Ultimate-N 6300 3x3 AGN Variant
8086:0085   8086:1311   Intel       Centrino Advanced-N 6205 (802.11a/b/g/n)
8086:0085   8086:1318   Intel       Centrino Advanced-N 6205 (802.11a/b/g/n) Variant    
10ec:8176   10ec:9581   Realtek     RTL8188CE 802.11b/g/n WiFi Adapter
8086:0891   8086:4222   Intel       Centrino Wireless-N 2200 BGN
14e4:4358   14e4:0543   Broadcom    BCM43227 802.11b/g/n
168c:002b   17aa:30a1   Qualcomm    Atheros AR9285 Wireless Network Adapter
1795:0720               OKB SAPR    ???
1795:0715               OKB SAPR    ???
1795:0022               OKB SAPR    ???
10ee:2012   10ee:0009   Xilinx      ???
10ee:2013   10ee:0009   Xilinx      ???
8086:088f   8086:4260   Intel       Centrino Advanced-N 6235 AGN
---------------------------------[USB Devices]-----------------------------------------
1199:9012               Sierra      Wireless Gobi 3000 Modem device (MC8355) Variant
1199:9013               Sierra      Wireless Gobi 3000 Modem device (MC8355)
0bdb:1927               Ericsson    H5321 gw HIDf
0bdb:1926               Ericsson    H5321 gw Mobile Broadband Network Adapter
114f:68a2               Wavecom     (Probably USB Modem)
0f3d:68a2               Airprime    (Probably USB Modem)
1199:68a2               Sierra      Wireless Gobi 3000 Modem device (MC7304)

The table also holds USB device IDs. This makes sense because the protocol interface was also referenced in LenovoWmaUsbDxe.efi.

Back to fcn.00010b10. Depending on the second argument (held in rdx) one of two block sets is executed. The argument is checked againts the following values: 0, 1, 5, which are familiar as they are the three possible values of the first value in each table entry (there’s also 6 but it’s used as the terminating value to mark the end of the table). One block is for values 1, 5 and the other block executes for the value 0. This is consistent with the ID table because entries with values 1, 5 are USB devices while the entries with 0 are PCI devices. I will skip the block for USB devices for obvious reasons.

PCI ID check block (aka where the whitelist is enforced)

The block starts with checking for the terminating entry:

Then the current entry is compared with the ID that was given as an argument to the function:

If the comparison fails, the next entry is checked if it’s the end of the table (0x10caa). If it is, the printError function is called (jump to 0x10bcd). If there are still entries left, the flow goes back to 0x10c7b for the check on the next entry.

If the entry is identical with the ID, The last value in the entry is checked (0x10cc5). If it’s 1 then unknownProtocolHandle2 is called. After looking through the references I found that it’s installed in the MiscGaIoDxe.efi application, which uses the protocol from CpuIo.efi. The names themselves don’t reveal much, so I still don’t know what this block does.

After some research on the entries, especially on the Intel cards, I found a feature that correlates with the values at the end of these entries. All the cards that is 1 features Intel vPro. One Intel card that does not have vPro also has a 0 value. I will assume that this is the case (Because of time constraints, I couldn’t go deeper to find out what this protocol actually does. I will have to continue with this assumption). Since I do not want anything to do with vPro, I will not use this block in my modified flow.

After setting sil accordingly, LenovoScratchData variable of the card (that is unique to the guid) is read and the value that falls into var_44h is compared against sil. If they’re not equal, then the value is set and the data is written back.

Here’s an interesting block:

The current ID is compared against the device Centrino Advanced-N 6235 AGN (which already exists in the table) and if it’s a match, LenovoScratchData is written back with the value at var_4fh set, then ResetSystem() function is called from the RuntimeServices table. No idea what this is for either.

After all this hassle, a couple more redundant operations are executed and the function returns. This function is referenced three times in InterfaceFunction, each for a different type of device. There’s not much to see in that function that’s relevant to my goal, so I’ll skip it entirely.

Getting rid of the whitelist

Now that I know mostly (okay maybe partially) how this thing works, I know that there are different solutions to my problem, some of which are:

The first option seems risky as I don’t know how the other applications will behave when the LocateProtocol call fails. The second one seems the safest and the most convenient because there are many entries in the table that I will never use. However, I’m here to slay the whitelist and that’s what I shall do.

Modifying the program flow

What I want to do is let the whitelist take care of the cards that are already in the table and not go into the error function for the rest. To accomplish this, I will modify the block in which the last entry is checked. If a card is compared against all entries and there are no other entries, the error function will be called. This part happens in the block 0x10caa:

This jump goes to the printError function. I will change it to 0x10de3 so that there is no error and the program will continue like the card is authorized.

After this anticlimactic solution, it’s time time to put this back into the image and flash it.

Putting everything back together

To test if the modification works successfully, I used an Intel Wireless-N 135 card from my old laptop. I put the card in and tried to boot my machine. The result was expected (sorry for the potato quality):

Then I tore the machine down and connected the BIOS chip to the USB programmer:

I dumped the current BIOS and patched it using UEFIPatch tool. I’ve created a pattern to patch the image:

79E0EDD7-9D1D-4F41-AE1A-F896169E5216 10 P:0AFFFFFF:20010000 

I erased the chip and then wrote the modified image back using the programmer’s software:

After verifying the written image, I put everything back together and tried to boot the machine. The machine did boot! The wifi card was recognized and I was able to connect to my router. I could see the card in the lspci output:

But wait, there’s more?

No, just kidding :). I accomplished what I’ve set out to do and learned a lot in the process. I hope I was able to pass it on. Now, off to other adventures. Thanks for reading.