SceKernelModulemgr is in charge of loading both user modules and kernel modules. SceSblAuthMgr facilitates the SELF decryption process and this library loads the ELF programs into memory along with linking with NIDs and relocation of ELF in position independent executables.
Module
This module exists only in non-secure world. The SELF can be found in os0:kd/modulemgr.skprx
.
Known NIDs
Version |
Name |
World |
Privilege |
NID
|
1.69 |
SceKernelModulemgr |
Non-secure |
Kernel |
0xAFDA75C2
|
Libraries
This module exports kernel and user libraries.
Known NIDs
Types
Data segment layout
Loading Sequence
When loading a module the sequence creates a SceModule structure to represent it.
typedef struct
{
const char *filename; // 0x64
Elf32_Ehdr ehdr; // 0x74
Elf32_Phdr phdr; // 0xA8
void *text_addr; // 0x108
SceUID text_uid; // 0x10C
u32 text_size; // 0x110
void *kernel_addr; // 0x114
SceUID kernel_uid; // 0x118
unsigned int parent_pid; // 0x3E4
void *unk_3E8; // working buffer? size = 0x130
} SceModule;
SELF Loading
The following source will decrypt a SELF located at path
. Set user
to 1 if decrypting a user module. Set fakecode
to 0 if you're decrypting the SELF at the right location (for example os0:kd/sysmem.skprx
). If you've copied the SELF elsewhere, you need to set the fakecode
to the right value for where the partition was. usecdram
is for modules that are too large and won't fit in contiguous regular memory.
int decrypt_self(const char *path, const char *outprefix, int fakecode, int usecdram, int user)
{
char outpath[256];
int ctx;
int ret;
int pid;
int fd = 0, wfd = 0;
char *somebuf = NULL;
char *hdr_buf = NULL, *hdr_buf_aligned;
char *data_buf = NULL, *data_buf_aligned;
int phdr;
unsigned int hdr_size;
// set up SBL decrypt context
ret = SceSblAuthMgrForKernel_0xA9CD2A09(&ctx);
printf("SceSblAuthMgrForKernel_0xA9CD2A09: 0x%08X, CTX: 0x%08X\n", ret, ctx);
if (ret < 0)
return 1;
// set up this weird buffer
somebuf = SceModulemgrForKernel_0xB4A1DE31_malloc(0x10005, 0x130);
printf("Weird buffer: 0x%08X\n", somebuf);
if (somebuf == NULL)
goto fail;
memset(somebuf, 0, 0x130);
if (ret < 0)
goto fail;
*(int *)(somebuf + 0x4) = user;
*(u64_t *)(somebuf + 0x8) = 0x2808000000000001LL;
*(u64_t *)(somebuf + 0x10) = 0xF000C000000080LL;
*(u64_t *)(somebuf + 0x18) = 0xFFFFFFFF00000000LL;
*(u64_t *)(somebuf + 0x30) = 0xC300003800980LL;
*(u64_t *)(somebuf + 0x38) = 0x8009800000LL;
*(u64_t *)(somebuf + 0x48) = 0xFFFFFFFF00000000LL;
if (fakecode)
{
*(int *)(somebuf + 0x128) = fakecode;
}
else
{
ret = SceIofilemgrForDriver_0x9C220246(0x10005, path, 1, somebuf + 0x128);
printf("SceIofilemgrForDriver_0x9C220246: 0x%08X\n", ret);
if (ret < 0)
goto fail;
}
// read header
fd = sceIoOpenForDriver(path, 1, 0);
printf("sceIoOpenForDriver: 0x%08X\n", fd);
if (fd < 0)
goto fail;
hdr_buf = SceModulemgrForKernel_0xB4A1DE31_malloc(0x10005, 0x1000+63);
hdr_buf_aligned = (char *)(((int)hdr_buf + 63) & 0xFFFFFFC0);
printf("Header buffer: 0x%08X, aligned: 0x%08X\n", hdr_buf, hdr_buf_aligned);
if (hdr_buf == NULL)
goto fail;
ret = sceIoReadForDriver(fd, hdr_buf_aligned, 0x1000);
printf("Header read: 0x%08X\n", ret);
hdr_size = *(unsigned int *)(hdr_buf_aligned + 0x10);
if (hdr_size > 0x1000)
{
printf("Header too large: 0x%08X\n", hdr_size);
goto fail;
}
ret = sceIoLseekForDriver(fd, 0LL, 0);
printf("Header rewind: 0x%08X\n", ret);
// set up SBL decryption for this SELF
ret = SceSblAuthMgrForKernel_0xF3411881(ctx, hdr_buf_aligned, hdr_size, somebuf);
printf("SceSblAuthMgrForKernel_0xF3411881: 0x%08X\n", ret);
if (ret < 0)
{
goto fail;
}
// set up read buffer
data_buf = SceModulemgrForKernel_0xB4A1DE31_malloc(0x10005, 0x10000+63);
data_buf_aligned = (char *)(((int)data_buf + 63) & 0xFFFFFFC0);
printf("Data buffer: 0x%08X, aligned: 0x%08X\n", data_buf, data_buf_aligned);
if (data_buf == NULL)
goto fail;
// get sections
int elf_offset = *(int*)(hdr_buf_aligned + 0x40);
int num_segs = *(short*)(hdr_buf_aligned + elf_offset + 0x2C);
printf("Number of segments to read: 0x%04X\n", num_segs);
int info_offset = *(int*)(hdr_buf_aligned + 0x58);
struct seg_info *segs = (struct seg_info *)(hdr_buf_aligned + info_offset);
int phdr_offset = *(int*)(hdr_buf_aligned + 0x48);
struct e_phdr *phdrs = (struct e_phdr *)(hdr_buf_aligned + phdr_offset);
// decrypt sections
int i;
int total, to_read, num_read, off;
int aligned_size;
int blkid = 0;
void *pgr_buf;
for (i = 0; i < num_segs; i++)
{
sprintf(outpath, "%s.seg%u", outprefix, i);
sceIoCloseForDriver(wfd);
wfd = sceIoOpenForDriver(outpath, 0x602, 6);
printf("sceIoOpenForDriver(%s): 0x%08X\n", outpath, wfd);
if (wfd < 0)
break;
if (blkid)
sceKernelFreeMemBlockForKernel(blkid);
aligned_size = (phdrs[i].p_filesz + 4095) & 0xFFFFF000;
if (usecdram)
blkid = sceKernelAllocMemBlockForKernel("self_decrypt_buffer", 0x40404006, 0x4000000, NULL);
else
blkid = sceKernelAllocMemBlockForKernel("self_decrypt_buffer", 0x1020D006, aligned_size, NULL);
printf("sceKernelAllocMemBlockForKernel: 0x%08X, size: 0x%08X\n", blkid, aligned_size);
ret = sceKernelGetMemBlockBaseForKernel(blkid, &pgr_buf);
printf("sceKernelGetMemBlockBaseForKernel: 0x%08X, base: 0x%08X\n", ret, pgr_buf);
if (ret < 0)
break;
// setup buffer for output
ret = SceSblAuthMgrForKernel_0x89CCDA2C(ctx, i, (u32_t)segs[i].length, pgr_buf, phdrs[i].p_filesz);
printf("SceSblAuthMgrForKernel_0x89CCDA2C: 0x%08X\n", ret);
if (ret < 0)
{
break;
}
ret = sceIoLseekForDriver(fd, segs[i].offset, 0);
printf("sceIoLseekForDriver(0x%08X): 0x%08X\n", (u32_t)segs[i].offset, ret);
if (ret < 0)
break;
total = (u32_t)segs[i].length;
to_read = total > 0x10000 ? 0x10000 : total;
off = 0;
while (total > 0 && (num_read = sceIoReadForDriver(fd, data_buf_aligned+off, to_read)) > 0)
{
off += num_read;
total -= num_read;
if (num_read < to_read)
{
to_read -= num_read;
continue;
}
ret = SceSblAuthMgrForKernel_0xBC422443(ctx, data_buf_aligned, off); // decrypt buffer
printf("SceSblAuthMgrForKernel_0xBC422443: 0x%08X\n", ret);
if (ret < 0)
printf("!!! ERROR !!!\n");
ret = SceSblAuthMgrForKernel_0x15248FB4(ctx, data_buf_aligned, off); // copy buffer to output
printf("SceSblAuthMgrForKernel_0x15248FB4: 0x%08X\n", ret);
if (ret < 0)
{
printf("!!! ERROR !!!\n");
}
off = 0;
to_read = total > 0x10000 ? 0x10000 : total;
}
// write buffer
off = 0;
while ((off += sceIoWriteForDriver(wfd, pgr_buf+off, phdrs[i].p_filesz-off)) < phdrs[i].p_filesz);
}
if (blkid)
sceKernelFreeMemBlockForKernel(blkid);
sceIoCloseForDriver(wfd);
fail:
SceSblAuthMgrForKernel_0x026ACBAD(ctx);
if (fd)
sceIoCloseForDriver(fd);
if (somebuf)
SceModulemgrForKernel_0xF4B2D8B8_free(somebuf);
if (hdr_buf)
SceModulemgrForKernel_0xF4B2D8B8_free(hdr_buf);
if (data_buf)
SceModulemgrForKernel_0xF4B2D8B8_free(data_buf);
return 1;
}
Partition Code
A code is passed in the buffer to decrypt the SELF based on where the SELF came from. This is likely a security feature to prevent SELFs that are designed to run from one one partition (for example os0
) from being copied and run from another partition (ux0
).
Partition
|
Code
|
sd0
|
1
|
os0
|
2
|
vs0
|
3
|
vd0
|
4
|
tm0
|
5
|
ur0
|
6
|
host0
|
7
|
boot0
|
10
|
ud0
|
11
|
ux0:app
|
23
|
ux0:patch
|
24
|
ux0:data
|
25
|
ux0:user
|
0
|
ux0
|
12
|
gro0:app
|
13
|
gro0:patch
|
14
|
sa0
|
15
|
mfa0
|
16
|
mfb0
|
17
|
lma0
|
18
|
lmb0
|
19
|
lmc0
|
20
|
lmd0
|
21
|
pd0
|
22
|
SceModulemgrForKernel
Decrypt SELF ELF Program
Version
|
NID
|
1.69
|
0x448810D5
|
int decrypt_program(const char *path, int e_phnum, void *buffer, uint32_t bufsize, int zero_unk, uint32_t *bytes_read);
This is an easy way of decrypting SELFs but you are limited to the kinds of SELFs you can load in the current context (for example, you can't load user libraries from kernel context). It is also susceptible to limitations of where the SELF can be loaded from. For example, you're not allowed to load SELFs found in os0
from ux0
.
sceKernelGetModuleListForKernel
Version |
NID
|
1.60 |
0x97CF7B4E
|
Load Default User Modules
Version |
NID
|
1.69 |
0x3AD26B43
|
This loads the default shared modules for a process (only the ones that are actually imported). This includes, for example, SceLibKernel
. Modules are loaded with flags 0x10000000
meaning that text pages can be shared. If dipsw 210 is set, then flag 0x1000
is set, meaning that if the existing page is found, do not share it but instead make a copy.
int load_default_suprx(int pid, void *unk_buf, int flags);
SceModulemgrForDriver
sceKernelGetModuleInfo
Version |
NID
|
1.69 |
0x36585DAF
|
sceKernelGetSystemSwVersion
Version |
NID
|
1.69 |
0x5182E212
|
sceKernelSetSystemSwVersion
Version |
NID
|
1.69 |
0x912AEB73
|
sceKernelLoadStartModuleForDriver
Version |
NID
|
1.69 |
0x189BFBBB
|
int sceKernelLoadStartModuleForDriver(const char *path, int argc, void *args, int flags);
sceKernelLoadModuleWithoutStartForDriver
Version |
NID
|
1.69 |
0x86D8D634
|
int opt = 4; // must be set
int sceKernelLoadModuleWithoutStartForDriver(const char *path, int flags, int *opt);
sceKernelStartModuleForDriver
Version |
NID
|
1.69 |
0x0675B682
|
// flags must be 0
// opt can be null
int sceKernelStartModuleForDriver(int modid, int argc, void *args, int flags, void *opt, int *res);
sceKernelStopUnloadModuleForDriver
Version |
NID
|
1.69 |
0x100DAEB9
|
// flags must be 0
// opt can be null
int sceKernelStopUnloadModuleForDriver(int modid, int argc, void *args, int flags, void *opt, int *res);
sceKernelUnloadModuleForDriver
Version |
NID
|
1.69 |
0x728E72A6
|
// flags must be 0
int sceKernelUnloadModuleForDriver(int modid, int flags);
SceModulemgr
__sceKernelStartModule
Version |
NID
|
1.69 |
0x1FD99C9F
|
sceKernelGetModuleList
Version |
NID
|
1.69 |
0x2EF2581F
|
sceKernelGetModuleInfo
Version |
NID
|
1.69 |
0x36585DAF
|
sceKernelGetAllowedSdkVersionOnSystem
Version |
NID
|
1.69 |
0x4397FC4E
|
sceKernelKttyWrite
Version |
NID
|
1.69 |
0x4D76CF9E
|
sceKernelGetSystemSwVersion
Version |
NID
|
1.69 |
0x5182E212
|
__sceKernelCloseModule
Version |
NID
|
1.69 |
0x5303C52F
|
sceKernelSetSystemSwVersion
Version |
NID
|
1.69 |
0x912AEB73
|
__sceKernelOpenModule
Version |
NID
|
1.69 |
0x9C2A9A49
|
sceKernelPutc
Version |
NID
|
1.69 |
0x9D2FE122
|
__sceKernelLoadModuleWithoutStart
Version |
NID
|
1.69 |
0xA4E6DA4D
|
__sceKernelStopModule
Version |
NID
|
1.69 |
0xBA49EA5C
|
__sceKernelUnloadModuleWithoutStop
Version |
NID
|
1.69 |
0xE439E26B
|
sceKernelGetLibraryInfoByNID
Version |
NID
|
1.69 |
0xEAEB1312
|
sceKernelGetModuleIdByAddr
Version |
NID
|
1.69 |
0xF5798C7C
|
Module decryption and signature checks
SELF_Loading.
The code below will patch signature checks and bypass module decryption and allow homebrew to run. The idea is to hook SceSblAuthMgr* calls that are imported to SceKernelModulemgr. The offsets are from 1.60, you will probably need to modify hook_resume_sbl_* defines (set them to addresses of functions) and INSTALL_HOOK second arguments (set to addresses of imports in SceKernelModulemgr). As a bonus there's also patch_npdrm functions that patches SceNpDrm to bypass some DRM checks and allow unsigned packages to be installed, which you also need to modify, see SceNpDrm#Package_integrity_checks.
#define G_OUR_EBOOT *(unsigned*)(0x01e60000 - 20)
#define G_BUF *(unsigned*)(0x01e60000 - 12)
#define G_WRITTEN *(unsigned*)(0x01e60000 - 16)
#define Func(addr) ((unsigned(*)())(addr))
#define hook_resume_sbl_F3411881 Func(0x4BC6C9)
#define hook_resume_sbl_89CCDA2C Func(0x4BC851)
#define hook_resume_sbl_BC422443 Func(0x4BC909)
#define hook_resume_sbl_15248FB4 Func(0x4BCA89)
// setup file decryption
unsigned hook_sbl_F3411881(unsigned a1, unsigned a2, unsigned a3, unsigned a4) {
unsigned res = hook_resume_sbl_F3411881(a1, a2, a3, a4);
if (res == 0x800f0624 || res == 0x800f0616 || res == 0x800f0024) {
G_OUR_EBOOT = 1;
// patch somebuf so our module actually runs
unsigned *somebuf = (unsigned*)a4;
somebuf[42] = 0x40;
return 0;
} else {
G_OUR_EBOOT = 0;
}
return res;
}
// setup output buffer
unsigned hook_sbl_89CCDA2C(unsigned a1, unsigned a2, unsigned a3, unsigned a4, unsigned a5) {
G_BUF = a4;
G_WRITTEN = 0;
if (G_OUR_EBOOT == 1) {
return 0;
}
return hook_resume_sbl_89CCDA2C(a1, a2, a3, a4, a5);
}
// decrypt
unsigned hook_sbl_BC422443(unsigned a1, unsigned a2, unsigned a3) {
if (G_OUR_EBOOT == 1) {
return 0;
}
return hook_resume_sbl_BC422443(a1, a2, a3);
}
// copy to output
unsigned hook_sbl_15248FB4(unsigned a1, unsigned a2, unsigned a3) {
if (G_OUR_EBOOT == 1) {
memcpy((void*)(G_BUF + G_WRITTEN), (void*)a2, a3);
G_WRITTEN += a3;
return 0;
}
return hook_resume_sbl_15248FB4(a1, a2, a3);
}
#define INSTALL_HOOK(func, addr) \
{ unsigned *target; \
target = (unsigned*)addr; \
*target++ = 0xE59FF000; /* ldr pc, [pc, #0] */ \
*target++; /* doesn't matter */ \
*target = (unsigned)func; \
}
void hook_install(void) {
INSTALL_HOOK(hook_sbl_BC422443, 0x5BAA0C);
INSTALL_HOOK(hook_sbl_15248FB4, 0x5BA9CC);
INSTALL_HOOK(hook_sbl_F3411881, 0x5BAA1C);
INSTALL_HOOK(hook_sbl_89CCDA2C, 0x5BA9DC);
}
unsigned get_module_base(const char *name) {
int * modlist[MOD_LIST_SIZE];
int modlist_records;
int res;
SceModInfo modinfo;
memset(modlist, 0, sizeof(modlist));
modlist_records = MOD_LIST_SIZE;
sceKernelGetModuleListForKernel(0x10005, 0x7FFFFFFF, 1, modlist, &modlist_records);
for(int j = 0; j < modlist_records; j++) {
memset(&modinfo, 0, sizeof(modinfo));
res=sceKernelGetModuleInfoForKernel(modlist[j], &modinfo);
if (strcmp(modinfo.name, name) == 0)
return (unsigned)modinfo.module_top;
}
return 0;
}
void patch_npdrm(unsigned base) {
unsigned *patch;
// check where check_func[0] is called
patch = (unsigned*)(base + 0x310);
*patch = 0x47702001;
// check where check_func[1] is called
patch = (unsigned*)(base + 0xaa4);
*patch = 0x47702001;
// always return 1 in install_allowed
patch = (unsigned*)(base + 0x2d64);
*patch = 0x47702001;
// patch error code 0x80870003
patch = (unsigned*)(base + 0x4856);
*patch = 0x2500;
// second same error code
patch = (unsigned*)(base + 0x35fe);
*patch = 0x2600;
}
// call this from a thread
int hook(void) {
fprintf("Hook start\n");
unsigned prev_dacr;
__asm__ volatile("mrc p15, 0, %0, c3, c0, 0" : "=r" (prev_dacr));
__asm__ volatile("mcr p15, 0, %0, c3, c0, 0" : : "r" (-1));
unsigned base_npdrm = get_module_base("SceNpDrm");
fprintf("SceNpDrm base: 0x%08x\n", base_npdrm);
patch_npdrm(base_npdrm);
hook_install();
__asm__ volatile("MCR p15, 0, %0, c7, c5, 0" : : "r" (0)); // flush icache
__asm__ volatile("mcr p15, 0, %0, c3, c0, 0" : : "r" (prev_dacr));
sceKernelDelayThread(4000000);
return 0;
}