 I think I have this Goal Stereoge, fantastic. All right, so our next talk on this stage, we are here on CCC Camp 2019 and here on the Plank Stage Direct next to the 300 Monkeys Village. We have two very special speakers for you. So I want to give a warm applause and warm welcome to Robert and Christian. So Robert and Christian will talk about processors in processors and what you can find out by dissecting them and maybe how to dissect them. In the end, we'll do a short Q&A. I will come to you with the microphone and so we have your question on the stream and on the recording. And I want you to have much fun today with dissecting the AMD Platform Security Processor. So, have fun. Welcome to dissecting the AMD Platform Security Processor. Who are we? This is Robert. He's a PhD candidate at the TU in Berlin and his research focuses on AMD encryption technology. And I actually did my master thesis about the AMD Platform Security Processor with him. And what this exactly is, I'm going to tell you in just a second. So this talk is going to present our firmware research, but we're not really firmware researchers as is. So we want you to get to know our methods of reverse engineering, such a deeply embedded and proprietary system such as the PSP. So this talk is suitable for everybody who's interested in reverse engineering, everybody who might be curious what processors inside processors do and maybe also people who are into x86 firmware. Let's start off with some background. If you've read the news recently, you might have recognized that AMD's market share is growing massively. So it might be worth to look at the security of AMD CPUs more closely than ever. The AMD secure processor, which we're going to call PSP in this talk, is an ARM Cortex A5 built into your AMD CPU since 2013. It's, as I said, a CPU inside your CPU and it exists both on desktop as well as server variants of AMD processors. So the current Ryzen and Epic series. Unfortunately, the PSP runs undocumented and proprietary firmware. It doesn't have full access to the system memory, including your actual x86 processor. And you might argue that it's very similar to the Intel management engine. Some of you might have heard of before. So what did we learn from others? Regarding AMD subsystems, there's the system management unit responsible for power management amongst others. It was dissected by Rudolf Marek already in 2014 at the 31 C3. And he managed to get arbitrary code execution on this very delicate piece of hardware. The Intel management engine was covered with two talks at the KS Congress in 2017. There were several vulnerabilities over the last years, including remote code execution. There are therefore open source projects that try to neuter the abilities of the management engine. So to sum up our motivation for this research and especially the focus of this talk, we wanted to reverse engineer that thing. We wanted to see if we can understand the inner workings of the PSP, although it's proprietary. There are cloud security or memory encryption features by AMD, which we're not gonna cover so much in this talk, but the PSP is a crucial component to implement these. And we're about to publish a paper about these technologies in the near future. And of course, system security. Does the PSP make our system more secure or does it make it less secure? First of all, we need to talk about the firmware and where the firmware actually is because in the beginning of this research, we didn't know where it is. What you see here is a Lenovo ThinkPad A285 from the bottom. You can coarsely spot a CPU under a heat sink. You can spot an SSD and what's a bit harder to spot is an SPI flash. This is a non-volatile memory that is responsible for storing the BIOS or today it's called UEFI. So in a traditional boot, the CPU would first grab code and data from this SPI flash. And only then after having proper drivers, it can load such a complex thing as an operating system from the SSD. An AMD boot looks a bit different. As we already knew before our research, the PSP was a crucial part of the boot process. In fact, it boots before the x86 CPU comes up. So we assumed it might be part of the UEFI or at least it might be stored on the SPI flash too. So the contents of the SPI flash are called UEFI image and this 16 megabyte image actually is completely replaced when you do what is still called often a BIOS update. The format of this UEFI image is standardized. So why don't we just have a look inside? Using the open source tool UEFI tool, we can parse the UEFI image. And what we see here among other things is for example that it's exactly a 60 megabyte in size. That's exactly the size of the SPI flash memory. And we see some things related to the UEFI to the boot process of the x86 CPU. What we also see is something called a padding, a non-empty padding. And this non-empty padding is where it gets interesting. Using the popular firmware analysis tool BinWalk or more specifically its option to recognize processor instructions of different architectures and using it on the BIOS image, we see that there are ARM instructions inside. Remember it's an x86 BIOS update. So we might be close to the PSP firmware we are looking for and we were really close. So the central data structure in what we call the firmware file system of the PSP is a directory which you can see here in a hex editor. So a directory as we found out starts with a magic string. In this case it's dollar PSP. There's a checksum that provides some error detection but no nothing else. There's a number of elements that will be listed in this directory and something else we haven't found out about. One of those entries or each of those entries consists of a type. It's just an integer size all in little endian and an address where it's located inside the UEFI image. And then there's one very special type of directory entry which actually doesn't point to for example data or code but it pointed to another directory which we call a secondary directory. This might be two legacy reasons. Maybe older PSPs could only store 16 entries in a directory so this was a way to actually have larger directories than this. But how did we get there? How did we get to the directory? Now we use binwalk to just find some arm instructions somewhere inside the UEFI image but to be able to properly pass the firmware we had to find the firmware entry table and this firmware entry table we found out about through the Corbwood project. The Corbwood project is an open source bias implementation which used to support systems with early versions of the PSP2. And the firmware entry table is, there's only one of those in one UEFI image. It begins with a specific byte sequence and it lists pointers to firmware blobs such as PSP directories. So now we end up with what we call entries or what you could call the files of the firmware file systems. And there are two major and important types of entries you found. One type is a public key entry. This data structure holds information about a cryptographic key that can be used to verify signatures of other components. This is actually the only data structure that is publicly documented by AMD as it's part of one of its memory encryption technologies. And apart from one special key, the root signing key, all the keys can contain signatures themselves too. The second important type is a header entry. An entry with a header. And it contains a header of 256 bytes with metadata such as what key was I signed with. And these entries can have a trailing signature and they can, for example, be ZLIP compressed too. So now that I've shown you what we found out about this proprietary symbol file system, I want you to show, I want to show you PSP tool. PSP tool is an open source, now open source tool we developed to pass PSP firmware from UEFI images. So what do we have here? Can you read this? What we have here is a UEFI update or BIOS update for the Lenovo ThinkPad you saw earlier from the bottom. So using PSP tool, we can get an overview of all the directories and entries that we passed from this. So using also some other old core boot resources, we found out which integer type belongs to what kind of string. So we found some things like a PSP firmware bootloader, something like a system management off chip firmware or similar things. If you would like to start exploring the firmware that runs on your AMD processor, you can, for example, extract and uncompress all entries of this specific firmware. And now what you will find is a directory with all these entries as separate files and, for example, keys possible by OpenSSL too. If you want to have a short glimpse inside one of those files, we could, for example, look up some strings in one of those files. Let's say, for example, the PSP boot time trustlets, whatever that is. And there we go. This might be the perfect starting point to start reversing the PSP boot time trustlets. So PSP tool is available on GitHub. You can get it and get a BIOS update of your favorite machine and then check out the firmware that's inside. So now that we got hold of the firmware itself, of course we wanted to find out if we could execute our own code or if we could manipulate existing code. For that, we had to manipulate the UEFA image. And this is feasible in two manners. The first one is through software. For example, through an operating system, sorry, through a BIOS update utility inside your running operating system. This will write the new UEFA image to a specific area and memory, trigger system management interrupt, and on the next reboot it will write the new contents of this UEFA image into your SPI flash memory. Then of course from the BIOS configuration during boot up, you can oftentimes also program a new UEFA image. But although this might be suitable for a real world attack, for our experiments it's not very handy since as soon as we would have a non-functioning UEFA image, the system wouldn't boot anymore at all and we wouldn't be able to get back to a functioning state. So that's why we did it through hardware. Having access to the SPI chip on the mainboard and using an SPI programmer. Now what you see here is again our Lenovo ThinkPad. In the background you can see that we're connected to the flash and what we see in front is an SPI programmer. So the black thing on the bottom is the programmer. It's like five euro hardware and in this case it's combined with a 1.8 volt logic level shifter since the flash on the Lenovo ThinkPad runs at an unusual voltage. And SPI is a really simple protocol. It's also used for SD cards and LCDs. You might have heard about SPI already yesterday in Trauma Hudson's talk about the SPI-SPI and it mainly consists of four channels. Chip select that means is the master, the chip set speaking or the slave, the SPI flash memory. Data out and data in for communication in the respective direction and the clock for synchronization. So with this we can externally program the flash and then we can boot the system and see what happens. But the problem is this gives us only binary output. Either the system boots or it doesn't boot. But we wanted something more sophisticated. So we took a logic analyzer. A logic analyzer is an electronic instrument that can record the data flow on electronic lines, logic lines. So we hooked this onto the flash, then booted the system and recorded the boot procedure. And through this we wanted to see what parts of the SPI flash are exactly accessed at what time during the boot process. Using the accompanying software of this logic analyzer, it's actually a salier logic analyzer. It's around 350 euros, but there are definitely cheaper ones available. You can see, you can really see what's happening. So for example this is a read command on the data inline. So that's a command sent from the chip set to the SPI flash memory. That the hex by three is a read is documented in the manual of the SPI chip. And then what follows is an address e20000. And then the flash chip will gladly respond with data at that position. We're actually not so much interested in the data that comes back. Because as you remember, this is what we control. We can program the SPI flash memory as we want. We're more interested in the order of the read commands especially. So we wrote another small tool. It's called PSP Trace. It takes one of those traces by the logic analyzer on one hand and on the other hand, it takes in the UEFI image and it will parse the file system, the firmware file system in it and correlate that information. So what you'll end up with is a really nice overview of the boot process. And it's first of all the proof that the PSP comes up first. PSP code is loaded first. And then only later it follows the UEFI. But we'll get more into detail about this one soon. So from those experiments, we gained some important insights about this black box. The entries are cryptographically protected by Signature. We didn't only find the Signature, we found out if we would alter a byte in the entry, it wouldn't boot anymore. So one of those header fields determines according public key, compressed entries are actually uncompressed first and only then they are Signature checked and there's the header field that determines the exact size. So as we also found out the AMD root public key is does not have a signature. But when we would try to alter this one, the system wouldn't boot anymore. So we assume there's maybe a hash of the root key in read-only memory. What you can see here is just to give you a glimpse of what this looked like to us in the beginning, a header of such an entry, a body and a trailing Signature. So with this, I'm gonna hand over to Robert to tell you about what we actually found out about the firmware. All right, thanks. So if you look again at SPI trace of the PSP firmware, you will see something like this. So in the first four entries, what you see here, what happens is the following. So a component, usually an on-chip bootloader will read the firmware entry table, determine the PSP directory, load the AMD public key, use the AMD public key to verify the authenticity of the FW bootloader and then execute it. So you can see here, there's a rather large delay after that happens. So in that delay, the PSP FW bootloader, which is loaded from Flash, is executed. So the PSP FW bootloader initializes the system and when it's done that, it will load additional applications from the Flash, which you see in subsequent accesses to the SPI Flash. Now, so this bootloader is the first component, which is loaded from Flash, which contains instructions and this huge delay gave us the idea that we want to have a closer look at that specific component, right? So we can extract that component from an image using the PSP tool and then just, I don't know, again, first look at some strings. Let's see what we find in this bootloader. So this is an output of the strings contained in a PSP bootloader from an AMD Epic, so the server-reunt system. So what you see here actually, there's some strings which are interesting for us. For example, it says PSP FW bootloader version, right? So when you just have this component at hand, you might think about what could it be, but okay, yeah, there's a string saying PSP FW bootloader version, so it's not too far-fetched to say this is the PSP FW bootloader. All right, so strings are always the first thing you could imagine to check and might be interesting, but let's have a look at the binary itself. So as we know already, the first hex 100 bytes are just the header. So what comes after that? So we took the binary and put it into a disassembler. And what you would see at the first instructions after the header is something like this. So if anyone here has some experience with the ARM architecture, maybe someone already recognizes what that is. If not, here's the explanation. So the ARM architecture defines a table for entry points for privilege code. So any asynchronous event, such an interrupt, system call, a page fault, the CPU has to continue execution at a specific address. And that specific address is defined in a fixed table. And this table, the structure is defined. And when you look at the highlighted part, what you see here is that at each offset, you have a fixed entry for the reset vector, for undefined instructions, for supervisor call, and so on. And each of those vectors is just a single instruction. So what happens is that an operating system programs that table in a way that, okay, the first vector, the reset vector, jump to my initialization code. And then for the SVC vector, this is just a jump to my system call handler. And from there on, I will determine what system call happened. So when you combine those, you will see the sixth entry is not used and the sixth entry in our disassembly is a knob instruction, so it does nothing. So this is also like educated guests from outside, this is the vectors table. So let's continue with that assumption. If that is the vector table, we want to have a look at the reset vector code because that code initializes the system. So, oh yeah, this is the assumption so far. And these are the first instructions which are executed on the reset vector. So why are we going through this in the first place? So one thing we want to learn now is where in memory this bootloader is executing? Because so far, we only know that it's loaded from flash and that it's executed, but we don't know where it is loaded, where in memory. Okay, so we look at the reset vector, the reset vector should initialize the system and what we see there is just the reset vector. We see there a configuration of a register called V bar. So the V bar register on ARM architectures defines the location of this table in memory and it's an absolute address. So this is a clear indication that at address hex 100, the PSP FW bootloader needs to have its vector table and it's loaded at that specific address. So now we know the physical load address of that component and we know all the locations of the different handlers for system call, for page fault and so on. All right, now we continue. So this is the code snippet you saw before with some additional lines after that. Just a few instructions later, you will see that it enables paging. So the exact instruction here is not really relevant, just that for you to know, this last instruction on the slide here will effectively enable paging. So now we use virtual memory so we need a page table which converts virtual addresses to physical addresses. Now, where in memory is that page table located? Again, on ARM, we have a specific register for that. It's a ttbr0 and this register is configured there. So now we know the physical location of this bootloader component and we know the physical location of its page table. All right, its page table is loaded at hex14000. It's also a fixed physical address. So the next thing we wanted to have a look at is how can we access this page table? We want to get more information about the system. So this page table will define which virtual address maps to which physical address with what attributes. So whether a page is writable or readable or executable. So we wanted to get that information too. So we know the address of the page table, but of course, we just statically analyze the binary. We don't have any access to the running memory. So what can we do about that? Emulation. So on a high level, an emulator can emulate the hardware environment of a device. So let's say we would be able to emulate the PSP. We could just stop execution at an arbitrary point in time and read out the memory at an arbitrary address. So that's how we could get the page table. So an emulator needs to emulate both the CPU and the peripherals of the target system. So one of the most common emulators which is also open source is Kimo. And Kimo has support for ARM. So this is good because the PSP is also an ARM CPU. And Kimo can, for example, emulate a whole Raspberry Pi 2. But of course, Kimo does not have support for the PSP. It's a proprietary, deeply embedded core which is not, whereas no information publicly available. So Kimo can, however, emulate the CPU part. So just a core. However, we don't have information over the memory layout or any peripherals. So for example, when you want to emulate an ARM system, you probably want to emulate an interrupt controller and there are very common interrupt controllers such as the Geek V4 or V5 or whatever. None of those is used in the PSP. They use a custom interrupt controller. So we're kind of out of luck there. However, there is Avatar 2. So Avatar 2 is a framework that allows you to implement device emulation in Python which is way simpler than having to patch QEMU to support all of the PSP functionality. Avatar can do a lot more, but I will just focus on the stuff we used from that. So what we have now is here's an example of how such a simple device emulation could look like. So when you have an Avatar 2 setup, you can program a peripheral with just defining two functions. In this example, I defined a hwread and an hwwrite function. The write function does nothing. It just ignores whatever is written to this peripheral. And the read function just returns an arbitrary value. So this is a stupid device, but it shows how simple it is in Avatar to create a device emulation. So you can easily extend that example with a more complex setup. So this specific device, if you would map it to an address of the emulation, when you read from that address, it will always return the same value. Now, if you want to emulate the PSP using QEMU and Avatar, it can be as simple as I will show you now. So on Avatar, it's Python based and uses QEMU as a backend. You create an Avatar object and then you add memory information to that object. So you say, hey, Avatar, I want to have a file loaded into memory at this specific address. So for example, this line, the second line here, says load my bootloader binary, which I just extracted at address zero. So then we have 100 bytes of the header and at address hex 100, our reset vector starts. All right. The bootloader uses some stack, so I'll give it just another memory page of stack memory saying, hey, at this address, I give you some additional memory. If you look at the disassembly of the bootloader, you will see that this is the exact page that the bootloader expects its stack to be. All right, so now we have the binary, we have stack memory, we had some peripherals. So the bootloader expects some peripherals to be there you don't need to really implement all of the functionality but let's say the bootloader expects that there is an interrupt controller which has a register saying I'm version five. So what you do is, okay, I added custom peripheral, it does nothing except for returning five at the correct offset. You don't care about the functionality, you just want the emulation to continue. Okay, so I will add some peripheral. Then I will just say, okay, I initialized the chemo target, I set the start address, which is the hacks 100, I set a specific CPU module, I initialize the whole thing, I set a breakpoint to an address where I want to stop the emulation and then I call chemo continue. And this will start a chemo session with the memory layout I just defined in this Python setup and the weight will return once the breakpoint is hit. So this is a very, very simple way to emulate unknown hardware. I just looked up some bits of where in the physical memory the binary expects to be and then I just start emulating. And I don't even care what any complex peripherals such as the IQ controller, I can just return the value that the code expects. All right, so we have a demo for that and the demo is also available on GitHub. So with the demo you can emulate the PSP firmware up to a certain part. I need to switch. So let's hope the demo got deems also worthy showing you this demo. All right, so what you see here is I will now execute the Python script which I wrote to emulate parts of the PSP bootloader firmware version. Okay, so you can see here it says virtual timer and initialize device at some address. This is a implementation of a timer device I implemented to make the emulation run as far as I want it to be. It's a very, very simple device. Again, it will just return an incrementing value whatever something is read. This is sufficient to let the firmware emulate up to a certain point. Now I set a breakpoint and now it stopped. So what can we do with that? Well, I have now an interactive IPython shell and my script allows me to call disconnect gdb. The avatar framework controlled QEMO using gdb. So now I detached it from gdb and I will now attach my own gdb. So this is gdb. So now I've attached the gdb to the emulation running inside QEMO. So this is kind of nice so I can inspect some of the instructions. So this is the instructions which are currently emulated. I stopped at the very first line. And coming back to our original plan, we wanted to dump the page tables if you can remember. So the page tables are at the fixed address. The breakpoint I hit here is past the function that will generate the page tables. So I just have a peek at the address where I expect the page tables to be. And there they are. Doesn't look too fancy. So what you see here is the dump of the page tables of the PSP. So of course this does not say too much, right? I don't know how many of you can read page tables by just looking at that. I hope no one. We have, where is it? Of course we have a script for that. Yeah, there it is. So what I did now, I dumped the memory containing the page tables and this is a simple Python script that parses the page tables. This is a lot of information so I will just highlight what's interesting here. The way to read it is it starts with a virtual address which maps to a specific physical address and you can see it's just one to one mappings. Not too interesting. Now it gets interesting exactly here. So if you look at those four lines, in the middle you can see the AP bits, right? So right there. AP bits are the access permission bits. So these bits determine who has access to those page tables. And you can see everything which is below hex 1500 has AP1. Everything after has AP11. Now if you look up the arm manual you will see that it means everything after hex 1500 is user space code. Everything above is operating system code. So now we have another piece of information about how the PSP loads its application and where. I've been known now exactly the user space applications or rather their address space starts as a fixed address. And of course these page tables could change over time, right? I stopped at a specific break point kind of early on so maybe they change. Well they don't. So this is the page table layout of the PSP throughout the whole usage of that processor. All right. Okay, coming back to the slides. Okay, so one thing, you can find the scripts for the emulation on GitHub. I will show the links later on so you can actually run your own emulation of the PSP firmware if you want. Okay, some insights what we gained here. So this is the memory layout of the PSP. The boot loader is loaded at offset zero. It has a header of 100 hex 100 bytes and it starts execution at exactly hex 100. It is privilege code. It handles syscalls, interrupts, it loads applications. Then we have the page table at this specific address and everything below that up to a certain point is application code. So interesting here is that at one point in time there's only one application. So the way this works is the boot loader has a fixed order of applications which are loaded. Once an application is running and finishes, the next is loaded and so on. So there's no multi-threading at all. So very simple setup. So the user space applications, there are many of them. So for example, if you're familiar with the secure encrypted virtualization technology, this technology uses a firmware and this firmware is a user space application on the PSP. Also we have applications which train DRAM for example. So on a modern X86 system, if you have DDR4 memory, it has to be trained. So you have to measure the timings of the specific DRAM chips which you are using. And the application that does that is a user space application on the PSP. We had a look at some of the syscalls which are handled by the boot loader, which is actually more an operating system than just the boot loader, I would say. So we have cache maintenance syscalls. We have syscalls which map an arbitrary X86 memory address into its own address space. We have syscalls which provide the applications access to a crypto accelerator if they want to do some cryptographic operations. We have random number and we have functions to read additional components from flash. So also a user space application can load additional components from flash. So some words to the security of the whole thing. ASLR is a technique which randomizes the virtual memory layout of applications in order to make it harder to mount a buffer overflow, for example. And stack canneries are random values which are inserted in the AP log and prolog of a function to be able to detect a buffer overflow. Yeah, of course, we don't have any of those. It's very simple, no randomization at all. And also what is interesting, usually when you have an operating system and a user space application, every syscall which gets a pointer, the operating system has to be very careful about sanitizing these pointers. So a user space application could provide a pointer to the operating system itself which then uses that pointer to write a value to. So you usually want to check whether your user space application that called the syscall provided you with the right pointer. So they don't do that here. Okay, you could argue this is all kind of AMD code and it's signed and trusted and whatnot. The thing is, the applications are not only provided by AMD and signed with the AMD key. They're also provided, for example, by UEFI vendors or motherboard vendors and signed with their key. So I would say it's rather disappointing that there is no security check in the syscalls as there are probably vendors that AMD should not trust too much. All right, coming to our conclusion. So takeaways for you. I hope we gave some insights to you how to approach such a problem and what you can do. So the emulation part is something you hopefully can also do on your own now. PSP tool and PSP trace is available for everyone. So if you want to look at your bios images from AMD systems, you can do that now quite easily. And also this SPI tracing using PSP trace is a very, very helpful analysis method because it gives you the exact idea of which component is loaded when. And if you're further interested in the secure encrypted virtualization technology and how the firmware or home security issues in the firmware could affect it, there's a paper pending which is gonna be published at CCS 19. Okay, some thanks. There is Peter Stugge who provided us with a flasher for SPI flash that allows us to easily and fast flash the flash, which is very helpful. Marius Munch who developed the avatar too and the secti department and SR Labs Berlin for providing us with hardware that we can have a look at. There are the links, thanks. All right, big applause for them. Great job, great job. And thank you for this very insightful talk, yeah. All right, so we have some short time left for some Q&A. Who's interested in asking some questions? Okay, so let me just come over to you. So we get your question on the recording. Yeah, thanks for the talk. So is the whole image signed as well or would I end up with a valid UI5 image if I just remove some applications that say those vendor ones I do not trust and hopefully aren't required to boot the system? So in general, no, it's not the, it depends. The answer is it depends. Some bios vendors have an image over the whole UI5 image but this is actually only checked if you use, for example, their BIOS update utility. So if you use an SPI programmer, this won't stop you from actually removing, for example, one of those entries or also replaying entries, for example, from another UI5 image that are signed with the same key. My understanding is the initial reads of the flash, the spy flash occur, they're done by some kind of boot ROM. Were you able to access the boot ROM? No. So we didn't find any code regarding this inside the flash image. But of course, if it's in ROM, it doesn't have to be in the flash. So maybe I can tell it like this. So we actually have some ways to access the piece-piece memory directly right now and we poked a bit in its memory around and we didn't find any code that could be something like an on-chip boot ROM. But we suspect that it has to be there but some part has to access the flash and load the second stage from the flash and verify its integrity. All right, do we have any more questions? Yes, over there, one second. Hello, thanks for the talk. How did you manage to find out the meaning of those registers that are early on set like the page table address? Oh, this is defined in the ARM reference. We know that it's an ARM Cortex CPU and the ARMv7 manual specifically says which register is doing what. So this is known. And there's a very nice IDA plug-in that actually makes comments on all those weird co-processor instructions and tells you exactly what they do. It's very handy. So was there some form of obfuscation on the client or on the boot server or on the apps? No. It's all plain ARM code and it's just waiting to be disassembled. Yes. Good to hear. Hey, so you said you can pick and replace whatever user space programs other people have written for different motherboards, different manufacturers. Have you started collecting all these from all the different boards that have been made to analyze and see if there's any interesting stuff there or do you have such an online collection somewhere? We have a small collection of images locally and we try to distinguish the vendors, whether there are some differences or not. We don't have an online version of that. The thing is, if you look at Epic, you will see mostly the same. No differences between the different vendors of motherboards. Ryzen, I'm not too sure. I can tell you that in general, the Ryzen is a much larger firmware with a lot more components than for Epic, but we have not looked into Ryzen that much, actually. All right, any more questions from the audience? Okay, looks good. So thank you again. Thank you, Robert. Thank you, Christian. A big applause again.