Skip to content

Commit

Permalink
Merge pull request #727 from RafBishopFox/master
Browse files Browse the repository at this point in the history
Moving filtering functionality of ls to the implant
  • Loading branch information
moloch-- authored Jun 23, 2022
2 parents be7b723 + 140c47e commit 814670d
Show file tree
Hide file tree
Showing 6 changed files with 963 additions and 905 deletions.
88 changes: 22 additions & 66 deletions client/command/filesystem/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"bytes"
"context"
"fmt"
"path/filepath"
"sort"
"strings"
"text/tabwriter"
Expand All @@ -45,49 +44,6 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}

remotePath := ctx.Args.String("path")
filter := ""

/*
Check to see if the remote path is a filter or contains a filter.
If the path passes the test to be a filter, then it is a filter
because paths are not valid filters.
*/
if remotePath != "." {

// Check if the path contains a filter
// Test on a standardized version of the path (change any \ to /)
testPath := strings.Replace(remotePath, "\\", "/", -1)
/*
Cannot use the path or filepath libraries because the OS
of the client does not necessarily match the OS of the
implant
*/
lastSeparatorOccurrence := strings.LastIndex(testPath, "/")

if lastSeparatorOccurrence == -1 {
// Then this is only a filter
filter = remotePath
remotePath = "."
} else {
// Then we need to test for a filter on the end of the string

// The indicies should be the same because we did not change the length of the string
baseDir := remotePath[:lastSeparatorOccurrence+1]
potentialFilter := remotePath[lastSeparatorOccurrence+1:]

_, err := filepath.Match(potentialFilter, "")

if err == nil {
// Then we have a filter on the end of the path
remotePath = baseDir
filter = potentialFilter
} else {
if !strings.HasSuffix(remotePath, "/") {
remotePath = remotePath + "/"
}
}
}
}

ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
Request: con.ActiveTarget.Request(ctx),
Expand All @@ -104,23 +60,38 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
PrintLs(ls, ctx.Flags, filter, con)
PrintLs(ls, ctx.Flags, con)
})
con.PrintAsyncResponse(ls.Response)
} else {
PrintLs(ls, ctx.Flags, filter, con)
PrintLs(ls, ctx.Flags, con)
}
}

// PrintLs - Display an sliverpb.Ls object
func PrintLs(ls *sliverpb.Ls, flags grumble.FlagMap, filter string, con *console.SliverConsoleClient) {
func PrintLs(ls *sliverpb.Ls, flags grumble.FlagMap, con *console.SliverConsoleClient) {
if ls.Response != nil && ls.Response.Err != "" {
con.PrintErrorf("%s\n", ls.Response.Err)
return
}

con.Printf("%s\n", ls.Path)
con.Printf("%s\n", strings.Repeat("=", len(ls.Path)))
// Generate metadata to print with the path
numberOfFiles := len(ls.Files)
var totalSize int64 = 0
var pathInfo string

for _, fileInfo := range ls.Files {
totalSize += fileInfo.Size
}

if numberOfFiles == 1 {
pathInfo = fmt.Sprintf("%s (%d item, %s)", ls.Path, numberOfFiles, util.ByteCountBinary(totalSize))
} else {
pathInfo = fmt.Sprintf("%s (%d items, %s)", ls.Path, numberOfFiles, util.ByteCountBinary(totalSize))
}

con.Printf("%s\n", pathInfo)
con.Printf("%s\n", strings.Repeat("=", len(pathInfo)))

outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
Expand Down Expand Up @@ -174,29 +145,14 @@ func PrintLs(ls *sliverpb.Ls, flags grumble.FlagMap, filter string, con *console
}

for _, fileInfo := range ls.Files {
if filter != "" {
fileMatch, err := filepath.Match(filter, fileInfo.Name)

if err != nil {
/*
This should not happen because we checked the filter
before reaching out to the implant.
*/
con.PrintErrorf("%s is not a valid filter: %s\n", filter, err)
break
}

if !fileMatch {
continue
}
}

modTime := time.Unix(fileInfo.ModTime, 0)
implantLocation := time.FixedZone(ls.Timezone, int(ls.TimezoneOffset))
modTime = modTime.In(implantLocation)

if fileInfo.IsDir {
fmt.Fprintf(table, "%s\t%s\t<dir>\t%s\n", fileInfo.Mode, fileInfo.Name, modTime.Format(time.RubyDate))
} else if fileInfo.Link != "" {
fmt.Fprintf(table, "%s\t%s -> %s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, fileInfo.Link, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
} else {
fmt.Fprintf(table, "%s\t%s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
}
Expand Down
2 changes: 1 addition & 1 deletion client/command/tasks/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverConsoleCli
"modified": &grumble.FlagMapItem{Value: false},
"size": &grumble.FlagMapItem{Value: false},
}
filesystem.PrintLs(ls, flags, "", con)
filesystem.PrintLs(ls, flags, con)

case sliverpb.MsgMvReq:
mv := &sliverpb.Mv{}
Expand Down
118 changes: 105 additions & 13 deletions implant/sliver/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,58 @@ func pingHandler(data []byte, resp RPCResponse) {
resp(data, err)
}

func determineDirPathFilter(targetPath string) (string, string) {
// The filter
filter := ""

// The path the filter applies to
path := ""

/*
Check to see if the remote path is a filter or contains a filter.
If the path passes the test to be a filter, then it is a filter
because paths are not valid filters.
*/
if targetPath != "." {

// Check if the path contains a filter
// Test on a standardized version of the path (change any \ to /)
testPath := strings.Replace(targetPath, "\\", "/", -1)
/*
Cannot use the path or filepath libraries because the OS
of the client does not necessarily match the OS of the
implant
*/
lastSeparatorOccurrence := strings.LastIndex(testPath, "/")

if lastSeparatorOccurrence == -1 {
// Then this is only a filter
filter = targetPath
path = "."
} else {
// Then we need to test for a filter on the end of the string

// The indicies should be the same because we did not change the length of the string
path = targetPath[:lastSeparatorOccurrence+1]
filter = targetPath[lastSeparatorOccurrence+1:]
}
} else {
path = targetPath
// filter remains blank
}

return path, filter
}

func pathIsDirectory(path string) bool {
fileInfo, err := os.Stat(path)
if err != nil {
return false
} else {
return fileInfo.IsDir()
}
}

func dirListHandler(data []byte, resp RPCResponse) {
dirListReq := &sliverpb.LsReq{}
err := proto.Unmarshal(data, dirListReq)
Expand All @@ -91,7 +143,19 @@ func dirListHandler(data []byte, resp RPCResponse) {
// {{end}}
return
}
dir, files, err := getDirList(dirListReq.Path)

// Handle the case where a directory is provided without a trailing separator
var targetPath string

if pathIsDirectory(dirListReq.Path) {
targetPath = dirListReq.Path + "/"
} else {
targetPath = dirListReq.Path
}

path, filter := determineDirPathFilter(targetPath)

dir, files, err := getDirList(path)

// Convert directory listing to protobuf
timezone, offset := time.Now().Zone()
Expand All @@ -102,19 +166,47 @@ func dirListHandler(data []byte, resp RPCResponse) {
dirList.Exists = false
}
dirList.Files = []*sliverpb.FileInfo{}

var match bool = false
var linkPath string = ""

for _, fileInfo := range files {
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
Name: fileInfo.Name(),
IsDir: fileInfo.IsDir(),
Size: fileInfo.Size(),
/* Send the time back to the client / server as the number of seconds
since epoch. This will decouple formatting the time to display from the
time itself. We can change the format of the time displayed in the client
and not have to worry about having to update implants.
*/
ModTime: fileInfo.ModTime().Unix(),
Mode: fileInfo.Mode().String(),
})
if filter == "" {
match = true
} else {
match, err = filepath.Match(filter, fileInfo.Name())
if err != nil {
// Then this is a bad filter, and it will be a bad filter
// on every iteration of the loop, so we might as well break now
break
}
}

if match {
// Check if this is a symlink, and if so, add the path the link points to
if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
linkPath, err = filepath.EvalSymlinks(path + fileInfo.Name())
if err != nil {
linkPath = ""
}
} else {
linkPath = ""
}

dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
Name: fileInfo.Name(),
IsDir: fileInfo.IsDir(),
Size: fileInfo.Size(),
/* Send the time back to the client / server as the number of seconds
since epoch. This will decouple formatting the time to display from the
time itself. We can change the format of the time displayed in the client
and not have to worry about having to update implants.
*/
ModTime: fileInfo.ModTime().Unix(),
Mode: fileInfo.Mode().String(),
Link: linkPath,
})
}
}

// Send back the response
Expand Down
4 changes: 2 additions & 2 deletions protobuf/commonpb/common.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 814670d

Please sign in to comment.