Jekyll2022-08-08T00:29:32+00:00/feed.xmlThrimbor’s unnamed blogDecrypting the Arch Linux boot partition with a FIDO2 security key2022-01-09T16:00:00+00:002022-01-09T16:00:00+00:00/2022/01/09/decrypting-the-Arch-Linux-boot-partition-with-a-FIDO2-stick<p>If you own a laptop, it’s certainly not the worst idea to have its hard drive encrypted, so your data won’t be in other people’s hands, even if your device gets stolen.
Using a normal passphrase for that isn’t perfect though: It’s usually either too short to be secure, or too long to not be a hassle when rebooting your machine.
Using a FIDO2 Security key <em>can</em> be a better option, and I’ll show you how to set it up.</p>
<h2 id="warnings">Warnings</h2>
<p>The following sections will instruct you to modify various sensitive files. Errors on your or my part may render your system unbootable, so make sure you have plenty of time to fix eventual problems, and keep a copy of your old GRUB configuration and initramfs around until you verify that everything works as intended.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>To keep this post simple, I’ll only describe one way to set this up. I’ll focus on the hmac-secret FIDO2 extension, LUKS-encrypted partitions and Arch Linux (with systemd >=248). What I’m describing may be applicable to other setups, but this is what I use, and what worked for me.
Most importantly, you’ll need a FIDO2 security key that supports the hmac-extension. Manufacturers often don’t clearly advertise this feature, but many modern security keys such as the YubiKey 5 support it. I personally own and use a “Token2 T2F2-NFC FIDO2, U2F and TOTP Security Key”.</p>
<h2 id="registering-your-security-key">Registering your security key</h2>
<p>This is the trivial part. Plug in your security key (make sure it’s the only one plugged in), then run the following command:</p>
<p><code class="language-plaintext highlighter-rouge">sudo systemd-cryptenroll --fido2-device=auto /dev/sda3</code></p>
<p>Make sure to replace <code class="language-plaintext highlighter-rouge">/dev/sda3</code> with the path to your LUKS-encrypted partition, and be prepared to enter your passphrase.
When you’re done, one of the key slots in the LUKS header of this partition will be used for your FIDO2 security key.</p>
<h2 id="unlocking-during-boot">Unlocking during boot</h2>
<p>This is the more complicated part, and it took me a while to get this going. Most other instructions tell you to simply modify a line in <code class="language-plaintext highlighter-rouge">/etc/crypttab</code>, but it appears that this doesn’t work for the root filesystem - after all, this file lies inside the partition we’re trying to decrypt.
If you’re (like me) booting your system with GRUB 2, you might be able to make the necessary adjustments on the kernel command line in <code class="language-plaintext highlighter-rouge">/etc/default/grub</code>, but I couldn’t make this work.
So what we’re doing instead is letting systemd take care of the partition decryption. First of all, create the file <code class="language-plaintext highlighter-rouge">/etc/crypttab.initramfs</code>. When we’re done, this file will end up as <code class="language-plaintext highlighter-rouge">/etc/crypttab</code> in your initramfs.
Add the following line to it:</p>
<p><code class="language-plaintext highlighter-rouge">luks /dev/sda3 - fido2-device=auto</code></p>
<p>Replace <code class="language-plaintext highlighter-rouge">luks</code> with your mapper name (if you can’t remember, run <code class="language-plaintext highlighter-rouge">mount | grep "on / "</code> to find it), and <code class="language-plaintext highlighter-rouge">/dev/sda3</code> with the path to your LUKS-partition. If you’re using an SSD, add <code class="language-plaintext highlighter-rouge">,discard</code> at the end.</p>
<p>Next, we need to make sure there’s nothing interfering with our file. Edit <code class="language-plaintext highlighter-rouge">/etc/default/grub</code>, and find the line starting with <code class="language-plaintext highlighter-rouge">GRUB_CMDLINE_LINUX</code>. It should contain something like <code class="language-plaintext highlighter-rouge">cryptdevice=/dev/sda3:luks:allow-discards</code>, delete this part, save the file, and update your grub.cfg (for me, that’s <code class="language-plaintext highlighter-rouge">sudo grub-mkconfig -o /boot/grub/grub.cfg</code>).</p>
<p>Now, we need to configure and create our initramfs. Open <code class="language-plaintext highlighter-rouge">/etc/mkinitcpio.conf</code>, find the line starting with <code class="language-plaintext highlighter-rouge">HOOKS=</code>, and add the following hooks: <code class="language-plaintext highlighter-rouge">systemd</code>, <code class="language-plaintext highlighter-rouge">sd-vconsole</code>, and <code class="language-plaintext highlighter-rouge">sd-encrypt</code>, and remove <code class="language-plaintext highlighter-rouge">encrypt</code>. For me, the line looks like this now:</p>
<p><code class="language-plaintext highlighter-rouge">HOOKS=(base systemd autodetect keyboard sd-vconsole modconf block sd-encrypt filesystems fsck)</code>.</p>
<p>Save the file, and run <code class="language-plaintext highlighter-rouge">sudo mkinitcpio -p linux</code>.</p>
<h2 id="trying-it-out">Trying it out</h2>
<p>When you’re done, you should be ready to try it out. Make sure your FIDO2 security key is plugged in, and reboot your system. When your key’s LED blinks, press the button (or however this works on your key), and your system should continue booting, without asking for a passphrase.</p>
<p>When no FIDO2 key is found, systemd should ask you for your passphrase. I noticed that this takes quite a few seconds, but I intend to mostly use my key from now on, so this is just a minor inconvenience for me.</p>
<h2 id="further-reading">Further reading</h2>
<p>Here’s a small collection links that dive further into the topics covered above:</p>
<p><a href="https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system#LUKS_on_a_partition">https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system#LUKS_on_a_partition</a></p>
<p><a href="http://0pointer.net/blog/unlocking-luks2-volumes-with-tpm2-fido2-pkcs11-security-hardware-on-systemd-248.html">http://0pointer.net/blog/unlocking-luks2-volumes-with-tpm2-fido2-pkcs11-security-hardware-on-systemd-248.html</a></p>If you own a laptop, it’s certainly not the worst idea to have its hard drive encrypted, so your data won’t be in other people’s hands, even if your device gets stolen. Using a normal passphrase for that isn’t perfect though: It’s usually either too short to be secure, or too long to not be a hassle when rebooting your machine. Using a FIDO2 Security key can be a better option, and I’ll show you how to set it up.Telling your ThinkPad to shut up (no more 2x five beeps!)2019-10-07T00:55:00+00:002019-10-07T00:55:00+00:00/2019/10/07/telling-your-thinkpad-to-shut-up<p>If you followed my previous post to remove the whitelist in your ThinkPad T440p’s firmware, you probably have noticed a major annoyance:
Sometimes after turning on, your Laptop will let out two groups of five beeps, and then boot up normally. The reason for this is that your UEFI firmware is cryptographically signed, and now that you modified it, the signature is invalid. The beeping signals to you that the signature check failed.</p>
<p>Fixing this manually is not a trivial task. It involves recalculating SHA-hashes of UEFI volumes contained in your image, putting those hashes in the correct places in the image, calculating new SHA-hashes of the blocks where you put the additional hashes, applying the RSA algorithm to this hash (with a private key you have to generate), and finally putting the corresponding RSA public key in the correct place in the image.</p>
<p>If you think this sounds tedious - well, then you’re right.</p>
<p>That’s why I didn’t bother doing it manually, and instead wrote me some Python tools for that.</p>
<p>The first tool is a script called <code class="language-plaintext highlighter-rouge">verify.py</code>. What it does is similar to what your ThinkPad does on startup - it extracts the RSA public key (which is required to check RSA signatures), extracts and checks all hashes, and then checks if the hashes are properly signed. If you just want to check whether your firmware image is properly signed - this is the tool to use.</p>
<p>The second tool is a script called <code class="language-plaintext highlighter-rouge">sign.py</code>. It’s meant to sign a firmware image that wasn’t properly signed before. To do that, it recalculates and updates the SHA hashes in the image, generates a new 1024-bit RSA key-pair, calculates signatures for the blocks where the hashes are stored, stores those signatures, and then, finally, stores the public key of the generated key-pair in the file. If you have a firmware image with an invalid signature, and want to fix it, this is the tool to use.</p>
<p>Both tools can be found here: https://github.com/thrimbor/thinkpad-uefi-sign</p>
<h2 id="tldr">TL;DR:</h2>
<p>Alright, so this is how to use it. First of all, these are Python 3 scripts, and they use the “pycryptodome” library for hashing, key generation and signing. If you’re on Linux, I trust you to be able to install your operating systems’s package management to install these requirements. For macOS, you’ll probably have to use something like <code class="language-plaintext highlighter-rouge">homebrew</code> to install Python, and <code class="language-plaintext highlighter-rouge">pip</code> to install pycryptodome. If you’re on Windows, installing Python is kinda annoying, so you can download pre-packaged binaries built from my scripts (here)[https://github.com/thrimbor/thinkpad-uefi-sign/releases]. Due to those being packaged, they’re .exe-files instead of .py, so remember to replace the file ending in the following steps if you’re on Windows.</p>
<p>Now, we assume you have a modified firmware image, like the one you’ll get if you follow my previous post. Let’s try to check it’s signature:
<code class="language-plaintext highlighter-rouge">./verify.py uefi_patched.bin</code>
And we get:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INFO: Found public key modulus at offset 0x3b4576
INFO: TCPA block found at offset 0x2c3a00
INFO: Volume offset: 0
INFO: Volume size: 2818048
ERROR: TCPA volume hash mismatch
TCPA volume hash: b15c2d0ec469188f3037d40d51df36cdf9758e88
Actual volume hash: faa2e5984938d11c2cf75898121c1194a6544fd1
SIGNATURES INCORRECT!
</code></pre></div></div>
<p>As expected, signature verification failed. We modified the file containing the whitelist, so the old hash is incorrect now.
Let’s try fixing it:
<code class="language-plaintext highlighter-rouge">./sign.py uefi_patched.bin -o uefi_patched_signed.bin</code>
The output should be like the following:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INFO: Found public key modulus at offset 0x3b4576
INFO: TCPA block found at offset 0x2c3a00
INFO: Generating new 1024 bit key with 3 as public exponent...
INFO: Volume offset: 0
INFO: Volume size: 2818048
INFO: Volume hash updated
INFO: Signature calculated
INFO: TCPA volume block signed
INFO: Public key stored
IMAGE SIGNED!
</code></pre></div></div>
<p>Ok, this looks promising! Let’s run our verify-script again, this time for our new file:
<code class="language-plaintext highlighter-rouge">./verify uefi_patched_signed.bin</code>
And we get:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INFO: Found public key modulus at offset 0x3b4576
INFO: TCPA block found at offset 0x2c3a00
INFO: Volume offset: 0
INFO: Volume size: 2818048
INFO: TCPA volume hash verified
INFO: Volume signature verified
SIGNATURES CORRECT!
</code></pre></div></div>
<p>Fantastic! This means that, if we flash the new, signed image to our Laptop, there should be no more beeps!</p>If you followed my previous post to remove the whitelist in your ThinkPad T440p’s firmware, you probably have noticed a major annoyance: Sometimes after turning on, your Laptop will let out two groups of five beeps, and then boot up normally. The reason for this is that your UEFI firmware is cryptographically signed, and now that you modified it, the signature is invalid. The beeping signals to you that the signature check failed.Removing the M.2 whitelist on a ThinkPad T440p2019-09-24T04:20:00+00:002019-09-24T04:20:00+00:00/2019/09/24/removing-the-m2-whitelist-on-a-thinkpad-t440p<p>I recently picked up a Lenovo ThinkPad T440p, a pretty good machine - sturdy, decent battery life, and upgradeable to be a pretty powerful machine.</p>
<p>There’s just one problem. <em>Disobedience:</em></p>
<p><img src="/assets/t440p_uefi_whitelist/error_1802.jpg" alt="Thanks for nothing, Lenovo." /></p>
<p>Yes, this is one of <em>those</em> machines, those that try to tell you what you are allowed to install in it and what not.</p>
<h2 id="the-problem">The problem</h2>
<p>When your T440p turns on, there’s a special kind of software that gets run. This software used to be called “BIOS”, but nowadays, “UEFI firmware” more accurately describes it. This firmware is stored on an SPI flash chip located near the RAM-slots, and it’s responsibility is not only to initialize the hardware, but also to provide configuration options to the user and an interface to the operating system. Unfortunately for us, Lenovo chose to include a special module called “LenovoWmaPolicyDxe” in their firmware, whose sole responsibility is to check hardware found in the M.2 slot against a list of allowed hardware (called a whitelist), and, if it’s not on the list, prevent the system from booting.</p>
<p>Now, Lenovo is not the only company to do that, but it’s one of the more prominent examples. They’ve been pissing off their customers with this bullshit for <em>years</em>.</p>
<p>The solution to this is obvious: We need to modify the firmware (or the “LenovoWmaPolicyDxe” module, to be specific) to stop refusing hardware that’s not on the whitelist. Now, there are places on the internet where you just upload your firmware image, and someone will send you an appropriately modified image back. I’m not a huge friend of magic binaries sent to me by strangers, especially if they’re that critical, plus I’d like to know what’s going on inside of it, so that’s no option for me. Instead, I’m going to get out the tools and modify the firmware myself.</p>
<p>Note: If you’re not interested in the how & why and only want to see the end result, skip to the “TL;DR:” section at the end of this post.</p>
<h2 id="tools-used">Tools used</h2>
<p>Of course there are several tools required for this adventure:</p>
<ul>
<li><a href="https://github.com/LongSoft/UEFITool">UEFITool</a>, which is a tool to view, extract and modify UEFI firmware images. Also includes UEFIPatch, which we’ll use later to do the actual modifications to our image.</li>
<li><a href="https://ghidra-sre.org/">Ghidra</a>, a reverse engineering tool developed by the NSA. There are other options here, such as Radare2 or IDA (well, if you can pay for it, that is), but I went for Ghidra because it has a very good decompiler built in and an ok UI (as well as it being open-source and free of cost). Keep in mind though that Ghidra is still pretty new, so don’t be surprised if you hit a bug (I’ve hit several).</li>
<li><a href="https://gist.githubusercontent.com/erfur/c1461af7475665d6f7ba13b5c04b74c3/raw/95fdb01d4b5f9816f06bdf210783d31805aaf43a/little-behemoth.h">little-behemoth.h</a>, a C header file including all important UEFI data types.</li>
</ul>
<h2 id="dumping-the-firmware">Dumping the firmware</h2>
<p>I’m keeping this short, since it’s not the scope of this article.</p>
<p>To dump the firmware of your T440p, you need an SPI flasher and a SOIC8-clip, plus the software to actually to use the flasher. I used a CH341A, since it can be bought together with the SOIC8 clip for very little money, and since I’m on Linux, <code class="language-plaintext highlighter-rouge">flashrom</code> is the software I use. Just attach the flasher and clip appropriately and run this command to dump the firmware: <code class="language-plaintext highlighter-rouge">sudo flashrom -p ch341a_spi -r uefi.bin</code>.</p>
<p><strong>Keep in mind that the image you dumped may be corrupt if your clip doesn’t have a proper connection - always create multiple dumps (I did five) and compare them (with sha256sum for example), also try to check whether they look incomplete in UEFITool.</strong></p>
<p><img src="/assets/t440p_uefi_whitelist/flash_clip.jpg" alt="Flasher clip attached" /></p>
<h2 id="extracting-the-module">Extracting the module</h2>
<p><em>The firmware version used in this article is 2.53. If you use a different UEFI firmware version, there will be differences!</em></p>
<p>To extract the LenovoWmaPolicyDxe module, we’re going to use UEFITool. Start it up, and open your firmware image with “File”->”Open image file…”. To find the module we’re looking for, select “File”->”Search…”, then select the “GUID” tab, and enter <code class="language-plaintext highlighter-rouge">79E0EDD7-9D1D-4F41-AE1A-F896169E5216</code>.</p>
<p><img src="/assets/t440p_uefi_whitelist/uefitool_search.png" alt="UEFITool search dialog" /></p>
<p>After clicking “Ok”, a search result should appear in the “Messages” box. Doubleclick on it to get taken to the module it found, which should be named “LenovoWmaPolicyDxe”. Click on the small arrow next to it to expand the entry, right-click on the “PE32 image section” entry and choose “Extract body…”. Enter the name “LenovoWmaPolicyDxe” and save the file.</p>
<p><em>You may wonder - how do we know this is the module containing the whitelist check? Well, UEFITool can also extract the whole UEFI image, which is something I did to then search the extracted files for the error string that appears when you install a non-whitelisted WLAN card. I found this error string, encoded as UTF16-LE, in this module, together with a list that contained the vendor and product IDs of the WLAN card previously installed in my T440p. This is how I knew which module to inspect.</em></p>
<h2 id="analyzing-the-module">Analyzing the module</h2>
<p>Before we start with Ghidra, open up the <code class="language-plaintext highlighter-rouge">little-behemoth.h</code> file with a text editor of your choice. As it is, this file is not compatible with Ghidra, so you’ll need to find the line <code class="language-plaintext highlighter-rouge">typedef __builtin_va_list VA_LIST;</code> and change it to <code class="language-plaintext highlighter-rouge">typedef char *VA_LIST;</code>.</p>
<p><img src="/assets/t440p_uefi_whitelist/little-behemoth-mod.png" alt="Necessary modification to little-behemoth.h" /></p>
<p>After that’s done, we can start up Ghidra. Create a new project, and then press “i” to import a file into your project. Select the “LenovoWmaPolicyDxe.bin” we extracted earlier, and double-click on it in the project view.</p>
<p>You should now see a window called “CodeBrowser”, which is where our analysis will take place. If you’re asked to let Ghidra start an automatic analysis, do so. As a first step, click on the small arrow next to “Functions” in the “Symbol Tree” window on the left. This will allow you to see all functions that Ghidra’s analysis identified. What you’ll want to do next, is to select “File”->”Parse C Source…”. Click on the small “+” sign and select the “little-behemoth.h” file.</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-parse-c-source.png" alt="Import little-behemoth.h into Ghidra" /></p>
<p>Then click “Parse to Program”.</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-parse-c-source-result.png" alt="Import little-behemoth.h into Ghidra result" /></p>
<p>Good, now Ghidra understands UEFI data types, which will allow us to get much better decompiler output.</p>
<h3 id="inspecting-the-entry-point">Inspecting the entry point</h3>
<p>If you’ve ever used a programming language similar to C, you’re probably familiar with the “main”-function. You may have also wondered how and why this main function gets called - this is usually the job of another function, often called “_start” or something similar, and it is usually the first function to run when a program starts. When your OS starts a program, it inspects the file headers to find an entry describing the entry point - in other words, the beginning of the “_start” function.</p>
<p>UEFI utilizes many concepts and formats originally coming from Windows, such as the PE file format - yes, UEFI modules follow the same format as your usual Windows .exe file. This is good for us, since it allows Ghidra to understand the format and tell us where the module starts execution. Ghidra find the entry point, and calls the function that starts execution “entry”. Let’s check it out:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-entry.png" alt="Ghidra entry point analysis" /></p>
<p>Oof, that doesn’t look easy to understand. But wait, Ghidra can help clean this mess up! Let us first take a look at how such an entry point of a UEFI module usually looks like (taken from <a href="http://x86asm.net/articles/uefi-programming-first-steps/">here</a>):</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include <efi.h>
</span>
<span class="n">EFI_STATUS</span> <span class="nf">main</span><span class="p">(</span><span class="n">EFI_HANDLE</span> <span class="n">ImageHandle</span><span class="p">,</span> <span class="n">EFI_SYSTEM_TABLE</span> <span class="o">*</span><span class="n">SystemTable</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">EFI_SUCCESS</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>This gives us the information we need to give the parameters and return values of our entry point the proper types. In the assembly view, click into the line describing “param_1”, and press “T” to enter the type name “EFI_HANDLE”. Then press “L” to give it the name “ImageHandle”. Now, repeat this for the return value and the second parameter.</p>
<p>Now, double-click on that function call “FUN_00010ee8”.</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-init-globals.png" alt="Init globals before" /></p>
<p>This functions just gets passed the same parameters as our entry point, so repeat this process here. Clearly this function just stores some pointers to global variables, so with a double-click on them, you can give them proper types and names too (hint: The back arrow in the top left can take you back to where you came from). Make sure to give that function a proper name, too (“L” can rename variables <em>and</em> functions), and the result should look something like this:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-initglobals-result.png" alt="Init globals results" /></p>
<p>Now let’s look at our entry point again:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-entry-result.png" alt="Ghidra entry point analysis result" /></p>
<p>Whoa, that’s much better already! We can clearly see that our module is calling some “LocateProtocol” function four times, and then a function called “InstallProtocolInterface”. Both functions use GUIDs (globally unique identifier) - I didn’t bother to check where else these GUIDs show up, but I’m pretty sure that the GUID used with “InstallProtocolInterface” would show up in the modules requesting this module to check the whitelist! Clearly this registered interface must be where the magic happens, but from this registration itself, it wasn’t clear to me which function does the actualy checking. So let’s take a look around and inspect some of the other functions.</p>
<p><em>You may wonder what a “Boot Service” is. UEFI firmware provides certain services to firmware modules and operating systems. These services are grouped into two categories: Boot Services and Runtime Services. Runtime Services are available even when the operating system took over, while Boot Services are only available when the system boots and can not be used by the operating system. The transition between the boot and the runtime phase is handled by a firmware call named “ExitBootServices”</em></p>
<h3 id="old-friends">Old friends</h3>
<p>At this point I inspected the other functions that Ghidra found, in an attempt to clean up some of those weird function names. For example, take a look at the function at 0x10ff0: It has a variable that gets initialized to zero, compares a byte at the address it was passed against zero, and if it wasn’t zero, increments its internal variable and the pointer. Then it jumps to the beginning again. Seems familiar? Yep, this is strlen:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-strlen.png" alt="Ghidra strlen" /></p>
<p>Let’s try this again! Check out FUN_00010f94:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-memset-before.png" alt="Ghidra memset before" /></p>
<p>Ok, this one gets a pointer and two values. If the third parameter is not zero, the memory location our pointer points to gets set to the value of our second parameter. Our pointer then gets increment, the third parameter decremented, and this repeats until our third parameter becomes zero. In other words, param_3 count elements at param_1 get set to param_2. Sounds familiar?</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-memset-result.png" alt="Ghidra memset result" /></p>
<p>Yep, this is memset! What a difference proper names and types can make. There are a few more standard functions, and some custom ones for which I made up my own names. This is the list of names and addresses I ended up with:</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Address</th>
</tr>
</thead>
<tbody>
<tr>
<td>AllocatePool</td>
<td>00010e34</td>
</tr>
<tr>
<td>checkAgainstWhitelist</td>
<td>00010804</td>
</tr>
<tr>
<td>CreateEvent</td>
<td>00010ea4</td>
</tr>
<tr>
<td>InitGlobals</td>
<td>00010ee8</td>
</tr>
<tr>
<td>malloc</td>
<td>00010e60</td>
</tr>
<tr>
<td>memset</td>
<td>00010f94</td>
</tr>
<tr>
<td>printError</td>
<td>000106f0</td>
</tr>
<tr>
<td>snprintf</td>
<td>00010f74</td>
</tr>
<tr>
<td>strlen</td>
<td>00010ff0</td>
</tr>
<tr>
<td>strncpy</td>
<td>00010fac</td>
</tr>
<tr>
<td>vsnprintf</td>
<td>00010f08</td>
</tr>
</tbody>
</table>
<h3 id="cutting-to-the-chase">Cutting to the chase</h3>
<p>If you’ve paid attention so far, you may wonder what the “printError” and “checkAgainstWhitelist” functions do that appear in my table. Let’s take a look at printError:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-printerror.png" alt="Ghidra printError" /></p>
<p>If you’re familiar with format strings (like used by printf) you’ll know what “%04x” means: It prints a four-digit zero-padded number in hexadecimal. Still remember that error message in the first picture? If you ask me, these format strings look like they’re producing the number part of that message. Now let’s take a look at the third snprintf - it looks kinda weird, what memory address is 0x4d0? Let’s check 0x000104d0:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-error-string.png" alt="Ghidra error string" /></p>
<p>Bingo, this is where the error messsage comes from! Now you can see why I named this function printError. But how does this help us find the whitelist check? The magic word is “XREF”. You may have seen this word appear in the disassembly window, and it always comes with a name or memory address next to it. This address next to it is a place where the location we’re looking at was referenced.
Armed with this information, we can see that printError is referenced exactly once. Double-click on the xref to see where it’s referenced:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-printerror-usage.png" alt="Ghidra printError usage location" /></p>
<p>Taking a closer look, this part looks interesting:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-whitelist-var.png" alt="Ghidra whitelist usage location" /></p>
<p>Yep, DAT_00010290 is where the whitelist starts! If you want, you can teach Ghidra the structure of this whitelist:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-struct-def.png" alt="Ghidra whitelist structure" /></p>
<p>This will allow you to enter the type “whitelistEntry” when pressing “T” to assign a type. Ghidra even allows to mark areas of memory as an array by marking the memory, right-clicking and selecting “Data”->”Create Array…”. Just make sure to only mark the memory from the beginning of the whitelist up to the entry that starts with 6 (category=6 marks the end of the whitelist).</p>
<h3 id="slaying-the-dragon">Slaying the dragon</h3>
<p>There are multiple ways to eliminate the problem. After issues with my initial approach, I decided to just patch out the ID comparisons. There are three comparisons to patch, let’s check out the first:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-vendorid-before.png" alt="Ghidra vendor ID comparison before patch" /></p>
<p>The selected assembly instruction is the jump that gets executed when the comparison of PCI vendor ID and device ID fails. With a right-click and selecting “Patch Instruction”, we can just change the address of the jump, so that, no matter the result of the comparison, execution always continues at 0x108f1:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-vendorid-after.png" alt="Ghidra vendor ID comparison after patch" /></p>
<p>The next comparison to patch is this one:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-subsystem-before.png" alt="Ghidra subsystem ID comparison before patch" /></p>
<p>This compares the subystem ID and the “unknown” field. If the comparison succeeds, the jump to 0x10925 gets executed. We can just replace the JZ (which executes the jump if the two values were equal) with a JMP (which always jumps):</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-subsystem-after.png" alt="Ghidra subsystem ID comparison after patch" /></p>
<p>This takes care of the PCIe whitelist issue! Now we patch one more comparison to get rid of the USB whitelist as well:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-usb-before.png" alt="Ghidra USB ID comparison after patch" /></p>
<p>Again, this jump only gets executed if the comparison succeeds. We replace it with an unconditional jump:</p>
<p><img src="/assets/t440p_uefi_whitelist/ghidra-usb-after.png" alt="Ghidra USB ID comparison after patch" /></p>
<p>Now we’re essentially done, but unfortunately Ghidra does additional modifications to the binary if we try to export it again. So instead, we’ll use UEFIPatch. For this, we’ll need to keep track of which bytes we changed and how - you can do this by undoing and redoing the changes in Ghidra and write down the bytes, or export the binary and use a tool like vbindiff. If you do either of this, you should end up with a patchstrings like these:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>79E0EDD7-9D1D-4F41-AE1A-F896169E5216 10 P:0bc841390b0f8468010000:0bc841390be96901000000
79E0EDD7-9D1D-4F41-AE1A-F896169E5216 10 P:41390b7517:41390b7500
79E0EDD7-9D1D-4F41-AE1A-F896169E5216 10 P:41394b04741b:41394b04eb1b
</code></pre></div></div>
<p>These patchstrings include the GUID of the “LenovoWmaPolicyDxe” module, the module type (10=PE), the patching method (P means that we specify how the bytes looked before and after) and finally the before and after blocks. This means that, for example, the second string searches the module for the byte sequence 41390b7517 and replaces it with 41390b7500.</p>
<p>To see what we can now do with these patchstrings, see the next section.</p>
<h2 id="tldr">TL;DR:</h2>
<ol>
<li>Make sure you’re on firmware 2.53 (I recommend to disable the TPM as a precaution)</li>
<li>Create an image of your firmware (<strong>make sure it’s valid!</strong>)</li>
<li>Grab the “whitelist-removal.txt” patch file from https://github.com/thrimbor/thinkpad-uefi-patches/tree/master/T440p/2.53</li>
<li>Run UEFIPatch: <code class="language-plaintext highlighter-rouge">uefipatch original_firmware.bin whitelist-removal.txt -o modified_firmware.bin</code></li>
<li>Flash your T440p with the modified firmware</li>
</ol>
<p>On the first boot, your ThinkPad may let out five beeps twice, but then should continue to boot, and have no whitelist.</p>I recently picked up a Lenovo ThinkPad T440p, a pretty good machine - sturdy, decent battery life, and upgradeable to be a pretty powerful machine.