Skip to content

kaplan-025/prismlauncher-runtime-patch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 

Repository files navigation

Prism Launcher Launch Mode Patch

Research project demonstrating signature-based ELF binary patching for Prism Launcher v11.x launch-mode functions on Linux.

Quick Start

# One-command patch (run as root)
sudo python3 -c "
import sys,os,struct
b='/usr/bin/prismlauncher';bk=b+'.bak';d=bytearray(open(b,'rb').read())
p1=bytes([0x8b,0x87,0x9c,0x00,0x00,0x00,0x89,0x87,0xa0,0x00,0x00,0x00,0xb8,0x01,0x00,0x00,0x00,0xc3])
p2=bytes([0xb8,0x01,0x00,0x00,0x00,0xc3])
if d.find(p1)>=0 and d.find(p2)>=0: print('Already patched!');sys.exit(0)
e_phoff=struct.unpack('<Q',d[32:40])[0];e_phentsize=struct.unpack('<H',d[54:56])[0];e_phnum=struct.unpack('<H',d[56:58])[0]
ph=[struct.unpack('<IIQQQQQQ',d[e_phoff+i*e_phentsize:e_phoff+(i+1)*e_phentsize]) for i in range(e_phnum)]
def f2v(fo):
    for t,f,o,va,pa,fsz,msz,al in ph:
        if t==1 and o<=fo<o+fsz: return fo-o+va
    return fo
s=b'In order to play Minecraft, you must have at least one Microsoft account which owns Minecraft logged in'
si=d.find(s)
if si<0: print('Error string not found!');sys.exit(1)
sv=f2v(si)
print(f'Error string vaddr=0x{sv:x}')
refs=[];pos=0
while pos+6<len(d):
    b0,b1,b2=d[pos],d[pos+1],d[pos+2]
    if b0 in (0x48,0x4c) and b1==0x8d and (b2&0xc7)==0x05:
        disp=struct.unpack('<i',d[pos+3:pos+7])[0]
        if f2v(pos)+7+disp==sv: refs.append(pos)
    pos+=1
if not refs: print('No string refs found!');sys.exit(1)
print(f'String LEA refs: {[hex(r) for r in refs]}')
aai_addr=None
for ref in refs:
    s2=max(0,ref-500);seg=d[s2:ref+5];rp=0
    while rp<len(seg)-7:
        if seg[rp]==0xe8:
            rel=struct.unpack('<i',seg[rp+1:rp+5])[0];t=s2+rp+5+rel
            if t>=0 and t<len(d) and rp+7<len(seg) and seg[rp+5:rp+7]==bytes([0x84,0xc0]):
                cond=seg[rp+7]
                if (cond&0xf0)==0x70: aai_addr=t;break
        rp+=1
    if aai_addr: break
if aai_addr is None: print('anyAccountIsValid FAILED');sys.exit(1)
if d[aai_addr:aai_addr+4]==bytes([0xf3,0x0f,0x1e,0xfa]): aai_addr+=4
print(f'anyAccountIsValid at 0x{aai_addr:x}')
pat=bytes([0x48,0x8b,0xbf,0xe0,0x00,0x00,0x00,0x48,0x85,0xff])
lp=d.find(pat)
if lp>=0:
    for bkw in range(max(0,lp-50),lp):
        if d[bkw]==0x55 and d[bkw+1]==0x48 and d[bkw+2]==0x89 and d[bkw+3]==0xe5: lp=bkw;break
        elif d[bkw]==0x41 and d[bkw+1]==0x57: lp=bkw;break
        elif d[bkw]==0xf3 and d[bkw+1]==0x0f and d[bkw+2]==0x1e and d[bkw+3]==0xfa:
            for bk2 in range(bkw+4,lp):
                if d[bk2]==0x55 or (d[bk2]==0x41 and d[bk2+1]==0x57): lp=bk2;break
            break
else:
    pat2=bytes([0x8b,0x85,0x9c,0x00,0x00,0x00,0x83,0xf8]);lp=d.find(pat2)
    if lp<0:
        pat3=bytes([0x41,0x8b,0x85,0x9c,0x00,0x00,0x00,0x83,0xf8]);lp=d.find(pat3)
    if lp<0:
        lp=-1;pos=0
        while pos<len(d)-6:
            if d[pos:pos+4]==bytes([0x9c,0x00,0x00,0x00]):
                for bj in range(max(0,pos-50),pos):
                    if d[bj]==0x55 and d[bj+1]==0x48 and d[bj+2]==0x89 and d[bj+3]==0xe5: lp=bj;break
                    elif d[bj]==0x41 and d[bj+1]==0x57: lp=bj;break
                if lp>=0: break
            pos+=1
    if lp<0: print('decideLaunchMode FAILED');sys.exit(1)
print(f'decideLaunchMode at 0x{lp:x}')
if not os.path.exists(bk):
    with open(bk,'wb') as f: f.write(d);print('Backup saved')
if d[lp:lp+len(p1)]==p1: print('  decideLaunchMode already patched')
else: d[lp:lp+len(p1)]=p1;print(f'  decideLaunchMode patched at 0x{lp:x}')
if d[aai_addr:aai_addr+len(p2)]==p2: print('  anyAccountIsValid already patched')
else: d[aai_addr:aai_addr+len(p2)]=p2;print(f'  anyAccountIsValid patched at 0x{aai_addr:x}')
with open(b,'wb') as f: f.write(d);print('Done!')
" && mkdir -p "$HOME/.local/share/PrismLauncher" && python3 -c "import json,os;json.dump({'accounts':[{'entitlement':{'canPlayMinecraft':True,'ownsMinecraft':True},'msa-client-id':'','type':'MSA'},{'active':True,'profile':{'capes':[],'id':'0c79d88a112537a0a302f01afa6bc94a','name':'YOUR-NICKNAME','skin':{'id':'','url':'','variant':''}},'type':'Offline','ygg':{'extra':{'clientToken':'8be89b1112474b5fb8f061699ff41bda','userName':'YOUR-NICKNAME'},'iat':1738858981,'token':'0'}}],'formatVersion':3},open(os.path.expanduser('~/.local/share/PrismLauncher/accounts.json'),'w'),indent=2);print('accounts.json created')"

Edit YOUR-NICKNAME in the command above to your desired username before running.

Targets

The patcher modifies two functions in the Prism Launcher ELF binary:

anyAccountIsValid()

  • Original: evaluates whether a valid account condition is met
  • Patched: always returns true (1) ¬タヤ mov eax, 1; ret
  • Locator strategy: search for a unique error string, find all RIP-relative LEA instructions referencing it, scan backward for a call; test al,al; je pattern

decideLaunchMode()

  • Original: determines launch mode based on m_wantedLaunchMode field
  • Patched: forces the function to return the wanted mode ¬タヤ mov eax,[reg+0x9c]; mov [reg+0xa0],eax; mov eax,1; ret
  • Locator strategy: search for mov rdi,[rdi+0xe0]; test rdi,rdi (m_accountToUse load), walk backward to function entry. Falls back to searching for 0x9c field displacement (m_wantedLaunchMode).

Requirements

  • Python 3
  • sudo access
  • Prism Launcher v11.0.x installed at /usr/bin/prismlauncher

Usage Notes

  • Backup: the original binary is saved to /usr/bin/prismlauncher.bak
  • Revert: sudo mv /usr/bin/prismlauncher.bak /usr/bin/prismlauncher or reinstall via your package manager
  • accounts.json: a pre-configured profile is written to ~/.local/share/PrismLauncher/accounts.json
  • Idempotent: running the script again detects already-patched functions and exits cleanly

Tested Builds

Build Package anyAccountIsValid decideLaunchMode
extra (generic x86_64) prismlauncher 11.0.2-1 0x5072b4 0x377450
cachyos-extra-v3 (x86_64_v3) prismlauncher 11.0.2-1.1 0x571f04 0x3b63d0

The signature-based approach (ELF program header parsing, string reference search, field offset patterns) works across different builds and CPU-optimized variants without hardcoded offsets.

Technical Overview

  1. Parse ELF program headers to build a file-offset-to-virtual-address mapping
  2. Locate a unique diagnostic string in .rodata
  3. Find all RIP-relative LEA instructions referencing it (ELF-agnostic pattern match)
  4. Walk backward from each LEA to identify anyAccountIsValid via call/test/conditional-jump pattern
  5. Locate decideLaunchMode by its m_accountToUse load signature or m_wantedLaunchMode field offset
  6. Replace function prologues with stubs that force the desired return value

About

Experimental runtime-independent patching techniques for Prism Launcher ELF binaries. cachyos

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors