Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bdcefbc
Showing
5 changed files
with
657 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Soundhax | ||
|
||
A heap overflow in tag processing leads to code execution when a specially- | ||
crafted m4a file is loaded by Nintendo 3DS Sound. | ||
|
||
This bug is particularly good, because as far as I can tell it is the first | ||
ever homebrew exploit that is free, offline, and works on every version | ||
of the firmware for which the sound app is available. | ||
|
||
## Regions and Versions | ||
|
||
I only had a USA 3DS on 11.0, so I tested on that version. Other regions coming | ||
soon. | ||
|
||
## Installation | ||
`exp.py`: builds final exploit file | ||
|
||
## Writeup | ||
|
||
### The Bug | ||
3DS Sound mallocs a buffer of 256 bytes to hold the name of song as described | ||
in its mp4 atom tags. This is sensible since it's the maximum allowed size according | ||
to the spec. When parsing an ascii title, `strncpy(dst, src, 256)` is used, which | ||
is safe and correct. However, because unicode strings contain null bytes, rather | ||
than using a unicode `strncpy` variant, the application simply `memcpy`s the name | ||
bytes onto the heap using the user provided size, which can be arbitrarily large. | ||
|
||
### Exploit | ||
I overflow my data onto the next heap chunk, which lets me fully control the | ||
malloc header of that chunk, which happens to be allocated at the time of the overflow. | ||
When that chunk is freed, a heap unlink is performed, which allows me to do | ||
an arbitrary write. This means I can write a dword to the stack and control | ||
PC. Unfortunately, there aren't any usable gadgets (trust me, I looked), so I | ||
had to use a more advanced technique to exploit the bug. I used the | ||
arbitrary write to overwrite the free list header with a stack address, | ||
while setting the start and end fields of the chunk being freed to cause the | ||
block to appear undersized, thus causing it to not be added to the free list | ||
and so the stack address I just wrote is used on the next malloc. Because malloc | ||
jumps through the free list looking for a suitable block, I had to find a stack | ||
address at which there appears to be a valid heap chunk header with a large enough | ||
size for the requested allocation and null pointers for the next and prev entries | ||
in the list, so that my stack chunk is chosen as the 'best' one. Once all of | ||
these conditions are met, the next malloc returns the stack address as the | ||
'heap' location to write my next tag data, which lets me turn the arbitrary | ||
write primitive into ROP. From there I use the gspwn GPU exploit to write | ||
my stage2 shellcode over the text section of the sound process, before finally | ||
jumping to it. | ||
|
||
In summary, the process looks like this: | ||
|
||
heap overflow -> arbitrary write to free list -> stack overflow -> gspwn -> code execution | ||
|
||
## Thanks | ||
Subv and Citra authors - for help emulating sound, this was invaluable | ||
|
||
plutoo - stage 2 shellcode | ||
|
||
yellows8 - help with gpu address translation for gspwn | ||
|
||
smea - homebrew launcher | ||
|
||
#cakey - advice and support | ||
|
||
PPP - teaching me everything I know | ||
|
||
geohot, comex, j00ru, loki, project zero - inspiring me to pursue bug hunting |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* sound constants by nedwill */ | ||
#define GARBAGE 0xDEADC0DE | ||
|
||
#define REGION_CONST(name, eur, usa, jap) .set name, usa | ||
#define GLOBAL_CONST(name, val) .set name, val | ||
|
||
#define PA_TO_GPU_ADDR(pa) ((pa) - 0x0C000000) | ||
#define GPU_TO_PA_ADDR(pa) ((pa) + 0x0C000000) | ||
|
||
/* Apparently JAP code is a bit smaller than the other ones, making code.bin | ||
being allocated at a slightly different address. */ | ||
REGION_CONST(NEW_VA_TO_PA, 0x27700000, 0x27700000, 0x27800000); | ||
REGION_CONST(OLD_VA_TO_PA, 0x23B00000, 0x23B00000, 0x23C00000); | ||
|
||
#if defined(NEW3DS) | ||
#define CODE_VA_TO_PA(va) ((va) + NEW_VA_TO_PA) | ||
#else | ||
#define CODE_VA_TO_PA(va) ((va) + OLD_VA_TO_PA) | ||
#endif | ||
|
||
/* FS functions. */ | ||
REGION_CONST(FS_MOUNT_SDMC, 0x002CCCF8, 0x002CDA28, 0x002CCA34); | ||
REGION_CONST(FS_OPEN_FILE, 0x0028CC30, 0x0028D220, 0x0028C830); | ||
REGION_CONST(FS_READ_FILE, 0x00115A90, 0x00115B00, 0x00115A80); | ||
|
||
/* GSP functions. */ | ||
REGION_CONST(GSP_FLUSH_DATA_CACHE, 0x00116AB8, 0x00116B28, 0x00116AA8); | ||
REGION_CONST(GSP_ENQUEUE_CMD, 0x00285994, 0x00285F84, 0x00285594); | ||
REGION_CONST(GSP_GX_CMD4, 0x00116BB8, 0x00116C28, 0x00116BA8); | ||
REGION_CONST(GSP_GET_HANDLE, 0x0028A8F8, 0x0028AEE8, 0x0028A4F8); | ||
REGION_CONST(GSP_GET_INTERRUPTRECEIVER, 0x00287F94, 0x00288584, 0x00287B94); | ||
REGION_CONST(GSP_WRITE_HW_REGS, 0x00110080, 0x001100F0, 0x00110070); | ||
REGION_CONST(GSP_THREAD_OBJ_PTR, 0x00594C40, 0x0051FC40, 0x00480C40); | ||
|
||
/* We will overwrite the padding at end of the last code page. This guarantees | ||
that we don't overwrite existing code, and also avoids potential issues | ||
with the instruction cache. */ | ||
REGION_CONST(STAGE2_CODE_VA, 0x003AE800, 0x003AF200, 0x003AE400); | ||
GLOBAL_CONST(STAGE2_CODE_PA, CODE_VA_TO_PA(STAGE2_CODE_VA)); | ||
GLOBAL_CONST(STAGE2_SIZE, 0x800); // European game limits this size. | ||
|
||
GLOBAL_CONST(FREAKYBIN_LOAD_ADDR, 0x14200000); | ||
GLOBAL_CONST(FRAMEBUF_ADDR, 0x14200000); | ||
GLOBAL_CONST(FRAMEBUF_SIZE, (400*240*4)); | ||
GLOBAL_CONST(PARAMBLK_ADDR, 0x14000000); | ||
GLOBAL_CONST(OTHERAPP_CODE_VA, 0x00101000); | ||
GLOBAL_CONST(OTHERAPP_CODE_PA, CODE_VA_TO_PA(OTHERAPP_CODE_VA)); | ||
GLOBAL_CONST(OTHERAPP_ADDR, 0x142C0000); | ||
GLOBAL_CONST(OTHERAPP_SIZE, 0xC000); |
Oops, something went wrong.