SceKernelModulemgr: Difference between revisions

From Vita Development Wiki
Jump to navigation Jump to search
Line 194: Line 194:
| 0x0000 || 0x340 || unknown
| 0x0000 || 0x340 || unknown
|-
|-
| 0x0340 || ? || self_decrypter_ctx
| 0x0340 || at least 0x4C || self_decrypter_ctx data
|-
|-
| ? || ? || unknown
| ? || ? || unknown

Revision as of 01:49, 28 September 2017

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

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

Types


//these types are defined in elfutils
typedef Elf32_Ehdr Elf32_Ehdr;
typedef Elf32_Phdr Elf32_Phdr;

typedef struct SCE_header
{
	uint32_t magic;                 /* 53434500 = SCE\0 */
	uint32_t version;               /* header version 3*/
	uint16_t sdk_type;              /* */
	uint16_t header_type;           /* 1 self, 2 unknown, 3 pkg */
	uint32_t metadata_offset;       /* metadata offset */
	uint64_t header_len;            /* self header length */
	uint64_t elf_filesize;          /* ELF file length */
	uint64_t self_filesize;         /* SELF file length */
	uint64_t unknown;               /* UNKNOWN */
	uint64_t self_offset;           /* SELF offset */
	uint64_t appinfo_offset;        /* app info offset */
	uint64_t elf_offset;            /* ELF #1 offset */
	uint64_t phdr_offset;           /* program header offset */
	uint64_t shdr_offset;           /* section header offset */
	uint64_t section_info_offset;   /* section info offset */
	uint64_t sceversion_offset;     /* version offset */
	uint64_t controlinfo_offset;    /* control info offset */
	uint64_t controlinfo_size;      /* control info size */
	uint64_t padding;
} SCE_header;

typedef struct SCE_appinfo
{
   uint64_t authid;                /* auth id */
   uint32_t vendor_id;             /* vendor id */
   uint32_t self_type;             /* app type */
   uint64_t version;               /* app version */
   uint64_t padding;               /* UNKNOWN */
} SCE_appinfo;

typedef struct segment_info
{
   uint64_t offset;
   uint64_t length;
   uint64_t compression; // 1 = uncompressed, 2 = compressed
   uint64_t encryption; // 1 = encrypted, 2 = plain
} segment_info;

typedef struct process_auth_id_ctx //size is 0x90
{
   uint32_t unk_8;
   uint32_t unk_C;
   
   uint32_t unk_10[20];
   
   uint32_t unk_60;
   uint32_t unk_64;
   char klicensee[0x10]; // offset 0x68
   
   uint32_t unk_78;
   uint32_t unk_7C;
   
   uint32_t unk_80;
   uint32_t unk_84;
   uint32_t unk_88;
   uint32_t unk_8C;
   
   uint32_t unk_90;
   uint32_t unk_94;
}process_auth_id_ctx;

typedef struct header_ctx_response //size is 0x90
{  
   char data[0x90]; // offset 0x98
}header_ctx_response;

typedef struct header_ctx // size is 0x130. probably SceSblSmCommContext130
{
   uint32_t unk_0;
   uint32_t self_type; //used - user = 1 / kernel = 0
   
   process_auth_id_ctx auth_ctx; //size is 0x90 - can be obtained with ksceKernelGetProcessAuthid
   
   header_ctx_response resp; //size is 0x90
   
   uint32_t unk_128; // used - SceSblACMgrForKernel_d442962e related
   uint32_t unk_12C;
   
}header_ctx;

typedef struct self_data_buffer
{
   SCE_header sce_header;
   SCE_appinfo sce_appinfo;
   Elf32_Ehdr elf_hdr;
   
   //... data goes
   
}self_data_buffer;

typedef struct self_data_ctx //size is 0x30
{
   self_data_buffer* self_header; //aligned buffer - based on (buffer_unaligned). 
                                  //points at SCE_header followed by SCE_appinfo
                                  //size is 0x1000
   int length;
   Elf32_Ehdr* elf_ptr; //pointer constructed with elf_offset
   Elf32_Phdr* phdr_ptr; //pointer constructed with phdr_offset

   uint8_t unk_10; // = 2
   uint8_t unk_11;
   uint8_t unk_12;
   uint8_t unk_13;
   
   segment_info* section_info_ptr; //pointer constructed with section_info_offset
   void* buffer_unaligned; //self header data - size 0x103F - raw data read from file
   int ctx; //F00D ctx (1/0) - obtained with sceSblAuthMgrStartF00DCommunication

   header_ctx* buffer;
   SceUID fd; // file descriptor of self file - obtained with sceIoOpenForDriver
   SceUID pid;
   uint32_t unk_2C;
}self_data_ctx;

typedef int(segment_decrypt_callback_t)(void* unk);

typedef struct self_decrypter_ctx //size is unknown
{
   uint32_t unk_0;
   uint32_t unk_4;
   uint32_t unk_8;
   uint32_t unk_C;
   
   uint32_t unk_10;
   uint32_t unk_14;
   uint32_t unk_18;
   uint32_t unk_1C;
   
   self_data_ctx* data_ctx;
   SceUID evid; //SceModuleMgrSelfDecryptComm event flag
   SceUID tid; //SceModuleMgrSelfDecrypter thread
   uint32_t unk_2C;
   
   uint32_t unk_30;
   uint32_t unk_34;
   uint16_t segment_number;
   uint16_t unk_3A;
   uint32_t unk_3C; // = 0x10
   
   uint32_t unk_40;
   uint32_t unk_44;
   segment_decrypt_callback_t* dec_callback;
   
   //... data goes on
   
}self_decrypter_ctx;

Data segment layout

Address Size Description
0x0000 0x340 unknown
0x0340 at least 0x4C self_decrypter_ctx data
? ? unknown

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;
}