mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cce67c289a | ||
|
|
93935b1691 | ||
|
|
a13a4dc12e | ||
|
|
4fa4562bea | ||
|
|
61b86a4a9a | ||
|
|
2c70ff1291 | ||
|
|
4a3a2a7be3 |
No files matched your search
@@ -1038,6 +1038,9 @@ func BindCommands(con *console.SliverConsoleClient) {
|
||||
LongHelp: help.GetHelpFor([]string{consts.LsStr}),
|
||||
Flags: func(f *grumble.Flags) {
|
||||
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
|
||||
f.Bool("r", "reverse", false, "reverse sort order")
|
||||
f.Bool("m", "modified", false, "sort by modified time")
|
||||
f.Bool("s", "size", false, "sort by size")
|
||||
},
|
||||
Args: func(a *grumble.Args) {
|
||||
a.String("path", "path to enumerate", grumble.Default("."))
|
||||
|
||||
@@ -22,8 +22,11 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -40,6 +43,45 @@ 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
|
||||
*/
|
||||
lastSeparatorOccurence := strings.LastIndex(testPath, "/")
|
||||
|
||||
if lastSeparatorOccurence == -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[:lastSeparatorOccurence]
|
||||
potentialFilter := remotePath[lastSeparatorOccurence+1:]
|
||||
|
||||
_, err := filepath.Match(potentialFilter, "")
|
||||
|
||||
if err == nil {
|
||||
// Then we have a filter on the end of the path
|
||||
remotePath = baseDir
|
||||
filter = potentialFilter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ls, err := con.Rpc.Ls(context.Background(), &sliverpb.LsReq{
|
||||
Request: con.ActiveSession.Request(ctx),
|
||||
@@ -48,22 +90,92 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if err != nil {
|
||||
con.PrintWarnf("%s\n", err)
|
||||
} else {
|
||||
PrintLs(ls, con)
|
||||
PrintLs(ls, con, ctx.Flags, filter)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintLs - Display an sliverpb.Ls object
|
||||
func PrintLs(ls *sliverpb.Ls, con *console.SliverConsoleClient) {
|
||||
func PrintLs(ls *sliverpb.Ls, con *console.SliverConsoleClient, flags grumble.FlagMap, filter string) {
|
||||
con.Printf("%s\n", ls.Path)
|
||||
con.Printf("%s\n", strings.Repeat("=", len(ls.Path)))
|
||||
|
||||
outputBuf := bytes.NewBufferString("")
|
||||
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
|
||||
for _, fileInfo := range ls.Files {
|
||||
if fileInfo.IsDir {
|
||||
fmt.Fprintf(table, "%s\t<dir>\t\n", fileInfo.Name)
|
||||
|
||||
// Extract the flags
|
||||
reverseSort := flags.Bool("reverse")
|
||||
sortByTime := flags.Bool("modified")
|
||||
sortBySize := flags.Bool("size")
|
||||
|
||||
/*
|
||||
By default, name sorting is case sensitive. Upper case entries come before
|
||||
lower case ones. Instead, we will level the playing field and sort
|
||||
regardless of case
|
||||
*/
|
||||
if reverseSort {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return strings.ToLower(ls.Files[i].Name) > strings.ToLower(ls.Files[j].Name)
|
||||
})
|
||||
} else {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return strings.ToLower(ls.Files[i].Name) < strings.ToLower(ls.Files[j].Name)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
After names are sorted properly, take care of the modified time if the
|
||||
user wants to sort by time. Doing this after sorting by name
|
||||
will make the time sorted entries properly sorted by name if times are equal.
|
||||
*/
|
||||
|
||||
if sortByTime {
|
||||
if reverseSort {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return ls.Files[i].ModTime > ls.Files[j].ModTime
|
||||
})
|
||||
} else {
|
||||
fmt.Fprintf(table, "%s\t%s\t\n", fileInfo.Name, util.ByteCountBinary(fileInfo.Size))
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return ls.Files[i].ModTime < ls.Files[j].ModTime
|
||||
})
|
||||
}
|
||||
} else if sortBySize {
|
||||
if reverseSort {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return ls.Files[i].Size > ls.Files[j].Size
|
||||
})
|
||||
} else {
|
||||
sort.SliceStable(ls.Files, func(i, j int) bool {
|
||||
return ls.Files[i].Size < ls.Files[j].Size
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Fprintf(table, "%s\t%s\t%s\t%s\n", fileInfo.Mode, fileInfo.Name, util.ByteCountBinary(fileInfo.Size), modTime.Format(time.RubyDate))
|
||||
}
|
||||
}
|
||||
table.Flush()
|
||||
|
||||
@@ -235,7 +235,31 @@ c2 message round trip to ensure the remote Sliver is still responding to command
|
||||
[[.Bold]]About:[[.Normal]] Kill a remote sliver process (does not delete file).`
|
||||
|
||||
lsHelp = `[[.Bold]]Command:[[.Normal]] ls <remote path>
|
||||
[[.Bold]]About:[[.Normal]] List remote files in current directory, or path if provided.`
|
||||
[[.Bold]]About:[[.Normal]] List remote files in current directory, or path if provided.
|
||||
|
||||
[[.Bold]][[.Underline]]Sorting[[.Normal]]
|
||||
Directory and file listings are sorted by name in ascending order by default. Listings can also be sorted by size (-s) and modified time (-m). All sorts can be reversed with -r.
|
||||
|
||||
[[.Bold]][[.Underline]]Filters[[.Normal]]
|
||||
Filters are a way to limit search results to directory and file names matching given criteria.
|
||||
|
||||
Filters are specified after the path. A blank path will filter on names in the current directory. For example:
|
||||
/etc/passwd will display the listing for /etc/passwd. "/etc/" is the path, and "passwd" is the filter.
|
||||
|
||||
Directory and file listings can be filtered using the following patterns:
|
||||
'*': Wildcard, matches any sequence of non-path separators (slashes)
|
||||
Example: n*.txt will match all file and directory names starting with n and ending with .txt
|
||||
|
||||
'?': Single character wildcard, matches a single non-path separator (slashes)
|
||||
Example: s?iver will match all file and directory names starting with s followed by any non-separator character and ending with iver.
|
||||
|
||||
'[{range}]': Match a range of characters. Ranges are specified with '-'. This is usually combined with other patterns. Ranges can be negated with '^'.
|
||||
Example: [a-c] will match the characters a, b, and c. [a-c]* will match all directory and file names that start with a, b, or c.
|
||||
^[r-u] will match all characters except r, s, t, and u.
|
||||
|
||||
If you need to match a special character (*, ?, '-', '[', ']', '\\'), place '\\' in front of it (example: \\?).
|
||||
On Windows, escaping is disabled. Instead, '\\' is treated as path separator.
|
||||
`
|
||||
|
||||
cdHelp = `[[.Bold]]Command:[[.Normal]] cd [remote path]
|
||||
[[.Bold]]About:[[.Normal]] Change working directory of the active Sliver.`
|
||||
|
||||
@@ -2,6 +2,8 @@ package privilege
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -60,6 +62,7 @@ func GetPrivsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
// for column width
|
||||
var nameColumnWidth int = 0
|
||||
var descriptionColumnWidth int = 0
|
||||
var introWidth int = 49 + len(strconv.Itoa(int(session.PID)))
|
||||
|
||||
for _, entry := range privs.PrivInfo {
|
||||
if len(entry.Name) > nameColumnWidth {
|
||||
@@ -74,12 +77,13 @@ func GetPrivsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
nameColumnWidth += 1
|
||||
descriptionColumnWidth += 1
|
||||
|
||||
con.Println("Privilege Information for Current Process")
|
||||
con.Println("-----------------------------------------")
|
||||
con.Printf("Privilege Information for Current Process (PID: %d)\n", session.PID)
|
||||
con.Println(strings.Repeat("-", introWidth))
|
||||
con.Printf("\nProcess Integrity Level: %s\n\n", privs.ProcessIntegrity)
|
||||
con.Printf("%-*s\t%-*s\t%s\n", nameColumnWidth, "Name", descriptionColumnWidth, "Description", "Attributes")
|
||||
con.Printf("%-*s\t%-*s\t%s\n", nameColumnWidth, "====", descriptionColumnWidth, "===========", "==========")
|
||||
for _, entry := range privs.PrivInfo {
|
||||
con.Printf("%-*s\t%-*s\t(", nameColumnWidth, entry.Name, descriptionColumnWidth, entry.Description)
|
||||
con.Printf("%-*s\t%-*s\t", nameColumnWidth, entry.Name, descriptionColumnWidth, entry.Description)
|
||||
if entry.Enabled {
|
||||
con.Printf("Enabled")
|
||||
} else {
|
||||
@@ -94,6 +98,6 @@ func GetPrivsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
|
||||
if entry.UsedForAccess {
|
||||
con.Printf(", Used for Access")
|
||||
}
|
||||
con.Printf(")\n")
|
||||
con.Printf("\n")
|
||||
}
|
||||
}
|
||||
+120
-24
@@ -1,15 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SLIVER_GPG_KEY_ID=4449039C
|
||||
|
||||
if [ "$EUID" -ne 0 ]
|
||||
then echo "Please run as root"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -n "$(command -v yum)" ]
|
||||
then
|
||||
sudo -S -p '' yum -y install zip unzip curl gcc gcc-c++ make mingw64-gcc
|
||||
yum -y install zip unzip gnupg curl gcc gcc-c++ make mingw64-gcc
|
||||
fi
|
||||
|
||||
if [ -n "$(command -v apt-get)" ]
|
||||
then
|
||||
DEBIAN_FRONTEND=noninteractive sudo -S -p '' apt-get install -yqq \
|
||||
zip unzip curl build-essential \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -yqq \
|
||||
zip unzip gpg curl build-essential \
|
||||
mingw-w64 binutils-mingw-w64 g++-mingw-w64
|
||||
fi
|
||||
|
||||
@@ -31,9 +38,69 @@ then
|
||||
echo "zip could not be found"
|
||||
exit 1
|
||||
fi
|
||||
# GPG
|
||||
if ! command -v gpg &> /dev/null
|
||||
then
|
||||
echo "gpg could not be found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd $HOME
|
||||
echo "Running from $HOME"
|
||||
cd /root
|
||||
echo "Running from $(pwd)"
|
||||
|
||||
gpg --import <<EOF
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=lxj2
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
EOF
|
||||
|
||||
#
|
||||
# Download and Unpack Sliver Server
|
||||
@@ -57,44 +124,73 @@ do
|
||||
fi
|
||||
done
|
||||
|
||||
if test -f "$SLIVER_SERVER_ZIP"; then
|
||||
unzip -o $SLIVER_SERVER_ZIP -d $HOME
|
||||
rm -f $SLIVER_SERVER_ZIP
|
||||
echo "Verifying signatures ..."
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_SERVER_ZIP.sig /root/$SLIVER_SERVER_ZIP
|
||||
gpg --default-key $SLIVER_GPG_KEY_ID --verify /root/$SLIVER_CLIENT_ZIP.sig /root/$SLIVER_CLIENT_ZIP
|
||||
|
||||
if test -f "/root/$SLIVER_SERVER_ZIP"; then
|
||||
unzip -o /root/$SLIVER_SERVER_ZIP -d /root
|
||||
rm -f /root/$SLIVER_SERVER_ZIP
|
||||
else
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if test -f "$HOME/$SLIVER_SERVER"; then
|
||||
chmod 755 $HOME/$SLIVER_SERVER
|
||||
$HOME/$SLIVER_SERVER unpack --force
|
||||
if test -f "/root/$SLIVER_SERVER"; then
|
||||
chmod 755 /root/$SLIVER_SERVER
|
||||
/root/$SLIVER_SERVER unpack --force
|
||||
else
|
||||
exit 3
|
||||
fi
|
||||
|
||||
if test -f "$SLIVER_CLIENT_ZIP"; then
|
||||
unzip -o $SLIVER_CLIENT_ZIP -d $HOME
|
||||
rm -f $SLIVER_CLIENT_ZIP
|
||||
if test -f "/root/$SLIVER_CLIENT_ZIP"; then
|
||||
unzip -o /root/$SLIVER_CLIENT_ZIP -d /root
|
||||
rm -f /root/$SLIVER_CLIENT_ZIP
|
||||
else
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if test -f "$HOME/$SLIVER_CLIENT"; then
|
||||
chmod 755 $HOME/$SLIVER_CLIENT
|
||||
cp -vv $HOME/$SLIVER_CLIENT /usr/local/bin/sliver
|
||||
if test -f "/root/$SLIVER_CLIENT"; then
|
||||
chmod 755 /root/$SLIVER_CLIENT
|
||||
cp -vv /root/$SLIVER_CLIENT /usr/local/bin/sliver
|
||||
chmod 755 /usr/local/bin/sliver
|
||||
else
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# systemd
|
||||
curl -o ./sliver.service https://sliver.sh/sliver.service
|
||||
cp ./sliver.service /etc/systemd/system/sliver.service
|
||||
echo "Configuring systemd service ..."
|
||||
cat > /etc/systemd/system/sliver.service <<-EOF
|
||||
[Unit]
|
||||
Description=Sliver
|
||||
After=network.target
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
User=root
|
||||
ExecStart=/root/sliver-server daemon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
chown root:root /etc/systemd/system/sliver.service
|
||||
chmod 600 /etc/systemd/system/sliver.service
|
||||
systemctl start sliver
|
||||
rm -f ./sliver.service
|
||||
|
||||
# generate local configs
|
||||
mkdir -p $HOME/.sliver-client/configs
|
||||
/root/sliver-server operator --name $USER --lhost localhost --save $HOME/.sliver-client/configs
|
||||
chown -R $USER:$USER $HOME/.sliver-client/
|
||||
# Generate local configs
|
||||
echo "Generating operator configs ..."
|
||||
mkdir -p /root/.sliver-client/configs
|
||||
/root/sliver-server operator --name root --lhost localhost --save /root/.sliver-client/configs
|
||||
chown -R root:root /root/.sliver-client/
|
||||
|
||||
USER_DIRS=(/home/*)
|
||||
for USER_DIR in "${USER_DIRS[@]}"; do
|
||||
USER=$(basename $USER_DIR)
|
||||
if id -u $USER >/dev/null 2>&1; then
|
||||
mkdir -p $USER_DIR/.sliver-client/configs
|
||||
/root/sliver-server operator --name $USER --lhost localhost --save $USER_DIR/.sliver-client/configs
|
||||
chown -R $USER:$USER $USER_DIR/.sliver-client/
|
||||
fi
|
||||
done
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
@@ -97,7 +98,8 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
dir, files, err := getDirList(dirListReq.Path)
|
||||
|
||||
// Convert directory listing to protobuf
|
||||
dirList := &sliverpb.Ls{Path: dir}
|
||||
timezone, offset := time.Now().Zone()
|
||||
dirList := &sliverpb.Ls{Path: dir, Timezone: timezone, TimezoneOffset: int32(offset)}
|
||||
if err == nil {
|
||||
dirList.Exists = true
|
||||
} else {
|
||||
@@ -109,6 +111,13 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -543,7 +543,7 @@ func getPrivsHandler(data []byte, resp RPCResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
privsInfo, err := priv.GetPrivs()
|
||||
privsInfo, integrity, err := priv.GetPrivs()
|
||||
|
||||
response_data := make([]*sliverpb.WindowsPrivilegeEntry, len(privsInfo))
|
||||
|
||||
@@ -567,8 +567,9 @@ func getPrivsHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
// Package up the response
|
||||
getPrivsResp := &sliverpb.GetPrivs{
|
||||
PrivInfo: response_data,
|
||||
Response: &commonpb.Response{},
|
||||
PrivInfo: response_data,
|
||||
ProcessIntegrity: integrity,
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -42,7 +42,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
THREAD_ALL_ACCESS = windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xffff
|
||||
THREAD_ALL_ACCESS = windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xffff
|
||||
SECURITY_MANDATORY_LOW_RID = 0x00001000
|
||||
SECURITY_MANDATORY_MEDIUM_RID = 0x00002000
|
||||
SECURITY_MANDATORY_HIGH_RID = 0x00003000
|
||||
SECURITY_MANDATORY_SYSTEM_RID = 0x00004000
|
||||
)
|
||||
|
||||
type PrivilegeInfo struct {
|
||||
@@ -340,6 +344,60 @@ func GetSystem(data []byte, hostingProcess string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func getProcessIntegrityLevel(processToken syscall.Token) (string, error) {
|
||||
// A place to put the size of the token integrity information
|
||||
var tokenIntegrityBufferSize uint32
|
||||
|
||||
// Determine the integrity of the process
|
||||
syscall.GetTokenInformation(processToken, windows.TokenIntegrityLevel, nil, 0, &tokenIntegrityBufferSize)
|
||||
|
||||
if tokenIntegrityBufferSize < 4 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("TokenIntegrityBuffer is too small (must be at least 4 bytes)")
|
||||
// {{end}}
|
||||
return "Unknown", nil
|
||||
}
|
||||
|
||||
tokenIntegrityBuffer := make([]byte, tokenIntegrityBufferSize)
|
||||
|
||||
err := syscall.GetTokenInformation(processToken,
|
||||
syscall.TokenIntegrityLevel,
|
||||
&tokenIntegrityBuffer[0],
|
||||
tokenIntegrityBufferSize,
|
||||
&tokenIntegrityBufferSize,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Error in call to GetTokenInformation (integrity): ", err)
|
||||
// {{end}}
|
||||
return "", err
|
||||
}
|
||||
|
||||
/*
|
||||
When calling GetTokenInformation with a type of TokenIntegrityLevel, the structure we get back
|
||||
is a TOKEN_MANDATORY_LABEL (https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-token_mandatory_label)
|
||||
which has one SID_AND_ATTRIBUTES structure (https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid_and_attributes)
|
||||
|
||||
We need the last 4 bytes (uint32) from the structure because that contains the attributes. The attributes
|
||||
tell us what privilege level we are operating at.
|
||||
*/
|
||||
|
||||
var privilegeLevel uint32 = binary.LittleEndian.Uint32(tokenIntegrityBuffer[tokenIntegrityBufferSize-4:])
|
||||
|
||||
if privilegeLevel < SECURITY_MANDATORY_LOW_RID {
|
||||
return "Untrusted", nil
|
||||
} else if privilegeLevel < SECURITY_MANDATORY_MEDIUM_RID {
|
||||
return "Low", nil
|
||||
} else if privilegeLevel >= SECURITY_MANDATORY_MEDIUM_RID && privilegeLevel < SECURITY_MANDATORY_HIGH_RID {
|
||||
return "Medium", nil
|
||||
} else if privilegeLevel >= SECURITY_MANDATORY_HIGH_RID {
|
||||
return "High", nil
|
||||
}
|
||||
|
||||
return "Unknown", nil
|
||||
}
|
||||
|
||||
func lookupPrivilegeNameByLUID(luid uint64) (string, string, error) {
|
||||
/*
|
||||
We will need the LookupPrivilegeNameW and LookupPrivilegeDisplayNameW functions
|
||||
@@ -348,6 +406,8 @@ func lookupPrivilegeNameByLUID(luid uint64) (string, string, error) {
|
||||
|
||||
Defined these syscalls in implant/sliver/syscalls/syscalls_windows.go and generated them with
|
||||
mkwinsyscall, so we are good to go.
|
||||
|
||||
mkwinsyscall -output zsyscalls_windows.go syscalls_windows.go
|
||||
*/
|
||||
|
||||
// Allocate 256 wide unicode characters (uint16) for the both names (255 characters plus a null terminator)
|
||||
@@ -381,7 +441,7 @@ func lookupPrivilegeNameByLUID(luid uint64) (string, string, error) {
|
||||
return syscall.UTF16ToString(nameBuffer), syscall.UTF16ToString(displayNameBuffer), nil
|
||||
}
|
||||
|
||||
func GetPrivs() ([]PrivilegeInfo, error) {
|
||||
func GetPrivs() ([]PrivilegeInfo, string, error) {
|
||||
// A place to store the process token
|
||||
var tokenHandle syscall.Token
|
||||
|
||||
@@ -395,7 +455,7 @@ func GetPrivs() ([]PrivilegeInfo, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Could not get a handle for the current process: ", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Get the process token from the current process
|
||||
@@ -405,7 +465,7 @@ func GetPrivs() ([]PrivilegeInfo, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Could not open process token: ", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Get the size of the token information buffer so we know how large of a buffer to allocate
|
||||
@@ -426,9 +486,9 @@ func GetPrivs() ([]PrivilegeInfo, error) {
|
||||
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Error in call to GetTokenInformation: ", err)
|
||||
log.Println("Error in call to GetTokenInformation (privileges): ", err)
|
||||
// {{end}}
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// The first 32 bits is the number of privileges in the structure
|
||||
@@ -439,7 +499,7 @@ func GetPrivs() ([]PrivilegeInfo, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Could not read the number of privileges from the token information.")
|
||||
// {{end}}
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -468,7 +528,7 @@ func GetPrivs() ([]PrivilegeInfo, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Could not read the LUID from the binary stream: ", err)
|
||||
// {{end}}
|
||||
return privInfo, err
|
||||
return privInfo, "", err
|
||||
}
|
||||
|
||||
// Read the attributes
|
||||
@@ -477,7 +537,7 @@ func GetPrivs() ([]PrivilegeInfo, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Could not read the attributes from the binary stream: ", err)
|
||||
// {{end}}
|
||||
return privInfo, err
|
||||
return privInfo, "", err
|
||||
}
|
||||
|
||||
currentPrivInfo.Name, currentPrivInfo.Description, err = lookupPrivilegeNameByLUID(luid)
|
||||
@@ -485,7 +545,7 @@ func GetPrivs() ([]PrivilegeInfo, error) {
|
||||
// {{if .Config.Debug}}
|
||||
log.Println("Could not get privilege info based on the LUID: ", err)
|
||||
// {{end}}
|
||||
return privInfo, err
|
||||
return privInfo, "", err
|
||||
}
|
||||
|
||||
// Figure out the attributes
|
||||
@@ -497,5 +557,12 @@ func GetPrivs() ([]PrivilegeInfo, error) {
|
||||
privInfo[index] = currentPrivInfo
|
||||
}
|
||||
|
||||
return privInfo, nil
|
||||
// Get the process integrity before we leave
|
||||
integrity, err := getProcessIntegrityLevel(tokenHandle)
|
||||
|
||||
if err != nil {
|
||||
return privInfo, "Could not determine integrity level", err
|
||||
}
|
||||
|
||||
return privInfo, integrity, nil
|
||||
}
|
||||
+772
-722
File diff suppressed because it is too large.
Load diff
@@ -120,6 +120,8 @@ message Ls {
|
||||
string Path = 1;
|
||||
bool Exists = 2;
|
||||
repeated FileInfo Files = 3;
|
||||
string timezone = 4;
|
||||
int32 timezoneOffset = 5;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
@@ -128,6 +130,8 @@ message FileInfo {
|
||||
string Name = 1;
|
||||
bool IsDir = 2;
|
||||
int64 Size = 3;
|
||||
int64 ModTime = 4;
|
||||
string Mode = 5;
|
||||
}
|
||||
|
||||
message CdReq {
|
||||
@@ -826,6 +830,7 @@ message WindowsPrivilegeEntry {
|
||||
|
||||
message GetPrivs {
|
||||
repeated WindowsPrivilegeEntry PrivInfo = 1;
|
||||
string ProcessIntegrity = 2;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
Reference in new issue
Block a user