├── .gitignore ├── README.md ├── lib ├── frida-core-x86 │ └── frida-core.h ├── libcrypto │ └── libcrypto.a └── libssl │ └── libssl.a ├── makefile ├── src ├── frida.c ├── frida.h ├── main.c ├── ssl.c ├── ssl.h ├── util.c └── util.h └── utils └── upx /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.la 22 | libfrida-core.a 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | bin 55 | build 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LoL Injector 2 | 3 | A pre-launch executable to assist on playing League of Legends on Linux. 4 | 5 | **UPDATE: Since Vanguard was introduced in May 1, 2024, this tool has no use anymore.** 6 | 7 | # Motivation 8 | 9 | Since the end of 2020, League of Legends on Linux is being affected by a really 10 | annoying bug: the game simply does not launch. When the League client process 11 | (actually the LeagueClientUx process) starts, it waits for a SSL response on a 12 | port opened by the parent process. However, because of the issue, this parent 13 | process takes too long to open the port, resulting in the League client hitting 14 | a timeout and not launching. 15 | 16 | The community on the LeagueOfLinux subreddit has worked hard to workaround this, 17 | providing some ways to bypass the bug. The most used workaround is a [simple bash script](https://www.reddit.com/r/leagueoflinux/comments/j07yrg/starting_the_client_script/) that suspends the League client process until a SSL response is received 18 | on the port. This has the disavantage of a long wait until the game is launched, 19 | from 2 to 5 minutes. 20 | 21 | In the beginning of 2022, Reddit user [u/FakedCake](https://www.reddit.com/user/FakedCake) released a Python script that 22 | injects into the Riot Client process using Frida and, with the help of some DLL 23 | tinkering, effectively removes the timeout and reduces significantly the launch 24 | time to 20 to 50 seconds. The code for the script can be found on his [GitHub repo](https://github.com/CakeTheLiar/launchhelper). 25 | 26 | This repo contains the source code for an executable that does the same job as 27 | the Python script, but without the need for the Python runtime, which makes it 28 | more portable and easier for the players. 29 | 30 | # Usage 31 | 32 | You may download the latest version of the executable by [clicking here](https://github.com/Matheuschn/lol-injector/releases/latest/download/launchhelper). 33 | 34 | Before launching League of Legends, run the executable and let it run on the 35 | background while the game starts. After it injects and makes sure the game started 36 | correctly, it will close itself. 37 | 38 | If you don't want to input your password every time the program runs, run the 39 | command `sudo setcap cap_sys_ptrace=eip PATH`, where PATH is the path to the 40 | executable. 41 | 42 | If you are using Lutris, you may register the executable as a pre-launch script. 43 | Remember that you may need to set abi.vsyscall32=0, depending on how you installed 44 | the game. If you do, create a .sh file (for example, launch.sh) that sets vsyscall32 and then calls the executable. 45 | 46 | # Security 47 | 48 | The executable does not edit the League client files or its process memory, only 49 | the Riot Games Service launcher. It also detaches itself and removes any trace of 50 | injection before the League client is finished loading. 51 | 52 | Because of this, I'm pretty certain it is safe to do and would not be detected 53 | as "cheating" by the League of Legends Anti-Cheat. However, there is no guarantee 54 | of this, only my own speculation. So far, no problems have been reported. 55 | 56 | # Building 57 | 58 | To build on Linux, you will need the frida-core library available [here](https://github.com/frida/frida/releases/download/15.1.17/frida-core-devkit-15.1.17-linux-x86.tar.xz). After downloading it, save it in the lib/frida-core-x86 folder and extract it. 59 | 60 | With the library set up, you may run `make` command on the cloned repo to build the 61 | executable, which will be available on the bin/ folder. 62 | 63 | You may note the executable size is quite large. If you want to redistribute 64 | the executable, run the `make dist` command on the cloned repo. This will build 65 | the executable and run an executable packer to compress the binary, reducing 66 | massively its size. 67 | 68 | # Known Issues 69 | 70 | The executable can't run with M-Reimer's [wine-lol](https://github.com/M-Reimer/wine-lol), since it uses a patched libc. I'm searching for a way to fix this, but 71 | any help is appreciated. 72 | -------------------------------------------------------------------------------- /lib/libcrypto/libcrypto.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sofiaschn/lol-injector/28d74e6b9e1f9886faf3db157f3e82d7900469cc/lib/libcrypto/libcrypto.a -------------------------------------------------------------------------------- /lib/libssl/libssl.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sofiaschn/lol-injector/28d74e6b9e1f9886faf3db157f3e82d7900469cc/lib/libssl/libssl.a -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | BUILDDIR := build 2 | SOURCEDIR := src 3 | LIBDIR := lib 4 | UTILSDIR := utils 5 | BINDIR := bin 6 | 7 | CC := gcc 8 | LDFLAGS := $(addprefix -L./, $(wildcard $(LIBDIR)/*/)) -lfrida-core -lssl -lcrypto -ldl 9 | CFLAGS := -Wall -m32 -fuse-ld=gold -Wl,--icf=all,--gc-sections,-z,noexecstack \ 10 | -Os -s -ffast-math -pthread -ffunction-sections -fdata-sections \ 11 | -static-libgcc 12 | 13 | SRC := $(foreach x, $(SOURCEDIR), $(wildcard $(addprefix $(x)/*,.c*))) 14 | OBJ := $(addprefix $(BUILDDIR)/, $(addsuffix .o, $(notdir $(basename $(SRC))))) 15 | 16 | default: all 17 | 18 | launchhelper: $(OBJ) 19 | $(CC) $(CFLAGS) $(OBJ) -o $(BINDIR)/$@ $(LDFLAGS) 20 | 21 | $(BUILDDIR)/%.o: $(SOURCEDIR)/%.c 22 | $(CC) $(CFLAGS) -c -o $@ $< $(LDFLAGS) 23 | 24 | .PHONY: makedir 25 | makedir: 26 | mkdir -p $(BUILDDIR) $(BINDIR) 27 | 28 | .PHONY: all 29 | all: makedir launchhelper 30 | 31 | .PHONY: dist 32 | dist: all 33 | $(UTILSDIR)/upx -9 $(BINDIR)/launchhelper 34 | 35 | .PHONY: clean 36 | clean: 37 | rm -rf $(BUILDDIR) $(BINDIR) 38 | -------------------------------------------------------------------------------- /src/frida.c: -------------------------------------------------------------------------------- 1 | #include "../lib/frida-core-x86/frida-core.h" 2 | #include "util.h" 3 | #include 4 | 5 | typedef struct { 6 | FridaDeviceManager *manager; 7 | FridaDevice *device; 8 | FridaSession *session; 9 | FridaScript *script; 10 | } Frida; 11 | 12 | Frida *cleanFrida(Frida *frida) { 13 | if (frida->script) { 14 | frida_script_unload_sync(frida->script, NULL, NULL); 15 | frida_unref(frida->script); 16 | } 17 | if (frida->session) { 18 | frida_session_detach_sync(frida->session, NULL, NULL); 19 | frida_unref(frida->session); 20 | } 21 | if (frida->device) 22 | frida_unref(frida->device); 23 | 24 | if (frida->manager) { 25 | frida_device_manager_close_sync(frida->manager, NULL, NULL); 26 | frida_unref(frida->manager); 27 | } 28 | 29 | free(frida); 30 | 31 | return NULL; 32 | } 33 | 34 | Frida *attach(int RiotClientPID) { 35 | GError *error = NULL; 36 | Frida *frida = calloc(1, sizeof(Frida)); 37 | 38 | frida_init(); 39 | 40 | frida->manager = frida_device_manager_new(); 41 | FridaDeviceList *devices = 42 | frida_device_manager_enumerate_devices_sync(frida->manager, NULL, &error); 43 | if (error != NULL) { 44 | frida_unref(devices); 45 | return cleanFrida(frida); 46 | } 47 | 48 | frida->device = frida_device_list_get(devices, 0); 49 | if (frida->device == NULL) { 50 | frida_unref(devices); 51 | return cleanFrida(frida); 52 | } 53 | 54 | frida_unref(devices); 55 | 56 | FridaSessionOptions *sessionOptions = frida_session_options_new(); 57 | frida->session = frida_device_attach_sync(frida->device, RiotClientPID, 58 | sessionOptions, NULL, &error); 59 | frida_unref(sessionOptions); 60 | 61 | if (error != NULL) 62 | return cleanFrida(frida); 63 | 64 | DLLInfo *dllInfo = getProcessMemoryMap(RiotClientPID); 65 | if (dllInfo == NULL) 66 | return cleanFrida(frida); 67 | 68 | char *offset = getOffsetAddress(dllInfo->path, RiotClientPID); 69 | 70 | FridaScriptOptions *scriptOptions = frida_script_options_new(); 71 | frida_script_options_set_name(scriptOptions, "lol-injector"); 72 | frida_script_options_set_runtime(scriptOptions, FRIDA_SCRIPT_RUNTIME_V8); 73 | 74 | char *source = calloc(4096, sizeof(char)); 75 | strcat(source, "let pointer = ptr('0x"); 76 | strcat(source, dllInfo->memoryAddress); 77 | strcat(source, "');\n" 78 | "pointer = pointer.add(0x"); 79 | strcat(source, offset); 80 | strcat(source, ");\n" 81 | "Interceptor.attach(new NativeFunction(pointer, 'int', " 82 | "['int', 'pointer', 'pointer', 'pointer', 'pointer']), {\n" 83 | " onEnter(args) {\n" 84 | " args[4].writeInt(0x0);\n" 85 | " }\n" 86 | "});"); 87 | 88 | frida->script = frida_session_create_script_sync(frida->session, source, 89 | scriptOptions, NULL, &error); 90 | free(dllInfo); 91 | free(offset); 92 | free(source); 93 | frida_unref(scriptOptions); 94 | if (error != NULL) 95 | return cleanFrida(frida); 96 | 97 | frida_script_load_sync(frida->script, NULL, &error); 98 | if (error != NULL) 99 | return cleanFrida(frida); 100 | 101 | return frida; 102 | } 103 | -------------------------------------------------------------------------------- /src/frida.h: -------------------------------------------------------------------------------- 1 | #include "../lib/frida-core-x86/frida-core.h" 2 | 3 | typedef struct { 4 | FridaDeviceManager *manager; 5 | FridaDevice *device; 6 | FridaSession *session; 7 | FridaScript *script; 8 | } Frida; 9 | 10 | Frida *cleanFrida(Frida *frida); 11 | 12 | Frida *attach(int RiotClientPID); 13 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "frida.h" 2 | #include "ssl.h" 3 | #include "util.h" 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char **argv) { 9 | if (geteuid() != 0 && !hasptrace(argv[0])) { 10 | char command[7 + maxPathSize + 1] = "pkexec "; 11 | strcat(command, argv[0]); 12 | 13 | return system(command); 14 | } 15 | 16 | pid_t RiotClientPID = 0, LeagueClientPID = 0, LeagueClientUXPID = 0; 17 | 18 | printf("[*] Waiting for RiotClientServices.exe...\n"); 19 | while (!RiotClientPID) { 20 | RiotClientPID = getPID("RiotClientServices.exe", "RiotClientServi"); 21 | } 22 | printf("[*] Found process RiotClientServices.exe, PID: %d\n", RiotClientPID); 23 | 24 | printf("[*] Waiting for LeagueClient.exe...\n"); 25 | while (!LeagueClientPID) { 26 | // TODO: For some reason, calling getPID with the alt_name as 27 | // "LeagueClient.ex" breaks everything 28 | LeagueClientPID = getPID("LeagueClient.exe", NULL); 29 | } 30 | printf("[*] Found process LeagueClient.exe, PID: %d\n", LeagueClientPID); 31 | 32 | Frida *frida = NULL; 33 | printf("[*] Waiting for LeagueClientUx.exe...\n"); 34 | 35 | while (!LeagueClientUXPID) { 36 | if (frida == NULL) { 37 | printf("[*] Attaching...\n"); 38 | frida = attach(RiotClientPID); 39 | } 40 | 41 | LeagueClientUXPID = getPID("LeagueClientUx.exe", "LeagueClientUx."); 42 | } 43 | printf("[*] Found process LeagueClientUx.exe, PID: %d\n", LeagueClientUXPID); 44 | 45 | int appPort = strtoul(getAppPort(LeagueClientUXPID), NULL, 10); 46 | printf("[*] Waiting for port %d...\n", appPort); 47 | 48 | waitForPort("127.0.0.1", appPort); 49 | printf("[*] Connected!\n"); 50 | 51 | if (frida) { 52 | printf("[*] Detaching...\n"); 53 | cleanFrida(frida); 54 | } 55 | 56 | printf("[*] Done!\n"); 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /src/ssl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void initSSL() { 6 | SSL_load_error_strings(); 7 | SSL_library_init(); 8 | } 9 | 10 | int createSocket(char *hostname, int port) { 11 | int sd; 12 | struct sockaddr_in addr; 13 | 14 | addr.sin_family = AF_INET; 15 | addr.sin_port = htons(port); 16 | addr.sin_addr.s_addr = inet_addr(hostname); 17 | 18 | sd = socket(AF_INET, SOCK_STREAM, 0); 19 | 20 | int connection; 21 | do { 22 | connection = connect(sd, (struct sockaddr *)&addr, sizeof(addr)); 23 | } while (connection != 0); 24 | 25 | return sd; 26 | } 27 | 28 | void connectSSL(int socket) { 29 | const SSL_METHOD *method = TLS_client_method(); 30 | SSL_CTX *ctx = SSL_CTX_new(method); 31 | 32 | SSL *ssl = SSL_new(ctx); 33 | SSL_set_fd(ssl, socket); 34 | 35 | int connection; 36 | do { 37 | connection = SSL_connect(ssl); 38 | } while (connection != 1); 39 | 40 | SSL_free(ssl); 41 | SSL_CTX_free(ctx); 42 | close(socket); 43 | } 44 | 45 | void waitForPort(char *hostname, int port) { 46 | initSSL(); 47 | 48 | int socket = createSocket(hostname, port); 49 | connectSSL(socket); 50 | } 51 | -------------------------------------------------------------------------------- /src/ssl.h: -------------------------------------------------------------------------------- 1 | void waitForPort(char *hostname, int port); 2 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define maxPathSize 4096 7 | #define maxPipeSize 65336 8 | #define maxFileNameSize 255 9 | 10 | typedef struct { 11 | char *memoryAddress; 12 | char *path; 13 | } DLLInfo; 14 | 15 | char *getWine(pid_t pid) { 16 | char path[64] = "/proc/"; 17 | sprintf(path + strlen(path), "%d", pid); 18 | strcat(path, "/exe"); 19 | 20 | char buf[maxPathSize + 1]; 21 | int len = readlink(path, buf, maxPathSize); 22 | buf[len] = '\0'; 23 | 24 | // 4096 bytes for MAX_PATH + 1 byte for \0 25 | char *wineEnv = calloc(4096 + 1, sizeof(char)); 26 | int pathSize = strrchr(buf, '/') - buf + 1; 27 | memcpy(wineEnv, buf, pathSize); 28 | memcpy(wineEnv + pathSize, "wine", 5); 29 | 30 | return wineEnv; 31 | } 32 | 33 | char *getWineDump(pid_t pid) { 34 | char *wine = getWine(pid); 35 | 36 | return strcat(wine, "dump"); 37 | } 38 | 39 | char *getOffsetAddress(char *path, pid_t pid) { 40 | char command[maxPathSize * 2 + 32]; 41 | char *winedumpBin = getWineDump(pid); 42 | 43 | strcpy(command, winedumpBin); 44 | free(winedumpBin); 45 | 46 | strcat(command, " -j export "); 47 | strcat(command, path); 48 | strcat(command, " | grep select"); 49 | 50 | FILE *stream = popen(command, "r"); 51 | 52 | char buf[maxPipeSize + 1]; 53 | fgets(buf, maxPipeSize, stream); 54 | pclose(stream); 55 | 56 | // 8 bytes for 64 bit address + 1 byte for \0 57 | char *offset = calloc(8 + 1, sizeof(char)); 58 | strcpy(offset, strtok(buf, " ")); 59 | 60 | return offset; 61 | } 62 | 63 | DLLInfo *getProcessMemoryMap(pid_t pid) { 64 | char command[64] = "cat /proc/"; 65 | sprintf(command + strlen(command), "%d", pid); 66 | strcat(command, "/maps | grep ws2_32.dll"); 67 | 68 | FILE *stream = popen(command, "r"); 69 | 70 | char buf[maxPipeSize + 1]; 71 | fgets(buf, maxPipeSize, stream); 72 | pclose(stream); 73 | 74 | DLLInfo *info = NULL; 75 | 76 | if (strstr(buf, "ws2_32.dll") != NULL) { 77 | char *firstLine = strtok(buf, "\n"); 78 | 79 | info = malloc(sizeof(DLLInfo)); 80 | 81 | // 8 bytes for 64bits address + 1 byte for \0 82 | char *addr = calloc(8 + 1, sizeof(char)); 83 | int len = strchr(firstLine, '-') - firstLine; 84 | memcpy(addr, firstLine, len); 85 | addr[len] = '\0'; 86 | 87 | // 4096 bytes for MAX_PATH + 1 byte for \0 88 | char *path = calloc(4096 + 1, sizeof(char)); 89 | strcpy(path, strchr(firstLine, '/')); 90 | 91 | info->memoryAddress = addr; 92 | info->path = path; 93 | } 94 | 95 | return info; 96 | } 97 | 98 | char *getAppPort(pid_t pid) { 99 | char command[64] = "cat /proc/"; 100 | sprintf(command + strlen(command), "%d", pid); 101 | strcat(command, "/cmdline | sed -e \"s/\\x00/ /g\""); 102 | 103 | FILE *stream = popen(command, "r"); 104 | 105 | char buf[maxPipeSize + 1]; 106 | fgets(buf, maxPipeSize, stream); 107 | pclose(stream); 108 | 109 | char *found = strstr(buf, "--app-port="); 110 | 111 | if (found != NULL) { 112 | // 11 bytes for the string "--app-port=, 5 bytes for max TCP port number, 1 113 | // byte for \0" 114 | char *appPort = calloc(11 + 5 + 1, sizeof(char)); 115 | strcpy(appPort, strtok(found, " ") + 11); 116 | 117 | return appPort; 118 | } else { 119 | return NULL; 120 | } 121 | } 122 | 123 | pid_t getPID(char *process_name, char *alt_name) { 124 | // 9 bytes for the command, 255 bytes for file name and 1 byte for \0 125 | char buf[9 + maxFileNameSize + 1] = "pidof -s "; 126 | strcat(buf, process_name); 127 | 128 | FILE *cmd_pipe = popen(buf, "r"); 129 | 130 | fgets(buf, 265, cmd_pipe); 131 | pid_t pid = strtoul(buf, NULL, 10); 132 | 133 | pclose(cmd_pipe); 134 | 135 | if (pid == 0 && alt_name != NULL) { 136 | pid = getPID(alt_name, NULL); 137 | } 138 | 139 | return pid; 140 | } 141 | 142 | int hasptrace(char *path) { 143 | char command[7 + maxPathSize + 1] = "getcap "; 144 | strcat(command, path); 145 | 146 | FILE *cmd_pipe = popen(command, "r"); 147 | 148 | char buf[maxPipeSize + 1]; 149 | fgets(buf, maxPipeSize, cmd_pipe); 150 | pclose(cmd_pipe); 151 | 152 | char *found = strstr(buf, "cap_sys_ptrace=eip"); 153 | 154 | return found != NULL ? 1 : 0; 155 | } 156 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define maxPathSize 4096 4 | #define maxPipeSize 65336 5 | #define maxFileNameSize 255 6 | 7 | typedef struct { 8 | char *memoryAddress; 9 | char *path; 10 | } DLLInfo; 11 | 12 | char *getOffsetAddress(char *path, pid_t pid); 13 | 14 | char *getWine(pid_t pid); 15 | 16 | char *getWineDump(pid_t pid); 17 | 18 | DLLInfo *getProcessMemoryMap(pid_t pid); 19 | 20 | char *getAppPort(pid_t pid); 21 | 22 | pid_t getPID(char *process_name, char *alt_name); 23 | 24 | int hasptrace(char *path); 25 | -------------------------------------------------------------------------------- /utils/upx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sofiaschn/lol-injector/28d74e6b9e1f9886faf3db157f3e82d7900469cc/utils/upx --------------------------------------------------------------------------------