What's new

Discussion Using XeKeysExecute To Run Custom Code

  • Thread starter TEIR1plus2
  • Start date
  • Views 3,324
T

TEIR1plus2

Getting There
Messages
506
Reaction score
225
I spent a day looking into how XeKeysExecute works and what it's purpose is other than blocking xbox live from modded consoles. This is the result

EDIT: I rewrote some of it to be more versatile and easier to use. Simply provide it with the payload to be executed and it will build the header and encrypt the data
UPDATE 12/12/2017: After going back and reversing the entirety of HvxKeysExecute, I rewrote this to be more inline to how the syscall handles it, and removed some unneeded checks that the HV takes care of. Also fixed a potential memory leak (although it wouldn't trigger unless you were executing hundreds of payloads per session).
Also: Added support for a variable entrypoint, that way if you wanted to store some data or subroutines before the actual entry for some reason, you can.
If anyone wants to see the reversed syscall, I put it on free60: http://free60.org/wiki/HvxKeysExecute


Code:
const WORD cMagic = 0x4D4D;
const WORD cVersion = 0x4099;
const DWORD cFlags = NULL;
const DWORD cHeaderSize = 0x120;
const BYTE cKey[0x10] = { 0x1b, 0x9d, 0xc0, 0x98, 0xd8, 0x09, 0x81, 0x50, 0xe5, 0x57, 0xc2, 0xc2, 0xd8, 0xfe, 0xf9, 0xf7 }; // https://www.random.org/bytes/
const BYTE cSig[0x100] = { 0 };
const DWORD cBufferSize = 0x3000;

// similar to a bootloader header, missing the presets field.
typedef struct _KeysExecute
{
    WORD Magic;            // 0 : 2
    WORD Version;        // 2 : 2
    DWORD Flags;        // 4 : 4
    DWORD EntryPoint;    // 8 : 4
    DWORD Size;            // 0xC : 4
    BYTE key[0x10];        // 0x10 : 0x10
    BYTE Sig[0x100];    // 0x20 : 0x100
    // Header: 0x120
}KeysExecutes, *PKeysExecute;

BYTE BLKey[0x10] = { <redacted> };

LPCSTR KeysExecutePayloadFile = "usb:\\XeKeys\\KeysExecutePayload.bin";
LPCSTR KeysExecuteRespFile = "usb:\\XeKeys\\KeysExecuteResp.bin";

// XeKeysExecute seems to be ms's official way of executing a privileged ppc block.
// Similar to expansions, except this will free the memory after being used instead of it being taken for the entirety of the session.
// Each(KeysExecute and Expansions) has their advantages/disadvantages.
// If your console freezes after calling HvxKeysExecute check the following:
//        - Allocate the code block in using something like XPhysicalAlloc or MmAllocatePhysicalMemory
//            The address must be within a certain range to be considered valid
//        - Check your PPC, I can't protect you from bad PPC code that causes a read/write exception
// Parameters
//        Payload: Payload buffer
//            - Must be 0x80 byte aligned
//            - Must be > 0x120 and < 0x10000
//            - Must be allocated with XPhysicalAlloc or MmAllocatePhysicalMemory
//        BufferSize: Return buffer size
//            - Must be > 0x120 and < 0x10000
//            - Must be 0x80 byte aligned
// Header Info
//        WORD Magic: Must be 0x4D4D
//        WORD Version: Doesn't really matter
//        DWORD Flags: Doesn't really matter, leave null
//        DWORD EntryPoint:
//            - Must be after >= 0x120 and < Size
//            - Must be 4 byte aligned
//        DWORD Size:
//            - Must be > 0x120 and < BufferSize
//        BYTE Key[0x10]: key used for Rc4 encryption/decryption. rc4Key = HmacSha(1BLKey, 0x10, header->Key, 0x10)
//        BYTE Sig[0x100]: Rsa signature, we patch the RSA check so this can be null without an issue
//        PBYTE CodeBlock: Payload to be executed

// 7s censors this word for some reason, but its needed here so its: _declspec(n_a_k_e_d) remove the underscores. pm me if there is an issue with this
static QWORD _declspec(n***d) HvxKeysExecute(PVOID pvPayload, DWORD cbPayload, QWORD Arg1, QWORD Arg2, QWORD Arg3, QWORD Arg4)
{
    __asm
    {
        li    r0, 0x40
        sc
        blr
    }
}

QWORD ExecutePayload(PBYTE pbPayload, DWORD cbPayload, DWORD EntryPoint, QWORD Arg1, QWORD Arg2, QWORD Arg3, QWORD Arg4)
{
    if (EntryPoint & 3 // entrypoint must be 4 byte aligned, as always
        || EntryPoint >= cbPayload // sanity check
        || cbPayload + cHeaderSize > cBufferSize) // sanity check
        return -1;

    BYTE* bPayload = (PBYTE)XPhysicalAlloc(cBufferSize, MAXULONG_PTR, 0x10000, PAGE_READWRITE);
    memset(bPayload, 0, cBufferSize);
    QWORD physPayload = 0x8000000000000000ULL + (DWORD)MmGetPhysicalAddress(bPayload);

    // we have memory allocated, we can't just return when something goes wrong.
    QWORD ret = 0;

    // check for an internal system
    // As long as the buffer does not extend over multiple 0x10000 byte pages, this will not trip
    QWORD SOCCheck = (((physPayload + cBufferSize) - 1) ^ physPayload) & 0xFFFF0000;
    if (SOCCheck == 0)
    {
        if ((physPayload & 0xFFFFFFFF) <= 0x1FFFFFFF) // address validation (HV hangs if it catches this)
        {
            // build the header
            PKeysExecute cHeader = (PKeysExecute)bPayload;
            cHeader->Magic = cMagic;
            cHeader->Version = cVersion;
            cHeader->Flags = cFlags;
            cHeader->Size = cHeaderSize + cbPayload;
            cHeader->EntryPoint = cHeaderSize + EntryPoint;
            memcpy(bPayload + cHeaderSize, pbPayload, cbPayload);
            memcpy(cHeader->key, cKey, 0x10);
            memcpy(cHeader->Sig, cSig, 0x100);

            // data needs to be sent encrypted with the key in the header! rc4Key = HmacSha(header+0x10, 0x10)
            // you could just patch the call to XeCryptRc4Ecb as well and send it unencrypted data. but im not doing that - address: 0x6148
            XECRYPT_RC4_STATE rc4;
            BYTE rc4Key[0x10];
            XeCryptHmacSha(Keys::BLKey, 0x10, cHeader->key, 0x10, 0, 0, 0, 0, rc4Key, 0x10);
            printf("Encrypting data...\n");
            XeCryptRc4Key(&rc4, rc4Key, 0x10);
            XeCryptRc4Ecb(&rc4, bPayload + 0x20, cHeader->Size - 0x20);
            printf("Done!\n");

            // patch the rsa check in HvxKeysExecute - allows a custom payload to be run.
            HvxPokeDWORD(0x617C, 0x38600001); // li r3, 1

            // attempt to execute payload by syscall - HvxKeysExecute = 0x40
            printf("Executing Payload...\n");
            if (Arg1)
                printf("Arg1: 0x%016llX\n", Arg1);
            if (Arg2)
                printf("Arg2: 0x%016llX\n", Arg2);
            if (Arg3)
                printf("Arg3: 0x%016llX\n", Arg3);
            if (Arg4)
                printf("Arg4: 0x%016llX\n", Arg4);
            ret = HvxKeysExecute(MmGetPhysicalAddress(bPayload), cBufferSize, Arg1, Arg2, Arg3, Arg4);

            // restore the rsa check
            HvxPokeDWORD(0x617C, 0x48005245); // bl XeCryptBnQwBeSigVerify
        }
        else
        {
            printf("Address out of range!\n");
            printf("physPayload: 0x%016llX\n", physPayload);
            ret = -1;
        }
    }
    else
    {
        printf("SOCCheck Failed\n");
        printf("[SOCCheck: 0x%016llX][cBufferSize: 0x%X]\n", SOCCheck, cBufferSize);
        ret = -1;
    }



    if (ret != 0)
    {
        if (ret == 0xC8000030)
            printf("[%08X] Parameter Fail!\n\tPayload must be 0x80 byte aligned!\n\tSize must be greater than 0x120 and less than 0x10000 AND must be 0x80 byte aligned!\n", ret);
        else if (ret == 0xC8000032)
            printf("[%08X] Magic or Address Fail!\n\tMagic must be 0x4D4D\n", ret);
        else if (ret == 0xC8000033)
            printf("[%08X] HV Magic Fail!\n\tHV magic must be 0x4E4E\n", ret);
        else if (ret == 0xC8000034)
            printf("[%08X] Header Size Fail!\n\tSize in header must be > 0x120 AND aligned to 0x10 AND < the buffer size!\n", ret);
        else if (ret == 0xC8000035)
            printf("[%08X] EntryPoint Fail!\n\tEntrypoint must be > 0x120 AND 4 byte aligned AND < code size\n", ret);
        else if (ret == 0xC8000036)
            printf("[%08X] Crypt/Signature Fail!\n\tPatch the call to XeCryptBnQwBeSigVerify!\n", ret);
        else
            printf("ret: %016llX\n", ret);
    }

    // dump return buffer (incase it was used)
    if (!CWriteFile("Hdd:\\KeysExecuteRetBuf.bin", bPayload, cBufferSize))
        printf("Return buffer not dumped!\n");

    // done, whether it failed or not, free the memory
    XPhysicalFree(bPayload);
    return ret;
}
 
Last edited:
T

TEIR1plus2

Getting There
Messages
506
Reaction score
225
updated it to be a little easier to use, IMO this is a lot easier to use than HvGetVersion or building and calling expansions.
 
T

TEIR1plus2

Getting There
Messages
506
Reaction score
225
I rewrote Dwack's DumpHV script for this, it works perfectly. And won't leave a payload in the HV. it does leave the rsa patch we did, the code below clears it though
Pastebin of the code: Call it with:
Code:
void DumpHv()
{
    DWORD Size = 0x7C;
    DWORD RetBufSize = 0x40000;
    PBYTE bpayload = (PBYTE)XPhysicalAlloc(Size, MAXULONG_PTR, NULL, PAGE_READWRITE);
    PBYTE bRetBuf = (PBYTE)XPhysicalAlloc(RetBufSize, MAXULONG_PTR, NULL, PAGE_READWRITE);
    memset(bpayload, 0, Size);
    memset(bRetBuf, 0, RetBufSize);
    if (!CReadFile("Hdd:\\KeysDumpHV.bin", bpayload, Size))
    {
        printf("Couldn't open payload file\n");
        return;
    }
    ExecutePayload(bpayload, Size, 0, MmGetPhysicalAddress(bRetBuf), NULL, NULL, NULL);
 
    // fix the rsa patch we did
    BYTE rsaOrig[4] = { 0x48, 0x00, 0x52, 0x45 }; // bl XeCryptBnQwBeSigVerify
    memcpy(bRetBuf+0x617C, rsaOrig, 4)
    if (!CWriteFile("Hdd:\\HV.bin", bRetBuf, RetBufSize))
        return;
    XPhysicalFree(bpayload);
    XPhysicalFree(bRetBuf);
}

The compiled ppc file: [Click here to view this link]
Virus scan: https://www.virustotal.com/en/file/...32818fd315db15a5b6b5016cb70b6b17456/analysis/
 
Last edited:
T

TEIR1plus2

Getting There
Messages
506
Reaction score
225
Updated: optimized a little, added support for a variable entrypoint, and fixed a potential memory leak.
 
M

Medaka

Getting There
Messages
391
Reaction score
373
Question, can you send me your CReadFile and CWriteFile functions cause it keeps hitting the bpayload check. Idk why, I'm allocating it with XPhysicalAlloc so it's being allocated dynamically in physical memory.
 
T

TEIR1plus2

Getting There
Messages
506
Reaction score
225
Question, can you send me your CReadFile and CWriteFile functions cause it keeps hitting the bpayload check. Idk why, I'm allocating it with XPhysicalAlloc so it's being allocated dynamically in physical memory.

I found the issue, bug in my code. I pulled this from the version I use, should work:

replace
Code:
BYTE* bPayload = (PBYTE)XPhysicalAlloc(cBufferSize, MAXULONG_PTR, NULL, PAGE_READWRITE);
memset(bPayload, 0, cBufferSize);

// we have memory allocated, we can't just return when something goes wrong.
QWORD ret = 0;

// lets check the addresses before doing anything else...
// The Following is a weird check done by the syscall. I've seen it trip and not trip without changing anything in the source files
// Seems to be Heavily dependent on the address XPhysicalAlloc assigns you
// Maybe MmAllocatePhysicalMemory is supposed to be used instead?
// Should think about a way to free and reallocate to a different address... maybe later
// TODO: add XPhysicalFree to this
QWORD physCheck = ((((DWORD)bPayload + cBufferSize) - 1) ^ (DWORD)bPayload) & 0xFFFF0000;
if (!physCheck)
{
    if (((DWORD)bPayload & 0xFFFFFFFF) <= 0x1FFBFFFF) // this is here because if the HV catches it, it'll just hang the console
    {
        ...
    }
    else
    {
        printf("Invalid bPayload address, make sure your sending a block created with XPhysicalAlloc!\n");
        printf("physPayload: 0x%08X\n", bPayload);
        ret = -1;
    }
}
else
{
    printf("PhysCheck Failed\n[physCheck: 0x%08X][cBufferSize: 0x%X]\n", physCheck, cBufferSize);
    ret = -1;
}

with:
Code:
BYTE* bPayload = (PBYTE)XPhysicalAlloc(cBufferSize, MAXULONG_PTR, 0x10000, PAGE_READWRITE);
memset(bPayload, 0, cBufferSize);
QWORD physPayload = 0x8000000000000000ULL + (DWORD)MmGetPhysicalAddress(bPayload);

// we have memory allocated, we can't just return when something goes wrong.
QWORD ret = 0;

// check for an internal system
// As long as the buffer does not extend over multiple 0x10000 byte pages, this will not trip
QWORD SOCCheck = (((physPayload + cBufferSize) - 1) ^ physPayload) & 0xFFFF0000;
if (SOCCheck == 0)
{
    if ((physPayload & 0xFFFFFFFF) <= 0x1FFFFFFF) // address validation (HV hangs if it catches this) (might be different on consoles with > 512mb ram)
    {
        ...
    }
    else
    {
        printf("Address out of range!\n");
        printf("physPayload: 0x%016llX\n", physPayload);
        ret = -1;
    }
}
else
{
    printf("SOCCheck Failed\n");
    printf("[SOCCheck: 0x%016llX][cBufferSize: 0x%X]\n", SOCCheck, cBufferSize);
    ret = -1;
}

Fixed in the original post as well.
 
Last edited:
M

Medaka

Getting There
Messages
391
Reaction score
373
I found the issue, bug in my code. I pulled this from the version I use, should work:

replace
Code:
BYTE* bPayload = (PBYTE)XPhysicalAlloc(cBufferSize, MAXULONG_PTR, NULL, PAGE_READWRITE);
memset(bPayload, 0, cBufferSize);

// we have memory allocated, we can't just return when something goes wrong.
QWORD ret = 0;

// lets check the addresses before doing anything else...
// The Following is a weird check done by the syscall. I've seen it trip and not trip without changing anything in the source files
// Seems to be Heavily dependent on the address XPhysicalAlloc assigns you
// Maybe MmAllocatePhysicalMemory is supposed to be used instead?
// Should think about a way to free and reallocate to a different address... maybe later
// TODO: add XPhysicalFree to this
QWORD physCheck = ((((DWORD)bPayload + cBufferSize) - 1) ^ (DWORD)bPayload) & 0xFFFF0000;
if (!physCheck)
{
    if (((DWORD)bPayload & 0xFFFFFFFF) <= 0x1FFBFFFF) // this is here because if the HV catches it, it'll just hang the console
    {
        ...
    }
    else
    {
        printf("Invalid bPayload address, make sure your sending a block created with XPhysicalAlloc!\n");
        printf("physPayload: 0x%08X\n", bPayload);
        ret = -1;
    }
}
else
{
    printf("PhysCheck Failed\n[physCheck: 0x%08X][cBufferSize: 0x%X]\n", physCheck, cBufferSize);
    ret = -1;
}

with:
Code:
BYTE* bPayload = (PBYTE)XPhysicalAlloc(cBufferSize, MAXULONG_PTR, 0x10000, PAGE_READWRITE);
memset(bPayload, 0, cBufferSize);
QWORD physPayload = 0x8000000000000000ULL + (DWORD)MmGetPhysicalAddress(bPayload);

// we have memory allocated, we can't just return when something goes wrong.
QWORD ret = 0;

// check for an internal system
// As long as the buffer does not extend over multiple 0x10000 byte pages, this will not trip
QWORD SOCCheck = (((physPayload + cBufferSize) - 1) ^ physPayload) & 0xFFFF0000;
if (SOCCheck == 0)
{
    if ((physPayload & 0xFFFFFFFF) <= 0x1FFFFFFF) // address validation (HV hangs if it catches this) (might be different on consoles with > 512mb ram)
    {
        ...
    }
    else
    {
        printf("Address out of range!\n");
        printf("physPayload: 0x%016llX\n", physPayload);
        ret = -1;
    }
}
else
{
    printf("SOCCheck Failed\n");
    printf("[SOCCheck: 0x%016llX][cBufferSize: 0x%X]\n", SOCCheck, cBufferSize);
    ret = -1;
}

Fixed in the original post as well.
Thanks for the fix. I will try it later when I sober up.
 
Top Bottom