Microcode is an abstraction layer on top of the physical components of a CPU and present in most general-purpose CPUs today. While it is well-known that CPUs feature a microcode update mechanism, very little is known about its inner workings given that microcode and the update mechanism itself are proprietary and have not been throughly analyzed yet. We close this gap by both analyzing microcode and writing our own programs for it. This talk will give an insight into our results and how we achieved them, including live demos of what we can do and technical details.
Given the complexity of modern instruction sets hardware vendors moved to hardware designs incorporating complex decode units. A single instruction of the complex outwardfacing instruction set is translated to multiple instructions of the simpler internal architecture. While it is possible to do this translation in hardware alone, some instructions would require huge amounts of space on the silicon and increase costs. These complex instructions are instead decoded using a software-like approach called microcode. While processing such an instruction, the CPU internally evaluates a sequence of operations, micro-ops, which decode the complex instruction into the corresponding simpler operations that are performed by the hardware. In the light of the existence of hardware bugs such as the infamous Pentium fdiv bug, hardware vendors developed a process to fix those errors without requiring a CPU replacement. However the microcode is stored in a ROM on the CPU die and can not be changed after production. Also relatively simple or often used instructions are still decoded in hardware. The update is instead achieved using microcode updates, which intercept certain instructions and replace their faulty implementation with a new, fixed version. These updates are applied either by the BIOS/UEFI or the operating system during early bootup. While the update process is well documented, the Linux kernel offers a module for it, and the updates are provided by the CPU vendors, the actual semantics of microcode are proprietary. Most update mechanisms are protected by signatures or other cryptographic primitives. However there were some indications that older CPU models (until around 2013) do not have a strong cryptographic protection and thus would accept custom updates. Given this chance we started to analyze the behavior of the CPU given our own updates and used these observations to infer the semantics of microcode. After some time we reverse-engineered enough of the semantics to write our own microcode programs. These programs range from very simple proof of concepts to stealthy backdoors and defensive primitives. As an additional approach we also performed hardware analysis. By delayering the CPU and imaging it with both an optical and an electron microscope we could locate and read out the ROM containing the microcode. After processing and reordering the physical connections we retrieved the hardwired microcode of the CPU. This gave us more information on what can be done with microcode and allowed more insights into the intended behavior than our reverse-engineering approach. In this talk we will first start with a (short) crash course in CPU architecture and where microcode is used in practice. We will then cover our reverse engineering methods and how we were able to discover the semantics of x86 microcode. We then demonstrate, also with live demos, this knowledge with multiple microcode programs that implement both defensive measures as well as provide an attacker with hard to detect backdoors. Lastly we will discuss security problems and possible solutions to protect against them. We also provide example microcode programs for your own CPUs (use at your own risk) and a kernel patch to apply them on a Linux system. Also we will have some systems with us so you can try your hand at writing some microcode yourself.
Speakers: Benjamin Kollenda Philipp Koppe