Skip to content

Instantly share code, notes, and snippets.

@aglab2
Last active May 29, 2024 00:43
Show Gist options
  • Save aglab2/6bb351c4bd0ac6e13c931e03aa8f9949 to your computer and use it in GitHub Desktop.
Save aglab2/6bb351c4bd0ac6e13c931e03aa8f9949 to your computer and use it in GitHub Desktop.
Project 64 1.6 container escape vulnerability writeup

Vulnerable emulator is Project 64 1.6.x/1.7. 2 vulnerabilities can be used to gain arbitrary code execution from emulation container from N64 ROM.

  1. Container escape and arbitrary writes from N64 ROM outside of designated N64 RAM

Vulnerable function Compile_R4300i_SB and its friends Compile_R4300i_S* https://github.com/zeromus/pj64/blob/master/RecompilerOps.cpp#L1955C6-L2024

If non const Opcode.base is used to avoid condition at 1961-1971 which does checks properly, we can load from volatile address addr (compiled to MIPS asm inside ROM):

volatile char* addr = (char*) 0x1234;
unsigned char payload[] = { ... };
void start()
{
    // plant content
    for (int i = 0; i < sizeof(payload); i++)
    {
        char* ptr = addr + i;
        unsigned char payloadSymbol = payload[i];
        ...
        *ptr = payloadSymbol;
    }
}

This code will end up being handled by TLB_WriteMap loading when UseTlb is enabled (default config) https://github.com/zeromus/pj64/blob/master/RecompilerOps.cpp#L1994-L2010

TempReg2 = Map_TempReg(Section,x86_Any,-1,FALSE);
MoveX86RegToX86Reg(TempReg1, TempReg2);
ShiftRightUnsignImmed(TempReg2,12);
MoveVariableDispToX86Reg(TLB_WriteMap,"TLB_WriteMap",TempReg2,TempReg2,4);
//For tlb miss
//0041C522 85 C0                test        eax,eax
//0041C524 75 01                jne         0041C527

XorConstToX86Reg(TempReg1,3);	
if (IsConst(Opcode.rt)) {
    MoveConstByteToX86regPointer((BYTE)MipsRegLo(Opcode.rt),TempReg1, TempReg2);
} else if (IsMapped(Opcode.rt) && Is8BitReg(MipsRegLo(Opcode.rt))) {
    MoveX86regByteToX86regPointer(MipsRegLo(Opcode.rt),TempReg1, TempReg2);
} else {	
    UnProtectGPR(Section,Opcode.rt);
    MoveX86regByteToX86regPointer(Map_TempReg(Section,x86_Any8Bit,Opcode.rt,FALSE),TempReg1, TempReg2);
}

Given X86 code will codegen the following X86 asm instructions:

7D3A2D49 89 CE                mov         esi,ecx  
7D3A2D4B C1 EE 0C             shr         esi,0Ch  
7D3A2D4E 8B 34 B5 00 00 44 7C mov         esi,dword ptr [esi*4+7C440000h]  
7D3A2D55 83 F1 03             xor         ecx,3  
7D3A2D58 88 14 31             mov         byte ptr [ecx+esi],dl

During execution registers will have values that look like

TLB_WriteMap=7C440000h
ecx=0x1234, esi=0x0, dl=0x89
TempReg1 = ecx
TempReg2 = esi

Normally TLB_WriteMap contains entries like https://github.com/zeromus/pj64/blob/master/Tlb.cpp#L67 that has offset from N64Mem but because there are no checks in place for TLB miss as notified by comment "For tlb miss", TLB_WriteMap[0] can be nullptr. In this case load 'mov' at 7D3A2D58 will write to 0x0 + addr instead N64Mem + off + addr. This means that addr can now refer to low values overwriting emulator memory.

TLB_WriteMap[count >> 12] = ((DWORD)N64MEM + (count & 0x1FFFFFFF)) - count;
  1. Insufficient page protection

Because PJ64 1.6 is an old binary, its execute pages are protected with RW rights. For the same age reason, there is no address randomization (ASLR) so X86 code is mapped at 0x400000 which means emulator code can be overwritten. Using any function that is executed every frame, for example controller polling function, 'payload' with X86 shellcode can be executed after it was written to execute pages in (1).

Project64 2.x and 3.x have vulnerability (1) fixed so it is impossible to exploit it. Project 64 Legacy 1.6.2 release version does not have vulnerability fixed but it is 'modern' so it has ASLR enabled. This means that exploit needs to be more complicated to defeat ASLR.

Fixed in Project 64 Legacy 1.6.3 release https://github.com/pj64team/Project64-Legacy/releases/tag/Project64-1.6.3

@TheGent
Copy link

TheGent commented Mar 14, 2024

@aglab2 Thank you for bringing this to our attention, Project64 Legacy 1.6.3 is fixed and released
Kind regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment