diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a04f9c2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,68 @@ +name: Build RAVEN C2 Framework + +on: + push: + branches: ["dev"] + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set Up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: maven + + - name: Clear Maven JavaFX Cache + run: rm -rf ~/.m2/repository/org/openjfx + + - name: Build With Maven + working-directory: RAVEN + run: mvn clean package -DskipTests -Plinux + + - name: Copy JAR To Output Folder + run: | + JAR=$(ls RAVEN/target/*.jar | grep -v original | head -1) + mv "$JAR" RAVEN/raven.jar + rm -rf RAVEN/target + echo "JAR copied: $JAR" + + - name: Commit Output To Repo + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git add RAVEN/raven.jar + git diff --cached --quiet || git commit -m "ci: build #${{ github.run_number }} [skip ci]" + git push + + - name: Upload JAR As Artifact + uses: actions/upload-artifact@v4 + with: + name: raven-jar + path: RAVEN/raven.jar + retention-days: 30 + + - name: Create Tag + run: | + git tag "RAVEN-C2-V3.0-${{ github.run_number }}" + git push origin "RAVEN-C2-V3.0-${{ github.run_number }}" + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v3.0-build-${{ github.run_number }} + name: "RAVEN C2 v3.0 - Build #${{ github.run_number }}" + files: output/raven.jar + generate_release_notes: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/RAVEN/config/server/raven.properties b/RAVEN/config/server/raven.properties index 6f58c89..32cf058 100644 --- a/RAVEN/config/server/raven.properties +++ b/RAVEN/config/server/raven.properties @@ -42,13 +42,13 @@ agent.command.timeout=120000 agent.max.connections=100 agent.buffer.size=8192 -logging.level=INFO -logging.verbose=false -logging.max.entries=1000 +logging.level=VERBOSE +logging.verbose=true +logging.max.entries=10000 logging.file=logs/raven.log -logging.file.enabled=false +logging.file.enabled=true -db.type=none +db.type=sqlite db.path=database db.url= db.name=raven diff --git a/RAVEN/src/main/java/com/raven/Start.java b/RAVEN/src/main/java/com/raven/Start.java index 994ae7f..6487041 100644 --- a/RAVEN/src/main/java/com/raven/Start.java +++ b/RAVEN/src/main/java/com/raven/Start.java @@ -82,13 +82,13 @@ private static ListenerMode ResolveMode(List Args) { } private static String ResolveInterface(List Args) { - if (Has(Args, "-TC")) return "teamclient"; - if (Has(Args, "-TSC")) return "teamserver-cli"; - if (Has(Args, "-TSW")) return "teamserver-web"; - if (Has(Args, "-TSG")) return "teamserver-gui"; - if (Has(Args, "-C", "-cli")) return "cli"; - if (Has(Args, "-G", "-gui")) return "gui"; - if (Has(Args, "-W", "-web")) return "web"; + if (Has(Args, "-TS", "-teamserver")) return "teamserver"; + if (Has(Args, "-TSC", "-teamserver-cli")) return "teamserver-cli"; + if (Has(Args, "-TSW", "-teamserver-web")) return "teamserver-web"; + if (Has(Args, "-TSG", "-teamserver-gui")) return "teamserver-gui"; + if (Has(Args, "-C", "-cli")) return "cli"; + if (Has(Args, "-G", "-gui")) return "gui"; + if (Has(Args, "-W", "-web")) return "web"; return Config.GetInterfaceMode(); } @@ -97,20 +97,30 @@ private static void StartInterface(String Host, int Port, ListenerMode Mode, Str switch (Interface) { case "cli" -> new CLI(Config).Run(Host, Port, Mode); case "gui" -> GUI.Launch(Config); - case "teamserver-cli" -> new CLI(Config).RunTeamServer(Host, Port, Mode); - case "teamserver-gui" -> GUI.LaunchTeam(Config); - case "teamclient" -> { + case "teamserver" -> { + int TeamPort = ParseInt(Arg(Args, "-tp", "-tport", String.valueOf(Config.GetTeamServerPort())), Config.GetTeamServerPort()); + new TeamServer(Config, Mode).RunAsBackend(Host, Port, Config.GetWebHost(), TeamPort); + Thread.currentThread().join(); + } + case "teamserver-cli" -> { String TeamHost = Arg(Args, "-ts", "-thost", "127.0.0.1"); int TeamPort = ParseInt(Arg(Args, "-tp", "-tport", String.valueOf(Config.GetTeamServerPort())), Config.GetTeamServerPort()); new com.raven.interfaces.TeamClient(Config, TeamHost, TeamPort).Run(); } + case "teamserver-gui" -> GUI.LaunchTeam(Config); case "teamserver-web" -> { int TeamPort = ParseInt(Arg(Args, "-tp", "-tport", String.valueOf(Config.GetTeamServerPort())), Config.GetTeamServerPort()); new TeamServer(Config, Mode).Run(Config.GetWebHost(), TeamPort); Thread.currentThread().join(); } + case "web" -> { + int WebPort = ParseInt(Arg(Args, "-wp", "-web-port", String.valueOf(Config.GetWebPort())), Config.GetWebPort()); + new WebApp(Config, Mode).Run(Config.GetWebHost(), WebPort); + Thread.currentThread().join(); + } default -> { - new WebApp(Config, Mode).Run(Config.GetWebHost(), Config.GetWebPort()); + int WebPort = ParseInt(Arg(Args, "-wp", "-web-port", String.valueOf(Config.GetWebPort())), Config.GetWebPort()); + new WebApp(Config, Mode).Run(Config.GetWebHost(), WebPort); Thread.currentThread().join(); } } diff --git a/RAVEN/src/main/java/com/raven/core/command/CommandDispatcher.java b/RAVEN/src/main/java/com/raven/core/command/CommandDispatcher.java index 147e75e..fc0f73d 100644 --- a/RAVEN/src/main/java/com/raven/core/command/CommandDispatcher.java +++ b/RAVEN/src/main/java/com/raven/core/command/CommandDispatcher.java @@ -11,6 +11,7 @@ import com.raven.interfaces.CLI.module.log.LogManager; import com.raven.utils.ServerConfig; import com.raven.utils.TerminalHelper; +import com.raven.utils.SystemHelper; import java.util.ArrayList; import java.util.List; @@ -62,7 +63,7 @@ public DispatchResult Dispatch(String Command, String[] Parts) { } case "help" -> OperatorCommands.ShowHelp(); case "clean" -> { - TerminalHelper.Clear(); + SystemHelper.ClearScreen(); return DispatchResult.UpdateLastCount; } case "sessions", "agents" -> SessionCommands.ShowSessions(); diff --git a/RAVEN/src/main/java/com/raven/core/command/ExportCommand.java b/RAVEN/src/main/java/com/raven/core/command/ExportCommand.java index b0cdaf7..be5b690 100644 --- a/RAVEN/src/main/java/com/raven/core/command/ExportCommand.java +++ b/RAVEN/src/main/java/com/raven/core/command/ExportCommand.java @@ -18,7 +18,6 @@ public final class ExportCommand { - private static final DateTimeFormatter FileFmt = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); private static final DateTimeFormatter LogFmt = com.raven.utils.RavenConstants.TimestampFmt; private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static final Path OUT_DIR = Paths.get("exports"); @@ -138,7 +137,7 @@ private void Write(String Name, Format F, List> Data) { } private void WriteRaw(String Name, String Ext, String Content) { - String Filename = Name + "_" + LocalDateTime.now().format(FileFmt) + "." + Ext; + String Filename = Name + "_" + LocalDateTime.now().format(com.raven.utils.RavenConstants.FilenameFmt) + "." + Ext; Path File = OUT_DIR.resolve(Filename); try { Files.writeString(File, Content); diff --git a/RAVEN/src/main/java/com/raven/core/server/BaseServer.java b/RAVEN/src/main/java/com/raven/core/server/BaseServer.java index da011f4..e59c5fc 100644 --- a/RAVEN/src/main/java/com/raven/core/server/BaseServer.java +++ b/RAVEN/src/main/java/com/raven/core/server/BaseServer.java @@ -31,8 +31,8 @@ public abstract class BaseServer { - protected static final byte[] EndMarker = "".getBytes(); - protected static final byte[] MetaMarker = "".getBytes(); + protected static final byte[] EndMarker = "".getBytes(java.nio.charset.StandardCharsets.UTF_8); + protected static final byte[] MetaMarker = "".getBytes(java.nio.charset.StandardCharsets.UTF_8); protected static final Gson Gson = new Gson(); protected static final int MinFrameBytes = 12 + 16; diff --git a/RAVEN/src/main/java/com/raven/interfaces/CLI/CLI.java b/RAVEN/src/main/java/com/raven/interfaces/CLI/CLI.java index 04357e7..0ca14d3 100644 --- a/RAVEN/src/main/java/com/raven/interfaces/CLI/CLI.java +++ b/RAVEN/src/main/java/com/raven/interfaces/CLI/CLI.java @@ -1,5 +1,7 @@ package com.raven.interfaces.CLI; +import com.raven.utils.Helper; + import com.raven.core.command.AgentCommandDispatcher; import com.raven.core.command.AgentCommandDispatcher.CommandResult; import com.raven.core.command.CommandRegistry; @@ -242,7 +244,7 @@ private int Dispatch(String Cmd, String[] P) { } case "help" -> ShowHelp(); case "clean" -> { - TerminalHelper.Clear(); + SystemHelper.ClearScreen(); return 1; } case "status" -> { @@ -272,7 +274,7 @@ private int Dispatch(String Cmd, String[] P) { } case "webstart" -> { String WebHost = P.length > 1 ? P[1] : Config.GetWebHost(); - int WebPort = P.length > 2 ? ParseIntSafe(P[2], Config.GetWebPort()) : Config.GetWebPort(); + int WebPort = P.length > 2 ? Helper.ParseInt(P[2], Config.GetWebPort()) : Config.GetWebPort(); WebPanelManager.Start(WebHost, WebPort, Server, ServerStartTime); } case "webstop" -> WebPanelManager.Stop(); @@ -426,12 +428,12 @@ private int Dispatch(String Cmd, String[] P) { } } case "history" -> { - int AgentId = P.length > 1 ? ParseIntSafe(P[1], 0) : 0; - int Limit = P.length > 2 ? ParseIntSafe(P[2], 50) : 50; + int AgentId = P.length > 1 ? Helper.ParseInt(P[1], 0) : 0; + int Limit = P.length > 2 ? Helper.ParseInt(P[2], 50) : 50; ShowCommandHistory(AgentId, Limit); } case "sessions-history", "sesshistory" -> { - int Limit = P.length > 1 ? ParseIntSafe(P[1], 50) : 50; + int Limit = P.length > 1 ? Helper.ParseInt(P[1], 50) : 50; List> Sessions = Db.GetSessionHistory(Limit); System.out.println(TerminalHelper.Box("SESSION HISTORY (last " + Limit + ")")); System.out.println(); @@ -656,18 +658,9 @@ private int Dispatch(String Cmd, String[] P) { } String SaveName = P[1]; String SaveDesc = P.length > 2 ? BuildArgs(P, 2) : ""; - Map CurrentSettings = new java.util.LinkedHashMap<>(); - CurrentSettings.put("operator.name", OperatorName != null ? OperatorName : ""); - CurrentSettings.put("operator.role", OperatorRole != null ? OperatorRole.name() : "MEMBER"); - CurrentSettings.put("operator.theme", "dark"); - CurrentSettings.put("operator.output.box", "true"); - CurrentSettings.put("operator.output.timestamp", "true"); - CurrentSettings.put("operator.session.log.limit", "100"); - CurrentSettings.put("operator.history.limit", "50"); - CurrentSettings.put("operator.auto.reconnect", "true"); - CurrentSettings.put("operator.chat.notify", "true"); - CurrentSettings.put("operator.broadcast.confirm", "true"); - CurrentSettings.put("operator.selfdestruct.confirm", "true"); + Map CurrentSettings = new java.util.LinkedHashMap<>(OperatorCfg.ToMap()); + if (OperatorName != null) CurrentSettings.put("operator.name", OperatorName); + if (OperatorRole != null) CurrentSettings.put("operator.role", OperatorRole.name()); if (ProfileManager.Save(SaveName, CurrentSettings, SaveDesc)) Logger.Custom(" %s✔ profile saved: %s%s%n%n", AnsiColor.Green, SaveName, AnsiColor.Reset); else Logger.Error("failed to save profile: " + SaveName); } @@ -854,7 +847,7 @@ private void Interactive(int Id) { String Input = Reader.readLine(); if (Input == null || Input.equalsIgnoreCase("back") || Input.equalsIgnoreCase("exit")) break; if (Input.isBlank()) continue; - if (Input.trim().equalsIgnoreCase("clean")) { TerminalHelper.Clear(); continue; } + if (Input.trim().equalsIgnoreCase("clean")) { SystemHelper.ClearScreen(); continue; } Execute(Id, Input); } catch (IOException Ex) { break; @@ -942,12 +935,12 @@ private void ShowChat() { 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(); + String From = M.getOrDefault("From", "?").toString(); + String To = M.getOrDefault("To", "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); + 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(); } @@ -962,12 +955,12 @@ private void ShowChatHistory(int Limit) { 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(); + String From = M.getOrDefault("From", "?").toString(); + String To = M.getOrDefault("To", "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); + 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(); } diff --git a/RAVEN/src/main/java/com/raven/interfaces/TeamClient.java b/RAVEN/src/main/java/com/raven/interfaces/TeamClient.java index 032da3c..a11f6cd 100644 --- a/RAVEN/src/main/java/com/raven/interfaces/TeamClient.java +++ b/RAVEN/src/main/java/com/raven/interfaces/TeamClient.java @@ -9,6 +9,7 @@ import com.raven.utils.AnsiColor; import com.raven.utils.ServerConfig; import com.raven.utils.TerminalHelper; +import com.raven.utils.SystemHelper; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -39,6 +40,7 @@ public final class TeamClient { private String OperatorName; private OperatorRole OperatorRoleValue; private volatile boolean Running = true; + private BufferedReader ConsoleReader; public TeamClient(ServerConfig Config, String TsHost, int TsPort) { this.Config = Config; @@ -50,7 +52,7 @@ public TeamClient(ServerConfig Config, String TsHost, int TsPort) { public void Run() { if (!Login()) return; - BufferedReader Reader = new BufferedReader(new InputStreamReader(System.in)); + ConsoleReader = new BufferedReader(new InputStreamReader(System.in)); while (Running) { try { System.out.println(); @@ -58,7 +60,7 @@ public void Run() { System.out.print(PROMPT_BOTTOM); System.out.flush(); PromptManager.MarkVisible(true); - String Input = Reader.readLine(); + String Input = ConsoleReader.readLine(); PromptManager.MarkVisible(false); if (Input == null) break; Input = Input.trim(); @@ -83,7 +85,7 @@ private void Dispatch(String Cmd, String[] P, String Line) { } catch (Exception Ign) {} } case "help" -> ShowHelp(); - case "clean" -> TerminalHelper.Clear(); + case "clean" -> SystemHelper.ClearScreen(); case "sessions", "agents" -> ShowSessions(); case "status" -> ShowStatus(); case "logs" -> ShowLogs(); @@ -302,6 +304,191 @@ private void Dispatch(String Cmd, String[] P, String Line) { int Lim = P.length > 1 ? ParseIntSafe(P[1], 50) : 50; ShowSessionHistory(Lim); } + case "use" -> { + if (P.length < 2) { Logger.Warn("usage: use "); break; } + try { InteractiveSession(ParseInt(P[1])); } + catch (NumberFormatException Ex) { Logger.Warn("invalid session ID"); } + } + case "id" -> { if (P.length < 2) Logger.Warn("usage: id "); else SimpleExec(P, "id"); } + case "hostname" -> { if (P.length < 2) Logger.Warn("usage: hostname "); else SimpleExec(P, "hostname"); } + case "uname" -> { if (P.length < 2) Logger.Warn("usage: uname "); else SimpleExec(P, "uname"); } + case "arp" -> { if (P.length < 2) Logger.Warn("usage: arp "); else SimpleExec(P, "arp"); } + case "route" -> { if (P.length < 2) Logger.Warn("usage: route "); else SimpleExec(P, "route"); } + case "users" -> { if (P.length < 2) Logger.Warn("usage: users "); else SimpleExec(P, "users"); } + case "groups" -> { if (P.length < 2) Logger.Warn("usage: groups "); else SimpleExec(P, "groups"); } + case "services" -> { if (P.length < 2) Logger.Warn("usage: services "); else SimpleExec(P, "services"); } + case "privcheck" -> { if (P.length < 2) Logger.Warn("usage: privcheck "); else SimpleExec(P, "privcheck"); } + case "antivirus" -> { if (P.length < 2) Logger.Warn("usage: antivirus "); else SimpleExec(P, "antivirus"); } + case "crontab" -> { if (P.length < 2) Logger.Warn("usage: crontab "); else SimpleExec(P, "crontab"); } + case "clipboard" -> { if (P.length < 2) Logger.Warn("usage: clipboard "); else SimpleExec(P, "clipboard"); } + case "hashdump" -> { if (P.length < 2) Logger.Warn("usage: hashdump "); else SimpleExec(P, "hashdump"); } + case "wifidump" -> { if (P.length < 2) Logger.Warn("usage: wifidump "); else SimpleExec(P, "wifidump"); } + case "dumpbrowsers" -> { if (P.length < 2) Logger.Warn("usage: dumpbrowsers "); else SimpleExec(P, "dumpbrowsers"); } + case "lastlog" -> { if (P.length < 2) Logger.Warn("usage: lastlog "); else SimpleExec(P, "lastlog"); } + case "jitter" -> { + if (P.length < 3) { Logger.Warn("usage: jitter "); break; } + SimpleExec(new String[]{P[0], P[1]}, "jitter " + P[2]); + } + case "keystroke" -> { + if (P.length < 3) { Logger.Warn("usage: keystroke "); break; } + SimpleExec(new String[]{P[0], P[1]}, "keystroke " + P[2]); + } + case "searchfiles" -> { + if (P.length < 3) { Logger.Warn("usage: searchfiles "); break; } + SimpleExec(new String[]{P[0], P[1]}, "searchfiles " + P[2]); + } + case "osquery" -> { + if (P.length < 3) { Logger.Warn("usage: osquery "); break; } + SimpleExec(new String[]{P[0], P[1]}, "osquery " + P[2]); + } + case "head" -> { + if (P.length < 3) { Logger.Warn("usage: head [n]"); break; } + SimpleExec(new String[]{P[0], P[1]}, "head " + P[2]); + } + case "tail" -> { + if (P.length < 3) { Logger.Warn("usage: tail [n]"); break; } + SimpleExec(new String[]{P[0], P[1]}, "tail " + P[2]); + } + case "rm" -> { + if (P.length < 3) { Logger.Warn("usage: rm "); break; } + SimpleExec(new String[]{P[0], P[1]}, "rm " + P[2]); + } + case "mkdir" -> { + if (P.length < 3) { Logger.Warn("usage: mkdir "); break; } + SimpleExec(new String[]{P[0], P[1]}, "mkdir " + P[2]); + } + case "cp" -> { + if (P.length < 3) { Logger.Warn("usage: cp "); break; } + SimpleExec(new String[]{P[0], P[1]}, "cp " + P[2]); + } + case "mv" -> { + if (P.length < 3) { Logger.Warn("usage: mv "); break; } + SimpleExec(new String[]{P[0], P[1]}, "mv " + P[2]); + } + case "chmod" -> { + if (P.length < 3) { Logger.Warn("usage: chmod "); break; } + SimpleExec(new String[]{P[0], P[1]}, "chmod " + P[2]); + } + case "find" -> { + if (P.length < 3) { Logger.Warn("usage: find [name]"); break; } + SimpleExec(new String[]{P[0], P[1]}, "find " + P[2]); + } + case "grep" -> { + if (P.length < 3) { Logger.Warn("usage: grep "); break; } + SimpleExec(new String[]{P[0], P[1]}, "grep " + P[2]); + } + case "hash" -> { + if (P.length < 3) { Logger.Warn("usage: hash [sha256|md5]"); break; } + SimpleExec(new String[]{P[0], P[1]}, "hash " + P[2]); + } + case "cd" -> { + if (P.length < 3) { Logger.Warn("usage: cd "); break; } + SimpleExec(new String[]{P[0], P[1]}, "cd " + P[2]); + } + case "stats" -> { + try { + Map R = Get("/api/agents"); + @SuppressWarnings("unchecked") + List> Agents = (List>) R.getOrDefault("Agents", new ArrayList<>()); + System.out.println(TerminalHelper.Box("SESSION STATS")); + System.out.println(); + long Raw = Agents.stream().filter(A -> "ReverseShell".equals(A.getOrDefault("Type", ""))).count(); + long Raven = Agents.stream().filter(A -> "RAVEN".equals(A.getOrDefault("Type", ""))).count(); + long Http = Agents.stream().filter(A -> "HttpBeacon".equals(A.getOrDefault("Type", ""))).count(); + Logger.Custom(" %sTotal Active %s%d%n", AnsiColor.Red, AnsiColor.White, Agents.size()); + if (Raw > 0) Logger.Custom(" %sRaw Shell %s%d%n", AnsiColor.Red, AnsiColor.White, Raw); + if (Raven > 0) Logger.Custom(" %sRAVEN Agent %s%d%n", AnsiColor.Red, AnsiColor.White, Raven); + if (Http > 0) Logger.Custom(" %sHTTP Beacon %s%d%n", AnsiColor.Red, AnsiColor.White, Http); + System.out.println(); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } + case "tasks" -> { + try { + Map R = Get("/api/tasks"); + @SuppressWarnings("unchecked") + List> Tasks = (List>) R.getOrDefault("Tasks", new ArrayList<>()); + System.out.println(TerminalHelper.Box("PENDING TASKS (" + Tasks.size() + ")")); + System.out.println(); + if (Tasks.isEmpty()) { Logger.Info(INDENT + "no pending tasks"); System.out.println(); break; } + for (Map T : Tasks) + Logger.Custom(" [%s] session-%s %s%n", T.getOrDefault("Queued", "?"), T.getOrDefault("AgentId", "?"), T.getOrDefault("Command", "?")); + System.out.println(); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } + case "addopt" -> { + String[] AddParts = Line.split("\\s+"); + if (AddParts.length < 3) { Logger.Warn("usage: addopt [ROLE]"); break; } + String AddUser = AddParts[1]; + String AddPass = AddParts[2]; + String AddRole = AddParts.length > 3 ? AddParts[3].toUpperCase() : "OPERATOR"; + try { + Map Body = new LinkedHashMap<>(); + Body.put("Username", AddUser); Body.put("Password", AddPass); + Body.put("Role", AddRole); Body.put("Operator", OperatorName); + Map R = Post("/api/team/operators/create", Body); + if (R.containsKey("Error")) Logger.Error(R.get("Error").toString()); + else Logger.Ok("operator created: " + P[1] + " [" + AddRole + "]"); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } + case "delopt" -> { + if (P.length < 2) { Logger.Warn("usage: delopt "); break; } + try { + Map R = Post("/api/team/operators/delete", Map.of("Username", P[1], "Operator", OperatorName)); + if (R.containsKey("Error")) Logger.Error(R.get("Error").toString()); + else Logger.Ok("operator deleted: " + P[1]); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } + case "setrole" -> { + if (P.length < 3) { Logger.Warn("usage: setrole "); break; } + try { + Map Body = Map.of("Username", P[1], "Role", P[2].toUpperCase(), "Operator", OperatorName); + Map R = Post("/api/team/operators/role", Body); + if (R.containsKey("Error")) Logger.Error(R.get("Error").toString()); + else Logger.Ok("role updated: " + P[1] + " → " + P[2].toUpperCase()); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } + case "passwd" -> { + if (P.length < 3) { Logger.Warn("usage: passwd "); break; } + try { + Map Body = Map.of("Username", P[1], "Password", P[2], "Operator", OperatorName); + Map R = Post("/api/team/operators/password", Body); + if (R.containsKey("Error")) Logger.Error(R.get("Error").toString()); + else Logger.Ok("password changed: " + P[1]); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } + case "kick" -> { + if (P.length < 2) { Logger.Warn("usage: kick "); break; } + try { + Map R = Post("/api/team/operators/kick", Map.of("Username", P[1], "Operator", OperatorName)); + if (R.containsKey("Error")) Logger.Error(R.get("Error").toString()); + else Logger.Ok("operator kicked: " + P[1]); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } + case "webstart" -> { + String WHost = P.length > 1 ? P[1] : "0.0.0.0"; + int WPort = P.length > 2 ? ParseIntSafe(P[2], 5000) : 5000; + try { + Map R = Post("/api/server/webpanel/start", + Map.of("Host", WHost, "Port", WPort, "Operator", OperatorName)); + if (R.containsKey("Error")) Logger.Error(R.get("Error").toString()); + else Logger.Ok("web panel started → " + R.getOrDefault("URL", "")); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } + case "webstop" -> { + try { + Map R = Post("/api/server/webpanel/stop", Map.of("Operator", OperatorName)); + if (R.containsKey("Error")) Logger.Error(R.get("Error").toString()); + else Logger.Ok("web panel stopped"); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } + case "webstatus" -> { + try { + Map R = Get("/api/server/webpanel/status"); + String Running = Boolean.parseBoolean(R.getOrDefault("Running", "false").toString()) ? "running" : "stopped"; + Logger.Custom(" %sWeb panel %s%s%n", AnsiColor.Red, AnsiColor.White, Running); + if (R.containsKey("URL")) Logger.Custom(" %sURL %s%s%n", AnsiColor.Red, AnsiColor.White, R.get("URL")); + } catch (Exception Ex) { Logger.Error(Ex.getMessage()); } + } default -> { Logger.Error("unknown command: " + Cmd); Logger.Info("type 'help' for available commands"); @@ -309,6 +496,34 @@ private void Dispatch(String Cmd, String[] P, String Line) { } } + private void InteractiveSession(int AgentId) { + String OldPrompt = PROMPT_BOTTOM; + String SessionPrompt = AnsiColor.Red + "[session-" + AgentId + "] " + AnsiColor.White + ">> " + AnsiColor.Reset; + PromptManager.SetPrompt(SessionPrompt); + Logger.Custom(INDENT + "%sInteractive shell — session-%d%s (type 'back' to return)%n%n", + AnsiColor.White, AgentId, AnsiColor.Reset); + while (Running) { + try { + System.out.print(SessionPrompt); + System.out.flush(); + PromptManager.MarkVisible(true); + String Input = ConsoleReader.readLine(); + PromptManager.MarkVisible(false); + if (Input == null) break; + Input = Input.trim(); + if (Input.isBlank()) continue; + if (Input.equalsIgnoreCase("back") || Input.equalsIgnoreCase("exit")) break; + if (Input.equalsIgnoreCase("clean")) { com.raven.utils.SystemHelper.ClearScreen(); continue; } + Exec(AgentId, Input); + } catch (Exception Ex) { + Logger.Error(Ex.getMessage()); + break; + } + } + PromptManager.SetPrompt(OldPrompt); + Logger.Custom(INDENT + "%s← returned to TeamClient%s%n%n", AnsiColor.Green, AnsiColor.Reset); + } + private boolean Login() { System.out.println(); System.out.println(TerminalHelper.Box("TEAMCLIENT — CONNECT TO TEAMSERVER")); @@ -455,7 +670,7 @@ private void ShowLogs() { private void ShowChat() { try { - Map R = Post("/api/chat/history", Map.of("Limit", 50)); + Map R = Get("/api/team/chat/messages"); @SuppressWarnings("unchecked") List> Msgs = (List>) R.getOrDefault("Messages", new ArrayList<>()); System.out.println(TerminalHelper.Box("CHAT MESSAGES")); @@ -479,9 +694,9 @@ private void ShowChat() { private void ShowChatHistory(int Limit) { try { - Map R = Post("/api/chat/history", Map.of("Limit", Limit)); + Map R = Post("/api/team/chat/logs", Map.of("Limit", Limit)); @SuppressWarnings("unchecked") - List> Messages = (List>) R.getOrDefault("Messages", new ArrayList<>()); + List> Messages = (List>) R.getOrDefault("Logs", new ArrayList<>()); System.out.println(TerminalHelper.Box("CHAT HISTORY (last " + Limit + ")")); System.out.println(); if (Messages.isEmpty()) { @@ -677,7 +892,7 @@ private void SendChat(String To, String Msg) { ChatBody.put("From", OperatorName != null ? OperatorName : "operator"); ChatBody.put("To", To); ChatBody.put("Message", Msg); - Post("/api/chat/send", ChatBody); + Post("/api/team/chat/send", ChatBody); Logger.Custom(INDENT + "%s→ %s:%s %s%n", AnsiColor.Green, To, AnsiColor.Reset, Msg); } catch (Exception Ex) { Logger.Error(Ex.getMessage()); @@ -715,38 +930,38 @@ private Map Get(String Path) throws Exception { return R != null ? R : new LinkedHashMap<>(); } - private static int ParseInt(String S) { - return Integer.parseInt(S.trim()); + private static int ParseInt(String Value) { + return Integer.parseInt(Value.trim()); } - private static int ParseIntSafe(String S, int D) { + private static int ParseIntSafe(String Value, int Default) { try { - return Integer.parseInt(S.trim()); - } catch (Exception Ex) { - return D; + return Integer.parseInt(Value.trim()); + } catch (Exception Ignored) { + return Default; } } 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 Ok(String Message) { + System.out.printf(" %s✔ %s%s%n%n", AnsiColor.Green, Message, AnsiColor.Reset); } - static void Warn(String M) { - System.out.printf(" %s⚠ %s%s%n", AnsiColor.White, M, AnsiColor.Reset); + static void Warn(String Message) { + System.out.printf(" %s⚠ %s%s%n", AnsiColor.White, Message, AnsiColor.Reset); } - static void Error(String M) { - System.out.printf(" %s✘ %s%s%n", AnsiColor.Red, M, AnsiColor.Reset); + static void Error(String Message) { + System.out.printf(" %s✘ %s%s%n", AnsiColor.Red, Message, AnsiColor.Reset); } - static void Info(String M) { - System.out.println(M); + static void Info(String Message) { + System.out.println(Message); } - static void Custom(String Fmt, Object... A) { - System.out.printf(Fmt, A); + static void Custom(String Format, Object... Args) { + System.out.printf(Format, Args); } } } diff --git a/RAVEN/src/main/java/com/raven/interfaces/TeamServer.java b/RAVEN/src/main/java/com/raven/interfaces/TeamServer.java index 25e6d34..fcdcab7 100644 --- a/RAVEN/src/main/java/com/raven/interfaces/TeamServer.java +++ b/RAVEN/src/main/java/com/raven/interfaces/TeamServer.java @@ -27,9 +27,9 @@ 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"; + private static final long TokenTtlMs = 8L * 60 * 60 * 1000; + private static final int MaxChatSize = 500; + private static final String JsonContentType = "application/json"; @FunctionalInterface private interface RouteHandler { @@ -66,6 +66,7 @@ public void Run(String Host, int Port) throws Exception { HttpSrv = HttpServer.create(new InetSocketAddress(Host, Port), 128); Router = new HttpRouter(HttpSrv, Config, PathResolver); RegisterRoutes(); + Router.RegisterStatic(); HttpSrv.setExecutor(Executors.newFixedThreadPool(32)); HttpSrv.start(); Logger.Info("TeamServer web panel : http://" + Host + ":" + Port + "/"); @@ -73,6 +74,64 @@ public void Run(String Host, int Port) throws Exception { Log.Add("TeamServer initialized — mode: " + Mode.name()); } + private volatile HttpServer WebPanelHttpServer = null; + private volatile String WebPanelHost = null; + private volatile int WebPanelPort = -1; + + private String ApiWebPanelStart(HttpExchange Exchange, AuthApi.TokenInfo Token) throws Exception { + if (!Token.Role().CanWrite()) return HttpHelper.Json(Map.of("Error", "insufficient permissions")); + if (WebPanelHttpServer != null) return HttpHelper.Json(Map.of("Error", "web panel already running")); + Map Body = Body(Exchange); + String RequestedHost = Str(Body, "Host", "0.0.0.0"); + int RequestedPort = Num(Body, "Port", 8080); + try { + HttpServer Panel = HttpServer.create(new InetSocketAddress(RequestedHost, RequestedPort), 64); + HttpRouter PanelRouter = new HttpRouter(Panel, Config, PathResolver); + PanelRouter.RegisterStatic(); + Panel.setExecutor(Executors.newFixedThreadPool(8)); + Panel.start(); + WebPanelHttpServer = Panel; + WebPanelHost = RequestedHost; + WebPanelPort = RequestedPort; + String DisplayHost = RequestedHost.equals("0.0.0.0") ? "localhost" : RequestedHost; + String Url = "http://" + DisplayHost + ":" + RequestedPort + "/"; + Logger.Info("Web panel enabled on " + Url + " by " + Token.Username()); + AddLog("Web panel started on " + Url + " by " + Token.Username()); + return HttpHelper.Json(Map.of("Success", true, "URL", Url)); + } catch (Exception Ex) { + return HttpHelper.Json(Map.of("Error", "web panel start failed: " + Ex.getMessage())); + } + } + + private String ApiWebPanelStop(HttpExchange Exchange, AuthApi.TokenInfo Token) throws Exception { + if (!Token.Role().CanWrite()) return HttpHelper.Json(Map.of("Error", "insufficient permissions")); + if (WebPanelHttpServer == null) return HttpHelper.Json(Map.of("Error", "web panel not running")); + WebPanelHttpServer.stop(1); + WebPanelHttpServer = null; + WebPanelHost = null; + WebPanelPort = -1; + Logger.Info("Web panel stopped by " + Token.Username()); + AddLog("Web panel stopped by " + Token.Username()); + return HttpHelper.Json(Map.of("Success", true)); + } + + private String ApiWebPanelStatus(HttpExchange Exchange, AuthApi.TokenInfo Token) throws Exception { + boolean Running = WebPanelHttpServer != null; + Map Response = new LinkedHashMap<>(); + Response.put("Running", Running); + if (Running) { + String DisplayHost = WebPanelHost != null && WebPanelHost.equals("0.0.0.0") ? "localhost" : WebPanelHost; + Response.put("URL", "http://" + DisplayHost + ":" + WebPanelPort + "/"); + Response.put("Host", WebPanelHost); + Response.put("Port", WebPanelPort); + } + return HttpHelper.Json(Response); + } + + private String ApiTasks(HttpExchange Exchange, AuthApi.TokenInfo Token) throws Exception { + return HttpHelper.Json(Map.of("Tasks", List.of())); + } + public void Stop() { if (Server != null) Server.StopServer(); if (HttpSrv != null) HttpSrv.stop(1); @@ -80,6 +139,28 @@ public void Stop() { Logger.Shutdown(); } + public void RunAsBackend(String AgentHost, int AgentPort, String ApiHost, int ApiPort) throws Exception { + Server = new RavenServer(AgentHost, AgentPort, Mode, Config); + Server.AddEventListener(this::OnEvent); + boolean[] Started = Server.StartServer(); + if (!Started[0]) throw new Exception("failed to start agent listener on " + AgentHost + ":" + AgentPort); + ServerStartTime = Instant.now(); + Thread AcceptThread = new Thread(Server::AcceptConnections, "AcceptConnections"); + AcceptThread.setDaemon(true); + AcceptThread.start(); + HttpSrv = HttpServer.create(new InetSocketAddress(ApiHost, ApiPort), 128); + Router = new HttpRouter(HttpSrv, Config, PathResolver); + RegisterRoutes(); + HttpSrv.setExecutor(Executors.newFixedThreadPool(32)); + HttpSrv.start(); + Logger.Info("RAVEN TeamServer backend running:"); + Logger.Info(" Agent listener : " + AgentHost + ":" + AgentPort + " [" + Mode.name() + "]"); + Logger.Info(" Operator API : http://" + ApiHost + ":" + ApiPort + "/api/"); + Logger.Info(" Connect CLI : java -jar raven.jar -TSC -ts " + ApiHost + " -tp " + ApiPort); + Logger.Info(" Connect Web : java -jar raven.jar -TSW -ts " + ApiHost + " -tp " + ApiPort); + Log.Add("TeamServer backend initialized — mode: " + Mode.name()); + } + private void RegisterRoutes() { // auth route("/api/auth/login", this::ApiAuthLogin, false); @@ -127,12 +208,17 @@ private void RegisterRoutes() { route("/api/team/operators/kick", this::ApiOpKick, true); route("/api/team/roles", this::ApiRoles, true); + // web panel control + route("/api/server/webpanel/start", this::ApiWebPanelStart, true); + route("/api/server/webpanel/stop", this::ApiWebPanelStop, true); + route("/api/server/webpanel/status", this::ApiWebPanelStatus, true); + route("/api/tasks", this::ApiTasks, 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) { @@ -156,7 +242,7 @@ private void Dispatch(HttpExchange E, RouteHandler H, boolean RequireAuth) { } String Result = H.Handle(E, T); byte[] Bytes = Result.getBytes("UTF-8"); - E.getResponseHeaders().set("Content-Type", MEDIA_JSON); + E.getResponseHeaders().set("Content-Type", JsonContentType); E.getResponseHeaders().set("X-Content-Type-Options", "nosniff"); E.sendResponseHeaders(200, Bytes.length); try (OutputStream Out = E.getResponseBody()) { @@ -171,7 +257,7 @@ private void Dispatch(HttpExchange E, RouteHandler H, boolean RequireAuth) { 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.getResponseHeaders().set("Content-Type", JsonContentType); E.sendResponseHeaders(Status, Bytes.length); try (OutputStream Out = E.getResponseBody()) { Out.write(Bytes); @@ -188,12 +274,12 @@ private Map Body(HttpExchange E) throws Exception { } } - private String S(Map M, String K, String Def) { - return HttpHelper.Str(M, K, Def); + private String Str(Map DataMap, String Key, String Default) { + return HttpHelper.Str(DataMap, Key, Default); } - private int N(Map M, String K, int Def) { - return HttpHelper.Num(M, K, Def); + private int Num(Map DataMap, String Key, int Default) { + return HttpHelper.Num(DataMap, Key, Default); } private void AddLog(String Msg) { @@ -210,17 +296,17 @@ private String Uptime() { private String ApiAuthLogin(HttpExchange E, TokenInfo Ignored) throws Exception { Map B = Body(E); - String User = S(B, "Username", ""); - String Pass = S(B, "Password", ""); + String User = Str(B, "Username", ""); + String Pass = Str(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)); + Tokens.put(Token, new TokenInfo(User, Role, System.currentTimeMillis() + TokenTtlMs)); 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)); + return HttpHelper.Json(Map.of("Token", Token, "Role", Role.name(), "Username", User, "Permissions", Role.PermissionString(), "ExpiresIn", TokenTtlMs / 1000)); } private String ApiAuthLogout(HttpExchange E, TokenInfo T) throws Exception { @@ -257,8 +343,8 @@ 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()); + String Host = Str(B, "Host", Config.GetServerHost()); + int Port = Num(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")); @@ -309,7 +395,7 @@ private String ApiAgents(HttpExchange E, TokenInfo T) { 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); + int Id = Num(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()); @@ -318,8 +404,8 @@ private String ApiAgentKill(HttpExchange E, TokenInfo T) throws Exception { private String ApiAgentNote(HttpExchange E, TokenInfo T) throws Exception { Map B = Body(E); - int Id = N(B, "AgentId", 0); - String Note = S(B, "Note", ""); + int Id = Num(B, "AgentId", 0); + String Note = Str(B, "Note", ""); if (Id == 0) return HttpHelper.Json(Map.of("Error", "AgentId required")); Db.SetAgentNote(Id, Note); return HttpHelper.Json(Map.of("Success", true)); @@ -335,8 +421,8 @@ 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", ""); + int Id = Num(B, "AgentId", 0); + String Cmd = Str(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); @@ -349,7 +435,7 @@ 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", ""); + String Cmd = Str(B, "Command", ""); if (Cmd.isEmpty()) return HttpHelper.Json(Map.of("Error", "Command required")); @SuppressWarnings("unchecked") List Raw = (List) B.getOrDefault("AgentIds", List.of()); @@ -366,7 +452,7 @@ private String ApiCmdBroadcast(HttpExchange E, TokenInfo T) throws Exception { 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", ""); + String Cmd = Str(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); @@ -384,7 +470,7 @@ private String BuildBroadcastResult(Map Results, String Opera 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); + int Id = Num(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]); @@ -395,8 +481,8 @@ private String ApiCmdScreenshot(HttpExchange E, TokenInfo T) throws Exception { 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", ""); + int Id = Num(B, "AgentId", 0); + String Path = Str(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]); @@ -407,9 +493,9 @@ private String ApiCmdDownload(HttpExchange E, TokenInfo T) throws Exception { 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", ""); + int Id = Num(B, "AgentId", 0); + String Local = Str(B, "LocalPath", ""); + String Remote = Str(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]); @@ -419,8 +505,8 @@ private String ApiCmdUpload(HttpExchange E, TokenInfo T) throws Exception { private String ApiCmdSleep(HttpExchange E, TokenInfo T) throws Exception { Map B = Body(E); - int Id = N(B, "AgentId", 0); - String Secs = S(B, "Seconds", ""); + int Id = Num(B, "AgentId", 0); + String Secs = Str(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])); @@ -428,8 +514,8 @@ private String ApiCmdSleep(HttpExchange E, TokenInfo T) throws Exception { private String ApiCmdPivot(HttpExchange E, TokenInfo T) throws Exception { Map B = Body(E); - int Id = N(B, "AgentId", 0); - String Target = S(B, "Target", ""); + int Id = Num(B, "AgentId", 0); + String Target = Str(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])); @@ -437,10 +523,10 @@ private String ApiCmdPivot(HttpExchange E, TokenInfo T) throws Exception { 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); + int Id = Num(B, "AgentId", 0); + int Lport = Num(B, "Lport", 0); + String Rhost = Str(B, "Rhost", ""); + int Rport = Num(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])); @@ -448,8 +534,8 @@ private String ApiCmdPortfwd(HttpExchange E, TokenInfo T) throws Exception { 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); + int Id = Num(B, "AgentId", 0); + int Lport = Num(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])); @@ -457,13 +543,13 @@ private String ApiCmdSocks(HttpExchange E, TokenInfo T) throws Exception { 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)))); + return HttpHelper.Json(Map.of("History", Db.GetCommandHistory(Num(B, "AgentId", 0), Num(B, "Limit", 100)))); } // SESSIONS private String ApiSessionHistory(HttpExchange E, TokenInfo T) throws Exception { - int Limit = N(Body(E), "Limit", 100); + int Limit = Num(Body(E), "Limit", 100); return HttpHelper.Json(Map.of("Sessions", Db.GetSessionHistory(Math.min(Limit, 1000)))); } @@ -477,8 +563,8 @@ private String ApiLogs(HttpExchange E, TokenInfo T) { private String ApiExport(HttpExchange E, TokenInfo T) throws Exception { Map B = Body(E); - String Target = S(B, "Target", ""); - String Format = S(B, "Format", "json"); + String Target = Str(B, "Target", ""); + String Format = Str(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)); @@ -494,9 +580,9 @@ private String ApiOpList(HttpExchange E, TokenInfo T) { 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"); + String User = Str(B, "Username", ""); + String Pass = Str(B, "Password", ""); + String Role = Str(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); @@ -508,7 +594,7 @@ private String ApiOpCreate(HttpExchange E, TokenInfo T) throws Exception { 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", ""); + String User = Str(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")); @@ -519,8 +605,8 @@ private String ApiOpDelete(HttpExchange E, TokenInfo T) throws Exception { 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", ""); + String User = Str(B, "Username", ""); + String Role = Str(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); @@ -532,8 +618,8 @@ private String ApiOpRole(HttpExchange E, TokenInfo T) throws Exception { 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", ""); + String User = Str(B, "Username", ""); + String Pass = Str(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")); @@ -543,7 +629,7 @@ private String ApiOpPassword(HttpExchange E, TokenInfo T) throws Exception { 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", ""); + String User = Str(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")); @@ -575,33 +661,34 @@ private String ApiRoles(HttpExchange E, TokenInfo T) { private String ApiChatSend(HttpExchange E, TokenInfo T) throws Exception { Map B = Body(E); - String Msg = S(B, "Message", ""); - String To = S(B, "To", "all"); + String Msg = Str(B, "Message", ""); + String To = Str(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"))); + Entry.put("Timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); Chat.add(Entry); - if (Chat.size() > MAX_CHAT) Chat.remove(0); + if (Chat.size() > MaxChatSize) 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> DbMessages = Db.GetChatLogs(MaxChatSize); 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); + for (Map Message : DbMessages) { + String To = Message.getOrDefault("To", "all").toString(); + String From = Message.getOrDefault("From", "").toString(); + if (To.equals("all") || To.equals(User) || From.equals(User)) Visible.add(Message); } 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); + int Limit = Num(Body(E), "Limit", 100); return HttpHelper.Json(Map.of("Logs", Db.GetChatLogs(Math.min(Limit, 1000)))); } diff --git a/RAVEN/src/main/java/com/raven/utils/Helper.java b/RAVEN/src/main/java/com/raven/utils/Helper.java index af81a67..2bee509 100644 --- a/RAVEN/src/main/java/com/raven/utils/Helper.java +++ b/RAVEN/src/main/java/com/raven/utils/Helper.java @@ -45,6 +45,7 @@ public static void PrintHelp() { " -C -cli CLI solo — no auth, direct access", " -G -gui GUI solo — no auth", " -W -web Web panel — auth required", + " -wp -web-port Web panel port for -W/-G (default: web.port)", " -TSC TeamServer CLI — login, no HTTP API", " -TSW TeamServer Web — multi-operator via HTTP", " -TSG TeamServer GUI — login, GUI interface", @@ -110,6 +111,10 @@ public static String Arg(List Args, String Short, String Long, String De return Default; } + public static int ParseInt(String Value) { + return ParseInt(Value, -1); + } + public static int ParseInt(String Value, int Default) { try { return Integer.parseInt(Value.trim()); diff --git a/RAVEN/src/main/java/com/raven/utils/RavenConstants.java b/RAVEN/src/main/java/com/raven/utils/RavenConstants.java index 9a1b8a7..31f5331 100644 --- a/RAVEN/src/main/java/com/raven/utils/RavenConstants.java +++ b/RAVEN/src/main/java/com/raven/utils/RavenConstants.java @@ -6,7 +6,8 @@ public final class RavenConstants { private RavenConstants() {} - public static final DateTimeFormatter TimestampFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + public static final DateTimeFormatter TimestampFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + public static final DateTimeFormatter FilenameFmt = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); public static final DateTimeFormatter ChatTimeFmt = DateTimeFormatter.ofPattern("HH:mm:ss"); public static final String ServerConfigPath = "config/server/raven.properties"; diff --git a/RAVEN/src/main/java/com/raven/utils/ServerConfig.java b/RAVEN/src/main/java/com/raven/utils/ServerConfig.java index 8cdbdf1..14a3e59 100644 --- a/RAVEN/src/main/java/com/raven/utils/ServerConfig.java +++ b/RAVEN/src/main/java/com/raven/utils/ServerConfig.java @@ -1,6 +1,5 @@ package com.raven.utils; -import com.raven.utils.OperatorConfig; import com.raven.core.output.Logger; import java.io.File; diff --git a/RAVEN/src/main/java/com/raven/utils/TerminalHelper.java b/RAVEN/src/main/java/com/raven/utils/TerminalHelper.java index ab6fbf7..23572b5 100644 --- a/RAVEN/src/main/java/com/raven/utils/TerminalHelper.java +++ b/RAVEN/src/main/java/com/raven/utils/TerminalHelper.java @@ -1,5 +1,7 @@ package com.raven.utils; +import com.raven.utils.SystemHelper; + import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -45,16 +47,6 @@ public static int GetWidth() { 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); @@ -110,12 +102,9 @@ public static String Indent(String Text) { return " " + Text; } - private static boolean IsWindows() { - return System.getProperty("os.name", "").toLowerCase().contains("win"); - } private static int Detect() { - if (IsWindows()) { + if (SystemHelper.IsWindows()) { Integer Width = DetectWindowsViaPowerShell(); if (Width != null) return Width; Width = DetectWindowsViaModeCon();