Simple Reverse Engineering Techniques for UEFI PEI Modules with a Useful Example

    Hello, dear readers of Habr.

    After a long break with you, I again and we continue to delve into the insides of UEFI . This time, I decided to show several techniques that simplify the reverse and debugging of executable UEFI components using the outdated but still popular PEI module SecureUpdating, which is designed to protect the firmware of some HP laptops from modification.

    The background is this: one evening, a familiar laptop repairman from Belarus wrote to me and asked me to see why a laptop with a replaced VideoBIOS doesn’t want to start, although it successfully starts next with the same one. The answer turned out to be on the surface - a laptop that did not start after modification had a newer version of UEFI, in which good people from HP integrated protection against modification of the DXE volume (and there we find the VideoBIOS we need along with 80% of the UEFI code) so that malicious viruses do not less malicious users did not accidentally break anything there. Then the problem was solved by transferring the SecureUpdating PEI module from the old UEFI version to the new one, but two weeks later the same person turned again, this time on the similar laptop the old version of the module refused to work, and my help was needed again.
    If you are interested in my further adventures in the world of UEFI PEI modules with a disassembler and patched transitions, welcome to cat.

    A couple of links to educational program

    If you understand almost nothing - it's okay, I have several articles explaining the terminology: 1 , 2 , 3 , read and come back. For fans of the original documentation, the UEFI PI specification is always available , everything is written there in much more detail.

    Required Files and Tools

    To disassemble the above firmware, we need:
    1. Actually the file with the firmware, this one was sent to me .
    2. Any utility for working with UEFI images, I will use UEFITool as its author, but you can use any one to your taste, for example uefi-firmware-parser or PhoenixTool - this is not important.
    3. Hex editor of your choice, I will use HxD .
    4. Disassembler with support for PE32 files, here IDA 6.6 Demo is ideal for us , because In most cases, PEI modules are 32-bit and the demo version restrictions will not hurt too much. If dear xvilka can show how to load structures from a C-file in radare2 , I will try to make the next mod in it, but for now, the IDA is our everything.
    5. From the efi-utils bundle, you need a hefty behemoth.h file that contains definitions of almost all possible data structures used in UEFI. In our case, we need only a couple of them.

    A starting point

    So, from the words of a repairman, we know the following: any change in the DXE volume leads to a dead laptop blinking with a caps lock, and changes in other parts of the image do not lead to such an outcome. This means that somewhere either a checksum or an EDS is stored, which is checked by the code of one of the PEI modules, and if it converges, the control is transferred to the DXE phase, and if not, it is transferred somewhere to where we don’t glad.

    We need to find out the following:
    1. Where exactly is the CS / EDS stored?
    2. Who is checking her?
    3. And, most importantly, how to make sure that the verification always ends successfully?


    Do it once!

    We open the file with the firmware in UEFITool and look carefully:

    It looks nothing unusual, except for the message that inside the free space of one of the UEFI volumes there were data that should not be there by specification. This is how manufacturers (of those who do not really believe in the specification) usually hide their checksums or digital signatures. By double-clicking on the message, we select the volume in which these same data were found, and we extract it in its entirety into the dxe.vol file for analysis. UEFITool is not necessary to close - still come in handy.

    We open the resulting file with a Hex editor and look at it from the end, because free space in the volume can only be there:

    There is a very suspicious piece of data of size 100h (marked in red), and behind it is the signature $ SIG, firmware version F.50 and platform code name 68CPK . Thus, the answer to the first question is supposedly received.

    Do two!

    To answer the second, you need to look for PEI modules that access this data block. This is quite difficult and often you have to try several options. The easiest way is to look for other occurrences of the $ SIG signature, but in this case, we immediately fail - there are no other occurrences of such a line in the image. But if the block is not searched by signature, then it is searched either by offset or by absolute address. Its offset within the volume is 12FEE0h. We switch to UEFITool and look for Hex-pattern E0FF12 without headings (Intel processors are still LittleEndian, so we had to change the byte order):

    III ... BINGO, only 2 occurrences, and both in the same PEI module with the promising name SecureUpdating . We take it out without headers to the su.bin file for further analysis:

    Thus, presumably, the answer to the second question is received.

    Do three!

    It remains to deal with the third. To do this, you need a disassembler, a little knowledge about the device PEI-modules and a lot of patience. We start IDA, we agree to the conditions of the demo mode and open the file received earlier.
    Go to Options -> Compiler ... and set them like this:

    Then go to File -> Load File -> Parse C header file ... and upload the above listed required behemoth.h file with definitions UEFI-structures:

    On parsing errors do not pay attention - they in this case will not harm.

    Now open the Structures tab, go to Edit -> Add structure type ... (or click Insert, it’s faster), there we click Add standard structureand in the list that appears, we find the structure most important for PEI files - EFI_PEI_SERVICES , which we add:

    At the same time, add EFI_GUID and EFI_FFS_FILE_HEADER - come in handy.

    The EFI_PEI_SERVICES structure (if absolutely precisely, a double pointer to its instance created by the PEI kernel) is passed as a parameter to the entry point of each PEI module and to almost all of its functions. This is done because part of the PEI is forced to be executed directly from flash-memory, which at that moment is read-only, therefore global variables in such PEI-modules are not available and you have to carry everything with you. This is an unpleasant restriction for a programmer, but it helps a lot in researching and debugging PEI modules, as dereferencing a double pointer is not a very popular procedure in regular code, and therefore most of the calls to PEI services can be traced directly through the listing. So we’ll return to it, but first we’ll recall (or find out) what the entry point into the PEI module looks like. Do not rush to google, it looks like this:
    IN EFI_FFS_FILE_HEADER *FfsFileHeader, 
    IN EFI_PEI_SERVICES **PeiServices

    EFI_STATUS is a typedef for unsigned int, EFIAPI is a typedef for stdcall, the first parameter indicates the FFS file in which the called PEI module is located (in case the module stores some data near it and needs access to it ), and the second is the double pointer to the PEI services table already described above. Armed with this knowledge, we boldly change the type of the start function (highlighting it and pressing the Y key ), it turns out something like this:

    Now you can see the following from the listing: first comes a series of function calls for which PeiServices is not needed. Most often, they are engaged in input-output to / from IO ports and other magic of this kind, we will verify this assumption by moving to the first in order:

    Indeed, the function performs data output to port 24Eh. I will omit the next few (they are very similar, write-read IO-ports) and move on to those that PeiServices still use.
    The first turns out to be trivial and simply saves PeiServices into a global variable (which indicates that our PEI module is already running from RAM, but the keen eye of a specialist would have noticed this from the information about the PE file in UEFITool):

    The following is already much larger and much more interesting, especially if you set the correct parameter and return value types for it:

    The fragment highlighted in red immediately after the prologue and zeroing of local variables is the same noticeable pattern with the dereferencing of the double pointer, which I spoke about above. To understand which PEI service was called, we needed all these dances around the structures, now we can position the cursor on [eax + 28h], press T and select EFI_PEI_SERVICES.GetBootMode in the window that appears: Having

    looked at its signature , we can conclude that var_134 is actually a variable on the stack into which the value of the current load mode will be written. Then this value is compared with 11h and if not equal, the calculations go further, but if it is still equal, we put zero in eax and go to return. 11h in this case is BOOT_ON_S3_RESUME, i.e. if the system wakes up from ACPI Sleep Mode, then the function always returns 0 (and this is in the local dialect EFI_SUCCESS). If the system boots from another state, then the execution goes on, and as a result passes through such an interesting place:

    Ba, old friends! The same occurrences of 12FEE0h, by which we found this module. And first, with the CopyMem function, that suspicious CS / EDS is copied to the buffer, and then the original place is overwritten with the FFh byte, which is used to fill the empty space in the DXE volume initially, and then the function is called that checks this CS / EDS.
    You can, of course, start exploring it now, but this part of the code does not execute at all if the system wakes up from S3 (which is logical, since nothing is needed from the DXE volume for S3, but you need to wake up as soon as possible), and yet works less, so to begin with, let's make this particular PEI module think that we have eternal summer and always S3_RESUME, and skip any checks.
    To do this, just change cmp [ebp + BootMode], 11h to xor eax, eax, then the next jnz will never succeed, but if it should never succeed, then it's easier to replace the transition with a couple of NOPs:

    Change in Hex- editor selected fragment 90 90 and you're done.


    Suddenly, some new circumstances emerged. It turns out that in this “old” version of protection there is a copy of the PEI volume, which can be used by the system to restore the original state of the PEI volume, and in this copy, you must also replace the SecureUpdating module with the patched one. A copy is stored in a file with GUID 05B3AFFD-F7CC-4C0A-A19A-A9774E2675D7 of type RAW, therefore the contents of this file are not displayed in current versions of UEFITool:

    In fact, this is a file of type Freeform, and to access its contents, you need to extract it through Extract as is ... , replace the bytes in the extracted file at offset 12h (File type) from 01 to 02 and insert the resulting file instead of the original one through Replace as is ... :

    Inside this file is a compressed section with a copy of the PEI volume, it is there that there is another instance of SecureUpdating, which also needs a patch. Now everything definitely works even where it did not want to before.


    Further is a matter of technology. We replace through the Replace Body ... the contents of the original PE32 section with a modified file, make the necessary changes to the DXE volume, save the changes and flash the resulting image on the programmer. I did not have this laptop, made a modification and sent the results to the supplicant. After a couple of hours, the answer was received: “thank you very much, everything works, the client is satisfied,” and I went with a clear conscience to write the article that you just read.
    Thank you for your attention and successful modifications to you.

    Also popular now: