Skip to content

Commit

Permalink
Updated version of go-smb lib to v0.5.7 and added printout of AES key…
Browse files Browse the repository at this point in the history
…s for the machine account which can be used for Kerberos authentication
  • Loading branch information
jfjallid committed Dec 19, 2024
1 parent e307524 commit c446b77
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 5 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/jfjallid/go-secdump
go 1.19

require (
github.com/jfjallid/go-smb v0.5.5
github.com/jfjallid/go-smb v0.5.7
github.com/jfjallid/golog v0.3.3
golang.org/x/crypto v0.6.0
golang.org/x/net v0.7.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/jfjallid/go-smb v0.5.5 h1:oY7MGmmziH0HzeUBF9sPNZOBjoWHwGON1sCazGJVAhU=
github.com/jfjallid/go-smb v0.5.5/go.mod h1:gCorMd5NXhyMR3f1/x+qTZRSN35/6iY5TuqFIWb+Ovg=
github.com/jfjallid/go-smb v0.5.7 h1:2lcXR9TNCfVuLYv2/Pyj6t6Zi6hSLe5pG8Kow9tODAo=
github.com/jfjallid/go-smb v0.5.7/go.mod h1:gCorMd5NXhyMR3f1/x+qTZRSN35/6iY5TuqFIWb+Ovg=
github.com/jfjallid/gofork v1.7.6 h1:OYyS2HH597860gkDxxjNsl+NZRxoAnuRI6ZsP++kYKE=
github.com/jfjallid/gofork v1.7.6/go.mod h1:r1EH4W9KY5iqtiGhAupnbzMRONsLDApdJ9EZH5NWFSc=
github.com/jfjallid/gokrb5/v8 v8.4.4 h1:log4i4lIQDOKe/RHWTHdTGeeJTYMW/+M07JgHAiE0as=
Expand Down
7 changes: 6 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import (
)

var log = golog.Get("")
var release string = "0.3.1"
var release string = "0.4.0"

var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

Expand Down Expand Up @@ -930,6 +930,11 @@ func main() {
}
}

if aesKey != "" && !kerberos {
fmt.Println("Must use Kerberos auth (-k) when using --aes-key")
flag.Usage()
}

if noPass {
password = ""
hashBytes = nil
Expand Down
40 changes: 39 additions & 1 deletion sam.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,22 @@ func parseSecret(rpccon *msrrp.RPCCon, base []byte, name string, secretItem []by
h := md4.New()
h.Write(secretItem)
printname := "$MACHINE.ACC"
secret = fmt.Sprintf("$MACHINE.ACC: 0x%x", h.Sum(nil))
secret = fmt.Sprintf("$MACHINE.ACC (NT Hash): %x", h.Sum(nil))
result.secrets = append(result.secrets, secret)
// Calculate AES128 and AES256 keys from plaintext passwords
hostname, domain, err := getHostnameAndDomain(rpccon, base)
if err != nil {
log.Errorln(err)
// Skip calculation of AES Keys if request failed or if domain is empty
} else if domain != "" {
aes128Key, aes256Key, err := CalcMachineAESKeys(hostname, domain, secretItem)
if err != nil {
log.Errorln(err)
} else {
result.secrets = append(result.secrets, fmt.Sprintf("%s:AES_128_key:%x", printname, aes128Key))
result.secrets = append(result.secrets, fmt.Sprintf("%s:AES_256_key:%x", printname, aes256Key))
}
}
// Always print plaintext anyway since this may be needed for some popular usecases
extrasecret = fmt.Sprintf("%s:plain_password_hex:%x", printname, secretItem)
result.extraSecret = extrasecret
Expand Down Expand Up @@ -1188,3 +1202,27 @@ func GetOSVersionBuild(rpccon *msrrp.RPCCon, base []byte) (build int, version fl

return
}

func getHostnameAndDomain(rpccon *msrrp.RPCCon, base []byte) (hostname, domain string, err error) {
hSubKey, err := rpccon.OpenSubKey(base, `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`)
if err != nil {
log.Noticef("Failed to open registry key Parameters with error: %v\n", err)
return
}
defer func(h []byte) {
rpccon.CloseKeyHandle(h)
}(hSubKey)

domain, err = rpccon.QueryValueString(hSubKey, "Domain")
if err != nil {
log.Errorln(err)
return
}

hostname, err = rpccon.QueryValueString(hSubKey, "Hostname")
if err != nil {
log.Errorln(err)
return
}
return
}
88 changes: 88 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ import (
"crypto/des"
"crypto/md5"
"crypto/rc4"
"crypto/sha1"
"crypto/sha256"
"encoding/binary"
"fmt"
"golang.org/x/crypto/pbkdf2"
"math/bits"
"strconv"
"strings"
"unicode/utf16"
)

const (
Expand All @@ -52,6 +57,8 @@ const (
WIN11
)

var aes256_constant = []byte{0x6B, 0x65, 0x72, 0x62, 0x65, 0x72, 0x6F, 0x73, 0x7B, 0x9B, 0x5B, 0x2B, 0x93, 0x13, 0x2B, 0x93, 0x5C, 0x9B, 0xDC, 0xDA, 0xD9, 0x5C, 0x98, 0x99, 0xC4, 0xCA, 0xE4, 0xDE, 0xE6, 0xD6, 0xCA, 0xE4}

var osNameMap = map[byte]string{
WIN_UNKNOWN: "Windows Unknown",
WINXP: "Windows XP",
Expand Down Expand Up @@ -286,3 +293,84 @@ func DecryptAESHash(doubleEncHash, encHashIV, syskey []byte, rid uint32) (ntHash
ntHash, err = decryptNTHash(encHash, ridBytes)
return
}

func calcAES256Key(key []byte) (result []byte, err error) {
key1 := make([]byte, 32)
key2 := make([]byte, 32)
block, err := aes.NewCipher(key)
if err != nil {
log.Errorf("Failed to create new AES cipher with error: %s\n", err)
return
}
iv := make([]byte, 16)
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(key1, aes256_constant)

block, err = aes.NewCipher(key)
if err != nil {
log.Errorf("Failed to create the second new AES cipher with error: %s\n", err)
return
}
mode = cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(key2, key1)
result = append(key1[:16], key2[:16]...)
return
}

func calcAES128Key(key []byte) (result []byte, err error) {
result = make([]byte, 16)
block, err := aes.NewCipher(key)
if err != nil {
log.Errorf("Failed to create new AES cipher with error: %s\n", err)
return
}
iv := make([]byte, 16)
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(result, aes256_constant[:16])
return
}

func unicodeHexToUtf8(utf16Bytes []byte) (result string, err error) {
if len(utf16Bytes)%2 > 0 {
err = fmt.Errorf("Unicode (UTF 16 LE) specified, but uneven data length")
log.Errorln(err)
return
}

utf16Data := make([]uint16, len(utf16Bytes)/2)
for i := 0; i < len(utf16Bytes); i += 2 {
utf16Data[i/2] = uint16(utf16Bytes[i]) | uint16(utf16Bytes[i+1])<<8
}

utf8Str := string(utf16.Decode(utf16Data))

return utf8Str, nil
}

func CalcMachineAESKeys(hostname, domain string, hexPass []byte) (aes128Key, aes256Key []byte, err error) {
const ITERATIONS int = 4096 // Default for Active Directory

domain = strings.ToUpper(domain)
salt := fmt.Sprintf("%shost%s.%s", domain, strings.ToLower(hostname), strings.ToLower(domain))

val, err := unicodeHexToUtf8(hexPass)
if err != nil {
log.Errorf("Failed to decode the MachineAccount's Unicode password: %s\n", err)
return
}
passBytes := []byte(val)

dk256 := pbkdf2.Key(passBytes, []byte(salt), ITERATIONS, 32, sha1.New)
dk128 := dk256[:16]
aes256Key, err = calcAES256Key(dk256)
if err != nil {
log.Errorln(err)
return
}
aes128Key, err = calcAES128Key(dk128)
if err != nil {
log.Errorln(err)
return
}
return
}

0 comments on commit c446b77

Please sign in to comment.