Updater
Various components in user, kernel, and secure kernel work together to update the system. All the relevant libraries are documented in this one page because they work close together.
SceCuiSetUpper
Library
This is an user application that is found inside the update PUP. It has not been seen used outside of development units but is included in retail updates. It does not change often between firmware updater versions.
Known NIDs
Version | Name | World | Privilege | NID |
---|---|---|---|---|
2.12 | SceCuiSetUpper | Non-secure | User (vsh) | 0x8802DAE9 |
Module
This library does not export any modules.
ScePsp2Swu
Library
This is an user application that is found inside the update PUP.
Known NIDs
Version | Name | World | Privilege | NID |
---|---|---|---|---|
1.69 | ScePsp2Swu | Non-secure | User (vsh) | 0xF8D9C101 |
Module
This library does not export any modules.
SceSblUpdateMgr
Library
This library exists only in the non-secure kernel. The non-secure world SELF can be found in bootfs:update_mgr.skprx
.
Known NIDs
Version | Name | World | Privilege | NID |
---|---|---|---|---|
1.69 | SceSblUpdateMgr | Non-secure | Kernel | 0xBA91FE90 |
Module
This library only exports modules to both kernel and user.
Known NIDs
Version | Name | World | Visibility | NID |
---|---|---|---|---|
1.69 | SceSblUpdateMgrForKernel | Non-secure | Kernel | 0xC4466E48 |
1.69 | SceSblUpdateMgrForDriver | Non-secure | Kernel | 0xE04CD3D |
1.69 | SceSblSsUpdateMgr | Non-secure | User | 0x31406C49 |
Update Process
The Vita updater is composed of various parts.
Initiator
The first part is the initiator of the update. The responsibility of the initiator is to copy the update file (PSP2UPDAT.PUP
) to the ud0
partition and extract psp2swu.self
from the update file to ud0:PSP2UPDATE/psp2swu.self
. Then it signals the syscon to start in update mode, where psp2swu.self
is loaded instead of the usual shell.
The initiator that most people use is likely the update option in SceSettings. Another is the update option in SceSafeMode. Another option is SceCuiSetUpper
, which is extracted from the update PUP. SceCuiSetUpper
is likely used in development units as it can load the update data from host0
, which only exists on development units. This file, however, can be found in retail update packages. SceCuiSetUpper
also sets a flag to start updater in CUI mode.
ScePsp2Swu
Once the system restarts into update mode, the updater runs. Normally, it runs in GUI mode which is the green screen with the progress bar. If a flag is set by the initiator, the updater will run in CUI mode, which is a text interface that provides more verbose information about the update process.
The updater first makes calls to SceSblSsUpdateMgr
to verify the PUP header. For more information, check out PUP. Next, the updater will spawn a thread that calls into the kernel to decrypt, verify, and flash each package file according to the information found in the package header. For more information, check out PUP#Packages.
SceSblSsUpdateMgr
This is kernel module that actually does the work and performs the update. It verifies the PUP headers as well as the package headers and then flashes the decrypted images directly to the eMMC with block writes.
Update Package Decryption
The following code snippet will decrypt and update a directory of package files on 1.69 (the kernel functions called are specific to 1.69 NIDs). It is an attempted reproduction of the main code in ScePsp2Swu
. The requirements are that you patch the application to be running in Vsh context (or specifically patch the authentication id to allow access to the kernel update functions). You also need an ability to read and write to the kernel from within your application. Finally, the updater will always attempt to flash the decrypted contents. If the flash failed because of model checks or whatever, it will return an error but the data will still be successfully decrypted and outputted. If it succeeds, note that you have now updated your Vita. This code has not been tested to attempt a downgrade and therefore is provided only for reference.
int start_decryption1(int code1, unsigned char * buf, int buflen, int code2, int * phandle) { int argst[11]; int res; memset(argst,0,0x2c); argst[0]=0x2c; argst[1]=code1; argst[2]=(int)buf; argst[3]=buflen; argst[4]=code2; sceClibPrintf("Calling type 1 decryption with code1 = 0x%x buf = 0x%x buflen = 0x%x code2 = 0x%x ", code1, (int) buf, buflen, code2); res=callKernelFunction(SceSblSsUpdateMgr_0x6E8DDAC4,code1,argst,phandle,0 ); return res; } int start_decryption2(int code1, unsigned char * buf, int buflen, int code2, int * phandle) { int argst[11]; int res; memset(argst,0,0x2c); argst[0]=0x2c; argst[1]=code1; argst[2]=(int)buf; argst[3]=buflen; argst[4]=code2; sceClibPrintf("Calling type 2 decryption with code1 = 0x%x buf = 0x%x buflen = 0x%x code2 = 0x%x ", code1, (int) buf, buflen, code2); res=callKernelFunction(SceSblSsUpdateMgr_0x1A39F6EE,code1,argst,phandle,0 ); return res; } int start_decryption3(int code1, unsigned char * buf, int buflen, int code2, int * phandle) { int argst[11]; int res; memset(argst,0,0x2c); argst[0]=0x2c; argst[1]=code1; argst[2]=(int)buf; argst[3]=buflen; argst[4]=code2; sceClibPrintf("Calling type 3 decryption with code1 = 0x%x buf = 0x%x buflen = 0x%x code2 = 0x%x ", code1, (int) buf, buflen, code2); res=callKernelFunction(SceSblSsUpdateMgr_0xC1792A1C,code1,argst,phandle,0 ); return res; } int check_decryption_status(int code,int handle,int * out1, int * out2, int * out3, int * out4) { int argst[11]; int res; memset(argst,0,0x2c); argst[0]=0x2c; argst[7]=(int)out1; argst[8]=(int)out2; argst[9]=(int)out3; argst[10]=(int)out4; sceClibPrintf("Calling status with code = 0x%x handle = 0x%x ", code, handle); res=callKernelFunction(SceSblSsUpdateMgr_0xF403143E,code,handle,argst,0); return res; } int get_final_size(unsigned char * buf) { int * poffs; int * psize; poffs = (int *) (buf+0x10); psize = (int *) (buf+(*poffs)+0x20); return *psize; } int get_type(unsigned char * buf) { int * poffs; int * psize; if ( *(int *)buf == 0x00454353 ) { poffs = (int *) (buf+0x10); psize = (int *) (buf+(*poffs)+4); return *psize; } else { return -1; } } unsigned char * get_data_offset(unsigned char * buf) { int * poffs; poffs = (int *) (buf+0x10); return (buf+(*poffs)+0x80); } int complete_decryption(int code, int handle, unsigned char * buf, int maxlen) { int argst[11]; int res; unsigned char * payload; int size; memset(argst,0,0x2c); argst[0]=0x2c; argst[1]=code; argst[5]=(int)buf; argst[6]=maxlen; sceClibPrintf("Calling complete decryption with code = 0x%x handle = 0x%x buf = 0x%x maxlen = 0x%x ", code, handle, (int) buf, maxlen); res=callKernelFunction(SceSblSsUpdateMgr_0x4897AD56,code,handle,argst,0); return res; } int do_decrypt_file (const char *inpath, const char *outpath, const char *errpath, unsigned int size) { int fd; int res; int memid; int read; int maxlen=0x810000; int argst[0x2c/4]; int id; int type; int code; unsigned char * src, *outbuf; unsigned int handle, p1,p2,p3,p4; res=callKernelFunction(SceSblSsUpdateMgr_0x4C06F41C,size,&src,0,0); sceClibPrintf("Allocation returned 0x%x addr 0x%x ", res, (int)src); if(res) { sceClibPrintf("Cannot allocate memory. (size 0x%x) fail. ", size); return 0; } //sceClibPrintf("Loading Firmware pkg file from host0:"); fd= sceIoOpen(inpath,1,0); read = 0; while ((read = sceIoRead(fd,src,size-read)) > 0); sceIoClose(fd); code = get_type(src); switch (code) { case -1: sceClibPrintf("Not an encrypted file. "); goto ERROR; case 3: case 4: case 0x1B: type = 3; res=start_decryption3(code,src,size,9,&handle); break; case 0: case 2: case 5: case 6: case 7: case 0xE: case 0x1A: sceClibPrintf("Warning, code %x is unsupported! ", code); default: type = 2; res=start_decryption2(code,src,size,9,&handle); break; } if(res) { sceClibPrintf("start_decryption failed. (0x%x) ", res); goto ERROR; } for(;;) { res=check_decryption_status(type,handle,&p1,&p2,&p3,&p4); if(res) { sceClibPrintf("check_decryption_status failed. (0x%x) ", res); goto ERROR; } if(p3 == 5) { break; } else { sceKernelDelayThread(0x7A120); } } sceClibPrintf("p1= 0x%x p2 = 0x%x p3 = 0x%x p4 = 0x%x ", p1,p2,p3,p4); if(p2 == 0) { sceClibPrintf("Starting to write %s ", outpath); fd= sceIoOpen(outpath,0x603,0x186); read = get_final_size(src); while ((read -= sceIoWrite(fd,get_data_offset(src),read)) > 0); sceIoClose(fd); } else { sceClibPrintf("Error decrypting. Writing results to %s ", errpath); fd= sceIoOpen(errpath,0x603,0x186); read = get_final_size(src); while ((read -= sceIoWrite(fd,get_data_offset(src),read)) > 0); sceIoClose(fd); goto ERROR; } res=complete_decryption(type,handle,src,maxlen); if(res) { sceClibPrintf("complete_decryption failed. (0x%x) ", res); goto ERROR; } res=callKernelFunction(SceSblSsUpdateMgr_0xBD677F5A,src,0,0,0); return 1; ERROR: res=callKernelFunction(SceSblSsUpdateMgr_0xBD677F5A,src,0,0,0); return 0; } void do_decrypt_dir (const char *path) { int fd; SceIoDirent dir; char input[256]; char output[256]; char errput[256]; if ((fd = sceIoDopen(path)) < 0) { sceClibPrintf("Error opening pkg dir. "); return; } while (sceIoDread(fd, &dir) > 0) { sprintf(input, "%s/%s", path, dir.d_name); sprintf(output, "%s/%s.dec", path, dir.d_name); sprintf(errput, "%s/%s.err", path, dir.d_name); sceClibPrintf("Decrypting %s (size 0x%x) ", input, (unsigned int)dir.d_stat.st_size); if (do_decrypt_file(input, output, errput, (unsigned int)dir.d_stat.st_size)) sceClibPrintf("Decrypted to %s ", output); else sceClibPrintf("Failed to decrypt %s ", dir.d_name); } sceIoDclose(fd); }
Bootloader
There's evidence that the bootloaders are re-encrypted with probably per-console keys during the update process. second_loader.enp
and second_loader.enc
are transformed into second_loader.enp_
and second_loader.enp
respectively by F00D before flashing (and the same thing is done to secure_kernel
).