6.61 Infinity – An explanation

A Brief History Lesson

Lets quickly rewind to 11 years ago (2006) and refresh our memory of the very first custom firmware: 2.71 SE-A. Dark_AleX and team designed this custom firmware based on Devhook, a piece of software that allowed you to load the latest PSP firmware from the memory stick. Devhook provided the basic foundation for loading non-host firmware and 2.71 SE-A exploited this knowledge and combined both 1.50 and 2.71 to create a hybrid firmware. This technique was implemented through SE, OE and the start of M33. Beyond this, a custom IPL was used for custom firmware.

The custom IPL method worked fantastically until newer PSP models started shipping with patched bootroms. The first hack for these newer devices was a simple homebrew enabler (HEN) on 5.03, called ChickHEN. You might remember the dramas and the struggle of constantly crashing when accessing photo menu. This was really the first stage of “new-era” homebrew/cfw since the departure of Dark_AleX and team M33. This stage onward, the primary source of homebrew was from HENs, which were non-persistent custom firmwares. These HENs were typically invoked using a kernel exploit and a user exploit in a game or third party library (libtiff, mainly…).

When the PS3 was hacked and a selection of PSP encryption keys were released, this allowed us to sign our own userland applications. This functionality brought us two great improvements:

HEN/CFW can be loaded much faster through a signed application rather than loading a game and we can now sign our own vshmain and replace a step in the bootchain

kgsws first demonstrated this bootchain injection back in 2011 and lead to the creation of 6.20 permanent custom firmware. Sony did patch this up in later firmware by applying an ECDSA signature to PRX files in the bootchain which we cannot forge.

So, for Infinity to work we need one single crucial component: Find an exploit in the bootchain. This is easier said than done, of course. First we need to analyse how the PSP bootchain works.

PSP bootchain

PSP bootchain

First step in the PSP bootchain is the CPU bootrom which loads the IPL from NAND. The bootrom is obviously ROM and cannot be modified, so there is nothing we can control here. The IPL is formatted in a very primitive block structure of KIRK CMD 1 blocks. Unfortunately, we cannot decrypt the blocks of newer PSP models as the bootrom performs a XOR over the encryption key. This makes it incredibly difficult to forge IPL blocks. Additionally, in these newer models each IPL block has 0x20 bytes of hash data in the footer of the block. These 0x20 bytes are an encrypted MAC and would require knowledge of the bootrom to forge. These issues make breaking IPL loading very difficult.

The IPL is mostly used for seeding the system and initialising hardware devices. It loads and decrypts only three files: the bootconfig, sysmem.prx and loadcore.prx.

The bootconfig is a binary file that details the bootchain under certain modes. There are different bootchains for running a game and loading the XMB. The IPL decrypts this file and verifies the signcheck and header hash of all the modules in the current bootchain. It also loads all these files into memory to be loaded later.

Sysmem is a fairly benign module and mostly used for managing the PSP memory resources and UIDs. It’s role in the boot process is minimal, but provides utilities and functionality to later modules that will be loaded. This is an encrypted PRX and the IPL must be able to decrypt, decompress and load it.

Loading loadcore is the last stage of the IPL and is where responsibility is handed to load the rest of the firmware. Loadcore’s role in the OS is specifically the task of loading executables. It manages decryption, decompression, import/export linking and tracking loaded modules. In boot-time, it manages loading upto init.prx.

Init.prx is a very simple module which loads the rest of the bootchain specified by the bootconfig from the IPL. Init.prx also resolves the anchors in the bootconfig for apps which aren’t located in the NAND, such as games, homebrew, apps, etc. Init.prx doesn’t do the loading itself, it works as a proxy for modulemgr.prx which in turn utilises loadcore.

Of course, there are other vectors beyond init.prx as the system becomes more active. This could be malformed firmware for the ME processor, breaking assets and resources in the XMB, and many more. However, for an early as possible boot-time hack, we need to look at PRX loading. An early boot-time exploit is preferred, as it both provides greater flexibility in terms of recovery should a user accidentally delete or corrupt files in the firmware and allows custom firmware to run unaltered. If custom firmware is loaded later, patches need to be applied retroactively.

The Giraffe Bug

Infinity is based off a bug in PRX loading. It turns out loadcore is a bit indecisive about what it does with the optional and rarely used ~SCE header. This header is 64 bytes long and mostly unused other than a 32 bit size/offset field (lets call it sce_size) at +4 in the header. The main PRX decryption function sceKernelCheckExecFile just skips past the 64 bytes when it detects that sce_size is positive. sceKernelLoadExecutableObject, the actual ELF loading aspect of loadcore does the same thing. However, sceKernelProbeExecutableObject, which is used to get information about the PRX meta-data, skips past sce_size bytes. This inconsistency leads to the loading of an unencrypted PRX.

Lets quickly talk about how loadcore loads the first set of boot modules.

// decrypt prx
sceKernelCheckExecFile(prx_data, &info);

if (info.compressed) {
   // allocate memory for decompression buffer, etc
   // need to decrypt/decompress
   sceKernelCheckExecFile(prx_data, &info);

// get meta data
sceKernelProbeExecutableObject(info->base, &info);

When sceKernelCheckExecFile is invoked the first time, it will skip past the ~SCE header if present and attempt to do various operations. I’ve simplified down the operation, but below is a flowchart describing the basic tasks.

sceKernelCheckExecFile flowchart

sceKernelCheckExecFile flowchart

First off, if we provide an unencrypted PRX it will actually succeed to get through sceKernelCheckExecFile. sceKernelCheckExecFile isn’t here to validate that the PRX or ELF being checked is valid for the context – it only checks that the executable is consistent. Since under certain circumstances an unencrypted ELF/PRX can be valid (such as early release game titles), there is a path that succeeds. The problem is the internal flags such as is_decrypted will not be set so sceKernelProbeExecutable will fail.

sceKernelProbeExecutable is the function that checks an executable is valid for the context. So for a boot executable it must be encrypted. So how do we get through sceKernelCheckExecFile with an unencrypted PRX, but still somehow set flags that can only be set with an encrypted PRX? Lets talk about how sceKernelProbeExecutableObject works. Firstly it does the ~SCE header skip by sce_size bytes. After this, it will check the if the is_decompressed and is_decrypted flags are false. If they are both false, it will call sceKernelCheckExecFile again, then continue normally.

Spotted the bug yet?

What if we have an ~SCE header followed by an unencrypted PRX and finally followed by an official 100% legit encrypted PRX? If we set sce_size to point to the encrypted PRX, we can force sceKernelProbeExecutableObject to call sceKernelCheckExecFile on an encrypted PRX which will in turn set the is_decrypted flag to true! The very observant of you will notice a problem we just laid in front of ourselves: the compression buffer is not set. Amazingly, when processing an unencrypted PRX or ELF Sony uses the compression buffer variable to store some data for calculating the size of the executable. Thus a crafted ELF can set the decryption buffer by correctly setting a program header p_vaddr. Perfect.

The smartest of readers would also figure out that the decrypted PRX would be stored in this compression buffer and not at the location of the encrypted PRX. However, sceKernelProbeExecutableObject expects the decrypted result to be in the same location of the encrypted PRX. This may be an oversight from the Sony engineers, but it means we must store an ELF header where encrypted data exists. Although, we can just repeat the exercise and use another ~SCE header. Since the data is not used and sceKernelCheckExecFile uses a constant offset, we can store an ELF header too in there as long as the sce_size is positive. As for the contents of the header, we can just offset them back to the ELF above it.

All we need to do now is load our PRX and sceKernelProbeExecutableObject does just that thanks to the constant offset for an ~SCE header. Finally, for it to be accepted by the IPL/bootconfig you need to copy the signcheck from the encrypted module to the location it would expect it to at, so +0x80. Then you have a bootable PRX that passes decryption and ECDSA.

This bug exists in 6.31 and below, it was mysteriously patched in 6.35 – not much longer after I discovered it. For this reason E1000 (PSP street) is not supported.

A touch of the old magic

So you might be wondering: “How does a patched 6.31 exploit and brief history lesson translate into 6.61 permanent patch?”. 6.61 Infinity goes back to the old SE/OE days and uses the same technique done back then. Instead of a 1.50 / 2.71 hybrid firmware we use 6.31 and 6.61. The catalyst for this hybrid firmware is the giraffe bug applied to systimer.prx. I chose this specific module only due to its size. Weighing in at only 3 KB compressed and 7 KB uncompressed it is the smallest PRX before init.prx. Size is important here – the PSP NAND is nearly at capacity under normal circumstances so we have to make savings.  In fact, on the original phat PSP the NAND cannot contain both 6.61 and the 6.31 subset firmware. The infinity installer actually excludes some features (location free TV) so everything important can be installed.

With a giraffe’d systimer on the PSP we can inject a payload and take control of the system. Due to the way the hack works, relocations are not applied correctly. This means that code execution is not as clean as loading an ELF. The installer instead just slams some MIPS assembly at the entry location of systimer. The payload then locates loadcore by doing some calculations based on arguments passed to the module. With this information we can patch the kernel decryption routine and clear the caches. We also do a small patch to the module loading routine that passes the bootconfig as an argument. Afterwards, it cleans up and returns to loadcore.

Here we have a compromised (and unstable) system that is now patched to load unencrypted PRX. How infinity takes advantage of this situation is by replacing init.prx with a custom module that acts as our bootloader. Our bootloader has three key roles:

  • Restore system stability
  • Provide a recovery system
  • Load 6.61 firmware

Restoring system stability is done by modifying the bootconfig to load an original copy of systimer.prx and init.prx. These modifications will allow the system to continue the boot process as normal. We need to do this as we need services further down the boot chain for detecting button presses, rebooting the system and loading from memory stick.

Recovery is implemented by doing a check for L trigger when the memory stick driver is loaded. If pressed, the file ms0:/infinity/recovery.prx is loaded and started. Currently, there is no official recovery implementation. Those wishing to unbrick a device that still has access to the memory stick must first implement their own recovery. This executable is run under the 6.31 kernel and does not have any typical services of a normal kernel PRX. No NID resolver, no screen and no systemctrl. Additionally, since we wait for the memory stick driver any damage to the boot chain before this module is a permanent brick.

In the typical case where L trigger is not held, the bootloader will automatically reboot into 6.61. It does this by patching NAND decryption keys, patching prx decryption keys and redirecting flash0/kd to flash0/kn and flash0/vsh to flash0/vsn. These folders are where infinity has installed the 6.61 firmware.

At this point, the system reboots into 6.61 much like a normal system. Infinity keeps itself hidden in the background to maintain NAND redirection but is ultimately silent. It uses clever patches in order to work alongside both PRO and ME and provides the necessary compatibility modules to load them on start. This design keeps infinity agnostic to the running CFW and allows users to choose whichever they prefer. This design also allows developers to create their own CFW and take advantage of infinity’s permanent patches.

An end of an era

Infinity left some open ended questions regarding the state of PSP development. I used the flexible CFW agnostic design to gauge health for homebrew development in this area. Whilst I had a few bites that were interested; it ultimately ended with nothing. Likewise the lack of recovery implementation further solidified that people have moved on from the PSP. This is, of course, no surprise to me.

There are still some unfulfilled hacking goals for the PSP. PSP street has no permanent hack and KIRK is still mostly unbroken. The crypto engine has had some leaks of keys from the PS3 but has ultimately been secure from the PSP side. Likewise the bootrom has not been dumped for the newer models. If I were to make a prediction I’d say that these goals will never be met. The PSP is over 12 years old (at time of writing) and development has dwindled into the abyss.

Whilst not perfect, Infinity is probably the final nail in the coffin for PSP.

Infinity 6.61 WebsiteInfinity 6.61 Source Code

Follow me on twitter: @DaveeFTW