diff --git a/Include/CTRL/CodeGen.h b/Include/CTRL/CodeGen.h new file mode 100644 index 0000000..b77f3f5 --- /dev/null +++ b/Include/CTRL/CodeGen.h @@ -0,0 +1,23 @@ +#ifndef _CTRL_CODEGEN_H +#define _CTRL_CODEGEN_H + +#include "CTRL/Types.h" + +typedef void* CTRLCodeRegion; + +#if defined(__cplusplus) +extern "C" { +#endif // __cplusplus + +u8* ctrlAllocCodeBlock(CTRLCodeRegion* region, size_t size); +Result ctrlCommitCodeRegion(CTRLCodeRegion* region); +Result ctrlDestroyCodeRegion(CTRLCodeRegion* region); +u32 ctrlFirstCodeBlock(const CTRLCodeRegion* region); +u32 ctrlNextCodeBlock(u32 codeBlock); +u32 ctrlGetCodeBlock(const CTRLCodeRegion* region, size_t index); + +#if defined(__cplusplus) +} +#endif // __cplusplus + +#endif /* _CTRL_CODEGEN_H */ \ No newline at end of file diff --git a/README.md b/README.md index b68a752..3f38fab 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CTRL -Low level-ish library for playing around in usermode on the 3DS. +3DS library containing utilities for hacking around in usermode. ## How-to diff --git a/Source/CodeGen.c b/Source/CodeGen.c new file mode 100644 index 0000000..c13ba6b --- /dev/null +++ b/Source/CodeGen.c @@ -0,0 +1,165 @@ +#include "CTRL/CodeGen.h" +#include "CTRL/App.h" +#include "CTRL/Memory.h" + +#include +#include +#include + +#define CODE_REGION_START 0x100000 +#define CODE_REGION_SIZE 0x3F00000 + +#define ERR_NO_MEM MAKERESULT(RL_PERMANENT, RS_OUTOFRESOURCE, RM_APPLICATION, RD_OUT_OF_MEMORY); + +typedef struct { + u32 size; +} BlockHeader; + +typedef struct { + u32 sourceAddr; + u32 regionSize; +} RegionHeader; + +static void* ctrl_alignedRealloc(void *p, size_t newSize, size_t alignment) { + void* q = aligned_alloc(alignment, newSize); + if (q) { + const size_t ogSize = malloc_usable_size(p); + memcpy(q, p, ogSize < newSize ? ogSize : newSize); + free(p); + } + + return q; +} + +static Result ctrl_findAddressForCodeMirror(size_t size, u32* out) { + size_t queriedSize = 0; + while (queriedSize < CODE_REGION_SIZE) { + MemInfo info; + Result ret = ctrlQueryRegion(CODE_REGION_START + queriedSize, &info); + if (R_FAILED(ret)) + return ret; + + if (info.state == MEMSTATE_FREE && info.size >= size) + break; + + queriedSize += info.size; + } + + if (queriedSize >= CODE_REGION_SIZE) + return ERR_NO_MEM; + + *out = CODE_REGION_START + queriedSize; + return 0; +} + +u8* ctrlAllocCodeBlock(CTRLCodeRegion* region, size_t size) { + // Align code block size to word. + size = ctrlAlignSize(size, sizeof(u32)); + + // Init region if needed. + if (!*region) { + // A dummy block of size zero tells when the chain ends. + const size_t newSize = sizeof(RegionHeader) + sizeof(BlockHeader) * 2 + size; + RegionHeader* r = aligned_alloc(CTRL_PAGE_SIZE, newSize); + if (!r) + return 0; + + r->sourceAddr = (u32)r; + r->regionSize = newSize; + ((BlockHeader*)((u32)r + sizeof(RegionHeader)))->size = size; + ((BlockHeader*)((u32)r + sizeof(RegionHeader) + size))->size = 0; + *region = (CTRLCodeRegion)r; + return (u8*)((u32)r + sizeof(RegionHeader) + sizeof(BlockHeader)); + } + + // Add space for code block. + RegionHeader* r = (RegionHeader*)*region; + const size_t newSize = r->regionSize + sizeof(BlockHeader) + size; + r = ctrl_alignedRealloc(r, newSize, CTRL_PAGE_SIZE); + if (!r) + return 0; + + // Write block info: last block gets allocated, and new block becomes the zero block. + const size_t offsetToLastBlock = r->regionSize - sizeof(BlockHeader); + const size_t offsetToNewBlock = newSize - sizeof(BlockHeader); + r->sourceAddr = (u32)r; + r->regionSize = newSize; + ((BlockHeader*)((u32)r + offsetToLastBlock))->size = size; + ((BlockHeader*)((u32)r + offsetToNewBlock))->size = 0; + *region = (CTRLCodeRegion)r; + return (u8*)((u32)r + offsetToLastBlock + sizeof(BlockHeader)); +} + +Result ctrlCommitCodeRegion(CTRLCodeRegion* region) { + RegionHeader* r = (RegionHeader*)*region; + + // Compute the base address. + u32 baseAddr = 0; + Result ret = ctrl_findAddressForCodeMirror(r->regionSize, &baseAddr); + if (R_FAILED(ret)) + return ret; + + // Map code. + const u32 sourceAddr = r->sourceAddr; + const size_t regionSize = ctrlAlignSize(r->regionSize, CTRL_PAGE_SIZE); + ret = ctrlMirror(baseAddr, sourceAddr, regionSize); + if (R_FAILED(ret)) + return ret; + + // Make it executable. + ret = ctrlChangePerms(baseAddr, regionSize, MEMPERM_READEXECUTE); + if (R_FAILED(ret)) { + ctrlUnmirror(baseAddr, sourceAddr, regionSize); + return ret; + } + + // Flush cache. + ret = ctrlFlushCache(CTRL_ICACHE | CTRL_DCACHE); + if (R_FAILED(ret)) { + ctrlUnmirror(baseAddr, sourceAddr, regionSize); + return ret; + } + + *region = (CTRLCodeRegion)baseAddr; + return ret; +} + +Result ctrlDestroyCodeRegion(CTRLCodeRegion* region) { + const RegionHeader* r = (RegionHeader*)*region; + const u32 baseAddr = (u32)*region; + const u32 sourceAddr = r->sourceAddr; + const u32 regionSize = ctrlAlignSize(r->regionSize, CTRL_PAGE_SIZE); + + Result ret = ctrlUnmirror(baseAddr, sourceAddr, regionSize); + if (R_FAILED(ret)) + return ret; + + free((void*)sourceAddr); + *region = NULL; + return 0; +} + +u32 ctrlFirstCodeBlock(const CTRLCodeRegion* region) { + const u32 codeBlock = (u32)*region + sizeof(RegionHeader) + sizeof(BlockHeader); + const BlockHeader* b = (BlockHeader*)(codeBlock - sizeof(BlockHeader)); + return b->size ? codeBlock : 0; +} + +u32 ctrlNextCodeBlock(u32 codeBlock) { + const BlockHeader* thisB = (BlockHeader*)(codeBlock - sizeof(BlockHeader)); + const BlockHeader* nextB = (BlockHeader*)(codeBlock + thisB->size); + return nextB->size ? codeBlock + thisB->size + sizeof(BlockHeader) : 0; +} + +u32 ctrlGetCodeBlock(const CTRLCodeRegion* region, size_t index) { + u32 codeBlock = ctrlFirstCodeBlock(region); + + for (size_t i = 0; codeBlock; ++i) { + if (i >= index) + break; + + codeBlock = ctrlNextCodeBlock(codeBlock); + } + + return codeBlock; +} \ No newline at end of file diff --git a/Tests/Main.c b/Tests/Main.c index 1a0e0a9..47fba38 100644 --- a/Tests/Main.c +++ b/Tests/Main.c @@ -2,6 +2,7 @@ #include "CTRL/Hook.h" #include "CTRL/Exception.h" #include "CTRL/App.h" +#include "CTRL/CodeGen.h" #include #include @@ -10,10 +11,9 @@ #define RAND_EXPECTED_RET 1337 #define RAND_ACTUAL_RET 0 -#define CODE_REGION_START 0x100000 -#define CODE_REGION_SIZE 0x3F00000 +/* APP INFO TEST */ -static bool test1(void) { +static bool appInfoTest(void) { printf("=== APP INFO TEST ===\n"); const CTRLAppSectionInfo* info = ctrlAppSectionInfo(); @@ -27,6 +27,8 @@ static bool test1(void) { return true; } +/* EX TEST */ + static void exHandler0(ERRF_ExceptionData* info) { printf("exHandler0(): This is called first (not handling)\n"); } @@ -37,7 +39,7 @@ static void exHandler1(ERRF_ExceptionData* info) { info->regs.pc += 4; } -static bool test2(void) { +static bool exTest(void) { printf("=== EXCEPTION HANDLER TEST ===\n"); if (ctrlExceptionHandlingIsSupported()) { @@ -63,152 +65,158 @@ static bool test2(void) { return true; } -static int myRand(void) { return RAND_EXPECTED_RET; } - -static bool test3(void) { - printf("=== HOOK TEST ===\n"); - - CTRLHook hook; - hook.addr = (u32)rand; - hook.callback = (u32)myRand; - - // Hook code. - Result ret = ctrlHook(&hook); - if (R_FAILED(ret)) { - printf("HOOK FAILED: 0x%08lx\n", ret); - return false; - } - - printf("Calling rand()...\n"); - int val = rand(); - printf("- Expected: %u\n", RAND_EXPECTED_RET); - printf("- Got: %u\n", val); - - if (val != RAND_EXPECTED_RET) { - printf("FAILED\n"); - return false; - } - - // Unhook code. - ret = ctrlUnhook(&hook); - if (R_FAILED(ret)) { - printf("UNHOOK FAILED: 0x%08lx\n", ret); - return false; - } - - srand(0); - val = rand(); - printf("- Expected: %u\n", RAND_ACTUAL_RET); - printf("- Got: %u\n", val); +/* CODEGEN TEST */ - if (val != RAND_ACTUAL_RET) { - printf("FAILED\n"); - return false; +static bool setupCodeBlock(CTRLCodeRegion* codeRegion, const u8* bytes, size_t size) { + u8* codeBlockData = ctrlAllocCodeBlock(codeRegion, size); + if (codeBlockData) { + memcpy(codeBlockData, bytes, size); + return true; } - printf("SUCCESS\n"); - return true; + return false; } -static bool test4(void) { +static bool codegenTest(void) { #define FUNC_PARAM_1 5 #define FUNC_PARAM_2 3 - const u8 codeBytes[] = { + const u8 codeAdd[] = { 0x08, 0x44, // ADD R0, R1 0x70, 0x47, // BX LR }; + const u8 codeSub[] = { + 0x01, 0x00, 0x40, 0xE0, // SUB R0, R1 + 0x1E, 0xFF, 0x2F, 0xE1, // BX LR + }; + printf("=== CODEGEN TEST ===\n"); - // Allocate code buffer. - void* mem = aligned_alloc(CTRL_PAGE_SIZE, sizeof(codeBytes)); - if (!mem) { - printf("ALLOC FAILED\n"); + CTRLCodeRegion codeRegion = NULL; + + // Allocate code blocks. + if (!setupCodeBlock(&codeRegion, codeAdd, sizeof(codeAdd))) { + printf("ADD ALLOC FAILED\n"); return false; } - memcpy(mem, codeBytes, sizeof(codeBytes)); + if (!setupCodeBlock(&codeRegion, codeSub, sizeof(codeSub))) { + printf("SUB ALLOC FAILED\n"); + return false; + } - // Find address in CODE region where to map the code. - size_t queriedSize = 0; - while (queriedSize < CODE_REGION_SIZE) { - MemInfo info; - Result ret = ctrlQueryRegion(CODE_REGION_START + queriedSize, &info); - if (R_FAILED(ret)) { - printf("QUERY FAILED: 0x%08lx\n", ret); - return false; - } + // Commit code region. + Result ret = ctrlCommitCodeRegion(&codeRegion); + if (R_FAILED(ret)) { + printf("MAP FAILED: 0x%08lx\n", ret); + return false; + } - if (info.state == MEMSTATE_FREE) - break; + // Get address for the code blocks. + const u32 addCodeAddr = ctrlGetCodeBlock(&codeRegion, 0); + if (!addCodeAddr) { + printf("NO ADD CODE ADDR\n"); + return false; + } - queriedSize += info.size; + const u32 subCodeAddr = ctrlGetCodeBlock(&codeRegion, 1); + if (!subCodeAddr) { + printf("NO SUB CODE ADDR\n"); + return false; } - if (queriedSize >= CODE_REGION_SIZE) { - printf("NO SPACE AVAILABLE\n"); + // Now we're ready to call the functions. + typedef int(*Fn_t)(int, int); + Fn_t f = (Fn_t)(addCodeAddr | 1); // Set bit 0 for thumb code. + printf("Doing addition\n"); + int val = f(FUNC_PARAM_1, FUNC_PARAM_2); + + printf("- Expected: %i\n", FUNC_PARAM_1 + FUNC_PARAM_2); + printf("- Got: %i\n", val); + + if (val != (FUNC_PARAM_1 + FUNC_PARAM_2)) { + printf("ADD FAILED\n"); return false; } - const u32 codeAddr = CODE_REGION_START + queriedSize; - printf("Found address in CODE region: 0x%08lx\n", codeAddr); + f = (Fn_t)subCodeAddr; + printf("Doing subtraction\n"); + val = f(FUNC_PARAM_1, FUNC_PARAM_2); - // Mirror code buffer to CODE region. - const u32 alignedCodeAddr = ctrlAlignAddr(codeAddr, CTRL_PAGE_SIZE); - const u32 alignedSrcAddr = ctrlAlignAddr((u32)mem, CTRL_PAGE_SIZE); - const u32 alignedSize = ctrlAlignSize(sizeof(codeBytes), CTRL_PAGE_SIZE); - Result ret = ctrlMirror(alignedCodeAddr, alignedSrcAddr, alignedSize); - if (R_FAILED(ret)) { - printf("MIRROR FAILED: 0x%08lx\n", ret); + printf("- Expected: %i\n", FUNC_PARAM_1 - FUNC_PARAM_2); + printf("- Got: %i\n", val); + + if (val != (FUNC_PARAM_1 - FUNC_PARAM_2)) { + printf("SUB FAILED\n"); return false; } - // Make it executable. - ret = ctrlChangePerms(codeAddr, sizeof(codeBytes), MEMPERM_READEXECUTE); + // Destroy code region. + ret = ctrlDestroyCodeRegion(&codeRegion); if (R_FAILED(ret)) { - printf("PERM CHANGE FAILED: 0x%08lx\n", ret); + printf("DESTROY FAILED: 0x%08lx\n", ret); return false; } - // Flush instruction cache. - ret = ctrlFlushCache(CTRL_ICACHE); + printf("SUCCESS\n"); + return true; +} + +/* HOOK TEST */ + +static int myRand(void) { return RAND_EXPECTED_RET; } + +static bool hookTest(void) { + printf("=== HOOK TEST ===\n"); + + CTRLHook hook; + hook.addr = (u32)rand; + hook.callback = (u32)myRand; + + // Hook code. + Result ret = ctrlHook(&hook); if (R_FAILED(ret)) { - printf("CACHE FLUSH FAILED: 0x%08lx\n", ret); + printf("HOOK FAILED: 0x%08lx\n", ret); return false; } - // Now we're ready to call the function. - printf("Calling function...\n"); - typedef int(*Add_t)(int, int); - Add_t f = (Add_t)(codeAddr | 1); // Set bit 0 for thumb. - int val = f(5, 3); - - printf("- Expected: %u\n", FUNC_PARAM_1 + FUNC_PARAM_2); + printf("Calling rand()...\n"); + int val = rand(); + printf("- Expected: %u\n", RAND_EXPECTED_RET); printf("- Got: %u\n", val); - if (val != (FUNC_PARAM_1 + FUNC_PARAM_2)) { + if (val != RAND_EXPECTED_RET) { printf("FAILED\n"); return false; } - // Unmirror buffer from CODE region. - ret = ctrlUnmirror(alignedCodeAddr, alignedSrcAddr, alignedSize); + // Unhook code. + ret = ctrlUnhook(&hook); if (R_FAILED(ret)) { - printf("UNMIRROR FAILED: 0x%08lx\n", ret); + printf("UNHOOK FAILED: 0x%08lx\n", ret); return false; } - // Free code buffer. - free(mem); + srand(0); + val = rand(); + printf("- Expected: %u\n", RAND_ACTUAL_RET); + printf("- Got: %u\n", val); + + if (val != RAND_ACTUAL_RET) { + printf("FAILED\n"); + return false; + } printf("SUCCESS\n"); return true; } +/* Main */ + int main(int argc, char* argv[]) { typedef bool(*Test_t)(void); - Test_t tests[] = { test1, test2, test3, test4 }; + Test_t tests[] = { appInfoTest, exTest, codegenTest, hookTest }; const size_t numTests = sizeof(tests) / sizeof(Test_t);