Skip to content

Commit

Permalink
Merge pull request #1596 from precurse-bf/mount_linux_rebase
Browse files Browse the repository at this point in the history
Adding mount command for Windows and Linux
  • Loading branch information
rkervella authored Mar 14, 2024
2 parents 715c37a + 49b55c7 commit 7465396
Show file tree
Hide file tree
Showing 15 changed files with 3,559 additions and 2,624 deletions.
14 changes: 14 additions & 0 deletions client/command/filesystem/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,19 @@ func Commands(con *console.SliverClient) []*cobra.Command {

carapace.Gen(memfilesRmCmd).PositionalCompletion(carapace.ActionValues().Usage("memfile file descriptor"))

mountCmd := &cobra.Command{
Use: consts.MountStr,
Short: "Get information on mounted filesystems",
Long: help.GetHelpFor([]string{consts.MountStr}),
Run: func(cmd *cobra.Command, args []string) {
MountCmd(cmd, con, args)
},
GroupID: consts.FilesystemHelpGroup,
}
flags.Bind("", false, mountCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})

grepCmd := &cobra.Command{
Use: consts.GrepStr,
Short: "Search for strings that match a regex within a file or directory",
Expand Down Expand Up @@ -324,6 +337,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
downloadCmd,
uploadCmd,
memfilesCmd,
mountCmd,
grepCmd,
headCmd,
tailCmd,
Expand Down
165 changes: 165 additions & 0 deletions client/command/filesystem/mount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
Sliver Implant Framework
Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/

package filesystem

import (
"context"
"fmt"
"math"

"google.golang.org/protobuf/proto"

"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"

"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)

// Drive mappings for Windows
var driveTypeMap = map[string]string{
"0": "Unknown",
"1": "Root Path invalid (no volume mounted for path)",
"2": "Removable",
"3": "Fixed disk",
"4": "Remote / network drive",
"5": "CD-ROM",
"6": "RAM disk",
}

// MountCmd - Print information about mounted filesystems
func MountCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
mount, err := con.Rpc.Mount(context.Background(), &sliverpb.MountReq{
Request: con.ActiveTarget.Request(cmd),
})
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
os := getOS(session, beacon)
if mount.Response != nil && mount.Response.Async {
con.AddBeaconCallback(mount.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, mount)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintMount(os, mount, con)
})
con.PrintAsyncResponse(mount.Response)
} else {
PrintMount(os, mount, con)
}
}

func getOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
if session != nil {
return session.OS
} else if beacon != nil {
return beacon.OS
}
return ""
}

func reduceSpaceMetric(numberOfBytes float64) string {
units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}

if numberOfBytes < 1 {
return fmt.Sprintf("0 %s", units[0])
}

base := 1024.0

exp := math.Floor(math.Log(numberOfBytes) / math.Log(base))
index := int(math.Min(exp, float64(len(units)-1)))
divisor := math.Pow(base, float64(index))

value := numberOfBytes / divisor

return fmt.Sprintf("%.2f %s", value, units[index])
}

// PrintMount - Print a table containing information on mounted filesystems
func PrintMount(os string, mount *sliverpb.Mount, con *console.SliverClient) {
if mount.Response != nil && mount.Response.Err != "" {
con.PrintErrorf("%s\n", mount.Response.Err)
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))

switch os {
case "windows":
tw.AppendHeader(table.Row{"Volume", "Volume Type", "Mount Point", "Label", "Filesystem", "Used Space", "Free Space", "Total Space"})
case "darwin":
fallthrough
case "linux":
fallthrough
default:
tw.AppendHeader(table.Row{"Source", "Mount Point", "Mount Root", "Filesystem Type", "Options", "Total Space"})
}

for _, mountPoint := range mount.Info {
row := mountRow(os, mountPoint)
tw.AppendRow(row)
}

settings.PaginateTable(tw, 0, false, false, con)
}

func mountRow(os string, mountInfo *sliverpb.MountInfo) table.Row {
var row table.Row

switch os {
case "windows":
// Translate VolumeType
volType, ok := driveTypeMap[mountInfo.VolumeType]
if !ok {
volType = driveTypeMap["0"]
}
row = table.Row{mountInfo.VolumeName,
volType,
mountInfo.MountPoint,
mountInfo.Label,
mountInfo.FileSystem,
reduceSpaceMetric(float64(mountInfo.UsedSpace)),
reduceSpaceMetric(float64(mountInfo.FreeSpace)),
reduceSpaceMetric(float64(mountInfo.TotalSpace)),
}
case "darwin":
fallthrough
case "linux":
fallthrough
default:
row = table.Row{mountInfo.VolumeName,
mountInfo.MountPoint,
mountInfo.Label,
mountInfo.VolumeType,
mountInfo.MountOptions,
reduceSpaceMetric(float64(mountInfo.TotalSpace)),
}
}
return row
}
4 changes: 4 additions & 0 deletions client/command/help/long-help.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ var (
consts.PsExecStr: psExecHelp,
consts.BackdoorStr: backdoorHelp,
consts.SpawnDllStr: spawnDllHelp,
consts.MountStr: mountHelp,

consts.WebsitesStr: websitesHelp,
consts.ScreenshotStr: screenshotHelp,
Expand Down Expand Up @@ -380,6 +381,9 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
[[.Bold]]About:[[.Normal]] (Windows Only) Executes the .NET assembly in a child process.
`

mountHelp = `[[.Bold]]Command:[[.Normal]] mount
[[.Bold]]About:[[.Normal]] Displays information about mounted drives on the system, including mount point, space metrics, and filesystem.`

executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode]
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process.
Expand Down
1 change: 1 addition & 0 deletions client/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ const (
ChmodStr = "chmod"
ChownStr = "chown"
ChtimesStr = "chtimes"
MountStr = "mount"

MemfilesStr = "memfiles"

Expand Down
23 changes: 23 additions & 0 deletions implant/sliver/handlers/handlers_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"unsafe"

"github.com/bishopfox/sliver/implant/sliver/procdump"
"github.com/bishopfox/sliver/implant/sliver/mount"
"github.com/bishopfox/sliver/implant/sliver/taskrunner"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
Expand Down Expand Up @@ -71,6 +72,7 @@ var (
sliverpb.MsgReconfigureReq: reconfigureHandler,
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
sliverpb.MsgProcessDumpReq: dumpHandler,
sliverpb.MsgMountReq: mountHandler,
sliverpb.MsgGrepReq: grepHandler,

// Wasm Extensions - Note that execution can be done via a tunnel handler
Expand Down Expand Up @@ -124,6 +126,27 @@ func dumpHandler(data []byte, resp RPCResponse) {
resp(data, err)
}

func mountHandler(data []byte, resp RPCResponse) {
mountReq := &sliverpb.MountReq{}
err := proto.Unmarshal(data, mountReq)
if err != nil {
return
}

mountData, err := mount.GetMountInformation()
mountResp := &sliverpb.Mount{
Info: mountData,
Response: &commonpb.Response{},
}

if err != nil {
mountResp.Response.Err = err.Error()
}

data, err = proto.Marshal(mountResp)
resp(data, err)
}

func taskHandler(data []byte, resp RPCResponse) {
var err error
task := &sliverpb.TaskReq{}
Expand Down
23 changes: 23 additions & 0 deletions implant/sliver/handlers/handlers_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"syscall"

"github.com/bishopfox/sliver/implant/sliver/extension"
"github.com/bishopfox/sliver/implant/sliver/mount"
"github.com/bishopfox/sliver/implant/sliver/priv"
"github.com/bishopfox/sliver/implant/sliver/procdump"
"github.com/bishopfox/sliver/implant/sliver/ps"
Expand Down Expand Up @@ -92,6 +93,7 @@ var (
sliverpb.MsgServicesReq: servicesListHandler,
sliverpb.MsgServiceDetailReq: serviceDetailHandler,
sliverpb.MsgStartServiceByNameReq: startServiceByNameHandler,
sliverpb.MsgMountReq: mountHandler,

// Generic
sliverpb.MsgPing: pingHandler,
Expand Down Expand Up @@ -872,6 +874,27 @@ func serviceDetailHandler(data []byte, resp RPCResponse) {
resp(data, err)
}

func mountHandler(data []byte, resp RPCResponse) {
mountReq := &sliverpb.MountReq{}

err := proto.Unmarshal(data, mountReq)
if err != nil {
return
}
mountData, err := mount.GetMountInformation()
mountResp := &sliverpb.Mount{
Info: mountData,
Response: &commonpb.Response{},
}

if err != nil {
mountResp.Response.Err = err.Error()
}

data, err = proto.Marshal(mountResp)
resp(data, err)
}

// Extensions

func registerExtensionHandler(data []byte, resp RPCResponse) {
Expand Down
56 changes: 56 additions & 0 deletions implant/sliver/mount/mount_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// +linux

package mount

import (
"bufio"
"os"
"strings"
"syscall"

"github.com/bishopfox/sliver/protobuf/sliverpb"
)

func GetMountInformation() ([]*sliverpb.MountInfo, error) {
mountInfo := make([]*sliverpb.MountInfo, 0)

file, err := os.Open("/proc/self/mountinfo")
if err != nil {
return mountInfo, err
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)

// Extract fields according to the /proc/self/mountinfo format
// https://man7.org/linux/man-pages/man5/proc.5.html
mountRoot := fields[3]
mountPoint := fields[4]
mountOptions := fields[5]
mountType := fields[len(fields)-3]
mountSource := fields[len(fields)-2]

// Get mount information using statfs
var stat syscall.Statfs_t
err := syscall.Statfs(mountPoint, &stat)
if err != nil {
continue
}

var mountData sliverpb.MountInfo

mountData.Label = mountRoot
mountData.MountPoint = mountPoint
mountData.VolumeType = mountType
mountData.VolumeName = mountSource
mountData.MountOptions = mountOptions
mountData.TotalSpace = stat.Blocks * uint64(stat.Bsize)
mountInfo = append(mountInfo, &mountData)

}

return mountInfo, nil
}
Loading

0 comments on commit 7465396

Please sign in to comment.