Iplloader: Difference between revisions

From PSP Developer wiki
Jump to navigation Jump to search
m (set chronological order)
m (CelesteBlue moved page PRE-IPL to Iplloader: iplloader)
 
(117 intermediate revisions by 7 users not shown)
Line 1: Line 1:
The PRE-IPL called "Lib-PSP iplloader" internally by sony is mapped to 0xBFC00000 which is the reset vector of PSP's MIPS R4000 CPU, on retail PSP units it is the Tachyon (Allegrex MIPS R4000 based SOC) bootrom, on DTP-T1000 it is loaded externally to volatile memory mapped at 0xBFC00000.
<b>iplloader</b> <i>(also known as PRE-IPL or BootROM)</i> — is the first code to run on PSP main [[CPU]].


0.90 Pre-ipl and onward are composed of two parts: A loader from 0xbfc00000 to 0xbfc0027F and a payload starting at 0xbfc00280 and ending at the size specified at 0xbfc000034.
Its role is to load and decrypt encrypted [[Initial Program Loader]] from the [[NAND_Flash_Memory|NAND]] or [[Memory Stick]]. iplloader contains the routines to boot into service mode.


A few example of payload sizes and their hashes are:  
= Location =
 
On retail PSP units, the iplloader is read from the [[Tachyon]]'s Allegrex MIPS R4000 based SOC boot ROM. On DTP-T1000, the iplloader is loaded externally. See also [[#Behaviour]].
 
The PSP main CPU is a custom-made Sony CPU with a MIPS32 core and has an embedded mask rom device (like most embedded systems do) which is exactly 4KB in size. This device holds the iplloader. This iplloader mask rom device is mapped to physical address 0x1FC00000 which is the address of reset exception vector on MIPS CPUs, and this is where the MIPS CPU starts executing from coldboot.
 
= Structure =
 
The BootROM is made up of two parts, the "bootstrap", and the "loader". It is located at physical address 0x1FC00000 and is accessed via non-cached kseg1. The ROM is accessed upon hardware reset, software reset, or NMI. When this occurs, the Status flags are set in the COP0 Status register with following bits set: Boot Exception Vector=1 and Error Level = 1. If its a hardware reset, Software Reset (SR) = 0, or if NMI or software reset it is 1. At this point the PC is set to 0xBFC00000 and it begins to execute instructions. Since this is currently in ROM mode, only registers can be modified.
 
= Versions =
 
A few examples of iplloader payload sizes and hashes are listed in the following table.
 
{| class="wikitable" style="text-align: center"
! Target
! SDK Version / Tachyon Revision
! Date (DD-MM-YYYY)
! Payload Size
! Hash
|-
| style="background:#C3F500"  | PSP (Retail/Testing Tool)
| style="background:#C3F500" | Tachyon 0x00140000-0x00300000
| style="background:#C3F500" | 20-04-2004 (build date in ROM)
| style="background:#C3F500" | 0xAF8
| style="background:#C3F500" | SHA-256: 48F4F11C383621C8569EC07273AE0AF6AD79681CF5B77263A69CF908EEFE4A53 (ROM)
|-
| style="background:#34D1B2" | PSP (Development Tool)
| style="background:#34D1B2" | 0.4.0
| style="background:#34D1B2" | 23-07-2004 (or older/last modified date for kbooti.bin)
| style="background:#34D1B2" | n/a
| style="background:#34D1B2" | SHA-256: 18B5BF7AEFE956D99B397AAAAC94DC965ADFDBC2BE0532096BBC1F8F8C5B7C34 (Full Binary)
|-
| style="background:#34D1B2" | PSP (Development Tool)
| style="background:#34D1B2" | 0.6.0
| style="background:#34D1B2" | 08-09-2004 (or older/last modified date for kbooti.bin)
| style="background:#34D1B2" | n/a
| style="background:#34D1B2" | SHA-256: 5CDEDDEBE11807DDAEB17BAC03945A0B828E8057C9587652CA207E3BB959AC96 (Full Binary)
|-
| style="background:#34D1B2" | PSP (Development Tool)
| style="background:#34D1B2" | 0.7.0
| style="background:#34D1B2" | 18-09-2004 (build date in the payload)
| style="background:#34D1B2" | 0x894
| style="background:#34D1B2" | SHA-256: 351ECD64C945489999D477ECAFBFBB8FE769C2484636D2F7323557F7EEFD54A2 (payload only)
SHA-256: 388FA1DB87973A2A37D576AAAB785D840CA4D883AB5111781DA2D0AF59CFE667 (Full rebuilt binary)
|-
| style="background:#34D1B2" | PSP (Development Tool)
| style="background:#34D1B2" | 0.9.0
| style="background:#34D1B2" | 15-10-2004 (build date in the payload)
| style="background:#34D1B2" | 0x894
| style="background:#34D1B2" | SHA-256: 4F794E4FF32D5267AEAEDBA362D005EF0B7E93E29CF7C8209E0D9DBB0144F4DB (payload only)
SHA-256: E415198C16E29D96C9232FF78272EE639D0630A56E370ED18A33D358FEF7CA95 (Full rebuilt binary)
|-
| style="background:#C3F500" | PSP (Retail)
| style="background:#C3F500" | Tachyon 0x00400000-0x00500000
| style="background:#C3F500" | 04-01-2005 (build date in ROM)
| style="background:#C3F500" | 0xB30
| style="background:#C3F500" | SHA-256: 41B2578F84BDE33E09356F0170FF99E2417EA7B1D02BD9163A41AE61FE74C3A5 (ROM)
|-
| style="background:#34D1B2" | PSP (Development Tool)
| style="background:#34D1B2" | 2.6.0
| style="background:#34D1B2" | 22-10-2005 (build date in the payload)
| style="background:#34D1B2" | 0xBF4
| style="background:#34D1B2" | SHA-256: 8821D96F5FB35C55DF649A97F5703F8A705362C2F54665B5EE4221E686B5578A (payload only)
SHA-256: 0A83CB36F1FE7C2A9A53BD46E6FFD915D4D1BB97EED3D1EF336960DB752C3446 (Full rebuilt binary)
|-
| style="background:#34D1B2" | PSP (Development Tool)
| style="background:#34D1B2" | 2.7.1
| style="background:#34D1B2" | 14-02-2006 (build date in the payload)
| style="background:#34D1B2" | 0xBF4
| style="background:#34D1B2" | SHA-256: F9160C03EC6174F54F1C1EB645CFBBDB65B3DA47DA1A5478BE30E5EB2B0852B4 (payload only)
SHA-256: 7DDFF7093906C10BA11D7402E9939763173F1ADEA59A38B4006484FD18EA21EA (Full rebuilt binary)
|-
| style="background:#34D1B2" | PSP (Development Tool)
| style="background:#34D1B2" | 3.5.0
| style="background:#34D1B2" | 12-02-2007 (build date in the payload)
| style="background:#34D1B2" | 0xC74
| style="background:#34D1B2" | SHA-256: AAF6F3CF0D7E028F43BE6FB788018F7A6F49B140A2591937B5C3A8373D2186A5 (payload including xor key)
SHA-256: 5066E257EC43DA37788535C331E5A01955F5F498B103C340903409649BCB5046 (full rebuilt binary)
|-
| style="background:#C3F500" | PSP (Retail)
| style="background:#C3F500" | Tachyon 0x00600000-0x00900000
| style="background:#C3F500" | 10-09-2007 (build date in ROM)
| style="background:#C3F500" | 0xCE8
| style="background:#C3F500" | SHA-256: E511D3DC78A209610F5B3EFEA2BC64BF86B9DF14A9C279C4499FECBFD70E6BF9 (ROM)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 0.920.000 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 06-22-2010 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0x60
| style="background:#FF8B00" | SHA-256: 98C8336C136DF901FC4EA38EB371AAF6F5402AC06574F107D3E2029BFC85CCAD (full binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 0.930.010-0.995.000 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 08-17-2010 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0x2C0
| style="background:#FF8B00" | SHA-256: 6D75EC720739C53228B1CA1AFF6CE073AE542BBB38FCC9B8710EC5EB3889B942 (full binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 0.996.070-0.996.090 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 07-22-2011 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xD34
| style="background:#FF8B00" | SHA-256: E09B36DE655A441D2C94D39EF7BBC505EAB1722E9380EBE73E9E6A7DC88D9731 (full binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 1.000.041-1.06 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 08-30-2011 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xDB4
| style="background:#FF8B00" | SHA-256: C2AE6939BC4B06CB4A81415E27EB1E7129C561B9C16C3AAFF6FDEBBAB48EBD09 (Full Binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 1.50-1.81 (inside PS Vita Compatibility Security Module)
| style="background:#FF8B00" | 12-14-2011 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: 522851781DD82F89D69EBFE0F25C3E7CFE5899A53E850F2F979AD1B0E53376F9 (full binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 2.00-2.05 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 11-16-2012 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: A9A097ED8925B83A210202AA4C943011C05FE48028BA6E05E85E1494143B0100 (full binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 2.06-2.12 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 02-22-2013 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: 25E22C1D988609AA948F103E1312297F9533CA689B3C1BDC2CECBBC43997D566 (Full Binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 2.50-3.01 (inside PS Vita Compatibility Security Module)
| style="background:#FF8B00" | 06-27-2013 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: 187DD28ADAD4167F3849392D570CA5A56DEEC608156D0EC6F2453958B1DB9672 (Full Binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 3.10-3.20 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 12-05-2013 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: EF7D498295E416CCBD79FED78656E683DA1DCBC7B88C521DF0A1E00F5EC450FE (Full Binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 3.30-3.35 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 09-25-2014 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: 7DF591C05BF66292B6868CE4331A1DC11B7A0E421D082971E9CA65C7236B6843 (Full Binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 3.36-3.50 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 01-09-2015 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: 7E66976C311F5D3797A30B09DC608A0FA2E67EAA060097423CFD2E5FC89A57D9 (full binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 3.51-3.55 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 05-12-2015 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: 6E869E08CCE41E0AA0D386DE8936F81F66CABB20B964C3EF2159548852F39F30 (full binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 3.57-3.63 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 11-25-2015 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: 047366634210449C62FD813B3BFDA6267A1FA8683BA17901A214FC32E473A35F (full binary)
|-
| style="background:#FF8B00" | PS Vita
| style="background:#FF8B00" | 3.65-3.74 (inside PS Vita's Compatibility security module)
| style="background:#FF8B00" | 03-17-2017 (last modified date for compat_sm.self)
| style="background:#FF8B00" | 0xE34
| style="background:#FF8B00" | SHA-256: B5EF4FB2C84D629B2BDC9A70A4B8E5A7EC31CD9EA330E309361C80A9A96B65C5 (full binary)
|}
 
= Behaviour =
 
== iplloader Boot Sequence ==
 
=== Part 1 (iplloader loader) ===
 
Because the BootROM is stored in non-volatile read-only memory, it cannot use any variable, so the bootstrap of the BootROM copies the loader BootROM (the payload) to the CPU's scratchpad RAM. It is the only RAM available at this time, along with another 4KB block of RAM and the 2MB EDRAM — normal DDR SDRAM has not been initialised yet.
 
The first thing checked is the 0xBC100000 value, if non-zero, it typically means a NMI exception and it either jumps to the exception vector in COP0 Control Register $9, or if that is not set, to the exception vector in COP0 Status Register $25. On a normal reset (hardware or software), the loader code of the ROM is copied to scratch pad at physical address 0x10000 in cached mode (0x80010000). At this point, it jumps to the real BootROM loader at 0x80010000. A stack is created at 0x80013FF0 as the top of the stack. This allows to the loader to use more typical local variables and memory in addition to registers.
 
=== Part 2 (iplloader payload) ===
 
The CPU is now executes the iplloader payload from the scratchpad RAM. The iplloader payload initializes the NAND hardware and reads the IPL NAND-block-table which is a table with the physical block numbers of the encrypted IPL's location on the NAND. That table is located at the 4th physical block of the NAND i.e. at offset 0x10000, and is repeated for the next 7 blocks. This is made so that if a bad block is met, the table can still be read. However, if all 8 blocks become bad blocks, it is a non-recoverable brick because the iplloader can no longer locate the IPL. The only solution to this problem is to either boot from Memory Stick instead, or use a custom IPL to patch the iplloader to remap the table. Both solutions require a Pandora battery.
 
If the SysCon has set 0xBE240004's 0x10 bit, the Memory Stick is used instead of the NAND for IPL loading.
 
The entire raw IPL is stored on the NAND encrypted. The iplloader payload uses a 4KB RAM as a temporary location to load and decrypt each encrypted IPL block. This RAM is mapped to physical address 0x1FD00000, but is later remapped to 0x1FC00000 to be used as the ME CPU reset exception vector. Because this RAM is only 4KB in size, the encrypted IPL is organised as 4KB blocks on the NAND. As the iplloader decrypts each of the 4KB IPL blocks, it loads the decrypted blocks to the IPL entry address 0x040F0000. This address is located in the 2MB EDRAM which is normally used as VRAM. Normal DDR RAM has not been initialised yet. When the iplloader has finished decrypting and loading all the encrypted IPL blocks, it jumps to the IPL entry address.
 
== Memory mapping ==
 
The PSP iplloader is mapped to <b>0xBFC00000</b>, which is the MIPS reset vector, <i>i.e CPU initial program counter</i>.
 
0.7.0 iplloader and onward are composed of two parts:
* a loader from <b>0xBFC00000</b> to <b>0xBFC0027F</b>
* a payload from <b>0xBFC00280</b> and ending at the size specified at <b>0xBFC000034</b> (<i>2 bytes with little endian order</i>).
 
The PSP iplloader distributed in PS Vita System Software version 0.996 and onward has its payload starting at 0xBFC00180, rather than at 0xBFC00280 on previous PS Vita software and PSP hardware versions.
 
== DevKit behaviour ==
 
=== bloadp ===
 
bloadp is used to send kbooti binaries (encrypted iplloader data from 0x0 to 0x1000 and IPL blocks at 0x1000) to the DTP-T1000.
 
bloadp (through tachsm) initializes the 0x1D600000 memory (from /dev/mem or 0x00000000 from /dev/tachsm0 ; /dev/tachsm1 is mapped from 0x1D400000 to 0x1D5FFFFF) on the [http://www.psdevwiki.com/ps3/Communication_Processor Communication Processor] side (0x200000 in size, 0x100000 on the tachsm0 device) which is the DTP-T1000 PSP Shared memory, on the PSP/Tachyon side it is mapped to 0xBFE00000, it then writes the kbooti binary at 0x1D600000, however on SDK 0.5.0 and below dstdb (which bloadp was originally a part of), it was possible to specify the address to which kbooti would be loaded (default address to be specified was 0xbfc00000, the allowed range was 0xbfc00000-0xbfc10000), this in effect would write kbooti to 0x1D600000/0xBFE00000 + the address specific in the range -0xbfc00000, so for instance if 0xbfc02000 was specified, this would write kbooti to 0xBFE02000, keep in mind that the actual 0xBFC00000 memory mirrors itself every 0x1000 segments.
 
If bloadp is never invoked, 0x1D700000 to 0x1D7FFFFF is uninitialized memory. It is cleared out as soon as bloadp is invoked.
 
=== Prototype 0.4.0-0.6.0 (23-07-2004 or older) ===
 
The prototype iplloader reads the IPL blocks in place from 0xBFE01000 and decrypts them to 0x88400000 before jumping there. Prototype IPL blocks do not contain metadata. Instead iplloader uses 0x88400000 as a hardcoded entry point (see code samples below). In fact the IPL payload, nested inside current IPL revisions and loaded by main.bin are using the prototype format, so the prototype iplloader loads the IPL payload directly.
 
It is also worthy of note that the prototype iplloaders for DEM-1000 (for 0.4.0 and 0.6.0 firmwares) do not make use of physical address 0xA0010000 as their payload location. The iplloader payload is instead executed in place at 0xBFC00000. As such the iplloader data is not wiped and can be dumped in its entirety using a custom IPL. Note that the IPL format is different than later revision IPLs: prototype IPLs are loaded in a single raw Kirk commmand 1 block.


<pre>
<pre>
From older to newer;
// 0.4 iplloader
DD-MM-YYYY:
    }
20-04-2004 (Tachyon 0x00140000-0x00300000): 0xAF8 |  SHA-256: 48F4F11C383621C8569EC07273AE0AF6AD79681CF5B77263A69CF908EEFE4A53 (ROM)
    // 0xbfc00138
23-07-2004 or older (0.4.0) : n/a                  |  SHA-256: 18B5BF7AEFE956D99B397AAAAC94DC965ADFDBC2BE0532096BBC1F8F8C5B7C34 (Full Binary)
    function_bfc00178(100);
08-09-2004 or older (0.6.0) : n/a                  |  SHA-256: 5CDEDDEBE11807DDAEB17BAC03945A0B828E8057C9587652CA207E3BB959AC96 (Full Binary)
    uint32_t v9 = function_bfc00550(0x8400000, 0x1fe01000); // 0xbfc0014c
18-09-2004: 0x894 (0.7.0)                          |  SHA-256: 351ECD64C945489999D477ECAFBFBB8FE769C2484636D2F7323557F7EEFD54A2 (payload only)
    // branch -> 0xbfc00154
15-10-2004: 0x894 (0.9.0)                          |  SHA-256: 4F794E4FF32D5267AEAEDBA362D005EF0B7E93E29CF7C8209E0D9DBB0144F4DB (payload only)
    while (v9 < 0) {
04-01-2005 (Tachyon 0x00400000-0x00500000): 0xB30 |  SHA-256: 41B2578F84BDE33E09356F0170FF99E2417EA7B1D02BD9163A41AE61FE74C3A5 (ROM)
        // 0xbfc00154
22-10-2005: 0xBF4 (2.60)                          |  SHA-256: 8821D96F5FB35C55DF649A97F5703F8A705362C2F54665B5EE4221E686B5578A (payload only)
        // continue -> 0xbfc00154
14-02-2006: 0xBF4 (2.71)                          |  SHA-256: F9160C03EC6174F54F1C1EB645CFBBDB65B3DA47DA1A5478BE30E5EB2B0852B4 (payload only)
    }
    // 0xbfc00158
    return unknown_88400000(-0x78000000, v8);
}
</pre>
</pre>


==Devkit behavior==
<pre>
// 0.6 iplloader
    // 0xbfc00168
    function_bfc001b0(100);
    uint32_t v13 = function_bfc006b0(0x8400000, 0x1fe01000); // 0xbfc0017c
    // branch -> 0xbfc00184
    while (v13 < 0) {
        // 0xbfc00184
        // continue -> 0xbfc00184
    }
    // 0xbfc00188
    return unknown_88400000(-0x78000000, v9, v10, v1);
}
</pre>


On DTP-T1000 and DEM-1000 a different ROM/hardware and loading process are used, pre-ipl will be part of a kbooti/bootdispi/writei... file containing the encrypted pre-ipl data from 0x0 to 0x1000 followed by IPL blocks.
=== 0.7.0-2.50 (18-09-2004) ===
these files are loaded externally through the Communication Processor using the bloadp command to the Persistent Boot Storage (0xBFE00000), the hardware checks the pre-ipl data size (0x10-0x12) to know how much of the file it needs to check against the data hash, most likely CMAC encrypted (0x0-0x10), (anything over the size range is not checked/loaded), if the hash fails execution halts, it then decrypts the pre-ipl data (cryptanalysis strongly suggest the use of a CBC block cipher, either CBC AES or CBC DES, both the CMAC and data are encrypted with a static key) itself and copies it to a memory mapped as 0xBFC00000 on the psp side starts/reset the MIPS cpu which jumps to it, (the regular CPU rom is not mapped, according to Tachyon's revision it is in fact identical to the rom present in TA-079v1 PSPs, it is capable of loading regular IPLs directly from NAND or Memory stick, on DTP-H1500/DTP-L1500 the normal behavior occurs and the IPL is loaded from NAND), on DTP-T1000 0xBFD00000 is an invalid address, trying to read from or write to it at IPL time will trigger a crash, this effectively renders the pre-ipl dynamic (updatable/downgradable) at will since it is not stored in rom format.


===Prototype 0.4.0-0.6.0 (23-07-2004 or older) ===
The 0.7.0+ iplloader copies its payload from 0xBFC000280 to 0x80010000 i.e. physical address 0xA0010000, and jumps there. Because on DTP-T1000 0xBFC00000 is writable during the iplloader execution, and because 0xBFD00000 is an invalid range on DTP-T1000, the payload uses the 0xBFC00000 memory, which originally contains the whole iplloader as work RAM. However, it does not wipe itself so the payload is dumpable from 0xA0010000, assuming that one has code execution at IPL time.
Prototype kbooti will read the IPL blocks in place from 0xBFE01000 and decrypts them to 0x88400000 before jumping there, IPL blocks have no metadata in prototype IPLs instead the pre-ipl uses 0x88400000 as an hardcoded entry point, in fact the IPL payload/part3, nested inside current IPL revisions and loaded by main.bin are using the prototype format, so the prototype pre-ipl loads the IPL part 3/payload directly.
It is also of worthy of note that prototype pre-ipls for DEM-1000 (for 0.4.0 and 0.6.0 firmwares) do not make use of 0xa0010000 as their payload location, the "payload" is instead executed in place at 0xbfc00000, as such the pre-ipl data is not wiped and can be dumped in its entirety using a custom IPL (please note that the IPL format is different than later revision IPLs, prototype IPLs are loaded in a single raw kirk cmd0 0x01 block)


===0.7.0-2.50 (18-09-2004) ===
IPL blocks are then loaded from 0xBFE01000 by the iplloader and copied to 0xBFC00000 where they are decrypted in place and copied to the location of load address specified in the metadata.
The 0.7.0+ pre-ipls will copy it's pre-ipl payload (stored at 0xBFC000280) to 0x80010000 (physical address 0xa0010000) and jump there, because on DTP-T1000 0xbfc00000 is writable at pre-ipl time and because 0xBFD00000 is an invalid range on DTP-T1000, the payload will use the 0xBFC00000 memory (which originally contains the whole pre-ipl loader+payload) as work ram; it will however not wipe itself so you can dump the important part (the payload) from 0xa0010000 (assuming you gain execution at IPL time), from 2.60 and onward the payload will just 0xbfc00000 using random data as IPL blocks get loaded.


IPL blocks are then loaded from 0xBFE01000 by the pre-ipl and copied to 0xBFC00000 where they are decrypted in place and copied to the location of load address specified in the metadata.
=== 2.60+ (22-10-2005) ===
 
Because a hash of the data stored between 0xBFC00040 and 0xBFC002C0 is used in an additional step by 2.60+ IPLs to decrypt main.bin, from 2.60 and onward the iplloader payload overwrites 0xBFC00000 with an identical copy of first 0x2C0 bytes of the original PSP-1000 (01g) iplloader ROM data. This data is stored at 0xBFC00BB0 in the 2.60 and 2.71 kbooti, 0x80010930 in the payload. The iplloader payload memsets 0x1000 bytes at 0xBFC00000 to 0 and writes the chunk there, before jumping to the IPL entrypoint. If bootstrapping the 1.50 firmware using the 2.60/2.71 iplloader part, data from the retail ROM addresses 0xBFC00200 to 0xBFC002C0 are retrievable. Note that this would not have been enough to generate the hash required to decrypt 2.60+ main.bin in any case and dumping using a custom IPL would be required to retrieve enough of the data even on a DTP-T1000.
 
Please note that starting from the 3.5.0 kbooti, the chunk of data written to 0xbfc00000 (which is hashed to be used as a seed for IPL part 2 decryption) changes and no longer matches any iplloader code. It is no longer code but random data.
 
2.6.0 kbooti also appears to fix one of the iplloader flaws that allowed to load the Pandora time attacked block. It checks for the entrypoint not to be in the 0xb* range. However, it does not check for the data size of the block.
 
The code that copies 0x2C0 bytes from 0x80010930 to 0xBFC0xxxx verbatim from the 2.6.0 iplloader payload:
<pre>
0x800100c4:  19 00 20 13  beq 0x8001012c <entry_point+0x12c>, $zero, 0x8001012c <entry_point+0x12c>
0x800100c8:  00 00 00 00  sll $zero, $zero, 0x0
0x800100cc:  8f 40 00 0c  jal 0x8001023c <function_8001023c>
0x800100d0:  00 00 00 00  sll $zero, $zero, 0x0
0x800100d4:  81 40 00 0c  jal 0x80010204 <function_80010204>
0x800100d8:  00 00 00 00  sll $zero, $zero, 0x0
0x800100dc:  c0 bf 04 3c  lui $a0, 0xbfc0
0x800100e0:  21 28 00 00  addu $a1, $zero, $zero
0x800100e4:  00 10 06 24  addiu $a2, $zero, 0x1000
0x800100e8:  5a 41 00 0c  jal 0x80010568 <function_80010568>
0x800100ec:  00 00 00 00  sll $zero, $zero, 0x0
0x800100f0:  c0 bf 04 3c  lui $a0, 0xbfc0 #0xbfc000000
0x800100f4:  01 80 05 3c  lui $a1, 0x8001
0x800100f8:  30 09 a5 24  addiu $a1, $a1, 0x930 #0x80010930
0x800100fc:  00 00 06 3c  lui $a2, 0x0
0x80010100:  c0 02 c6 24  addiu $a2, $a2, 0x2c0 #size of the data (0x2C0)
0x80010104:  50 41 00 0c  jal 0x80010540 <function_80010540>
</pre>
 
Note that neither the 0xbfc00000 (on DTP-T1000) nor 0xa0010000 memory locations survive reboots.
 
=== 3.50 (12-02-2007) ===
 
The iplloader adds a step using a 0x40 bytes XOR key to decrypt the CMAC hash and data keys from the IPL block headers. As a result you cannot decrypt using Kirk command 1 the IPL blocks meant for the new iplloader.
 
The XOR key is overwritten early using a memset to 0 whenever the special Jig emulation mode is used (so the IPL block is xored by 0), this makes it impossible to obtain without glitching, knowing the kbooti CBC encryption key, knowing the xor key beforehand or using an IPL Loader exploit.
 
    if ( MEMORY[0xBFEFFFFC] < 0 )
    sub_800105F0(&unk_8001088C, 0, 64);
 
The seed for the IPL decryption (as it is used since 2.6.0) has changed to become a pseudo random 0x2C0 sized blob that is copied back from 0x800109B0 to 0xBFC00000 before jumping to the IPL entry point.
SHA-256: 1E6FC02124901F6F5A3F1BB02F065064C63E423D759A131BA1086EA8FC2D90AA


=== 2.60+ (22-10-2005) ===
=== Development Tool Jig Memory Stick Emulation Mode ===
Because a hash of the data stored between 0xbfc00040 and 0xbfc002c0 is used as an additional step by 2.60+ IPLs to decrypt main.bin, from 2.60 and onward the payload will overwrite 0xbfc00000 with an identical copy of first 0x2C0 bytes of the original psp-1000(01g) pre-ipl rom data (stored at 0xBFC00BB0 in the 2.60 and 2.71 kbooti) and wipe everything after 0xbfc002C0 with 00 before jumping to the IPL entrypoint, if bootstrapping the 1.50 firmware using the 2.60/2.71 kbooti pre-ipl part, data from the retail rom addresses 0xBFC00200 to 0xBFC002C0 is retrievable, please note that this would not have been enough to generate the hash required to decrypt 2.60+ main.bin in any case and dumping using a custom IPL would be required to retrieve enough of the data even on a DTP-T1000 because every Sony IPL performs "*(u32 *)0xBC100004 = -1;)


Please note that neither the 0xbfc00000 (on DTP-T1000) nor 0xa0010000 memory locations survive reboots.
Starting from kbooti 0.7.0, a special Jig test mode exists. It reads an IPL block at 0x2000 on the Memory Stick instead of address 0xBFE01000 if the following condition is met:


=== 3.50+ (01-06-2007 or older) ===
    if ( MEMORY[0xBFEFFFFC] < 0 ) use MS


The pre-ipl adds a step using an xor key to decrypt the CMAC hash and data keys from the IPL block headers, as a result you cannot decrypt the IPL blocks meant for the new pre-ipl using kirk cmd 1.
Indeed, writing 0xFFFFFFFF at 0xBFEFFFFC enables the pseudo service mode and reads the IPL block from the Memory stick. This mode is used by Sony engineers to debug Jig Memory Sticks by writing a kbooti using bloadp and then using reset parameters to set the flag at 0xBFEFFFFC using the sbootp param/arg from the reset command of either dstdb or bsreset (dspreset) such as follows:
/usr/local/sony/bin/bootdispi/dspreset 80000000 (FFFEFFFF for example sets all boot flags to 0xFF), to set the DTP-T1000 into Jig emulation mode.  


==Retail behavior==
Because flags are incremental, the only way to clear the Jig flag using official SDK tools is to run the bloadp command again as this clears the whole tachsm0 memory including the flags.


pseucode from Tachyon 0x00140000-0x00300000 pre-ipl payload:
Kbooti remains loaded in memory until the main unit is turned off or bloadp has ran again, allowing then to power cycle through different memory stick.
<pre>
int iplBlockNumber = 0;
u32 checksum = 0;


// load/decrypt all encrypted ipl blocks
In kbooti revision 3.5.0, this mode skips the XOR step on the Kirk header. It overwrites the XOR key with zeroes in the scratchpad. That allows one to use a regular IPL block to achieve code execution and dump the iplloader payload.
while(1)
{
// copy an encrypted ipl block to 0xBFD00000-0xBFD01000 (4KB embedded cpu ram)
if (LoadIplBlock(iplBlockNum ber, block) < 0)
while(1);


// decrypt the ipl block in place (uh oh...)
== Retail behaviour ==
if (DecryptIplBlock(block, block))
while(1);


// first block will have zero as its checksum since there is no previous block (another uh oh...)
The full reverse engineered assembly code can be found in uOFW: https://github.com/uofw/uofw/tree/master/src/preipl.
if (block->checksum != checksum)
while(1);


// load the 'data' section of the ipl block to the specified address (0x040Fxxxx range)
=== The loader ===
if (block->loadaddr)
checksum = memcpy(block->loadaddr, block->data, block->blocksize);


// reached the end of the ipl, jump to the entry address (0x040F0000)
The loader part is almost the same for all retail iplloader versions.
if (block->entry)
{
// clear caches
Dcache();
Icache();


// jump to ipl - do not return
<pre>
block->entry();
ctc0($4, $v0); // save $v0
if (*0xBC100000 != 0) { // NMI interrupts enabled
    if (cfc0($9) != 0) { // NMI handler
        jump to $9;
    } else {
        $v0 = cfc0($4); // restore $v0
        // disable the BEV bit (on R4400, it moves the non-reset and NMI interruption handling to 0x80000000 instead of 0xBFC00200, but it may have a different behavior here)
        mtc0(Status, mfc0(Status) & 0xFFBFFFFF);
        jump to mfc0(EBase);
    }
} else {
    ctc0($9, 0); // reset NMI handler
    sceKernelL1IcacheInvalidateAll();
    sceKernelL1DcacheInvalidateAll();
    memcpy(0xA0010000, preipl_payload, preipl_payload_size);
    $sp = 0x80013FF0;
    sync();
    jump to 0x80010000;
}
}
// image ends with a copyright string, and a build timestamp: 0x20040420, 0x20050104 and 0x20070910 for successive iplloader versions
</pre>
=== First version ===
This version of the iplloader is used for Tachyon 0x00140000 to 0x00300000 (ie all versions of 01g except the few last).
Here is the pseudocode of the payload (not including implementation details, and excluding the information to interface with NAND & MemoryStick):
<pre>
0xBC100058 |= 0x00800000; // Enable GPIO clock
0xBC100050 |= 0x0000608E; // Enable bus clock for AW (which is GE) (RegA, RegB, Edram), KIRK, NAND (EMCSM) and APB (for syscon?)
0xBC10004C &= ~0x408;    // Clear reset for AW and KIRK
0xBC100078 |= 2;          // IO enable NAND (EMCSM)
0xBE240000 &= ~0x10;      // Disable GPIO pin 4 output
0xBE240040 |= 0x10;      // Enable GPIO pin 4 input
sleep(1);                // Wait a bit
0xBD500010 = 1;          // Initialize GE Edram
while (*0xBD500010 & 1 != 0) {} // Wait for the Edram to be initialized
0xBD500040 = 1;          // Finish initializing GE Edram


iplBlockNumber++;
if (*0xBC100068 >> 16 != 0) { // Unknown bits
    *0xBC100078 |= 0x800; // Enable audio clock out??
} else {
    *0xBC10007C |= 0x10; // Enable GPIO pin 4 (used for jigkick)
}
sleep(10); // Wait a bit
int (*InitStorage)(void);
int (*ReadBlock)(int blkIndex, void *destination);
if (*0xBE240004 & 0x10 == 0) { // Check GPIO pin 4 to decide if we boot from NAND or MemoryStick (ie jigkick)
    InitStorage = InitNand;
    ReadBlock = ReadNandBlock;
} else {
    InitStorage = InitMemoryStick;
    ReadBlock = ReadMemoryStickBlock;
}
}


</pre>
u32 iplBlockIdx = 0;
u32 lastBlockChecksum = 0;
 
while (1) {
    // Read one IPL block (size 0x1000) from the NAND or MemoryStick
    if (ReadBlock(iplBlockIdx, 0xBFD00000) < 0) {
        while (1); // infinite loop
    }
 
    // Decrypt the block in-place
    if (Kirk1Decrypt(0xBFD00000, 0xBFD00000) < 0) {
        while (1);
    }


Tachyon revisions 0x00140000 to 0x00300000 pre-ipl pseudo code:
    // Read the decrypted block header
    u32 dstAddress  = *(u32*)0xBFD00000; // destination address for the decrypted data
    u32 dataSize    = *(u32*)0xBFD00004; // size of the decrypted data (excluding the header)
    u32 entrypoint  = *(u32*)0xBFD00008; // the entrypoint address if this is the last block, 0 otherwise
    u32 prevChecksum = *(u32*)0xBFD0000C; // checksum of the previous block (computed below)


<pre>
    if (lastBlockChecksum != prevChecksum) {
        while (1);
    }


PSP Disassembler Ver.0.20 Copyright(c)2005,2006 BOOSTER
    if (dstAddress != 0) {
incl. elf-lib 0.1r2 copyright (c) 2005 djhuevo
        // Copy the rest of the block at the specified address
        // The checksum is just the XOR of the 32-bit words of the data
        lastBlockChecksum = _memcpy(dstAddress, 0xBFD00010, dataSize);
    }


:file name '0x80010000.bin',size = 4096
    if (entrypoint != 0) {
Load 3684 NID's name
        dcacheWritebackInvalidateAll();
        icacheWritebackInvalidateAll();
        jump to entrypoint;
    }


:Disasm
    iplBlockidx++;
}
</pre>


;copied by pre-ipl bootcode, from 0xbfc00280-0xbfc00d78
=== Second version ===
;here code is 0x80010000 to 0x80010af8


;
This version of the iplloader is used by Tachyon versions 0x00400000 to 0x00500000. This corresponds to the last 01g motherboard versions, and all the 02g versions except the last.
;$bfd00000-$bfd00fff : sector read buffer
;


;-------------------------------------------------------
It is very close to the original version, only some hardware initialization is modified, probably to improve stability/prevent crashes:
;recovery mode selector


;be240004 GPIO READ REG.
<pre>
; bit4 : device select ,0=NAND Flash, 1= rec-dev
0xBC100058 |= 0x00800000; // (same) Enable GPIO clock
;
0xBC100050 |= 0x0000608E; // (same) Enable bus clock for AW (which is GE) (RegA, RegB, Edram), KIRK, NAND (EMCSM) and APB (for syscon?)
;-------------------------------------------------------
0xBC100050 &= ~0x8E;      // (new ) Disable bus clock for AW (RegA, RegB, Edram) and KIRK
0xBC10004C &= ~0x408;    // (same) Clear reset for AW and KIRK
0xBC100050 |= 0x0000008E; // (new ) Re-enable bus clock for AW (RegA, RegB, Edram) and KIRK
0xBC100078 |= 2;         // (same) IO enable NAND (EMCSM)
0xBE240000 &= ~0x10;      // (same) Disable GPIO pin 4 output
0xBE240040 |= 0x10;       // (same) Enable GPIO pin 4 input
for (i = 0; i < 0x800; i++) { (void)*0xBC100040; } // (new) Read 0xBC100040 2048 times (unsure why)
sleep(1);                // (same) Wait a bit
0xBD500010 = 1;          // (same) Initialize GE Edram
while (*0xBD500010 & 1 != 0) {} // (same) Wait for the Edram to be initialized
0xBD500040 = 1;          // (same) Finish initializing GE Edram
</pre>


;-------------------------------------------------------
There are also similar minor differences in the code to initialize MemoryStick hardware.
;recovery boot device (rec-dev) HW assign
;
;bd200030 command register
00009007 : read request (write size = 8)
00002200 : read sector buffer (read size=200)
00008004 : ? status (write size = 8 , read size=8)
00004000 : ? status (read size=8)
00007001 : ? (data size=8?)


;bd200034 data register (read / write)
=== Third version ===
;bd200038 status register
; bit14:read data ready
; bit13:parameter wirte ready
; bit12:transmit finish ?
; bit 9:???? error
; bit 8:read data error
;
;bd20003c ???
bit15:reset device ?
;
;-------------------------------------------------------


This version of the iplloader is present on the last 02g model and all of the 03g+ models.


;---------------------------------------------------------------------------
It adds many security checks to avoid the creation of custom IPLs for these models — all of which have been broken since.
;entry
;---------------------------------------------------------------------------
L80010000:
;
;reset I/O
$800106B0(L80010a80)
;
  lui    r8,$bc10                          ;80010010[3C08BC10,'...<']
;
if(r8[$68]>>16 == 0) $80010034
;
r9 = r8[$78]
  ori    r9,r9,$0800                        ;80010028[35290800,'..)5']
  b      $80010040                          ;8001002C[10000004,'....']
  sw    r9,$78(r8)                        ;80010030[AD090078,'x...']
;
;80010034
  lw    r9,$7c(r8)                        ;80010034[8D09007C,'|...']
  ori    r9,r9,$0010                        ;80010038[35290010,'..)5']
  sw    r9,$7c(r8)                        ;8001003C[AD09007C,'|...']
  addiu  r4,0,$a                            ;80010040[2404000A,'...$']
  jal    $80010768                          ;80010044[0C0041DA,'.A..']
  sync                                      ;80010048[0000000F,'....']
;80010768
;
;check recovery boot mode switch
;
r8 = [$be240004] & 0x10 // GPIO bit 4
;
r9 = $80010194 // NAND read BLOCK entry
r10= $80010130 // NAND initialize(read FAT) entry
;
if(r8!=0)// $80010080
{
r9 = $80010248 // rec-dev read BLOCK entry
r10= $80010240 // rec-dev initialize entry
}
;80010080
[$80010808] = r9
[$8001080c] = 0x000000000
;
;call read FAT
;
(r10)()
;
r23 = 0  ; check sum of last block
;
;READ BLOCK LOOP
;
L80010098
r25 = [$80010808] ; read BLOCK entry
r4  = [$8001080c] ; block num of read
;
;call read body function
;
r2 = (r25)(r4,$bfd000000);
if(r2<0) $80010128
;
;decrypt 1000H block
;
r2 = $80010620($bfd00000,$bfd00000)
if(r2<0) $80010128
;
;+000c : check sum of last block ?
;
if( [$bfd0000c] != r23) $80010128
;
;+0000 : distination address
;+0004 : block size
;
r4 = [$bfd00000] ; +0000 : top pointer
r6 = [$bfd00004] ; +0004 : size
if(r4!=0)
{
;transmit BLOCK body
r23 = $80010688(r4,r16 + $10 , r6)
}
;
;+0008 : entry point or continue MARK check
;
r25 = r16[8]
if(r25==0) //$80010114
{
;L80010114:
[$8001080c]++  ; next block
goto $80010098
}
;
;cache ?
$800102D8()
;
;cache ?
$800102A0()
;
;goto IPL entry point
;
  jalr  r25                                ;8001010C[0320F809,'.. .']
;
;80010128
while(1); // HALT
;
;---------------------------------------------------------------------------
;read IPL FAT
;---------------------------------------------------------------------------
L80010130:
r16 =r31
;
;80010134
;NAND reset
$80010308()
;
;8001013C
r17  = $80 // top of IPL-FAT sector
;80010140
;read IPL FAT
r2 = $80010334(r17,8001081c,80010810)
if(r2<0) $8001018c
;80010164
r8 = r6[0]
r9 = r6[4]
r10= r6[8]
;ECC signature
if(r9 != $6dc64a38) // $8001018c
{
r17 += $20 // next IPL sector
goto $80010140
}
; jr    r16                                ;80010184[02000008,'....']
return


;------------------------------------------------------------------------
List of changes:  
;NAND read body
;
;r4 : fat logical ptr (400H bytes lba? )
;
;------------------------------------------------------------------------
L80010194:
[$80010800] = r31
[$80010804] = r4
;
r17 = r5
;get FAT location
r8 = $8001081c ; FAT table
r9 = (r4>>2)<<1
r8 += r9
r9 = (u16)r8[0]
;
r8 = (r9<<2) | (r4 & 3)
;
r16 = r8 << 3 ; * 8
r18 = 0
;800101CC
do
{
;read body one
r2 = $80010334(r16+r18,r17 + (r18<<9),80010810)
if(r2<0) $80010230
r8 = r6[0]
r9 = r6[4]
r10= r6[8]
;ECC signature
if(r9 != $6dc64a38) $80010230
r18++
}while(r18<8);
;80010220
r31 = [$80010800]
return r0


L80010230:
* IPL load address now blacklists the CPU Scratchpad range (address & 0x1FFF0000 != 0x00010000)
r31 = [$80010800]
return -1


;------------------------------------------------------------------------
* The mode field of the KIRK headers must have their 16 MSB set to 0, or have their LSB set to 1 (ie mode & 0xFFFF0000 == 0, or mode & 0x00010000 == 1). The second case seems to mean the XOR keys have to be applied. The 16 MSB are erased before the KIRK command is actually ran
;rec-dev initialize
;------------------------------------------------------------------------
L80010240:
return $800103B4()


;------------------------------------------------------------------------
* IPL blocks now must have a minimum size of 0x100 bytes (including the 16-byte header)
;rec-dev read 1000H block
;------------------------------------------------------------------------
L80010248:
[$80010$800] = r31
;
r16 = r4
r17 = r5
r18 = 0
;8001025C
do{
r2 = $80010418(r18+0x10+r16<<3 ,r17 + r18<<9)
if(r2<0) $8001025c
r18++
}while(r18<8);
;
r31 = [$80010$800]
return r2


;------------------------------------------------------------------------
* Blocks can be encrypted with KIRK commands 0~3 (in practice only command 1 is used)
;cache ?
;------------------------------------------------------------------------
L800102A0:
  mfc0  r8,Config                          ;800102A0[40088000,'...@']
  addiu  r9,0,$1000                        ;800102A4[24091000,'...$']
  dc.l  $7d081240 [invalid]                ;800102A8[7D081240,'@..}']
  sllv  r9,r9,r8                          ;800102AC[01094804,'.H..']
  mtc0  0,TagLo                            ;800102B0[4080E000,'...@']
  mtc0  0,TagHi                            ;800102B4[4080E800,'...@']
  addu  r8,0,0                            ;800102B8[00004021,'!@..']
;
  cache  $01,r8($0)                        ;800102BC[BD010000,'....']
  cache  $03,r8($0)                         ;800102C0[BD030000,'....']
  addiu  r8,r8,$40                          ;800102C4[25080040,'@..%']
  bne    r8,r9,$800102bc                    ;800102C8[1509FFFC,'....']
  nop                                      ;800102CC[00000000,'....']
  jr    r31                                ;800102D0[03E00008,'....']
  nop                                      ;800102D4[00000000,'....']


;------------------------------------------------------------------------
* ECDSA verification (as specified in the KIRK header) must be enforced on the last KIRK-encrypted block
;cache ?
;------------------------------------------------------------------------
L800102D8:
  mfc0  r8,Config                          ;800102D8[40088000,'...@']
  addiu  r9,0,$800                          ;800102DC[24090800,'...$']
  dc.l  $7d081180 [invalid]                ;800102E0[7D081180,'...}']
  sllv  r9,r9,r8                          ;800102E4[01094804,'.H..']
  addu  r8,0,0                            ;800102E8[00004021,'!@..']
;
  cache  $14,r8($0)                         ;800102EC[BD140000,'....']
  cache  $14,r8($0)                        ;800102F0[BD140000,'....']
  addiu  r8,r8,$40                          ;800102F4[25080040,'@..%']
  bne    r8,r9,$800102ec                    ;800102F8[1509FFFC,'....']
  nop                                      ;800102FC[00000000,'....']
  jr    r31                                ;80010300[03E00008,'....']
  sync                                      ;80010304[0000000F,'....']


;----------------------------------------------------------------------------
* The last 0x20 bytes of the block contain a SHA1 hash encrypted with KIRK command 7 and keyseed = 0x6C.
;NAND reset CMD
;----------------------------------------------------------------------------
L80010308:
;nand cmd
[$bd101008] = 0xff
;nand sts
while( [$bd101004] & 1 ==0);
;
[$bd101014] = 0x01
return


;----------------------------------------------------------------------------
The block hash is calculated using: <pre>sha1(block[8:] + block[:8])</pre>
;NAND Read Sector
Which means the first 8 bytes of the payload (containing the first half of the block header) are copied to the end before the block is hashed.
;
;r4 : sector
;r5 : data buffer
;r6 : Extra buffer
;
;----------------------------------------------------------------------------
L80010334:
;nand sts
while([$bd101004] & 1 == 0);
;
[$bd101020] = r4 << 10
[$bd101024] = $301
;80010354
while([$bd101024] & 1 == 0);
;80010364
if([$bd101028] != 0) return -1
;
  lui    r8,$bff0                          ;80010370[3C08BFF0,'...<']
;
r9  = r8[$900]
r10 = r8[$904]
r2  = r8[$908]
;
r6[0] = r9
r6[4] = r10
r6[8] = r2
;
r9 = r5
r2 = $200
L80010394:
do
{
r10 = r8[0]
r2 -= 4
r8 += 4
r9[0] = r10
r9 += 4
}while(r2);
;800103AC
return 0


;----------------------------------------------------------------------------
The last block hash remains in memory and is xored in each SHA1 for each block, the result is used for the hash compare in the Kirk command 0x10 ECDSA check.
;rec-dev initialize
;----------------------------------------------------------------------------
L800103B4:
r24 = r31
;
;rec-dev I/O init : device & clock enable ?
$800106B0(L80010ad4)
;
;reset device ?
;
[$bd20003c] = $8000
;800103D4
while( [$bd20003c] & 0x8000);
;
$80010530()
$80010508()
;
;800103F4
do{
r2 = $800105B8()
if(r2<0) continue // $800103f4
;
}while(r2 & $0080 == 0); // $800103f4
;
;  jr    r24                                ;80010410[03000008,'....']
return 0


;----------------------------------------------------------------------------
* The XOR of all the blocks' hashes (as computed in the previous step) is computed, and in the last block (entrypoint != 0), offset 0xFA0 contains a signature of this value, which is verified with KIRK command 0x11 using a custom public key stored inside the ROM (0xbc660611a70bd7f2d140a48215c096d11d2d4112, 0xf0e9379ac4e0d387c542d091349dd15169dd5a87).
;rec-dev read sector one
;
;a1:sector address
;a2:buffer
;
;----------------------------------------------------------------------------
L80010418:
r14 = r5
; r24 = r31
;  lui    r25,$bd20
;
;read sector COMMAND ?
[$bd200030] = $00009007
;
  dc.l  $7c0428e0 [invalid]                ;8001042C[7C0428E0,'.(.|']
;
r5 >>= 8
r9 = (r4 >> 24) << 24
;
r4 = $00010020
r4 |= r9
;
;write parameter
r2 = $800104C0(r4,r5)
if(r2<0) return -1
;busy wait
$80010608()
;
;$8001045c
do{
r2 = $800105B8()
if(r2<0) return -1
r2 = r2 & $0020
}while((r2 & $0020)==0);
;
if(r2 & $0040) return -1
;
;read buffer COMMAND ?
[$bd200030] = $00002200
;
;read sector data
r2 = $800104CC(r14,$200) // r14 == r5
if(r2<0) return -1
;wait finish
r2 = $80010508()
if(r2<0) return -1
;busy wait
$80010608()
;
goto $800103f4
;
;800104B8
;  jr    r24                                ;800104B8[03000008,'....']
return -1


;----------------------------------------------------------------------------
* The first 0x10 bytes of IPL blocks are, before decryption, xored using a XOR key, stored in the ROM and selected from a value stored in the NAND spare data (see [[NAND Flash Memory]] for more details), or set to 0 for jigkick boot. If this value is zero, the XOR key step is skipped (probably to allow compatibility of the Jig Memory Stick across all devices). Otherwise, bits 0..4 of that value are used as an index on the XOR key table, and bits 5..11 are used as an additional rotation index. In practice, this value is 1 for all targets except 05g, and 05g uses the value 2.
;rec-dev : commmand output ?
;
;a1:1st write data
;a2:2nd write data
;----------------------------------------------------------------------------
L800104C0:
[$bd200034] = r4
[$bd200034] = r5
goto $80010508


;----------------------------------------------------------------------------
==== Vulnerabilities ====
;rec-dev read data block
;
;a1:distination pointer
;a2:transmit size
;
;----------------------------------------------------------------------------
L800104CC:
do{
do{
r9 = [$bd200038]
if(r9 & $0100) return -1;
}while(r9 & $4000 == 0);
;
r2 = [$bd200034]
r5 -= 4
r4[0] = r2
r4 += 4
}while(r5 >=0);
;
return 0;


;----------------------------------------------------------------------------
Since the KIRK command 1 ECDSA private key & encryption key were known, the main issues for dumping the iplloader were the XOR keys, the hash check, and the additional ECDSA check on the XOR of hashes.
;wait for TX finish ?
;----------------------------------------------------------------------------
L80010508:
do{
r9 = [$bd200038]
}while(r9 & $1000 == 0);
;
if(r9 & $0300) return -1// $80010528
;
return 0


;----------------------------------------------------------------------------
The XOR keys are easy to disable if you can enable jigkick, which had been done for 03g before Davee dumped the iplloader. (Note that now that we know that it can be disabled writing the appropriate data in the NAND spare data, it could also be circumvented that way.)
;rec-dev read status ?
;----------------------------------------------------------------------------
L80010530:
;  addu  r15,r31,0                          ;80010530[03E07821,'!x..']
;  lui    r25,$bd20                          ;80010534[3C19BD20,' ..<']
;
[$bd200030] = $00008004
[$bd200034] = $06100800
[$bd200034] = 0
r2 = $80010508()
if(r2<0) return -1
;
$800105A4($80010a1c,8);
;
;get status ?
;
r4 = [$80010a1c]
r5 = [$80010a20]
;
if( (r4>>16)&0x15 != 0) return -1
;
return 0


;----------------------------------------------------------------------------
The hash check would've been easy to solve, but the fact the block was rotated before being hashed was unknown. Davee glitched this check in order to pass.
;rec-dev read status ?
;
;a1:buffer
;a2:size
;
;----------------------------------------------------------------------------
L800105A4:
[$bd200030] = $00004000
;read sector body
return $800104cc(r4,r5)


;-----------------------------------------------------------------------------
For the additional check, it could be skipped using a clever trick: if you set 0xBC10004C as the destination address of the IPL block, then the CPU resets and remaps 0xBFD00000 to 0xBFC00000 then runs back at 0xBFC00000. Since 0xBFD00000 is used as a temporary space for the decrypted IPL, it means you can easily achieve code execution. Davee used this method to dump the iplloader payload, which contains all the relevant information.
;rec-dev device ready check?
;-----------------------------------------------------------------------------
L800105B8:
;r25 = $bd200000
[$bd200030] = $00007001
;wait
do
{
r9 = [$bd200038]
}while(r9 & $0100);
if((r9 & $4000)==0) $800105c0
;
r2 = [$bd200034]
r0 = [$bd200034] // ?
;800105E0
do{
r9 = [$bd200038]
if(r9 & $0100) //$80010600
{
L80010600:
return -1
}
}while( (r9 & $1000)==0);
;
return r2 & 0xff;


;----------------------------------------------------------------------------
Now that the code is known, the two first issues are very easy to handle. For the last one, an easy trick is that the signature is on the XOR of the SHA1's, which means if you place your own block twice before a legitimate IPL, the two SHA1's will cancel each other and the signature check will pass.
;wait for rec-dev busy
;----------------------------------------------------------------------------
L80010608:
do
{
r9 = [$bd200038]
}while( (r9 & $2000) == 0);
return


;-----------------------------------------------------------------------------
== PS Vita Compatibility mode behaviour ==  
;decrypt 1000H block
;-----------------------------------------------------------------------------
L80010620:
;  lui    r25,$bde0                          ;80010620[3C19BDE0,'...<']
;
[$bde00010] = $00000001
;r8 = r4
  dc.l  $7ca8e000 [invalid]                ;8001062C[7CA8E000,'...|']
[$bde0002c] = r8
;
;r8 = r5
  dc.l  $7c88e000 [invalid]                ;80010634[7C88E000,'...|']
[$bde00030] = r8
;
[$bde0000c] = $00000001
;$80010644
do{
r8 = [$bde0001c]
}while(r8 & $0011 == 0)
;
[$bde00028] = r8
if(r8&$0010) // $80010664
{
L80010664:
[$bde0000c] = $00000002
;$8001066C
do{
r8 = [$bde0001c]
}while(r8 & $0002 == 0)
;
[$bde00028] = r8
sync
return -1
}
;
return [$bde00014]


;----------------------------------------------------------------------------
On PS Vita, PSP iplloader is sent by the <b>Compatibility Security Module</b> (<code>os0:sm/compat_sm.self</code>) to the non-secure ARM kernel, which writes it to <b>0xE8100000</b> (<i>named CompatSharedSram and mapped to the <b>0xBFC00000</b> reset vector on the emulated PSP/Tachyon side</i>).
;transmit data with calc check sum
;
;arg1: source
;arg2: distination
;arg1: size
;
;return : 32bit check sum (add)
;
;----------------------------------------------------------------------------
L80010688:
r2 = 0 ; clear check sum
do{
r3 = r5[0]
r5 += 4
r6 -= 4
r4[0] = r3
r2 += r3 ; check sum
r4 += 4
}while(r6>=0);
return


;---------------------------------------------------------------------------
Then, <code>compat_sm.self</code> sends a specific 0x40-bytes XOR key to be used by the PS Vita's iplloader as a 0x40 bytes XOR mask against the IPL header. The IPL is stored in the <code>pcbc.skprx</code> kernel module.
;script executer
;
;a1:script pointer
;
;2 word command
;
;+00[31:28] : CMD
;+00[27: 0] : OFFSET
;+04[31: 0] : VALUE
;
;CMD : command :
; 0  : store  | [$b0000000 + OFFSET]  = VALUE
; 1  : or      | [$b0000000 + OFFSET] |= VALUE
; 2  : and    | [$b0000000 + OFFSET] &= VALUE
; 3  : wait toL| while( ( [$b0000000 + OFFSET] &= VALUE) != 0)
;(4) : wait toH| while( (~[$b0000000 + OFFSET] &= VALUE) != 0)
; 5  : delay  | for(cnt=VALUE*96;cnt;cnt--)
; F  : end    | return
;
;---------------------------------------------------------------------------
L800106B0:
r8 = r4
;addu  r25,r31,0
;
;800106b8
do{
// read CMD:offset + VALUE
r4 = r8[0]
r9 = r4 >> 28
r4 = ( (r4 << 4)>>4 ) | $b0000000
r5 = r4[4]
;
if(r9==0) //$80010724
{
;80010724
r4[0]=r5
goto $8001071c
}
if(r9==1)// $8001072c
{
;8001072C
r4[0] = r4[0] | r5
goto $8001071c
}
if(r9==2) // $8001073c
{
r4[0] = r4[0] & r5
goto $8001071c
}
r1 = 0
if(r9==3) $8001074c
;
r1 = $ffffffff ;  nor    r1,0,0
;!!!!! buggy code !!!!!
;  addiu  r10,r10,-$4
; if(r9==4) $8001074c
if(r9==7) $8001074c
;!!!!! buggy code !!!!!


; if(r9==5) //$80010714
[[Kirk]] command 1 is then used on the result. Unlike actual PSP units, the IPL is decrypting in a single large block, rather than in multiple blocks.
{
L80010714:
$80010768(r5);
goto L8001071C
}
;default:
;  jr    r25                                ;8001070C[03200008,'.. .']
return;
;
;case end
L8001071C:
// next script point
r8 += 8
}while(1);
;
;4,7
L8001074C:
{
do{
r9 =  (r4[0] ^ r1) & r5
}while(r9!=0);
goto $8001071c
}


;--------------------------------------------------------------------------
The 0x40 bytes key gets updated depending on the firmware version in use.
;delay
;--------------------------------------------------------------------------
L80010768:
r1 = ((r4 << 1) + r4)<<5 // * 96
while(r1) r1--;
return


;--------------------------------------------------------------------------
A 0x40 bytes XOR mask is also part of the 3.50+ DTP-T1000 security [[#Behaviour|(<i>see PSP iplloader section</i>)]].
;--------------------------------------------------------------------------
L80010784:
  nop                                      ;80010784[00000000,'....']
  nop                                      ;80010788[00000000,'....']
  nop                                      ;8001078C[00000000,'....']
  nop                                      ;80010790[00000000,'....']
  nop                                      ;80010794[00000000,'....']
  nop                                      ;80010798[00000000,'....']
  nop                                      ;8001079C[00000000,'....']
  nop                                      ;800107A0[00000000,'....']
  nop                                      ;800107A4[00000000,'....']
  nop                                      ;800107A8[00000000,'....']
  nop                                      ;800107AC[00000000,'....']
  nop                                      ;800107B0[00000000,'....']
  nop                                      ;800107B4[00000000,'....']
  nop                                      ;800107B8[00000000,'....']
  nop                                      ;800107BC[00000000,'....']
  nop                                      ;800107C0[00000000,'....']
  nop                                      ;800107C4[00000000,'....']
  nop                                      ;800107C8[00000000,'....']
  nop                                      ;800107CC[00000000,'....']
  nop                                      ;800107D0[00000000,'....']
  nop                                      ;800107D4[00000000,'....']
  nop                                      ;800107D8[00000000,'....']
  nop                                      ;800107DC[00000000,'....']
  nop                                      ;800107E0[00000000,'....']
  nop                                      ;800107E4[00000000,'....']
  nop                                      ;800107E8[00000000,'....']
  nop                                      ;800107EC[00000000,'....']
  nop                                      ;800107F0[00000000,'....']
  nop                                      ;800107F4[00000000,'....']
  nop                                      ;800107F8[00000000,'....']
  nop                                      ;800107FC[00000000,'....']


;-----------------------------------------------------------------------------
= Dumper =
;
;r31 save buffer
;
L80010800:
dl 800100B0


;
As of March 21st 2018, a dumper for DTP-T1000 iplloader has been made by mathieulh, [https://github.com/mathieulh/DTP-T1000-Pre-IPL-dumper it is available on GitHub].
;r4 save buffer
;
L80010804:
dl 0000002E
;
;read sector function entry
;
L80010808:
dl L80010194
;
;read block number
;
L8001080C:
dl L0000002E
;
;NAND Extra buffer
;
L80010810:
dl FFFFFFFF
dl 6DC64A38
dl FFFFFD89


;
= Trivia =
;NAND Data buffer (IPL FAT)
;
L8001081c
  mfhi  0                                  ;8001081C[00110010,'....']
  mflo  0                                  ;80010820[00130012,'....']
  dsllv  0,r21,0                            ;80010824[00150014,'....']
  dsrlv  0,r23,0                            ;80010828[00170016,'....']
  mult  0,r25                              ;8001082C[00190018,'....']
  div    0,r27                              ;80010830[001B001A,'....']
  nop                                      ;80010834[00000000,'....']
  nop                                      ;80010838[00000000,'....']
  nop                                      ;8001083C[00000000,'....']
  nop                                      ;80010840[00000000,'....']
  nop                                      ;80010844[00000000,'....']
  nop                                      ;80010848[00000000,'....']
  nop                                      ;8001084C[00000000,'....']
  nop                                      ;80010850[00000000,'....']
  nop                                      ;80010854[00000000,'....']
  nop                                      ;80010858[00000000,'....']
  nop                                      ;8001085C[00000000,'....']
  nop                                      ;80010860[00000000,'....']
  nop                                      ;80010864[00000000,'....']
  nop                                      ;80010868[00000000,'....']
  nop                                      ;8001086C[00000000,'....']
  nop                                      ;80010870[00000000,'....']
  nop                                      ;80010874[00000000,'....']
  nop                                      ;80010878[00000000,'....']
  nop                                      ;8001087C[00000000,'....']
  nop                                      ;80010880[00000000,'....']
  nop                                      ;80010884[00000000,'....']
  nop                                      ;80010888[00000000,'....']
  nop                                      ;8001088C[00000000,'....']
  nop                                      ;80010890[00000000,'....']
  nop                                      ;80010894[00000000,'....']
  nop                                      ;80010898[00000000,'....']
  nop                                      ;8001089C[00000000,'....']
  nop                                      ;800108A0[00000000,'....']
  nop                                      ;800108A4[00000000,'....']
  nop                                      ;800108A8[00000000,'....']
  nop                                      ;800108AC[00000000,'....']
  nop                                      ;800108B0[00000000,'....']
  nop                                      ;800108B4[00000000,'....']
  nop                                      ;800108B8[00000000,'....']
  nop                                      ;800108BC[00000000,'....']
  nop                                      ;800108C0[00000000,'....']
  nop                                      ;800108C4[00000000,'....']
  nop                                      ;800108C8[00000000,'....']
  nop                                      ;800108CC[00000000,'....']
  nop                                      ;800108D0[00000000,'....']
  nop                                      ;800108D4[00000000,'....']
  nop                                      ;800108D8[00000000,'....']
  nop                                      ;800108DC[00000000,'....']
  nop                                      ;800108E0[00000000,'....']
  nop                                      ;800108E4[00000000,'....']
  nop                                      ;800108E8[00000000,'....']
  nop                                      ;800108EC[00000000,'....']
  nop                                      ;800108F0[00000000,'....']
  nop                                      ;800108F4[00000000,'....']
  nop                                      ;800108F8[00000000,'....']
  nop                                      ;800108FC[00000000,'....']
  nop                                      ;80010900[00000000,'....']
  nop                                      ;80010904[00000000,'....']
  nop                                      ;80010908[00000000,'....']
  nop                                      ;8001090C[00000000,'....']
  nop                                      ;80010910[00000000,'....']
  nop                                      ;80010914[00000000,'....']
  nop                                      ;80010918[00000000,'....']
  nop                                      ;8001091C[00000000,'....']
  nop                                      ;80010920[00000000,'....']
  nop                                      ;80010924[00000000,'....']
  nop                                      ;80010928[00000000,'....']
  nop                                      ;8001092C[00000000,'....']
  nop                                      ;80010930[00000000,'....']
  nop                                      ;80010934[00000000,'....']
  nop                                      ;80010938[00000000,'....']
  nop                                      ;8001093C[00000000,'....']
  nop                                      ;80010940[00000000,'....']
  nop                                      ;80010944[00000000,'....']
  nop                                      ;80010948[00000000,'....']
  nop                                      ;8001094C[00000000,'....']
  nop                                      ;80010950[00000000,'....']
  nop                                      ;80010954[00000000,'....']
  nop                                      ;80010958[00000000,'....']
  nop                                      ;8001095C[00000000,'....']
  nop                                      ;80010960[00000000,'....']
  nop                                      ;80010964[00000000,'....']
  nop                                      ;80010968[00000000,'....']
  nop                                      ;8001096C[00000000,'....']
  nop                                      ;80010970[00000000,'....']
  nop                                      ;80010974[00000000,'....']
  nop                                      ;80010978[00000000,'....']
  nop                                      ;8001097C[00000000,'....']
  nop                                      ;80010980[00000000,'....']
  nop                                      ;80010984[00000000,'....']
  nop                                      ;80010988[00000000,'....']
  nop                                      ;8001098C[00000000,'....']
  nop                                      ;80010990[00000000,'....']
  nop                                      ;80010994[00000000,'....']
  nop                                      ;80010998[00000000,'....']
  nop                                      ;8001099C[00000000,'....']
  nop                                      ;800109A0[00000000,'....']
  nop                                      ;800109A4[00000000,'....']
  nop                                      ;800109A8[00000000,'....']
  nop                                      ;800109AC[00000000,'....']
  nop                                      ;800109B0[00000000,'....']
  nop                                      ;800109B4[00000000,'....']
  nop                                      ;800109B8[00000000,'....']
  nop                                      ;800109BC[00000000,'....']
  nop                                      ;800109C0[00000000,'....']
  nop                                      ;800109C4[00000000,'....']
  nop                                      ;800109C8[00000000,'....']
  nop                                      ;800109CC[00000000,'....']
  nop                                      ;800109D0[00000000,'....']
  nop                                      ;800109D4[00000000,'....']
  nop                                      ;800109D8[00000000,'....']
  nop                                      ;800109DC[00000000,'....']
  nop                                      ;800109E0[00000000,'....']
  nop                                      ;800109E4[00000000,'....']
  nop                                      ;800109E8[00000000,'....']
  nop                                      ;800109EC[00000000,'....']
  nop                                      ;800109F0[00000000,'....']
  nop                                      ;800109F4[00000000,'....']
  nop                                      ;800109F8[00000000,'....']
  nop                                      ;800109FC[00000000,'....']
  nop                                      ;80010A00[00000000,'....']
  nop                                      ;80010A04[00000000,'....']
  nop                                      ;80010A08[00000000,'....']
  nop                                      ;80010A0C[00000000,'....']
  nop                                      ;80010A10[00000000,'....']
  nop                                      ;80010A14[00000000,'....']
  nop                                      ;80010A18[00000000,'....']
;
;rec-dev status read buffer ?
L80010A1C:
dl 00000000,00000000
;
  nop                                      ;80010A24[00000000,'....']
  nop                                      ;80010A28[00000000,'....']
  nop                                      ;80010A2C[00000000,'....']
  nop                                      ;80010A30[00000000,'....']
  nop                                      ;80010A34[00000000,'....']
  nop                                      ;80010A38[00000000,'....']
  nop                                      ;80010A3C[00000000,'....']
  nop                                      ;80010A40[00000000,'....']
  nop                                      ;80010A44[00000000,'....']
  nop                                      ;80010A48[00000000,'....']
  nop                                      ;80010A4C[00000000,'....']
  nop                                      ;80010A50[00000000,'....']
  nop                                      ;80010A54[00000000,'....']
  nop                                      ;80010A58[00000000,'....']
  nop                                      ;80010A5C[00000000,'....']
  nop                                      ;80010A60[00000000,'....']
  nop                                      ;80010A64[00000000,'....']
  nop                                      ;80010A68[00000000,'....']
  nop                                      ;80010A6C[00000000,'....']
  nop                                      ;80010A70[00000000,'....']
  nop                                      ;80010A74[00000000,'....']
  nop                                      ;80010A78[00000000,'....']
  nop                                      ;80010A7C[00000000,'....']
;
;
;script command : I/O init
;
L80010A80:
dl 1C100058,00800000 ; [$bc100058] |= 00800000
dl 1C100050,0000608E ; [$bc100050] |= 0000608E
dl 2C10004C,FFFFFBF7 ; [$bc10004C] &= FFFFFBF7
dl 1C100078,00000002 ; [$bc100078] |= 00000002
dl 2E240000,FFFFFFEF ; [$be240000] &= FFFFFFEF : GPIO bit4 direction read?
dl 1E240040,00000010 ; [$be240040] |= 00000010 : GPIO bit4 pullup enable ?
dl 50000000,00000001 ; delay 1
dl 0D500010,00000001 ; [$bd500010] = 00000001
dl 3D500010,00000001 ; while( [$bd500010] & 1)
dl 0D500040,00000001 ; [$bd500040] = 00000001
dl F0000000 ; end
;
;script command : rec-dev I/O init
;
L80010AD4:
dl 3D500010,00000001 ; while( [$bd500010] & 1)
dl 1C100054,00000100 ; [$bc100054] |= 00000100
dl 1C100050,00000400 ; [$bc100050] |= 00000400
dl 1C100078,00000010 ; [$bc100078] |= 00000010
dl 2C10004C,FFFFFEFF ; [$bc10004C] &= FFFFFEFF
dl F0000000 ; end
;
;------------------------------------------------------------------------------
;code end
;------------------------------------------------------------------------------


On PSP iplloader versions 0.7 and later, the build number/date/version is copied from 0xBFC00FFC to ctc0  $17
<pre>
lw      $t0, 0xBFC00FFC
ctc0    $t0, $17
</pre>
</pre>
= See also =
* [[Initial Program Loader]]
* [https://web.archive.org/web/20090826053327/http://silverspring.lan.st/NPSPTD_01.txt iplloader and IPL descriptions by SilverSpring]

Latest revision as of 21:35, 17 June 2024

iplloader (also known as PRE-IPL or BootROM) — is the first code to run on PSP main CPU.

Its role is to load and decrypt encrypted Initial Program Loader from the NAND or Memory Stick. iplloader contains the routines to boot into service mode.

Location[edit | edit source]

On retail PSP units, the iplloader is read from the Tachyon's Allegrex MIPS R4000 based SOC boot ROM. On DTP-T1000, the iplloader is loaded externally. See also #Behaviour.

The PSP main CPU is a custom-made Sony CPU with a MIPS32 core and has an embedded mask rom device (like most embedded systems do) which is exactly 4KB in size. This device holds the iplloader. This iplloader mask rom device is mapped to physical address 0x1FC00000 which is the address of reset exception vector on MIPS CPUs, and this is where the MIPS CPU starts executing from coldboot.

Structure[edit | edit source]

The BootROM is made up of two parts, the "bootstrap", and the "loader". It is located at physical address 0x1FC00000 and is accessed via non-cached kseg1. The ROM is accessed upon hardware reset, software reset, or NMI. When this occurs, the Status flags are set in the COP0 Status register with following bits set: Boot Exception Vector=1 and Error Level = 1. If its a hardware reset, Software Reset (SR) = 0, or if NMI or software reset it is 1. At this point the PC is set to 0xBFC00000 and it begins to execute instructions. Since this is currently in ROM mode, only registers can be modified.

Versions[edit | edit source]

A few examples of iplloader payload sizes and hashes are listed in the following table.

Target SDK Version / Tachyon Revision Date (DD-MM-YYYY) Payload Size Hash
PSP (Retail/Testing Tool) Tachyon 0x00140000-0x00300000 20-04-2004 (build date in ROM) 0xAF8 SHA-256: 48F4F11C383621C8569EC07273AE0AF6AD79681CF5B77263A69CF908EEFE4A53 (ROM)
PSP (Development Tool) 0.4.0 23-07-2004 (or older/last modified date for kbooti.bin) n/a SHA-256: 18B5BF7AEFE956D99B397AAAAC94DC965ADFDBC2BE0532096BBC1F8F8C5B7C34 (Full Binary)
PSP (Development Tool) 0.6.0 08-09-2004 (or older/last modified date for kbooti.bin) n/a SHA-256: 5CDEDDEBE11807DDAEB17BAC03945A0B828E8057C9587652CA207E3BB959AC96 (Full Binary)
PSP (Development Tool) 0.7.0 18-09-2004 (build date in the payload) 0x894 SHA-256: 351ECD64C945489999D477ECAFBFBB8FE769C2484636D2F7323557F7EEFD54A2 (payload only)
SHA-256: 388FA1DB87973A2A37D576AAAB785D840CA4D883AB5111781DA2D0AF59CFE667 (Full rebuilt binary)
PSP (Development Tool) 0.9.0 15-10-2004 (build date in the payload) 0x894 SHA-256: 4F794E4FF32D5267AEAEDBA362D005EF0B7E93E29CF7C8209E0D9DBB0144F4DB (payload only)
SHA-256: E415198C16E29D96C9232FF78272EE639D0630A56E370ED18A33D358FEF7CA95 (Full rebuilt binary)
PSP (Retail) Tachyon 0x00400000-0x00500000 04-01-2005 (build date in ROM) 0xB30 SHA-256: 41B2578F84BDE33E09356F0170FF99E2417EA7B1D02BD9163A41AE61FE74C3A5 (ROM)
PSP (Development Tool) 2.6.0 22-10-2005 (build date in the payload) 0xBF4 SHA-256: 8821D96F5FB35C55DF649A97F5703F8A705362C2F54665B5EE4221E686B5578A (payload only)
SHA-256: 0A83CB36F1FE7C2A9A53BD46E6FFD915D4D1BB97EED3D1EF336960DB752C3446 (Full rebuilt binary)
PSP (Development Tool) 2.7.1 14-02-2006 (build date in the payload) 0xBF4 SHA-256: F9160C03EC6174F54F1C1EB645CFBBDB65B3DA47DA1A5478BE30E5EB2B0852B4 (payload only)
SHA-256: 7DDFF7093906C10BA11D7402E9939763173F1ADEA59A38B4006484FD18EA21EA (Full rebuilt binary)
PSP (Development Tool) 3.5.0 12-02-2007 (build date in the payload) 0xC74 SHA-256: AAF6F3CF0D7E028F43BE6FB788018F7A6F49B140A2591937B5C3A8373D2186A5 (payload including xor key)
SHA-256: 5066E257EC43DA37788535C331E5A01955F5F498B103C340903409649BCB5046 (full rebuilt binary)
PSP (Retail) Tachyon 0x00600000-0x00900000 10-09-2007 (build date in ROM) 0xCE8 SHA-256: E511D3DC78A209610F5B3EFEA2BC64BF86B9DF14A9C279C4499FECBFD70E6BF9 (ROM)
PS Vita 0.920.000 (inside PS Vita's Compatibility security module) 06-22-2010 (last modified date for compat_sm.self) 0x60 SHA-256: 98C8336C136DF901FC4EA38EB371AAF6F5402AC06574F107D3E2029BFC85CCAD (full binary)
PS Vita 0.930.010-0.995.000 (inside PS Vita's Compatibility security module) 08-17-2010 (last modified date for compat_sm.self) 0x2C0 SHA-256: 6D75EC720739C53228B1CA1AFF6CE073AE542BBB38FCC9B8710EC5EB3889B942 (full binary)
PS Vita 0.996.070-0.996.090 (inside PS Vita's Compatibility security module) 07-22-2011 (last modified date for compat_sm.self) 0xD34 SHA-256: E09B36DE655A441D2C94D39EF7BBC505EAB1722E9380EBE73E9E6A7DC88D9731 (full binary)
PS Vita 1.000.041-1.06 (inside PS Vita's Compatibility security module) 08-30-2011 (last modified date for compat_sm.self) 0xDB4 SHA-256: C2AE6939BC4B06CB4A81415E27EB1E7129C561B9C16C3AAFF6FDEBBAB48EBD09 (Full Binary)
PS Vita 1.50-1.81 (inside PS Vita Compatibility Security Module) 12-14-2011 (last modified date for compat_sm.self) 0xE34 SHA-256: 522851781DD82F89D69EBFE0F25C3E7CFE5899A53E850F2F979AD1B0E53376F9 (full binary)
PS Vita 2.00-2.05 (inside PS Vita's Compatibility security module) 11-16-2012 (last modified date for compat_sm.self) 0xE34 SHA-256: A9A097ED8925B83A210202AA4C943011C05FE48028BA6E05E85E1494143B0100 (full binary)
PS Vita 2.06-2.12 (inside PS Vita's Compatibility security module) 02-22-2013 (last modified date for compat_sm.self) 0xE34 SHA-256: 25E22C1D988609AA948F103E1312297F9533CA689B3C1BDC2CECBBC43997D566 (Full Binary)
PS Vita 2.50-3.01 (inside PS Vita Compatibility Security Module) 06-27-2013 (last modified date for compat_sm.self) 0xE34 SHA-256: 187DD28ADAD4167F3849392D570CA5A56DEEC608156D0EC6F2453958B1DB9672 (Full Binary)
PS Vita 3.10-3.20 (inside PS Vita's Compatibility security module) 12-05-2013 (last modified date for compat_sm.self) 0xE34 SHA-256: EF7D498295E416CCBD79FED78656E683DA1DCBC7B88C521DF0A1E00F5EC450FE (Full Binary)
PS Vita 3.30-3.35 (inside PS Vita's Compatibility security module) 09-25-2014 (last modified date for compat_sm.self) 0xE34 SHA-256: 7DF591C05BF66292B6868CE4331A1DC11B7A0E421D082971E9CA65C7236B6843 (Full Binary)
PS Vita 3.36-3.50 (inside PS Vita's Compatibility security module) 01-09-2015 (last modified date for compat_sm.self) 0xE34 SHA-256: 7E66976C311F5D3797A30B09DC608A0FA2E67EAA060097423CFD2E5FC89A57D9 (full binary)
PS Vita 3.51-3.55 (inside PS Vita's Compatibility security module) 05-12-2015 (last modified date for compat_sm.self) 0xE34 SHA-256: 6E869E08CCE41E0AA0D386DE8936F81F66CABB20B964C3EF2159548852F39F30 (full binary)
PS Vita 3.57-3.63 (inside PS Vita's Compatibility security module) 11-25-2015 (last modified date for compat_sm.self) 0xE34 SHA-256: 047366634210449C62FD813B3BFDA6267A1FA8683BA17901A214FC32E473A35F (full binary)
PS Vita 3.65-3.74 (inside PS Vita's Compatibility security module) 03-17-2017 (last modified date for compat_sm.self) 0xE34 SHA-256: B5EF4FB2C84D629B2BDC9A70A4B8E5A7EC31CD9EA330E309361C80A9A96B65C5 (full binary)

Behaviour[edit | edit source]

iplloader Boot Sequence[edit | edit source]

Part 1 (iplloader loader)[edit | edit source]

Because the BootROM is stored in non-volatile read-only memory, it cannot use any variable, so the bootstrap of the BootROM copies the loader BootROM (the payload) to the CPU's scratchpad RAM. It is the only RAM available at this time, along with another 4KB block of RAM and the 2MB EDRAM — normal DDR SDRAM has not been initialised yet.

The first thing checked is the 0xBC100000 value, if non-zero, it typically means a NMI exception and it either jumps to the exception vector in COP0 Control Register $9, or if that is not set, to the exception vector in COP0 Status Register $25. On a normal reset (hardware or software), the loader code of the ROM is copied to scratch pad at physical address 0x10000 in cached mode (0x80010000). At this point, it jumps to the real BootROM loader at 0x80010000. A stack is created at 0x80013FF0 as the top of the stack. This allows to the loader to use more typical local variables and memory in addition to registers.

Part 2 (iplloader payload)[edit | edit source]

The CPU is now executes the iplloader payload from the scratchpad RAM. The iplloader payload initializes the NAND hardware and reads the IPL NAND-block-table which is a table with the physical block numbers of the encrypted IPL's location on the NAND. That table is located at the 4th physical block of the NAND i.e. at offset 0x10000, and is repeated for the next 7 blocks. This is made so that if a bad block is met, the table can still be read. However, if all 8 blocks become bad blocks, it is a non-recoverable brick because the iplloader can no longer locate the IPL. The only solution to this problem is to either boot from Memory Stick instead, or use a custom IPL to patch the iplloader to remap the table. Both solutions require a Pandora battery.

If the SysCon has set 0xBE240004's 0x10 bit, the Memory Stick is used instead of the NAND for IPL loading.

The entire raw IPL is stored on the NAND encrypted. The iplloader payload uses a 4KB RAM as a temporary location to load and decrypt each encrypted IPL block. This RAM is mapped to physical address 0x1FD00000, but is later remapped to 0x1FC00000 to be used as the ME CPU reset exception vector. Because this RAM is only 4KB in size, the encrypted IPL is organised as 4KB blocks on the NAND. As the iplloader decrypts each of the 4KB IPL blocks, it loads the decrypted blocks to the IPL entry address 0x040F0000. This address is located in the 2MB EDRAM which is normally used as VRAM. Normal DDR RAM has not been initialised yet. When the iplloader has finished decrypting and loading all the encrypted IPL blocks, it jumps to the IPL entry address.

Memory mapping[edit | edit source]

The PSP iplloader is mapped to 0xBFC00000, which is the MIPS reset vector, i.e CPU initial program counter.

0.7.0 iplloader and onward are composed of two parts:

  • a loader from 0xBFC00000 to 0xBFC0027F
  • a payload from 0xBFC00280 and ending at the size specified at 0xBFC000034 (2 bytes with little endian order).

The PSP iplloader distributed in PS Vita System Software version 0.996 and onward has its payload starting at 0xBFC00180, rather than at 0xBFC00280 on previous PS Vita software and PSP hardware versions.

DevKit behaviour[edit | edit source]

bloadp[edit | edit source]

bloadp is used to send kbooti binaries (encrypted iplloader data from 0x0 to 0x1000 and IPL blocks at 0x1000) to the DTP-T1000.

bloadp (through tachsm) initializes the 0x1D600000 memory (from /dev/mem or 0x00000000 from /dev/tachsm0 ; /dev/tachsm1 is mapped from 0x1D400000 to 0x1D5FFFFF) on the Communication Processor side (0x200000 in size, 0x100000 on the tachsm0 device) which is the DTP-T1000 PSP Shared memory, on the PSP/Tachyon side it is mapped to 0xBFE00000, it then writes the kbooti binary at 0x1D600000, however on SDK 0.5.0 and below dstdb (which bloadp was originally a part of), it was possible to specify the address to which kbooti would be loaded (default address to be specified was 0xbfc00000, the allowed range was 0xbfc00000-0xbfc10000), this in effect would write kbooti to 0x1D600000/0xBFE00000 + the address specific in the range -0xbfc00000, so for instance if 0xbfc02000 was specified, this would write kbooti to 0xBFE02000, keep in mind that the actual 0xBFC00000 memory mirrors itself every 0x1000 segments.

If bloadp is never invoked, 0x1D700000 to 0x1D7FFFFF is uninitialized memory. It is cleared out as soon as bloadp is invoked.

Prototype 0.4.0-0.6.0 (23-07-2004 or older)[edit | edit source]

The prototype iplloader reads the IPL blocks in place from 0xBFE01000 and decrypts them to 0x88400000 before jumping there. Prototype IPL blocks do not contain metadata. Instead iplloader uses 0x88400000 as a hardcoded entry point (see code samples below). In fact the IPL payload, nested inside current IPL revisions and loaded by main.bin are using the prototype format, so the prototype iplloader loads the IPL payload directly.

It is also worthy of note that the prototype iplloaders for DEM-1000 (for 0.4.0 and 0.6.0 firmwares) do not make use of physical address 0xA0010000 as their payload location. The iplloader payload is instead executed in place at 0xBFC00000. As such the iplloader data is not wiped and can be dumped in its entirety using a custom IPL. Note that the IPL format is different than later revision IPLs: prototype IPLs are loaded in a single raw Kirk commmand 1 block.

// 0.4 iplloader
    }
    // 0xbfc00138
    function_bfc00178(100);
    uint32_t v9 = function_bfc00550(0x8400000, 0x1fe01000); // 0xbfc0014c
    // branch -> 0xbfc00154
    while (v9 < 0) {
        // 0xbfc00154
        // continue -> 0xbfc00154
    }
    // 0xbfc00158
    return unknown_88400000(-0x78000000, v8);
}
// 0.6 iplloader
    // 0xbfc00168
    function_bfc001b0(100);
    uint32_t v13 = function_bfc006b0(0x8400000, 0x1fe01000); // 0xbfc0017c
    // branch -> 0xbfc00184
    while (v13 < 0) {
        // 0xbfc00184
        // continue -> 0xbfc00184
    }
    // 0xbfc00188
    return unknown_88400000(-0x78000000, v9, v10, v1);
}

0.7.0-2.50 (18-09-2004)[edit | edit source]

The 0.7.0+ iplloader copies its payload from 0xBFC000280 to 0x80010000 i.e. physical address 0xA0010000, and jumps there. Because on DTP-T1000 0xBFC00000 is writable during the iplloader execution, and because 0xBFD00000 is an invalid range on DTP-T1000, the payload uses the 0xBFC00000 memory, which originally contains the whole iplloader as work RAM. However, it does not wipe itself so the payload is dumpable from 0xA0010000, assuming that one has code execution at IPL time.

IPL blocks are then loaded from 0xBFE01000 by the iplloader and copied to 0xBFC00000 where they are decrypted in place and copied to the location of load address specified in the metadata.

2.60+ (22-10-2005)[edit | edit source]

Because a hash of the data stored between 0xBFC00040 and 0xBFC002C0 is used in an additional step by 2.60+ IPLs to decrypt main.bin, from 2.60 and onward the iplloader payload overwrites 0xBFC00000 with an identical copy of first 0x2C0 bytes of the original PSP-1000 (01g) iplloader ROM data. This data is stored at 0xBFC00BB0 in the 2.60 and 2.71 kbooti, 0x80010930 in the payload. The iplloader payload memsets 0x1000 bytes at 0xBFC00000 to 0 and writes the chunk there, before jumping to the IPL entrypoint. If bootstrapping the 1.50 firmware using the 2.60/2.71 iplloader part, data from the retail ROM addresses 0xBFC00200 to 0xBFC002C0 are retrievable. Note that this would not have been enough to generate the hash required to decrypt 2.60+ main.bin in any case and dumping using a custom IPL would be required to retrieve enough of the data even on a DTP-T1000.

Please note that starting from the 3.5.0 kbooti, the chunk of data written to 0xbfc00000 (which is hashed to be used as a seed for IPL part 2 decryption) changes and no longer matches any iplloader code. It is no longer code but random data.

2.6.0 kbooti also appears to fix one of the iplloader flaws that allowed to load the Pandora time attacked block. It checks for the entrypoint not to be in the 0xb* range. However, it does not check for the data size of the block.

The code that copies 0x2C0 bytes from 0x80010930 to 0xBFC0xxxx verbatim from the 2.6.0 iplloader payload:

0x800100c4:   19 00 20 13   	beq 0x8001012c <entry_point+0x12c>, $zero, 0x8001012c <entry_point+0x12c>
0x800100c8:   00 00 00 00   	sll $zero, $zero, 0x0
0x800100cc:   8f 40 00 0c   	jal 0x8001023c <function_8001023c>
0x800100d0:   00 00 00 00   	sll $zero, $zero, 0x0
0x800100d4:   81 40 00 0c   	jal 0x80010204 <function_80010204>
0x800100d8:   00 00 00 00   	sll $zero, $zero, 0x0
0x800100dc:   c0 bf 04 3c   	lui $a0, 0xbfc0
0x800100e0:   21 28 00 00   	addu $a1, $zero, $zero
0x800100e4:   00 10 06 24   	addiu $a2, $zero, 0x1000
0x800100e8:   5a 41 00 0c   	jal 0x80010568 <function_80010568>
0x800100ec:   00 00 00 00   	sll $zero, $zero, 0x0
0x800100f0:   c0 bf 04 3c   	lui $a0, 0xbfc0 #0xbfc000000
0x800100f4:   01 80 05 3c   	lui $a1, 0x8001
0x800100f8:   30 09 a5 24   	addiu $a1, $a1, 0x930 #0x80010930
0x800100fc:   00 00 06 3c   	lui $a2, 0x0
0x80010100:   c0 02 c6 24   	addiu $a2, $a2, 0x2c0 #size of the data (0x2C0)
0x80010104:   50 41 00 0c   	jal 0x80010540 <function_80010540>

Note that neither the 0xbfc00000 (on DTP-T1000) nor 0xa0010000 memory locations survive reboots.

3.50 (12-02-2007)[edit | edit source]

The iplloader adds a step using a 0x40 bytes XOR key to decrypt the CMAC hash and data keys from the IPL block headers. As a result you cannot decrypt using Kirk command 1 the IPL blocks meant for the new iplloader.

The XOR key is overwritten early using a memset to 0 whenever the special Jig emulation mode is used (so the IPL block is xored by 0), this makes it impossible to obtain without glitching, knowing the kbooti CBC encryption key, knowing the xor key beforehand or using an IPL Loader exploit.

   if ( MEMORY[0xBFEFFFFC] < 0 )
   sub_800105F0(&unk_8001088C, 0, 64);

The seed for the IPL decryption (as it is used since 2.6.0) has changed to become a pseudo random 0x2C0 sized blob that is copied back from 0x800109B0 to 0xBFC00000 before jumping to the IPL entry point. SHA-256: 1E6FC02124901F6F5A3F1BB02F065064C63E423D759A131BA1086EA8FC2D90AA

Development Tool Jig Memory Stick Emulation Mode[edit | edit source]

Starting from kbooti 0.7.0, a special Jig test mode exists. It reads an IPL block at 0x2000 on the Memory Stick instead of address 0xBFE01000 if the following condition is met:

   if ( MEMORY[0xBFEFFFFC] < 0 ) use MS

Indeed, writing 0xFFFFFFFF at 0xBFEFFFFC enables the pseudo service mode and reads the IPL block from the Memory stick. This mode is used by Sony engineers to debug Jig Memory Sticks by writing a kbooti using bloadp and then using reset parameters to set the flag at 0xBFEFFFFC using the sbootp param/arg from the reset command of either dstdb or bsreset (dspreset) such as follows: /usr/local/sony/bin/bootdispi/dspreset 80000000 (FFFEFFFF for example sets all boot flags to 0xFF), to set the DTP-T1000 into Jig emulation mode.

Because flags are incremental, the only way to clear the Jig flag using official SDK tools is to run the bloadp command again as this clears the whole tachsm0 memory including the flags.

Kbooti remains loaded in memory until the main unit is turned off or bloadp has ran again, allowing then to power cycle through different memory stick.

In kbooti revision 3.5.0, this mode skips the XOR step on the Kirk header. It overwrites the XOR key with zeroes in the scratchpad. That allows one to use a regular IPL block to achieve code execution and dump the iplloader payload.

Retail behaviour[edit | edit source]

The full reverse engineered assembly code can be found in uOFW: https://github.com/uofw/uofw/tree/master/src/preipl.

The loader[edit | edit source]

The loader part is almost the same for all retail iplloader versions.

ctc0($4, $v0); // save $v0
if (*0xBC100000 != 0) { // NMI interrupts enabled
    if (cfc0($9) != 0) { // NMI handler
        jump to $9;
    } else {
        $v0 = cfc0($4); // restore $v0
        // disable the BEV bit (on R4400, it moves the non-reset and NMI interruption handling to 0x80000000 instead of 0xBFC00200, but it may have a different behavior here)
        mtc0(Status, mfc0(Status) & 0xFFBFFFFF);
        jump to mfc0(EBase);
    }
} else {
    ctc0($9, 0); // reset NMI handler
    sceKernelL1IcacheInvalidateAll();
    sceKernelL1DcacheInvalidateAll();
    memcpy(0xA0010000, preipl_payload, preipl_payload_size);
    $sp = 0x80013FF0;
    sync();
    jump to 0x80010000;
}
// image ends with a copyright string, and a build timestamp: 0x20040420, 0x20050104 and 0x20070910 for successive iplloader versions

First version[edit | edit source]

This version of the iplloader is used for Tachyon 0x00140000 to 0x00300000 (ie all versions of 01g except the few last).

Here is the pseudocode of the payload (not including implementation details, and excluding the information to interface with NAND & MemoryStick):

0xBC100058 |= 0x00800000; // Enable GPIO clock
0xBC100050 |= 0x0000608E; // Enable bus clock for AW (which is GE) (RegA, RegB, Edram), KIRK, NAND (EMCSM) and APB (for syscon?)
0xBC10004C &= ~0x408;     // Clear reset for AW and KIRK
0xBC100078 |= 2;          // IO enable NAND (EMCSM)
0xBE240000 &= ~0x10;      // Disable GPIO pin 4 output
0xBE240040 |= 0x10;       // Enable GPIO pin 4 input
sleep(1);                 // Wait a bit
0xBD500010 = 1;           // Initialize GE Edram
while (*0xBD500010 & 1 != 0) {} // Wait for the Edram to be initialized
0xBD500040 = 1;           // Finish initializing GE Edram

if (*0xBC100068 >> 16 != 0) { // Unknown bits
    *0xBC100078 |= 0x800; // Enable audio clock out??
} else {
    *0xBC10007C |= 0x10; // Enable GPIO pin 4 (used for jigkick)
}
sleep(10); // Wait a bit
int (*InitStorage)(void);
int (*ReadBlock)(int blkIndex, void *destination);
if (*0xBE240004 & 0x10 == 0) { // Check GPIO pin 4 to decide if we boot from NAND or MemoryStick (ie jigkick)
    InitStorage = InitNand;
    ReadBlock = ReadNandBlock;
} else {
    InitStorage = InitMemoryStick;
    ReadBlock = ReadMemoryStickBlock;
}

u32 iplBlockIdx = 0;
u32 lastBlockChecksum = 0;

while (1) {
    // Read one IPL block (size 0x1000) from the NAND or MemoryStick
    if (ReadBlock(iplBlockIdx, 0xBFD00000) < 0) {
        while (1); // infinite loop
    }

    // Decrypt the block in-place
    if (Kirk1Decrypt(0xBFD00000, 0xBFD00000) < 0) {
        while (1);
    }

    // Read the decrypted block header
    u32 dstAddress   = *(u32*)0xBFD00000; // destination address for the decrypted data
    u32 dataSize     = *(u32*)0xBFD00004; // size of the decrypted data (excluding the header)
    u32 entrypoint   = *(u32*)0xBFD00008; // the entrypoint address if this is the last block, 0 otherwise
    u32 prevChecksum = *(u32*)0xBFD0000C; // checksum of the previous block (computed below)

    if (lastBlockChecksum != prevChecksum) {
        while (1);
    }

    if (dstAddress != 0) {
        // Copy the rest of the block at the specified address
        // The checksum is just the XOR of the 32-bit words of the data
        lastBlockChecksum = _memcpy(dstAddress, 0xBFD00010, dataSize);
    }

    if (entrypoint != 0) {
        dcacheWritebackInvalidateAll();
        icacheWritebackInvalidateAll();
        jump to entrypoint;
    }

    iplBlockidx++;
}

Second version[edit | edit source]

This version of the iplloader is used by Tachyon versions 0x00400000 to 0x00500000. This corresponds to the last 01g motherboard versions, and all the 02g versions except the last.

It is very close to the original version, only some hardware initialization is modified, probably to improve stability/prevent crashes:

0xBC100058 |= 0x00800000; // (same) Enable GPIO clock
0xBC100050 |= 0x0000608E; // (same) Enable bus clock for AW (which is GE) (RegA, RegB, Edram), KIRK, NAND (EMCSM) and APB (for syscon?)
0xBC100050 &= ~0x8E;      // (new ) Disable bus clock for AW (RegA, RegB, Edram) and KIRK
0xBC10004C &= ~0x408;     // (same) Clear reset for AW and KIRK
0xBC100050 |= 0x0000008E; // (new ) Re-enable bus clock for AW (RegA, RegB, Edram) and KIRK
0xBC100078 |= 2;          // (same) IO enable NAND (EMCSM)
0xBE240000 &= ~0x10;      // (same) Disable GPIO pin 4 output
0xBE240040 |= 0x10;       // (same) Enable GPIO pin 4 input
for (i = 0; i < 0x800; i++) { (void)*0xBC100040; } // (new) Read 0xBC100040 2048 times (unsure why)
sleep(1);                 // (same) Wait a bit
0xBD500010 = 1;           // (same) Initialize GE Edram
while (*0xBD500010 & 1 != 0) {} // (same) Wait for the Edram to be initialized
0xBD500040 = 1;           // (same) Finish initializing GE Edram

There are also similar minor differences in the code to initialize MemoryStick hardware.

Third version[edit | edit source]

This version of the iplloader is present on the last 02g model and all of the 03g+ models.

It adds many security checks to avoid the creation of custom IPLs for these models — all of which have been broken since.

List of changes:

  • IPL load address now blacklists the CPU Scratchpad range (address & 0x1FFF0000 != 0x00010000)
  • The mode field of the KIRK headers must have their 16 MSB set to 0, or have their LSB set to 1 (ie mode & 0xFFFF0000 == 0, or mode & 0x00010000 == 1). The second case seems to mean the XOR keys have to be applied. The 16 MSB are erased before the KIRK command is actually ran
  • IPL blocks now must have a minimum size of 0x100 bytes (including the 16-byte header)
  • Blocks can be encrypted with KIRK commands 0~3 (in practice only command 1 is used)
  • ECDSA verification (as specified in the KIRK header) must be enforced on the last KIRK-encrypted block
  • The last 0x20 bytes of the block contain a SHA1 hash encrypted with KIRK command 7 and keyseed = 0x6C.

The block hash is calculated using:

sha1(block[8:] + block[:8])

Which means the first 8 bytes of the payload (containing the first half of the block header) are copied to the end before the block is hashed.

The last block hash remains in memory and is xored in each SHA1 for each block, the result is used for the hash compare in the Kirk command 0x10 ECDSA check.

  • The XOR of all the blocks' hashes (as computed in the previous step) is computed, and in the last block (entrypoint != 0), offset 0xFA0 contains a signature of this value, which is verified with KIRK command 0x11 using a custom public key stored inside the ROM (0xbc660611a70bd7f2d140a48215c096d11d2d4112, 0xf0e9379ac4e0d387c542d091349dd15169dd5a87).
  • The first 0x10 bytes of IPL blocks are, before decryption, xored using a XOR key, stored in the ROM and selected from a value stored in the NAND spare data (see NAND Flash Memory for more details), or set to 0 for jigkick boot. If this value is zero, the XOR key step is skipped (probably to allow compatibility of the Jig Memory Stick across all devices). Otherwise, bits 0..4 of that value are used as an index on the XOR key table, and bits 5..11 are used as an additional rotation index. In practice, this value is 1 for all targets except 05g, and 05g uses the value 2.

Vulnerabilities[edit | edit source]

Since the KIRK command 1 ECDSA private key & encryption key were known, the main issues for dumping the iplloader were the XOR keys, the hash check, and the additional ECDSA check on the XOR of hashes.

The XOR keys are easy to disable if you can enable jigkick, which had been done for 03g before Davee dumped the iplloader. (Note that now that we know that it can be disabled writing the appropriate data in the NAND spare data, it could also be circumvented that way.)

The hash check would've been easy to solve, but the fact the block was rotated before being hashed was unknown. Davee glitched this check in order to pass.

For the additional check, it could be skipped using a clever trick: if you set 0xBC10004C as the destination address of the IPL block, then the CPU resets and remaps 0xBFD00000 to 0xBFC00000 then runs back at 0xBFC00000. Since 0xBFD00000 is used as a temporary space for the decrypted IPL, it means you can easily achieve code execution. Davee used this method to dump the iplloader payload, which contains all the relevant information.

Now that the code is known, the two first issues are very easy to handle. For the last one, an easy trick is that the signature is on the XOR of the SHA1's, which means if you place your own block twice before a legitimate IPL, the two SHA1's will cancel each other and the signature check will pass.

PS Vita Compatibility mode behaviour[edit | edit source]

On PS Vita, PSP iplloader is sent by the Compatibility Security Module (os0:sm/compat_sm.self) to the non-secure ARM kernel, which writes it to 0xE8100000 (named CompatSharedSram and mapped to the 0xBFC00000 reset vector on the emulated PSP/Tachyon side).

Then, compat_sm.self sends a specific 0x40-bytes XOR key to be used by the PS Vita's iplloader as a 0x40 bytes XOR mask against the IPL header. The IPL is stored in the pcbc.skprx kernel module.

Kirk command 1 is then used on the result. Unlike actual PSP units, the IPL is decrypting in a single large block, rather than in multiple blocks.

The 0x40 bytes key gets updated depending on the firmware version in use.

A 0x40 bytes XOR mask is also part of the 3.50+ DTP-T1000 security (see PSP iplloader section).

Dumper[edit | edit source]

As of March 21st 2018, a dumper for DTP-T1000 iplloader has been made by mathieulh, it is available on GitHub.

Trivia[edit | edit source]

On PSP iplloader versions 0.7 and later, the build number/date/version is copied from 0xBFC00FFC to ctc0 $17

lw      $t0, 0xBFC00FFC
ctc0    $t0, $17

See also[edit | edit source]