scripts/games/LoL/linux/sulaunchhelper2.py

199 lines
6.1 KiB
Python

#!/usr/bin/env python3
import frida
import socket
import ssl
import psutil
import time
import subprocess
import sys
"""
requires psutil, frida (pip install psutil frida)
suLaunchhelper2 will try to lauch the League of Legends client by instrumenting frida (https://frida.re/)
This version requires sudo or `sysctl kernel.yama.ptrace_scope=0`.
It will launch frida directly from outside of wine.
"""
WINEDUMP = 'winedump' # path to winedump, does not necessarily need to be the same version as wine
SELECT_SIGNATURE = "'long', ['long', 'pointer', 'pointer', 'pointer'], 'stdcall'" # return type, [arg types...], abi
sleep_time = 1
timeout = 0 # 0 to disable
hide_backtrace = True
class FridaWrapper():
def __init__(self, process, module=None, export=None, position=None, signature=None):
self.session = None
self.process = process
self.module = module
self.export = export
self.position = position
self.signature = signature
def get_location(self):
if self.module and self.export:
return f"Process.getModuleByName('{self.module}').getExportByName('{self.export}')"
if self.position and self.signature:
return f"new NativeFunction(ptr({hex(self.position)}), {self.signature})"
def attach(self):
if self.session: return
self.session = frida.attach(self.process)
script = self.session.create_script("""
Interceptor.attach(
""" + self.get_location() + """, {
onEnter: function(args) {
args[4].writeInt(0x0);
}
}
);
""")
script.load()
def detach(self):
if not self.session: return
script = self.session.create_script("Interceptor.detachAll();")
script.load()
self.session.detach()
self.session = None
def attached(self):
return self.session is not None
class TimeoutException(Exception):
pass
class Process:
def __init__(self, name, internal=None):
self.name = name
self.internal = internal or name
self.process = None
def find(self):
if p := find_process_by_name(self.internal):
self.process = p
return True
return False
def wait_for(self, tsleep=1, timeout=0):
start = time.time()
while not self.find():
time.sleep(sleep_time)
if timeout and time.time() - start > timeout:
raise TimeoutException(f'Timeout while waiting for {self.name}')
def __repr__(self):
return self.name
class Symbol:
def __init__(self, offset, position, name):
self.offset = offset if type(offset) == int else int(offset, 16)
self.position = int(position)
self.name = name
def __repr__(self):
return f'Symbol(offset={hex(self.offset)}, position={self.position}, name=\'{self.name}\')'
def check_ssl_connect(host, port, verify=True):
ctx = ssl.create_default_context()
if not verify:
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
with socket.create_connection((host, port)) as sock:
with ctx.wrap_socket(sock) as ssock:
return True
except:
return False
def find_process_by_name(name):
for p in psutil.process_iter(attrs=['pid', 'name']):
if p.info['name'] == name:
return p
def find_section(proc, name):
for m in proc.memory_maps(grouped=False):
if m.path.lower().endswith(name.lower()):
return int(m.addr.split('-')[0], 16), m
def get_dll_exports(dll):
exports = subprocess.run(['winedump', '-j', 'export', dll], stdout=subprocess.PIPE).stdout
exports = str(exports, 'utf-8')
exports = exports.split('\n')
exports = exports[19:-3] # everything before this is just the pretext
exports = [Symbol(sym[0], sym[1], sym[2]) for sym in [e.strip().split() for e in exports]]
return exports
def find_dll_export(dll, export):
exports = get_dll_exports(dll)
for e in exports:
if e.name == export:
return e
if __name__ == '__main__':
# hide backtrace
if hide_backtrace:
sys.tracebacklimit = None
rclient = Process('RiotClientServices.exe', 'RiotClientServi')
lclient = Process('LeagueClient.exe')
lclientux = Process('LeagueClientUx.exe')
# Wait for RiotClientServices.exe
print(f'Waiting for {rclient}')
rclient.wait_for(tsleep=sleep_time, timeout=timeout)
print(f'Found {rclient}: pid {rclient.process.pid}')
base, module = find_section(rclient.process, 'ws2_32.dll')
exp_select = find_dll_export(module.path, 'select')
# Wait for LeagueClient.exe
print(f'Waiting for {lclient}')
lclient.wait_for(tsleep=sleep_time, timeout=timeout)
print(f'Found {lclient}: pid {lclient.process.pid}')
f = FridaWrapper(rclient.process.pid, position = base + exp_select.offset, signature = SELECT_SIGNATURE)
# Wait for LeagueClientUx.exe
print(f'Waiting for {lclientux}')
start = time.time()
while not lclientux.find():
if not f.attached():
print('Attaching...')
f.attach()
time.sleep(sleep_time)
if timeout and time.time() - start > timeout:
f.detach()
raise TimeoutException(f'Timeout while waiting for {lclientux}')
print(f'Found {lclientux}: pid {lclientux.process.pid}')
# Find app-port
port_xarg = next(x for x in lclientux.process.cmdline() if '--app-port=' in x)
port = port_xarg.split('=')[1]
# Wait for SSL response on app-port
print(f'Waiting for port {port}')
start = time.time()
while not check_ssl_connect('127.0.0.1', port, verify=False):
if not f.attached():
print('Attaching...')
f.attach()
time.sleep(sleep_time)
if timeout and time.time() - start > timeout:
f.detach()
raise TimeoutException(f'Timeout while waiting for SSL response')
if f.attached():
print('Detaching...')
f.detach()
else:
print('Nothing to do')
print('Done')