SceKernelModulemgr

From Vita Development Wiki
Revision as of 06:30, 7 October 2016 by Yifan Lu (talk | contribs) (→‎SceModulemgrForKernel)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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.

Library

This library 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

Module

This library exports kernel and user modules.

Known NIDs

Version Name World Visibility NID
1.69 SceModulemgrForKernel Non-secure Kernel 0xC445FA63
1.69 SceModulemgrForDriver Non-secure Kernel 0xD4A60A52
1.69 SceModulemgr Non-secure User 0xEAED1616

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 library. 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 libraries 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
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;
}