Skip to content

Commit

Permalink
Adding getprivs command for Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
RafBishopFox committed Jul 25, 2021
1 parent 7a38ddf commit e6d1041
Show file tree
Hide file tree
Showing 17 changed files with 1,243 additions and 651 deletions.
18 changes: 18 additions & 0 deletions client/command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2168,4 +2168,22 @@ func BindCommands(con *console.SliverConsoleClient) {
},
}
con.App.AddCommand(dllhijackCmd)

// [ Get Privs ] -----------------------------------------------------------------
getprivsCmd := &grumble.Command{
Name: consts.GetPrivsStr,
Help: "Get current privileges (Windows)",
LongHelp: help.GetHelpFor([]string{consts.GetPrivsStr}),
HelpGroup: consts.SliverWinHelpGroup,
Run: func(ctx *grumble.Context) error {
con.Println()
privilege.GetPrivsCmd(ctx, con)
con.Println()
return nil
},
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
}
con.App.AddCommand(getprivsCmd)
}
4 changes: 4 additions & 0 deletions client/command/help/long-help.go
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,10 @@ dllhijack --reference-path c:\\windows\\system32\\msasn1.dll --profile dll c:\\u
# Use a local DLL as the reference DLL
dllhijack --reference-path c:\\windows\\system32\\msasn1.dll --reference-file /tmp/msasn1.dll.orig --profile dll c:\\users\\bob\\appdata\\slack\\app-4.18.0\\msasn1.dll
`

getPrivsHelp = `[[.Bold]]Command:[[.Normal]] getprivs
[[.Bold]]About:[[.Normal]] Get privilege information for the current process (Windows only).
`
)

Expand Down
99 changes: 99 additions & 0 deletions client/command/privilege/getprivs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package privilege

import (
"context"

"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/desertbit/grumble"
)

/*
Sliver Implant Framework
Copyright (C) 2021 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/>.
*/

func GetPrivsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveSession.GetInteractive()
if session == nil {
return
}

if session.OS != "windows" {
con.PrintErrorf("Command not supported on this operating system.")
return
}

privs, err := con.Rpc.GetPrivs(context.Background(), &sliverpb.GetPrivsReq{
Request: con.ActiveSession.Request(ctx),
})

if err != nil {
con.PrintErrorf("%s\n", err)
return
}

// Response is the Envelope (see RPC API), Err is part of it.
if privs.Response != nil && privs.Response.Err != "" {
con.PrintErrorf("NOTE: Information may be incomplete due to an error:\n")
con.PrintErrorf("%s\n", privs.Response.Err)
}

if privs.PrivInfo == nil {
return
}

// To make things look pretty, figure out the longest name and description
// for column width
var nameColumnWidth int = 0
var descriptionColumnWidth int = 0

for _, entry := range privs.PrivInfo {
if len(entry.Name) > nameColumnWidth {
nameColumnWidth = len(entry.Name)
}
if len(entry.Description) > descriptionColumnWidth {
descriptionColumnWidth = len(entry.Description)
}
}

// Give one more space
nameColumnWidth += 1
descriptionColumnWidth += 1

con.Println("Privilege Information for Current Process")
con.Println("-----------------------------------------")
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)
if entry.Enabled {
con.Printf("Enabled")
} else {
con.Printf("Disabled")
}
if entry.EnabledByDefault {
con.Printf(", Enabled by Default")
}
if entry.Removed {
con.Printf(", Removed")
}
if entry.UsedForAccess {
con.Printf(", Used for Access")
}
con.Printf(")\n")
}
}
2 changes: 2 additions & 0 deletions client/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ const (
ReactionStr = "reaction"

LicensesStr = "licenses"

GetPrivsStr = "getprivs"
)

// Groups
Expand Down
45 changes: 45 additions & 0 deletions implant/sliver/handlers/handlers_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ var (
sliverpb.MsgSetEnvReq: setEnvHandler,
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
sliverpb.MsgExecuteTokenReq: executeTokenHandler,
sliverpb.MsgGetPrivsReq: getPrivsHandler,

// Platform specific
sliverpb.MsgIfconfigReq: ifconfigHandler,
Expand Down Expand Up @@ -493,3 +494,47 @@ func regCreateKeyHandler(data []byte, resp RPCResponse) {
data, err = proto.Marshal(createResp)
resp(data, err)
}

func getPrivsHandler(data []byte, resp RPCResponse) {
createReq := &sliverpb.GetPrivsReq{}

err := proto.Unmarshal(data, createReq)
if err != nil {
return
}

privsInfo, err := priv.GetPrivs()

response_data := make([]*sliverpb.WindowsPrivilegeEntry, len(privsInfo))

/*
Translate the PrivilegeInfo structs into
sliverpb.WindowsPrivilegeEntry structs and put them in the data
that will go back to the server / client
*/
for index, entry := range privsInfo {
var currentEntry sliverpb.WindowsPrivilegeEntry

currentEntry.Name = entry.Name
currentEntry.Description = entry.Description
currentEntry.Enabled = entry.Enabled
currentEntry.EnabledByDefault = entry.EnabledByDefault
currentEntry.Removed = entry.Removed
currentEntry.UsedForAccess = entry.UsedForAccess

response_data[index] = &currentEntry
}

// Package up the response
getPrivsResp := &sliverpb.GetPrivs{
PrivInfo: response_data,
Response: &commonpb.Response{},
}

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

data, err = proto.Marshal(getPrivsResp)
resp(data, err)
}
171 changes: 171 additions & 0 deletions implant/sliver/priv/priv_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"log"

// {{end}}
"bytes"
"encoding/binary"
"fmt"
"os/exec"
"runtime"
Expand All @@ -43,6 +45,15 @@ const (
THREAD_ALL_ACCESS = windows.STANDARD_RIGHTS_REQUIRED | windows.SYNCHRONIZE | 0xffff
)

type PrivilegeInfo struct {
Name string
Description string
Enabled bool
EnabledByDefault bool
Removed bool
UsedForAccess bool
}

var CurrentToken windows.Token

func SePrivEnable(s string) error {
Expand Down Expand Up @@ -328,3 +339,163 @@ func GetSystem(data []byte, hostingProcess string) (err error) {
}
return
}

func lookupPrivilegeNameByLUID(luid uint64) (string, string, error) {
/*
We will need the LookupPrivilegeNameW and LookupPrivilegeDisplayNameW functions
https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupprivilegenamew
https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupprivilegedisplaynamew
Defined these syscalls in implant/sliver/syscalls/syscalls_windows.go and generated them with
mkwinsyscall, so we are good to go.
*/

// Allocate 256 wide unicode characters (uint16) for the both names (255 characters plus a null terminator)
nameBuffer := make([]uint16, 256)
nameBufferSize := uint32(len(nameBuffer))
displayNameBuffer := make([]uint16, 256)
displayNameBufferSize := uint32(len(displayNameBuffer))

// A blank string for the system name tells the call to use the local machine
systemName := ""

/*
A language ID that gets returned from LookupPrivilegeDisplayNameW
We do not need it for anything, but we still need to provide it
*/
var langID uint32

err := syscalls.LookupPrivilegeNameW(systemName, &luid, &nameBuffer[0], &nameBufferSize)

if err != nil {
return "", "", err
}

err = syscalls.LookupPrivilegeDisplayNameW(systemName, &nameBuffer[0], &displayNameBuffer[0], &displayNameBufferSize, &langID)

if err != nil {
// We already got the privilege name, so we might as well return that
return syscall.UTF16ToString(nameBuffer), "", err
}

return syscall.UTF16ToString(nameBuffer), syscall.UTF16ToString(displayNameBuffer), nil
}

func GetPrivs() ([]PrivilegeInfo, error) {
// A place to store the process token
var tokenHandle syscall.Token

// A place to put the size of the token information
var tokenInfoBufferSize uint32

// Get a handle for the current process
currentProcHandle, err := syscall.GetCurrentProcess()

if err != nil {
// {{if .Config.Debug}}
log.Println("Could not get a handle for the current process: ", err)
// {{end}}
return nil, err
}

// Get the process token from the current process
err = syscall.OpenProcessToken(currentProcHandle, syscall.TOKEN_QUERY, &tokenHandle)

if err != nil {
// {{if .Config.Debug}}
log.Println("Could not open process token: ", err)
// {{end}}
return nil, err
}

// Get the size of the token information buffer so we know how large of a buffer to allocate
// This produces an error about a data area passed to the syscall being too small, but
// we do not care about that because we just want to know how big of a buffer to make
syscall.GetTokenInformation(tokenHandle, syscall.TokenPrivileges, nil, 0, &tokenInfoBufferSize)

// Make the buffer and get token information
// Using a bytes Buffer so that we can Read from it later
tokenInfoBuffer := bytes.NewBuffer(make([]byte, tokenInfoBufferSize))

err = syscall.GetTokenInformation(tokenHandle,
syscall.TokenPrivileges,
&tokenInfoBuffer.Bytes()[0],
uint32(tokenInfoBuffer.Len()),
&tokenInfoBufferSize,
)

if err != nil {
// {{if .Config.Debug}}
log.Println("Error in call to GetTokenInformation: ", err)
// {{end}}
return nil, err
}

// The first 32 bits is the number of privileges in the structure
var privilegeCount uint32
err = binary.Read(tokenInfoBuffer, binary.LittleEndian, &privilegeCount)

if err != nil {
// {{if .Config.Debug}}
log.Println("Could not read the number of privileges from the token information.")
// {{end}}
return nil, err
}

/*
The remaining bytes contain the privileges themselves
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]
Structure of the array: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-luid_and_attributes
*/

privInfo := make([]PrivilegeInfo, int(privilegeCount))

for index := 0; index < int(privilegeCount); index++ {
// Iterate over the privileges and make sense of them
// In case of errors, return what we have so far and the error

// LUIDs consist of a DWORD and a LONG
var luid uint64

// Attributes are up to 32 one bit flags, so a uint32 is good for that
var attributes uint32

var currentPrivInfo PrivilegeInfo

// Read the LUID
err = binary.Read(tokenInfoBuffer, binary.LittleEndian, &luid)
if err != nil {
// {{if .Config.Debug}}
log.Println("Could not read the LUID from the binary stream: ", err)
// {{end}}
return privInfo, err
}

// Read the attributes
err = binary.Read(tokenInfoBuffer, binary.LittleEndian, &attributes)
if err != nil {
// {{if .Config.Debug}}
log.Println("Could not read the attributes from the binary stream: ", err)
// {{end}}
return privInfo, err
}

currentPrivInfo.Name, currentPrivInfo.Description, err = lookupPrivilegeNameByLUID(luid)
if err != nil {
// {{if .Config.Debug}}
log.Println("Could not get privilege info based on the LUID: ", err)
// {{end}}
return privInfo, err
}

// Figure out the attributes
currentPrivInfo.EnabledByDefault = (attributes & windows.SE_PRIVILEGE_ENABLED_BY_DEFAULT) > 0
currentPrivInfo.UsedForAccess = (attributes & windows.SE_PRIVILEGE_USED_FOR_ACCESS) > 0
currentPrivInfo.Enabled = (attributes & windows.SE_PRIVILEGE_ENABLED) > 0
currentPrivInfo.Removed = (attributes & windows.SE_PRIVILEGE_REMOVED) > 0

privInfo[index] = currentPrivInfo
}

return privInfo, nil
}
2 changes: 2 additions & 0 deletions implant/sliver/syscalls/syscalls_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ package syscalls
//sys SelectObject(hdc windows.Handle, h windows.Handle) (HGDIOBJ windows.Handle, err error) = Gdi32.SelectObject
//sys BitBlt(hdc windows.Handle, x uint32, y uint32, cx uint32, cy uint32, hdcSrc windows.Handle, x1 uint32, y1 uint32, rop int32) (BOOL int, err error) = Gdi32.BitBlt
//sys GetDIBits(hdc windows.Handle, hbm windows.Handle, start uint32, cLines uint32, lpvBits uintptr, lpbmi uintptr, usage int) (ret int, err error) = Gdi32.GetDIBits
//sys LookupPrivilegeNameW(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
//sys LookupPrivilegeDisplayNameW(systemName string, privilegeName *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
Loading

0 comments on commit e6d1041

Please sign in to comment.