Research project demonstrating signature-based ELF binary patching for Prism Launcher v11.x launch-mode functions on Linux.
# 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.
The patcher modifies two functions in the Prism Launcher ELF binary:
- 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; jepattern
- Original: determines launch mode based on
m_wantedLaunchModefield - 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_accountToUseload), walk backward to function entry. Falls back to searching for0x9cfield displacement (m_wantedLaunchMode).
- Python 3
sudoaccess- Prism Launcher v11.0.x installed at
/usr/bin/prismlauncher
- Backup: the original binary is saved to
/usr/bin/prismlauncher.bak - Revert:
sudo mv /usr/bin/prismlauncher.bak /usr/bin/prismlauncheror 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
| 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.
- Parse ELF program headers to build a file-offset-to-virtual-address mapping
- Locate a unique diagnostic string in
.rodata - Find all RIP-relative LEA instructions referencing it (ELF-agnostic pattern match)
- Walk backward from each LEA to identify
anyAccountIsValidvia call/test/conditional-jump pattern - Locate
decideLaunchModeby itsm_accountToUseload signature orm_wantedLaunchModefield offset - Replace function prologues with stubs that force the desired return value